File indexing completed on 2024-04-14 03:58:23
0001 /* 0002 * dialog.cpp 0003 * 0004 * SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org> 0005 * SPDX-FileCopyrightText: 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net> 0006 * 0007 * SPDX-License-Identifier: LGPL-2.1-or-later 0008 */ 0009 #include "dialog.h" 0010 #include "ui_sonnetui.h" 0011 0012 #include "backgroundchecker.h" 0013 #include "settingsimpl_p.h" 0014 #include "speller.h" 0015 0016 #include <QProgressDialog> 0017 0018 #include <QDialogButtonBox> 0019 #include <QMessageBox> 0020 #include <QPushButton> 0021 #include <QStringListModel> 0022 0023 namespace Sonnet 0024 { 0025 // to initially disable sorting in the suggestions listview 0026 #define NONSORTINGCOLUMN 2 0027 0028 class ReadOnlyStringListModel : public QStringListModel 0029 { 0030 public: 0031 explicit ReadOnlyStringListModel(QObject *parent) 0032 : QStringListModel(parent) 0033 { 0034 } 0035 0036 Qt::ItemFlags flags(const QModelIndex &index) const override 0037 { 0038 Q_UNUSED(index); 0039 return Qt::ItemIsEnabled | Qt::ItemIsSelectable; 0040 } 0041 }; 0042 0043 class DialogPrivate 0044 { 0045 public: 0046 Ui_SonnetUi ui; 0047 ReadOnlyStringListModel *suggestionsModel = nullptr; 0048 QWidget *wdg = nullptr; 0049 QDialogButtonBox *buttonBox = nullptr; 0050 QProgressDialog *progressDialog = nullptr; 0051 QString originalBuffer; 0052 BackgroundChecker *checker = nullptr; 0053 0054 QString currentWord; 0055 int currentPosition; 0056 QMap<QString, QString> replaceAllMap; 0057 bool restart; // used when text is distributed across several qtextedits, eg in KAider 0058 0059 QMap<QString, QString> dictsMap; 0060 0061 int progressDialogTimeout; 0062 bool showCompletionMessageBox; 0063 bool spellCheckContinuedAfterReplacement; 0064 bool canceled; 0065 0066 void deleteProgressDialog(bool directly) 0067 { 0068 if (progressDialog) { 0069 progressDialog->hide(); 0070 if (directly) { 0071 delete progressDialog; 0072 } else { 0073 progressDialog->deleteLater(); 0074 } 0075 progressDialog = nullptr; 0076 } 0077 } 0078 }; 0079 0080 Dialog::Dialog(BackgroundChecker *checker, QWidget *parent) 0081 : QDialog(parent) 0082 , d(new DialogPrivate) 0083 { 0084 setModal(true); 0085 setWindowTitle(tr("Check Spelling", "@title:window")); 0086 0087 d->checker = checker; 0088 0089 d->canceled = false; 0090 d->showCompletionMessageBox = false; 0091 d->spellCheckContinuedAfterReplacement = true; 0092 d->progressDialogTimeout = -1; 0093 d->progressDialog = nullptr; 0094 0095 initGui(); 0096 initConnections(); 0097 } 0098 0099 Dialog::~Dialog() = default; 0100 0101 void Dialog::initConnections() 0102 { 0103 connect(d->ui.m_addBtn, &QAbstractButton::clicked, this, &Dialog::slotAddWord); 0104 connect(d->ui.m_replaceBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceWord); 0105 connect(d->ui.m_replaceAllBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceAll); 0106 connect(d->ui.m_skipBtn, &QAbstractButton::clicked, this, &Dialog::slotSkip); 0107 connect(d->ui.m_skipAllBtn, &QAbstractButton::clicked, this, &Dialog::slotSkipAll); 0108 connect(d->ui.m_suggestBtn, &QAbstractButton::clicked, this, &Dialog::slotSuggest); 0109 connect(d->ui.m_language, &DictionaryComboBox::textActivated, this, &Dialog::slotChangeLanguage); 0110 connect(d->ui.m_suggestions, &QListView::clicked, this, &Dialog::slotSelectionChanged); 0111 connect(d->checker, &BackgroundChecker::misspelling, this, &Dialog::slotMisspelling); 0112 connect(d->checker, &BackgroundChecker::done, this, &Dialog::slotDone); 0113 connect(d->ui.m_suggestions, &QListView::doubleClicked, this, [this](const QModelIndex &) { 0114 slotReplaceWord(); 0115 }); 0116 connect(d->buttonBox, &QDialogButtonBox::accepted, this, &Dialog::slotFinished); 0117 connect(d->buttonBox, &QDialogButtonBox::rejected, this, &Dialog::slotCancel); 0118 connect(d->ui.m_replacement, &QLineEdit::returnPressed, this, &Dialog::slotReplaceWord); 0119 connect(d->ui.m_autoCorrect, &QPushButton::clicked, this, &Dialog::slotAutocorrect); 0120 // button use by kword/kpresenter 0121 // hide by default 0122 d->ui.m_autoCorrect->hide(); 0123 } 0124 0125 void Dialog::initGui() 0126 { 0127 QVBoxLayout *layout = new QVBoxLayout(this); 0128 0129 d->wdg = new QWidget(this); 0130 d->ui.setupUi(d->wdg); 0131 layout->addWidget(d->wdg); 0132 setGuiEnabled(false); 0133 0134 d->buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0135 0136 layout->addWidget(d->wdg); 0137 layout->addWidget(d->buttonBox); 0138 0139 // d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN ); 0140 fillDictionaryComboBox(); 0141 d->restart = false; 0142 0143 d->suggestionsModel = new ReadOnlyStringListModel(this); 0144 d->ui.m_suggestions->setModel(d->suggestionsModel); 0145 } 0146 0147 void Dialog::activeAutoCorrect(bool _active) 0148 { 0149 if (_active) { 0150 d->ui.m_autoCorrect->show(); 0151 } else { 0152 d->ui.m_autoCorrect->hide(); 0153 } 0154 } 0155 0156 void Dialog::showProgressDialog(int timeout) 0157 { 0158 d->progressDialogTimeout = timeout; 0159 } 0160 0161 void Dialog::showSpellCheckCompletionMessage(bool b) 0162 { 0163 d->showCompletionMessageBox = b; 0164 } 0165 0166 void Dialog::setSpellCheckContinuedAfterReplacement(bool b) 0167 { 0168 d->spellCheckContinuedAfterReplacement = b; 0169 } 0170 0171 void Dialog::slotAutocorrect() 0172 { 0173 setGuiEnabled(false); 0174 setProgressDialogVisible(true); 0175 Q_EMIT autoCorrect(d->currentWord, d->ui.m_replacement->text()); 0176 slotReplaceWord(); 0177 } 0178 0179 void Dialog::setGuiEnabled(bool b) 0180 { 0181 d->wdg->setEnabled(b); 0182 } 0183 0184 void Dialog::setProgressDialogVisible(bool b) 0185 { 0186 if (!b) { 0187 d->deleteProgressDialog(true); 0188 } else if (d->progressDialogTimeout >= 0) { 0189 if (d->progressDialog) { 0190 return; 0191 } 0192 d->progressDialog = new QProgressDialog(this); 0193 d->progressDialog->setLabelText(tr("Spell checking in progress...", "progress label")); 0194 d->progressDialog->setWindowTitle(tr("Check Spelling", "@title:window")); 0195 d->progressDialog->setModal(true); 0196 d->progressDialog->setAutoClose(false); 0197 d->progressDialog->setAutoReset(false); 0198 // create an 'indefinite' progress box as we currently cannot get progress feedback from 0199 // the speller 0200 d->progressDialog->reset(); 0201 d->progressDialog->setRange(0, 0); 0202 d->progressDialog->setValue(0); 0203 connect(d->progressDialog, &QProgressDialog::canceled, this, &Dialog::slotCancel); 0204 d->progressDialog->setMinimumDuration(d->progressDialogTimeout); 0205 } 0206 } 0207 0208 void Dialog::slotFinished() 0209 { 0210 setProgressDialogVisible(false); 0211 Q_EMIT stop(); 0212 // FIXME: should we emit done here? 0213 Q_EMIT spellCheckDone(d->checker->text()); 0214 Q_EMIT spellCheckStatus(tr("Spell check stopped.")); 0215 accept(); 0216 } 0217 0218 void Dialog::slotCancel() 0219 { 0220 d->canceled = true; 0221 d->deleteProgressDialog(false); // this method can be called in response to 0222 // pressing 'Cancel' on the dialog 0223 Q_EMIT cancel(); 0224 Q_EMIT spellCheckStatus(tr("Spell check canceled.")); 0225 reject(); 0226 } 0227 0228 QString Dialog::originalBuffer() const 0229 { 0230 return d->originalBuffer; 0231 } 0232 0233 QString Dialog::buffer() const 0234 { 0235 return d->checker->text(); 0236 } 0237 0238 void Dialog::setBuffer(const QString &buf) 0239 { 0240 d->originalBuffer = buf; 0241 // it is possible to change buffer inside slot connected to done() signal 0242 d->restart = true; 0243 } 0244 0245 void Dialog::fillDictionaryComboBox() 0246 { 0247 // Since m_language is changed to DictionaryComboBox most code here is gone, 0248 // So fillDictionaryComboBox() could be removed and code moved to initGui() 0249 // because the call in show() looks obsolete 0250 Speller speller = d->checker->speller(); 0251 d->dictsMap = speller.availableDictionaries(); 0252 0253 updateDictionaryComboBox(); 0254 } 0255 0256 void Dialog::updateDictionaryComboBox() 0257 { 0258 const Speller &speller = d->checker->speller(); 0259 d->ui.m_language->setCurrentByDictionary(speller.language()); 0260 } 0261 0262 void Dialog::updateDialog(const QString &word) 0263 { 0264 d->ui.m_unknownWord->setText(word); 0265 d->ui.m_contextLabel->setText(d->checker->currentContext()); 0266 const QStringList suggs = d->checker->suggest(word); 0267 0268 if (suggs.isEmpty()) { 0269 d->ui.m_replacement->clear(); 0270 } else { 0271 d->ui.m_replacement->setText(suggs.first()); 0272 } 0273 fillSuggestions(suggs); 0274 } 0275 0276 void Dialog::show() 0277 { 0278 d->canceled = false; 0279 fillDictionaryComboBox(); 0280 if (d->originalBuffer.isEmpty()) { 0281 d->checker->start(); 0282 } else { 0283 d->checker->setText(d->originalBuffer); 0284 } 0285 setProgressDialogVisible(true); 0286 } 0287 0288 void Dialog::slotAddWord() 0289 { 0290 setGuiEnabled(false); 0291 setProgressDialogVisible(true); 0292 d->checker->addWordToPersonal(d->currentWord); 0293 d->checker->continueChecking(); 0294 } 0295 0296 void Dialog::slotReplaceWord() 0297 { 0298 setGuiEnabled(false); 0299 setProgressDialogVisible(true); 0300 QString replacementText = d->ui.m_replacement->text(); 0301 Q_EMIT replace(d->currentWord, d->currentPosition, replacementText); 0302 0303 if (d->spellCheckContinuedAfterReplacement) { 0304 d->checker->replace(d->currentPosition, d->currentWord, replacementText); 0305 d->checker->continueChecking(); 0306 } else { 0307 d->checker->stop(); 0308 } 0309 } 0310 0311 void Dialog::slotReplaceAll() 0312 { 0313 setGuiEnabled(false); 0314 setProgressDialogVisible(true); 0315 d->replaceAllMap.insert(d->currentWord, d->ui.m_replacement->text()); 0316 slotReplaceWord(); 0317 } 0318 0319 void Dialog::slotSkip() 0320 { 0321 setGuiEnabled(false); 0322 setProgressDialogVisible(true); 0323 d->checker->continueChecking(); 0324 } 0325 0326 void Dialog::slotSkipAll() 0327 { 0328 setGuiEnabled(false); 0329 setProgressDialogVisible(true); 0330 //### do we want that or should we have a d->ignoreAll list? 0331 Speller speller = d->checker->speller(); 0332 speller.addToPersonal(d->currentWord); 0333 d->checker->setSpeller(speller); 0334 d->checker->continueChecking(); 0335 } 0336 0337 void Dialog::slotSuggest() 0338 { 0339 const QStringList suggs = d->checker->suggest(d->ui.m_replacement->text()); 0340 fillSuggestions(suggs); 0341 } 0342 0343 void Dialog::slotChangeLanguage(const QString &lang) 0344 { 0345 const QString languageCode = d->dictsMap[lang]; 0346 if (!languageCode.isEmpty()) { 0347 d->checker->changeLanguage(languageCode); 0348 slotSuggest(); 0349 Q_EMIT languageChanged(languageCode); 0350 } 0351 } 0352 0353 void Dialog::slotSelectionChanged(const QModelIndex &item) 0354 { 0355 d->ui.m_replacement->setText(item.data().toString()); 0356 } 0357 0358 void Dialog::fillSuggestions(const QStringList &suggs) 0359 { 0360 d->suggestionsModel->setStringList(suggs); 0361 } 0362 0363 void Dialog::slotMisspelling(const QString &word, int start) 0364 { 0365 setGuiEnabled(true); 0366 setProgressDialogVisible(false); 0367 Q_EMIT misspelling(word, start); 0368 // NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods 0369 // this dramatically reduces spellchecking time in Lokalize 0370 // as this doesn't fetch suggestions for words that are present in msgid 0371 if (!updatesEnabled()) { 0372 return; 0373 } 0374 0375 d->currentWord = word; 0376 d->currentPosition = start; 0377 if (d->replaceAllMap.contains(word)) { 0378 d->ui.m_replacement->setText(d->replaceAllMap[word]); 0379 slotReplaceWord(); 0380 } else { 0381 updateDialog(word); 0382 } 0383 QDialog::show(); 0384 } 0385 0386 void Dialog::slotDone() 0387 { 0388 d->restart = false; 0389 Q_EMIT spellCheckDone(d->checker->text()); 0390 if (d->restart) { 0391 updateDictionaryComboBox(); 0392 d->checker->setText(d->originalBuffer); 0393 d->restart = false; 0394 } else { 0395 setProgressDialogVisible(false); 0396 Q_EMIT spellCheckStatus(tr("Spell check complete.")); 0397 accept(); 0398 if (!d->canceled && d->showCompletionMessageBox) { 0399 QMessageBox::information(this, tr("Spell check complete."), tr("Check Spelling", "@title:window")); 0400 } 0401 } 0402 } 0403 } 0404 0405 #include "moc_dialog.cpp"