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 ¤tWord, 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"