KTextAddons

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

KDE's Doxygen guidelines are available online.