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"