File indexing completed on 2024-04-14 03:43:56
0001 /* 0002 SPDX-FileCopyrightText: 2003-2008 Cies Breijs <cies AT kde DOT nl> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef _EDITOR_H_ 0008 #define _EDITOR_H_ 0009 0010 #include <cmath> 0011 0012 #include <QAbstractTextDocumentLayout> 0013 #include <QFrame> 0014 #include <QPainter> 0015 #include <QScrollBar> 0016 #include <QTextEdit> 0017 0018 #include <KFindDialog> 0019 0020 #include "highlighter.h" 0021 #include "interpreter/token.h" 0022 #include "interpreter/tokenizer.h" 0023 #include "interpreter/treenode.h" 0024 0025 class QHBoxLayout; 0026 0027 0028 static const QColor LINE_HIGHLIGHT_COLOR(239, 247, 255); 0029 static const QColor WORD_HIGHLIGHT_COLOR(255, 255, 156); 0030 static const QColor ERROR_HIGHLIGHT_COLOR(255, 200, 200); 0031 0032 static const int EXTRA_SATURATION = 30; // used for drawing the highlighted background 0033 static const int EDITOR_MARGIN = 2; // some margin that can't be set to zero, yet painters should know it 0034 static const int CURSOR_RECT_MARGIN = 5; // another margin that cannot be traced 0035 static const int LINENUMBER_SPACING = 2; // sets the margin for the line numbers 0036 0037 const QString KTURTLE_MAGIC_1_0 = QStringLiteral("kturtle-script-v1.0"); 0038 0039 0040 //BEGIN LineNumbers class 0041 0042 class LineNumbers : public QWidget 0043 { 0044 Q_OBJECT 0045 0046 public: 0047 LineNumbers(QWidget *parent, QTextEdit *te) : QWidget(parent), editor(te), maxWidth(0) {} 0048 ~LineNumbers() override {} 0049 0050 void setFont(const QFont& f) { QWidget::setFont(f); } 0051 0052 void setWidth(int w) { 0053 if (w == maxWidth) return; // save some cpu cycles 0054 maxWidth = w; 0055 QString s; 0056 for (; w > 0; w--) s += QLatin1Char('0'); 0057 setFixedWidth(fontMetrics().boundingRect(s).width() + 2*LINENUMBER_SPACING); 0058 } 0059 0060 void paintEvent(QPaintEvent*) override { 0061 QAbstractTextDocumentLayout* layout = editor->document()->documentLayout(); 0062 int contentsY = editor->verticalScrollBar()->value(); 0063 qreal pageBottom = contentsY + editor->viewport()->height(); 0064 const QFontMetrics fm = fontMetrics(); 0065 const int ascent = fontMetrics().ascent() + 1; // height = ascent + descent + 1 0066 int lineCount = 1; 0067 QPainter painter(this); 0068 for (QTextBlock block = editor->document()->begin(); block.isValid(); block = block.next(), ++lineCount) { 0069 const QRectF boundingRect = layout->blockBoundingRect(block); 0070 QPointF position = boundingRect.topLeft(); 0071 if (position.y() + boundingRect.height() < contentsY) continue; 0072 if (position.y() > pageBottom) break; 0073 const QString txt = QString::number(lineCount); 0074 painter.drawText(width() - fm.boundingRect(txt).width() - LINENUMBER_SPACING, qRound(position.y()) - contentsY + ascent, txt); 0075 } 0076 painter.end(); 0077 } 0078 0079 private: 0080 QTextEdit *editor; 0081 int maxWidth; 0082 }; 0083 0084 //END class LineNumbers 0085 0086 0087 0088 //BEGIN QTextEdit sub-class 0089 0090 class TextEdit : public QTextEdit 0091 { 0092 Q_OBJECT 0093 0094 public: 0095 explicit TextEdit(QWidget* parent = nullptr) 0096 : QTextEdit(parent) {} 0097 0098 void markCurrentWord(int startRow, int startCol, int endRow, int endCol) { 0099 currentWord.setCoords(startRow, startCol, endRow, endCol); 0100 viewport()->update(); 0101 } 0102 0103 void removeCurrentWordMark() { 0104 currentWord = QRect(); 0105 viewport()->update(); 0106 } 0107 0108 void markCurrentError(int startRow, int startCol, int endRow, int endCol) { 0109 currentError.setCoords(startRow, startCol, endRow, endCol); 0110 viewport()->update(); 0111 } 0112 0113 void removeCurrentErrorMark() { 0114 currentError = QRect(); 0115 viewport()->update(); 0116 } 0117 0118 void highlightCurrentLine() { viewport()->update(); } 0119 0120 QRect currentLineRect() { 0121 // this method is also used for highlighting the background of the numbers 0122 QTextCursor cursor = textCursor(); 0123 cursor.movePosition(QTextCursor::StartOfBlock); 0124 QRect rect = cursorRect(cursor); 0125 cursor.movePosition(QTextCursor::EndOfBlock); 0126 rect |= cursorRect(cursor); // get the bounding rectangle of both rects 0127 rect.setX(0); 0128 rect.setWidth(viewport()->width()); 0129 return rect; 0130 } 0131 0132 protected: 0133 void paintEvent(QPaintEvent *event) override { 0134 QPainter painter(viewport()); 0135 painter.fillRect(currentLineRect(), QBrush(LINE_HIGHLIGHT_COLOR)); 0136 if (!currentWord.isNull()) { 0137 const auto coordsToRectsList{coordsToRects(currentWord)}; 0138 for (const QRect &rect : coordsToRectsList) 0139 painter.fillRect(rect, QBrush(WORD_HIGHLIGHT_COLOR)); 0140 } 0141 if (!currentError.isNull()) { 0142 const auto coordsToRectsLst{coordsToRects(currentError)}; 0143 for (const QRect &rect : coordsToRectsLst) 0144 painter.fillRect(rect, QBrush(ERROR_HIGHLIGHT_COLOR)); 0145 } 0146 painter.end(); 0147 QTextEdit::paintEvent(event); 0148 } 0149 0150 private: 0151 QList<QRect> coordsToRects(QRect coords) { 0152 // this methods calculate the viewport rectangles that cover a (multi-line) word or error 0153 // after switching the tokenizer to use the QTextDocument we might optimize this methods 0154 int startRow, startCol, endRow, endCol; 0155 coords.getCoords(&startRow, &startCol, &endRow, &endCol); 0156 0157 QTextCursor cursor(document()); 0158 cursor.movePosition(QTextCursor::Start); 0159 QTextCursor endCursor(cursor); 0160 cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, startRow - 1); 0161 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, startCol - 1); 0162 endCursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, endRow - 1); 0163 endCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, endCol - 1); 0164 0165 QRect rect = cursorRect(cursor).adjusted(CURSOR_RECT_MARGIN, 0, 0, 0); 0166 cursor.movePosition(QTextCursor::EndOfLine); 0167 QList<QRect> rects; 0168 while (cursor < endCursor) { 0169 cursor.movePosition(QTextCursor::PreviousCharacter); 0170 rects << (rect | cursorRect(cursor).adjusted(0, 0, fontMetrics().boundingRect(QStringLiteral("0")).width() - CURSOR_RECT_MARGIN, 0)); 0171 cursor.movePosition(QTextCursor::Down); 0172 cursor.movePosition(QTextCursor::StartOfLine); 0173 rect = cursorRect(cursor).adjusted(CURSOR_RECT_MARGIN, 0, 0, 0); 0174 cursor.movePosition(QTextCursor::EndOfLine); 0175 } 0176 rects << (rect | cursorRect(endCursor).adjusted(0, 0, -CURSOR_RECT_MARGIN, 0)); 0177 return rects; 0178 } 0179 0180 // stores the start/end row/col of currentWord and currentError in the coods of 2 rectangles 0181 QRect currentWord, currentError; 0182 }; 0183 0184 //END QTextEdit sub-class 0185 0186 0187 0188 0189 class Editor : public QFrame 0190 { 0191 Q_OBJECT 0192 0193 public: 0194 explicit Editor(QWidget *parent = nullptr); 0195 ~Editor() override; 0196 0197 QTextEdit* view() const { return editor; } 0198 QTextDocument* document() const { return editor->document(); } 0199 0200 void enable(); 0201 void disable(); 0202 0203 const QUrl ¤tUrl() { return m_currentUrl; } 0204 void setCurrentUrl(const QUrl &url = QUrl()); 0205 0206 bool maybeSave(); 0207 0208 bool isModified() { return editor->document()->isModified(); } 0209 QString content() { return editor->document()->toPlainText(); } 0210 QString toHtml(const QString& title, const QString& lang); 0211 0212 int row() { return currentRow; } 0213 int col() { return currentCol; } 0214 Token* currentToken(); 0215 0216 void removeMarkings() { 0217 editor->removeCurrentWordMark(); 0218 editor->removeCurrentErrorMark(); 0219 } 0220 0221 0222 public Q_SLOTS: 0223 bool newFile(); 0224 bool openFile(const QUrl &url = QUrl()); 0225 void openExample(const QString& example, const QString& exampleName); 0226 bool saveFile(const QUrl &url = QUrl()); 0227 bool saveFileAs(); 0228 void toggleLineNumbers(bool b) { numbers->setVisible(b); } 0229 void setModified(bool); 0230 void setOverwriteMode(bool b); 0231 0232 void markCurrentWord(TreeNode* node) { 0233 editor->markCurrentWord( 0234 node->token()->startRow(), 0235 node->token()->startCol(), 0236 node->token()->endRow(), 0237 node->token()->endCol()); 0238 } 0239 void markCurrentError(int startRow, int startCol, int endRow, int endCol) { 0240 editor->markCurrentError(startRow, startCol, endRow, endCol); 0241 } 0242 void find(); 0243 void findNext(); 0244 void findPrev(); 0245 void insertPlainText(const QString& txt); 0246 void rehighlight() { highlighter->rehighlight(); } 0247 0248 0249 Q_SIGNALS: 0250 void contentNameChanged(const QString&); 0251 void fileOpened(const QUrl&); 0252 void fileSaved(const QUrl&); 0253 void modificationChanged(); 0254 void contentChanged(); 0255 void cursorPositionChanged(); 0256 0257 0258 protected Q_SLOTS: 0259 void textChanged(int pos, int added, int removed); 0260 // void cursorPositionChanged(); 0261 0262 protected: 0263 void paintEvent(QPaintEvent *event) override; 0264 0265 0266 private Q_SLOTS: 0267 void updateOnCursorPositionChange(); 0268 void highlightCurrentLine() { this->update(); } 0269 0270 private: 0271 void setContent(const QString&); 0272 0273 TextEdit *editor; // TODO why pointers? 0274 Highlighter *highlighter; // TODO could this class become a singleton? (shared with the inspector, errdlg) 0275 Tokenizer *tokenizer; // TODO could this class become a singleton? (shared with the highlighter, interpreter) 0276 LineNumbers *numbers; 0277 QHBoxLayout *box; // TODO is this relly needed? 0278 KFindDialog *fdialog; 0279 QUrl m_currentUrl; // contains url to the currently load file or the exampleName 0280 QColor highlightedLineBackgroundColor; // the bg color of the current line's line number space 0281 QString currentLine; 0282 int currentRow; 0283 int currentCol; 0284 }; 0285 0286 0287 #endif // _EDITOR_H_