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

KDE's Doxygen guidelines are available online.