File indexing completed on 2024-12-22 04:28:11

0001 /*
0002    SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "plaintexteditor.h"
0007 
0008 #include "config-textcustomeditor.h"
0009 #include "widgets/textmessageindicator.h"
0010 #include <KConfig>
0011 #include <KConfigGroup>
0012 #include <KCursor>
0013 #include <KIO/KUriFilterSearchProviderActions>
0014 #include <KLocalizedString>
0015 #include <KSharedConfig>
0016 #include <KStandardAction>
0017 #include <KStandardGuiItem>
0018 #include <QActionGroup>
0019 #include <QIcon>
0020 
0021 #if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
0022 #include <TextEditTextToSpeech/TextToSpeech>
0023 #endif
0024 #include <Sonnet/Dialog>
0025 #include <TextEmoticonsWidgets/EmoticonTextEditAction>
0026 #include <sonnet/backgroundchecker.h>
0027 
0028 #include <KColorScheme>
0029 #include <QApplication>
0030 #include <QClipboard>
0031 #include <QDBusConnection>
0032 #include <QDBusConnectionInterface>
0033 #include <QMenu>
0034 #include <QScrollBar>
0035 #include <QShortcut>
0036 #include <QTextDocumentFragment>
0037 
0038 #include <sonnet/spellcheckdecorator.h>
0039 
0040 #include <sonnet/speller.h>
0041 
0042 #include <Sonnet/Highlighter>
0043 
0044 using namespace TextCustomEditor;
0045 
0046 class Q_DECL_HIDDEN PlainTextEditor::PlainTextEditorPrivate
0047 {
0048 public:
0049     PlainTextEditorPrivate(PlainTextEditor *qq)
0050         : q(qq)
0051         , mTextIndicator(new TextCustomEditor::TextMessageIndicator(q))
0052         , webshortcutMenuManager(new KIO::KUriFilterSearchProviderActions(q))
0053     {
0054         KConfig sonnetKConfig(QStringLiteral("sonnetrc"));
0055         KConfigGroup group(&sonnetKConfig, QLatin1String("Spelling"));
0056         checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
0057         supportFeatures |= PlainTextEditor::Search;
0058         supportFeatures |= PlainTextEditor::SpellChecking;
0059         supportFeatures |= PlainTextEditor::TextToSpeech;
0060         supportFeatures |= PlainTextEditor::AllowWebShortcut;
0061     }
0062 
0063     ~PlainTextEditorPrivate()
0064     {
0065         delete richTextDecorator;
0066         delete speller;
0067     }
0068 
0069     QStringList ignoreSpellCheckingWords;
0070     PlainTextEditor *const q;
0071     TextCustomEditor::TextMessageIndicator *const mTextIndicator;
0072     KIO::KUriFilterSearchProviderActions *const webshortcutMenuManager;
0073     Sonnet::SpellCheckDecorator *richTextDecorator = nullptr;
0074     Sonnet::Speller *speller = nullptr;
0075 
0076     QString spellCheckingConfigFileName;
0077     QString spellCheckingLanguage;
0078     QTextDocumentFragment originalDoc;
0079     PlainTextEditor::SupportFeatures supportFeatures;
0080     QColor mReadOnlyBackgroundColor;
0081     int mInitialFontSize = 0;
0082     bool customPalette = false;
0083     bool activateLanguageMenu = true;
0084     bool checkSpellingEnabled = false;
0085 };
0086 
0087 PlainTextEditor::PlainTextEditor(QWidget *parent)
0088     : QPlainTextEdit(parent)
0089     , d(new PlainTextEditor::PlainTextEditorPrivate(this))
0090 {
0091     KCursor::setAutoHideCursor(this, true, false);
0092     setSpellCheckingConfigFileName(QString());
0093     d->mInitialFontSize = font().pointSize();
0094     regenerateColorScheme();
0095 }
0096 
0097 PlainTextEditor::~PlainTextEditor() = default;
0098 
0099 void PlainTextEditor::regenerateColorScheme()
0100 {
0101     d->mReadOnlyBackgroundColor = KColorScheme(QPalette::Disabled, KColorScheme::View).background().color();
0102     updateReadOnlyColor();
0103 }
0104 
0105 void PlainTextEditor::addIgnoreWords(const QStringList &lst)
0106 {
0107     d->ignoreSpellCheckingWords = lst;
0108     addIgnoreWordsToHighLighter();
0109 }
0110 
0111 void PlainTextEditor::slotDisplayMessageIndicator(const QString &message)
0112 {
0113     d->mTextIndicator->display(message);
0114 }
0115 
0116 void PlainTextEditor::contextMenuEvent(QContextMenuEvent *event)
0117 {
0118     QMenu *popup = createStandardContextMenu();
0119     if (popup) {
0120         const bool emptyDocument = document()->isEmpty();
0121         if (!isReadOnly()) {
0122             const QList<QAction *> actionList = popup->actions();
0123             enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
0124             QAction *separatorAction = nullptr;
0125             const int idx = actionList.indexOf(actionList[SelectAllAct]) + 1;
0126             if (idx < actionList.count()) {
0127                 separatorAction = actionList.at(idx);
0128             }
0129             if (separatorAction) {
0130                 if (!emptyDocument) {
0131                     QAction *clearAllAction = KStandardAction::clear(this, &PlainTextEditor::slotUndoableClear, popup);
0132                     popup->insertAction(separatorAction, clearAllAction);
0133                 }
0134             }
0135         }
0136         if (d->supportFeatures & Search) {
0137             popup->addSeparator();
0138             if (!emptyDocument) {
0139                 QAction *findAction = KStandardAction::find(this, &PlainTextEditor::findText, popup);
0140                 popup->addAction(findAction);
0141                 popup->addSeparator();
0142             }
0143             if (!isReadOnly()) {
0144                 if (!emptyDocument) {
0145                     QAction *replaceAction = KStandardAction::replace(this, &PlainTextEditor::replaceText, popup);
0146                     popup->addAction(replaceAction);
0147                     popup->addSeparator();
0148                 }
0149             }
0150         } else {
0151             popup->addSeparator();
0152         }
0153 
0154         if (!isReadOnly() && spellCheckingSupport()) {
0155             if (!d->speller) {
0156                 d->speller = new Sonnet::Speller();
0157             }
0158             if (!d->speller->availableBackends().isEmpty()) {
0159                 if (!emptyDocument) {
0160                     popup->addAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")),
0161                                      i18n("Check Spelling..."),
0162                                      this,
0163                                      &PlainTextEditor::slotCheckSpelling);
0164                     popup->addSeparator();
0165                 }
0166                 QAction *autoSpellCheckAction = popup->addAction(i18n("Auto Spell Check"), this, &PlainTextEditor::slotToggleAutoSpellCheck);
0167                 autoSpellCheckAction->setCheckable(true);
0168                 autoSpellCheckAction->setChecked(checkSpellingEnabled());
0169                 popup->addAction(autoSpellCheckAction);
0170 
0171                 if (checkSpellingEnabled() && d->activateLanguageMenu) {
0172                     auto languagesMenu = new QMenu(i18n("Spell Checking Language"), popup);
0173                     auto languagesGroup = new QActionGroup(languagesMenu);
0174                     languagesGroup->setExclusive(true);
0175 
0176                     QString defaultSpellcheckingLanguage = spellCheckingLanguage();
0177                     if (defaultSpellcheckingLanguage.isEmpty()) {
0178                         // TODO fix default value
0179                         defaultSpellcheckingLanguage = d->speller->defaultLanguage();
0180                     }
0181 
0182                     QMapIterator<QString, QString> i(d->speller->availableDictionaries());
0183                     while (i.hasNext()) {
0184                         i.next();
0185                         QAction *languageAction = languagesMenu->addAction(i.key());
0186                         languageAction->setCheckable(true);
0187                         languageAction->setChecked(defaultSpellcheckingLanguage == i.value());
0188                         languageAction->setData(i.value());
0189                         languageAction->setActionGroup(languagesGroup);
0190                         connect(languageAction, &QAction::triggered, this, &PlainTextEditor::slotLanguageSelected);
0191                     }
0192                     popup->addMenu(languagesMenu);
0193                 }
0194                 popup->addSeparator();
0195             }
0196         }
0197         if (d->supportFeatures & TextToSpeech) {
0198 #if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
0199             if (!emptyDocument) {
0200                 QAction *speakAction = popup->addAction(i18n("Speak Text"));
0201                 speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech")));
0202                 connect(speakAction, &QAction::triggered, this, &PlainTextEditor::slotSpeakText);
0203             }
0204 #endif
0205         }
0206         if (webShortcutSupport() && textCursor().hasSelection()) {
0207             popup->addSeparator();
0208             const QString selectedText = textCursor().selectedText();
0209             d->webshortcutMenuManager->setSelectedText(selectedText);
0210             d->webshortcutMenuManager->addWebShortcutsToMenu(popup);
0211         }
0212         if (emojiSupport()) {
0213             popup->addSeparator();
0214             auto action = new TextEmoticonsWidgets::EmoticonTextEditAction(this);
0215             popup->addAction(action);
0216             connect(action, &TextEmoticonsWidgets::EmoticonTextEditAction::insertEmoticon, this, &PlainTextEditor::slotInsertEmoticon);
0217         }
0218         addExtraMenuEntry(popup, event->pos());
0219         popup->exec(event->globalPos());
0220 
0221         delete popup;
0222     }
0223 }
0224 
0225 void PlainTextEditor::slotInsertEmoticon(const QString &str)
0226 {
0227     insertPlainText(str);
0228 }
0229 void PlainTextEditor::setEmojiSupport(bool b)
0230 {
0231     if (b) {
0232         d->supportFeatures |= Emoji;
0233     } else {
0234         d->supportFeatures = (d->supportFeatures & ~Emoji);
0235     }
0236 }
0237 
0238 bool PlainTextEditor::emojiSupport() const
0239 {
0240     return d->supportFeatures & Emoji;
0241 }
0242 
0243 void PlainTextEditor::addExtraMenuEntry(QMenu *menu, QPoint pos)
0244 {
0245     Q_UNUSED(menu)
0246     Q_UNUSED(pos)
0247 }
0248 
0249 void PlainTextEditor::slotSpeakText()
0250 {
0251     QString text;
0252     if (textCursor().hasSelection()) {
0253         text = textCursor().selectedText();
0254     } else {
0255         text = toPlainText();
0256     }
0257     // qCDebug(TEXTCUSTOMEDITOR_LOG) << " TextCustomEditor::TextToSpeech::self()->isReady() :" << TextCustomEditor::TextToSpeech::self()->isReady();
0258     Q_EMIT say(text);
0259 }
0260 
0261 void PlainTextEditor::slotUndoableClear()
0262 {
0263     QTextCursor cursor = textCursor();
0264     cursor.beginEditBlock();
0265     cursor.movePosition(QTextCursor::Start);
0266     cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
0267     cursor.removeSelectedText();
0268     cursor.endEditBlock();
0269 }
0270 
0271 void PlainTextEditor::setSearchSupport(bool b)
0272 {
0273     if (b) {
0274         d->supportFeatures |= Search;
0275     } else {
0276         d->supportFeatures = (d->supportFeatures & ~Search);
0277     }
0278 }
0279 
0280 bool PlainTextEditor::searchSupport() const
0281 {
0282     return d->supportFeatures & Search;
0283 }
0284 
0285 void PlainTextEditor::setTextToSpeechSupport(bool b)
0286 {
0287     if (b) {
0288         d->supportFeatures |= TextToSpeech;
0289     } else {
0290         d->supportFeatures = (d->supportFeatures & ~TextToSpeech);
0291     }
0292 }
0293 
0294 bool PlainTextEditor::textToSpeechSupport() const
0295 {
0296     return d->supportFeatures & TextToSpeech;
0297 }
0298 
0299 bool PlainTextEditor::spellCheckingSupport() const
0300 {
0301     return d->supportFeatures & SpellChecking;
0302 }
0303 
0304 void PlainTextEditor::setSpellCheckingSupport(bool check)
0305 {
0306     if (check) {
0307         d->supportFeatures |= SpellChecking;
0308     } else {
0309         d->supportFeatures = (d->supportFeatures & ~SpellChecking);
0310     }
0311 }
0312 
0313 void PlainTextEditor::setWebShortcutSupport(bool b)
0314 {
0315     if (b) {
0316         d->supportFeatures |= AllowWebShortcut;
0317     } else {
0318         d->supportFeatures = (d->supportFeatures & ~AllowWebShortcut);
0319     }
0320 }
0321 
0322 bool PlainTextEditor::webShortcutSupport() const
0323 {
0324     return d->supportFeatures & AllowWebShortcut;
0325 }
0326 
0327 void PlainTextEditor::updateReadOnlyColor()
0328 {
0329     if (isReadOnly()) {
0330         QPalette p = palette();
0331         p.setColor(QPalette::Base, d->mReadOnlyBackgroundColor);
0332         p.setColor(QPalette::Window, d->mReadOnlyBackgroundColor);
0333         setPalette(p);
0334     }
0335 }
0336 
0337 void PlainTextEditor::setReadOnly(bool readOnly)
0338 {
0339     if (!readOnly && hasFocus() && d->checkSpellingEnabled && !d->richTextDecorator) {
0340         createHighlighter();
0341     }
0342 
0343     if (readOnly == isReadOnly()) {
0344         return;
0345     }
0346 
0347     if (readOnly) {
0348         clearDecorator();
0349         d->customPalette = testAttribute(Qt::WA_SetPalette);
0350         updateReadOnlyColor();
0351     } else {
0352         if (d->customPalette && testAttribute(Qt::WA_SetPalette)) {
0353             QPalette p = palette();
0354             QColor color = p.color(QPalette::Normal, QPalette::Base);
0355             p.setColor(QPalette::Base, color);
0356             p.setColor(QPalette::Window, color);
0357             setPalette(p);
0358         } else {
0359             setPalette(QPalette());
0360         }
0361     }
0362 
0363     QPlainTextEdit::setReadOnly(readOnly);
0364 }
0365 
0366 void PlainTextEditor::slotCheckSpelling()
0367 {
0368     if (document()->isEmpty()) {
0369         slotDisplayMessageIndicator(i18n("Nothing to spell check."));
0370         return;
0371     }
0372     auto backgroundSpellCheck = new Sonnet::BackgroundChecker;
0373     if (backgroundSpellCheck->speller().availableBackends().isEmpty()) {
0374         slotDisplayMessageIndicator(i18n("No backend available for spell checking."));
0375         delete backgroundSpellCheck;
0376         return;
0377     }
0378     if (!d->spellCheckingLanguage.isEmpty()) {
0379         backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage);
0380     }
0381     if (!d->ignoreSpellCheckingWords.isEmpty()) {
0382         for (const QString &word : std::as_const(d->ignoreSpellCheckingWords)) {
0383             backgroundSpellCheck->speller().addToSession(word);
0384         }
0385     }
0386     auto spellDialog = new Sonnet::Dialog(backgroundSpellCheck, nullptr);
0387     backgroundSpellCheck->setParent(spellDialog);
0388     spellDialog->setAttribute(Qt::WA_DeleteOnClose, true);
0389     connect(spellDialog, &Sonnet::Dialog::replace, this, &PlainTextEditor::slotSpellCheckerCorrected);
0390     connect(spellDialog, &Sonnet::Dialog::misspelling, this, &PlainTextEditor::slotSpellCheckerMisspelling);
0391     connect(spellDialog, &Sonnet::Dialog::autoCorrect, this, &PlainTextEditor::slotSpellCheckerAutoCorrect);
0392     connect(spellDialog, &Sonnet::Dialog::spellCheckDone, this, &PlainTextEditor::slotSpellCheckerFinished);
0393     connect(spellDialog, &Sonnet::Dialog::cancel, this, &PlainTextEditor::slotSpellCheckerCanceled);
0394     connect(spellDialog, &Sonnet::Dialog::spellCheckStatus, this, &PlainTextEditor::spellCheckStatus);
0395     connect(spellDialog, &Sonnet::Dialog::languageChanged, this, &PlainTextEditor::languageChanged);
0396     d->originalDoc = QTextDocumentFragment(document());
0397     spellDialog->setBuffer(toPlainText());
0398     spellDialog->show();
0399 }
0400 
0401 void PlainTextEditor::slotSpellCheckerCanceled()
0402 {
0403     QTextDocument *doc = document();
0404     doc->clear();
0405     QTextCursor cursor(doc);
0406     cursor.insertFragment(d->originalDoc);
0407     slotSpellCheckerFinished();
0408 }
0409 
0410 void PlainTextEditor::slotSpellCheckerAutoCorrect(const QString &currentWord, const QString &autoCorrectWord)
0411 {
0412     Q_EMIT spellCheckerAutoCorrect(currentWord, autoCorrectWord);
0413 }
0414 
0415 void PlainTextEditor::slotSpellCheckerMisspelling(const QString &text, int pos)
0416 {
0417     highlightWord(text.length(), pos);
0418 }
0419 
0420 void PlainTextEditor::slotSpellCheckerCorrected(const QString &oldWord, int pos, const QString &newWord)
0421 {
0422     if (oldWord != newWord) {
0423         QTextCursor cursor(document());
0424         cursor.setPosition(pos);
0425         cursor.setPosition(pos + oldWord.length(), QTextCursor::KeepAnchor);
0426         cursor.insertText(newWord);
0427     }
0428 }
0429 
0430 void PlainTextEditor::slotSpellCheckerFinished()
0431 {
0432     QTextCursor cursor(document());
0433     cursor.clearSelection();
0434     setTextCursor(cursor);
0435 }
0436 
0437 void PlainTextEditor::highlightWord(int length, int pos)
0438 {
0439     QTextCursor cursor(document());
0440     cursor.setPosition(pos);
0441     cursor.setPosition(pos + length, QTextCursor::KeepAnchor);
0442     setTextCursor(cursor);
0443     ensureCursorVisible();
0444 }
0445 
0446 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
0447 {
0448     cursor.clearSelection();
0449     cursor.movePosition(op, QTextCursor::KeepAnchor);
0450     cursor.removeSelectedText();
0451 }
0452 
0453 void PlainTextEditor::deleteWordBack()
0454 {
0455     deleteWord(textCursor(), QTextCursor::PreviousWord);
0456 }
0457 
0458 void PlainTextEditor::deleteWordForward()
0459 {
0460     deleteWord(textCursor(), QTextCursor::WordRight);
0461 }
0462 
0463 bool PlainTextEditor::event(QEvent *ev)
0464 {
0465     if (ev->type() == QEvent::ShortcutOverride) {
0466         auto e = static_cast<QKeyEvent *>(ev);
0467         if (overrideShortcut(e)) {
0468             e->accept();
0469             return true;
0470         }
0471     } else if (ev->type() == QEvent::ApplicationPaletteChange) {
0472         regenerateColorScheme();
0473     }
0474 
0475     return QPlainTextEdit::event(ev);
0476 }
0477 
0478 bool PlainTextEditor::overrideShortcut(QKeyEvent *event)
0479 {
0480     const int key = event->key() | event->modifiers();
0481     if (KStandardShortcut::copy().contains(key)) {
0482         return true;
0483     } else if (KStandardShortcut::paste().contains(key)) {
0484         return true;
0485     } else if (KStandardShortcut::cut().contains(key)) {
0486         return true;
0487     } else if (KStandardShortcut::undo().contains(key)) {
0488         return true;
0489     } else if (KStandardShortcut::redo().contains(key)) {
0490         return true;
0491     } else if (KStandardShortcut::deleteWordBack().contains(key)) {
0492         return true;
0493     } else if (KStandardShortcut::deleteWordForward().contains(key)) {
0494         return true;
0495     } else if (KStandardShortcut::backwardWord().contains(key)) {
0496         return true;
0497     } else if (KStandardShortcut::forwardWord().contains(key)) {
0498         return true;
0499     } else if (KStandardShortcut::next().contains(key)) {
0500         return true;
0501     } else if (KStandardShortcut::prior().contains(key)) {
0502         return true;
0503     } else if (KStandardShortcut::begin().contains(key)) {
0504         return true;
0505     } else if (KStandardShortcut::end().contains(key)) {
0506         return true;
0507     } else if (KStandardShortcut::beginningOfLine().contains(key)) {
0508         return true;
0509     } else if (KStandardShortcut::endOfLine().contains(key)) {
0510         return true;
0511     } else if (KStandardShortcut::pasteSelection().contains(key)) {
0512         return true;
0513     } else if (searchSupport() && KStandardShortcut::find().contains(key)) {
0514         return true;
0515     } else if (searchSupport() && KStandardShortcut::replace().contains(key)) {
0516         return true;
0517     } else if (searchSupport() && KStandardShortcut::findNext().contains(key)) {
0518         return true;
0519     } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
0520         return true;
0521     } else if (event == QKeySequence::DeleteEndOfLine) {
0522         return true;
0523     }
0524     return false;
0525 }
0526 
0527 bool PlainTextEditor::handleShortcut(QKeyEvent *event)
0528 {
0529     const int key = event->key() | event->modifiers();
0530 
0531     if (KStandardShortcut::copy().contains(key)) {
0532         copy();
0533         return true;
0534     } else if (KStandardShortcut::paste().contains(key)) {
0535         paste();
0536         return true;
0537     } else if (KStandardShortcut::cut().contains(key)) {
0538         cut();
0539         return true;
0540     } else if (KStandardShortcut::undo().contains(key)) {
0541         if (!isReadOnly()) {
0542             undo();
0543         }
0544         return true;
0545     } else if (KStandardShortcut::redo().contains(key)) {
0546         if (!isReadOnly()) {
0547             redo();
0548         }
0549         return true;
0550     } else if (KStandardShortcut::deleteWordBack().contains(key)) {
0551         if (!isReadOnly()) {
0552             deleteWordBack();
0553         }
0554         return true;
0555     } else if (KStandardShortcut::deleteWordForward().contains(key)) {
0556         if (!isReadOnly()) {
0557             deleteWordForward();
0558         }
0559         return true;
0560     } else if (KStandardShortcut::backwardWord().contains(key)) {
0561         QTextCursor cursor = textCursor();
0562         cursor.movePosition(QTextCursor::PreviousWord);
0563         setTextCursor(cursor);
0564         return true;
0565     } else if (KStandardShortcut::forwardWord().contains(key)) {
0566         QTextCursor cursor = textCursor();
0567         cursor.movePosition(QTextCursor::NextWord);
0568         setTextCursor(cursor);
0569         return true;
0570     } else if (KStandardShortcut::next().contains(key)) {
0571         QTextCursor cursor = textCursor();
0572         bool moved = false;
0573         qreal lastY = cursorRect(cursor).bottom();
0574         qreal distance = 0;
0575         do {
0576             qreal y = cursorRect(cursor).bottom();
0577             distance += qAbs(y - lastY);
0578             lastY = y;
0579             moved = cursor.movePosition(QTextCursor::Down);
0580         } while (moved && distance < viewport()->height());
0581 
0582         if (moved) {
0583             cursor.movePosition(QTextCursor::Up);
0584             verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
0585         }
0586         setTextCursor(cursor);
0587         return true;
0588     } else if (KStandardShortcut::prior().contains(key)) {
0589         QTextCursor cursor = textCursor();
0590         bool moved = false;
0591         qreal lastY = cursorRect(cursor).bottom();
0592         qreal distance = 0;
0593         do {
0594             qreal y = cursorRect(cursor).bottom();
0595             distance += qAbs(y - lastY);
0596             lastY = y;
0597             moved = cursor.movePosition(QTextCursor::Up);
0598         } while (moved && distance < viewport()->height());
0599 
0600         if (moved) {
0601             cursor.movePosition(QTextCursor::Down);
0602             verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
0603         }
0604         setTextCursor(cursor);
0605         return true;
0606     } else if (KStandardShortcut::begin().contains(key)) {
0607         QTextCursor cursor = textCursor();
0608         cursor.movePosition(QTextCursor::Start);
0609         setTextCursor(cursor);
0610         return true;
0611     } else if (KStandardShortcut::end().contains(key)) {
0612         QTextCursor cursor = textCursor();
0613         cursor.movePosition(QTextCursor::End);
0614         setTextCursor(cursor);
0615         return true;
0616     } else if (KStandardShortcut::beginningOfLine().contains(key)) {
0617         QTextCursor cursor = textCursor();
0618         cursor.movePosition(QTextCursor::StartOfLine);
0619         setTextCursor(cursor);
0620         return true;
0621     } else if (KStandardShortcut::endOfLine().contains(key)) {
0622         QTextCursor cursor = textCursor();
0623         cursor.movePosition(QTextCursor::EndOfLine);
0624         setTextCursor(cursor);
0625         return true;
0626     } else if (searchSupport() && KStandardShortcut::find().contains(key)) {
0627         Q_EMIT findText();
0628         return true;
0629     } else if (searchSupport() && KStandardShortcut::replace().contains(key)) {
0630         if (!isReadOnly()) {
0631             Q_EMIT replaceText();
0632         }
0633         return true;
0634     } else if (KStandardShortcut::pasteSelection().contains(key)) {
0635         QString text = QApplication::clipboard()->text(QClipboard::Selection);
0636         if (!text.isEmpty()) {
0637             insertPlainText(text); // TODO: check if this is html? (MiB)
0638         }
0639         return true;
0640     } else if (event == QKeySequence::DeleteEndOfLine) {
0641         deleteEndOfLine();
0642         return true;
0643     }
0644     return false;
0645 }
0646 
0647 void PlainTextEditor::deleteEndOfLine()
0648 {
0649     QTextCursor cursor = textCursor();
0650     QTextBlock block = cursor.block();
0651     if (cursor.position() == block.position() + block.length() - 2) {
0652         cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0653     } else {
0654         cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
0655     }
0656     cursor.removeSelectedText();
0657     setTextCursor(cursor);
0658 }
0659 
0660 void PlainTextEditor::moveCursorBeginUpDown(bool moveUp)
0661 {
0662     QTextCursor cursor = textCursor();
0663     QTextCursor move = cursor;
0664     move.beginEditBlock();
0665     cursor.clearSelection();
0666     move.movePosition(QTextCursor::StartOfBlock);
0667     move.movePosition(moveUp ? QTextCursor::PreviousBlock : QTextCursor::NextBlock);
0668     move.endEditBlock();
0669     setTextCursor(move);
0670 }
0671 
0672 void PlainTextEditor::moveLineUpDown(bool moveUp)
0673 {
0674     QTextCursor cursor = textCursor();
0675     QTextCursor move = cursor;
0676     move.beginEditBlock();
0677 
0678     const bool hasSelection = cursor.hasSelection();
0679 
0680     if (hasSelection) {
0681         move.setPosition(cursor.selectionStart());
0682         move.movePosition(QTextCursor::StartOfBlock);
0683         move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
0684         move.movePosition(move.atBlockStart() ? QTextCursor::Left : QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
0685     } else {
0686         move.movePosition(QTextCursor::StartOfBlock);
0687         move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
0688     }
0689     const QString text = move.selectedText();
0690 
0691     move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
0692     move.removeSelectedText();
0693 
0694     if (moveUp) {
0695         move.movePosition(QTextCursor::PreviousBlock);
0696         move.insertBlock();
0697         move.movePosition(QTextCursor::Left);
0698     } else {
0699         move.movePosition(QTextCursor::EndOfBlock);
0700         if (move.atBlockStart()) { // empty block
0701             move.movePosition(QTextCursor::NextBlock);
0702             move.insertBlock();
0703             move.movePosition(QTextCursor::Left);
0704         } else {
0705             move.insertBlock();
0706         }
0707     }
0708 
0709     int start = move.position();
0710     move.clearSelection();
0711     move.insertText(text);
0712     int end = move.position();
0713 
0714     if (hasSelection) {
0715         move.setPosition(end);
0716         move.setPosition(start, QTextCursor::KeepAnchor);
0717     } else {
0718         move.setPosition(start);
0719     }
0720     move.endEditBlock();
0721 
0722     setTextCursor(move);
0723 }
0724 
0725 void PlainTextEditor::wheelEvent(QWheelEvent *event)
0726 {
0727     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
0728         if (event->angleDelta().y() > 0) {
0729             zoomIn();
0730         } else if (event->angleDelta().y() < 0) {
0731             zoomOut();
0732         }
0733         event->accept();
0734         return;
0735     }
0736     QPlainTextEdit::wheelEvent(event);
0737 }
0738 
0739 void PlainTextEditor::keyPressEvent(QKeyEvent *event)
0740 {
0741     const bool isControlClicked = event->modifiers() & Qt::ControlModifier;
0742     const bool isShiftClicked = event->modifiers() & Qt::ShiftModifier;
0743     if (handleShortcut(event)) {
0744         event->accept();
0745     } else if (event->key() == Qt::Key_Up && isControlClicked && isShiftClicked) {
0746         moveLineUpDown(true);
0747         event->accept();
0748     } else if (event->key() == Qt::Key_Down && isControlClicked && isShiftClicked) {
0749         moveLineUpDown(false);
0750         event->accept();
0751     } else if (event->key() == Qt::Key_Up && isControlClicked) {
0752         moveCursorBeginUpDown(true);
0753         event->accept();
0754     } else if (event->key() == Qt::Key_Down && isControlClicked) {
0755         moveCursorBeginUpDown(false);
0756         event->accept();
0757     } else {
0758         QPlainTextEdit::keyPressEvent(event);
0759     }
0760 }
0761 
0762 bool PlainTextEditor::activateLanguageMenu() const
0763 {
0764     return d->activateLanguageMenu;
0765 }
0766 
0767 void PlainTextEditor::setActivateLanguageMenu(bool activate)
0768 {
0769     d->activateLanguageMenu = activate;
0770 }
0771 
0772 Sonnet::Highlighter *PlainTextEditor::highlighter() const
0773 {
0774     if (d->richTextDecorator) {
0775         return d->richTextDecorator->highlighter();
0776     } else {
0777         return nullptr;
0778     }
0779 }
0780 
0781 Sonnet::SpellCheckDecorator *PlainTextEditor::createSpellCheckDecorator()
0782 {
0783     return new Sonnet::SpellCheckDecorator(this);
0784 }
0785 
0786 void PlainTextEditor::addIgnoreWordsToHighLighter()
0787 {
0788     if (d->ignoreSpellCheckingWords.isEmpty()) {
0789         return;
0790     }
0791     if (d->richTextDecorator) {
0792         Sonnet::Highlighter *_highlighter = d->richTextDecorator->highlighter();
0793         for (const QString &word : std::as_const(d->ignoreSpellCheckingWords)) {
0794             _highlighter->ignoreWord(word);
0795         }
0796     }
0797 }
0798 
0799 void PlainTextEditor::setHighlighter(Sonnet::Highlighter *_highLighter)
0800 {
0801     Sonnet::SpellCheckDecorator *decorator = createSpellCheckDecorator();
0802     delete decorator->highlighter();
0803     decorator->setHighlighter(_highLighter);
0804 
0805     d->richTextDecorator = decorator;
0806     addIgnoreWordsToHighLighter();
0807 }
0808 
0809 void PlainTextEditor::focusInEvent(QFocusEvent *event)
0810 {
0811     if (checkSpellingEnabled() && !isReadOnly() && !d->richTextDecorator && spellCheckingSupport()) {
0812         createHighlighter();
0813     }
0814 
0815     QPlainTextEdit::focusInEvent(event);
0816 }
0817 
0818 bool PlainTextEditor::checkSpellingEnabled() const
0819 {
0820     return d->checkSpellingEnabled;
0821 }
0822 
0823 void PlainTextEditor::setCheckSpellingEnabled(bool check)
0824 {
0825     if (check == d->checkSpellingEnabled) {
0826         return;
0827     }
0828     d->checkSpellingEnabled = check;
0829     Q_EMIT checkSpellingChanged(check);
0830     // From the above statement we know that if we're turning checking
0831     // on that we need to create a new highlighter and if we're turning it
0832     // off we should remove the old one.
0833     if (check) {
0834         if (hasFocus()) {
0835             if (!d->richTextDecorator) {
0836                 createHighlighter();
0837             }
0838             if (!d->spellCheckingLanguage.isEmpty()) {
0839                 setSpellCheckingLanguage(spellCheckingLanguage());
0840             }
0841         }
0842     } else {
0843         clearDecorator();
0844     }
0845     updateHighLighter();
0846 }
0847 
0848 void PlainTextEditor::updateHighLighter()
0849 {
0850 }
0851 
0852 void PlainTextEditor::clearDecorator()
0853 {
0854     delete d->richTextDecorator;
0855     d->richTextDecorator = nullptr;
0856 }
0857 
0858 void PlainTextEditor::createHighlighter()
0859 {
0860     auto highlighter = new Sonnet::Highlighter(this);
0861     highlighter->setCurrentLanguage(spellCheckingLanguage());
0862     setHighlighter(highlighter);
0863 }
0864 
0865 void PlainTextEditor::setSpellCheckingConfigFileName(const QString &_fileName)
0866 {
0867     d->spellCheckingConfigFileName = _fileName;
0868     KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
0869     if (config->hasGroup(QLatin1String("Spelling"))) {
0870         KConfigGroup group(config, QLatin1String("Spelling"));
0871         d->checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
0872         d->spellCheckingLanguage = group.readEntry("Language", QString());
0873     }
0874     setCheckSpellingEnabled(checkSpellingEnabled());
0875 
0876     if (!d->spellCheckingLanguage.isEmpty() && highlighter()) {
0877         highlighter()->setCurrentLanguage(d->spellCheckingLanguage);
0878         highlighter()->rehighlight();
0879     }
0880 }
0881 
0882 QString PlainTextEditor::spellCheckingConfigFileName() const
0883 {
0884     return d->spellCheckingConfigFileName;
0885 }
0886 
0887 void PlainTextEditor::slotLanguageSelected()
0888 {
0889     auto languageAction = static_cast<QAction *>(QObject::sender());
0890     setSpellCheckingLanguage(languageAction->data().toString());
0891 }
0892 
0893 const QString &PlainTextEditor::spellCheckingLanguage() const
0894 {
0895     return d->spellCheckingLanguage;
0896 }
0897 
0898 void PlainTextEditor::setSpellCheckingLanguage(const QString &_language)
0899 {
0900     if (highlighter()) {
0901         highlighter()->setCurrentLanguage(_language);
0902         highlighter()->rehighlight();
0903     }
0904 
0905     if (_language != d->spellCheckingLanguage) {
0906         d->spellCheckingLanguage = _language;
0907         KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
0908         KConfigGroup group(config, QLatin1String("Spelling"));
0909         group.writeEntry("Language", d->spellCheckingLanguage);
0910         setCheckSpellingEnabled(checkSpellingEnabled());
0911 
0912         Q_EMIT languageChanged(_language);
0913     }
0914 }
0915 
0916 void PlainTextEditor::slotToggleAutoSpellCheck()
0917 {
0918     setCheckSpellingEnabled(!checkSpellingEnabled());
0919     KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
0920     KConfigGroup group(config, QLatin1String("Spelling"));
0921     group.writeEntry("checkerEnabledByDefault", d->checkSpellingEnabled);
0922 }
0923 
0924 void PlainTextEditor::slotZoomReset()
0925 {
0926     QFont f = font();
0927     if (d->mInitialFontSize != f.pointSize()) {
0928         f.setPointSize(d->mInitialFontSize);
0929         setFont(f);
0930     }
0931 }
0932 
0933 #include "moc_plaintexteditor.cpp"