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"