156 lines
4.3 KiB
C++
156 lines
4.3 KiB
C++
#include "CodeEditor.h"
|
|
#include "EditorGutter.h"
|
|
#include <QPainter>
|
|
#include <QTextBlock>
|
|
//
|
|
// 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);
|
|
highlightCurrentLine();
|
|
}
|
|
|
|
int CodeEditor::gutterAreaWidth()
|
|
{
|
|
int digits = 1;
|
|
int max = qMax(1, blockCount());
|
|
while (max >= 10) {
|
|
max /= 10;
|
|
++digits;
|
|
}
|
|
|
|
int space = 3 + fontMetrics().width(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;
|
|
}
|
|
}
|