Refactor painting of editor gutter

This commit is contained in:
eelke 2022-04-10 10:29:40 +02:00
parent e082a5731d
commit a5563949e5
7 changed files with 188 additions and 85 deletions

View file

@ -120,45 +120,17 @@ void CodeEditor::updateExtraSelections()
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)
void CodeEditor::drawGutterErrorMarker(QPainter &painter, int top)
{
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;
}
bool CodeEditor::lineHasError(int blockNumber) const
{
return errorLines.count(blockNumber) > 0;
}
void CodeEditor::keyPressEvent(QKeyEvent *e)
@ -194,6 +166,8 @@ void CodeEditor::keyPressEvent(QKeyEvent *e)
QPlainTextEdit::keyPressEvent(e);
}
bool CodeEditor::indentSelection(bool indent)
{
auto cursor = textCursor();
@ -222,13 +196,28 @@ bool CodeEditor::indentSelection(bool indent)
{
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
if (indent)
insertIndentation(cursor);
else
removeIndentation(cursor);
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
}
cursor.endEditBlock();
makeSelection(cursor, first_pos, end_block);
setTextCursor(cursor);
return true;
}
void CodeEditor::insertIndentation(QTextCursor &cursor)
{
if (m_useTab)
cursor.insertText("\t");
else
cursor.insertText(QString(m_tabSize, ' '));
}
else
void CodeEditor::removeIndentation(QTextCursor &cursor)
{
// remove tab if there is a tab
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
@ -252,20 +241,17 @@ bool CodeEditor::indentSelection(bool indent)
for (int i = 0; i < index; ++i)
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
}
cursor.endEditBlock();
}
void CodeEditor::makeSelection(QTextCursor &cursor, int first_pos, int end_block)
{
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)

View file

@ -13,7 +13,6 @@ class CodeEditor : public QPlainTextEdit
public:
explicit CodeEditor(QWidget *parent = nullptr);
void gutterAreaPaintEvent(QPaintEvent *event);
int gutterAreaWidth();
void addErrorMarker(int position, int length);
@ -47,9 +46,16 @@ private:
int m_tabSize = 0; // tabSize in characters
bool m_useTab = false;
bool lineHasError(int blockNumber) const;
void updateExtraSelections();
bool indentSelection(bool indent);
void drawGutterErrorMarker(QPainter &painter, int top);
void insertIndentation(QTextCursor &cursor);
void removeIndentation(QTextCursor &cursor);
void makeSelection(QTextCursor &cursor, int first_pos, int end_block);
friend class GutterPainter;
};
#endif // CODEEDITOR_H

View file

@ -1,5 +1,6 @@
#include "EditorGutter.h"
#include "CodeEditor.h"
#include "GutterPainter.h"
EditorGutter::EditorGutter(CodeEditor *editor)
: QWidget(editor)
@ -13,5 +14,6 @@ QSize EditorGutter::sizeHint() const
void EditorGutter::paintEvent(QPaintEvent *event)
{
codeEditor->gutterAreaPaintEvent(event);
GutterPainter gutterpainter(codeEditor, event);
gutterpainter.Paint();
}

View file

@ -0,0 +1,69 @@
#include "GutterPainter.h"
#include "CodeEditor.h"
GutterPainter::GutterPainter(CodeEditor *editor, QPaintEvent *event)
: editor(editor)
, painter(editor->gutterArea)
, event(event)
, fontMetrics(editor->fontMetrics())
{}
void GutterPainter::Paint()
{
painter.fillRect(event->rect(), Qt::lightGray);
LoopState loopState(editor);
// 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 (loopState.block.isValid() && loopState.top <= event->rect().bottom())
{
if (loopState.block.isVisible() && loopState.bottom >= event->rect().top())
{
drawLineNumber(loopState);
if (editor->lineHasError(loopState.blockNumber()))
drawGutterErrorMarker(loopState);
}
loopState.advanceToNextLine();
}
}
void GutterPainter::drawLineNumber(const LoopState &loopState)
{
QString number = QString::number(loopState.blockNumber() + 1);
painter.setPen(Qt::black);
painter.drawText(0, loopState.top, editor->gutterArea->width(), fontMetrics.height(),
Qt::AlignRight, number);
}
void GutterPainter::drawGutterErrorMarker(const LoopState &loopState)
{
int s = fontMetrics.height() - 8;
painter.setBrush(QBrush(Qt::red));
painter.drawEllipse(4, loopState.top + 4, s, s);
}
GutterPainter::LoopState::LoopState(CodeEditor *editor)
: editor(editor)
, block(editor->firstVisibleBlock())
, top((int) editor->blockBoundingGeometry(block).translated(editor->contentOffset()).top())
, bottom(top + (int) editor->blockBoundingRect(block).height())
{}
int GutterPainter::LoopState::blockNumber() const
{
return block.blockNumber();
}
void GutterPainter::LoopState::advanceToNextLine()
{
block = block.next();
top = bottom;
bottom = top + (int) editor->blockBoundingRect(block).height();
}

View file

@ -0,0 +1,38 @@
#include <QPainter>
#include <QTextBlock>
#pragma once
class CodeEditor;
class QPaintEvent;
class GutterPainter
{
public:
GutterPainter(CodeEditor *editor, QPaintEvent *event);
void Paint();
private:
CodeEditor *editor;
QPainter painter;
QPaintEvent *event;
QFontMetrics fontMetrics;
// Loop state
struct LoopState {
CodeEditor *editor;
QTextBlock block;
int top;
int bottom;
LoopState(CodeEditor *editor);
int blockNumber() const;
void advanceToNextLine();
};
void drawLineNumber(const LoopState &loopState);
void drawGutterErrorMarker(const LoopState &loopState);
};

View file

@ -70,7 +70,7 @@ void CrudTab::on_actionRemove_rows_triggered()
{
std::set<IntegerRange<int>> row_ranges;
auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection());
for (auto range : selection) {
for (auto& range : selection) {
row_ranges.emplace(range.top(), range.height());
}
std::set<IntegerRange<int>> merged_ranges;

View file

@ -45,6 +45,7 @@ SOURCES += main.cpp\
catalog/widgets/ColumnPage.cpp \
catalog/widgets/DependantsPage.cpp \
catalog/widgets/TriggerPage.cpp \
codeeditor/GutterPainter.cpp \
crud/CrudModel.cpp \
crud/CrudTab.cpp \
querytool/QueryExplainModel.cpp \
@ -119,6 +120,7 @@ HEADERS += \
catalog/widgets/ColumnPage.h \
catalog/widgets/DependantsPage.h \
catalog/widgets/TriggerPage.h \
codeeditor/GutterPainter.h \
crud/CrudModel.h \
crud/CrudTab.h \
querytool/QueryExplainModel.h \