Move codeeditor to folder
This commit is contained in:
parent
91ac77a058
commit
d266882927
5 changed files with 4 additions and 4 deletions
272
pglab/codeeditor/CodeEditor.cpp
Normal file
272
pglab/codeeditor/CodeEditor.cpp
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
#include "CodeEditor.h"
|
||||
#include "EditorGutter.h"
|
||||
#include <QPainter>
|
||||
#include <QTextBlock>
|
||||
#include <QDebug>
|
||||
//
|
||||
// Adapted from codeeditor example
|
||||
// http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
|
||||
//
|
||||
// Used term gutter as I want to expand it to do other things to like error
|
||||
// position marking.
|
||||
//
|
||||
|
||||
CodeEditor::CodeEditor(QWidget *parent)
|
||||
: QPlainTextEdit(parent)
|
||||
, gutterArea(new EditorGutter(this))
|
||||
{
|
||||
connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateGutterAreaWidth(int)));
|
||||
connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateGutterArea(QRect,int)));
|
||||
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
|
||||
connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
|
||||
|
||||
setWordWrapMode(QTextOption::NoWrap);
|
||||
|
||||
updateGutterAreaWidth(0);
|
||||
setTabSize(4);
|
||||
highlightCurrentLine();
|
||||
}
|
||||
|
||||
int CodeEditor::gutterAreaWidth()
|
||||
{
|
||||
int digits = 1;
|
||||
int max = qMax(1, blockCount());
|
||||
while (max >= 10) {
|
||||
max /= 10;
|
||||
++digits;
|
||||
}
|
||||
|
||||
int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * (digits + 2);
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
void CodeEditor::updateGutterAreaWidth(int /* newBlockCount */)
|
||||
{
|
||||
setViewportMargins(gutterAreaWidth(), 0, 0, 0);
|
||||
}
|
||||
|
||||
void CodeEditor::updateGutterArea(const QRect &rect, int dy)
|
||||
{
|
||||
if (dy)
|
||||
gutterArea->scroll(0, dy);
|
||||
else
|
||||
gutterArea->update(0, rect.y(), gutterArea->width(), rect.height());
|
||||
|
||||
if (rect.contains(viewport()->rect()))
|
||||
updateGutterAreaWidth(0);
|
||||
}
|
||||
|
||||
void CodeEditor::resizeEvent(QResizeEvent *e)
|
||||
{
|
||||
QPlainTextEdit::resizeEvent(e);
|
||||
|
||||
QRect cr = contentsRect();
|
||||
gutterArea->setGeometry(QRect(cr.left(), cr.top(), gutterAreaWidth(), cr.height()));
|
||||
}
|
||||
|
||||
void CodeEditor::highlightCurrentLine()
|
||||
{
|
||||
QTextEdit::ExtraSelection selection;
|
||||
QColor lineColor = QColor(Qt::yellow).lighter(160);
|
||||
selection.format.setBackground(lineColor);
|
||||
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
||||
selection.cursor = textCursor();
|
||||
selection.cursor.clearSelection();
|
||||
currentLine = selection;
|
||||
|
||||
updateExtraSelections();
|
||||
}
|
||||
|
||||
void CodeEditor::onTextChanged()
|
||||
{
|
||||
clearErrorMarkers();
|
||||
}
|
||||
|
||||
void CodeEditor::addErrorMarker(int position, int length)
|
||||
{
|
||||
QTextEdit::ExtraSelection selection;
|
||||
QColor lineColor = QColor(Qt::red).lighter(160);
|
||||
selection.format.setBackground(lineColor);
|
||||
selection.format.setFontItalic(true);
|
||||
selection.cursor = textCursor();
|
||||
selection.cursor.setPosition(position);
|
||||
int lineno = selection.cursor.blockNumber();
|
||||
errorLines.insert(lineno);
|
||||
selection.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, length);
|
||||
errorMarkers.append(selection);
|
||||
|
||||
update();
|
||||
|
||||
updateExtraSelections();
|
||||
}
|
||||
|
||||
void CodeEditor::clearErrorMarkers()
|
||||
{
|
||||
errorMarkers.clear();
|
||||
errorLines.clear();
|
||||
update();
|
||||
updateExtraSelections();
|
||||
}
|
||||
|
||||
void CodeEditor::updateExtraSelections()
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> extraSelections;
|
||||
extraSelections.append(currentLine);
|
||||
for (auto&& e : errorMarkers)
|
||||
extraSelections.append(e);
|
||||
|
||||
setExtraSelections(extraSelections);
|
||||
}
|
||||
|
||||
void CodeEditor::gutterAreaPaintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter(gutterArea);
|
||||
painter.fillRect(event->rect(), Qt::lightGray);
|
||||
|
||||
QTextBlock block = firstVisibleBlock();
|
||||
int blockNumber = block.blockNumber();
|
||||
int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
|
||||
int bottom = top + (int) blockBoundingRect(block).height();
|
||||
|
||||
// We will now loop through all visible lines and paint the line numbers in the
|
||||
// extra area for each line. Notice that in a plain text edit each line will
|
||||
// consist of one QTextBlock; though, if line wrapping is enabled, a line may span
|
||||
// several rows in the text edit's viewport.
|
||||
//
|
||||
// We get the top and bottom y-coordinate of the first text block, and adjust these
|
||||
// values by the height of the current text block in each iteration in the loop.
|
||||
while (block.isValid() && top <= event->rect().bottom()) {
|
||||
if (block.isVisible() && bottom >= event->rect().top()) {
|
||||
QString number = QString::number(blockNumber + 1);
|
||||
painter.setPen(Qt::black);
|
||||
painter.drawText(0, top, gutterArea->width(), fontMetrics().height(),
|
||||
Qt::AlignRight, number);
|
||||
|
||||
if (errorLines.count(blockNumber) > 0) {
|
||||
int s = fontMetrics().height() - 8;
|
||||
painter.setBrush(QBrush(Qt::red));
|
||||
painter.drawEllipse(4, top + 4, s, s);
|
||||
}
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
top = bottom;
|
||||
bottom = top + (int) blockBoundingRect(block).height();
|
||||
++blockNumber;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditor::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
auto k = e->key();
|
||||
if (k == Qt::Key_Tab) {
|
||||
// Function returns false if there was no selection to indent
|
||||
if (indentSelection(true))
|
||||
return;
|
||||
}
|
||||
else if (k == Qt::Key_Backtab) {
|
||||
// Function returns false if there was no selection to indent
|
||||
if (indentSelection(false))
|
||||
return;
|
||||
}
|
||||
else if (k == Qt::Key_Equal || k == Qt::Key_Plus) {
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
auto f = font();
|
||||
f.setPointSize(f.pointSize() + 1);
|
||||
setFont(f);
|
||||
}
|
||||
}
|
||||
else if (k == Qt::Key_Minus) {
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
auto f = font();
|
||||
f.setPointSize(f.pointSize() - 1);
|
||||
setFont(f);
|
||||
}
|
||||
}
|
||||
QPlainTextEdit::keyPressEvent(e);
|
||||
}
|
||||
|
||||
bool CodeEditor::indentSelection(bool indent)
|
||||
{
|
||||
auto cursor = textCursor();
|
||||
|
||||
if(!cursor.hasSelection())
|
||||
return false;
|
||||
|
||||
auto first_pos = cursor.anchor();
|
||||
auto end_pos = cursor.position();
|
||||
|
||||
if(first_pos > end_pos)
|
||||
std::swap(first_pos, end_pos);
|
||||
|
||||
cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
|
||||
auto start_block = cursor.block().blockNumber();
|
||||
|
||||
cursor.setPosition(end_pos, QTextCursor::MoveAnchor);
|
||||
auto end_block = cursor.block().blockNumber();
|
||||
if (end_block == start_block)
|
||||
return false;
|
||||
|
||||
cursor.beginEditBlock();
|
||||
cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
|
||||
const auto block_count = end_block - start_block;
|
||||
for(int block = 0; block <= block_count; ++block) {
|
||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
||||
if (indent) {
|
||||
if (m_useTab)
|
||||
cursor.insertText("\t");
|
||||
else
|
||||
cursor.insertText(QString(m_tabSize, ' '));
|
||||
}
|
||||
else {
|
||||
// remove tab if there is a tab
|
||||
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
|
||||
const QString text = cursor.selectedText();
|
||||
int index = 0;
|
||||
int pos = 0;
|
||||
while (pos < m_tabSize && index < text.length()) {
|
||||
QChar c = text[index++];
|
||||
if (c == ' ')
|
||||
++pos;
|
||||
else if (c == '\t')
|
||||
pos = ((pos + m_tabSize) / m_tabSize) * m_tabSize;
|
||||
else {
|
||||
--index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
||||
for (int i = 0; i < index; ++i)
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
|
||||
cursor.removeSelectedText();
|
||||
}
|
||||
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
|
||||
}
|
||||
cursor.endEditBlock();
|
||||
|
||||
cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
|
||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
||||
while(cursor.block().blockNumber() < end_block)
|
||||
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
|
||||
|
||||
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
||||
|
||||
setTextCursor(cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CodeEditor::setFont(const QFont &f)
|
||||
{
|
||||
QWidget::setFont(f);
|
||||
auto orig_tab = m_tabSize;
|
||||
m_tabSize = 0;
|
||||
setTabSize(orig_tab);
|
||||
}
|
||||
|
||||
void CodeEditor::setTabSize(int chars)
|
||||
{
|
||||
m_tabSize = chars;
|
||||
int pixels = fontMetrics().horizontalAdvance(QString(chars, '0'));
|
||||
this->setTabStopDistance(pixels);
|
||||
}
|
||||
55
pglab/codeeditor/CodeEditor.h
Normal file
55
pglab/codeeditor/CodeEditor.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef CODEEDITOR_H
|
||||
#define CODEEDITOR_H
|
||||
|
||||
#include <QPlainTextEdit>
|
||||
#include <set>
|
||||
|
||||
/** This class adds some capabilities to QPlainTextEdit that are useful
|
||||
* in code editor scenarios.
|
||||
*/
|
||||
class CodeEditor : public QPlainTextEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CodeEditor(QWidget *parent = nullptr);
|
||||
|
||||
void gutterAreaPaintEvent(QPaintEvent *event);
|
||||
int gutterAreaWidth();
|
||||
|
||||
void addErrorMarker(int position, int length);
|
||||
void clearErrorMarkers();
|
||||
void setFont(const QFont &);
|
||||
void setTabSize(int chars);
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
private slots:
|
||||
void updateGutterAreaWidth(int newBlockCount);
|
||||
void highlightCurrentLine();
|
||||
void updateGutterArea(const QRect &, int);
|
||||
void onTextChanged();
|
||||
|
||||
|
||||
private:
|
||||
QWidget *gutterArea;
|
||||
|
||||
QTextEdit::ExtraSelection currentLine;
|
||||
QList<QTextEdit::ExtraSelection> errorMarkers;
|
||||
|
||||
std::set<int> errorLines;
|
||||
|
||||
// Settings
|
||||
int m_tabSize = 0; // tabSize in characters
|
||||
bool m_useTab = false;
|
||||
|
||||
void updateExtraSelections();
|
||||
bool indentSelection(bool indent);
|
||||
|
||||
};
|
||||
|
||||
#endif // CODEEDITOR_H
|
||||
19
pglab/codeeditor/EditorGutter.cpp
Normal file
19
pglab/codeeditor/EditorGutter.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include "EditorGutter.h"
|
||||
#include "CodeEditor.h"
|
||||
|
||||
EditorGutter::EditorGutter(CodeEditor *editor)
|
||||
: QWidget(editor)
|
||||
, codeEditor(editor)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QSize EditorGutter::sizeHint() const
|
||||
{
|
||||
return QSize(codeEditor->gutterAreaWidth(), 0);
|
||||
}
|
||||
|
||||
void EditorGutter::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
codeEditor->gutterAreaPaintEvent(event);
|
||||
}
|
||||
28
pglab/codeeditor/EditorGutter.h
Normal file
28
pglab/codeeditor/EditorGutter.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef EDITORGUTTER_H
|
||||
#define EDITORGUTTER_H
|
||||
|
||||
#include <QWidget>
|
||||
//#include <QPlainTextEdit>
|
||||
|
||||
class CodeEditor;
|
||||
|
||||
class EditorGutter : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EditorGutter(CodeEditor *editor);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
CodeEditor *codeEditor;
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // EDITORGUTTER_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue