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 | |
16 | void BackgroundCheckerPrivate::start() |
17 | { |
18 | sentenceOffset = -1; |
19 | continueChecking(); |
20 | } |
21 | |
22 | void BackgroundCheckerPrivate::continueChecking() |
23 | { |
24 | metaObject()->invokeMethod(obj: this, member: "checkNext" , c: Qt::QueuedConnection); |
25 | } |
26 | |
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: 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(attr: Speller::AutoDetectLanguage); |
48 | const bool ignoreUpperCase = !currentDict.testAttribute(attr: 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 | |
67 | BackgroundChecker::BackgroundChecker(QObject *parent) |
68 | : QObject(parent) |
69 | , d(new BackgroundCheckerPrivate) |
70 | { |
71 | connect(sender: d.get(), signal: &BackgroundCheckerPrivate::misspelling, context: this, slot: &BackgroundChecker::misspelling); |
72 | connect(sender: d.get(), signal: &BackgroundCheckerPrivate::done, context: this, slot: &BackgroundChecker::slotEngineDone); |
73 | } |
74 | |
75 | BackgroundChecker::BackgroundChecker(const Speller &speller, QObject *parent) |
76 | : QObject(parent) |
77 | , d(new BackgroundCheckerPrivate) |
78 | { |
79 | d->currentDict = speller; |
80 | connect(sender: d.get(), signal: &BackgroundCheckerPrivate::misspelling, context: this, slot: &BackgroundChecker::misspelling); |
81 | connect(sender: d.get(), signal: &BackgroundCheckerPrivate::done, context: this, slot: &BackgroundChecker::slotEngineDone); |
82 | } |
83 | |
84 | BackgroundChecker::~BackgroundChecker() = default; |
85 | |
86 | void BackgroundChecker::setText(const QString &text) |
87 | { |
88 | d->mainTokenizer.setBuffer(text); |
89 | d->start(); |
90 | } |
91 | |
92 | void BackgroundChecker::start() |
93 | { |
94 | // ## what if d->currentText.isEmpty()? |
95 | |
96 | // TODO: carry state from last buffer |
97 | d->mainTokenizer.setBuffer(fetchMoreText()); |
98 | d->start(); |
99 | } |
100 | |
101 | void BackgroundChecker::stop() |
102 | { |
103 | // d->stop(); |
104 | } |
105 | |
106 | QString BackgroundChecker::fetchMoreText() |
107 | { |
108 | return QString(); |
109 | } |
110 | |
111 | void BackgroundChecker::finishedCurrentFeed() |
112 | { |
113 | } |
114 | |
115 | bool BackgroundChecker::autoDetectLanguageDisabled() const |
116 | { |
117 | return d->autoDetectLanguageDisabled; |
118 | } |
119 | |
120 | void BackgroundChecker::setAutoDetectLanguageDisabled(bool autoDetectDisabled) |
121 | { |
122 | d->autoDetectLanguageDisabled = autoDetectDisabled; |
123 | } |
124 | |
125 | void BackgroundChecker::setSpeller(const Speller &speller) |
126 | { |
127 | d->currentDict = speller; |
128 | } |
129 | |
130 | Speller BackgroundChecker::speller() const |
131 | { |
132 | return d->currentDict; |
133 | } |
134 | |
135 | bool BackgroundChecker::checkWord(const QString &word) |
136 | { |
137 | return d->currentDict.isCorrect(word); |
138 | } |
139 | |
140 | bool BackgroundChecker::addWordToPersonal(const QString &word) |
141 | { |
142 | return d->currentDict.addToPersonal(word); |
143 | } |
144 | |
145 | bool BackgroundChecker::addWordToSession(const QString &word) |
146 | { |
147 | return d->currentDict.addToSession(word); |
148 | } |
149 | |
150 | QStringList BackgroundChecker::suggest(const QString &word) const |
151 | { |
152 | return d->currentDict.suggest(word); |
153 | } |
154 | |
155 | void BackgroundChecker::changeLanguage(const QString &lang) |
156 | { |
157 | // this sets language only for current sentence |
158 | d->currentDict.setLanguage(lang); |
159 | } |
160 | |
161 | void BackgroundChecker::continueChecking() |
162 | { |
163 | d->continueChecking(); |
164 | } |
165 | |
166 | void BackgroundChecker::slotEngineDone() |
167 | { |
168 | finishedCurrentFeed(); |
169 | const QString currentText = fetchMoreText(); |
170 | |
171 | if (currentText.isNull()) { |
172 | Q_EMIT done(); |
173 | } else { |
174 | d->mainTokenizer.setBuffer(currentText); |
175 | d->start(); |
176 | } |
177 | } |
178 | |
179 | QString BackgroundChecker::text() const |
180 | { |
181 | return d->mainTokenizer.buffer(); |
182 | } |
183 | |
184 | QString BackgroundChecker::currentContext() const |
185 | { |
186 | int len = 60; |
187 | // we don't want the expression underneath casted to an unsigned int |
188 | // which would cause it to always evaluate to false |
189 | int currentPosition = d->lastMisspelled.position() + d->sentenceOffset; |
190 | bool begin = ((currentPosition - len / 2) <= 0) ? true : false; |
191 | |
192 | QString buffer = d->mainTokenizer.buffer(); |
193 | buffer.replace(i: currentPosition, len: d->lastMisspelled.length(), QStringLiteral("<b>%1</b>" ).arg(a: d->lastMisspelled.toString())); |
194 | |
195 | QString context; |
196 | if (begin) { |
197 | context = QStringLiteral("%1..." ).arg(a: buffer.mid(position: 0, n: len)); |
198 | } else { |
199 | context = QStringLiteral("...%1..." ).arg(a: buffer.mid(position: currentPosition - 20, n: len)); |
200 | } |
201 | |
202 | context.replace(before: QLatin1Char('\n'), after: QLatin1Char(' ')); |
203 | |
204 | return context; |
205 | } |
206 | |
207 | void Sonnet::BackgroundChecker::replace(int start, const QString &oldText, const QString &newText) |
208 | { |
209 | // FIXME: here we assume that replacement is in current fragment. So 'words' has |
210 | // to be adjusted and sentenceOffset does not |
211 | d->words.replace(position: start - (d->sentenceOffset), len: oldText.length(), newWord: newText); |
212 | d->mainTokenizer.replace(position: start, len: oldText.length(), newWord: newText); |
213 | } |
214 | |
215 | #include "moc_backgroundchecker.cpp" |
216 | #include "moc_backgroundchecker_p.cpp" |
217 | |