File indexing completed on 2024-04-14 03:55:24

0001 /*
0002     SPDX-FileCopyrightText: 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net>
0003     SPDX-FileCopyrightText: 2008 Mirko Stocker <me@misto.ch>
0004     SPDX-FileCopyrightText: 2004-2005 Anders Lund <anders@alweb.dk>
0005     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0006     SPDX-FileCopyrightText: 2001-2004 Christoph Cullmann <cullmann@kde.org>
0007     SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
0008     SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 
0013 #include "spellcheckdialog.h"
0014 
0015 #include "katedocument.h"
0016 #include "kateglobal.h"
0017 #include "kateview.h"
0018 #include "spellcheck/spellcheck.h"
0019 #include "spellcheck/spellcheckbar.h"
0020 
0021 #include <KActionCollection>
0022 #include <KLocalizedString>
0023 #include <KStandardAction>
0024 
0025 #include <sonnet/backgroundchecker.h>
0026 #include <sonnet/speller.h>
0027 
0028 KateSpellCheckDialog::KateSpellCheckDialog(KTextEditor::ViewPrivate *view)
0029     : QObject(view)
0030     , m_view(view)
0031     , m_speller(nullptr)
0032     , m_backgroundChecker(nullptr)
0033     , m_sonnetDialog(nullptr)
0034     , m_globalSpellCheckRange(nullptr)
0035     , m_spellCheckCancelledByUser(false)
0036 {
0037 }
0038 
0039 KateSpellCheckDialog::~KateSpellCheckDialog()
0040 {
0041     delete m_globalSpellCheckRange;
0042     delete m_sonnetDialog;
0043     delete m_backgroundChecker;
0044     delete m_speller;
0045 }
0046 
0047 void KateSpellCheckDialog::createActions(KActionCollection *ac)
0048 {
0049     ac->addAction(KStandardAction::Spelling, this, SLOT(spellcheck()));
0050 
0051     QAction *a = new QAction(i18n("Spelling (from Cursor)..."), this);
0052     ac->addAction(QStringLiteral("tools_spelling_from_cursor"), a);
0053     a->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling")));
0054     a->setWhatsThis(i18n("Check the document's spelling from the cursor and forward"));
0055     connect(a, &QAction::triggered, this, &KateSpellCheckDialog::spellcheckFromCursor);
0056 }
0057 
0058 void KateSpellCheckDialog::spellcheckFromCursor()
0059 {
0060     if (m_view->selection()) {
0061         spellcheckSelection();
0062     } else {
0063         spellcheck(m_view->cursorPosition());
0064     }
0065 }
0066 
0067 void KateSpellCheckDialog::spellcheckSelection()
0068 {
0069     spellcheck(m_view->selectionRange().start(), m_view->selectionRange().end());
0070 }
0071 
0072 void KateSpellCheckDialog::spellcheck()
0073 {
0074     if (m_view->selection()) {
0075         spellcheckSelection();
0076     } else {
0077         spellcheck(KTextEditor::Cursor(0, 0));
0078     }
0079 }
0080 
0081 void KateSpellCheckDialog::spellcheck(const KTextEditor::Cursor from, const KTextEditor::Cursor to)
0082 {
0083     KTextEditor::Cursor start = from;
0084     KTextEditor::Cursor end = to;
0085 
0086     if (end.line() == 0 && end.column() == 0) {
0087         end = m_view->doc()->documentEnd();
0088     }
0089 
0090     if (!m_speller) {
0091         m_speller = new Sonnet::Speller();
0092     }
0093     m_speller->restore();
0094 
0095     if (!m_backgroundChecker) {
0096         m_backgroundChecker = new Sonnet::BackgroundChecker(*m_speller);
0097     }
0098 
0099     if (!m_sonnetDialog) {
0100         m_sonnetDialog = new SpellCheckBar(m_backgroundChecker, m_view);
0101         m_sonnetDialog->showProgressDialog(200);
0102         m_sonnetDialog->showSpellCheckCompletionMessage();
0103         m_sonnetDialog->setSpellCheckContinuedAfterReplacement(false);
0104 
0105         connect(m_sonnetDialog, &SpellCheckBar::done, this, &KateSpellCheckDialog::installNextSpellCheckRange);
0106 
0107         connect(m_sonnetDialog, &SpellCheckBar::replace, this, &KateSpellCheckDialog::corrected);
0108 
0109         connect(m_sonnetDialog, &SpellCheckBar::misspelling, this, &KateSpellCheckDialog::misspelling);
0110 
0111         connect(m_sonnetDialog, &SpellCheckBar::cancel, this, &KateSpellCheckDialog::cancelClicked);
0112 
0113         connect(m_sonnetDialog, &SpellCheckBar::destroyed, this, &KateSpellCheckDialog::objectDestroyed);
0114 
0115         connect(m_sonnetDialog, &SpellCheckBar::languageChanged, this, &KateSpellCheckDialog::languageChanged);
0116     }
0117 
0118     m_view->bottomViewBar()->addBarWidget(m_sonnetDialog);
0119 
0120     m_userSpellCheckLanguage.clear();
0121     m_previousGivenSpellCheckLanguage.clear();
0122     delete m_globalSpellCheckRange;
0123     // we expand to handle the situation when the last word in the range is replace by a new one
0124     m_globalSpellCheckRange =
0125         m_view->doc()->newMovingRange(KTextEditor::Range(start, end), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
0126     m_spellCheckCancelledByUser = false;
0127     performSpellCheck(*m_globalSpellCheckRange);
0128 }
0129 
0130 KTextEditor::Cursor KateSpellCheckDialog::locatePosition(int pos)
0131 {
0132     uint remains;
0133 
0134     while (m_spellLastPos < (uint)pos) {
0135         remains = pos - m_spellLastPos;
0136         uint l = m_view->doc()->lineLength(m_spellPosCursor.line()) - m_spellPosCursor.column();
0137         if (l > remains) {
0138             m_spellPosCursor.setColumn(m_spellPosCursor.column() + remains);
0139             m_spellLastPos = pos;
0140         } else {
0141             m_spellPosCursor.setLine(m_spellPosCursor.line() + 1);
0142             m_spellPosCursor.setColumn(0);
0143             m_spellLastPos += l + 1;
0144         }
0145     }
0146 
0147     return m_spellPosCursor;
0148 }
0149 
0150 void KateSpellCheckDialog::misspelling(const QString &word, int pos)
0151 {
0152     KTextEditor::Cursor cursor;
0153     int length;
0154     int origPos = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos);
0155     cursor = locatePosition(origPos);
0156     length = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos + word.length()) - origPos;
0157 
0158     m_view->setCursorPositionInternal(cursor, 1);
0159     m_view->setSelection(KTextEditor::Range(cursor, length));
0160 }
0161 
0162 void KateSpellCheckDialog::corrected(const QString &word, int pos, const QString &newWord)
0163 {
0164     int origPos = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos);
0165 
0166     int length = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos + word.length()) - origPos;
0167 
0168     KTextEditor::Cursor replacementStartCursor = locatePosition(origPos);
0169     KTextEditor::Range replacementRange = KTextEditor::Range(replacementStartCursor, length);
0170     KTextEditor::DocumentPrivate *doc = m_view->doc();
0171     KTextEditor::EditorPrivate::self()->spellCheckManager()->replaceCharactersEncodedIfNecessary(newWord, doc, replacementRange);
0172 
0173     // we have to be careful here: due to static word wrapping the text might change in addition to simply
0174     // the misspelled word being replaced, i.e. new line breaks might be inserted as well. As such, the text
0175     // in the 'Sonnet::Dialog' might be eventually out of sync with the visible text. Therefore, we 'restart'
0176     // spell checking from the current position.
0177     performSpellCheck(KTextEditor::Range(replacementStartCursor, m_globalSpellCheckRange->end()));
0178 }
0179 
0180 void KateSpellCheckDialog::performSpellCheck(KTextEditor::Range range)
0181 {
0182     if (range.isEmpty()) {
0183         spellCheckDone();
0184         m_sonnetDialog->closed();
0185         return;
0186     }
0187     m_languagesInSpellCheckRange = KTextEditor::EditorPrivate::self()->spellCheckManager()->spellCheckLanguageRanges(m_view->doc(), range);
0188     m_currentLanguageRangeIterator = m_languagesInSpellCheckRange.begin();
0189     m_currentSpellCheckRange = KTextEditor::Range::invalid();
0190     installNextSpellCheckRange();
0191     // first check if there is really something to spell check
0192     if (m_currentSpellCheckRange.isValid()) {
0193         m_view->bottomViewBar()->showBarWidget(m_sonnetDialog);
0194         m_sonnetDialog->show();
0195         m_sonnetDialog->setFocus();
0196     } else {
0197         m_sonnetDialog->closed();
0198     }
0199 }
0200 
0201 void KateSpellCheckDialog::installNextSpellCheckRange()
0202 {
0203     if (m_spellCheckCancelledByUser || m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end()) {
0204         spellCheckDone();
0205         return;
0206     }
0207     KateSpellCheckManager *spellCheckManager = KTextEditor::EditorPrivate::self()->spellCheckManager();
0208     KTextEditor::Cursor nextRangeBegin = (m_currentSpellCheckRange.isValid() ? m_currentSpellCheckRange.end() : KTextEditor::Cursor::invalid());
0209     m_currentSpellCheckRange = KTextEditor::Range::invalid();
0210     m_currentDecToEncOffsetList.clear();
0211     QList<QPair<KTextEditor::Range, QString>> rangeDictionaryPairList;
0212     while (m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end()) {
0213         KTextEditor::Range currentLanguageRange = (*m_currentLanguageRangeIterator).first;
0214         const QString &dictionary = (*m_currentLanguageRangeIterator).second;
0215         KTextEditor::Range languageSubRange =
0216             (nextRangeBegin.isValid() ? KTextEditor::Range(nextRangeBegin, currentLanguageRange.end()) : currentLanguageRange);
0217         rangeDictionaryPairList = spellCheckManager->spellCheckWrtHighlightingRanges(m_view->doc(), languageSubRange, dictionary, false, true);
0218         Q_ASSERT(rangeDictionaryPairList.size() <= 1);
0219         if (rangeDictionaryPairList.size() == 0) {
0220             ++m_currentLanguageRangeIterator;
0221             if (m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end()) {
0222                 nextRangeBegin = (*m_currentLanguageRangeIterator).first.start();
0223             }
0224         } else {
0225             m_currentSpellCheckRange = rangeDictionaryPairList.first().first;
0226             QString dictionary = rangeDictionaryPairList.first().second;
0227             const bool languageChanged = (dictionary != m_previousGivenSpellCheckLanguage);
0228             m_previousGivenSpellCheckLanguage = dictionary;
0229 
0230             // if there was no change of dictionary stemming from the document language ranges and
0231             // the user has set a dictionary in the dialog, we use that one
0232             if (!languageChanged && !m_userSpellCheckLanguage.isEmpty()) {
0233                 dictionary = m_userSpellCheckLanguage;
0234             }
0235             // we only allow the user to override the preset dictionary within a language range
0236             // given by the document
0237             else if (languageChanged) {
0238                 m_userSpellCheckLanguage.clear();
0239             }
0240 
0241             m_spellPosCursor = m_currentSpellCheckRange.start();
0242             m_spellLastPos = 0;
0243 
0244             m_currentDecToEncOffsetList.clear();
0245             KTextEditor::DocumentPrivate::OffsetList encToDecOffsetList;
0246             QString text = m_view->doc()->decodeCharacters(m_currentSpellCheckRange, m_currentDecToEncOffsetList, encToDecOffsetList);
0247             // ensure that no empty string is passed on to Sonnet as this can lead to a crash
0248             // (bug 228789)
0249             if (text.isEmpty()) {
0250                 nextRangeBegin = m_currentSpellCheckRange.end();
0251                 continue;
0252             }
0253 
0254             if (m_speller->language() != dictionary) {
0255                 m_speller->setLanguage(dictionary);
0256                 m_backgroundChecker->setSpeller(*m_speller);
0257             }
0258 
0259             m_sonnetDialog->setBuffer(text);
0260             break;
0261         }
0262     }
0263     if (m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end()) {
0264         spellCheckDone();
0265         return;
0266     }
0267 }
0268 
0269 void KateSpellCheckDialog::cancelClicked()
0270 {
0271     m_spellCheckCancelledByUser = true;
0272     spellCheckDone();
0273 }
0274 
0275 void KateSpellCheckDialog::spellCheckDone()
0276 {
0277     m_currentSpellCheckRange = KTextEditor::Range::invalid();
0278     m_currentDecToEncOffsetList.clear();
0279     m_view->clearSelection();
0280 }
0281 
0282 void KateSpellCheckDialog::objectDestroyed(QObject *object)
0283 {
0284     Q_UNUSED(object);
0285     m_sonnetDialog = nullptr;
0286 }
0287 
0288 void KateSpellCheckDialog::languageChanged(const QString &language)
0289 {
0290     m_userSpellCheckLanguage = language;
0291 }
0292 
0293 // END
0294 
0295 #include "moc_spellcheckdialog.cpp"