1 /*
2 * backgroundchecker.cpp
3 *
4 * SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
5 * SPDX-FileCopyrightText: 2009 Jakub Stachowski <qbast@go2.pl>
6 *
7 * SPDX-License-Identifier: LGPL-2.1-or-later
8 */
9 #include "backgroundchecker.h"
10 #include "backgroundchecker_p.h"
11
12 #include "core_debug.h"
13
14 using namespace Sonnet;
15
start()16 void BackgroundCheckerPrivate::start()
17 {
18 sentenceOffset = -1;
19 continueChecking();
20 }
21
continueChecking()22 void BackgroundCheckerPrivate::continueChecking()
23 {
24 metaObject()->invokeMethod(this, "checkNext", Qt::QueuedConnection);
25 }
26
checkNext()27 void BackgroundCheckerPrivate::checkNext()
28 {
29 do {
30 // go over current sentence
31 while (sentenceOffset != -1 && words.hasNext()) {
32 Token word = words.next();
33 if (!words.isSpellcheckable()) {
34 continue;
35 }
36
37 // ok, this is valid word, do something
38 if (currentDict.isMisspelled(word.toString())) {
39 lastMisspelled = word;
40 Q_EMIT misspelling(word.toString(), word.position() + sentenceOffset);
41 return;
42 }
43 }
44 // current sentence done, grab next suitable
45
46 sentenceOffset = -1;
47 const bool autodetectLanguage = currentDict.testAttribute(Speller::AutoDetectLanguage);
48 const bool ignoreUpperCase = !currentDict.testAttribute(Speller::CheckUppercase);
49 while (mainTokenizer.hasNext()) {
50 Token sentence = mainTokenizer.next();
51 if (autodetectLanguage && !autoDetectLanguageDisabled) {
52 if (!mainTokenizer.isSpellcheckable()) {
53 continue;
54 }
55 // FIXME: find best from family en -> en_US, en_GB, ... ?
56 currentDict.setLanguage(mainTokenizer.language());
57 }
58 sentenceOffset = sentence.position();
59 words.setBuffer(sentence.toString());
60 words.setIgnoreUppercase(ignoreUpperCase);
61 break;
62 }
63 } while (sentenceOffset != -1);
64 Q_EMIT done();
65 }
66
BackgroundChecker(QObject * parent)67 BackgroundChecker::BackgroundChecker(QObject *parent)
68 : QObject(parent)
69 , d(new BackgroundCheckerPrivate)
70 {
71 connect(d, &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling);
72 connect(d, &BackgroundCheckerPrivate::done, this, &BackgroundChecker::slotEngineDone);
73 }
74
BackgroundChecker(const Speller & speller,QObject * parent)75 BackgroundChecker::BackgroundChecker(const Speller &speller, QObject *parent)
76 : QObject(parent)
77 , d(new BackgroundCheckerPrivate)
78 {
79 d->currentDict = speller;
80 connect(d, &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling);
81 connect(d, &BackgroundCheckerPrivate::done, this, &BackgroundChecker::slotEngineDone);
82 }
83
~BackgroundChecker()84 BackgroundChecker::~BackgroundChecker()
85 {
86 delete d;
87 }
88
setText(const QString & text)89 void BackgroundChecker::setText(const QString &text)
90 {
91 d->mainTokenizer.setBuffer(text);
92 d->start();
93 }
94
start()95 void BackgroundChecker::start()
96 {
97 // ## what if d->currentText.isEmpty()?
98
99 // TODO: carry state from last buffer
100 d->mainTokenizer.setBuffer(fetchMoreText());
101 d->start();
102 }
103
stop()104 void BackgroundChecker::stop()
105 {
106 // d->stop();
107 }
108
fetchMoreText()109 QString BackgroundChecker::fetchMoreText()
110 {
111 return QString();
112 }
113
finishedCurrentFeed()114 void BackgroundChecker::finishedCurrentFeed()
115 {
116 }
117
autoDetectLanguageDisabled() const118 bool BackgroundChecker::autoDetectLanguageDisabled() const
119 {
120 return d->autoDetectLanguageDisabled;
121 }
122
setAutoDetectLanguageDisabled(bool autoDetectDisabled)123 void BackgroundChecker::setAutoDetectLanguageDisabled(bool autoDetectDisabled)
124 {
125 d->autoDetectLanguageDisabled = autoDetectDisabled;
126 }
127
setSpeller(const Speller & speller)128 void BackgroundChecker::setSpeller(const Speller &speller)
129 {
130 d->currentDict = speller;
131 }
132
speller() const133 Speller BackgroundChecker::speller() const
134 {
135 return d->currentDict;
136 }
137
checkWord(const QString & word)138 bool BackgroundChecker::checkWord(const QString &word)
139 {
140 return d->currentDict.isCorrect(word);
141 }
142
addWordToPersonal(const QString & word)143 bool BackgroundChecker::addWordToPersonal(const QString &word)
144 {
145 return d->currentDict.addToPersonal(word);
146 }
147
addWordToSession(const QString & word)148 bool BackgroundChecker::addWordToSession(const QString &word)
149 {
150 return d->currentDict.addToSession(word);
151 }
152
suggest(const QString & word) const153 QStringList BackgroundChecker::suggest(const QString &word) const
154 {
155 return d->currentDict.suggest(word);
156 }
157
changeLanguage(const QString & lang)158 void BackgroundChecker::changeLanguage(const QString &lang)
159 {
160 // this sets language only for current sentence
161 d->currentDict.setLanguage(lang);
162 }
163
continueChecking()164 void BackgroundChecker::continueChecking()
165 {
166 d->continueChecking();
167 }
168
slotEngineDone()169 void BackgroundChecker::slotEngineDone()
170 {
171 finishedCurrentFeed();
172 const QString currentText = fetchMoreText();
173
174 if (currentText.isNull()) {
175 Q_EMIT done();
176 } else {
177 d->mainTokenizer.setBuffer(currentText);
178 d->start();
179 }
180 }
181
text() const182 QString BackgroundChecker::text() const
183 {
184 return d->mainTokenizer.buffer();
185 }
186
currentContext() const187 QString BackgroundChecker::currentContext() const
188 {
189 int len = 60;
190 // we don't want the expression underneath casted to an unsigned int
191 // which would cause it to always evaluate to false
192 int currentPosition = d->lastMisspelled.position() + d->sentenceOffset;
193 bool begin = ((currentPosition - len / 2) <= 0) ? true : false;
194
195 QString buffer = d->mainTokenizer.buffer();
196 buffer.replace(currentPosition, d->lastMisspelled.length(), QStringLiteral("<b>%1</b>").arg(d->lastMisspelled.toString()));
197
198 QString context;
199 if (begin) {
200 context = QStringLiteral("%1...").arg(buffer.mid(0, len));
201 } else {
202 context = QStringLiteral("...%1...").arg(buffer.mid(currentPosition - 20, len));
203 }
204
205 context.replace(QLatin1Char('\n'), QLatin1Char(' '));
206
207 return context;
208 }
209
replace(int start,const QString & oldText,const QString & newText)210 void Sonnet::BackgroundChecker::replace(int start, const QString &oldText, const QString &newText)
211 {
212 // FIXME: here we assume that replacement is in current fragment. So 'words' has
213 // to be adjusted and sentenceOffset does not
214 d->words.replace(start - (d->sentenceOffset), oldText.length(), newText);
215 d->mainTokenizer.replace(start, oldText.length(), newText);
216 }
217