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 <KStandardAction>
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 = KStandardAction::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")), i18n("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 = KStandardAction::find(this, &KTextEdit::slotFind, popup);
541 QAction *findNextAction = KStandardAction::findNext(this, &KTextEdit::slotFindNext, popup);
542 QAction *findPrevAction = KStandardAction::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 = KStandardAction::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 Q_D(KTextEdit);
577
578#ifdef HAVE_SPEECH
579 QString text;
580 if (textCursor().hasSelection()) {
581 text = textCursor().selectedText();
582 } else {
583 text = toPlainText();
584 }
585 if (!d->textToSpeech) {
586 d->textToSpeech = new QTextToSpeech(this);
587 }
588 d->textToSpeech->say(text);
589#endif
590}
591
593{
594 QMenu *popup = mousePopupMenu();
595 if (popup) {
597 popup->exec(event->globalPos());
598 delete popup;
599 }
600}
601
606
608{
609 Q_D(const KTextEdit);
610
611 if (d->decorator) {
612 return d->decorator->highlighter();
613 } else {
614 return nullptr;
615 }
616}
617
619{
620 Q_D(KTextEdit);
621
622 // Set pointer to null before deleting KTextDecorator as dtor will emit signal,
623 // which could call this code again and cause double delete/crash
624 auto decorator = d->decorator;
625 d->decorator = nullptr;
626 delete decorator;
627}
628
630{
631 Q_D(KTextEdit);
632
633 d->decorator = decorator;
634}
635
637{
638 KTextDecorator *decorator = new KTextDecorator(this);
639 // The old default highlighter must be manually deleted.
640 delete decorator->highlighter();
641 decorator->setHighlighter(_highLighter);
642
643 // KTextEdit used to take ownership of the highlighter, Sonnet::SpellCheckDecorator does not.
644 // so we reparent the highlighter so it will be deleted when the decorator is destroyed
645 _highLighter->setParent(decorator);
646 addTextDecorator(decorator);
647}
648
650{
651 Q_D(KTextEdit);
652
654 if (check == d->spellCheckingEnabled) {
655 return;
656 }
657
658 // From the above statement we now know that if we're turning checking
659 // on that we need to create a new highlighter and if we're turning it
660 // off we should remove the old one.
661
662 d->spellCheckingEnabled = check;
663 if (check) {
664 if (hasFocus()) {
666 if (!spellCheckingLanguage().isEmpty()) {
667 setSpellCheckingLanguage(spellCheckingLanguage());
668 }
669 }
670 } else {
672 }
673}
674
676{
677 Q_D(KTextEdit);
678
679 if (d->spellCheckingEnabled && !isReadOnly() && !d->decorator) {
681 }
682
684}
685
686bool KTextEdit::checkSpellingEnabled() const
687{
688 Q_D(const KTextEdit);
689
690 return d->spellCheckingEnabled;
691}
692
694{
695 return true;
696}
697
698void KTextEdit::setReadOnly(bool readOnly)
699{
700 Q_D(KTextEdit);
701
702 if (!readOnly && hasFocus() && d->spellCheckingEnabled && !d->decorator) {
704 }
705
706 if (readOnly == isReadOnly()) {
707 return;
708 }
709
710 if (readOnly) {
711 // Set pointer to null before deleting KTextDecorator as dtor will emit signal,
712 // which could call this code again and cause double delete/crash
713 auto decorator = d->decorator;
714 d->decorator = nullptr;
715 delete decorator;
716
717 d->customPalette = testAttribute(Qt::WA_SetPalette);
718 QPalette p = palette();
720 p.setColor(QPalette::Base, color);
721 p.setColor(QPalette::Window, color);
722 setPalette(p);
723 } else {
724 if (d->customPalette && testAttribute(Qt::WA_SetPalette)) {
725 QPalette p = palette();
727 p.setColor(QPalette::Base, color);
728 p.setColor(QPalette::Window, color);
729 setPalette(p);
730 } else {
732 }
733 }
734
736}
737
739{
740 Q_D(KTextEdit);
741
742 d->checkSpelling(false);
743}
744
746{
747 Q_D(KTextEdit);
748
749 d->checkSpelling(true);
750}
751
752void KTextEdit::highlightWord(int length, int pos)
753{
755 cursor.setPosition(pos);
756 cursor.setPosition(pos + length, QTextCursor::KeepAnchor);
759}
760
762{
763 Q_D(KTextEdit);
764
765 if (document()->isEmpty()) { // saves having to track the text changes
766 return;
767 }
768
769 if (d->repDlg) {
770 d->repDlg->activateWindow();
771 } else {
772 d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false);
774 }
775 d->repDlg->show();
776}
777
779{
780 Q_D(KTextEdit);
781
782 if (!d->repDlg) {
783 // Should really assert()
784 return;
785 }
786
787 if (d->repDlg->pattern().isEmpty()) {
788 delete d->replace;
789 d->replace = nullptr;
791 return;
792 }
793
794 delete d->replace;
795 d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this);
796 d->repIndex = 0;
797 if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) {
798 d->repIndex = textCursor().anchor();
799 }
800
801 // Connect textFound signal to code which handles highlighting of found text.
802 connect(d->replace, &KFind::textFound, this, [d](const QString &text, int matchingIndex, int matchedLength) {
803 d->slotFindHighlight(text, matchingIndex, matchedLength);
804 });
805 connect(d->replace, &KFind::findNext, this, &KTextEdit::slotReplaceNext);
806
807 connect(d->replace, &KReplace::textReplaced, this, [d](const QString &text, int replacementIndex, int replacedLength, int matchedLength) {
808 d->slotReplaceText(text, replacementIndex, replacedLength, matchedLength);
809 });
810
811 d->repDlg->close();
812 slotReplaceNext();
813}
814
815void KTextEdit::slotReplaceNext()
816{
817 Q_D(KTextEdit);
818
819 if (!d->replace) {
820 return;
821 }
822
823 d->lastReplacedPosition = -1;
824 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
825 textCursor().beginEditBlock(); // #48541
826 viewport()->setUpdatesEnabled(false);
827 }
828
829 if (d->replace->needData()) {
830 d->replace->setData(toPlainText(), d->repIndex);
831 }
832 KFind::Result res = d->replace->replace();
833 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
834 textCursor().endEditBlock(); // #48541
835 if (d->lastReplacedPosition >= 0) {
836 QTextCursor tc = textCursor();
837 tc.setPosition(d->lastReplacedPosition);
838 setTextCursor(tc);
840 }
841
843 viewport()->update();
844 }
845
846 if (res == KFind::NoMatch) {
847 d->replace->displayFinalDialog();
848 d->replace->disconnect(this);
849 d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away
850 d->replace = nullptr;
852 // or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
853 } else {
854 // m_replace->closeReplaceNextDialog();
855 }
856}
857
858void KTextEdit::slotDoFind()
859{
860 Q_D(KTextEdit);
861
862 if (!d->findDlg) {
863 // Should really assert()
864 return;
865 }
866 if (d->findDlg->pattern().isEmpty()) {
867 delete d->find;
868 d->find = nullptr;
869 return;
870 }
871 delete d->find;
872 d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this);
873 d->findIndex = 0;
874 if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) {
875 d->findIndex = textCursor().anchor();
876 }
877
878 // Connect textFound() signal to code which handles highlighting of found text
879 connect(d->find, &KFind::textFound, this, [d](const QString &text, int matchingIndex, int matchedLength) {
880 d->slotFindHighlight(text, matchingIndex, matchedLength);
881 });
882 connect(d->find, &KFind::findNext, this, &KTextEdit::slotFindNext);
883
884 d->findDlg->close();
885 d->find->closeFindNextDialog();
886 slotFindNext();
887}
888
889void KTextEdit::slotFindNext()
890{
891 Q_D(KTextEdit);
892
893 if (!d->find) {
894 return;
895 }
896 if (document()->isEmpty()) {
897 d->find->disconnect(this);
898 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
899 d->find = nullptr;
900 return;
901 }
902
903 if (d->find->needData()) {
904 d->find->setData(toPlainText(), d->findIndex);
905 }
906 KFind::Result res = d->find->find();
907
908 if (res == KFind::NoMatch) {
909 d->find->displayFinalDialog();
910 d->find->disconnect(this);
911 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
912 d->find = nullptr;
913 // or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); }
914 } else {
915 // m_find->closeFindNextDialog();
916 }
917}
918
920{
921 Q_D(KTextEdit);
922
923 if (!d->find) {
924 return;
925 }
926 const long oldOptions = d->find->options();
927 d->find->setOptions(oldOptions ^ KFind::FindBackwards);
928 slotFindNext();
929 if (d->find) {
930 d->find->setOptions(oldOptions);
931 }
932}
933
934void KTextEdit::slotFind()
935{
936 Q_D(KTextEdit);
937
938 if (document()->isEmpty()) { // saves having to track the text changes
939 return;
940 }
941
942 if (d->findDlg) {
943 d->findDlg->activateWindow();
944 } else {
945 d->findDlg = new KFindDialog(this);
946 connect(d->findDlg, &KFindDialog::okClicked, this, &KTextEdit::slotDoFind);
947 }
948 d->findDlg->show();
949}
950
951void KTextEdit::slotReplace()
952{
953 Q_D(KTextEdit);
954
955 if (document()->isEmpty()) { // saves having to track the text changes
956 return;
957 }
958
959 if (d->repDlg) {
960 d->repDlg->activateWindow();
961 } else {
962 d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false);
964 }
965 d->repDlg->show();
966}
967
969{
970 Q_D(KTextEdit);
971
972 d->findReplaceEnabled = enabled;
973}
974
976{
977 Q_D(KTextEdit);
978
979 d->showTabAction = show;
980}
981
982bool KTextEditPrivate::overrideShortcut(const QKeyEvent *event)
983{
984 const int key = event->key() | event->modifiers();
985
986 if (KStandardShortcut::copy().contains(key)) {
987 return true;
988 } else if (KStandardShortcut::paste().contains(key)) {
989 return true;
990 } else if (KStandardShortcut::cut().contains(key)) {
991 return true;
992 } else if (KStandardShortcut::undo().contains(key)) {
993 return true;
994 } else if (KStandardShortcut::redo().contains(key)) {
995 return true;
996 } else if (KStandardShortcut::deleteWordBack().contains(key)) {
997 return true;
998 } else if (KStandardShortcut::deleteWordForward().contains(key)) {
999 return true;
1000 } else if (KStandardShortcut::backwardWord().contains(key)) {
1001 return true;
1002 } else if (KStandardShortcut::forwardWord().contains(key)) {
1003 return true;
1004 } else if (KStandardShortcut::next().contains(key)) {
1005 return true;
1006 } else if (KStandardShortcut::prior().contains(key)) {
1007 return true;
1008 } else if (KStandardShortcut::begin().contains(key)) {
1009 return true;
1010 } else if (KStandardShortcut::end().contains(key)) {
1011 return true;
1012 } else if (KStandardShortcut::beginningOfLine().contains(key)) {
1013 return true;
1014 } else if (KStandardShortcut::endOfLine().contains(key)) {
1015 return true;
1016 } else if (KStandardShortcut::pasteSelection().contains(key)) {
1017 return true;
1018 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
1019 return true;
1020 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
1021 return true;
1022 } else if (findReplaceEnabled && KStandardShortcut::findPrev().contains(key)) {
1023 return true;
1024 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
1025 return true;
1026 } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
1027 return true;
1028 }
1029 return false;
1030}
1031
1033{
1034 Q_D(KTextEdit);
1035
1036 if (d->handleShortcut(event)) {
1037 event->accept();
1038 } else {
1040 }
1041}
1042
1044{
1045 Q_D(KTextEdit);
1046
1047 d->showAutoCorrectionButton = show;
1048}
1049
1050#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 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)
QAction * replace(const QObject *recvr, const char *slot, QObject *parent)
QAction * findPrev(const QObject *recvr, const char *slot, QObject *parent)
QAction * findNext(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 > & 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 Tue Mar 26 2024 11:17:07 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.