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