1 | // SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org> |
2 | // SPDX-FileCopyrightText: 2013 Martin Sandsmark <martin.sandsmark@kde.org> |
3 | // SPDX-FileCopyrightText: 2013 Aurélien Gâteau <agateau@kde.org> |
4 | // SPDX-FileCopyrightText: 2020 Christian Mollekopf <mollekopf@kolabsystems.com> |
5 | // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> |
6 | // SPDX-License-Identifier: LGPL-2.1-or-later |
7 | |
8 | #pragma once |
9 | |
10 | // TODO KF6 create AbstractSpellcheckHighlighter and make the QtQuick and QtWidget inherit |
11 | // from it. |
12 | |
13 | #include <QQuickTextDocument> |
14 | #include <QSyntaxHighlighter> |
15 | |
16 | class HighlighterPrivate; |
17 | |
18 | /// \brief The Sonnet Highlighter class, used for drawing red lines in text fields |
19 | /// when detecting spelling mistakes. |
20 | /// |
21 | /// SpellcheckHighlighter is adapted for QML applications. In usual Kirigami/QQC2-desktop-style |
22 | /// applications, this can be used directly by adding `Kirigami.SpellCheck.enabled: true` on |
23 | /// a TextArea. |
24 | /// |
25 | /// On other QML applications, you can add the SpellcheckHighlighter as a child of a TextArea. |
26 | /// |
27 | /// Note: TextField is not supported, as it lacks QTextDocument API that Sonnet relies on. |
28 | /// |
29 | /// \code{.qml} |
30 | /// TextArea { |
31 | /// id: textArea |
32 | /// Sonnet.SpellcheckHighlighter { |
33 | /// id: spellcheckhighlighter |
34 | /// document: textArea.textDocument |
35 | /// cursorPosition: textArea.cursorPosition |
36 | /// selectionStart: textArea.selectionStart |
37 | /// selectionEnd: textArea.selectionEnd |
38 | /// misspelledColor: Kirigami.Theme.negativeTextColor |
39 | /// active: true |
40 | /// |
41 | /// onChangeCursorPosition: { |
42 | /// textArea.cursorPosition = start; |
43 | /// textArea.moveCursorSelection(end, TextEdit.SelectCharacters); |
44 | /// } |
45 | /// } |
46 | /// } |
47 | /// \endcode |
48 | /// |
49 | /// Additionally SpellcheckHighlighter provides some convenient methods to create |
50 | /// a context menu with suggestions. \see suggestions |
51 | /// |
52 | /// \since 5.88 |
53 | class SpellcheckHighlighter : public QSyntaxHighlighter |
54 | { |
55 | Q_OBJECT |
56 | QML_ELEMENT |
57 | /// This property holds the underneath document from a QML TextEdit. |
58 | /// \since 5.88 |
59 | Q_PROPERTY(QQuickTextDocument *document READ quickDocument WRITE setQuickDocument NOTIFY documentChanged) |
60 | |
61 | /// This property holds the current cursor position. |
62 | /// \since 5.88 |
63 | Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) |
64 | |
65 | /// This property holds the start of the selection. |
66 | /// \since 5.88 |
67 | Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) |
68 | |
69 | /// This property holds the end of the selection. |
70 | /// \since 5.88 |
71 | Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) |
72 | |
73 | /// This property holds whether the current word under the mouse is misspelled. |
74 | /// \since 5.88 |
75 | Q_PROPERTY(bool wordIsMisspelled READ wordIsMisspelled NOTIFY wordIsMisspelledChanged) |
76 | |
77 | /// This property holds the current word under the mouse. |
78 | /// \since 5.88 |
79 | Q_PROPERTY(QString wordUnderMouse READ wordUnderMouse NOTIFY wordUnderMouseChanged) |
80 | |
81 | /// This property holds the spell color. By default, it's red. |
82 | /// \since 5.88 |
83 | Q_PROPERTY(QColor misspelledColor READ misspelledColor WRITE setMisspelledColor NOTIFY misspelledColorChanged) |
84 | |
85 | /// This property holds the current language used for spell checking. |
86 | /// \since 5.88 |
87 | Q_PROPERTY(QString currentLanguage READ currentLanguage NOTIFY currentLanguageChanged) |
88 | |
89 | /// This property holds whether a spell checking backend with support for the |
90 | /// \ref currentLanguage was found. |
91 | /// \since 5.88 |
92 | Q_PROPERTY(bool spellCheckerFound READ spellCheckerFound CONSTANT) |
93 | |
94 | /// \brief This property holds whether spell checking is enabled. |
95 | /// |
96 | /// If \p active is true then spell checking is enabled; otherwise it |
97 | /// is disabled. Note that you have to disable automatic (de)activation |
98 | /// with \ref automatic before you change the state of spell |
99 | /// checking if you want to persistently enable/disable spell |
100 | /// checking. |
101 | /// |
102 | /// \see automatic |
103 | /// \since 5.88 |
104 | Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) |
105 | |
106 | /// This property holds whether spell checking is automatically disabled |
107 | /// if there's too many errors. |
108 | /// \since 5.88 |
109 | Q_PROPERTY(bool automatic READ automatic WRITE setAutomatic NOTIFY automaticChanged) |
110 | |
111 | /// This property holds whether the automatic language detection is disabled |
112 | /// overriding the Sonnet global settings. |
113 | /// \since 5.88 |
114 | Q_PROPERTY(bool autoDetectLanguageDisabled READ autoDetectLanguageDisabled WRITE setAutoDetectLanguageDisabled NOTIFY autoDetectLanguageDisabledChanged) |
115 | |
116 | public: |
117 | explicit SpellcheckHighlighter(QObject *parent = nullptr); |
118 | ~SpellcheckHighlighter() override; |
119 | |
120 | /// Returns a list of suggested replacements for the given misspelled word. |
121 | /// If the word is not misspelled, the list will be empty. |
122 | /// |
123 | /// \param word the misspelled word |
124 | /// \param max at most this many suggestions will be returned. If this is |
125 | /// -1, as many suggestions as the spell backend supports will |
126 | /// be returned. |
127 | /// \return a list of suggested replacements for the word |
128 | /// \since 5.88 |
129 | Q_INVOKABLE QStringList suggestions(int position, int max = 5); |
130 | |
131 | /// Ignores the given word. This word will not be marked misspelled for |
132 | /// this session. It will again be marked as misspelled when creating |
133 | /// new highlighters. |
134 | /// |
135 | /// \param word the word which will be ignored |
136 | /// \since 5.88 |
137 | Q_INVOKABLE void ignoreWord(const QString &word); |
138 | |
139 | /// Adds the given word permanently to the dictionary. It will never |
140 | /// be marked as misspelled again, even after restarting the application. |
141 | /// |
142 | /// \param word the word which will be added to the dictionary |
143 | /// \since 5.88 |
144 | Q_INVOKABLE void addWordToDictionary(const QString &word); |
145 | |
146 | /// Replace word at the current cursor position, or @param at if |
147 | /// @param at is not -1. |
148 | /// \since 5.88 |
149 | Q_INVOKABLE void replaceWord(const QString &word, int at = -1); |
150 | |
151 | /// Checks if a given word is marked as misspelled by the highlighter. |
152 | /// |
153 | /// \param word the word to be checked |
154 | /// \return true if the given word is misspelled. |
155 | /// \since 5.88 |
156 | Q_INVOKABLE bool isWordMisspelled(const QString &word); |
157 | |
158 | Q_REQUIRED_RESULT QQuickTextDocument *quickDocument() const; |
159 | void setQuickDocument(QQuickTextDocument *document); |
160 | Q_REQUIRED_RESULT int cursorPosition() const; |
161 | void setCursorPosition(int position); |
162 | Q_REQUIRED_RESULT int selectionStart() const; |
163 | void setSelectionStart(int position); |
164 | Q_REQUIRED_RESULT int selectionEnd() const; |
165 | void setSelectionEnd(int position); |
166 | Q_REQUIRED_RESULT bool wordIsMisspelled() const; |
167 | Q_REQUIRED_RESULT QString wordUnderMouse() const; |
168 | Q_REQUIRED_RESULT bool spellCheckerFound() const; |
169 | Q_REQUIRED_RESULT QString currentLanguage() const; |
170 | void setActive(bool active); |
171 | Q_REQUIRED_RESULT bool active() const; |
172 | void setAutomatic(bool automatic); |
173 | Q_REQUIRED_RESULT bool automatic() const; |
174 | void setAutoDetectLanguageDisabled(bool autoDetectDisabled); |
175 | Q_REQUIRED_RESULT bool autoDetectLanguageDisabled() const; |
176 | void setMisspelledColor(const QColor &color); |
177 | Q_REQUIRED_RESULT QColor misspelledColor() const; |
178 | void setQuoteColor(const QColor &color); |
179 | Q_REQUIRED_RESULT QColor quoteColor() const; |
180 | |
181 | /// Return true if checker is enabled by default |
182 | /// \since 5.88 |
183 | bool checkerEnabledByDefault() const; |
184 | |
185 | /// Set a new @ref QTextDocument for this highlighter to operate on. |
186 | /// \param document the new document to operate on. |
187 | /// \since 5.88 |
188 | void setDocument(QTextDocument *document); |
189 | |
190 | Q_SIGNALS: |
191 | void documentChanged(); |
192 | void cursorPositionChanged(); |
193 | void selectionStartChanged(); |
194 | void selectionEndChanged(); |
195 | void wordIsMisspelledChanged(); |
196 | void wordUnderMouseChanged(); |
197 | void changeCursorPosition(int start, int end); |
198 | void activeChanged(); |
199 | void misspelledColorChanged(); |
200 | void autoDetectLanguageDisabledChanged(); |
201 | void automaticChanged(); |
202 | void currentLanguageChanged(); |
203 | |
204 | /// Emitted when as-you-type spell checking is enabled or disabled. |
205 | /// |
206 | /// \param description is a i18n description of the new state, |
207 | /// with an optional reason |
208 | /// \since 5.88 |
209 | void activeChanged(const QString &description); |
210 | |
211 | protected: |
212 | void highlightBlock(const QString &text) override; |
213 | virtual void setMisspelled(int start, int count); |
214 | virtual void setMisspelledSelected(int start, int count); |
215 | virtual void unsetMisspelled(int start, int count); |
216 | bool eventFilter(QObject *o, QEvent *e) override; |
217 | |
218 | bool intraWordEditing() const; |
219 | void setIntraWordEditing(bool editing); |
220 | |
221 | public Q_SLOTS: |
222 | /// Set language to use for spell checking. |
223 | /// |
224 | /// \param language the language code for the new language to use. |
225 | /// \since 5.88 |
226 | void setCurrentLanguage(const QString &language); |
227 | |
228 | /// Run auto detection, disabling spell checking if too many errors are found. |
229 | /// \since 5.88 |
230 | void slotAutoDetection(); |
231 | |
232 | /// Force a new highlighting. |
233 | /// \since 5.88 |
234 | void slotRehighlight(); |
235 | |
236 | private: |
237 | Q_REQUIRED_RESULT QTextCursor textCursor() const; |
238 | Q_REQUIRED_RESULT QTextDocument *textDocument() const; |
239 | void contentsChange(int pos, int add, int rem); |
240 | |
241 | void autodetectLanguage(const QString &sentence); |
242 | |
243 | HighlighterPrivate *const d; |
244 | Q_DISABLE_COPY(SpellcheckHighlighter) |
245 | }; |
246 | |