File indexing completed on 2024-04-21 04:00:57
0001 // SPDX-FileCopyrightText: 2013 Aurélien Gâteau <agateau@kde.org> 0002 // SPDX-FileCopyrightText: 2020 Christian Mollekopf <mollekopf@kolabsystems.com> 0003 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> 0004 // SPDX-License-Identifier: LGPL-2.1-or-later 0005 0006 #include "spellcheckhighlighter.h" 0007 #include "guesslanguage.h" 0008 #include "languagefilter_p.h" 0009 #include "loader_p.h" 0010 #include "settingsimpl_p.h" 0011 #include "speller.h" 0012 #include "tokenizer_p.h" 0013 0014 #include "quick_debug.h" 0015 0016 #include <QColor> 0017 #include <QHash> 0018 #include <QKeyEvent> 0019 #include <QMetaMethod> 0020 #include <QTextBoundaryFinder> 0021 #include <QTextCharFormat> 0022 #include <QTextCursor> 0023 #include <QTimer> 0024 #include <memory> 0025 0026 using namespace Sonnet; 0027 0028 // Cache of previously-determined languages (when using AutoDetectLanguage) 0029 // There is one such cache per block (paragraph) 0030 class LanguageCache : public QTextBlockUserData 0031 { 0032 public: 0033 // Key: QPair<start, length> 0034 // Value: language name 0035 QMap<QPair<int, int>, QString> languages; 0036 0037 // Remove all cached language information after @p pos 0038 void invalidate(int pos) 0039 { 0040 QMutableMapIterator<QPair<int, int>, QString> it(languages); 0041 it.toBack(); 0042 while (it.hasPrevious()) { 0043 it.previous(); 0044 if (it.key().first + it.key().second >= pos) { 0045 it.remove(); 0046 } else { 0047 break; 0048 } 0049 } 0050 } 0051 0052 QString languageAtPos(int pos) const 0053 { 0054 // The data structure isn't really great for such lookups... 0055 QMapIterator<QPair<int, int>, QString> it(languages); 0056 while (it.hasNext()) { 0057 it.next(); 0058 if (it.key().first <= pos && it.key().first + it.key().second >= pos) { 0059 return it.value(); 0060 } 0061 } 0062 return QString(); 0063 } 0064 }; 0065 0066 class HighlighterPrivate 0067 { 0068 public: 0069 HighlighterPrivate(SpellcheckHighlighter *qq) 0070 : q(qq) 0071 { 0072 tokenizer = std::make_unique<WordTokenizer>(); 0073 active = true; 0074 automatic = false; 0075 autoDetectLanguageDisabled = false; 0076 connected = false; 0077 wordCount = 0; 0078 errorCount = 0; 0079 intraWordEditing = false; 0080 completeRehighlightRequired = false; 0081 spellColor = spellColor.isValid() ? spellColor : Qt::red; 0082 languageFilter = std::make_unique<LanguageFilter>(new SentenceTokenizer()); 0083 0084 loader = Loader::openLoader(); 0085 loader->settings()->restore(); 0086 0087 spellchecker = std::make_unique<Speller>(); 0088 spellCheckerFound = spellchecker->isValid(); 0089 rehighlightRequest = new QTimer(q); 0090 q->connect(rehighlightRequest, &QTimer::timeout, q, &SpellcheckHighlighter::slotRehighlight); 0091 0092 if (!spellCheckerFound) { 0093 return; 0094 } 0095 0096 disablePercentage = loader->settings()->disablePercentageWordError(); 0097 disableWordCount = loader->settings()->disableWordErrorCount(); 0098 0099 completeRehighlightRequired = true; 0100 rehighlightRequest->setInterval(0); 0101 rehighlightRequest->setSingleShot(true); 0102 rehighlightRequest->start(); 0103 0104 // Danger red from our color scheme 0105 errorFormat.setForeground(spellColor); 0106 errorFormat.setUnderlineColor(spellColor); 0107 errorFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); 0108 0109 selectedErrorFormat.setForeground(spellColor); 0110 auto bg = spellColor; 0111 bg.setAlphaF(0.1); 0112 selectedErrorFormat.setBackground(bg); 0113 selectedErrorFormat.setUnderlineColor(spellColor); 0114 selectedErrorFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); 0115 0116 quoteFormat.setForeground(QColor{"#7f8c8d"}); 0117 } 0118 0119 ~HighlighterPrivate(); 0120 std::unique_ptr<WordTokenizer> tokenizer; 0121 std::unique_ptr<LanguageFilter> languageFilter; 0122 Loader *loader = nullptr; 0123 std::unique_ptr<Speller> spellchecker; 0124 0125 QTextCharFormat errorFormat; 0126 QTextCharFormat selectedErrorFormat; 0127 QTextCharFormat quoteFormat; 0128 std::unique_ptr<Sonnet::GuessLanguage> languageGuesser; 0129 QString selectedWord; 0130 QQuickTextDocument *document = nullptr; 0131 int cursorPosition; 0132 int selectionStart; 0133 int selectionEnd; 0134 0135 int autoCompleteBeginPosition = -1; 0136 int autoCompleteEndPosition = -1; 0137 int wordIsMisspelled = false; 0138 bool active; 0139 bool automatic; 0140 bool autoDetectLanguageDisabled; 0141 bool completeRehighlightRequired; 0142 bool intraWordEditing; 0143 bool spellCheckerFound; // cached d->dict->isValid() value 0144 bool connected; 0145 int disablePercentage = 0; 0146 int disableWordCount = 0; 0147 int wordCount, errorCount; 0148 QTimer *rehighlightRequest = nullptr; 0149 QColor spellColor; 0150 SpellcheckHighlighter *const q; 0151 }; 0152 0153 HighlighterPrivate::~HighlighterPrivate() 0154 { 0155 } 0156 0157 SpellcheckHighlighter::SpellcheckHighlighter(QObject *parent) 0158 : QSyntaxHighlighter(parent) 0159 , d(new HighlighterPrivate(this)) 0160 { 0161 } 0162 0163 SpellcheckHighlighter::~SpellcheckHighlighter() 0164 { 0165 if (document()) { 0166 disconnect(document(), nullptr, this, nullptr); 0167 } 0168 } 0169 0170 bool SpellcheckHighlighter::spellCheckerFound() const 0171 { 0172 return d->spellCheckerFound; 0173 } 0174 0175 void SpellcheckHighlighter::slotRehighlight() 0176 { 0177 if (d->completeRehighlightRequired) { 0178 d->wordCount = 0; 0179 d->errorCount = 0; 0180 rehighlight(); 0181 } else { 0182 // rehighlight the current para only (undo/redo safe) 0183 QTextCursor cursor = textCursor(); 0184 if (cursor.hasSelection()) { 0185 cursor.clearSelection(); 0186 } 0187 cursor.insertText(QString()); 0188 } 0189 // if (d->checksDone == d->checksRequested) 0190 // d->completeRehighlightRequired = false; 0191 QTimer::singleShot(0, this, &SpellcheckHighlighter::slotAutoDetection); 0192 } 0193 0194 bool SpellcheckHighlighter::automatic() const 0195 { 0196 return d->automatic; 0197 } 0198 0199 bool SpellcheckHighlighter::autoDetectLanguageDisabled() const 0200 { 0201 return d->autoDetectLanguageDisabled; 0202 } 0203 0204 bool SpellcheckHighlighter::intraWordEditing() const 0205 { 0206 return d->intraWordEditing; 0207 } 0208 0209 void SpellcheckHighlighter::setIntraWordEditing(bool editing) 0210 { 0211 d->intraWordEditing = editing; 0212 } 0213 0214 void SpellcheckHighlighter::setAutomatic(bool automatic) 0215 { 0216 if (automatic == d->automatic) { 0217 return; 0218 } 0219 0220 d->automatic = automatic; 0221 if (d->automatic) { 0222 slotAutoDetection(); 0223 } 0224 } 0225 0226 void SpellcheckHighlighter::setAutoDetectLanguageDisabled(bool autoDetectDisabled) 0227 { 0228 d->autoDetectLanguageDisabled = autoDetectDisabled; 0229 } 0230 0231 void SpellcheckHighlighter::slotAutoDetection() 0232 { 0233 bool savedActive = d->active; 0234 0235 // don't disable just because 1 of 4 is misspelled. 0236 if (d->automatic && d->wordCount >= 10) { 0237 // tme = Too many errors 0238 /* clang-format off */ 0239 bool tme = (d->errorCount >= d->disableWordCount) 0240 && (d->errorCount * 100 >= d->disablePercentage * d->wordCount); 0241 /* clang-format on */ 0242 0243 if (d->active && tme) { 0244 d->active = false; 0245 } else if (!d->active && !tme) { 0246 d->active = true; 0247 } 0248 } 0249 0250 if (d->active != savedActive) { 0251 if (d->active) { 0252 Q_EMIT activeChanged(tr("As-you-type spell checking enabled.")); 0253 } else { 0254 qCDebug(SONNET_LOG_QUICK) << "Sonnet: Disabling spell checking, too many errors"; 0255 Q_EMIT activeChanged( 0256 tr("Too many misspelled words. " 0257 "As-you-type spell checking disabled.")); 0258 } 0259 0260 d->completeRehighlightRequired = true; 0261 d->rehighlightRequest->setInterval(100); 0262 d->rehighlightRequest->setSingleShot(true); 0263 } 0264 } 0265 0266 void SpellcheckHighlighter::setActive(bool active) 0267 { 0268 if (active == d->active) { 0269 return; 0270 } 0271 d->active = active; 0272 Q_EMIT activeChanged(); 0273 rehighlight(); 0274 0275 if (d->active) { 0276 Q_EMIT activeChanged(tr("As-you-type spell checking enabled.")); 0277 } else { 0278 Q_EMIT activeChanged(tr("As-you-type spell checking disabled.")); 0279 } 0280 } 0281 0282 bool SpellcheckHighlighter::active() const 0283 { 0284 return d->active; 0285 } 0286 0287 static bool hasNotEmptyText(const QString &text) 0288 { 0289 for (int i = 0; i < text.length(); ++i) { 0290 if (!text.at(i).isSpace()) { 0291 return true; 0292 } 0293 } 0294 return false; 0295 } 0296 0297 void SpellcheckHighlighter::contentsChange(int pos, int add, int rem) 0298 { 0299 // Invalidate the cache where the text has changed 0300 const QTextBlock &lastBlock = document()->findBlock(pos + add - rem); 0301 QTextBlock block = document()->findBlock(pos); 0302 do { 0303 LanguageCache *cache = dynamic_cast<LanguageCache *>(block.userData()); 0304 if (cache) { 0305 cache->invalidate(pos - block.position()); 0306 } 0307 block = block.next(); 0308 } while (block.isValid() && block < lastBlock); 0309 } 0310 0311 void SpellcheckHighlighter::highlightBlock(const QString &text) 0312 { 0313 if (!hasNotEmptyText(text) || !d->active || !d->spellCheckerFound) { 0314 return; 0315 } 0316 0317 // Avoid spellchecking quotes 0318 if (text.isEmpty() || text.at(0) == QLatin1Char('>')) { 0319 setFormat(0, text.length(), d->quoteFormat); 0320 return; 0321 } 0322 0323 if (!d->connected) { 0324 connect(textDocument(), &QTextDocument::contentsChange, this, &SpellcheckHighlighter::contentsChange); 0325 d->connected = true; 0326 } 0327 QTextCursor cursor = textCursor(); 0328 const int index = cursor.position() + 1; 0329 0330 const int lengthPosition = text.length() - 1; 0331 0332 if (index != lengthPosition // 0333 || (lengthPosition > 0 && !text[lengthPosition - 1].isLetter())) { 0334 d->languageFilter->setBuffer(text); 0335 0336 LanguageCache *cache = dynamic_cast<LanguageCache *>(currentBlockUserData()); 0337 if (!cache) { 0338 cache = new LanguageCache; 0339 setCurrentBlockUserData(cache); 0340 } 0341 0342 const bool autodetectLanguage = d->spellchecker->testAttribute(Speller::AutoDetectLanguage); 0343 while (d->languageFilter->hasNext()) { 0344 Sonnet::Token sentence = d->languageFilter->next(); 0345 if (autodetectLanguage && !d->autoDetectLanguageDisabled) { 0346 QString lang; 0347 QPair<int, int> spos = QPair<int, int>(sentence.position(), sentence.length()); 0348 // try cache first 0349 if (cache->languages.contains(spos)) { 0350 lang = cache->languages.value(spos); 0351 } else { 0352 lang = d->languageFilter->language(); 0353 if (!d->languageFilter->isSpellcheckable()) { 0354 lang.clear(); 0355 } 0356 cache->languages[spos] = lang; 0357 } 0358 if (lang.isEmpty()) { 0359 continue; 0360 } 0361 d->spellchecker->setLanguage(lang); 0362 } 0363 0364 d->tokenizer->setBuffer(sentence.toString()); 0365 int offset = sentence.position(); 0366 while (d->tokenizer->hasNext()) { 0367 Sonnet::Token word = d->tokenizer->next(); 0368 if (!d->tokenizer->isSpellcheckable()) { 0369 continue; 0370 } 0371 ++d->wordCount; 0372 if (d->spellchecker->isMisspelled(word.toString())) { 0373 ++d->errorCount; 0374 if (word.position() + offset <= cursor.position() && cursor.position() <= word.position() + offset + word.length()) { 0375 setMisspelledSelected(word.position() + offset, word.length()); 0376 } else { 0377 setMisspelled(word.position() + offset, word.length()); 0378 } 0379 } else { 0380 unsetMisspelled(word.position() + offset, word.length()); 0381 } 0382 } 0383 } 0384 } 0385 // QTimer::singleShot( 0, this, SLOT(checkWords()) ); 0386 setCurrentBlockState(0); 0387 } 0388 0389 QStringList SpellcheckHighlighter::suggestions(int mousePosition, int max) 0390 { 0391 if (!textDocument()) { 0392 return {}; 0393 } 0394 0395 Q_EMIT changeCursorPosition(mousePosition, mousePosition); 0396 0397 QTextCursor cursor = textCursor(); 0398 0399 QTextCursor cursorAtMouse(textDocument()); 0400 cursorAtMouse.setPosition(mousePosition); 0401 0402 // Check if the user clicked a selected word 0403 const bool selectedWordClicked = cursor.hasSelection() && mousePosition >= cursor.selectionStart() && mousePosition <= cursor.selectionEnd(); 0404 0405 // Get the word under the (mouse-)cursor and see if it is misspelled. 0406 // Don't include apostrophes at the start/end of the word in the selection. 0407 QTextCursor wordSelectCursor(cursorAtMouse); 0408 wordSelectCursor.clearSelection(); 0409 wordSelectCursor.select(QTextCursor::WordUnderCursor); 0410 d->selectedWord = wordSelectCursor.selectedText(); 0411 0412 // Clear the selection again, we re-select it below (without the apostrophes). 0413 wordSelectCursor.setPosition(wordSelectCursor.position() - d->selectedWord.size()); 0414 if (d->selectedWord.startsWith(QLatin1Char('\'')) || d->selectedWord.startsWith(QLatin1Char('\"'))) { 0415 d->selectedWord = d->selectedWord.right(d->selectedWord.size() - 1); 0416 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor); 0417 } 0418 if (d->selectedWord.endsWith(QLatin1Char('\'')) || d->selectedWord.endsWith(QLatin1Char('\"'))) { 0419 d->selectedWord.chop(1); 0420 } 0421 0422 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d->selectedWord.size()); 0423 0424 int endSelection = wordSelectCursor.selectionEnd(); 0425 Q_EMIT wordUnderMouseChanged(); 0426 0427 bool isMouseCursorInsideWord = true; 0428 if ((mousePosition < wordSelectCursor.selectionStart() || mousePosition >= wordSelectCursor.selectionEnd()) // 0429 && (d->selectedWord.length() > 1)) { 0430 isMouseCursorInsideWord = false; 0431 } 0432 0433 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d->selectedWord.size()); 0434 0435 d->wordIsMisspelled = isMouseCursorInsideWord && !d->selectedWord.isEmpty() && d->spellchecker->isMisspelled(d->selectedWord); 0436 Q_EMIT wordIsMisspelledChanged(); 0437 0438 if (!d->wordIsMisspelled || selectedWordClicked) { 0439 return QStringList{}; 0440 } 0441 0442 LanguageCache *cache = dynamic_cast<LanguageCache *>(cursor.block().userData()); 0443 if (cache) { 0444 const QString cachedLanguage = cache->languageAtPos(cursor.positionInBlock()); 0445 if (!cachedLanguage.isEmpty()) { 0446 d->spellchecker->setLanguage(cachedLanguage); 0447 } 0448 } 0449 QStringList suggestions = d->spellchecker->suggest(d->selectedWord); 0450 if (max >= 0 && suggestions.count() > max) { 0451 suggestions = suggestions.mid(0, max); 0452 } 0453 0454 return suggestions; 0455 } 0456 0457 QString SpellcheckHighlighter::currentLanguage() const 0458 { 0459 return d->spellchecker->language(); 0460 } 0461 0462 void SpellcheckHighlighter::setCurrentLanguage(const QString &lang) 0463 { 0464 QString prevLang = d->spellchecker->language(); 0465 d->spellchecker->setLanguage(lang); 0466 d->spellCheckerFound = d->spellchecker->isValid(); 0467 if (!d->spellCheckerFound) { 0468 qCDebug(SONNET_LOG_QUICK) << "No dictionary for \"" << lang << "\" staying with the current language."; 0469 d->spellchecker->setLanguage(prevLang); 0470 return; 0471 } 0472 d->wordCount = 0; 0473 d->errorCount = 0; 0474 if (d->automatic || d->active) { 0475 d->rehighlightRequest->start(0); 0476 } 0477 } 0478 0479 void SpellcheckHighlighter::setMisspelled(int start, int count) 0480 { 0481 setFormat(start, count, d->errorFormat); 0482 } 0483 0484 void SpellcheckHighlighter::setMisspelledSelected(int start, int count) 0485 { 0486 setFormat(start, count, d->selectedErrorFormat); 0487 } 0488 0489 void SpellcheckHighlighter::unsetMisspelled(int start, int count) 0490 { 0491 setFormat(start, count, QTextCharFormat()); 0492 } 0493 0494 void SpellcheckHighlighter::addWordToDictionary(const QString &word) 0495 { 0496 d->spellchecker->addToPersonal(word); 0497 rehighlight(); 0498 } 0499 0500 void SpellcheckHighlighter::ignoreWord(const QString &word) 0501 { 0502 d->spellchecker->addToSession(word); 0503 rehighlight(); 0504 } 0505 0506 void SpellcheckHighlighter::replaceWord(const QString &replacement, int at) 0507 { 0508 QTextCursor textCursorUnderUserCursor(textDocument()); 0509 textCursorUnderUserCursor.setPosition(at == -1 ? d->cursorPosition : at); 0510 0511 // Get the word under the cursor 0512 QTextCursor wordSelectCursor(textCursorUnderUserCursor); 0513 wordSelectCursor.clearSelection(); 0514 wordSelectCursor.select(QTextCursor::WordUnderCursor); 0515 0516 auto selectedWord = wordSelectCursor.selectedText(); 0517 0518 // Trim leading and trailing apostrophes 0519 wordSelectCursor.setPosition(wordSelectCursor.position() - selectedWord.size()); 0520 if (selectedWord.startsWith(QLatin1Char('\'')) || selectedWord.startsWith(QLatin1Char('\"'))) { 0521 selectedWord = selectedWord.right(selectedWord.size() - 1); 0522 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor); 0523 } 0524 if (selectedWord.endsWith(QLatin1Char('\'')) || d->selectedWord.endsWith(QLatin1Char('\"'))) { 0525 selectedWord.chop(1); 0526 } 0527 0528 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d->selectedWord.size()); 0529 0530 wordSelectCursor.insertText(replacement); 0531 } 0532 0533 QQuickTextDocument *SpellcheckHighlighter::quickDocument() const 0534 { 0535 return d->document; 0536 } 0537 0538 void SpellcheckHighlighter::setQuickDocument(QQuickTextDocument *document) 0539 { 0540 if (document == d->document) { 0541 return; 0542 } 0543 0544 if (d->document) { 0545 d->document->parent()->removeEventFilter(this); 0546 d->document->textDocument()->disconnect(this); 0547 } 0548 d->document = document; 0549 document->parent()->installEventFilter(this); 0550 setDocument(document->textDocument()); 0551 Q_EMIT documentChanged(); 0552 } 0553 0554 void SpellcheckHighlighter::setDocument(QTextDocument *document) 0555 { 0556 d->connected = false; 0557 QSyntaxHighlighter::setDocument(document); 0558 } 0559 0560 int SpellcheckHighlighter::cursorPosition() const 0561 { 0562 return d->cursorPosition; 0563 } 0564 0565 void SpellcheckHighlighter::setCursorPosition(int position) 0566 { 0567 if (position == d->cursorPosition) { 0568 return; 0569 } 0570 0571 d->cursorPosition = position; 0572 d->rehighlightRequest->start(0); 0573 Q_EMIT cursorPositionChanged(); 0574 } 0575 0576 int SpellcheckHighlighter::selectionStart() const 0577 { 0578 return d->selectionStart; 0579 } 0580 0581 void SpellcheckHighlighter::setSelectionStart(int position) 0582 { 0583 if (position == d->selectionStart) { 0584 return; 0585 } 0586 0587 d->selectionStart = position; 0588 Q_EMIT selectionStartChanged(); 0589 } 0590 0591 int SpellcheckHighlighter::selectionEnd() const 0592 { 0593 return d->selectionEnd; 0594 } 0595 0596 void SpellcheckHighlighter::setSelectionEnd(int position) 0597 { 0598 if (position == d->selectionEnd) { 0599 return; 0600 } 0601 0602 d->selectionEnd = position; 0603 Q_EMIT selectionEndChanged(); 0604 } 0605 0606 QTextCursor SpellcheckHighlighter::textCursor() const 0607 { 0608 QTextDocument *doc = textDocument(); 0609 if (!doc) { 0610 return QTextCursor(); 0611 } 0612 0613 QTextCursor cursor(doc); 0614 if (d->selectionStart != d->selectionEnd) { 0615 cursor.setPosition(d->selectionStart); 0616 cursor.setPosition(d->selectionEnd, QTextCursor::KeepAnchor); 0617 } else { 0618 cursor.setPosition(d->cursorPosition); 0619 } 0620 return cursor; 0621 } 0622 0623 QTextDocument *SpellcheckHighlighter::textDocument() const 0624 { 0625 if (!d->document) { 0626 return nullptr; 0627 } 0628 0629 return d->document->textDocument(); 0630 } 0631 0632 bool SpellcheckHighlighter::wordIsMisspelled() const 0633 { 0634 return d->wordIsMisspelled; 0635 } 0636 0637 QString SpellcheckHighlighter::wordUnderMouse() const 0638 { 0639 return d->selectedWord; 0640 } 0641 0642 QColor SpellcheckHighlighter::misspelledColor() const 0643 { 0644 return d->spellColor; 0645 } 0646 0647 void SpellcheckHighlighter::setMisspelledColor(const QColor &color) 0648 { 0649 if (color == d->spellColor) { 0650 return; 0651 } 0652 d->spellColor = color; 0653 Q_EMIT misspelledColorChanged(); 0654 } 0655 0656 bool SpellcheckHighlighter::isWordMisspelled(const QString &word) 0657 { 0658 return d->spellchecker->isMisspelled(word); 0659 } 0660 0661 bool SpellcheckHighlighter::eventFilter(QObject *o, QEvent *e) 0662 { 0663 if (!d->spellCheckerFound) { 0664 return false; 0665 } 0666 if (o == d->document->parent() && (e->type() == QEvent::KeyPress)) { 0667 QKeyEvent *k = static_cast<QKeyEvent *>(e); 0668 0669 if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return || k->key() == Qt::Key_Up || k->key() == Qt::Key_Down || k->key() == Qt::Key_Left 0670 || k->key() == Qt::Key_Right || k->key() == Qt::Key_PageUp || k->key() == Qt::Key_PageDown || k->key() == Qt::Key_Home || k->key() == Qt::Key_End 0671 || (k->modifiers() == Qt::ControlModifier 0672 && (k->key() == Qt::Key_A || k->key() == Qt::Key_B || k->key() == Qt::Key_E || k->key() == Qt::Key_N 0673 || k->key() == Qt::Key_P))) { /* clang-format on */ 0674 if (intraWordEditing()) { 0675 setIntraWordEditing(false); 0676 d->completeRehighlightRequired = true; 0677 d->rehighlightRequest->setInterval(500); 0678 d->rehighlightRequest->setSingleShot(true); 0679 d->rehighlightRequest->start(); 0680 } 0681 } else { 0682 setIntraWordEditing(true); 0683 } 0684 if (k->key() == Qt::Key_Space // 0685 || k->key() == Qt::Key_Enter // 0686 || k->key() == Qt::Key_Return) { 0687 QTimer::singleShot(0, this, SLOT(slotAutoDetection())); 0688 } 0689 } else if (d->document && e->type() == QEvent::MouseButtonPress) { 0690 if (intraWordEditing()) { 0691 setIntraWordEditing(false); 0692 d->completeRehighlightRequired = true; 0693 d->rehighlightRequest->setInterval(0); 0694 d->rehighlightRequest->setSingleShot(true); 0695 d->rehighlightRequest->start(); 0696 } 0697 } 0698 return false; 0699 } 0700 0701 #include "moc_spellcheckhighlighter.cpp"