KPimTextEdit

plaintexteditor.cpp
1 /*
2  SPDX-FileCopyrightText: 2013-2020 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 #include "plaintexteditor.h"
7 #include "kpimtextedit_debug.h"
8 
9 #include "texteditor/commonwidget/textmessageindicator.h"
10 #include <KIO/KUriFilterSearchProviderActions>
11 #include <KLocalizedString>
12 #include <KStandardGuiItem>
13 #include <KStandardAction>
14 #include <KCursor>
15 #include <QIcon>
16 #include <KConfigGroup>
17 #include <KSharedConfig>
18 #include <KConfig>
19 
20 #include <sonnet/backgroundchecker.h>
21 #include <Sonnet/Dialog>
22 #include "texttospeech/texttospeech.h"
23 
24 #include <QMenu>
25 #include <QDBusConnection>
26 #include <QDBusConnectionInterface>
27 #include <QTextDocumentFragment>
28 #include <QScrollBar>
29 #include <QApplication>
30 #include <QClipboard>
31 #include <QShortcut>
32 
33 #include <sonnet/spellcheckdecorator.h>
34 
35 #include <SonnetCore/sonnet/speller.h>
36 
37 #include <Sonnet/Highlighter>
38 
39 using namespace KPIMTextEdit;
40 
41 class Q_DECL_HIDDEN PlainTextEditor::PlainTextEditorPrivate
42 {
43 public:
44  PlainTextEditorPrivate(PlainTextEditor *qq)
45  : q(qq)
46  , mTextIndicator(new KPIMTextEdit::TextMessageIndicator(q))
47  , webshortcutMenuManager(new KIO::KUriFilterSearchProviderActions(q))
48  {
49  KConfig sonnetKConfig(QStringLiteral("sonnetrc"));
50  KConfigGroup group(&sonnetKConfig, "Spelling");
51  checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
52  supportFeatures |= PlainTextEditor::Search;
53  supportFeatures |= PlainTextEditor::SpellChecking;
54  supportFeatures |= PlainTextEditor::TextToSpeech;
55  supportFeatures |= PlainTextEditor::AllowWebShortcut;
56  }
57 
58  ~PlainTextEditorPrivate()
59  {
60  delete richTextDecorator;
61  delete speller;
62  }
63 
64  QStringList ignoreSpellCheckingWords;
65  PlainTextEditor *const q;
66  KPIMTextEdit::TextMessageIndicator *const mTextIndicator;
67  KIO::KUriFilterSearchProviderActions *const webshortcutMenuManager;
68  Sonnet::SpellCheckDecorator *richTextDecorator = nullptr;
69  Sonnet::Speller *speller = nullptr;
70 
71  QString spellCheckingConfigFileName;
72  QString spellCheckingLanguage;
73  QTextDocumentFragment originalDoc;
74  PlainTextEditor::SupportFeatures supportFeatures;
75  int mInitialFontSize = 0;
76  bool customPalette = false;
77  bool activateLanguageMenu = true;
78  bool checkSpellingEnabled = false;
79 };
80 
81 PlainTextEditor::PlainTextEditor(QWidget *parent)
82  : QPlainTextEdit(parent)
83  , d(new PlainTextEditor::PlainTextEditorPrivate(this))
84 {
85  KCursor::setAutoHideCursor(this, true, false);
86  setSpellCheckingConfigFileName(QString());
87  d->mInitialFontSize = font().pointSize();
88 }
89 
90 PlainTextEditor::~PlainTextEditor()
91 {
92  delete d;
93 }
94 
95 void PlainTextEditor::addIgnoreWords(const QStringList &lst)
96 {
97  d->ignoreSpellCheckingWords = lst;
98  addIgnoreWordsToHighLighter();
99 }
100 
101 void PlainTextEditor::slotDisplayMessageIndicator(const QString &message)
102 {
103  d->mTextIndicator->display(message);
104 }
105 
106 void PlainTextEditor::contextMenuEvent(QContextMenuEvent *event)
107 {
108  QMenu *popup = createStandardContextMenu();
109  if (popup) {
110  const bool emptyDocument = document()->isEmpty();
111  if (!isReadOnly()) {
112  QList<QAction *> actionList = popup->actions();
113  enum {
114  UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs
115  };
116  QAction *separatorAction = nullptr;
117  const int idx = actionList.indexOf(actionList[SelectAllAct]) + 1;
118  if (idx < actionList.count()) {
119  separatorAction = actionList.at(idx);
120  }
121  if (separatorAction) {
122  if (!emptyDocument) {
123  QAction *clearAllAction = KStandardAction::clear(this, &PlainTextEditor::slotUndoableClear, popup);
124  popup->insertAction(separatorAction, clearAllAction);
125  }
126  }
127  }
128  if (d->supportFeatures & Search) {
129  popup->addSeparator();
130  if (!emptyDocument) {
131  popup->addAction(KStandardGuiItem::find().icon(), KStandardGuiItem::find().text(), this, &PlainTextEditor::findText, Qt::Key_F + Qt::CTRL);
132  popup->addSeparator();
133  }
134  if (!isReadOnly()) {
135  if (!emptyDocument) {
136  popup->addAction(i18n("Replace..."), this, &PlainTextEditor::replaceText, Qt::Key_R + Qt::CTRL);
137  popup->addSeparator();
138  }
139  }
140  } else {
141  popup->addSeparator();
142  }
143 
144  if (!isReadOnly() && spellCheckingSupport()) {
145  if (!d->speller) {
146  d->speller = new Sonnet::Speller();
147  }
148  if (!d->speller->availableBackends().isEmpty()) {
149  if (!emptyDocument) {
150  popup->addAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("Check Spelling..."), this, &PlainTextEditor::slotCheckSpelling);
151  popup->addSeparator();
152  }
153  QAction *autoSpellCheckAction = popup->addAction(i18n("Auto Spell Check"), this, &PlainTextEditor::slotToggleAutoSpellCheck);
154  autoSpellCheckAction->setCheckable(true);
155  autoSpellCheckAction->setChecked(checkSpellingEnabled());
156  popup->addAction(autoSpellCheckAction);
157 
158  if (checkSpellingEnabled() && d->activateLanguageMenu) {
159  QMenu *languagesMenu = new QMenu(i18n("Spell Checking Language"), popup);
160  auto *languagesGroup = new QActionGroup(languagesMenu);
161  languagesGroup->setExclusive(true);
162 
163  QString defaultSpellcheckingLanguage = spellCheckingLanguage();
164  if (defaultSpellcheckingLanguage.isEmpty()) {
165  //TODO fix default value
166  defaultSpellcheckingLanguage = d->speller->defaultLanguage();
167  }
168 
169  QMapIterator<QString, QString> i(d->speller->availableDictionaries());
170  while (i.hasNext()) {
171  i.next();
172  QAction *languageAction = languagesMenu->addAction(i.key());
173  languageAction->setCheckable(true);
174  languageAction->setChecked(defaultSpellcheckingLanguage == i.value());
175  languageAction->setData(i.value());
176  languageAction->setActionGroup(languagesGroup);
177  connect(languageAction, &QAction::triggered, this, &PlainTextEditor::slotLanguageSelected);
178  }
179  popup->addMenu(languagesMenu);
180  }
181  popup->addSeparator();
182  }
183  }
184  if (d->supportFeatures & TextToSpeech) {
185  if (KPIMTextEdit::TextToSpeech::self()->isReady()) {
186  if (!emptyDocument) {
187  QAction *speakAction = popup->addAction(i18n("Speak Text"));
188  speakAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech")));
189  connect(speakAction, &QAction::triggered, this, &PlainTextEditor::slotSpeakText);
190  }
191  }
192  }
193  if (webShortcutSupport() && textCursor().hasSelection()) {
194  popup->addSeparator();
195  const QString selectedText = textCursor().selectedText();
196  d->webshortcutMenuManager->setSelectedText(selectedText);
197  d->webshortcutMenuManager->addWebShortcutsToMenu(popup);
198  }
199 
200  addExtraMenuEntry(popup, event->pos());
201  popup->exec(event->globalPos());
202 
203  delete popup;
204  }
205 }
206 
207 void PlainTextEditor::addExtraMenuEntry(QMenu *menu, QPoint pos)
208 {
209  Q_UNUSED(menu);
210  Q_UNUSED(pos);
211 }
212 
213 void PlainTextEditor::slotSpeakText()
214 {
215  QString text;
216  if (textCursor().hasSelection()) {
217  text = textCursor().selectedText();
218  } else {
219  text = toPlainText();
220  }
221  //qCDebug(KPIMTEXTEDIT_LOG) << " KPIMTextEdit::TextToSpeech::self()->isReady() :" << KPIMTextEdit::TextToSpeech::self()->isReady();
222  Q_EMIT say(text);
223 }
224 
225 void PlainTextEditor::slotUndoableClear()
226 {
227  QTextCursor cursor = textCursor();
228  cursor.beginEditBlock();
231  cursor.removeSelectedText();
232  cursor.endEditBlock();
233 }
234 
235 void PlainTextEditor::setSearchSupport(bool b)
236 {
237  if (b) {
238  d->supportFeatures |= Search;
239  } else {
240  d->supportFeatures = (d->supportFeatures & ~Search);
241  }
242 }
243 
244 bool PlainTextEditor::searchSupport() const
245 {
246  return d->supportFeatures & Search;
247 }
248 
249 void PlainTextEditor::setTextToSpeechSupport(bool b)
250 {
251  if (b) {
252  d->supportFeatures |= TextToSpeech;
253  } else {
254  d->supportFeatures = (d->supportFeatures & ~TextToSpeech);
255  }
256 }
257 
258 bool PlainTextEditor::textToSpeechSupport() const
259 {
260  return d->supportFeatures & TextToSpeech;
261 }
262 
263 bool PlainTextEditor::spellCheckingSupport() const
264 {
265  return d->supportFeatures & SpellChecking;
266 }
267 
268 void PlainTextEditor::setSpellCheckingSupport(bool check)
269 {
270  if (check) {
271  d->supportFeatures |= SpellChecking;
272  } else {
273  d->supportFeatures = (d->supportFeatures & ~SpellChecking);
274  }
275 }
276 
277 void PlainTextEditor::setWebShortcutSupport(bool b)
278 {
279  if (b) {
280  d->supportFeatures |= AllowWebShortcut;
281  } else {
282  d->supportFeatures = (d->supportFeatures & ~AllowWebShortcut);
283  }
284 }
285 
286 bool PlainTextEditor::webShortcutSupport() const
287 {
288  return d->supportFeatures & AllowWebShortcut;
289 }
290 
291 void PlainTextEditor::setReadOnly(bool readOnly)
292 {
293  if (!readOnly && hasFocus() && d->checkSpellingEnabled && !d->richTextDecorator) {
294  createHighlighter();
295  }
296 
297  if (readOnly == isReadOnly()) {
298  return;
299  }
300 
301  if (readOnly) {
302  clearDecorator();
303 
304  d->customPalette = testAttribute(Qt::WA_SetPalette);
305  QPalette p = palette();
307  p.setColor(QPalette::Base, color);
308  p.setColor(QPalette::Window, color);
309  setPalette(p);
310  } else {
311  if (d->customPalette && testAttribute(Qt::WA_SetPalette)) {
312  QPalette p = palette();
314  p.setColor(QPalette::Base, color);
315  p.setColor(QPalette::Window, color);
316  setPalette(p);
317  } else {
318  setPalette(QPalette());
319  }
320  }
321 
322  QPlainTextEdit::setReadOnly(readOnly);
323 }
324 
325 void PlainTextEditor::slotCheckSpelling()
326 {
327  if (document()->isEmpty()) {
328  slotDisplayMessageIndicator(i18n("Nothing to spell check."));
329  return;
330  }
331  auto *backgroundSpellCheck = new Sonnet::BackgroundChecker;
332  if (backgroundSpellCheck->speller().availableBackends().isEmpty()) {
333  slotDisplayMessageIndicator(i18n("No backend available for spell checking."));
334  delete backgroundSpellCheck;
335  return;
336  }
337  if (!d->spellCheckingLanguage.isEmpty()) {
338  backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage);
339  }
340  if (!d->ignoreSpellCheckingWords.isEmpty()) {
341  for (const QString &word : qAsConst(d->ignoreSpellCheckingWords)) {
342  backgroundSpellCheck->speller().addToSession(word);
343  }
344  }
345  auto *spellDialog = new Sonnet::Dialog(backgroundSpellCheck, nullptr);
346  backgroundSpellCheck->setParent(spellDialog);
347  spellDialog->setAttribute(Qt::WA_DeleteOnClose, true);
348  connect(spellDialog, &Sonnet::Dialog::replace, this, &PlainTextEditor::slotSpellCheckerCorrected);
349  connect(spellDialog, &Sonnet::Dialog::misspelling, this, &PlainTextEditor::slotSpellCheckerMisspelling);
350  connect(spellDialog, &Sonnet::Dialog::autoCorrect, this, &PlainTextEditor::slotSpellCheckerAutoCorrect);
351  connect(spellDialog, &Sonnet::Dialog::spellCheckDone, this, &PlainTextEditor::slotSpellCheckerFinished);
352  connect(spellDialog, &Sonnet::Dialog::cancel, this, &PlainTextEditor::slotSpellCheckerCanceled);
353  connect(spellDialog, &Sonnet::Dialog::spellCheckStatus, this, &PlainTextEditor::spellCheckStatus);
354  connect(spellDialog, &Sonnet::Dialog::languageChanged, this, &PlainTextEditor::languageChanged);
355  d->originalDoc = QTextDocumentFragment(document());
356  spellDialog->setBuffer(toPlainText());
357  spellDialog->show();
358 }
359 
360 void PlainTextEditor::slotSpellCheckerCanceled()
361 {
362  QTextDocument *doc = document();
363  doc->clear();
364  QTextCursor cursor(doc);
365  cursor.insertFragment(d->originalDoc);
366  slotSpellCheckerFinished();
367 }
368 
369 void PlainTextEditor::slotSpellCheckerAutoCorrect(const QString &currentWord, const QString &autoCorrectWord)
370 {
371  Q_EMIT spellCheckerAutoCorrect(currentWord, autoCorrectWord);
372 }
373 
374 void PlainTextEditor::slotSpellCheckerMisspelling(const QString &text, int pos)
375 {
376  highlightWord(text.length(), pos);
377 }
378 
379 void PlainTextEditor::slotSpellCheckerCorrected(const QString &oldWord, int pos, const QString &newWord)
380 {
381  if (oldWord != newWord) {
382  QTextCursor cursor(document());
383  cursor.setPosition(pos);
384  cursor.setPosition(pos + oldWord.length(), QTextCursor::KeepAnchor);
385  cursor.insertText(newWord);
386  }
387 }
388 
389 void PlainTextEditor::slotSpellCheckerFinished()
390 {
391  QTextCursor cursor(document());
392  cursor.clearSelection();
393  setTextCursor(cursor);
394 }
395 
396 void PlainTextEditor::highlightWord(int length, int pos)
397 {
398  QTextCursor cursor(document());
399  cursor.setPosition(pos);
400  cursor.setPosition(pos + length, QTextCursor::KeepAnchor);
401  setTextCursor(cursor);
402  ensureCursorVisible();
403 }
404 
405 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
406 {
407  cursor.clearSelection();
409  cursor.removeSelectedText();
410 }
411 
412 void PlainTextEditor::deleteWordBack()
413 {
414  deleteWord(textCursor(), QTextCursor::PreviousWord);
415 }
416 
417 void PlainTextEditor::deleteWordForward()
418 {
419  deleteWord(textCursor(), QTextCursor::WordRight);
420 }
421 
422 bool PlainTextEditor::event(QEvent *ev)
423 {
424  if (ev->type() == QEvent::ShortcutOverride) {
425  auto *e = static_cast<QKeyEvent *>(ev);
426  if (overrideShortcut(e)) {
427  e->accept();
428  return true;
429  }
430  }
431  return QPlainTextEdit::event(ev);
432 }
433 
434 bool PlainTextEditor::overrideShortcut(QKeyEvent *event)
435 {
436  const int key = event->key() | event->modifiers();
437  if (KStandardShortcut::copy().contains(key)) {
438  return true;
439  } else if (KStandardShortcut::paste().contains(key)) {
440  return true;
441  } else if (KStandardShortcut::cut().contains(key)) {
442  return true;
443  } else if (KStandardShortcut::undo().contains(key)) {
444  return true;
445  } else if (KStandardShortcut::redo().contains(key)) {
446  return true;
447  } else if (KStandardShortcut::deleteWordBack().contains(key)) {
448  return true;
449  } else if (KStandardShortcut::deleteWordForward().contains(key)) {
450  return true;
451  } else if (KStandardShortcut::backwardWord().contains(key)) {
452  return true;
453  } else if (KStandardShortcut::forwardWord().contains(key)) {
454  return true;
455  } else if (KStandardShortcut::next().contains(key)) {
456  return true;
457  } else if (KStandardShortcut::prior().contains(key)) {
458  return true;
459  } else if (KStandardShortcut::begin().contains(key)) {
460  return true;
461  } else if (KStandardShortcut::end().contains(key)) {
462  return true;
463  } else if (KStandardShortcut::beginningOfLine().contains(key)) {
464  return true;
465  } else if (KStandardShortcut::endOfLine().contains(key)) {
466  return true;
467  } else if (KStandardShortcut::pasteSelection().contains(key)) {
468  return true;
469  } else if (searchSupport() && KStandardShortcut::find().contains(key)) {
470  return true;
471  } else if (searchSupport() && KStandardShortcut::replace().contains(key)) {
472  return true;
473  } else if (searchSupport() && KStandardShortcut::findNext().contains(key)) {
474  return true;
475  } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
476  return true;
477  } else if (event == QKeySequence::DeleteEndOfLine) {
478  return true;
479  }
480  return false;
481 }
482 
483 bool PlainTextEditor::handleShortcut(QKeyEvent *event)
484 {
485  const int key = event->key() | event->modifiers();
486 
487  if (KStandardShortcut::copy().contains(key)) {
488  copy();
489  return true;
490  } else if (KStandardShortcut::paste().contains(key)) {
491  paste();
492  return true;
493  } else if (KStandardShortcut::cut().contains(key)) {
494  cut();
495  return true;
496  } else if (KStandardShortcut::undo().contains(key)) {
497  if (!isReadOnly()) {
498  undo();
499  }
500  return true;
501  } else if (KStandardShortcut::redo().contains(key)) {
502  if (!isReadOnly()) {
503  redo();
504  }
505  return true;
506  } else if (KStandardShortcut::deleteWordBack().contains(key)) {
507  if (!isReadOnly()) {
508  deleteWordBack();
509  }
510  return true;
511  } else if (KStandardShortcut::deleteWordForward().contains(key)) {
512  if (!isReadOnly()) {
514  }
515  return true;
516  } else if (KStandardShortcut::backwardWord().contains(key)) {
517  QTextCursor cursor = textCursor();
519  setTextCursor(cursor);
520  return true;
521  } else if (KStandardShortcut::forwardWord().contains(key)) {
522  QTextCursor cursor = textCursor();
524  setTextCursor(cursor);
525  return true;
526  } else if (KStandardShortcut::next().contains(key)) {
527  QTextCursor cursor = textCursor();
528  bool moved = false;
529  qreal lastY = cursorRect(cursor).bottom();
530  qreal distance = 0;
531  do {
532  qreal y = cursorRect(cursor).bottom();
533  distance += qAbs(y - lastY);
534  lastY = y;
535  moved = cursor.movePosition(QTextCursor::Down);
536  } while (moved && distance < viewport()->height());
537 
538  if (moved) {
540  verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
541  }
542  setTextCursor(cursor);
543  return true;
544  } else if (KStandardShortcut::prior().contains(key)) {
545  QTextCursor cursor = textCursor();
546  bool moved = false;
547  qreal lastY = cursorRect(cursor).bottom();
548  qreal distance = 0;
549  do {
550  qreal y = cursorRect(cursor).bottom();
551  distance += qAbs(y - lastY);
552  lastY = y;
553  moved = cursor.movePosition(QTextCursor::Up);
554  } while (moved && distance < viewport()->height());
555 
556  if (moved) {
558  verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
559  }
560  setTextCursor(cursor);
561  return true;
562  } else if (KStandardShortcut::begin().contains(key)) {
563  QTextCursor cursor = textCursor();
565  setTextCursor(cursor);
566  return true;
567  } else if (KStandardShortcut::end().contains(key)) {
568  QTextCursor cursor = textCursor();
570  setTextCursor(cursor);
571  return true;
572  } else if (KStandardShortcut::beginningOfLine().contains(key)) {
573  QTextCursor cursor = textCursor();
575  setTextCursor(cursor);
576  return true;
577  } else if (KStandardShortcut::endOfLine().contains(key)) {
578  QTextCursor cursor = textCursor();
580  setTextCursor(cursor);
581  return true;
582  } else if (searchSupport() && KStandardShortcut::find().contains(key)) {
583  Q_EMIT findText();
584  return true;
585  } else if (searchSupport() && KStandardShortcut::replace().contains(key)) {
586  if (!isReadOnly()) {
587  Q_EMIT replaceText();
588  }
589  return true;
590  } else if (KStandardShortcut::pasteSelection().contains(key)) {
592  if (!text.isEmpty()) {
593  insertPlainText(text); // TODO: check if this is html? (MiB)
594  }
595  return true;
596  } else if (event == QKeySequence::DeleteEndOfLine) {
597  deleteEndOfLine();
598  return true;
599  }
600  return false;
601 }
602 
603 void PlainTextEditor::deleteEndOfLine()
604 {
605  QTextCursor cursor = textCursor();
606  QTextBlock block = cursor.block();
607  if (cursor.position() == block.position() + block.length() - 2) {
609  } else {
611  }
612  cursor.removeSelectedText();
613  setTextCursor(cursor);
614 }
615 
616 void PlainTextEditor::moveCursorBeginUpDown(bool moveUp)
617 {
618  QTextCursor cursor = textCursor();
620  move.beginEditBlock();
621  cursor.clearSelection();
624  move.endEditBlock();
625  setTextCursor(move);
626 }
627 
628 void PlainTextEditor::moveLineUpDown(bool moveUp)
629 {
630  QTextCursor cursor = textCursor();
631  QTextCursor move = cursor;
632  move.beginEditBlock();
633 
634  const bool hasSelection = cursor.hasSelection();
635 
636  if (hasSelection) {
637  move.setPosition(cursor.selectionStart());
642  } else {
645  }
646  const QString text = move.selectedText();
647 
649  move.removeSelectedText();
650 
651  if (moveUp) {
653  move.insertBlock();
655  } else {
657  if (move.atBlockStart()) { // empty block
658  move.movePosition(QTextCursor::NextBlock);
659  move.insertBlock();
661  } else {
662  move.insertBlock();
663  }
664  }
665 
666  int start = move.position();
667  move.clearSelection();
668  move.insertText(text);
669  int end = move.position();
670 
671  if (hasSelection) {
672  move.setPosition(end);
674  } else {
675  move.setPosition(start);
676  }
677  move.endEditBlock();
678 
679  setTextCursor(move);
680 }
681 
682 void PlainTextEditor::wheelEvent(QWheelEvent *event)
683 {
685  if (event->angleDelta().y() > 0) {
686  zoomIn();
687  } else if (event->angleDelta().y() < 0) {
688  zoomOut();
689  }
690  event->accept();
691  return;
692  }
694 }
695 
696 void PlainTextEditor::keyPressEvent(QKeyEvent *event)
697 {
698  const bool isControlClicked = event->modifiers() & Qt::ControlModifier;
699  const bool isShiftClicked = event->modifiers() & Qt::ShiftModifier;
700  if (handleShortcut(event)) {
701  event->accept();
702  } else if (event->key() == Qt::Key_Up && isControlClicked && isShiftClicked) {
703  moveLineUpDown(true);
704  event->accept();
705  } else if (event->key() == Qt::Key_Down && isControlClicked && isShiftClicked) {
706  moveLineUpDown(false);
707  event->accept();
708  } else if (event->key() == Qt::Key_Up && isControlClicked) {
709  moveCursorBeginUpDown(true);
710  event->accept();
711  } else if (event->key() == Qt::Key_Down && isControlClicked) {
712  moveCursorBeginUpDown(false);
713  event->accept();
714  } else {
716  }
717 }
718 
719 bool PlainTextEditor::activateLanguageMenu() const
720 {
721  return d->activateLanguageMenu;
722 }
723 
724 void PlainTextEditor::setActivateLanguageMenu(bool activate)
725 {
726  d->activateLanguageMenu = activate;
727 }
728 
729 Sonnet::Highlighter *PlainTextEditor::highlighter() const
730 {
731  if (d->richTextDecorator) {
732  return d->richTextDecorator->highlighter();
733  } else {
734  return nullptr;
735  }
736 }
737 
738 Sonnet::SpellCheckDecorator *PlainTextEditor::createSpellCheckDecorator()
739 {
740  return new Sonnet::SpellCheckDecorator(this);
741 }
742 
743 void PlainTextEditor::addIgnoreWordsToHighLighter()
744 {
745  if (d->ignoreSpellCheckingWords.isEmpty()) {
746  return;
747  }
748  if (d->richTextDecorator) {
749  Sonnet::Highlighter *_highlighter = d->richTextDecorator->highlighter();
750  for (const QString &word : qAsConst(d->ignoreSpellCheckingWords)) {
751  _highlighter->ignoreWord(word);
752  }
753  }
754 }
755 
756 void PlainTextEditor::setHighlighter(Sonnet::Highlighter *_highLighter)
757 {
758  Sonnet::SpellCheckDecorator *decorator = createSpellCheckDecorator();
759  delete decorator->highlighter();
760  decorator->setHighlighter(_highLighter);
761 
762  //KTextEdit used to take ownership of the highlighter, Sonnet::SpellCheckDecorator does not.
763  //so we reparent the highlighter so it will be deleted when the decorator is destroyed
764  _highLighter->setParent(decorator);
765  d->richTextDecorator = decorator;
766  addIgnoreWordsToHighLighter();
767 }
768 
769 void PlainTextEditor::focusInEvent(QFocusEvent *event)
770 {
771  if (checkSpellingEnabled() && !isReadOnly() && !d->richTextDecorator && spellCheckingSupport()) {
772  createHighlighter();
773  }
774 
776 }
777 
778 bool PlainTextEditor::checkSpellingEnabled() const
779 {
780  return d->checkSpellingEnabled;
781 }
782 
783 void PlainTextEditor::setCheckSpellingEnabled(bool check)
784 {
785  if (check == d->checkSpellingEnabled) {
786  return;
787  }
788  d->checkSpellingEnabled = check;
789  Q_EMIT checkSpellingChanged(check);
790  // From the above statement we know that if we're turning checking
791  // on that we need to create a new highlighter and if we're turning it
792  // off we should remove the old one.
793  if (check) {
794  if (hasFocus()) {
795  if (!d->richTextDecorator) {
796  createHighlighter();
797  }
798  if (!d->spellCheckingLanguage.isEmpty()) {
799  setSpellCheckingLanguage(spellCheckingLanguage());
800  }
801  }
802  } else {
803  clearDecorator();
804  }
805  updateHighLighter();
806 }
807 
808 void PlainTextEditor::updateHighLighter()
809 {
810 }
811 
812 void PlainTextEditor::clearDecorator()
813 {
814  delete d->richTextDecorator;
815  d->richTextDecorator = nullptr;
816 }
817 
818 void PlainTextEditor::createHighlighter()
819 {
820  Sonnet::Highlighter *highlighter = new Sonnet::Highlighter(this);
821  highlighter->setCurrentLanguage(spellCheckingLanguage());
822  setHighlighter(highlighter);
823 }
824 
825 void PlainTextEditor::setSpellCheckingConfigFileName(const QString &_fileName)
826 {
827  d->spellCheckingConfigFileName = _fileName;
828  KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
829  if (config->hasGroup("Spelling")) {
830  KConfigGroup group(config, "Spelling");
831  d->checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
832  d->spellCheckingLanguage = group.readEntry("Language", QString());
833  }
834  setCheckSpellingEnabled(checkSpellingEnabled());
835 
836  if (!d->spellCheckingLanguage.isEmpty() && highlighter()) {
837  highlighter()->setCurrentLanguage(d->spellCheckingLanguage);
838  highlighter()->rehighlight();
839  }
840 }
841 
842 QString PlainTextEditor::spellCheckingConfigFileName() const
843 {
844  return d->spellCheckingConfigFileName;
845 }
846 
847 void PlainTextEditor::slotLanguageSelected()
848 {
849  auto *languageAction = static_cast<QAction *>(QObject::sender());
850  setSpellCheckingLanguage(languageAction->data().toString());
851 }
852 
853 const QString &PlainTextEditor::spellCheckingLanguage() const
854 {
855  return d->spellCheckingLanguage;
856 }
857 
858 void PlainTextEditor::setSpellCheckingLanguage(const QString &_language)
859 {
860  if (highlighter()) {
861  highlighter()->setCurrentLanguage(_language);
862  highlighter()->rehighlight();
863  }
864 
865  if (_language != d->spellCheckingLanguage) {
866  d->spellCheckingLanguage = _language;
867  KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
868  KConfigGroup group(config, "Spelling");
869  group.writeEntry("Language", d->spellCheckingLanguage);
870  setCheckSpellingEnabled(checkSpellingEnabled());
871 
872  Q_EMIT languageChanged(_language);
873  }
874 }
875 
876 void PlainTextEditor::slotToggleAutoSpellCheck()
877 {
878  setCheckSpellingEnabled(!checkSpellingEnabled());
879  KSharedConfig::Ptr config = KSharedConfig::openConfig(d->spellCheckingConfigFileName);
880  KConfigGroup group(config, "Spelling");
881  group.writeEntry("checkerEnabledByDefault", d->checkSpellingEnabled);
882 }
883 
884 void PlainTextEditor::slotZoomReset()
885 {
886  QFont f = font();
887  if (d->mInitialFontSize != f.pointSize()) {
888  f.setPointSize(d->mInitialFontSize);
889  setFont(f);
890  }
891 }
int position() const const
ControlModifier
void triggered(bool checked)
void setPointSize(int pointSize)
const QList< QKeySequence > & redo()
ShortcutOverride
void removeSelectedText()
const QList< QKeySequence > & deleteWordBack()
QEvent::Type type() const const
const QPalette & palette() const const
int position() const const
QCursor cursor() const const
void clearSelection()
void setColor(QPalette::ColorGroup group, QPalette::ColorRole role, const QColor &color)
const QList< QKeySequence > & next()
virtual void focusInEvent(QFocusEvent *e) override
void languageChanged(const QString &language)
QObject * sender() const const
void setChecked(bool)
int selectionStart() const const
QVariant data() const const
void setIcon(const QIcon &icon)
const QColor & color(QPalette::ColorGroup group, QPalette::ColorRole role) const const
const T & at(int i) const const
QAction * clear(const QObject *recvr, const char *slot, QObject *parent)
QString selectedText() const const
int y() const const
void ignoreWord(const QString &word)
StandardShortcut find(const QKeySequence &keySeq)
void setCurrentLanguage(const QString &language)
bool hasFocus() const const
void insertFragment(const QTextDocumentFragment &fragment)
const QList< QKeySequence > & cut()
const QPoint & globalPos() const const
Qt::KeyboardModifiers keyboardModifiers()
The TextToSpeech class.
Definition: texttospeech.h:20
void spellCheckDone(const QString &newBuffer)
QAction * addAction(const QString &text)
const QList< QKeySequence > & begin()
int y() const const
bool movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n)
KSharedConfigPtr config()
int indexOf(const T &value, int from) const const
const QList< QKeySequence > & prior()
const QList< QKeySequence > & deleteWordForward()
KGuiItem find()
const QList< QKeySequence > & beginningOfLine()
KIOCORE_EXPORT CopyJob * copy(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
bool hasSelection() const const
void insertBlock()
void insertAction(QAction *before, QAction *action)
bool testAttribute(Qt::WidgetAttribute attribute) const const
const QList< QKeySequence > & undo()
int count(const T &value) const const
const QList< QKeySequence > & forwardWord()
const QList< QKeySequence > & findNext()
const QList< QKeySequence > & copy()
QMapIterator::Item next()
const QList< QKeySequence > & zoomOut()
void endEditBlock()
void insertText(const QString &text)
void spellCheckStatus(const QString &)
bool isEmpty() const const
void setHighlighter(Highlighter *highlighter)
QPoint pos() const const
KIOWIDGETS_EXPORT PasteJob * paste(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags=DefaultFlags)
virtual void clear()
QAction * addSeparator()
Highlighter * highlighter() const
const QList< QKeySequence > & pasteSelection()
virtual void wheelEvent(QWheelEvent *e) override
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
QAction * exec()
virtual void keyPressEvent(QKeyEvent *e) override
bool matches(QKeySequence::StandardKey key) const const
virtual bool event(QEvent *event) override
const QList< QKeySequence > & replace()
void setData(const QVariant &userData)
static void setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter=false)
int key() const const
QTextBlock block() const const
bool atBlockStart() const const
void accept()
void setParent(QObject *parent)
const QFont & font() const const
void beginEditBlock()
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void setCheckable(bool)
const QList< QKeySequence > & endOfLine()
void move(int x, int y)
WA_SetPalette
QString i18n(const char *text, const TYPE &arg...)
QString text(QClipboard::Mode mode) const const
const QList< QKeySequence > & end()
void setReadOnly(bool ro)
QPoint angleDelta() const const
const QPoint & pos() const const
A widget that displays messages in the top-left corner.
const QList< QKeySequence > & backwardWord()
void setActionGroup(QActionGroup *group)
QAction * addMenu(QMenu *menu)
int length() const const
The PlainTextEditor class.
QIcon fromTheme(const QString &name)
const QList< QKeySequence > & paste()
int selectionEnd() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QList< QAction * > actions() const const
QString toString() const const
QClipboard * clipboard()
int pointSize() const const
int height() const const
Q_EMITQ_EMIT
const QList< QKeySequence > & zoomIn()
int length() const const
void setPosition(int pos, QTextCursor::MoveMode m)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Nov 30 2020 23:09:16 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.