File indexing completed on 2024-03-24 04:01:18
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org> 0004 SPDX-FileCopyrightText: 2005 Michael Brade <brade@kde.org> 0005 SPDX-FileCopyrightText: 2012 Laurent Montel <montel@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "ktextedit.h" 0011 #include "ktextedit_p.h" 0012 0013 #include <QAction> 0014 #include <QActionGroup> 0015 #include <QApplication> 0016 #include <QClipboard> 0017 #include <QDebug> 0018 #include <QKeyEvent> 0019 #include <QMenu> 0020 #include <QScrollBar> 0021 #include <QTextCursor> 0022 0023 #include <KCursor> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <KStandardAction> 0027 #include <KStandardShortcut> 0028 #include <sonnet/backgroundchecker.h> 0029 #include <sonnet/configdialog.h> 0030 #include <sonnet/dialog.h> 0031 0032 class KTextDecorator : public Sonnet::SpellCheckDecorator 0033 { 0034 public: 0035 explicit KTextDecorator(KTextEdit *textEdit); 0036 bool isSpellCheckingEnabledForBlock(const QString &textBlock) const override; 0037 0038 private: 0039 KTextEdit *m_textEdit; 0040 }; 0041 0042 void KTextEditPrivate::checkSpelling(bool force) 0043 { 0044 Q_Q(KTextEdit); 0045 0046 if (q->document()->isEmpty()) { 0047 KMessageBox::information(q, i18n("Nothing to spell check.")); 0048 if (force) { 0049 Q_EMIT q->spellCheckingFinished(); 0050 } 0051 return; 0052 } 0053 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker; 0054 if (!spellCheckingLanguage.isEmpty()) { 0055 backgroundSpellCheck->changeLanguage(spellCheckingLanguage); 0056 } 0057 Sonnet::Dialog *spellDialog = new Sonnet::Dialog(backgroundSpellCheck, force ? q : nullptr); 0058 backgroundSpellCheck->setParent(spellDialog); 0059 spellDialog->setAttribute(Qt::WA_DeleteOnClose, true); 0060 spellDialog->activeAutoCorrect(showAutoCorrectionButton); 0061 QObject::connect(spellDialog, &Sonnet::Dialog::replace, q, [this](const QString &oldWord, int pos, const QString &newWord) { 0062 spellCheckerCorrected(oldWord, pos, newWord); 0063 }); 0064 QObject::connect(spellDialog, &Sonnet::Dialog::misspelling, q, [this](const QString &text, int pos) { 0065 spellCheckerMisspelling(text, pos); 0066 }); 0067 QObject::connect(spellDialog, &Sonnet::Dialog::autoCorrect, q, &KTextEdit::spellCheckerAutoCorrect); 0068 QObject::connect(spellDialog, &Sonnet::Dialog::spellCheckDone, q, [this]() { 0069 spellCheckerFinished(); 0070 }); 0071 QObject::connect(spellDialog, &Sonnet::Dialog::cancel, q, [this]() { 0072 spellCheckerCanceled(); 0073 }); 0074 0075 // Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary 0076 // connect(spellDialog, SIGNAL(stop()), q, SLOT(spellCheckerFinished())); 0077 0078 QObject::connect(spellDialog, &Sonnet::Dialog::spellCheckStatus, q, &KTextEdit::spellCheckStatus); 0079 QObject::connect(spellDialog, &Sonnet::Dialog::languageChanged, q, &KTextEdit::languageChanged); 0080 if (force) { 0081 QObject::connect(spellDialog, &Sonnet::Dialog::spellCheckDone, q, &KTextEdit::spellCheckingFinished); 0082 QObject::connect(spellDialog, &Sonnet::Dialog::cancel, q, &KTextEdit::spellCheckingCanceled); 0083 // Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary 0084 // connect(spellDialog, SIGNAL(stop()), q, SIGNAL(spellCheckingFinished())); 0085 } 0086 originalDoc = QTextDocumentFragment(q->document()); 0087 spellDialog->setBuffer(q->toPlainText()); 0088 spellDialog->show(); 0089 } 0090 0091 void KTextEditPrivate::spellCheckerCanceled() 0092 { 0093 Q_Q(KTextEdit); 0094 0095 QTextDocument *doc = q->document(); 0096 doc->clear(); 0097 QTextCursor cursor(doc); 0098 cursor.insertFragment(originalDoc); 0099 spellCheckerFinished(); 0100 } 0101 0102 void KTextEditPrivate::spellCheckerAutoCorrect(const QString ¤tWord, const QString &autoCorrectWord) 0103 { 0104 Q_Q(KTextEdit); 0105 0106 Q_EMIT q->spellCheckerAutoCorrect(currentWord, autoCorrectWord); 0107 } 0108 0109 void KTextEditPrivate::spellCheckerMisspelling(const QString &text, int pos) 0110 { 0111 Q_Q(KTextEdit); 0112 0113 // qDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<<text<<" pos :"<<pos; 0114 q->highlightWord(text.length(), pos); 0115 } 0116 0117 void KTextEditPrivate::spellCheckerCorrected(const QString &oldWord, int pos, const QString &newWord) 0118 { 0119 Q_Q(KTextEdit); 0120 0121 // qDebug()<<" oldWord :"<<oldWord<<" newWord :"<<newWord<<" pos : "<<pos; 0122 if (oldWord != newWord) { 0123 QTextCursor cursor(q->document()); 0124 cursor.setPosition(pos); 0125 cursor.setPosition(pos + oldWord.length(), QTextCursor::KeepAnchor); 0126 cursor.insertText(newWord); 0127 } 0128 } 0129 0130 void KTextEditPrivate::spellCheckerFinished() 0131 { 0132 Q_Q(KTextEdit); 0133 0134 QTextCursor cursor(q->document()); 0135 cursor.clearSelection(); 0136 q->setTextCursor(cursor); 0137 if (q->highlighter()) { 0138 q->highlighter()->rehighlight(); 0139 } 0140 } 0141 0142 void KTextEditPrivate::toggleAutoSpellCheck() 0143 { 0144 Q_Q(KTextEdit); 0145 0146 q->setCheckSpellingEnabled(!q->checkSpellingEnabled()); 0147 } 0148 0149 void KTextEditPrivate::undoableClear() 0150 { 0151 Q_Q(KTextEdit); 0152 0153 QTextCursor cursor = q->textCursor(); 0154 cursor.beginEditBlock(); 0155 cursor.movePosition(QTextCursor::Start); 0156 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 0157 cursor.removeSelectedText(); 0158 cursor.endEditBlock(); 0159 } 0160 0161 void KTextEditPrivate::slotAllowTab() 0162 { 0163 Q_Q(KTextEdit); 0164 0165 q->setTabChangesFocus(!q->tabChangesFocus()); 0166 } 0167 0168 void KTextEditPrivate::menuActivated(QAction *action) 0169 { 0170 Q_Q(KTextEdit); 0171 0172 if (action == spellCheckAction) { 0173 q->checkSpelling(); 0174 } else if (action == autoSpellCheckAction) { 0175 toggleAutoSpellCheck(); 0176 } else if (action == allowTab) { 0177 slotAllowTab(); 0178 } 0179 } 0180 0181 void KTextEditPrivate::slotFindHighlight(const QString &text, int matchingIndex, int matchingLength) 0182 { 0183 Q_Q(KTextEdit); 0184 0185 Q_UNUSED(text) 0186 // qDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength; 0187 QTextCursor tc = q->textCursor(); 0188 tc.setPosition(matchingIndex); 0189 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength); 0190 q->setTextCursor(tc); 0191 q->ensureCursorVisible(); 0192 } 0193 0194 void KTextEditPrivate::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength) 0195 { 0196 Q_Q(KTextEdit); 0197 0198 // qDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength; 0199 QTextCursor tc = q->textCursor(); 0200 tc.setPosition(replacementIndex); 0201 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength); 0202 tc.removeSelectedText(); 0203 tc.insertText(text.mid(replacementIndex, replacedLength)); 0204 if (replace->options() & KReplaceDialog::PromptOnReplace) { 0205 q->setTextCursor(tc); 0206 q->ensureCursorVisible(); 0207 } 0208 lastReplacedPosition = replacementIndex; 0209 } 0210 0211 void KTextEditPrivate::init() 0212 { 0213 Q_Q(KTextEdit); 0214 0215 KCursor::setAutoHideCursor(q, true, false); 0216 q->connect(q, &KTextEdit::languageChanged, q, &KTextEdit::setSpellCheckingLanguage); 0217 } 0218 0219 KTextDecorator::KTextDecorator(KTextEdit *textEdit) 0220 : SpellCheckDecorator(textEdit) 0221 , m_textEdit(textEdit) 0222 { 0223 } 0224 0225 bool KTextDecorator::isSpellCheckingEnabledForBlock(const QString &textBlock) const 0226 { 0227 return m_textEdit->shouldBlockBeSpellChecked(textBlock); 0228 } 0229 0230 KTextEdit::KTextEdit(const QString &text, QWidget *parent) 0231 : KTextEdit(*new KTextEditPrivate(this), text, parent) 0232 { 0233 } 0234 0235 KTextEdit::KTextEdit(KTextEditPrivate &dd, const QString &text, QWidget *parent) 0236 : QTextEdit(text, parent) 0237 , d_ptr(&dd) 0238 { 0239 Q_D(KTextEdit); 0240 0241 d->init(); 0242 } 0243 0244 KTextEdit::KTextEdit(QWidget *parent) 0245 : KTextEdit(*new KTextEditPrivate(this), parent) 0246 { 0247 } 0248 0249 KTextEdit::KTextEdit(KTextEditPrivate &dd, QWidget *parent) 0250 : QTextEdit(parent) 0251 , d_ptr(&dd) 0252 { 0253 Q_D(KTextEdit); 0254 0255 d->init(); 0256 } 0257 0258 KTextEdit::~KTextEdit() = default; 0259 0260 const QString &KTextEdit::spellCheckingLanguage() const 0261 { 0262 Q_D(const KTextEdit); 0263 0264 return d->spellCheckingLanguage; 0265 } 0266 0267 void KTextEdit::setSpellCheckingLanguage(const QString &_language) 0268 { 0269 Q_D(KTextEdit); 0270 0271 if (highlighter()) { 0272 highlighter()->setCurrentLanguage(_language); 0273 highlighter()->rehighlight(); 0274 } 0275 0276 if (_language != d->spellCheckingLanguage) { 0277 d->spellCheckingLanguage = _language; 0278 Q_EMIT languageChanged(_language); 0279 } 0280 } 0281 0282 void KTextEdit::showSpellConfigDialog(const QString &windowIcon) 0283 { 0284 Q_D(KTextEdit); 0285 0286 Sonnet::ConfigDialog dialog(this); 0287 if (!d->spellCheckingLanguage.isEmpty()) { 0288 dialog.setLanguage(d->spellCheckingLanguage); 0289 } 0290 if (!windowIcon.isEmpty()) { 0291 dialog.setWindowIcon(QIcon::fromTheme(windowIcon, dialog.windowIcon())); 0292 } 0293 if (dialog.exec()) { 0294 setSpellCheckingLanguage(dialog.language()); 0295 } 0296 } 0297 0298 bool KTextEdit::event(QEvent *ev) 0299 { 0300 Q_D(KTextEdit); 0301 0302 if (ev->type() == QEvent::ShortcutOverride) { 0303 QKeyEvent *e = static_cast<QKeyEvent *>(ev); 0304 if (d->overrideShortcut(e)) { 0305 e->accept(); 0306 return true; 0307 } 0308 } 0309 return QTextEdit::event(ev); 0310 } 0311 0312 bool KTextEditPrivate::handleShortcut(const QKeyEvent *event) 0313 { 0314 Q_Q(KTextEdit); 0315 0316 const int key = event->key() | event->modifiers(); 0317 0318 if (KStandardShortcut::copy().contains(key)) { 0319 q->copy(); 0320 return true; 0321 } else if (KStandardShortcut::paste().contains(key)) { 0322 q->paste(); 0323 return true; 0324 } else if (KStandardShortcut::cut().contains(key)) { 0325 q->cut(); 0326 return true; 0327 } else if (KStandardShortcut::undo().contains(key)) { 0328 if (!q->isReadOnly()) { 0329 q->undo(); 0330 } 0331 return true; 0332 } else if (KStandardShortcut::redo().contains(key)) { 0333 if (!q->isReadOnly()) { 0334 q->redo(); 0335 } 0336 return true; 0337 } else if (KStandardShortcut::deleteWordBack().contains(key)) { 0338 if (!q->isReadOnly()) { 0339 q->deleteWordBack(); 0340 } 0341 return true; 0342 } else if (KStandardShortcut::deleteWordForward().contains(key)) { 0343 if (!q->isReadOnly()) { 0344 q->deleteWordForward(); 0345 } 0346 return true; 0347 } else if (KStandardShortcut::backwardWord().contains(key)) { 0348 QTextCursor cursor = q->textCursor(); 0349 // We use visual positioning here since keyboard arrows represents visual direction (left, right) 0350 cursor.movePosition(QTextCursor::WordLeft); 0351 q->setTextCursor(cursor); 0352 return true; 0353 } else if (KStandardShortcut::forwardWord().contains(key)) { 0354 QTextCursor cursor = q->textCursor(); 0355 // We use visual positioning here since keyboard arrows represents visual direction (left, right) 0356 cursor.movePosition(QTextCursor::WordRight); 0357 q->setTextCursor(cursor); 0358 return true; 0359 } else if (KStandardShortcut::next().contains(key)) { 0360 QTextCursor cursor = q->textCursor(); 0361 bool moved = false; 0362 qreal lastY = q->cursorRect(cursor).bottom(); 0363 qreal distance = 0; 0364 do { 0365 qreal y = q->cursorRect(cursor).bottom(); 0366 distance += qAbs(y - lastY); 0367 lastY = y; 0368 moved = cursor.movePosition(QTextCursor::Down); 0369 } while (moved && distance < q->viewport()->height()); 0370 0371 if (moved) { 0372 cursor.movePosition(QTextCursor::Up); 0373 q->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); 0374 } 0375 q->setTextCursor(cursor); 0376 return true; 0377 } else if (KStandardShortcut::prior().contains(key)) { 0378 QTextCursor cursor = q->textCursor(); 0379 bool moved = false; 0380 qreal lastY = q->cursorRect(cursor).bottom(); 0381 qreal distance = 0; 0382 do { 0383 qreal y = q->cursorRect(cursor).bottom(); 0384 distance += qAbs(y - lastY); 0385 lastY = y; 0386 moved = cursor.movePosition(QTextCursor::Up); 0387 } while (moved && distance < q->viewport()->height()); 0388 0389 if (moved) { 0390 cursor.movePosition(QTextCursor::Down); 0391 q->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); 0392 } 0393 q->setTextCursor(cursor); 0394 return true; 0395 } else if (KStandardShortcut::begin().contains(key)) { 0396 QTextCursor cursor = q->textCursor(); 0397 cursor.movePosition(QTextCursor::Start); 0398 q->setTextCursor(cursor); 0399 return true; 0400 } else if (KStandardShortcut::end().contains(key)) { 0401 QTextCursor cursor = q->textCursor(); 0402 cursor.movePosition(QTextCursor::End); 0403 q->setTextCursor(cursor); 0404 return true; 0405 } else if (KStandardShortcut::beginningOfLine().contains(key)) { 0406 QTextCursor cursor = q->textCursor(); 0407 cursor.movePosition(QTextCursor::StartOfLine); 0408 q->setTextCursor(cursor); 0409 return true; 0410 } else if (KStandardShortcut::endOfLine().contains(key)) { 0411 QTextCursor cursor = q->textCursor(); 0412 cursor.movePosition(QTextCursor::EndOfLine); 0413 q->setTextCursor(cursor); 0414 return true; 0415 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { 0416 q->slotFind(); 0417 return true; 0418 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { 0419 q->slotFindNext(); 0420 return true; 0421 } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) { 0422 q->slotFindPrevious(); 0423 return true; 0424 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { 0425 if (!q->isReadOnly()) { 0426 q->slotReplace(); 0427 } 0428 return true; 0429 } else if (KStandardShortcut::pasteSelection().contains(key)) { 0430 QString text = QApplication::clipboard()->text(QClipboard::Selection); 0431 if (!text.isEmpty()) { 0432 q->insertPlainText(text); // TODO: check if this is html? (MiB) 0433 } 0434 return true; 0435 } 0436 return false; 0437 } 0438 0439 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op) 0440 { 0441 cursor.clearSelection(); 0442 cursor.movePosition(op, QTextCursor::KeepAnchor); 0443 cursor.removeSelectedText(); 0444 } 0445 0446 void KTextEdit::deleteWordBack() 0447 { 0448 // We use logical positioning here since deleting should always delete the previous word 0449 // (left in case of LTR text, right in case of RTL text) 0450 deleteWord(textCursor(), QTextCursor::PreviousWord); 0451 } 0452 0453 void KTextEdit::deleteWordForward() 0454 { 0455 // We use logical positioning here since deleting should always delete the previous word 0456 // (left in case of LTR text, right in case of RTL text) 0457 deleteWord(textCursor(), QTextCursor::NextWord); 0458 } 0459 0460 QMenu *KTextEdit::mousePopupMenu() 0461 { 0462 Q_D(KTextEdit); 0463 0464 QMenu *popup = createStandardContextMenu(); 0465 if (!popup) { 0466 return nullptr; 0467 } 0468 connect(popup, &QMenu::triggered, this, [d](QAction *action) { 0469 d->menuActivated(action); 0470 }); 0471 0472 const bool emptyDocument = document()->isEmpty(); 0473 if (!isReadOnly()) { 0474 QList<QAction *> actionList = popup->actions(); 0475 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; 0476 QAction *separatorAction = nullptr; 0477 int idx = actionList.indexOf(actionList[SelectAllAct]) + 1; 0478 if (idx < actionList.count()) { 0479 separatorAction = actionList.at(idx); 0480 } 0481 0482 auto undoableClearSlot = [d]() { 0483 d->undoableClear(); 0484 }; 0485 0486 if (separatorAction) { 0487 QAction *clearAllAction = KStandardAction::clear(this, undoableClearSlot, popup); 0488 if (emptyDocument) { 0489 clearAllAction->setEnabled(false); 0490 } 0491 popup->insertAction(separatorAction, clearAllAction); 0492 } 0493 } 0494 0495 if (!isReadOnly()) { 0496 popup->addSeparator(); 0497 if (!d->speller) { 0498 d->speller = new Sonnet::Speller(); 0499 } 0500 if (!d->speller->availableBackends().isEmpty()) { 0501 d->spellCheckAction = popup->addAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("Check Spelling...")); 0502 if (emptyDocument) { 0503 d->spellCheckAction->setEnabled(false); 0504 } 0505 if (checkSpellingEnabled()) { 0506 d->languagesMenu = new QMenu(i18n("Spell Checking Language"), popup); 0507 QActionGroup *languagesGroup = new QActionGroup(d->languagesMenu); 0508 languagesGroup->setExclusive(true); 0509 0510 QMapIterator<QString, QString> i(d->speller->availableDictionaries()); 0511 const QString language = spellCheckingLanguage(); 0512 while (i.hasNext()) { 0513 i.next(); 0514 0515 QAction *languageAction = d->languagesMenu->addAction(i.key()); 0516 languageAction->setCheckable(true); 0517 languageAction->setChecked(language == i.value() || (language.isEmpty() && d->speller->defaultLanguage() == i.value())); 0518 languageAction->setData(i.value()); 0519 languageAction->setActionGroup(languagesGroup); 0520 connect(languageAction, &QAction::triggered, [this, languageAction]() { 0521 setSpellCheckingLanguage(languageAction->data().toString()); 0522 }); 0523 } 0524 popup->addMenu(d->languagesMenu); 0525 } 0526 0527 d->autoSpellCheckAction = popup->addAction(i18n("Auto Spell Check")); 0528 d->autoSpellCheckAction->setCheckable(true); 0529 d->autoSpellCheckAction->setChecked(checkSpellingEnabled()); 0530 popup->addSeparator(); 0531 } 0532 if (d->showTabAction) { 0533 d->allowTab = popup->addAction(i18n("Allow Tabulations")); 0534 d->allowTab->setCheckable(true); 0535 d->allowTab->setChecked(!tabChangesFocus()); 0536 } 0537 } 0538 0539 if (d->findReplaceEnabled) { 0540 QAction *findAction = KStandardAction::find(this, &KTextEdit::slotFind, popup); 0541 QAction *findNextAction = KStandardAction::findNext(this, &KTextEdit::slotFindNext, popup); 0542 QAction *findPrevAction = KStandardAction::findPrev(this, &KTextEdit::slotFindPrevious, popup); 0543 if (emptyDocument) { 0544 findAction->setEnabled(false); 0545 findNextAction->setEnabled(false); 0546 findPrevAction->setEnabled(false); 0547 } else { 0548 findNextAction->setEnabled(d->find != nullptr); 0549 findPrevAction->setEnabled(d->find != nullptr); 0550 } 0551 popup->addSeparator(); 0552 popup->addAction(findAction); 0553 popup->addAction(findNextAction); 0554 popup->addAction(findPrevAction); 0555 0556 if (!isReadOnly()) { 0557 QAction *replaceAction = KStandardAction::replace(this, &KTextEdit::slotReplace, popup); 0558 if (emptyDocument) { 0559 replaceAction->setEnabled(false); 0560 } 0561 popup->addAction(replaceAction); 0562 } 0563 } 0564 #ifdef HAVE_SPEECH 0565 popup->addSeparator(); 0566 QAction *speakAction = popup->addAction(i18n("Speak Text")); 0567 speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); 0568 speakAction->setEnabled(!emptyDocument); 0569 connect(speakAction, &QAction::triggered, this, &KTextEdit::slotSpeakText); 0570 #endif 0571 return popup; 0572 } 0573 0574 void KTextEdit::slotSpeakText() 0575 { 0576 Q_D(KTextEdit); 0577 0578 #ifdef HAVE_SPEECH 0579 QString text; 0580 if (textCursor().hasSelection()) { 0581 text = textCursor().selectedText(); 0582 } else { 0583 text = toPlainText(); 0584 } 0585 if (!d->textToSpeech) { 0586 d->textToSpeech = new QTextToSpeech(this); 0587 } 0588 d->textToSpeech->say(text); 0589 #endif 0590 } 0591 0592 void KTextEdit::contextMenuEvent(QContextMenuEvent *event) 0593 { 0594 QMenu *popup = mousePopupMenu(); 0595 if (popup) { 0596 aboutToShowContextMenu(popup); 0597 popup->exec(event->globalPos()); 0598 delete popup; 0599 } 0600 } 0601 0602 void KTextEdit::createHighlighter() 0603 { 0604 setHighlighter(new Sonnet::Highlighter(this)); 0605 } 0606 0607 Sonnet::Highlighter *KTextEdit::highlighter() const 0608 { 0609 Q_D(const KTextEdit); 0610 0611 if (d->decorator) { 0612 return d->decorator->highlighter(); 0613 } else { 0614 return nullptr; 0615 } 0616 } 0617 0618 void KTextEdit::clearDecorator() 0619 { 0620 Q_D(KTextEdit); 0621 0622 // Set pointer to null before deleting KTextDecorator as dtor will emit signal, 0623 // which could call this code again and cause double delete/crash 0624 auto decorator = d->decorator; 0625 d->decorator = nullptr; 0626 delete decorator; 0627 } 0628 0629 void KTextEdit::addTextDecorator(Sonnet::SpellCheckDecorator *decorator) 0630 { 0631 Q_D(KTextEdit); 0632 0633 d->decorator = decorator; 0634 } 0635 0636 void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter) 0637 { 0638 KTextDecorator *decorator = new KTextDecorator(this); 0639 // The old default highlighter must be manually deleted. 0640 delete decorator->highlighter(); 0641 decorator->setHighlighter(_highLighter); 0642 0643 // KTextEdit used to take ownership of the highlighter, Sonnet::SpellCheckDecorator does not. 0644 // so we reparent the highlighter so it will be deleted when the decorator is destroyed 0645 _highLighter->setParent(decorator); 0646 addTextDecorator(decorator); 0647 } 0648 0649 void KTextEdit::setCheckSpellingEnabled(bool check) 0650 { 0651 Q_D(KTextEdit); 0652 0653 Q_EMIT checkSpellingChanged(check); 0654 if (check == d->spellCheckingEnabled) { 0655 return; 0656 } 0657 0658 // From the above statement we now know that if we're turning checking 0659 // on that we need to create a new highlighter and if we're turning it 0660 // off we should remove the old one. 0661 0662 d->spellCheckingEnabled = check; 0663 if (check) { 0664 if (hasFocus()) { 0665 createHighlighter(); 0666 if (!spellCheckingLanguage().isEmpty()) { 0667 setSpellCheckingLanguage(spellCheckingLanguage()); 0668 } 0669 } 0670 } else { 0671 clearDecorator(); 0672 } 0673 } 0674 0675 void KTextEdit::focusInEvent(QFocusEvent *event) 0676 { 0677 Q_D(KTextEdit); 0678 0679 if (d->spellCheckingEnabled && !isReadOnly() && !d->decorator) { 0680 createHighlighter(); 0681 } 0682 0683 QTextEdit::focusInEvent(event); 0684 } 0685 0686 bool KTextEdit::checkSpellingEnabled() const 0687 { 0688 Q_D(const KTextEdit); 0689 0690 return d->spellCheckingEnabled; 0691 } 0692 0693 bool KTextEdit::shouldBlockBeSpellChecked(const QString &) const 0694 { 0695 return true; 0696 } 0697 0698 void KTextEdit::setReadOnly(bool readOnly) 0699 { 0700 Q_D(KTextEdit); 0701 0702 if (!readOnly && hasFocus() && d->spellCheckingEnabled && !d->decorator) { 0703 createHighlighter(); 0704 } 0705 0706 if (readOnly == isReadOnly()) { 0707 return; 0708 } 0709 0710 if (readOnly) { 0711 // Set pointer to null before deleting KTextDecorator as dtor will emit signal, 0712 // which could call this code again and cause double delete/crash 0713 auto decorator = d->decorator; 0714 d->decorator = nullptr; 0715 delete decorator; 0716 0717 d->customPalette = testAttribute(Qt::WA_SetPalette); 0718 QPalette p = palette(); 0719 QColor color = p.color(QPalette::Disabled, QPalette::Window); 0720 p.setColor(QPalette::Base, color); 0721 p.setColor(QPalette::Window, color); 0722 setPalette(p); 0723 } else { 0724 if (d->customPalette && testAttribute(Qt::WA_SetPalette)) { 0725 QPalette p = palette(); 0726 QColor color = p.color(QPalette::Normal, QPalette::Base); 0727 p.setColor(QPalette::Base, color); 0728 p.setColor(QPalette::Window, color); 0729 setPalette(p); 0730 } else { 0731 setPalette(QPalette()); 0732 } 0733 } 0734 0735 QTextEdit::setReadOnly(readOnly); 0736 } 0737 0738 void KTextEdit::checkSpelling() 0739 { 0740 Q_D(KTextEdit); 0741 0742 d->checkSpelling(false); 0743 } 0744 0745 void KTextEdit::forceSpellChecking() 0746 { 0747 Q_D(KTextEdit); 0748 0749 d->checkSpelling(true); 0750 } 0751 0752 void KTextEdit::highlightWord(int length, int pos) 0753 { 0754 QTextCursor cursor(document()); 0755 cursor.setPosition(pos); 0756 cursor.setPosition(pos + length, QTextCursor::KeepAnchor); 0757 setTextCursor(cursor); 0758 ensureCursorVisible(); 0759 } 0760 0761 void KTextEdit::replace() 0762 { 0763 Q_D(KTextEdit); 0764 0765 if (document()->isEmpty()) { // saves having to track the text changes 0766 return; 0767 } 0768 0769 if (d->repDlg) { 0770 d->repDlg->activateWindow(); 0771 } else { 0772 d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false); 0773 connect(d->repDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoReplace); 0774 } 0775 d->repDlg->show(); 0776 } 0777 0778 void KTextEdit::slotDoReplace() 0779 { 0780 Q_D(KTextEdit); 0781 0782 if (!d->repDlg) { 0783 // Should really assert() 0784 return; 0785 } 0786 0787 if (d->repDlg->pattern().isEmpty()) { 0788 delete d->replace; 0789 d->replace = nullptr; 0790 ensureCursorVisible(); 0791 return; 0792 } 0793 0794 delete d->replace; 0795 d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this); 0796 d->repIndex = 0; 0797 if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) { 0798 d->repIndex = textCursor().anchor(); 0799 } 0800 0801 // Connect textFound signal to code which handles highlighting of found text. 0802 connect(d->replace, &KFind::textFound, this, [d](const QString &text, int matchingIndex, int matchedLength) { 0803 d->slotFindHighlight(text, matchingIndex, matchedLength); 0804 }); 0805 connect(d->replace, &KFind::findNext, this, &KTextEdit::slotReplaceNext); 0806 0807 connect(d->replace, &KReplace::textReplaced, this, [d](const QString &text, int replacementIndex, int replacedLength, int matchedLength) { 0808 d->slotReplaceText(text, replacementIndex, replacedLength, matchedLength); 0809 }); 0810 0811 d->repDlg->close(); 0812 slotReplaceNext(); 0813 } 0814 0815 void KTextEdit::slotReplaceNext() 0816 { 0817 Q_D(KTextEdit); 0818 0819 if (!d->replace) { 0820 return; 0821 } 0822 0823 d->lastReplacedPosition = -1; 0824 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { 0825 textCursor().beginEditBlock(); // #48541 0826 viewport()->setUpdatesEnabled(false); 0827 } 0828 0829 if (d->replace->needData()) { 0830 d->replace->setData(toPlainText(), d->repIndex); 0831 } 0832 KFind::Result res = d->replace->replace(); 0833 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { 0834 textCursor().endEditBlock(); // #48541 0835 if (d->lastReplacedPosition >= 0) { 0836 QTextCursor tc = textCursor(); 0837 tc.setPosition(d->lastReplacedPosition); 0838 setTextCursor(tc); 0839 ensureCursorVisible(); 0840 } 0841 0842 viewport()->setUpdatesEnabled(true); 0843 viewport()->update(); 0844 } 0845 0846 if (res == KFind::NoMatch) { 0847 d->replace->displayFinalDialog(); 0848 d->replace->disconnect(this); 0849 d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away 0850 d->replace = nullptr; 0851 ensureCursorVisible(); 0852 // or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } 0853 } else { 0854 // m_replace->closeReplaceNextDialog(); 0855 } 0856 } 0857 0858 void KTextEdit::slotDoFind() 0859 { 0860 Q_D(KTextEdit); 0861 0862 if (!d->findDlg) { 0863 // Should really assert() 0864 return; 0865 } 0866 if (d->findDlg->pattern().isEmpty()) { 0867 delete d->find; 0868 d->find = nullptr; 0869 return; 0870 } 0871 delete d->find; 0872 d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this); 0873 d->findIndex = 0; 0874 if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) { 0875 d->findIndex = textCursor().anchor(); 0876 } 0877 0878 // Connect textFound() signal to code which handles highlighting of found text 0879 connect(d->find, &KFind::textFound, this, [d](const QString &text, int matchingIndex, int matchedLength) { 0880 d->slotFindHighlight(text, matchingIndex, matchedLength); 0881 }); 0882 connect(d->find, &KFind::findNext, this, &KTextEdit::slotFindNext); 0883 0884 d->findDlg->close(); 0885 d->find->closeFindNextDialog(); 0886 slotFindNext(); 0887 } 0888 0889 void KTextEdit::slotFindNext() 0890 { 0891 Q_D(KTextEdit); 0892 0893 if (!d->find) { 0894 return; 0895 } 0896 if (document()->isEmpty()) { 0897 d->find->disconnect(this); 0898 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away 0899 d->find = nullptr; 0900 return; 0901 } 0902 0903 if (d->find->needData()) { 0904 d->find->setData(toPlainText(), d->findIndex); 0905 } 0906 KFind::Result res = d->find->find(); 0907 0908 if (res == KFind::NoMatch) { 0909 d->find->displayFinalDialog(); 0910 d->find->disconnect(this); 0911 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away 0912 d->find = nullptr; 0913 // or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } 0914 } else { 0915 // m_find->closeFindNextDialog(); 0916 } 0917 } 0918 0919 void KTextEdit::slotFindPrevious() 0920 { 0921 Q_D(KTextEdit); 0922 0923 if (!d->find) { 0924 return; 0925 } 0926 const long oldOptions = d->find->options(); 0927 d->find->setOptions(oldOptions ^ KFind::FindBackwards); 0928 slotFindNext(); 0929 if (d->find) { 0930 d->find->setOptions(oldOptions); 0931 } 0932 } 0933 0934 void KTextEdit::slotFind() 0935 { 0936 Q_D(KTextEdit); 0937 0938 if (document()->isEmpty()) { // saves having to track the text changes 0939 return; 0940 } 0941 0942 if (d->findDlg) { 0943 d->findDlg->activateWindow(); 0944 } else { 0945 d->findDlg = new KFindDialog(this); 0946 connect(d->findDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoFind); 0947 } 0948 d->findDlg->show(); 0949 } 0950 0951 void KTextEdit::slotReplace() 0952 { 0953 Q_D(KTextEdit); 0954 0955 if (document()->isEmpty()) { // saves having to track the text changes 0956 return; 0957 } 0958 0959 if (d->repDlg) { 0960 d->repDlg->activateWindow(); 0961 } else { 0962 d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false); 0963 connect(d->repDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoReplace); 0964 } 0965 d->repDlg->show(); 0966 } 0967 0968 void KTextEdit::enableFindReplace(bool enabled) 0969 { 0970 Q_D(KTextEdit); 0971 0972 d->findReplaceEnabled = enabled; 0973 } 0974 0975 void KTextEdit::showTabAction(bool show) 0976 { 0977 Q_D(KTextEdit); 0978 0979 d->showTabAction = show; 0980 } 0981 0982 bool KTextEditPrivate::overrideShortcut(const QKeyEvent *event) 0983 { 0984 const int key = event->key() | event->modifiers(); 0985 0986 if (KStandardShortcut::copy().contains(key)) { 0987 return true; 0988 } else if (KStandardShortcut::paste().contains(key)) { 0989 return true; 0990 } else if (KStandardShortcut::cut().contains(key)) { 0991 return true; 0992 } else if (KStandardShortcut::undo().contains(key)) { 0993 return true; 0994 } else if (KStandardShortcut::redo().contains(key)) { 0995 return true; 0996 } else if (KStandardShortcut::deleteWordBack().contains(key)) { 0997 return true; 0998 } else if (KStandardShortcut::deleteWordForward().contains(key)) { 0999 return true; 1000 } else if (KStandardShortcut::backwardWord().contains(key)) { 1001 return true; 1002 } else if (KStandardShortcut::forwardWord().contains(key)) { 1003 return true; 1004 } else if (KStandardShortcut::next().contains(key)) { 1005 return true; 1006 } else if (KStandardShortcut::prior().contains(key)) { 1007 return true; 1008 } else if (KStandardShortcut::begin().contains(key)) { 1009 return true; 1010 } else if (KStandardShortcut::end().contains(key)) { 1011 return true; 1012 } else if (KStandardShortcut::beginningOfLine().contains(key)) { 1013 return true; 1014 } else if (KStandardShortcut::endOfLine().contains(key)) { 1015 return true; 1016 } else if (KStandardShortcut::pasteSelection().contains(key)) { 1017 return true; 1018 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { 1019 return true; 1020 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { 1021 return true; 1022 } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) { 1023 return true; 1024 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { 1025 return true; 1026 } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit 1027 return true; 1028 } 1029 return false; 1030 } 1031 1032 void KTextEdit::keyPressEvent(QKeyEvent *event) 1033 { 1034 Q_D(KTextEdit); 1035 1036 if (d->handleShortcut(event)) { 1037 event->accept(); 1038 } else { 1039 QTextEdit::keyPressEvent(event); 1040 } 1041 } 1042 1043 void KTextEdit::showAutoCorrectButton(bool show) 1044 { 1045 Q_D(KTextEdit); 1046 1047 d->showAutoCorrectionButton = show; 1048 } 1049 1050 #include "moc_ktextedit.cpp"