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

source code of sonnet/src/quick/spellcheckhighlighter.h