File indexing completed on 2024-04-28 07:47:30

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 &currentWord, 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"