#ifndef EDITOR_HXX #define EDITOR_HXX /// from qt http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html #include #include #include #include #include #include #include #include #include class Highlighter: public QSyntaxHighlighter { Q_OBJECT Q_SIGNALS: void include(QString); public: Highlighter(QTextDocument *parent): QSyntaxHighlighter(parent) { QString commands="auth|ca-certificate|call|case|default|check|clear-cookies|click|clicktype|client-certificate|do|download|echo|execute|exists|exit|expect|fail|for|function|if|else|while|ignore|unignore|ignoreto|include|label|load|not|offline-storage-path|open|screenshot|set|setvalue|sleep|testcase|testsuite|timeout|timeout-false|unset|upload"; _expressions< _expressions; }; class CodeEditor; class LineNumberArea: public QWidget { public: LineNumberArea(CodeEditor *editor); QSize sizeHint() const override; protected: void paintEvent(QPaintEvent *event) override; private: CodeEditor *codeEditor; }; class CodeEditor: public QPlainTextEdit { Q_OBJECT; Q_SIGNALS: void include(QString); void link(QString); public Q_SLOTS: void gotoLine(int line) { if (textCursor().blockNumber()==line-1) return; QTextCursor cursor(document()->findBlockByNumber(line-1)); setTextCursor(cursor); highlightCurrentLine(); } public: CodeEditor(QWidget *parent = 0): QPlainTextEdit(parent) { Highlighter *highlighter(new Highlighter(document())); lineNumberArea = new LineNumberArea(this); assert(connect(this, SIGNAL(blockCountChanged(int)), SLOT(updateLineNumberAreaWidth(int)))); assert(connect(this, SIGNAL(updateRequest(QRect,int)), SLOT(updateLineNumberArea(QRect,int)))); assert(connect(this, SIGNAL(cursorPositionChanged()), SLOT(highlightCurrentLine()))); assert(connect(highlighter, SIGNAL(include(QString)), SIGNAL(include(QString)))); updateLineNumberAreaWidth(0); highlightCurrentLine(); } void lineNumberAreaPaintEvent(QPaintEvent *event) { QPainter painter(lineNumberArea); 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(); 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, lineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number); } block = block.next(); top = bottom; bottom = top + (int) blockBoundingRect(block).height(); ++blockNumber; } } int lineNumberAreaWidth() { int digits(1); int max = qMax(1, blockCount()); while (max >= 10) { max /= 10; ++digits; } int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; return space; } void mousePressEvent(QMouseEvent *e) { clickedAnchor = (e->button() & Qt::LeftButton) ? document()->findBlock(cursorForPosition(e->pos()).position()).text() : QString(); QPlainTextEdit::mousePressEvent(e); } void mouseReleaseEvent(QMouseEvent *e) { if (e->button() & Qt::LeftButton && !clickedAnchor.isEmpty() && document()->findBlock(cursorForPosition(e->pos()).position()).text() == clickedAnchor) { static QRegularExpression inc("^ *include +([^ ].*.\\.wt)"); QRegularExpressionMatch m(inc.match(clickedAnchor)); if (m.hasMatch() && QFile(m.captured(1)).exists()) { link(m.captured(1)); } } QPlainTextEdit::mouseReleaseEvent(e); } protected: void resizeEvent(QResizeEvent *e) override { QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); } private Q_SLOTS: void updateLineNumberAreaWidth(int newBlockCount) { setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); } void highlightCurrentLine() { QList extraSelections; if (!isReadOnly()) { 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(); extraSelections.append(selection); } setExtraSelections(extraSelections); } void updateLineNumberArea(const QRect &rect, int dy) { if (dy) lineNumberArea->scroll(0, dy); else lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); if (rect.contains(viewport()->rect())) updateLineNumberAreaWidth(0); } private: QWidget *lineNumberArea; QString clickedAnchor; }; inline LineNumberArea::LineNumberArea(CodeEditor *editor): QWidget(editor) { codeEditor = editor; } inline QSize LineNumberArea::sizeHint() const { return QSize(codeEditor->lineNumberAreaWidth(), 0); } inline void LineNumberArea::paintEvent(QPaintEvent *event) { codeEditor->lineNumberAreaPaintEvent(event); } #endif