Refactor painting of editor gutter
This commit is contained in:
parent
e082a5731d
commit
a5563949e5
7 changed files with 188 additions and 85 deletions
|
|
@ -120,45 +120,17 @@ void CodeEditor::updateExtraSelections()
|
||||||
setExtraSelections(extraSelections);
|
setExtraSelections(extraSelections);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeEditor::gutterAreaPaintEvent(QPaintEvent *event)
|
void CodeEditor::drawGutterErrorMarker(QPainter &painter, int top)
|
||||||
{
|
{
|
||||||
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;
|
int s = fontMetrics().height() - 8;
|
||||||
painter.setBrush(QBrush(Qt::red));
|
painter.setBrush(QBrush(Qt::red));
|
||||||
painter.drawEllipse(4, top + 4, s, s);
|
painter.drawEllipse(4, top + 4, s, s);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
block = block.next();
|
}
|
||||||
top = bottom;
|
|
||||||
bottom = top + (int) blockBoundingRect(block).height();
|
bool CodeEditor::lineHasError(int blockNumber) const
|
||||||
++blockNumber;
|
{
|
||||||
}
|
return errorLines.count(blockNumber) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeEditor::keyPressEvent(QKeyEvent *e)
|
void CodeEditor::keyPressEvent(QKeyEvent *e)
|
||||||
|
|
@ -194,6 +166,8 @@ void CodeEditor::keyPressEvent(QKeyEvent *e)
|
||||||
QPlainTextEdit::keyPressEvent(e);
|
QPlainTextEdit::keyPressEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool CodeEditor::indentSelection(bool indent)
|
bool CodeEditor::indentSelection(bool indent)
|
||||||
{
|
{
|
||||||
auto cursor = textCursor();
|
auto cursor = textCursor();
|
||||||
|
|
@ -222,14 +196,29 @@ bool CodeEditor::indentSelection(bool indent)
|
||||||
{
|
{
|
||||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
||||||
if (indent)
|
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)
|
if (m_useTab)
|
||||||
cursor.insertText("\t");
|
cursor.insertText("\t");
|
||||||
else
|
else
|
||||||
cursor.insertText(QString(m_tabSize, ' '));
|
cursor.insertText(QString(m_tabSize, ' '));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
void CodeEditor::removeIndentation(QTextCursor &cursor)
|
||||||
|
{
|
||||||
// remove tab if there is a tab
|
// remove tab if there is a tab
|
||||||
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
|
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
|
||||||
const QString text = cursor.selectedText();
|
const QString text = cursor.selectedText();
|
||||||
|
|
@ -252,20 +241,17 @@ bool CodeEditor::indentSelection(bool indent)
|
||||||
for (int i = 0; i < index; ++i)
|
for (int i = 0; i < index; ++i)
|
||||||
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
|
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
|
||||||
cursor.removeSelectedText();
|
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.setPosition(first_pos, QTextCursor::MoveAnchor);
|
||||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
||||||
while(cursor.block().blockNumber() < end_block)
|
while(cursor.block().blockNumber() < end_block)
|
||||||
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
|
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
|
||||||
|
|
||||||
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
||||||
|
|
||||||
setTextCursor(cursor);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeEditor::setFont(const QFont &f)
|
void CodeEditor::setFont(const QFont &f)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ class CodeEditor : public QPlainTextEdit
|
||||||
public:
|
public:
|
||||||
explicit CodeEditor(QWidget *parent = nullptr);
|
explicit CodeEditor(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void gutterAreaPaintEvent(QPaintEvent *event);
|
|
||||||
int gutterAreaWidth();
|
int gutterAreaWidth();
|
||||||
|
|
||||||
void addErrorMarker(int position, int length);
|
void addErrorMarker(int position, int length);
|
||||||
|
|
@ -47,9 +46,16 @@ private:
|
||||||
int m_tabSize = 0; // tabSize in characters
|
int m_tabSize = 0; // tabSize in characters
|
||||||
bool m_useTab = false;
|
bool m_useTab = false;
|
||||||
|
|
||||||
|
bool lineHasError(int blockNumber) const;
|
||||||
void updateExtraSelections();
|
void updateExtraSelections();
|
||||||
bool indentSelection(bool indent);
|
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
|
#endif // CODEEDITOR_H
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "EditorGutter.h"
|
#include "EditorGutter.h"
|
||||||
#include "CodeEditor.h"
|
#include "CodeEditor.h"
|
||||||
|
#include "GutterPainter.h"
|
||||||
|
|
||||||
EditorGutter::EditorGutter(CodeEditor *editor)
|
EditorGutter::EditorGutter(CodeEditor *editor)
|
||||||
: QWidget(editor)
|
: QWidget(editor)
|
||||||
|
|
@ -13,5 +14,6 @@ QSize EditorGutter::sizeHint() const
|
||||||
|
|
||||||
void EditorGutter::paintEvent(QPaintEvent *event)
|
void EditorGutter::paintEvent(QPaintEvent *event)
|
||||||
{
|
{
|
||||||
codeEditor->gutterAreaPaintEvent(event);
|
GutterPainter gutterpainter(codeEditor, event);
|
||||||
|
gutterpainter.Paint();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
pglab/codeeditor/GutterPainter.cpp
Normal file
69
pglab/codeeditor/GutterPainter.cpp
Normal 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();
|
||||||
|
}
|
||||||
38
pglab/codeeditor/GutterPainter.h
Normal file
38
pglab/codeeditor/GutterPainter.h
Normal 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);
|
||||||
|
};
|
||||||
|
|
@ -70,7 +70,7 @@ void CrudTab::on_actionRemove_rows_triggered()
|
||||||
{
|
{
|
||||||
std::set<IntegerRange<int>> row_ranges;
|
std::set<IntegerRange<int>> row_ranges;
|
||||||
auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection());
|
auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection());
|
||||||
for (auto range : selection) {
|
for (auto& range : selection) {
|
||||||
row_ranges.emplace(range.top(), range.height());
|
row_ranges.emplace(range.top(), range.height());
|
||||||
}
|
}
|
||||||
std::set<IntegerRange<int>> merged_ranges;
|
std::set<IntegerRange<int>> merged_ranges;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ SOURCES += main.cpp\
|
||||||
catalog/widgets/ColumnPage.cpp \
|
catalog/widgets/ColumnPage.cpp \
|
||||||
catalog/widgets/DependantsPage.cpp \
|
catalog/widgets/DependantsPage.cpp \
|
||||||
catalog/widgets/TriggerPage.cpp \
|
catalog/widgets/TriggerPage.cpp \
|
||||||
|
codeeditor/GutterPainter.cpp \
|
||||||
crud/CrudModel.cpp \
|
crud/CrudModel.cpp \
|
||||||
crud/CrudTab.cpp \
|
crud/CrudTab.cpp \
|
||||||
querytool/QueryExplainModel.cpp \
|
querytool/QueryExplainModel.cpp \
|
||||||
|
|
@ -119,6 +120,7 @@ HEADERS += \
|
||||||
catalog/widgets/ColumnPage.h \
|
catalog/widgets/ColumnPage.h \
|
||||||
catalog/widgets/DependantsPage.h \
|
catalog/widgets/DependantsPage.h \
|
||||||
catalog/widgets/TriggerPage.h \
|
catalog/widgets/TriggerPage.h \
|
||||||
|
codeeditor/GutterPainter.h \
|
||||||
crud/CrudModel.h \
|
crud/CrudModel.h \
|
||||||
crud/CrudTab.h \
|
crud/CrudTab.h \
|
||||||
querytool/QueryExplainModel.h \
|
querytool/QueryExplainModel.h \
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue