1 /*
2  * Copyright (C) Pedram Pourang (aka Tsu Jan) 2014-2021 <tsujan2000@gmail.com>
3  *
4  * FeatherPad is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the
6  * Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * FeatherPad is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * @license GPL-3.0+ <https://spdx.org/licenses/GPL-3.0+.html>
18  */
19 
20 #ifndef TEXTEDIT_H
21 #define TEXTEDIT_H
22 
23 #include <QPlainTextEdit>
24 #include <QUrl>
25 #include <QMimeData>
26 #include <QDateTime>
27 #include <QElapsedTimer>
28 #include <QSyntaxHighlighter>
29 
30 namespace FeatherPad {
31 
32 /* This is for auto-indentation, line numbers, DnD, zooming, customized
33    vertical scrollbar, appropriate signals, and saving/getting useful info. */
34 class TextEdit : public QPlainTextEdit
35 {
36     Q_OBJECT
37 
38 public:
39     TextEdit (QWidget *parent = nullptr, int bgColorValue = 255);
40     ~TextEdit();
41 
setTextCursor(const QTextCursor & cursor)42     void setTextCursor (const QTextCursor &cursor)
43     {
44         QPlainTextEdit::setTextCursor (cursor);
45         /* this is needed for formatTextRect() to be called (for syntax highlighting) */
46         emit QPlainTextEdit::updateRequest (rect(), 1);
47     }
48 
49     void setEditorFont (const QFont &f, bool setDefault = true);
50     void adjustScrollbars();
51 
52     void lineNumberAreaPaintEvent (QPaintEvent *event);
53     int lineNumberAreaWidth();
54     void showLineNumbers (bool show);
55 
56     void sortLines (bool reverse = false);
57 
58     bool toSoftTabs();
59 
60     QString getUrl (const int pos) const;
61 
getDefaultFont()62     QFont getDefaultFont() const {
63         return font_;
64     }
65 
getTextTab_()66     QString getTextTab_() const {
67         return textTab_;
68     }
setTtextTab(int textTabSize)69     void setTtextTab (int textTabSize) {
70         textTab_ = textTab_.leftJustified (textTabSize, ' ', true);
71     }
72 
currentLineSelection()73     QTextEdit::ExtraSelection currentLineSelection() {
74         return currentLine_;
75     }
76 
setAutoIndentation(bool indent)77     void setAutoIndentation (bool indent) {
78         autoIndentation_ = indent;
79     }
getAutoIndentation()80     bool getAutoIndentation() const {
81         return autoIndentation_;
82     }
83 
setAutoReplace(bool replace)84     void setAutoReplace (bool replace) {
85         autoReplace_ = replace;
86     }
getAutoReplace()87     bool getAutoReplace() const {
88         return autoReplace_;
89     }
90 
setDrawIndetLines(bool draw)91     void setDrawIndetLines (bool draw) {
92         drawIndetLines_ = draw;
93     }
94 
setVLineDistance(int distance)95     void setVLineDistance (int distance) {
96         vLineDistance_ = distance;
97     }
98 
setDateFormat(const QString & format)99     void setDateFormat (const QString &format) {
100         dateFormat_ = format;
101     }
102 
setAutoBracket(bool autoB)103     void setAutoBracket (bool autoB) {
104         autoBracket_ = autoB;
105     }
106 
hasDarkScheme()107     bool hasDarkScheme() const {
108         return (darkValue_ > -1);
109     }
getDarkValue()110     int getDarkValue() const {
111         return darkValue_;
112     }
113 
getTextPrintColor()114     QColor getTextPrintColor() const {
115         /* with syntax highlighting, the color of line/document ends
116            should be returned because the ordinary text is formatted */
117         if (highlighter_)
118             return separatorColor_;
119         return (darkValue_ == -1 ? Qt::black : Qt::white);
120     }
121 
122     void setCurLineHighlight (int value);
123 
124     void zooming (float range);
125 
getSize()126     qint64 getSize() const {
127         return size_;
128     }
setSize(qint64 size)129     void setSize (qint64 size) {
130         size_ = size;
131     }
132 
getLastModified()133     QDateTime getLastModified() const {
134         return lastModified_;
135     }
setLastModified(const QDateTime & m)136     void setLastModified (const QDateTime& m) {
137         lastModified_ = m;
138     }
139 
getWordNumber()140     int getWordNumber() const {
141         return wordNumber_;
142     }
setWordNumber(int n)143     void setWordNumber (int n) {
144         wordNumber_ = n;
145     }
146 
getSearchedText()147     QString getSearchedText() const {
148         return searchedText_;
149     }
setSearchedText(const QString & text)150     void setSearchedText (const QString &text) {
151         searchedText_ = text;
152     }
153 
getReplaceTitle()154     QString getReplaceTitle() const {
155         return replaceTitle_;
156     }
setReplaceTitle(const QString & title)157     void setReplaceTitle (const QString &title) {
158         replaceTitle_ = title;
159     }
160 
getFileName()161     QString getFileName() const {
162         return fileName_;
163     }
setFileName(const QString & name)164     void setFileName (const QString &name) {
165         fileName_ = name;
166     }
167 
getProg()168     QString getProg() const {
169         return prog_.isEmpty() ? "url" // impossible; just a precaution
170                                : prog_;
171     }
setProg(const QString & prog)172     void setProg (const QString &prog) {
173         prog_ = prog;
174     }
175 
getLang()176     QString getLang() const {
177         return lang_;
178     }
setLang(const QString & lang)179     void setLang (const QString &lang) {
180         lang_ = lang;
181     }
182 
getEncoding()183     QString getEncoding() const {
184         return encoding_;
185     }
setEncoding(const QString & encoding)186     void setEncoding (const QString &encoding) {
187         encoding_ = encoding;
188     }
189 
getGreenSel()190     QList<QTextEdit::ExtraSelection> getGreenSel() const {
191         return greenSel_;
192     }
setGreenSel(QList<QTextEdit::ExtraSelection> sel)193     void setGreenSel (QList<QTextEdit::ExtraSelection> sel) {
194         greenSel_ = sel;
195     }
196 
getRedSel()197     QList<QTextEdit::ExtraSelection> getRedSel() const {
198         return redSel_;
199     }
setRedSel(QList<QTextEdit::ExtraSelection> sel)200     void setRedSel (QList<QTextEdit::ExtraSelection> sel) {
201         redSel_ = sel;
202     }
203 
getBlueSel()204     QList<QTextEdit::ExtraSelection> getBlueSel() const {
205         return blueSel_;
206     }
207 
isUneditable()208     bool isUneditable() const {
209         return uneditable_;
210     }
makeUneditable(bool readOnly)211     void makeUneditable (bool readOnly) {
212         uneditable_ = readOnly;
213     }
214 
getHighlighter()215     QSyntaxHighlighter *getHighlighter() const {
216         return highlighter_;
217     }
setHighlighter(QSyntaxHighlighter * h)218     void setHighlighter (QSyntaxHighlighter *h) {
219         highlighter_ = h;
220         matchedBrackets_ = false;
221     }
222 
getInertialScrolling()223     bool getInertialScrolling() const {
224         return inertialScrolling_;
225     }
setInertialScrolling(bool inertial)226     void setInertialScrolling (bool inertial) {
227         inertialScrolling_ = inertial;
228     }
229 
getSaveCursor()230     bool getSaveCursor() const {
231         return saveCursor_;
232     }
setSaveCursor(bool save)233     void setSaveCursor (bool save) {
234         saveCursor_ = save;
235     }
236 
getThickCursor()237     bool getThickCursor() const {
238         return (cursorWidth() > 1);
239     }
setThickCursor(bool thick)240     void setThickCursor (bool thick) {
241         setCursorWidth (thick ? 2 : 1);
242     }
243 
matchedBrackets()244     void matchedBrackets() {
245         matchedBrackets_ = true;
246     }
247 
forgetTxtCurHPos()248     void forgetTxtCurHPos() {
249         keepTxtCurHPos_ = false;
250         txtCurHPos_ = -1;
251     }
252 
getSelectionHighlighting()253     bool getSelectionHighlighting() const {
254         return selectionHighlighting_;
255     }
256     void setSelectionHighlighting (bool enable);
257 
skipSelectionHighlighting()258     void skipSelectionHighlighting() {
259         highlightThisSelection_ = false;
260     }
261 
setPastePaths(bool pastePaths)262     void setPastePaths (bool pastePaths) {
263         pastePaths_ = pastePaths;
264     }
265 
266     QTextCursor finding (const QString& str, const QTextCursor& start,
267                          QTextDocument::FindFlags flags = QTextDocument::FindFlags(),
268                          bool isRegex = false, const int end = 0) const;
269 
270 signals:
271     /* inform the main widget */
272     void fileDropped (const QString& localFile,
273                       int restoreCursor, // Only for connecting to FPwin::newTabFromName().
274                       int posInLine, // Only for connecting to FPwin::newTabFromName().
275                       bool multiple); // Multiple files are dropped?
276     void resized(); // needed by syntax highlighting
277     void updateRect();
278     void zoomedOut (TextEdit *textEdit); // needed for reformatting text
279     void updateBracketMatching();
280 
281 public slots:
282     void copy();
283     void cut();
284     void undo();
285     void redo();
286     void paste();
287     void selectAll();
288     void insertPlainText (const QString &text);
289     void selectionHlight();
290     void onContentsChange (int position, int charsRemoved, int charsAdded);
291 
292 protected:
293     void keyPressEvent (QKeyEvent *event);
294     void keyReleaseEvent (QKeyEvent *event);
295     void wheelEvent (QWheelEvent *event);
296     void resizeEvent (QResizeEvent *event);
297     void timerEvent (QTimerEvent *event);
298     void paintEvent (QPaintEvent *event); // only for working around the RTL bug
299     void showEvent (QShowEvent *event);
300     void mouseMoveEvent (QMouseEvent *event);
301     void mousePressEvent (QMouseEvent *event);
302     void mouseReleaseEvent (QMouseEvent *event);
303     void mouseDoubleClickEvent (QMouseEvent *event);
304     bool event (QEvent *event);
305     bool eventFilter (QObject *watched, QEvent *event);
306 
307     QMimeData* createMimeDataFromSelection() const;
308     /* we want to pass dropping of files to
309        the main widget with a custom signal */
310     bool canInsertFromMimeData (const QMimeData* source) const;
311     void insertFromMimeData (const QMimeData* source);
312 
313 private slots:
314     void updateLineNumberAreaWidth (int newBlockCount);
315     void highlightCurrentLine();
316     void updateLineNumberArea (const QRect &rect, int dy);
317     void onUpdateRequesting (const QRect&, int dy);
318     void onSelectionChanged();
319     void scrollWithInertia();
320 
321 private:
322     QString computeIndentation (const QTextCursor &cur) const;
323     QString remainingSpaces (const QString& spaceTab, const QTextCursor& cursor) const;
324     QTextCursor backTabCursor(const QTextCursor& cursor, bool twoSpace) const;
325 
326     int prevAnchor_, prevPos_; // used only for bracket matching
327     QWidget *lineNumberArea_;
328     QTextEdit::ExtraSelection currentLine_;
329     QRect lastCurrentLine_;
330     int widestDigit_;
331     bool autoIndentation_;
332     bool autoReplace_;
333     bool drawIndetLines_;
334     bool autoBracket_;
335     int darkValue_;
336     QColor separatorColor_;
337     int vLineDistance_;
338     QString dateFormat_;
339     QColor lineHColor_;
340     int resizeTimerId_, selectionTimerId_; // for not wasting CPU's time
341     QPoint pressPoint_; // used internally for hyperlinks
342     QFont font_; // used internally for keeping track of the unzoomed font
343     QString textTab_; // text tab in terms of spaces
344     QElapsedTimer tripleClickTimer_;
345     /* To know whether text may be pasted, in contrast to text/file dropping: */
346     bool pasting_;
347     /* To keep text cursor's horizontal position with Up/Down keys
348        (also used in a workaround for a Qt regression): */
349     bool keepTxtCurHPos_;
350     int txtCurHPos_;
351     /********************************************
352      ***** All needed information on a page *****
353      ********************************************/
354     qint64 size_; // file size for limiting syntax highlighting (the file may be removed)
355     QDateTime lastModified_; // the last modification time for knowing about changes.
356     int wordNumber_; // the calculated number of words (-1 if not counted yet)
357     QString searchedText_; // the text that is being searched in the document
358     QString replaceTitle_; // the title of the Replacement dock (can change)
359     QString fileName_; // opened file
360     QString prog_; // real programming language (never empty; defaults to "url")
361     QString lang_; // selected (enforced) programming language (empty if nothing's enforced)
362     QString encoding_; // text encoding (UTF-8 by default)
363     /*
364        Highlighting order: (1) current line;
365                            (2) replacing;
366                            (3) search matches;
367                            (4) selection matches;
368                            (5) bracket matches.
369     */
370     QList<QTextEdit::ExtraSelection> greenSel_; // for replaced matches
371     QList<QTextEdit::ExtraSelection> blueSel_; // for selection highlighting
372     QList<QTextEdit::ExtraSelection> redSel_; // for bracket matching
373     bool selectionHighlighting_; // should selections be highlighted?
374     bool highlightThisSelection_; // should this selection be highlighted?
375     bool removeSelectionHighlights_; // used only internally
376     bool matchedBrackets_; // is bracket matching done (is FPwin::matchBrackets called)?
377     bool uneditable_; // the doc should be made uneditable because of its contents
378     QSyntaxHighlighter *highlighter_; // syntax highlighter
379     bool saveCursor_;
380     bool pastePaths_;
381     /******************************
382      ***** Inertial scrolling *****
383      ******************************/
384     bool inertialScrolling_;
385     struct scrollData {
386       int delta;
387       int leftSteps;
388       int totalSteps;
389     };
390     QList<scrollData> queuedScrollSteps_;
391     QTimer *scrollTimer_;
392 };
393 /*************************/
394 class LineNumberArea : public QWidget
395 {
396     Q_OBJECT
397 
398 public:
LineNumberArea(TextEdit * Editor)399     LineNumberArea (TextEdit *Editor) : QWidget (Editor) {
400         editor = Editor;
401     }
402 
sizeHint()403     QSize sizeHint() const {
404         return QSize (editor->lineNumberAreaWidth(), 0);
405     }
406 
407 protected:
paintEvent(QPaintEvent * event)408     void paintEvent (QPaintEvent *event) {
409         editor->lineNumberAreaPaintEvent (event);
410     }
411 
mouseDoubleClickEvent(QMouseEvent * event)412     void mouseDoubleClickEvent (QMouseEvent *event) {
413         if (rect().contains (event->pos()))
414             editor->centerCursor();
415         QWidget::mouseDoubleClickEvent (event);
416     }
417 
418 private:
419     TextEdit *editor;
420 };
421 
422 }
423 
424 #endif // TEXTEDIT_H
425