KTextEditor

spellcheckbar.cpp
1/*
2 SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
3 SPDX-FileCopyrightText: 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "spellcheckbar.h"
9#include "ui_spellcheckbar.h"
10#include <KLocalizedString>
11
12#include "sonnet/backgroundchecker.h"
13#include "sonnet/speller.h"
14/*
15#include "sonnet/filter_p.h"
16#include "sonnet/settings_p.h"
17*/
18
19#include <QProgressDialog>
20
21#include <QComboBox>
22#include <QDialogButtonBox>
23#include <QLabel>
24#include <QListView>
25#include <QMessageBox>
26#include <QPushButton>
27#include <QStringListModel>
28#include <QTimer>
29
30// to initially disable sorting in the suggestions listview
31#define NONSORTINGCOLUMN 2
32
33class ReadOnlyStringListModel : public QStringListModel
34{
35public:
36 ReadOnlyStringListModel(QObject *parent)
38 {
39 }
40 Qt::ItemFlags flags(const QModelIndex &index) const override
41 {
42 Q_UNUSED(index);
44 }
45};
46
47/**
48 * Structure abstracts the word and its position in the
49 * parent text.
50 *
51 * @author Zack Rusin <zack@kde.org>
52 * @short struct represents word
53 */
54struct Word {
55 Word()
56 {
57 }
58
59 Word(const QString &w, int st, bool e = false)
60 : word(w)
61 , start(st)
62 , end(e)
63 {
64 }
65 Word(const Word &other)
66 : word(other.word)
67 , start(other.start)
68 , end(other.end)
69 {
70 }
71
72 Word &operator=(const Word &) = default;
73
74 QString word;
75 int start = 0;
76 bool end = true;
77};
78
79class SpellCheckBar::Private
80{
81public:
82 Ui_SonnetUi ui;
83 ReadOnlyStringListModel *suggestionsModel;
84 QWidget *wdg;
85 QDialogButtonBox *buttonBox;
86 QProgressDialog *progressDialog;
87 QString originalBuffer;
89
90 Word currentWord;
91 std::map<QString, QString> replaceAllMap;
92 bool restart; // used when text is distributed across several qtextedits, eg in KAider
93
95
96 int progressDialogTimeout;
97 bool showCompletionMessageBox;
98 bool spellCheckContinuedAfterReplacement;
99 bool canceled;
100
101 void deleteProgressDialog(bool directly)
102 {
103 if (progressDialog) {
104 progressDialog->hide();
105 if (directly) {
106 delete progressDialog;
107 } else {
108 progressDialog->deleteLater();
109 }
110 progressDialog = nullptr;
111 }
112 }
113};
114
115SpellCheckBar::SpellCheckBar(Sonnet::BackgroundChecker *checker, QWidget *parent)
116 : KateViewBarWidget(true, parent)
117 , d(new Private)
118{
119 d->checker = checker;
120
121 d->canceled = false;
122 d->showCompletionMessageBox = false;
123 d->spellCheckContinuedAfterReplacement = true;
124 d->progressDialogTimeout = -1;
125 d->progressDialog = nullptr;
126
127 initGui();
128 initConnections();
129}
130
131SpellCheckBar::~SpellCheckBar()
132{
133 delete d;
134}
135
136void SpellCheckBar::closed()
137{
138 if (viewBar()) {
139 viewBar()->removeBarWidget(this);
140 }
141
142 // called from hideMe, so don't call it again!
143 d->canceled = true;
144 d->deleteProgressDialog(false); // this method can be called in response to
145 d->replaceAllMap.clear();
146 // pressing 'Cancel' on the dialog
147 Q_EMIT cancel();
148 Q_EMIT spellCheckStatus(i18n("Spell check canceled."));
149}
150
151void SpellCheckBar::initConnections()
152{
153 connect(d->ui.m_addBtn, &QPushButton::clicked, this, &SpellCheckBar::slotAddWord);
154 connect(d->ui.m_replaceBtn, &QPushButton::clicked, this, &SpellCheckBar::slotReplaceWord);
155 connect(d->ui.m_replaceAllBtn, &QPushButton::clicked, this, &SpellCheckBar::slotReplaceAll);
156 connect(d->ui.m_skipBtn, &QPushButton::clicked, this, &SpellCheckBar::slotSkip);
157 connect(d->ui.m_skipAllBtn, &QPushButton::clicked, this, &SpellCheckBar::slotSkipAll);
158 connect(d->ui.m_suggestBtn, &QPushButton::clicked, this, &SpellCheckBar::slotSuggest);
159 connect(d->ui.m_language, &Sonnet::DictionaryComboBox::textActivated, this, &SpellCheckBar::slotChangeLanguage);
160 connect(d->checker, &Sonnet::BackgroundChecker::misspelling, this, &SpellCheckBar::slotMisspelling);
161 connect(d->checker, &Sonnet::BackgroundChecker::done, this, &SpellCheckBar::slotDone);
162 /*
163 connect(d->ui.m_suggestions, SIGNAL(doubleClicked(QModelIndex)),
164 SLOT(slotReplaceWord()));
165 */
166
167 connect(d->ui.cmbReplacement, &KComboBox::returnPressed, this, &SpellCheckBar::slotReplaceWord);
168 connect(d->ui.m_autoCorrect, &QPushButton::clicked, this, &SpellCheckBar::slotAutocorrect);
169 // button use by kword/kpresenter
170 // hide by default
171 d->ui.m_autoCorrect->hide();
172}
173
174void SpellCheckBar::initGui()
175{
176 QVBoxLayout *layout = new QVBoxLayout(centralWidget());
177 layout->setContentsMargins(0, 0, 0, 0);
178
179 d->wdg = new QWidget(this);
180 d->ui.setupUi(d->wdg);
181 layout->addWidget(d->wdg);
182 setGuiEnabled(false);
183
184 /*
185 d->buttonBox = new QDialogButtonBox(this);
186 d->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
187
188 layout->addWidget(d->buttonBox);
189 */
190
191 // d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN );
192 fillDictionaryComboBox();
193 d->restart = false;
194
195 d->suggestionsModel = new ReadOnlyStringListModel(this);
196 d->ui.cmbReplacement->setModel(d->suggestionsModel);
197}
198
199void SpellCheckBar::activeAutoCorrect(bool _active)
200{
201 if (_active) {
202 d->ui.m_autoCorrect->show();
203 } else {
204 d->ui.m_autoCorrect->hide();
205 }
206}
207
209{
210 d->progressDialogTimeout = timeout;
211}
212
214{
215 d->showCompletionMessageBox = b;
216}
217
219{
220 d->spellCheckContinuedAfterReplacement = b;
221}
222
223void SpellCheckBar::slotAutocorrect()
224{
225 setGuiEnabled(false);
226 setProgressDialogVisible(true);
227 Q_EMIT autoCorrect(d->currentWord.word, d->ui.cmbReplacement->lineEdit()->text());
228 slotReplaceWord();
229}
230
231void SpellCheckBar::setGuiEnabled(bool b)
232{
233 d->wdg->setEnabled(b);
234}
235
236void SpellCheckBar::setProgressDialogVisible(bool b)
237{
238 if (!b) {
239 d->deleteProgressDialog(true);
240 } else if (d->progressDialogTimeout >= 0) {
241 if (d->progressDialog) {
242 return;
243 }
244 d->progressDialog = new QProgressDialog(this);
245 d->progressDialog->setLabelText(i18nc("progress label", "Spell checking in progress..."));
246 d->progressDialog->setWindowTitle(i18nc("@title:window", "Check Spelling"));
247 d->progressDialog->setModal(true);
248 d->progressDialog->setAutoClose(false);
249 d->progressDialog->setAutoReset(false);
250 // create an 'indefinite' progress box as we currently cannot get progress feedback from
251 // the speller
252 d->progressDialog->reset();
253 d->progressDialog->setRange(0, 0);
254 d->progressDialog->setValue(0);
255 connect(d->progressDialog, &QProgressDialog::canceled, this, &SpellCheckBar::slotCancel);
256 d->progressDialog->setMinimumDuration(d->progressDialogTimeout);
257 }
258}
259
260void SpellCheckBar::slotCancel()
261{
262 hideMe();
263}
264
265QString SpellCheckBar::originalBuffer() const
266{
267 return d->originalBuffer;
268}
269
270QString SpellCheckBar::buffer() const
271{
272 return d->checker->text();
273}
274
275void SpellCheckBar::setBuffer(const QString &buf)
276{
277 d->originalBuffer = buf;
278 // it is possible to change buffer inside slot connected to done() signal
279 d->restart = true;
280}
281
282void SpellCheckBar::fillDictionaryComboBox()
283{
284 // Since m_language is changed to DictionaryComboBox most code here is gone,
285 // So fillDictionaryComboBox() could be removed and code moved to initGui()
286 // because the call in show() looks obsolete
287 Sonnet::Speller speller = d->checker->speller();
288 d->dictsMap = speller.availableDictionaries();
289
290 updateDictionaryComboBox();
291}
292
293void SpellCheckBar::updateDictionaryComboBox()
294{
295 Sonnet::Speller speller = d->checker->speller();
296 d->ui.m_language->setCurrentByDictionary(speller.language());
297}
298
299void SpellCheckBar::updateDialog(const QString &word)
300{
301 d->ui.m_unknownWord->setText(word);
302 // d->ui.m_contextLabel->setText(d->checker->currentContext());
303 const QStringList suggs = d->checker->suggest(word);
304
305 if (suggs.isEmpty()) {
306 d->ui.cmbReplacement->lineEdit()->clear();
307 } else {
308 d->ui.cmbReplacement->lineEdit()->setText(suggs.first());
309 }
310 fillSuggestions(suggs);
311}
312
313void SpellCheckBar::show()
314{
315 d->canceled = false;
316 fillDictionaryComboBox();
317 updateDictionaryComboBox();
318 if (d->originalBuffer.isEmpty()) {
319 d->checker->start();
320 } else {
321 d->checker->setText(d->originalBuffer);
322 }
323 setProgressDialogVisible(true);
324}
325
326void SpellCheckBar::slotAddWord()
327{
328 setGuiEnabled(false);
329 setProgressDialogVisible(true);
330 d->checker->addWordToPersonal(d->currentWord.word);
331 d->checker->continueChecking();
332}
333
334void SpellCheckBar::slotReplaceWord()
335{
336 setGuiEnabled(false);
337 setProgressDialogVisible(true);
338 const QString replacementText = d->ui.cmbReplacement->lineEdit()->text();
339 Q_EMIT replace(d->currentWord.word, d->currentWord.start, replacementText);
340
341 if (d->spellCheckContinuedAfterReplacement) {
342 d->checker->replace(d->currentWord.start, d->currentWord.word, replacementText);
343 d->checker->continueChecking();
344 } else {
345 setProgressDialogVisible(false);
346 d->checker->stop();
347 }
348}
349
350void SpellCheckBar::slotReplaceAll()
351{
352 setGuiEnabled(false);
353 setProgressDialogVisible(true);
354 d->replaceAllMap.insert_or_assign(d->currentWord.word, d->ui.cmbReplacement->lineEdit()->text());
355 slotReplaceWord();
356}
357
358void SpellCheckBar::slotSkip()
359{
360 setGuiEnabled(false);
361 setProgressDialogVisible(true);
362 d->checker->continueChecking();
363}
364
365void SpellCheckBar::slotSkipAll()
366{
367 setGuiEnabled(false);
368 setProgressDialogVisible(true);
369 // ### do we want that or should we have a d->ignoreAll list?
370 Sonnet::Speller speller = d->checker->speller();
371 speller.addToPersonal(d->currentWord.word);
372 d->checker->setSpeller(speller);
373 d->checker->continueChecking();
374}
375
376void SpellCheckBar::slotSuggest()
377{
378 QStringList suggs = d->checker->suggest(d->ui.cmbReplacement->lineEdit()->text());
379 fillSuggestions(suggs);
380}
381
382void SpellCheckBar::slotChangeLanguage(const QString &lang)
383{
384 Sonnet::Speller speller = d->checker->speller();
385 QString languageCode = d->dictsMap[lang];
386 if (!languageCode.isEmpty()) {
387 d->checker->changeLanguage(languageCode);
388 slotSuggest();
389 Q_EMIT languageChanged(languageCode);
390 }
391}
392
393void SpellCheckBar::fillSuggestions(const QStringList &suggs)
394{
395 d->suggestionsModel->setStringList(suggs);
396 if (!suggs.isEmpty()) {
397 d->ui.cmbReplacement->setCurrentIndex(0);
398 }
399}
400
401void SpellCheckBar::slotMisspelling(const QString &word, int start)
402{
403 setGuiEnabled(true);
404 setProgressDialogVisible(false);
405 Q_EMIT misspelling(word, start);
406 // NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods
407 // this dramatically reduces spellchecking time in Lokalize
408 // as this doesn't fetch suggestions for words that are present in msgid
409 if (!updatesEnabled()) {
410 return;
411 }
412
413 d->currentWord = Word(word, start);
414 if (d->replaceAllMap.find(word) != d->replaceAllMap.end()) {
415 d->ui.cmbReplacement->lineEdit()->setText(d->replaceAllMap[word]);
416 slotReplaceWord();
417 } else {
418 updateDialog(word);
419 }
420}
421
422void SpellCheckBar::slotDone()
423{
424 d->restart = false;
425 Q_EMIT done(d->checker->text());
426 if (d->restart) {
427 updateDictionaryComboBox();
428 d->checker->setText(d->originalBuffer);
429 d->restart = false;
430 } else {
431 setProgressDialogVisible(false);
432 Q_EMIT spellCheckStatus(i18n("Spell check complete."));
433 hideMe();
434 if (!d->canceled && d->showCompletionMessageBox) {
435 QMessageBox::information(this, i18n("Spell check complete."), i18nc("@title:window", "Check Spelling"));
436 }
437 }
438}
439
440#include "moc_spellcheckbar.cpp"
void returnPressed(const QString &text)
void setText(const QString &text)
void misspelling(const QString &word, int start)
virtual void continueChecking()
QString language() const
QMap< QString, QString > availableDictionaries() const
bool addToPersonal(const QString &word)
void setSpellCheckContinuedAfterReplacement(bool b)
Controls whether the spell checking is continued after the replacement of a misspelled word has been ...
void showSpellCheckCompletionMessage(bool b=true)
Controls whether a message box indicating the completion of the spell checking is shown or not.
void languageChanged(const QString &language)
Emitted when the user changes the language used for spellchecking, which is shown in a combobox of th...
void spellCheckStatus(const QString &)
Signal sends when spell checking is finished/stopped/completed.
void done(const QString &newBuffer)
The dialog won't be closed if you setBuffer() in slot connected to this signal.
void showProgressDialog(int timeout=500)
Controls whether an (indefinite) progress dialog is shown when the spell checking takes longer than t...
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void clicked(bool checked)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
void textActivated(const QString &text)
void setModal(bool modal)
void addWidget(QWidget *w)
void setContentsMargins(const QMargins &margins)
T & first()
bool isEmpty() const const
StandardButton information(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
void setAutoClose(bool close)
void setAutoReset(bool reset)
void setLabelText(const QString &text)
void setMinimumDuration(int ms)
void setRange(int minimum, int maximum)
void setValue(int progress)
bool isEmpty() const const
void setStringList(const QStringList &strings)
typedef ItemFlags
QWidget(QWidget *parent, Qt::WindowFlags f)
void setEnabled(bool)
void hide()
QLayout * layout() const const
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.