KTextWidgets

ktextedit.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
4 SPDX-FileCopyrightText: 2005 Michael Brade <brade@kde.org>
5 SPDX-FileCopyrightText: 2012 Laurent Montel <montel@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "ktextedit.h"
11#include "ktextedit_p.h"
12
13#include <QAction>
14#include <QActionGroup>
15#include <QApplication>
16#include <QClipboard>
17#include <QDebug>
18#include <QKeyEvent>
19#include <QMenu>
20#include <QScrollBar>
21#include <QTextCursor>
22
23#include <KCursor>
24#include <KLocalizedString>
25#include <KMessageBox>
26#include <KStandardActions>
27#include <KStandardShortcut>
28#include <sonnet/backgroundchecker.h>
29#include <sonnet/configdialog.h>
30#include <sonnet/dialog.h>
31
32class KTextDecorator : public Sonnet::SpellCheckDecorator
33{
34public:
35 explicit KTextDecorator(KTextEdit *textEdit);
36 bool isSpellCheckingEnabledForBlock(const QString &textBlock) const override;
37
38private:
39 KTextEdit *m_textEdit;
40};
41
42void KTextEditPrivate::checkSpelling(bool force)
43{
44 Q_Q(KTextEdit);
45
46 if (q->document()->isEmpty()) {
47 KMessageBox::information(q, i18n("Nothing to spell check."));
48 if (force) {
49 Q_EMIT q->spellCheckingFinished();
50 }
51 return;
52 }
53 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker;
54 if (!spellCheckingLanguage.isEmpty()) {
55 backgroundSpellCheck->changeLanguage(spellCheckingLanguage);
56 }
57 Sonnet::Dialog *spellDialog = new Sonnet::Dialog(backgroundSpellCheck, force ? q : nullptr);
58 backgroundSpellCheck->setParent(spellDialog);
59 spellDialog->setAttribute(Qt::WA_DeleteOnClose, true);
60 spellDialog->activeAutoCorrect(showAutoCorrectionButton);
61 QObject::connect(spellDialog, &Sonnet::Dialog::replace, q, [this](const QString &oldWord, int pos, const QString &newWord) {
62 spellCheckerCorrected(oldWord, pos, newWord);
63 });
64 QObject::connect(spellDialog, &Sonnet::Dialog::misspelling, q, [this](const QString &text, int pos) {
65 spellCheckerMisspelling(text, pos);
66 });
67 QObject::connect(spellDialog, &Sonnet::Dialog::autoCorrect, q, &KTextEdit::spellCheckerAutoCorrect);
68 QObject::connect(spellDialog, &Sonnet::Dialog::spellCheckDone, q, [this]() {
69 spellCheckerFinished();
70 });
71 QObject::connect(spellDialog, &Sonnet::Dialog::cancel, q, [this]() {
72 spellCheckerCanceled();
73 });
74
75 // Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary
76 // connect(spellDialog, SIGNAL(stop()), q, SLOT(spellCheckerFinished()));
77
80 if (force) {
82 QObject::connect(spellDialog, &Sonnet::Dialog::cancel, q, &KTextEdit::spellCheckingCanceled);
83 // Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary
84 // connect(spellDialog, SIGNAL(stop()), q, SIGNAL(spellCheckingFinished()));
85 }
86 originalDoc = QTextDocumentFragment(q->document());
87 spellDialog->setBuffer(q->toPlainText());
88 spellDialog->show();
89}
90
91void KTextEditPrivate::spellCheckerCanceled()
92{
93 Q_Q(KTextEdit);
94
95 QTextDocument *doc = q->document();
96 doc->clear();
97 QTextCursor cursor(doc);
98 cursor.insertFragment(originalDoc);
99 spellCheckerFinished();
100}
101
102void KTextEditPrivate::spellCheckerAutoCorrect(const QString &currentWord, const QString &autoCorrectWord)
103{
104 Q_Q(KTextEdit);
105
106 Q_EMIT q->spellCheckerAutoCorrect(currentWord, autoCorrectWord);
107}
108
109void KTextEditPrivate::spellCheckerMisspelling(const QString &text, int pos)
110{
111 Q_Q(KTextEdit);
112
113 // qDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<<text<<" pos :"<<pos;
114 q->highlightWord(text.length(), pos);
115}
116
117void KTextEditPrivate::spellCheckerCorrected(const QString &oldWord, int pos, const QString &newWord)
118{
119 Q_Q(KTextEdit);
120
121 // qDebug()<<" oldWord :"<<oldWord<<" newWord :"<<newWord<<" pos : "<<pos;
122 if (oldWord != newWord) {
123 QTextCursor cursor(q->document());
124 cursor.setPosition(pos);
125 cursor.setPosition(pos + oldWord.length(), QTextCursor::KeepAnchor);
126 cursor.insertText(newWord);
127 }
128}
129
130void KTextEditPrivate::spellCheckerFinished()
131{
132 Q_Q(KTextEdit);
133
134 QTextCursor cursor(q->document());
135 cursor.clearSelection();
136 q->setTextCursor(cursor);
137 if (q->highlighter()) {
138 q->highlighter()->rehighlight();
139 }
140}
141
142void KTextEditPrivate::toggleAutoSpellCheck()
143{
144 Q_Q(KTextEdit);
145
146 q->setCheckSpellingEnabled(!q->checkSpellingEnabled());
147}
148
149void KTextEditPrivate::undoableClear()
150{
151 Q_Q(KTextEdit);
152
153 QTextCursor cursor = q->textCursor();
154 cursor.beginEditBlock();
157 cursor.removeSelectedText();
158 cursor.endEditBlock();
159}
160
161void KTextEditPrivate::slotAllowTab()
162{
163 Q_Q(KTextEdit);
164
165 q->setTabChangesFocus(!q->tabChangesFocus());
166}
167
168void KTextEditPrivate::menuActivated(QAction *action)
169{
170 Q_Q(KTextEdit);
171
172 if (action == spellCheckAction) {
173 q->checkSpelling();
174 } else if (action == autoSpellCheckAction) {
175 toggleAutoSpellCheck();
176 } else if (action == allowTab) {
177 slotAllowTab();
178 }
179}
180
181void KTextEditPrivate::slotFindHighlight(const QString &text, int matchingIndex, int matchingLength)
182{
183 Q_Q(KTextEdit);
184
185 Q_UNUSED(text)
186 // qDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength;
187 QTextCursor tc = q->textCursor();
188 tc.setPosition(matchingIndex);
190 q->setTextCursor(tc);
191 q->ensureCursorVisible();
192}
193
194void KTextEditPrivate::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength)
195{
196 Q_Q(KTextEdit);
197
198 // qDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength;
199 QTextCursor tc = q->textCursor();
200 tc.setPosition(replacementIndex);
203 tc.insertText(text.mid(replacementIndex, replacedLength));
204 if (replace->options() & KReplaceDialog::PromptOnReplace) {
205 q->setTextCursor(tc);
206 q->ensureCursorVisible();
207 }
208 lastReplacedPosition = replacementIndex;
209}
210
211void KTextEditPrivate::init()
212{
213 Q_Q(KTextEdit);
214
215 KCursor::setAutoHideCursor(q, true, false);
217}
218
219KTextDecorator::KTextDecorator(KTextEdit *textEdit)
220 : SpellCheckDecorator(textEdit)
221 , m_textEdit(textEdit)
222{
223}
224
225bool KTextDecorator::isSpellCheckingEnabledForBlock(const QString &textBlock) const
226{
227 return m_textEdit->shouldBlockBeSpellChecked(textBlock);
228}
229
231 : KTextEdit(*new KTextEditPrivate(this), text, parent)
232{
233}
234
235KTextEdit::KTextEdit(KTextEditPrivate &dd, const QString &text, QWidget *parent)
236 : QTextEdit(text, parent)
237 , d_ptr(&dd)
238{
239 Q_D(KTextEdit);
240
241 d->init();
242}
243
245 : KTextEdit(*new KTextEditPrivate(this), parent)
246{
247}
248
249KTextEdit::KTextEdit(KTextEditPrivate &dd, QWidget *parent)
250 : QTextEdit(parent)
251 , d_ptr(&dd)
252{
253 Q_D(KTextEdit);
254
255 d->init();
256}
257
258KTextEdit::~KTextEdit() = default;
259
260const QString &KTextEdit::spellCheckingLanguage() const
261{
262 Q_D(const KTextEdit);
263
264 return d->spellCheckingLanguage;
265}
266
268{
269 Q_D(KTextEdit);
270
271 if (highlighter()) {
272 highlighter()->setCurrentLanguage(_language);
274 }
275
276 if (_language != d->spellCheckingLanguage) {
277 d->spellCheckingLanguage = _language;
278 Q_EMIT languageChanged(_language);
279 }
280}
281
283{
284 Q_D(KTextEdit);
285
286 Sonnet::ConfigDialog dialog(this);
287 if (!d->spellCheckingLanguage.isEmpty()) {
288 dialog.setLanguage(d->spellCheckingLanguage);
289 }
290 if (!windowIcon.isEmpty()) {
292 }
293 if (dialog.exec()) {
295 }
296}
297
299{
300 Q_D(KTextEdit);
301
302 if (ev->type() == QEvent::ShortcutOverride) {
303 QKeyEvent *e = static_cast<QKeyEvent *>(ev);
304 if (d->overrideShortcut(e)) {
305 e->accept();
306 return true;
307 }
308 }
309 return QTextEdit::event(ev);
310}
311
312bool KTextEditPrivate::handleShortcut(const QKeyEvent *event)
313{
314 Q_Q(KTextEdit);
315
316 const int key = event->key() | event->modifiers();
317
318 if (KStandardShortcut::copy().contains(key)) {
319 q->copy();
320 return true;
321 } else if (KStandardShortcut::paste().contains(key)) {
322 q->paste();
323 return true;
324 } else if (KStandardShortcut::cut().contains(key)) {
325 q->cut();
326 return true;
327 } else if (KStandardShortcut::undo().contains(key)) {
328 if (!q->isReadOnly()) {
329 q->undo();
330 }
331 return true;
332 } else if (KStandardShortcut::redo().contains(key)) {
333 if (!q->isReadOnly()) {
334 q->redo();
335 }
336 return true;
337 } else if (KStandardShortcut::deleteWordBack().contains(key)) {
338 if (!q->isReadOnly()) {
339 q->deleteWordBack();
340 }
341 return true;
342 } else if (KStandardShortcut::deleteWordForward().contains(key)) {
343 if (!q->isReadOnly()) {
344 q->deleteWordForward();
345 }
346 return true;
347 } else if (KStandardShortcut::backwardWord().contains(key)) {
348 QTextCursor cursor = q->textCursor();
349 // We use visual positioning here since keyboard arrows represents visual direction (left, right)
351 q->setTextCursor(cursor);
352 return true;
353 } else if (KStandardShortcut::forwardWord().contains(key)) {
354 QTextCursor cursor = q->textCursor();
355 // We use visual positioning here since keyboard arrows represents visual direction (left, right)
357 q->setTextCursor(cursor);
358 return true;
359 } else if (KStandardShortcut::next().contains(key)) {
360 QTextCursor cursor = q->textCursor();
361 bool moved = false;
362 qreal lastY = q->cursorRect(cursor).bottom();
363 qreal distance = 0;
364 do {
365 qreal y = q->cursorRect(cursor).bottom();
366 distance += qAbs(y - lastY);
367 lastY = y;
368 moved = cursor.movePosition(QTextCursor::Down);
369 } while (moved && distance < q->viewport()->height());
370
371 if (moved) {
373 q->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
374 }
375 q->setTextCursor(cursor);
376 return true;
377 } else if (KStandardShortcut::prior().contains(key)) {
378 QTextCursor cursor = q->textCursor();
379 bool moved = false;
380 qreal lastY = q->cursorRect(cursor).bottom();
381 qreal distance = 0;
382 do {
383 qreal y = q->cursorRect(cursor).bottom();
384 distance += qAbs(y - lastY);
385 lastY = y;
386 moved = cursor.movePosition(QTextCursor::Up);
387 } while (moved && distance < q->viewport()->height());
388
389 if (moved) {
391 q->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
392 }
393 q->setTextCursor(cursor);
394 return true;
395 } else if (KStandardShortcut::begin().contains(key)) {
396 QTextCursor cursor = q->textCursor();
398 q->setTextCursor(cursor);
399 return true;
400 } else if (KStandardShortcut::end().contains(key)) {
401 QTextCursor cursor = q->textCursor();
403 q->setTextCursor(cursor);
404 return true;
405 } else if (KStandardShortcut::beginningOfLine().contains(key)) {
406 QTextCursor cursor = q->textCursor();
408 q->setTextCursor(cursor);
409 return true;
410 } else if (KStandardShortcut::endOfLine().contains(key)) {
411 QTextCursor cursor = q->textCursor();
413 q->setTextCursor(cursor);
414 return true;
415 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
416 q->slotFind();
417 return true;
418 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
419 q->slotFindNext();
420 return true;
421 } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) {
422 q->slotFindPrevious();
423 return true;
424 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
425 if (!q->isReadOnly()) {
426 q->slotReplace();
427 }
428 return true;
429 } else if (KStandardShortcut::pasteSelection().contains(key)) {
431 if (!text.isEmpty()) {
432 q->insertPlainText(text); // TODO: check if this is html? (MiB)
433 }
434 return true;
435 }
436 return false;
437}
438
439static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
440{
441 cursor.clearSelection();
443 cursor.removeSelectedText();
444}
445
447{
448 // We use logical positioning here since deleting should always delete the previous word
449 // (left in case of LTR text, right in case of RTL text)
451}
452
454{
455 // We use logical positioning here since deleting should always delete the previous word
456 // (left in case of LTR text, right in case of RTL text)
457 deleteWord(textCursor(), QTextCursor::NextWord);
458}
459
461{
462 Q_D(KTextEdit);
463
465 if (!popup) {
466 return nullptr;
467 }
468 connect(popup, &QMenu::triggered, this, [d](QAction *action) {
469 d->menuActivated(action);
470 });
471
472 const bool emptyDocument = document()->isEmpty();
473 if (!isReadOnly()) {
474 QList<QAction *> actionList = popup->actions();
475 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
476 QAction *separatorAction = nullptr;
477 int idx = actionList.indexOf(actionList[SelectAllAct]) + 1;
478 if (idx < actionList.count()) {
479 separatorAction = actionList.at(idx);
480 }
481
482 auto undoableClearSlot = [d]() {
483 d->undoableClear();
484 };
485
486 if (separatorAction) {
487 QAction *clearAllAction = KStandardActions::clear(this, undoableClearSlot, popup);
488 if (emptyDocument) {
489 clearAllAction->setEnabled(false);
490 }
491 popup->insertAction(separatorAction, clearAllAction);
492 }
493 }
494
495 if (!isReadOnly()) {
496 popup->addSeparator();
497 if (!d->speller) {
498 d->speller = new Sonnet::Speller();
499 }
500 if (!d->speller->availableBackends().isEmpty()) {
501 d->spellCheckAction = popup->addAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18nc("@action:inmenu", "Check Spelling…"));
502 if (emptyDocument) {
503 d->spellCheckAction->setEnabled(false);
504 }
505 if (checkSpellingEnabled()) {
506 d->languagesMenu = new QMenu(i18n("Spell Checking Language"), popup);
507 QActionGroup *languagesGroup = new QActionGroup(d->languagesMenu);
508 languagesGroup->setExclusive(true);
509
510 QMapIterator<QString, QString> i(d->speller->availableDictionaries());
511 const QString language = spellCheckingLanguage();
512 while (i.hasNext()) {
513 i.next();
514
515 QAction *languageAction = d->languagesMenu->addAction(i.key());
516 languageAction->setCheckable(true);
517 languageAction->setChecked(language == i.value() || (language.isEmpty() && d->speller->defaultLanguage() == i.value()));
518 languageAction->setData(i.value());
519 languageAction->setActionGroup(languagesGroup);
520 connect(languageAction, &QAction::triggered, [this, languageAction]() {
521 setSpellCheckingLanguage(languageAction->data().toString());
522 });
523 }
524 popup->addMenu(d->languagesMenu);
525 }
526
527 d->autoSpellCheckAction = popup->addAction(i18n("Auto Spell Check"));
528 d->autoSpellCheckAction->setCheckable(true);
529 d->autoSpellCheckAction->setChecked(checkSpellingEnabled());
530 popup->addSeparator();
531 }
532 if (d->showTabAction) {
533 d->allowTab = popup->addAction(i18n("Allow Tabulations"));
534 d->allowTab->setCheckable(true);
535 d->allowTab->setChecked(!tabChangesFocus());
536 }
537 }
538
539 if (d->findReplaceEnabled) {
540 QAction *findAction = KStandardActions::find(this, &KTextEdit::slotFind, popup);
541 QAction *findNextAction = KStandardActions::findNext(this, &KTextEdit::slotFindNext, popup);
542 QAction *findPrevAction = KStandardActions::findPrev(this, &KTextEdit::slotFindPrevious, popup);
543 if (emptyDocument) {
544 findAction->setEnabled(false);
545 findNextAction->setEnabled(false);
546 findPrevAction->setEnabled(false);
547 } else {
548 findNextAction->setEnabled(d->find != nullptr);
549 findPrevAction->setEnabled(d->find != nullptr);
550 }
551 popup->addSeparator();
552 popup->addAction(findAction);
553 popup->addAction(findNextAction);
554 popup->addAction(findPrevAction);
555
556 if (!isReadOnly()) {
557 QAction *replaceAction = KStandardActions::replace(this, &KTextEdit::slotReplace, popup);
558 if (emptyDocument) {
559 replaceAction->setEnabled(false);
560 }
561 popup->addAction(replaceAction);
562 }
563 }
564#ifdef HAVE_SPEECH
565 popup->addSeparator();
566 QAction *speakAction = popup->addAction(i18n("Speak Text"));
567 speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech")));
568 speakAction->setEnabled(!emptyDocument);
570#endif
571 return popup;
572}
573
575{
576#ifdef HAVE_SPEECH
577 Q_D(KTextEdit);
578 QString text;
579 if (textCursor().hasSelection()) {
580 text = textCursor().selectedText();
581 } else {
582 text = toPlainText();
583 }
584 if (!d->textToSpeech) {
585 d->textToSpeech = new QTextToSpeech(this);
586 }
587 d->textToSpeech->say(text);
588#endif
589}
590
592{
593 QMenu *popup = mousePopupMenu();
594 if (popup) {
596 popup->exec(event->globalPos());
597 delete popup;
598 }
599}
600
605
607{
608 Q_D(const KTextEdit);
609
610 if (d->decorator) {
611 return d->decorator->highlighter();
612 } else {
613 return nullptr;
614 }
615}
616
618{
619 Q_D(KTextEdit);
620
621 // Set pointer to null before deleting KTextDecorator as dtor will emit signal,
622 // which could call this code again and cause double delete/crash
623 auto decorator = d->decorator;
624 d->decorator = nullptr;
625 delete decorator;
626}
627
629{
630 Q_D(KTextEdit);
631
632 d->decorator = decorator;
633}
634
636{
637 KTextDecorator *decorator = new KTextDecorator(this);
638 // The old default highlighter must be manually deleted.
639 delete decorator->highlighter();
640 decorator->setHighlighter(_highLighter);
641
642 // KTextEdit used to take ownership of the highlighter, Sonnet::SpellCheckDecorator does not.
643 // so we reparent the highlighter so it will be deleted when the decorator is destroyed
644 _highLighter->setParent(decorator);
645 addTextDecorator(decorator);
646}
647
649{
650 Q_D(KTextEdit);
651
653 if (check == d->spellCheckingEnabled) {
654 return;
655 }
656
657 // From the above statement we now know that if we're turning checking
658 // on that we need to create a new highlighter and if we're turning it
659 // off we should remove the old one.
660
661 d->spellCheckingEnabled = check;
662 if (check) {
663 if (hasFocus()) {
665 if (!spellCheckingLanguage().isEmpty()) {
666 setSpellCheckingLanguage(spellCheckingLanguage());
667 }
668 }
669 } else {
671 }
672}
673
675{
676 Q_D(KTextEdit);
677
678 if (d->spellCheckingEnabled && !isReadOnly() && !d->decorator) {
680 }
681
683}
684
685bool KTextEdit::checkSpellingEnabled() const
686{
687 Q_D(const KTextEdit);
688
689 return d->spellCheckingEnabled;
690}
691
693{
694 return true;
695}
696
697void KTextEdit::setReadOnly(bool readOnly)
698{
699 Q_D(KTextEdit);
700
701 if (!readOnly && hasFocus() && d->spellCheckingEnabled && !d->decorator) {
703 }
704
705 if (readOnly == isReadOnly()) {
706 return;
707 }
708
709 if (readOnly) {
710 // Set pointer to null before deleting KTextDecorator as dtor will emit signal,
711 // which could call this code again and cause double delete/crash
712 auto decorator = d->decorator;
713 d->decorator = nullptr;
714 delete decorator;
715
716 d->customPalette = testAttribute(Qt::WA_SetPalette);
717 QPalette p = palette();
719 p.setColor(QPalette::Base, color);
720 p.setColor(QPalette::Window, color);
721 setPalette(p);
722 } else {
723 if (d->customPalette && testAttribute(Qt::WA_SetPalette)) {
724 QPalette p = palette();
726 p.setColor(QPalette::Base, color);
727 p.setColor(QPalette::Window, color);
728 setPalette(p);
729 } else {
731 }
732 }
733
735}
736
738{
739 Q_D(KTextEdit);
740
741 d->checkSpelling(false);
742}
743
745{
746 Q_D(KTextEdit);
747
748 d->checkSpelling(true);
749}
750
751void KTextEdit::highlightWord(int length, int pos)
752{
754 cursor.setPosition(pos);
755 cursor.setPosition(pos + length, QTextCursor::KeepAnchor);
758}
759
761{
762 Q_D(KTextEdit);
763
764 if (document()->isEmpty()) { // saves having to track the text changes
765 return;
766 }
767
768 if (d->repDlg) {
769 d->repDlg->activateWindow();
770 } else {
771 d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false);
773 }
774 d->repDlg->show();
775}
776
778{
779 Q_D(KTextEdit);
780
781 if (!d->repDlg) {
782 // Should really assert()
783 return;
784 }
785
786 if (d->repDlg->pattern().isEmpty()) {
787 delete d->replace;
788 d->replace = nullptr;
790 return;
791 }
792
793 delete d->replace;
794 d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this);
795 d->repIndex = 0;
796 if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) {
797 d->repIndex = textCursor().anchor();
798 }
799
800 // Connect textFound signal to code which handles highlighting of found text.
801 connect(d->replace, &KFind::textFound, this, [d](const QString &text, int matchingIndex, int matchedLength) {
802 d->slotFindHighlight(text, matchingIndex, matchedLength);
803 });
804 connect(d->replace, &KFind::findNext, this, &KTextEdit::slotReplaceNext);
805
806 connect(d->replace, &KReplace::textReplaced, this, [d](const QString &text, int replacementIndex, int replacedLength, int matchedLength) {
807 d->slotReplaceText(text, replacementIndex, replacedLength, matchedLength);
808 });
809
810 d->repDlg->close();
811 slotReplaceNext();
812}
813
814void KTextEdit::slotReplaceNext()
815{
816 Q_D(KTextEdit);
817
818 if (!d->replace) {
819 return;
820 }
821
822 d->lastReplacedPosition = -1;
823 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
824 textCursor().beginEditBlock(); // #48541
825 viewport()->setUpdatesEnabled(false);
826 }
827
828 if (d->replace->needData()) {
829 d->replace->setData(toPlainText(), d->repIndex);
830 }
831 KFind::Result res = d->replace->replace();
832 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
833 textCursor().endEditBlock(); // #48541
834 if (d->lastReplacedPosition >= 0) {
835 QTextCursor tc = textCursor();
836 tc.setPosition(d->lastReplacedPosition);
837 setTextCursor(tc);
839 }
840
842 viewport()->update();
843 }
844
845 if (res == KFind::NoMatch) {
846 d->replace->displayFinalDialog();
847 d->replace->disconnect(this);
848 d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away
849 d->replace = nullptr;
851 // or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
852 } else {
853 // m_replace->closeReplaceNextDialog();
854 }
855}
856
857void KTextEdit::slotDoFind()
858{
859 Q_D(KTextEdit);
860
861 if (!d->findDlg) {
862 // Should really assert()
863 return;
864 }
865 if (d->findDlg->pattern().isEmpty()) {
866 delete d->find;
867 d->find = nullptr;
868 return;
869 }
870 delete d->find;
871 d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this);
872 d->findIndex = 0;
873 if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) {
874 d->findIndex = textCursor().anchor();
875 }
876
877 // Connect textFound() signal to code which handles highlighting of found text
878 connect(d->find, &KFind::textFound, this, [d](const QString &text, int matchingIndex, int matchedLength) {
879 d->slotFindHighlight(text, matchingIndex, matchedLength);
880 });
881 connect(d->find, &KFind::findNext, this, &KTextEdit::slotFindNext);
882
883 d->findDlg->close();
884 d->find->closeFindNextDialog();
885 slotFindNext();
886}
887
888void KTextEdit::slotFindNext()
889{
890 Q_D(KTextEdit);
891
892 if (!d->find) {
893 return;
894 }
895 if (document()->isEmpty()) {
896 d->find->disconnect(this);
897 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
898 d->find = nullptr;
899 return;
900 }
901
902 if (d->find->needData()) {
903 d->find->setData(toPlainText(), d->findIndex);
904 }
905 KFind::Result res = d->find->find();
906
907 if (res == KFind::NoMatch) {
908 d->find->displayFinalDialog();
909 d->find->disconnect(this);
910 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
911 d->find = nullptr;
912 // or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); }
913 } else {
914 // m_find->closeFindNextDialog();
915 }
916}
917
919{
920 Q_D(KTextEdit);
921
922 if (!d->find) {
923 return;
924 }
925 const long oldOptions = d->find->options();
926 d->find->setOptions(oldOptions ^ KFind::FindBackwards);
927 slotFindNext();
928 if (d->find) {
929 d->find->setOptions(oldOptions);
930 }
931}
932
933void KTextEdit::slotFind()
934{
935 Q_D(KTextEdit);
936
937 if (document()->isEmpty()) { // saves having to track the text changes
938 return;
939 }
940
941 if (d->findDlg) {
942 d->findDlg->activateWindow();
943 } else {
944 d->findDlg = new KFindDialog(this);
945 connect(d->findDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoFind);
946 }
947 d->findDlg->show();
948}
949
950void KTextEdit::slotReplace()
951{
952 Q_D(KTextEdit);
953
954 if (document()->isEmpty()) { // saves having to track the text changes
955 return;
956 }
957
958 if (d->repDlg) {
959 d->repDlg->activateWindow();
960 } else {
961 d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false);
963 }
964 d->repDlg->show();
965}
966
968{
969 Q_D(KTextEdit);
970
971 d->findReplaceEnabled = enabled;
972}
973
975{
976 Q_D(KTextEdit);
977
978 d->showTabAction = show;
979}
980
981bool KTextEditPrivate::overrideShortcut(const QKeyEvent *event)
982{
983 const int key = event->key() | event->modifiers();
984
985 if (KStandardShortcut::copy().contains(key)) {
986 return true;
987 } else if (KStandardShortcut::paste().contains(key)) {
988 return true;
989 } else if (KStandardShortcut::cut().contains(key)) {
990 return true;
991 } else if (KStandardShortcut::undo().contains(key)) {
992 return true;
993 } else if (KStandardShortcut::redo().contains(key)) {
994 return true;
995 } else if (KStandardShortcut::deleteWordBack().contains(key)) {
996 return true;
997 } else if (KStandardShortcut::deleteWordForward().contains(key)) {
998 return true;
999 } else if (KStandardShortcut::backwardWord().contains(key)) {
1000 return true;
1001 } else if (KStandardShortcut::forwardWord().contains(key)) {
1002 return true;
1003 } else if (KStandardShortcut::next().contains(key)) {
1004 return true;
1005 } else if (KStandardShortcut::prior().contains(key)) {
1006 return true;
1007 } else if (KStandardShortcut::begin().contains(key)) {
1008 return true;
1009 } else if (KStandardShortcut::end().contains(key)) {
1010 return true;
1011 } else if (KStandardShortcut::beginningOfLine().contains(key)) {
1012 return true;
1013 } else if (KStandardShortcut::endOfLine().contains(key)) {
1014 return true;
1015 } else if (KStandardShortcut::pasteSelection().contains(key)) {
1016 return true;
1017 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
1018 return true;
1019 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
1020 return true;
1021 } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) {
1022 return true;
1023 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
1024 return true;
1025 } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
1026 return true;
1027 }
1028 return false;
1029}
1030
1032{
1033 Q_D(KTextEdit);
1034
1035 if (d->handleShortcut(event)) {
1036 event->accept();
1037 } else {
1039 }
1040}
1041
1043{
1044 Q_D(KTextEdit);
1045
1046 d->showAutoCorrectionButton = show;
1047}
1048
1049#include "moc_ktextedit.cpp"
static void setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter=false)
A generic "find" dialog.
Definition kfinddialog.h:66
void okClicked()
This signal is sent when the user clicks on Ok button.
A generic implementation of the "find" function.
Definition kfind.h:94
void textFound(const QString &text, int matchingIndex, int matchedLength)
Connect to this signal to implement highlighting of found text during the find operation.
@ FromCursor
Start from current cursor position.
Definition kfind.h:103
@ FindBackwards
Go backwards.
Definition kfind.h:106
A generic "replace" dialog.
@ PromptOnReplace
Should the user be prompted before the replace operation?
A generic implementation of the "replace" function.
Definition kreplace.h:90
void textReplaced(const QString &text, int replacementIndex, int replacedLength, int matchedLength)
Connect to this signal to implement updating of replaced text during the replace operation.
A KDE'ified QTextEdit.
Definition ktextedit.h:46
void forceSpellChecking()
void clearDecorator()
clearDecorator clear the spellcheckerdecorator
void setSpellCheckingLanguage(const QString &language)
Set the spell check language which will be used for highlighting spelling mistakes and for the spellc...
void slotSpeakText()
virtual bool shouldBlockBeSpellChecked(const QString &block) const
Returns true if the given paragraph or block should be spellcheck.
void checkSpelling()
Show a dialog to check the spelling.
virtual void setCheckSpellingEnabled(bool check)
Turns background spell checking for this text edit on or off.
virtual void createHighlighter()
Allows to create a specific highlighter if reimplemented.
void slotDoReplace()
void addTextDecorator(Sonnet::SpellCheckDecorator *decorator)
Add custom spell checker decorator.
void spellCheckingCanceled()
signal spellCheckingCanceled is sent when we cancel spell checking.
void showSpellConfigDialog(const QString &windowIcon=QString())
Opens a Sonnet::ConfigDialog for this text edit.
void highlightWord(int length, int pos)
Selects the characters at the specified position.
virtual void setReadOnly(bool readOnly)
Reimplemented to set a proper "deactivated" background color.
KTextEdit(const QString &text, QWidget *parent=nullptr)
Constructs a KTextEdit object.
void replace()
Create replace dialogbox.
virtual void deleteWordBack()
Deletes a word backwards from the current cursor position, if available.
void enableFindReplace(bool enabled)
Enable find replace action.
void showAutoCorrectButton(bool show)
~KTextEdit() override
Destroys the KTextEdit object.
bool event(QEvent *) override
Reimplemented to catch "delete word" shortcut events.
void contextMenuEvent(QContextMenuEvent *) override
Reimplemented from QTextEdit to add spelling related items when appropriate.
void keyPressEvent(QKeyEvent *) override
Reimplemented for internal reasons.
void spellCheckingFinished()
signal spellCheckingFinished is sent when we finish spell check or we click on "Terminate" button in ...
virtual QMenu * mousePopupMenu()
Return standard KTextEdit popupMenu.
void focusInEvent(QFocusEvent *) override
Reimplemented to instantiate a KDictSpellingHighlighter, if spellchecking is enabled.
void showTabAction(bool show)
void spellCheckerAutoCorrect(const QString &currentWord, const QString &autoCorrectWord)
void slotFindPrevious()
void setHighlighter(Sonnet::Highlighter *_highLighter)
Sets a custom background spell highlighter for this text edit.
void checkSpellingChanged(bool)
emit signal when we activate or not autospellchecking
void spellCheckStatus(const QString &)
Signal sends when spell checking is finished/stopped/completed.
void aboutToShowContextMenu(QMenu *menu)
Emitted before the context menu is displayed.
Sonnet::Highlighter * highlighter() const
Returns the current highlighter, which is 0 if spell checking is disabled.
void languageChanged(const QString &language)
Emitted when the user changes the language in the spellcheck dialog shown by checkSpelling() or when ...
virtual void deleteWordForward()
Deletes a word forwards from the current cursor position, if available.
QString language() const
void setLanguage(const QString &language)
void spellCheckDone(const QString &newBuffer)
void spellCheckStatus(const QString &)
void languageChanged(const QString &language)
void setCurrentLanguage(const QString &language)
Highlighter * highlighter() const
void setHighlighter(Highlighter *highlighter)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
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 > & findPrev()
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
QWidget * viewport() const const
void setCheckable(bool)
void setChecked(bool)
QVariant data() const const
void setEnabled(bool)
void setIcon(const QIcon &icon)
void setActionGroup(QActionGroup *group)
void setData(const QVariant &data)
void triggered(bool checked)
void setExclusive(bool b)
QString text(Mode mode) const const
virtual int exec()
ShortcutOverride
void accept()
Type type() const const
QClipboard * clipboard()
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
bool hasNext() const const
const Key & key() const const
const T & value() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addMenu(QMenu *menu)
QAction * addSeparator()
QAction * exec()
void triggered(QAction *action)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setParent(QObject *parent)
const QColor & color(ColorGroup group, ColorRole role) const const
void setColor(ColorGroup group, ColorRole role, const QColor &color)
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
WA_DeleteOnClose
int anchor() const const
void beginEditBlock()
void clearSelection()
void endEditBlock()
void insertText(const QString &text)
bool movePosition(MoveOperation operation, MoveMode mode, int n)
void removeSelectedText()
QString selectedText() const const
void setPosition(int pos, MoveMode m)
virtual void clear()
QMenu * createStandardContextMenu()
void ensureCursorVisible()
virtual void focusInEvent(QFocusEvent *e) override
virtual void keyPressEvent(QKeyEvent *e) override
bool isReadOnly() const const
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
QString toPlainText() const const
QString toString() const const
QList< QAction * > actions() const const
bool hasFocus() const const
void insertAction(QAction *before, QAction *action)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void show()
bool testAttribute(Qt::WidgetAttribute attribute) const const
void update()
void setUpdatesEnabled(bool enable)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:20 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.