KTextAddons

plaintexteditor.cpp
1/*
2 SPDX-FileCopyrightText: 2013-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6#include "plaintexteditor.h"
7#include "config-textcustomeditor.h"
8#include "widgets/textmessageindicator.h"
9#include <KConfig>
10#include <KConfigGroup>
11#include <KCursor>
12#if HAVE_KTEXTADDONS_KIO_SUPPORT
13#include <KIO/KUriFilterSearchProviderActions>
14#endif
15#include <KLocalizedString>
16#include <KSharedConfig>
17#include <KStandardActions>
18#include <QActionGroup>
19#include <QIcon>
20
21#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
22#include <TextEditTextToSpeech/TextToSpeech>
23#endif
24#include <Sonnet/Dialog>
25#include <TextEmoticonsWidgets/EmoticonTextEditAction>
26#include <sonnet/backgroundchecker.h>
27
28#include <KColorScheme>
29#include <QApplication>
30#include <QClipboard>
31#include <QMenu>
32#include <QScrollBar>
33#include <QShortcut>
34#include <QTextDocumentFragment>
35
36#include <sonnet/spellcheckdecorator.h>
37
38#include <sonnet/speller.h>
39
40#include <Sonnet/Highlighter>
41
42using namespace TextCustomEditor;
43using namespace Qt::Literals::StringLiterals;
44class Q_DECL_HIDDEN PlainTextEditor::PlainTextEditorPrivate
45{
46public:
47 PlainTextEditorPrivate(PlainTextEditor *qq)
48 : q(qq)
49 , mTextIndicator(new TextCustomEditor::TextMessageIndicator(q))
50#if HAVE_KTEXTADDONS_KIO_SUPPORT
51 , webshortcutMenuManager(new KIO::KUriFilterSearchProviderActions(q))
52#endif
53 {
54 KConfig sonnetKConfig(QStringLiteral("sonnetrc"));
55 KConfigGroup group(&sonnetKConfig, "Spelling"_L1);
56 checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
57 supportFeatures |= PlainTextEditor::Search;
58 supportFeatures |= PlainTextEditor::SpellChecking;
59 supportFeatures |= PlainTextEditor::TextToSpeech;
60#if HAVE_KTEXTADDONS_KIO_SUPPORT
61 supportFeatures |= PlainTextEditor::AllowWebShortcut;
62#endif
63 }
64
65 ~PlainTextEditorPrivate()
66 {
67 delete richTextDecorator;
68 delete speller;
69 }
70
71 QStringList ignoreSpellCheckingWords;
72 PlainTextEditor *const q;
73 TextCustomEditor::TextMessageIndicator *const mTextIndicator;
74#if HAVE_KTEXTADDONS_KIO_SUPPORT
75 KIO::KUriFilterSearchProviderActions *const webshortcutMenuManager;
76#endif
77 Sonnet::SpellCheckDecorator *richTextDecorator = nullptr;
78 Sonnet::Speller *speller = nullptr;
79
80 QString spellCheckingConfigFileName;
81 QString spellCheckingLanguage;
82 QTextDocumentFragment originalDoc;
84 QColor mReadOnlyBackgroundColor;
85 int mInitialFontSize = 0;
86 bool customPalette = false;
87 bool activateLanguageMenu = true;
88 bool checkSpellingEnabled = false;
89};
90
91PlainTextEditor::PlainTextEditor(QWidget *parent)
92 : QPlainTextEdit(parent)
93 , d(new PlainTextEditor::PlainTextEditorPrivate(this))
94{
95 KCursor::setAutoHideCursor(this, true, false);
96 setSpellCheckingConfigFileName(QString());
97 d->mInitialFontSize = font().pointSize();
98 regenerateColorScheme();
99}
100
101PlainTextEditor::~PlainTextEditor() = default;
102
103void PlainTextEditor::regenerateColorScheme()
104{
105 d->mReadOnlyBackgroundColor = KColorScheme(QPalette::Disabled, KColorScheme::View).background().color();
106 updateReadOnlyColor();
107}
108
109void PlainTextEditor::addIgnoreWords(const QStringList &lst)
110{
111 d->ignoreSpellCheckingWords = lst;
112 addIgnoreWordsToHighLighter();
113}
114
115void PlainTextEditor::slotDisplayMessageIndicator(const QString &message)
116{
117 d->mTextIndicator->display(message);
118}
119
120void PlainTextEditor::contextMenuEvent(QContextMenuEvent *event)
121{
123 if (popup) {
124 const bool emptyDocument = document()->isEmpty();
125 if (!isReadOnly()) {
126 const QList<QAction *> actionList = popup->actions();
127 enum {
128 UndoAct,
129 RedoAct,
130 CutAct,
131 CopyAct,
132 PasteAct,
133 ClearAct,
134 SelectAllAct,
135 NCountActs
136 };
137 QAction *separatorAction = nullptr;
138 const int idx = actionList.indexOf(actionList[SelectAllAct]) + 1;
139 if (idx < actionList.count()) {
140 separatorAction = actionList.at(idx);
141 }
142 if (separatorAction) {
143 if (!emptyDocument) {
144 QAction *clearAllAction = KStandardActions::clear(this, &PlainTextEditor::slotUndoableClear, popup);
145 popup->insertAction(separatorAction, clearAllAction);
146 }
147 }
148 }
149 if (d->supportFeatures & Search) {
150 popup->addSeparator();
151 if (!emptyDocument) {
152 QAction *findAction = KStandardActions::find(this, &PlainTextEditor::findText, popup);
153 popup->addAction(findAction);
154 popup->addSeparator();
155 }
156 if (!isReadOnly()) {
157 if (!emptyDocument) {
158 QAction *replaceAction = KStandardActions::replace(this, &PlainTextEditor::replaceText, popup);
159 popup->addAction(replaceAction);
160 popup->addSeparator();
161 }
162 }
163 } else {
164 popup->addSeparator();
165 }
166
167 if (!isReadOnly() && spellCheckingSupport()) {
168 if (!d->speller) {
169 d->speller = new Sonnet::Speller();
170 }
171 if (!d->speller->availableBackends().isEmpty()) {
172 if (!emptyDocument) {
173 popup->addAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")),
174 i18n("Check Spelling…"),
175 this,
176 &PlainTextEditor::slotCheckSpelling);
177 popup->addSeparator();
178 }
179 QAction *autoSpellCheckAction = popup->addAction(i18n("Auto Spell Check"), this, &PlainTextEditor::slotToggleAutoSpellCheck);
180 autoSpellCheckAction->setCheckable(true);
181 autoSpellCheckAction->setChecked(checkSpellingEnabled());
182 popup->addAction(autoSpellCheckAction);
183
184 if (checkSpellingEnabled() && d->activateLanguageMenu) {
185 auto languagesMenu = new QMenu(i18n("Spell Checking Language"), popup);
186 auto languagesGroup = new QActionGroup(languagesMenu);
187 languagesGroup->setExclusive(true);
188
189 QString defaultSpellcheckingLanguage = spellCheckingLanguage();
190 if (defaultSpellcheckingLanguage.isEmpty()) {
191 // TODO fix default value
192 defaultSpellcheckingLanguage = d->speller->defaultLanguage();
193 }
194
195 QMapIterator<QString, QString> i(d->speller->availableDictionaries());
196 while (i.hasNext()) {
197 i.next();
198 QAction *languageAction = languagesMenu->addAction(i.key());
199 languageAction->setCheckable(true);
200 languageAction->setChecked(defaultSpellcheckingLanguage == i.value());
201 languageAction->setData(i.value());
202 languageAction->setActionGroup(languagesGroup);
203 connect(languageAction, &QAction::triggered, this, &PlainTextEditor::slotLanguageSelected);
204 }
205 popup->addMenu(languagesMenu);
206 }
207 popup->addSeparator();
208 }
209 }
210 if (d->supportFeatures & TextToSpeech) {
211#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
212 if (!emptyDocument) {
213 QAction *speakAction = popup->addAction(i18n("Speak Text"));
214 speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech")));
215 connect(speakAction, &QAction::triggered, this, &PlainTextEditor::slotSpeakText);
216 }
217#endif
218 }
219#if HAVE_KTEXTADDONS_KIO_SUPPORT
220 if (webShortcutSupport() && textCursor().hasSelection()) {
221 popup->addSeparator();
222 const QString selectedText = textCursor().selectedText();
223 d->webshortcutMenuManager->setSelectedText(selectedText);
224 d->webshortcutMenuManager->addWebShortcutsToMenu(popup);
225 }
226#endif
227 if (emojiSupport()) {
228 popup->addSeparator();
229 auto action = new TextEmoticonsWidgets::EmoticonTextEditAction(this);
230 popup->addAction(action);
231 connect(action, &TextEmoticonsWidgets::EmoticonTextEditAction::insertEmoticon, this, &PlainTextEditor::slotInsertEmoticon);
232 }
233 addExtraMenuEntry(popup, event->pos());
234 popup->exec(event->globalPos());
235
236 delete popup;
237 }
238}
239
240void PlainTextEditor::slotInsertEmoticon(const QString &str)
241{
242 insertPlainText(str);
243}
244void PlainTextEditor::setEmojiSupport(bool b)
245{
246 if (b) {
247 d->supportFeatures |= Emoji;
248 } else {
249 d->supportFeatures = (d->supportFeatures & ~Emoji);
250 }
251}
252
253bool PlainTextEditor::emojiSupport() const
254{
255 return d->supportFeatures & Emoji;
256}
257
258void PlainTextEditor::addExtraMenuEntry(QMenu *menu, QPoint pos)
259{
260 Q_UNUSED(menu)
261 Q_UNUSED(pos)
262}
263
264void PlainTextEditor::slotSpeakText()
265{
266 QString text;
267 if (textCursor().hasSelection()) {
268 text = textCursor().selectedText();
269 } else {
270 text = toPlainText();
271 }
272 // qCDebug(TEXTCUSTOMEDITOR_LOG) << " TextCustomEditor::TextToSpeech::self()->isReady() :" << TextCustomEditor::TextToSpeech::self()->isReady();
273 Q_EMIT say(text);
274}
275
276void PlainTextEditor::slotUndoableClear()
277{
279 cursor.beginEditBlock();
280 cursor.movePosition(QTextCursor::Start);
282 cursor.removeSelectedText();
283 cursor.endEditBlock();
284}
285
286void PlainTextEditor::setSearchSupport(bool b)
287{
288 if (b) {
289 d->supportFeatures |= Search;
290 } else {
291 d->supportFeatures = (d->supportFeatures & ~Search);
292 }
293}
294
295bool PlainTextEditor::searchSupport() const
296{
297 return d->supportFeatures & Search;
298}
299
300void PlainTextEditor::setTextToSpeechSupport(bool b)
301{
302 if (b) {
303 d->supportFeatures |= TextToSpeech;
304 } else {
305 d->supportFeatures = (d->supportFeatures & ~TextToSpeech);
306 }
307}
308
309bool PlainTextEditor::textToSpeechSupport() const
310{
311 return d->supportFeatures & TextToSpeech;
312}
313
314bool PlainTextEditor::spellCheckingSupport() const
315{
316 return d->supportFeatures & SpellChecking;
317}
318
319void PlainTextEditor::setSpellCheckingSupport(bool check)
320{
321 if (check) {
322 d->supportFeatures |= SpellChecking;
323 } else {
324 d->supportFeatures = (d->supportFeatures & ~SpellChecking);
325 }
326}
327
328void PlainTextEditor::setWebShortcutSupport(bool b)
329{
330#if HAVE_KTEXTADDONS_KIO_SUPPORT
331 if (b) {
332 d->supportFeatures |= AllowWebShortcut;
333 } else {
334 d->supportFeatures = (d->supportFeatures & ~AllowWebShortcut);
335 }
336#else
337 Q_UNUSED(b);
338#endif
339}
340
341bool PlainTextEditor::webShortcutSupport() const
342{
343#if HAVE_KTEXTADDONS_KIO_SUPPORT
344 return d->supportFeatures & AllowWebShortcut;
345#else
346 return false;
347#endif
348}
349
350void PlainTextEditor::updateReadOnlyColor()
351{
352 if (isReadOnly()) {
353 QPalette p = palette();
354 p.setColor(QPalette::Base, d->mReadOnlyBackgroundColor);
355 p.setColor(QPalette::Window, d->mReadOnlyBackgroundColor);
356 setPalette(p);
357 }
358}
359
360void PlainTextEditor::setReadOnly(bool readOnly)
361{
362 if (!readOnly && hasFocus() && d->checkSpellingEnabled && !d->richTextDecorator) {
363 createHighlighter();
364 }
365
366 if (readOnly == isReadOnly()) {
367 return;
368 }
369
370 if (readOnly) {
371 clearDecorator();
372 d->customPalette = testAttribute(Qt::WA_SetPalette);
373 updateReadOnlyColor();
374 } else {
375 if (d->customPalette && testAttribute(Qt::WA_SetPalette)) {
376 QPalette p = palette();
378 p.setColor(QPalette::Base, color);
379 p.setColor(QPalette::Window, color);
380 setPalette(p);
381 } else {
383 }
384 }
385
387}
388
389void PlainTextEditor::slotCheckSpelling()
390{
391 if (document()->isEmpty()) {
392 slotDisplayMessageIndicator(i18n("Nothing to spell check."));
393 return;
394 }
395 auto backgroundSpellCheck = new Sonnet::BackgroundChecker;
396 if (backgroundSpellCheck->speller().availableBackends().isEmpty()) {
397 slotDisplayMessageIndicator(i18n("No backend available for spell checking."));
398 delete backgroundSpellCheck;
399 return;
400 }
401 if (!d->spellCheckingLanguage.isEmpty()) {
402 backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage);
403 }
404 if (!d->ignoreSpellCheckingWords.isEmpty()) {
405 for (const QString &word : std::as_const(d->ignoreSpellCheckingWords)) {
406 backgroundSpellCheck->speller().addToSession(word);
407 }
408 }
409 auto spellDialog = new Sonnet::Dialog(backgroundSpellCheck, nullptr);
410 backgroundSpellCheck->setParent(spellDialog);
411 spellDialog->setAttribute(Qt::WA_DeleteOnClose, true);
412 connect(spellDialog, &Sonnet::Dialog::replace, this, &PlainTextEditor::slotSpellCheckerCorrected);
413 connect(spellDialog, &Sonnet::Dialog::misspelling, this, &PlainTextEditor::slotSpellCheckerMisspelling);
414 connect(spellDialog, &Sonnet::Dialog::autoCorrect, this, &PlainTextEditor::slotSpellCheckerAutoCorrect);
415 connect(spellDialog, &Sonnet::Dialog::spellCheckDone, this, &PlainTextEditor::slotSpellCheckerFinished);
416 connect(spellDialog, &Sonnet::Dialog::cancel, this, &PlainTextEditor::slotSpellCheckerCanceled);
417 connect(spellDialog, &Sonnet::Dialog::spellCheckStatus, this, &PlainTextEditor::spellCheckStatus);
418 connect(spellDialog, &Sonnet::Dialog::languageChanged, this, &PlainTextEditor::languageChanged);
419 d->originalDoc = QTextDocumentFragment(document());
420 spellDialog->setBuffer(toPlainText());
421 spellDialog->show();
422}
423
424void PlainTextEditor::slotSpellCheckerCanceled()
425{
426 QTextDocument *doc = document();
427 doc->clear();
428 QTextCursor cursor(doc);
429 cursor.insertFragment(d->originalDoc);
430 slotSpellCheckerFinished();
431}
432
433void PlainTextEditor::slotSpellCheckerAutoCorrect(const QString &currentWord, const QString &autoCorrectWord)
434{
435 Q_EMIT spellCheckerAutoCorrect(currentWord, autoCorrectWord);
436}
437
438void PlainTextEditor::slotSpellCheckerMisspelling(const QString &text, int pos)
439{
440 highlightWord(text.length(), pos);
441}
442
443void PlainTextEditor::slotSpellCheckerCorrected(const QString &oldWord, int pos, const QString &newWord)
444{
445 if (oldWord != newWord) {
447 cursor.setPosition(pos);
448 cursor.setPosition(pos + oldWord.length(), QTextCursor::KeepAnchor);
449 cursor.insertText(newWord);
450 }
451}
452
453void PlainTextEditor::slotSpellCheckerFinished()
454{
456 cursor.clearSelection();
458}
459
460void PlainTextEditor::highlightWord(int length, int pos)
461{
463 cursor.setPosition(pos);
464 cursor.setPosition(pos + length, QTextCursor::KeepAnchor);
467}
468
469static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
470{
471 cursor.clearSelection();
473 cursor.removeSelectedText();
474}
475
476void PlainTextEditor::deleteWordBack()
477{
479}
480
481void PlainTextEditor::deleteWordForward()
482{
483 deleteWord(textCursor(), QTextCursor::WordRight);
484}
485
486bool PlainTextEditor::event(QEvent *ev)
487{
488 if (ev->type() == QEvent::ShortcutOverride) {
489 auto e = static_cast<QKeyEvent *>(ev);
490 if (overrideShortcut(e)) {
491 e->accept();
492 return true;
493 }
494 } else if (ev->type() == QEvent::ApplicationPaletteChange) {
495 regenerateColorScheme();
496 }
497
498 return QPlainTextEdit::event(ev);
499}
500
501bool PlainTextEditor::overrideShortcut(QKeyEvent *event)
502{
503 const int key = event->key() | event->modifiers();
504 if (KStandardShortcut::copy().contains(key)) {
505 return true;
506 } else if (KStandardShortcut::paste().contains(key)) {
507 return true;
508 } else if (KStandardShortcut::cut().contains(key)) {
509 return true;
510 } else if (KStandardShortcut::undo().contains(key)) {
511 return true;
512 } else if (KStandardShortcut::redo().contains(key)) {
513 return true;
514 } else if (KStandardShortcut::deleteWordBack().contains(key)) {
515 return true;
516 } else if (KStandardShortcut::deleteWordForward().contains(key)) {
517 return true;
518 } else if (KStandardShortcut::backwardWord().contains(key)) {
519 return true;
520 } else if (KStandardShortcut::forwardWord().contains(key)) {
521 return true;
522 } else if (KStandardShortcut::next().contains(key)) {
523 return true;
524 } else if (KStandardShortcut::prior().contains(key)) {
525 return true;
526 } else if (KStandardShortcut::begin().contains(key)) {
527 return true;
528 } else if (KStandardShortcut::end().contains(key)) {
529 return true;
530 } else if (KStandardShortcut::beginningOfLine().contains(key)) {
531 return true;
532 } else if (KStandardShortcut::endOfLine().contains(key)) {
533 return true;
534 } else if (KStandardShortcut::pasteSelection().contains(key)) {
535 return true;
536 } else if (searchSupport() && KStandardShortcut::find().contains(key)) {
537 return true;
538 } else if (searchSupport() && KStandardShortcut::replace().contains(key)) {
539 return true;
540 } else if (searchSupport() && KStandardShortcut::findNext().contains(key)) {
541 return true;
542 } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
543 return true;
544 } else if (event == QKeySequence::DeleteEndOfLine) {
545 return true;
546 }
547 return false;
548}
549
550bool PlainTextEditor::handleShortcut(QKeyEvent *event)
551{
552 const int key = event->key() | event->modifiers();
553
554 if (KStandardShortcut::copy().contains(key)) {
555 copy();
556 return true;
557 } else if (KStandardShortcut::paste().contains(key)) {
558 paste();
559 return true;
560 } else if (KStandardShortcut::cut().contains(key)) {
561 cut();
562 return true;
563 } else if (KStandardShortcut::undo().contains(key)) {
564 if (!isReadOnly()) {
565 undo();
566 }
567 return true;
568 } else if (KStandardShortcut::redo().contains(key)) {
569 if (!isReadOnly()) {
570 redo();
571 }
572 return true;
573 } else if (KStandardShortcut::deleteWordBack().contains(key)) {
574 if (!isReadOnly()) {
575 deleteWordBack();
576 }
577 return true;
578 } else if (KStandardShortcut::deleteWordForward().contains(key)) {
579 if (!isReadOnly()) {
580 deleteWordForward();
581 }
582 return true;
583 } else if (KStandardShortcut::backwardWord().contains(key)) {
585 cursor.movePosition(QTextCursor::PreviousWord);
587 return true;
588 } else if (KStandardShortcut::forwardWord().contains(key)) {
590 cursor.movePosition(QTextCursor::NextWord);
592 return true;
593 } else if (KStandardShortcut::next().contains(key)) {
595 bool moved = false;
596 qreal lastY = cursorRect(cursor).bottom();
597 qreal distance = 0;
598 do {
599 qreal y = cursorRect(cursor).bottom();
600 distance += qAbs(y - lastY);
601 lastY = y;
602 moved = cursor.movePosition(QTextCursor::Down);
603 } while (moved && distance < viewport()->height());
604
605 if (moved) {
606 cursor.movePosition(QTextCursor::Up);
608 }
610 return true;
611 } else if (KStandardShortcut::prior().contains(key)) {
613 bool moved = false;
614 qreal lastY = cursorRect(cursor).bottom();
615 qreal distance = 0;
616 do {
617 qreal y = cursorRect(cursor).bottom();
618 distance += qAbs(y - lastY);
619 lastY = y;
620 moved = cursor.movePosition(QTextCursor::Up);
621 } while (moved && distance < viewport()->height());
622
623 if (moved) {
624 cursor.movePosition(QTextCursor::Down);
626 }
628 return true;
629 } else if (KStandardShortcut::begin().contains(key)) {
631 cursor.movePosition(QTextCursor::Start);
633 return true;
634 } else if (KStandardShortcut::end().contains(key)) {
636 cursor.movePosition(QTextCursor::End);
638 return true;
639 } else if (KStandardShortcut::beginningOfLine().contains(key)) {
641 cursor.movePosition(QTextCursor::StartOfLine);
643 return true;
644 } else if (KStandardShortcut::endOfLine().contains(key)) {
646 cursor.movePosition(QTextCursor::EndOfLine);
648 return true;
649 } else if (searchSupport() && KStandardShortcut::find().contains(key)) {
650 Q_EMIT findText();
651 return true;
652 } else if (searchSupport() && KStandardShortcut::replace().contains(key)) {
653 if (!isReadOnly()) {
654 Q_EMIT replaceText();
655 }
656 return true;
657 } else if (KStandardShortcut::pasteSelection().contains(key)) {
659 if (!text.isEmpty()) {
660 insertPlainText(text); // TODO: check if this is html? (MiB)
661 }
662 return true;
663 } else if (event == QKeySequence::DeleteEndOfLine) {
664 deleteEndOfLine();
665 return true;
666 }
667 return false;
668}
669
670void PlainTextEditor::deleteEndOfLine()
671{
673 QTextBlock block = cursor.block();
674 if (cursor.position() == block.position() + block.length() - 2) {
676 } else {
678 }
679 cursor.removeSelectedText();
681}
682
683void PlainTextEditor::moveCursorBeginUpDown(bool moveUp)
684{
688 cursor.clearSelection();
689 move.movePosition(QTextCursor::StartOfBlock);
691 move.endEditBlock();
693}
694
695void PlainTextEditor::moveLineUpDown(bool moveUp)
696{
700
701 const bool hasSelection = cursor.hasSelection();
702
703 if (hasSelection) {
704 move.setPosition(cursor.selectionStart());
705 move.movePosition(QTextCursor::StartOfBlock);
706 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
708 } else {
709 move.movePosition(QTextCursor::StartOfBlock);
711 }
712 const QString text = move.selectedText();
713
715 move.removeSelectedText();
716
717 if (moveUp) {
718 move.movePosition(QTextCursor::PreviousBlock);
719 move.insertBlock();
720 move.movePosition(QTextCursor::Left);
721 } else {
722 move.movePosition(QTextCursor::EndOfBlock);
723 if (move.atBlockStart()) { // empty block
724 move.movePosition(QTextCursor::NextBlock);
725 move.insertBlock();
726 move.movePosition(QTextCursor::Left);
727 } else {
728 move.insertBlock();
729 }
730 }
731
732 int start = move.position();
733 move.clearSelection();
734 move.insertText(text);
735 int end = move.position();
736
737 if (hasSelection) {
738 move.setPosition(end);
739 move.setPosition(start, QTextCursor::KeepAnchor);
740 } else {
741 move.setPosition(start);
742 }
743 move.endEditBlock();
744
746}
747
748void PlainTextEditor::wheelEvent(QWheelEvent *event)
749{
751 if (event->angleDelta().y() > 0) {
752 zoomIn();
753 } else if (event->angleDelta().y() < 0) {
754 zoomOut();
755 }
756 event->accept();
757 return;
758 }
760}
761
762void PlainTextEditor::keyPressEvent(QKeyEvent *event)
763{
764 const bool isControlClicked = event->modifiers() & Qt::ControlModifier;
765 const bool isShiftClicked = event->modifiers() & Qt::ShiftModifier;
766 if (handleShortcut(event)) {
767 event->accept();
768 } else if (event->key() == Qt::Key_Up && isControlClicked && isShiftClicked) {
769 moveLineUpDown(true);
770 event->accept();
771 } else if (event->key() == Qt::Key_Down && isControlClicked && isShiftClicked) {
772 moveLineUpDown(false);
773 event->accept();
774 } else if (event->key() == Qt::Key_Up && isControlClicked) {
775 moveCursorBeginUpDown(true);
776 event->accept();
777 } else if (event->key() == Qt::Key_Down && isControlClicked) {
778 moveCursorBeginUpDown(false);
779 event->accept();
780 } else {
782 }
783}
784
785bool PlainTextEditor::activateLanguageMenu() const
786{
787 return d->activateLanguageMenu;
788}
789
790void PlainTextEditor::setActivateLanguageMenu(bool activate)
791{
792 d->activateLanguageMenu = activate;
793}
794
795Sonnet::Highlighter *PlainTextEditor::highlighter() const
796{
797 if (d->richTextDecorator) {
798 return d->richTextDecorator->highlighter();
799 } else {
800 return nullptr;
801 }
802}
803
804Sonnet::SpellCheckDecorator *PlainTextEditor::createSpellCheckDecorator()
805{
806 return new Sonnet::SpellCheckDecorator(this);
807}
808
809void PlainTextEditor::addIgnoreWordsToHighLighter()
810{
811 if (d->ignoreSpellCheckingWords.isEmpty()) {
812 return;
813 }
814 if (d->richTextDecorator) {
815 Sonnet::Highlighter *_highlighter = d->richTextDecorator->highlighter();
816 for (const QString &word : std::as_const(d->ignoreSpellCheckingWords)) {
817 _highlighter->ignoreWord(word);
818 }
819 }
820}
821
822void PlainTextEditor::setHighlighter(Sonnet::Highlighter *_highLighter)
823{
824 Sonnet::SpellCheckDecorator *decorator = createSpellCheckDecorator();
825 delete decorator->highlighter();
826 decorator->setHighlighter(_highLighter);
827
828 d->richTextDecorator = decorator;
829 addIgnoreWordsToHighLighter();
830}
831
832void PlainTextEditor::focusInEvent(QFocusEvent *event)
833{
834 if (checkSpellingEnabled() && !isReadOnly() && !d->richTextDecorator && spellCheckingSupport()) {
835 createHighlighter();
836 }
837
839}
840
841bool PlainTextEditor::checkSpellingEnabled() const
842{
843 return d->checkSpellingEnabled;
844}
845
846void PlainTextEditor::setCheckSpellingEnabled(bool check)
847{
848 if (check == d->checkSpellingEnabled) {
849 return;
850 }
851 d->checkSpellingEnabled = check;
852 Q_EMIT checkSpellingChanged(check);
853 // From the above statement we know that if we're turning checking
854 // on that we need to create a new highlighter and if we're turning it
855 // off we should remove the old one.
856 if (check) {
857 if (hasFocus()) {
858 if (!d->richTextDecorator) {
859 createHighlighter();
860 }
861 if (!d->spellCheckingLanguage.isEmpty()) {
862 setSpellCheckingLanguage(spellCheckingLanguage());
863 }
864 }
865 } else {
866 clearDecorator();
867 }
868 updateHighLighter();
869}
870
871void PlainTextEditor::updateHighLighter()
872{
873}
874
875void PlainTextEditor::clearDecorator()
876{
877 delete d->richTextDecorator;
878 d->richTextDecorator = nullptr;
879}
880
881void PlainTextEditor::createHighlighter()
882{
883 auto highlighter = new Sonnet::Highlighter(this);
884 highlighter->setCurrentLanguage(spellCheckingLanguage());
885 setHighlighter(highlighter);
886}
887
888void PlainTextEditor::setSpellCheckingConfigFileName(const QString &_fileName)
889{
890 d->spellCheckingConfigFileName = _fileName;
891 KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
892 if (config->hasGroup("Spelling"_L1)) {
893 KConfigGroup group(config, "Spelling"_L1);
894 d->checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
895 d->spellCheckingLanguage = group.readEntry("Language", QString());
896 }
897 setCheckSpellingEnabled(checkSpellingEnabled());
898
899 if (!d->spellCheckingLanguage.isEmpty() && highlighter()) {
900 highlighter()->setCurrentLanguage(d->spellCheckingLanguage);
901 highlighter()->rehighlight();
902 }
903}
904
905QString PlainTextEditor::spellCheckingConfigFileName() const
906{
907 return d->spellCheckingConfigFileName;
908}
909
910void PlainTextEditor::slotLanguageSelected()
911{
912 auto languageAction = static_cast<QAction *>(QObject::sender());
913 setSpellCheckingLanguage(languageAction->data().toString());
914}
915
916const QString &PlainTextEditor::spellCheckingLanguage() const
917{
918 return d->spellCheckingLanguage;
919}
920
921void PlainTextEditor::setSpellCheckingLanguage(const QString &_language)
922{
923 if (highlighter()) {
924 highlighter()->setCurrentLanguage(_language);
925 highlighter()->rehighlight();
926 }
927
928 if (_language != d->spellCheckingLanguage) {
929 d->spellCheckingLanguage = _language;
930 KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
931 KConfigGroup group(config, "Spelling"_L1);
932 group.writeEntry("Language", d->spellCheckingLanguage);
933 setCheckSpellingEnabled(checkSpellingEnabled());
934
935 Q_EMIT languageChanged(_language);
936 }
937}
938
939void PlainTextEditor::slotToggleAutoSpellCheck()
940{
941 setCheckSpellingEnabled(!checkSpellingEnabled());
942 KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
943 KConfigGroup group(config, "Spelling"_L1);
944 group.writeEntry("checkerEnabledByDefault", d->checkSpellingEnabled);
945}
946
947void PlainTextEditor::slotZoomReset()
948{
949 QFont f = font();
950 if (d->mInitialFontSize != f.pointSize()) {
951 f.setPointSize(d->mInitialFontSize);
952 setFont(f);
953 }
954}
955
956#include "moc_plaintexteditor.cpp"
QBrush background(BackgroundRole=NormalBackground) const
static void setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter=false)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void spellCheckDone(const QString &newBuffer)
void spellCheckStatus(const QString &)
void languageChanged(const QString &language)
void ignoreWord(const QString &word)
void setCurrentLanguage(const QString &language)
Highlighter * highlighter() const
void setHighlighter(Highlighter *highlighter)
The PlainTextEditor class.
A widget that displays messages in the top-left corner.
The TextEmoticonsWidgets::EmoticonTextEditAction class.
void insertEmoticon(const QString &)
This signal is emitted each time the user selects an emoji.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
const QList< QKeySequence > & beginningOfLine()
const QList< QKeySequence > & begin()
const QList< QKeySequence > & cut()
const QList< QKeySequence > & undo()
const QList< QKeySequence > & next()
const QList< QKeySequence > & deleteWordBack()
const QList< QKeySequence > & find()
const QList< QKeySequence > & paste()
const QList< QKeySequence > & end()
const QList< QKeySequence > & copy()
const QList< QKeySequence > & backwardWord()
const QList< QKeySequence > & endOfLine()
const QList< QKeySequence > & forwardWord()
const QList< QKeySequence > & deleteWordForward()
const QList< QKeySequence > & findNext()
const QList< QKeySequence > & prior()
const QList< QKeySequence > & replace()
const QList< QKeySequence > & pasteSelection()
const QList< QKeySequence > & redo()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
virtual bool event(QEvent *event) override
QScrollBar * verticalScrollBar() const const
QWidget * viewport() const const
void triggerAction(SliderAction action)
void setCheckable(bool)
void setChecked(bool)
QVariant data() const const
void setIcon(const QIcon &icon)
void setActionGroup(QActionGroup *group)
void setData(const QVariant &data)
void triggered(bool checked)
const QColor & color() const const
QString text(Mode mode) const const
ShortcutOverride
Type type() const const
int pointSize() const const
void setPointSize(int pointSize)
QClipboard * clipboard()
Qt::KeyboardModifiers keyboardModifiers()
QIcon fromTheme(const QString &name)
const_reference at(qsizetype i) const const
qsizetype count() const const
qsizetype indexOf(const AT &value, qsizetype from) const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addMenu(QMenu *menu)
QAction * addSeparator()
QAction * exec()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * sender() const const
const QColor & color(ColorGroup group, ColorRole role) const const
void setColor(ColorGroup group, ColorRole role, const QColor &color)
QMenu * createStandardContextMenu()
QRect cursorRect() const const
QTextDocument * document() const const
void ensureCursorVisible()
virtual void focusInEvent(QFocusEvent *e) override
void insertPlainText(const QString &text)
virtual void keyPressEvent(QKeyEvent *e) override
bool isReadOnly() const const
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
QString toPlainText() const const
virtual void wheelEvent(QWheelEvent *e) override
void zoomIn(int range)
void zoomOut(int range)
int bottom() const const
bool isEmpty() const const
qsizetype length() const const
ControlModifier
WA_SetPalette
int length() const const
int position() const const
void beginEditBlock()
void clearSelection()
bool movePosition(MoveOperation operation, MoveMode mode, int n)
void removeSelectedText()
QString selectedText() const const
virtual void clear()
bool isEmpty() const const
QString toString() const const
QList< QAction * > actions() const const
bool hasFocus() const const
void insertAction(QAction *before, QAction *action)
bool testAttribute(Qt::WidgetAttribute attribute) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 3 2025 11:46:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.