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

KDE's Doxygen guidelines are available online.