presentation/ComposeHighlighter.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QColor> | ||
| 4 | #include <QHash> | ||
| 5 | #include <QSet> | ||
| 6 | #include <QSyntaxHighlighter> | ||
| 7 | #include <QTextCursor> | ||
| 8 | #include <QTimer> | ||
| 9 | #include <functional> | ||
| 10 | |||
| 11 | // Sprint 108 (S108.3.7, S108.3.8, S108.4.3): the composer's display layer. | ||
| 12 | // A QSyntaxHighlighter adds formats only to what is shown — the document | ||
| 13 | // and therefore the sent message never change: | ||
| 14 | // - headings larger and bold, code (blocks and inline) in a monospace font | ||
| 15 | // on the code background, quotes in the secondary colour, table header | ||
| 16 | // cells bold (the document carries structure, not these looks); | ||
| 17 | // - in the dark theme, explicit text colours that would be unreadable on | ||
| 18 | // the editor's surface are shown lighter until they reach 4.5:1 (§4.14, | ||
| 19 | // the stored colour is kept); | ||
| 20 | // - misspellings with a wavy underline (D-12): only words outside quotes, | ||
| 21 | // the signature, code, URLs and addresses, not all-caps and without | ||
| 22 | // digits, never the word at the caret while typing. | ||
| 23 | // One highlighter per editor document; GUI thread. | ||
| 24 | class ComposeHighlighter final : public QSyntaxHighlighter { | ||
| 25 | Q_OBJECT | ||
| 26 | public: | ||
| 27 | struct Palette { | ||
| 28 | QColor text; | ||
| 29 | QColor secondary; | ||
| 30 | QColor surface; | ||
| 31 | QColor codeBackground; | ||
| 32 | QColor spellError; | ||
| 33 | bool dark = false; | ||
| 34 | }; | ||
| 35 | // A word to check: offset and length in the block's text. | ||
| 36 | struct Word { | ||
| 37 | int start = 0; | ||
| 38 | int length = 0; | ||
| 39 | }; | ||
| 40 | // Returns true when `word` is misspelled in `language`. | ||
| 41 | using SpellCheck = std::function<bool(const QString &word, const QString &language)>; | ||
| 42 | |||
| 43 | explicit ComposeHighlighter(QTextDocument *document); | ||
| 44 | void setPalette(const Palette &palette); | ||
| 45 | void setBaseFontPointSize(qreal points); | ||
| 46 | void setSpellCheck(SpellCheck check, const QString &language); | ||
| 47 | QString language() const { return m_language; } | ||
| 48 | // Everything again after a change of language, words, palette or size | ||
| 49 | // (G11): at once up to GraduallyFromCharacters, in slices of | ||
| 50 | // SliceBudgetMs per turn of the event loop for longer documents (a | ||
| 51 | // 1 MiB paste is checked without a stall). Blocks edited meanwhile are | ||
| 52 | // highlighted by the edit itself. | ||
| 53 | static constexpr int GraduallyFromCharacters = 32 * 1024; | ||
| 54 | static constexpr int SliceBudgetMs = 8; | ||
| 55 | void rehighlightAll(); | ||
| 56 | 31 | bool rehighlighting() const { return !m_next.isNull(); } | |
| 57 | // The caret's position while typing: the word under it is not marked | ||
| 58 | // until typingPaused() (D-12, 400 ms after the last keystroke). | ||
| 59 | void setCaret(int position, bool typing); | ||
| 60 | void typingPaused(); | ||
| 61 | // Words of `text` that spell checking looks at (skip zones applied). | ||
| 62 | static QList<Word> checkableWords(const QString &text); | ||
| 63 | // Misspelled words of a block as found by the last highlight (tests, | ||
| 64 | // Shift+F7 and the context menu). | ||
| 65 | QList<Word> misspellings(const QTextBlock &block) const; | ||
| 66 | // A colour readable on `surface` with at least `ratio` contrast, shifted | ||
| 67 | // in lightness only (display adjustment of explicit colours). | ||
| 68 | static QColor readableOn(const QColor &color, const QColor &surface, qreal ratio = 4.5); | ||
| 69 | static qreal contrast(const QColor &a, const QColor &b); | ||
| 70 | |||
| 71 | protected: | ||
| 72 | void highlightBlock(const QString &text) override; | ||
| 73 | |||
| 74 | private: | ||
| 75 | Palette m_palette; | ||
| 76 | qreal m_basePoints = 0; | ||
| 77 | SpellCheck m_spellCheck; | ||
| 78 | QString m_language; | ||
| 79 | int m_caret = -1; | ||
| 80 | bool m_typing = false; | ||
| 81 | QHash<int, QList<Word>> m_misspellings; // block number → words | ||
| 82 | QTextCursor m_next; // the next block of a gradual rehighlight (null: none) | ||
| 83 | void rehighlightSlice(); | ||
| 84 | void highlightStructure(const QString &text); | ||
| 85 | void highlightSpelling(const QString &text); | ||
| 86 | }; | ||
| 87 |