File indexing completed on 2024-04-28 11:49:03

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()
0100 {
0101     delete d;
0102 }
0103 
0104 void Dialog::initConnections()
0105 {
0106     connect(d->ui.m_addBtn, &QAbstractButton::clicked, this, &Dialog::slotAddWord);
0107     connect(d->ui.m_replaceBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceWord);
0108     connect(d->ui.m_replaceAllBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceAll);
0109     connect(d->ui.m_skipBtn, &QAbstractButton::clicked, this, &Dialog::slotSkip);
0110     connect(d->ui.m_skipAllBtn, &QAbstractButton::clicked, this, &Dialog::slotSkipAll);
0111     connect(d->ui.m_suggestBtn, &QAbstractButton::clicked, this, &Dialog::slotSuggest);
0112     connect(d->ui.m_language, &DictionaryComboBox::textActivated, this, &Dialog::slotChangeLanguage);
0113     connect(d->ui.m_suggestions, &QListView::clicked, this, &Dialog::slotSelectionChanged);
0114     connect(d->checker, &BackgroundChecker::misspelling, this, &Dialog::slotMisspelling);
0115     connect(d->checker, &BackgroundChecker::done, this, &Dialog::slotDone);
0116     connect(d->ui.m_suggestions, &QListView::doubleClicked, this, [this](const QModelIndex &) {
0117         slotReplaceWord();
0118     });
0119     connect(d->buttonBox, &QDialogButtonBox::accepted, this, &Dialog::slotFinished);
0120     connect(d->buttonBox, &QDialogButtonBox::rejected, this, &Dialog::slotCancel);
0121     connect(d->ui.m_replacement, &QLineEdit::returnPressed, this, &Dialog::slotReplaceWord);
0122     connect(d->ui.m_autoCorrect, &QPushButton::clicked, this, &Dialog::slotAutocorrect);
0123     // button use by kword/kpresenter
0124     // hide by default
0125     d->ui.m_autoCorrect->hide();
0126 }
0127 
0128 void Dialog::initGui()
0129 {
0130     QVBoxLayout *layout = new QVBoxLayout(this);
0131 
0132     d->wdg = new QWidget(this);
0133     d->ui.setupUi(d->wdg);
0134     layout->addWidget(d->wdg);
0135     setGuiEnabled(false);
0136 
0137     d->buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0138 
0139     layout->addWidget(d->wdg);
0140     layout->addWidget(d->buttonBox);
0141 
0142     // d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN );
0143     fillDictionaryComboBox();
0144     d->restart = false;
0145 
0146     d->suggestionsModel = new ReadOnlyStringListModel(this);
0147     d->ui.m_suggestions->setModel(d->suggestionsModel);
0148 }
0149 
0150 void Dialog::activeAutoCorrect(bool _active)
0151 {
0152     if (_active) {
0153         d->ui.m_autoCorrect->show();
0154     } else {
0155         d->ui.m_autoCorrect->hide();
0156     }
0157 }
0158 
0159 void Dialog::showProgressDialog(int timeout)
0160 {
0161     d->progressDialogTimeout = timeout;
0162 }
0163 
0164 void Dialog::showSpellCheckCompletionMessage(bool b)
0165 {
0166     d->showCompletionMessageBox = b;
0167 }
0168 
0169 void Dialog::setSpellCheckContinuedAfterReplacement(bool b)
0170 {
0171     d->spellCheckContinuedAfterReplacement = b;
0172 }
0173 
0174 void Dialog::slotAutocorrect()
0175 {
0176     setGuiEnabled(false);
0177     setProgressDialogVisible(true);
0178     Q_EMIT autoCorrect(d->currentWord, d->ui.m_replacement->text());
0179     slotReplaceWord();
0180 }
0181 
0182 void Dialog::setGuiEnabled(bool b)
0183 {
0184     d->wdg->setEnabled(b);
0185 }
0186 
0187 void Dialog::setProgressDialogVisible(bool b)
0188 {
0189     if (!b) {
0190         d->deleteProgressDialog(true);
0191     } else if (d->progressDialogTimeout >= 0) {
0192         if (d->progressDialog) {
0193             return;
0194         }
0195         d->progressDialog = new QProgressDialog(this);
0196         d->progressDialog->setLabelText(tr("Spell checking in progress...", "progress label"));
0197         d->progressDialog->setWindowTitle(tr("Check Spelling", "@title:window"));
0198         d->progressDialog->setModal(true);
0199         d->progressDialog->setAutoClose(false);
0200         d->progressDialog->setAutoReset(false);
0201         // create an 'indefinite' progress box as we currently cannot get progress feedback from
0202         // the speller
0203         d->progressDialog->reset();
0204         d->progressDialog->setRange(0, 0);
0205         d->progressDialog->setValue(0);
0206         connect(d->progressDialog, &QProgressDialog::canceled, this, &Dialog::slotCancel);
0207         d->progressDialog->setMinimumDuration(d->progressDialogTimeout);
0208     }
0209 }
0210 
0211 void Dialog::slotFinished()
0212 {
0213     setProgressDialogVisible(false);
0214     Q_EMIT stop();
0215     // FIXME: should we emit done here?
0216 #if SONNETUI_BUILD_DEPRECATED_SINCE(5, 65)
0217     Q_EMIT done(d->checker->text());
0218 #endif
0219     Q_EMIT spellCheckDone(d->checker->text());
0220     Q_EMIT spellCheckStatus(tr("Spell check stopped."));
0221     accept();
0222 }
0223 
0224 void Dialog::slotCancel()
0225 {
0226     d->canceled = true;
0227     d->deleteProgressDialog(false); // this method can be called in response to
0228     // pressing 'Cancel' on the dialog
0229     Q_EMIT cancel();
0230     Q_EMIT spellCheckStatus(tr("Spell check canceled."));
0231     reject();
0232 }
0233 
0234 QString Dialog::originalBuffer() const
0235 {
0236     return d->originalBuffer;
0237 }
0238 
0239 QString Dialog::buffer() const
0240 {
0241     return d->checker->text();
0242 }
0243 
0244 void Dialog::setBuffer(const QString &buf)
0245 {
0246     d->originalBuffer = buf;
0247     // it is possible to change buffer inside slot connected to done() signal
0248     d->restart = true;
0249 }
0250 
0251 void Dialog::fillDictionaryComboBox()
0252 {
0253     // Since m_language is changed to DictionaryComboBox most code here is gone,
0254     // So fillDictionaryComboBox() could be removed and code moved to initGui()
0255     // because the call in show() looks obsolete
0256     Speller speller = d->checker->speller();
0257     d->dictsMap = speller.availableDictionaries();
0258 
0259     updateDictionaryComboBox();
0260 }
0261 
0262 void Dialog::updateDictionaryComboBox()
0263 {
0264     const Speller &speller = d->checker->speller();
0265     d->ui.m_language->setCurrentByDictionary(speller.language());
0266 }
0267 
0268 void Dialog::updateDialog(const QString &word)
0269 {
0270     d->ui.m_unknownWord->setText(word);
0271     d->ui.m_contextLabel->setText(d->checker->currentContext());
0272     const QStringList suggs = d->checker->suggest(word);
0273 
0274     if (suggs.isEmpty()) {
0275         d->ui.m_replacement->clear();
0276     } else {
0277         d->ui.m_replacement->setText(suggs.first());
0278     }
0279     fillSuggestions(suggs);
0280 }
0281 
0282 void Dialog::show()
0283 {
0284     d->canceled = false;
0285     fillDictionaryComboBox();
0286     if (d->originalBuffer.isEmpty()) {
0287         d->checker->start();
0288     } else {
0289         d->checker->setText(d->originalBuffer);
0290     }
0291     setProgressDialogVisible(true);
0292 }
0293 
0294 void Dialog::slotAddWord()
0295 {
0296     setGuiEnabled(false);
0297     setProgressDialogVisible(true);
0298     d->checker->addWordToPersonal(d->currentWord);
0299     d->checker->continueChecking();
0300 }
0301 
0302 void Dialog::slotReplaceWord()
0303 {
0304     setGuiEnabled(false);
0305     setProgressDialogVisible(true);
0306     QString replacementText = d->ui.m_replacement->text();
0307     Q_EMIT replace(d->currentWord, d->currentPosition, replacementText);
0308 
0309     if (d->spellCheckContinuedAfterReplacement) {
0310         d->checker->replace(d->currentPosition, d->currentWord, replacementText);
0311         d->checker->continueChecking();
0312     } else {
0313         d->checker->stop();
0314     }
0315 }
0316 
0317 void Dialog::slotReplaceAll()
0318 {
0319     setGuiEnabled(false);
0320     setProgressDialogVisible(true);
0321     d->replaceAllMap.insert(d->currentWord, d->ui.m_replacement->text());
0322     slotReplaceWord();
0323 }
0324 
0325 void Dialog::slotSkip()
0326 {
0327     setGuiEnabled(false);
0328     setProgressDialogVisible(true);
0329     d->checker->continueChecking();
0330 }
0331 
0332 void Dialog::slotSkipAll()
0333 {
0334     setGuiEnabled(false);
0335     setProgressDialogVisible(true);
0336     //### do we want that or should we have a d->ignoreAll list?
0337     Speller speller = d->checker->speller();
0338     speller.addToPersonal(d->currentWord);
0339     d->checker->setSpeller(speller);
0340     d->checker->continueChecking();
0341 }
0342 
0343 void Dialog::slotSuggest()
0344 {
0345     const QStringList suggs = d->checker->suggest(d->ui.m_replacement->text());
0346     fillSuggestions(suggs);
0347 }
0348 
0349 void Dialog::slotChangeLanguage(const QString &lang)
0350 {
0351     const QString languageCode = d->dictsMap[lang];
0352     if (!languageCode.isEmpty()) {
0353         d->checker->changeLanguage(languageCode);
0354         slotSuggest();
0355         Q_EMIT languageChanged(languageCode);
0356     }
0357 }
0358 
0359 void Dialog::slotSelectionChanged(const QModelIndex &item)
0360 {
0361     d->ui.m_replacement->setText(item.data().toString());
0362 }
0363 
0364 void Dialog::fillSuggestions(const QStringList &suggs)
0365 {
0366     d->suggestionsModel->setStringList(suggs);
0367 }
0368 
0369 void Dialog::slotMisspelling(const QString &word, int start)
0370 {
0371     setGuiEnabled(true);
0372     setProgressDialogVisible(false);
0373     Q_EMIT misspelling(word, start);
0374     // NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods
0375     // this dramatically reduces spellchecking time in Lokalize
0376     // as this doesn't fetch suggestions for words that are present in msgid
0377     if (!updatesEnabled()) {
0378         return;
0379     }
0380 
0381     d->currentWord = word;
0382     d->currentPosition = start;
0383     if (d->replaceAllMap.contains(word)) {
0384         d->ui.m_replacement->setText(d->replaceAllMap[word]);
0385         slotReplaceWord();
0386     } else {
0387         updateDialog(word);
0388     }
0389     QDialog::show();
0390 }
0391 
0392 void Dialog::slotDone()
0393 {
0394     d->restart = false;
0395 #if SONNETUI_BUILD_DEPRECATED_SINCE(5, 65)
0396     Q_EMIT done(d->checker->text());
0397 #endif
0398     Q_EMIT spellCheckDone(d->checker->text());
0399     if (d->restart) {
0400         updateDictionaryComboBox();
0401         d->checker->setText(d->originalBuffer);
0402         d->restart = false;
0403     } else {
0404         setProgressDialogVisible(false);
0405         Q_EMIT spellCheckStatus(tr("Spell check complete."));
0406         accept();
0407         if (!d->canceled && d->showCompletionMessageBox) {
0408             QMessageBox::information(this, tr("Spell check complete."), tr("Check Spelling", "@title:window"));
0409         }
0410     }
0411 }
0412 }
0413 
0414 #include "moc_dialog.cpp"