Sonnet

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

KDE's Doxygen guidelines are available online.