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