File indexing completed on 2024-03-24 03:48:13

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 &currentUrl() { 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_