• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.11.5 API Reference
  • KDE Home
  • Contact Us
 

KDEUI

  • kdeui
  • widgets
ktextedit.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright (C) 2002 Carsten Pfeiffer <pfeiffer@kde.org>
3  2005 Michael Brade <brade@kde.org>
4  2012 Laurent Montel <montel@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 #include "ktextedit.h"
23 #include <ktoolinvocation.h>
24 #include <kdebug.h>
25 
26 #include <QApplication>
27 #include <QClipboard>
28 #include <QKeyEvent>
29 #include <QMenu>
30 #include <QPainter>
31 #include <QScrollBar>
32 #include <QTextCursor>
33 #include <QTextDocumentFragment>
34 #include <QDBusInterface>
35 #include <QDBusConnection>
36 #include <QDBusConnectionInterface>
37 
38 #include <configdialog.h>
39 #include <dialog.h>
40 #include "backgroundchecker.h"
41 #include <kaction.h>
42 #include <kcursor.h>
43 #include <kglobalsettings.h>
44 #include <kstandardaction.h>
45 #include <kstandardshortcut.h>
46 #include <kicon.h>
47 #include <kiconloader.h>
48 #include <klocale.h>
49 #include <kdialog.h>
50 #include <kreplacedialog.h>
51 #include <kfinddialog.h>
52 #include <kfind.h>
53 #include <kreplace.h>
54 #include <kmessagebox.h>
55 #include <kmenu.h>
56 #include <kwindowsystem.h>
57 #include <QDebug>
58 
59 class KTextEdit::Private
60 {
61  public:
62  Private( KTextEdit *_parent )
63  : parent( _parent ),
64  customPalette( false ),
65  checkSpellingEnabled( false ),
66  findReplaceEnabled(true),
67  showTabAction(true),
68  showAutoCorrectionButton(false),
69  highlighter( 0 ), findDlg(0),find(0),repDlg(0),replace(0), findIndex(0), repIndex(0),
70  lastReplacedPosition(-1)
71  {
72  //Check the default sonnet settings to see if spellchecking should be enabled.
73  sonnetKConfig = new KConfig("sonnetrc");
74  KConfigGroup group(sonnetKConfig, "Spelling");
75  checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
76 
77  // i18n: Placeholder text in text edit widgets is the text appearing
78  // before any user input, briefly explaining to the user what to type
79  // (e.g. "Enter message").
80  // By default the text is set in italic, which may not be appropriate
81  // for some languages and scripts (e.g. for CJK ideographs).
82  QString metaMsg = i18nc("Italic placeholder text in line edits: 0 no, 1 yes", "1");
83  italicizePlaceholder = (metaMsg.trimmed() != QString('0'));
84  }
85 
86  ~Private()
87  {
88  delete highlighter;
89  delete findDlg;
90  delete find;
91  delete replace;
92  delete repDlg;
93  delete sonnetKConfig;
94  }
95 
101  bool overrideShortcut(const QKeyEvent* e);
105  bool handleShortcut(const QKeyEvent* e);
106 
107  void spellCheckerMisspelling( const QString &text, int pos );
108  void spellCheckerCorrected( const QString &, int,const QString &);
109  void spellCheckerAutoCorrect(const QString&,const QString&);
110  void spellCheckerCanceled();
111  void spellCheckerFinished();
112  void toggleAutoSpellCheck();
113 
114  void slotFindHighlight(const QString& text, int matchingIndex, int matchingLength);
115  void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength);
116 
121  void undoableClear();
122 
123  void slotAllowTab();
124  void menuActivated( QAction* action );
125 
126  QRect clickMessageRect() const;
127 
128  void init();
129 
130  void checkSpelling(bool force);
131  KTextEdit *parent;
132  KTextEditSpellInterface *spellInterface;
133  QAction *autoSpellCheckAction;
134  QAction *allowTab;
135  QAction *spellCheckAction;
136  QString clickMessage;
137  bool italicizePlaceholder : 1;
138  bool customPalette : 1;
139 
140  bool checkSpellingEnabled : 1;
141  bool findReplaceEnabled: 1;
142  bool showTabAction: 1;
143  bool showAutoCorrectionButton: 1;
144  QTextDocumentFragment originalDoc;
145  QString spellCheckingConfigFileName;
146  QString spellCheckingLanguage;
147  Sonnet::Highlighter *highlighter;
148  KFindDialog *findDlg;
149  KFind *find;
150  KReplaceDialog *repDlg;
151  KReplace *replace;
152  int findIndex, repIndex;
153  int lastReplacedPosition;
154  KConfig *sonnetKConfig;
155 };
156 
157 void KTextEdit::Private::checkSpelling(bool force)
158 {
159  if(parent->document()->isEmpty())
160  {
161  KMessageBox::information(parent, i18n("Nothing to spell check."));
162  if(force) {
163  emit parent->spellCheckingFinished();
164  }
165  return;
166  }
167  Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker;
168  if(!spellCheckingLanguage.isEmpty())
169  backgroundSpellCheck->changeLanguage(spellCheckingLanguage);
170  Sonnet::Dialog *spellDialog = new Sonnet::Dialog(
171  backgroundSpellCheck, force ? parent : 0);
172  backgroundSpellCheck->setParent(spellDialog);
173  spellDialog->setAttribute(Qt::WA_DeleteOnClose, true);
174  spellDialog->activeAutoCorrect(showAutoCorrectionButton);
175  connect(spellDialog, SIGNAL(replace(QString,int,QString)),
176  parent, SLOT(spellCheckerCorrected(QString,int,QString)));
177  connect(spellDialog, SIGNAL(misspelling(QString,int)),
178  parent, SLOT(spellCheckerMisspelling(QString,int)));
179  connect(spellDialog, SIGNAL(autoCorrect(QString,QString)),
180  parent, SLOT(spellCheckerAutoCorrect(QString,QString)));
181  connect(spellDialog, SIGNAL(done(QString)),
182  parent, SLOT(spellCheckerFinished()));
183  connect(spellDialog, SIGNAL(cancel()),
184  parent, SLOT(spellCheckerCanceled()));
185  //Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary
186  /*
187  connect(spellDialog, SIGNAL(stop()),
188  parent, SLOT(spellCheckerFinished()));
189  */
190  connect(spellDialog, SIGNAL(spellCheckStatus(QString)),
191  parent, SIGNAL(spellCheckStatus(QString)));
192  connect(spellDialog, SIGNAL(languageChanged(QString)),
193  parent, SIGNAL(languageChanged(QString)));
194  if(force) {
195  connect(spellDialog, SIGNAL(done(QString)),parent, SIGNAL(spellCheckingFinished()));
196  connect(spellDialog, SIGNAL(cancel()), parent, SIGNAL(spellCheckingCanceled()));
197  //Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary
198  //connect(spellDialog, SIGNAL(stop()), parent, SIGNAL(spellCheckingFinished()));
199  }
200  originalDoc = QTextDocumentFragment(parent->document());
201  spellDialog->setBuffer(parent->toPlainText());
202  spellDialog->show();
203 }
204 
205 
206 void KTextEdit::Private::spellCheckerCanceled()
207 {
208  QTextDocument *doc = parent->document();
209  doc->clear();
210  QTextCursor cursor(doc);
211  cursor.insertFragment(originalDoc);
212  spellCheckerFinished();
213 }
214 
215 void KTextEdit::Private::spellCheckerAutoCorrect(const QString& currentWord,const QString& autoCorrectWord)
216 {
217  emit parent->spellCheckerAutoCorrect(currentWord, autoCorrectWord);
218 }
219 
220 void KTextEdit::Private::spellCheckerMisspelling( const QString &text, int pos )
221 {
222  //kDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<<text<<" pos :"<<pos;
223  parent->highlightWord( text.length(), pos );
224 }
225 
226 void KTextEdit::Private::spellCheckerCorrected( const QString& oldWord, int pos,const QString& newWord)
227 {
228  //kDebug()<<" oldWord :"<<oldWord<<" newWord :"<<newWord<<" pos : "<<pos;
229  if (oldWord != newWord ) {
230  QTextCursor cursor(parent->document());
231  cursor.setPosition(pos);
232  cursor.setPosition(pos+oldWord.length(),QTextCursor::KeepAnchor);
233  cursor.insertText(newWord);
234  }
235 }
236 
237 void KTextEdit::Private::spellCheckerFinished()
238 {
239  QTextCursor cursor(parent->document());
240  cursor.clearSelection();
241  parent->setTextCursor(cursor);
242  if (parent->highlighter())
243  parent->highlighter()->rehighlight();
244 }
245 
246 void KTextEdit::Private::toggleAutoSpellCheck()
247 {
248  parent->setCheckSpellingEnabled( !parent->checkSpellingEnabled() );
249 }
250 
251 void KTextEdit::Private::undoableClear()
252 {
253  QTextCursor cursor = parent->textCursor();
254  cursor.beginEditBlock();
255  cursor.movePosition(QTextCursor::Start);
256  cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
257  cursor.removeSelectedText();
258  cursor.endEditBlock();
259 }
260 
261 void KTextEdit::Private::slotAllowTab()
262 {
263  parent->setTabChangesFocus( !parent->tabChangesFocus() );
264 }
265 
266 void KTextEdit::Private::menuActivated( QAction* action )
267 {
268  if ( action == spellCheckAction )
269  parent->checkSpelling();
270  else if ( action == autoSpellCheckAction )
271  toggleAutoSpellCheck();
272  else if ( action == allowTab )
273  slotAllowTab();
274 }
275 
276 
277 void KTextEdit::Private::slotFindHighlight(const QString& text, int matchingIndex, int matchingLength)
278 {
279  Q_UNUSED(text)
280  //kDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength;
281  QTextCursor tc = parent->textCursor();
282  tc.setPosition(matchingIndex);
283  tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength);
284  parent->setTextCursor(tc);
285  parent->ensureCursorVisible();
286 }
287 
288 
289 void KTextEdit::Private::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength) {
290  //kDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength;
291  QTextCursor tc = parent->textCursor();
292  tc.setPosition(replacementIndex);
293  tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength);
294  tc.removeSelectedText();
295  tc.insertText(text.mid(replacementIndex, replacedLength));
296  if (replace->options() & KReplaceDialog::PromptOnReplace) {
297  parent->setTextCursor(tc);
298  parent->ensureCursorVisible();
299  }
300  lastReplacedPosition = replacementIndex;
301 }
302 
303 QRect KTextEdit::Private::clickMessageRect() const
304 {
305  int margin = int(parent->document()->documentMargin());
306  QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin);
307  return parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage);
308 }
309 
310 void KTextEdit::Private::init()
311 {
312  spellInterface = 0;
313  KCursor::setAutoHideCursor(parent, true, false);
314  parent->connect(parent, SIGNAL(languageChanged(QString)),
315  parent, SLOT(setSpellCheckingLanguage(QString)));
316 }
317 
318 KTextEdit::KTextEdit( const QString& text, QWidget *parent )
319  : QTextEdit( text, parent ), d( new Private( this ) )
320 {
321  d->init();
322 }
323 
324 KTextEdit::KTextEdit( QWidget *parent )
325  : QTextEdit( parent ), d( new Private( this ) )
326 {
327  d->init();
328 }
329 
330 KTextEdit::~KTextEdit()
331 {
332  delete d;
333 }
334 
335 void KTextEdit::setSpellCheckingConfigFileName(const QString &_fileName)
336 {
337  d->spellCheckingConfigFileName = _fileName;
338 }
339 
340 const QString& KTextEdit::spellCheckingLanguage() const
341 {
342  return d->spellCheckingLanguage;
343 }
344 
345 void KTextEdit::setSpellCheckingLanguage(const QString &_language)
346 {
347  if (highlighter()) {
348  highlighter()->setCurrentLanguage(_language);
349  highlighter()->rehighlight();
350  }
351 
352  if (_language != d->spellCheckingLanguage) {
353  d->spellCheckingLanguage = _language;
354  emit languageChanged(_language);
355  }
356 }
357 
358 void KTextEdit::showSpellConfigDialog(const QString &configFileName,
359  const QString &windowIcon)
360 {
361  KConfig config(configFileName);
362  Sonnet::ConfigDialog dialog(&config, this);
363  if (!d->spellCheckingLanguage.isEmpty())
364  dialog.setLanguage(d->spellCheckingLanguage);
365  if (!windowIcon.isEmpty())
366  dialog.setWindowIcon(KIcon(windowIcon));
367  if(dialog.exec()) {
368  setSpellCheckingLanguage( dialog.language() );
369  }
370 }
371 
372 bool KTextEdit::event(QEvent* ev)
373 {
374  if (ev->type() == QEvent::ShortcutOverride) {
375  QKeyEvent *e = static_cast<QKeyEvent *>( ev );
376  if (d->overrideShortcut(e)) {
377  e->accept();
378  return true;
379  }
380  }
381  return QTextEdit::event(ev);
382 }
383 
384 bool KTextEdit::Private::handleShortcut(const QKeyEvent* event)
385 {
386  const int key = event->key() | event->modifiers();
387 
388  if ( KStandardShortcut::copy().contains( key ) ) {
389  parent->copy();
390  return true;
391  } else if ( KStandardShortcut::paste().contains( key ) ) {
392  parent->paste();
393  return true;
394  } else if ( KStandardShortcut::cut().contains( key ) ) {
395  parent->cut();
396  return true;
397  } else if ( KStandardShortcut::undo().contains( key ) ) {
398  if(!parent->isReadOnly())
399  parent->undo();
400  return true;
401  } else if ( KStandardShortcut::redo().contains( key ) ) {
402  if(!parent->isReadOnly())
403  parent->redo();
404  return true;
405  } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) {
406  parent->deleteWordBack();
407  return true;
408  } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) {
409  parent->deleteWordForward();
410  return true;
411  } else if ( KStandardShortcut::backwardWord().contains( key ) ) {
412  QTextCursor cursor = parent->textCursor();
413  cursor.movePosition( QTextCursor::PreviousWord );
414  parent->setTextCursor( cursor );
415  return true;
416  } else if ( KStandardShortcut::forwardWord().contains( key ) ) {
417  QTextCursor cursor = parent->textCursor();
418  cursor.movePosition( QTextCursor::NextWord );
419  parent->setTextCursor( cursor );
420  return true;
421  } else if ( KStandardShortcut::next().contains( key ) ) {
422  QTextCursor cursor = parent->textCursor();
423  bool moved = false;
424  qreal lastY = parent->cursorRect(cursor).bottom();
425  qreal distance = 0;
426  do {
427  qreal y = parent->cursorRect(cursor).bottom();
428  distance += qAbs(y - lastY);
429  lastY = y;
430  moved = cursor.movePosition(QTextCursor::Down);
431  } while (moved && distance < parent->viewport()->height());
432 
433  if (moved) {
434  cursor.movePosition(QTextCursor::Up);
435  parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
436  }
437  parent->setTextCursor(cursor);
438  return true;
439  } else if ( KStandardShortcut::prior().contains( key ) ) {
440  QTextCursor cursor = parent->textCursor();
441  bool moved = false;
442  qreal lastY = parent->cursorRect(cursor).bottom();
443  qreal distance = 0;
444  do {
445  qreal y = parent->cursorRect(cursor).bottom();
446  distance += qAbs(y - lastY);
447  lastY = y;
448  moved = cursor.movePosition(QTextCursor::Up);
449  } while (moved && distance < parent->viewport()->height());
450 
451  if (moved) {
452  cursor.movePosition(QTextCursor::Down);
453  parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
454  }
455  parent->setTextCursor(cursor);
456  return true;
457  } else if ( KStandardShortcut::begin().contains( key ) ) {
458  QTextCursor cursor = parent->textCursor();
459  cursor.movePosition( QTextCursor::Start );
460  parent->setTextCursor( cursor );
461  return true;
462  } else if ( KStandardShortcut::end().contains( key ) ) {
463  QTextCursor cursor = parent->textCursor();
464  cursor.movePosition( QTextCursor::End );
465  parent->setTextCursor( cursor );
466  return true;
467  } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) {
468  QTextCursor cursor = parent->textCursor();
469  cursor.movePosition( QTextCursor::StartOfLine );
470  parent->setTextCursor( cursor );
471  return true;
472  } else if ( KStandardShortcut::endOfLine().contains( key ) ) {
473  QTextCursor cursor = parent->textCursor();
474  cursor.movePosition( QTextCursor::EndOfLine );
475  parent->setTextCursor( cursor );
476  return true;
477  } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
478  parent->slotFind();
479  return true;
480  } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
481  parent->slotFindNext();
482  return true;
483  } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
484  if (!parent->isReadOnly())
485  parent->slotReplace();
486  return true;
487  } else if ( KStandardShortcut::pasteSelection().contains( key ) ) {
488  QString text = QApplication::clipboard()->text( QClipboard::Selection );
489  if ( !text.isEmpty() )
490  parent->insertPlainText( text ); // TODO: check if this is html? (MiB)
491  return true;
492  }
493  return false;
494 }
495 
496 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
497 {
498  cursor.clearSelection();
499  cursor.movePosition( op, QTextCursor::KeepAnchor );
500  cursor.removeSelectedText();
501 }
502 
503 void KTextEdit::deleteWordBack()
504 {
505  deleteWord(textCursor(), QTextCursor::PreviousWord);
506 }
507 
508 void KTextEdit::deleteWordForward()
509 {
510  deleteWord(textCursor(), QTextCursor::WordRight);
511 }
512 
513 QMenu *KTextEdit::mousePopupMenu()
514 {
515  QMenu *popup = createStandardContextMenu();
516  if (!popup) return 0;
517  connect( popup, SIGNAL(triggered(QAction*)),
518  this, SLOT(menuActivated(QAction*)) );
519 
520  const bool emptyDocument = document()->isEmpty();
521  if( !isReadOnly() )
522  {
523  QList<QAction *> actionList = popup->actions();
524  enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
525  QAction *separatorAction = 0L;
526  int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1;
527  if ( idx < actionList.count() )
528  separatorAction = actionList.at( idx );
529  if ( separatorAction )
530  {
531  KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup);
532  if ( emptyDocument )
533  clearAllAction->setEnabled( false );
534  popup->insertAction( separatorAction, clearAllAction );
535  }
536  }
537  KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText
538  : KIconTheme::TextEditor,
539  popup->actions() );
540 
541  if( !isReadOnly() )
542  {
543  popup->addSeparator();
544  d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ),
545  i18n( "Check Spelling..." ) );
546  if ( emptyDocument )
547  d->spellCheckAction->setEnabled( false );
548  d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) );
549  d->autoSpellCheckAction->setCheckable( true );
550  d->autoSpellCheckAction->setChecked( checkSpellingEnabled() );
551  popup->addSeparator();
552  if (d->showTabAction) {
553  d->allowTab = popup->addAction( i18n("Allow Tabulations") );
554  d->allowTab->setCheckable( true );
555  d->allowTab->setChecked( !tabChangesFocus() );
556  }
557  }
558 
559  if (d->findReplaceEnabled) {
560  KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup);
561  KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup);
562  if (emptyDocument) {
563  findAction->setEnabled(false);
564  findNextAction->setEnabled(false);
565  } else {
566  findNextAction->setEnabled(d->find != 0);
567  }
568  popup->addSeparator();
569  popup->addAction(findAction);
570  popup->addAction(findNextAction);
571 
572  if (!isReadOnly()) {
573  KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup);
574  if (emptyDocument) {
575  replaceAction->setEnabled(false);
576  }
577  popup->addAction(replaceAction);
578  }
579  }
580  popup->addSeparator();
581  QAction *speakAction = popup->addAction(i18n("Speak Text"));
582  speakAction->setIcon(KIcon("preferences-desktop-text-to-speech"));
583  speakAction->setEnabled(!emptyDocument );
584  connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) );
585  return popup;
586 }
587 
588 void KTextEdit::slotSpeakText()
589 {
590  // If KTTSD not running, start it.
591  if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd"))
592  {
593  QString error;
594  if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error))
595  {
596  KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error );
597  return;
598  }
599  }
600  QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech");
601  QString text;
602  if(textCursor().hasSelection())
603  text = textCursor().selectedText();
604  else
605  text = toPlainText();
606  ktts.asyncCall("say", text, 0);
607 }
608 
609 void KTextEdit::contextMenuEvent(QContextMenuEvent *event)
610 {
611  // Obtain the cursor at the mouse position and the current cursor
612  QTextCursor cursorAtMouse = cursorForPosition(event->pos());
613  const int mousePos = cursorAtMouse.position();
614  QTextCursor cursor = textCursor();
615 
616  // Check if the user clicked a selected word
617  const bool selectedWordClicked = cursor.hasSelection() &&
618  mousePos >= cursor.selectionStart() &&
619  mousePos <= cursor.selectionEnd();
620 
621  // Get the word under the (mouse-)cursor and see if it is misspelled.
622  // Don't include apostrophes at the start/end of the word in the selection.
623  QTextCursor wordSelectCursor(cursorAtMouse);
624  wordSelectCursor.clearSelection();
625  wordSelectCursor.select(QTextCursor::WordUnderCursor);
626  QString selectedWord = wordSelectCursor.selectedText();
627 
628  bool isMouseCursorInsideWord = true;
629  if ((mousePos < wordSelectCursor.selectionStart() ||
630  mousePos >= wordSelectCursor.selectionEnd())
631  && (selectedWord.length() > 1)) {
632  isMouseCursorInsideWord = false;
633  }
634 
635  // Clear the selection again, we re-select it below (without the apostrophes).
636  wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size());
637  if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) {
638  selectedWord = selectedWord.right(selectedWord.size() - 1);
639  wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
640  }
641  if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"'))
642  selectedWord.chop(1);
643 
644  wordSelectCursor.movePosition(QTextCursor::NextCharacter,
645  QTextCursor::KeepAnchor, selectedWord.size());
646 
647  const bool wordIsMisspelled = isMouseCursorInsideWord &&
648  checkSpellingEnabled() &&
649  !selectedWord.isEmpty() &&
650  highlighter() &&
651  highlighter()->isWordMisspelled(selectedWord);
652 
653  // If the user clicked a selected word, do nothing.
654  // If the user clicked somewhere else, move the cursor there.
655  // If the user clicked on a misspelled word, select that word.
656  // Same behavior as in OpenOffice Writer.
657  bool inQuote = false;
658  if (d->spellInterface &&
659  !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text()))
660  inQuote = true;
661  if (!selectedWordClicked) {
662  if (wordIsMisspelled && !inQuote)
663  setTextCursor(wordSelectCursor);
664  else
665  setTextCursor(cursorAtMouse);
666  cursor = textCursor();
667  }
668 
669  // Use standard context menu for already selected words, correctly spelled
670  // words and words inside quotes.
671  if (!wordIsMisspelled || selectedWordClicked || inQuote) {
672  QMetaObject::invokeMethod(this, "mousePopupMenuImplementation", Q_ARG(QPoint, event->globalPos()));
673  }
674  else {
675  QMenu menu; //don't use KMenu here we don't want auto management accelerator
676 
677  //Add the suggestions to the menu
678  const QStringList reps = highlighter()->suggestionsForWord(selectedWord);
679  if (reps.isEmpty()) {
680  QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord));
681  suggestionsAction->setEnabled(false);
682  }
683  else {
684  QStringList::const_iterator end(reps.constEnd());
685  for (QStringList::const_iterator it = reps.constBegin(); it != end; ++it) {
686  menu.addAction(*it);
687  }
688  }
689 
690  menu.addSeparator();
691 
692  QAction *ignoreAction = menu.addAction(i18n("Ignore"));
693  QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary"));
694  //Execute the popup inline
695  const QAction *selectedAction = menu.exec(event->globalPos());
696 
697  if (selectedAction) {
698  Q_ASSERT(cursor.selectedText() == selectedWord);
699 
700  if (selectedAction == ignoreAction) {
701  highlighter()->ignoreWord(selectedWord);
702  highlighter()->rehighlight();
703  }
704  else if (selectedAction == addToDictAction) {
705  highlighter()->addWordToDictionary(selectedWord);
706  highlighter()->rehighlight();
707  }
708 
709  // Other actions can only be one of the suggested words
710  else {
711  const QString replacement = selectedAction->text();
712  Q_ASSERT(reps.contains(replacement));
713  cursor.insertText(replacement);
714  setTextCursor(cursor);
715  }
716  }
717  }
718 }
719 
720 void KTextEdit::wheelEvent( QWheelEvent *event )
721 {
722  if ( KGlobalSettings::wheelMouseZooms() )
723  QTextEdit::wheelEvent( event );
724  else // thanks, we don't want to zoom, so skip QTextEdit's impl.
725  QAbstractScrollArea::wheelEvent( event );
726 }
727 
728 void KTextEdit::createHighlighter()
729 {
730  setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName));
731 }
732 
733 Sonnet::Highlighter* KTextEdit::highlighter() const
734 {
735  return d->highlighter;
736 }
737 
738 void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter)
739 {
740  delete d->highlighter;
741  d->highlighter = _highLighter;
742 }
743 
744 void KTextEdit::setCheckSpellingEnabled(bool check)
745 {
746  if (d->spellInterface)
747  d->spellInterface->setSpellCheckingEnabled(check);
748  else
749  setCheckSpellingEnabledInternal(check);
750 }
751 
752 void KTextEdit::setCheckSpellingEnabledInternal( bool check )
753 {
754  emit checkSpellingChanged( check );
755  if ( check == d->checkSpellingEnabled )
756  return;
757 
758  // From the above statment we know know that if we're turning checking
759  // on that we need to create a new highlighter and if we're turning it
760  // off we should remove the old one.
761 
762  d->checkSpellingEnabled = check;
763  if ( check )
764  {
765  if ( hasFocus() ) {
766  createHighlighter();
767  if (!spellCheckingLanguage().isEmpty())
768  setSpellCheckingLanguage(spellCheckingLanguage());
769  }
770  }
771  else
772  {
773  delete d->highlighter;
774  d->highlighter = 0;
775  }
776 }
777 
778 void KTextEdit::focusInEvent( QFocusEvent *event )
779 {
780  if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter )
781  createHighlighter();
782 
783  QTextEdit::focusInEvent( event );
784 }
785 
786 bool KTextEdit::checkSpellingEnabled() const
787 {
788  if (d->spellInterface)
789  return d->spellInterface->isSpellCheckingEnabled();
790  else
791  return checkSpellingEnabledInternal();
792 }
793 
794 bool KTextEdit::checkSpellingEnabledInternal() const
795 {
796  return d->checkSpellingEnabled;
797 }
798 
799 void KTextEdit::setReadOnly( bool readOnly )
800 {
801  if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter )
802  createHighlighter();
803 
804  if ( readOnly == isReadOnly() )
805  return;
806 
807  if ( readOnly ) {
808  delete d->highlighter;
809  d->highlighter = 0;
810 
811  d->customPalette = testAttribute( Qt::WA_SetPalette );
812  QPalette p = palette();
813  QColor color = p.color( QPalette::Disabled, QPalette::Background );
814  p.setColor( QPalette::Base, color );
815  p.setColor( QPalette::Background, color );
816  setPalette( p );
817  } else {
818  if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) {
819  QPalette p = palette();
820  QColor color = p.color( QPalette::Normal, QPalette::Base );
821  p.setColor( QPalette::Base, color );
822  p.setColor( QPalette::Background, color );
823  setPalette( p );
824  } else
825  setPalette( QPalette() );
826  }
827 
828  QTextEdit::setReadOnly( readOnly );
829 }
830 
831 void KTextEdit::checkSpelling()
832 {
833  d->checkSpelling(false);
834 }
835 
836 void KTextEdit::forceSpellChecking()
837 {
838  d->checkSpelling(true);
839 }
840 
841 void KTextEdit::highlightWord( int length, int pos )
842 {
843  QTextCursor cursor(document());
844  cursor.setPosition(pos);
845  cursor.setPosition(pos+length,QTextCursor::KeepAnchor);
846  setTextCursor (cursor);
847  ensureCursorVisible();
848 }
849 
850 void KTextEdit::replace()
851 {
852  if ( document()->isEmpty() ) // saves having to track the text changes
853  return;
854 
855  if ( d->repDlg ) {
856  KWindowSystem::activateWindow( d->repDlg->winId() );
857  } else {
858  d->repDlg = new KReplaceDialog(this, 0,
859  QStringList(), QStringList(), false);
860  connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) );
861  }
862  d->repDlg->show();
863 }
864 
865 void KTextEdit::slotDoReplace()
866 {
867  if (!d->repDlg) {
868  // Should really assert()
869  return;
870  }
871 
872  if(d->repDlg->pattern().isEmpty()) {
873  delete d->replace;
874  d->replace = 0;
875  ensureCursorVisible();
876  return;
877  }
878 
879  delete d->replace;
880  d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this);
881  d->repIndex = 0;
882  if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) {
883  d->repIndex = textCursor().anchor();
884  }
885 
886  // Connect highlight signal to code which handles highlighting
887  // of found text.
888  connect(d->replace, SIGNAL(highlight(QString,int,int)),
889  this, SLOT(slotFindHighlight(QString,int,int)));
890  connect(d->replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext()));
891  connect(d->replace, SIGNAL(replace(QString,int,int,int)),
892  this, SLOT(slotReplaceText(QString,int,int,int)));
893 
894  d->repDlg->close();
895  slotReplaceNext();
896 }
897 
898 
899 void KTextEdit::slotReplaceNext()
900 {
901  if (!d->replace)
902  return;
903 
904  d->lastReplacedPosition = -1;
905  if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
906  textCursor().beginEditBlock(); // #48541
907  viewport()->setUpdatesEnabled(false);
908  }
909 
910  KFind::Result res = KFind::NoMatch;
911 
912  if (d->replace->needData())
913  d->replace->setData(toPlainText(), d->repIndex);
914  res = d->replace->replace();
915  if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
916  textCursor().endEditBlock(); // #48541
917  if (d->lastReplacedPosition >= 0) {
918  QTextCursor tc = textCursor();
919  tc.setPosition(d->lastReplacedPosition);
920  setTextCursor(tc);
921  ensureCursorVisible();
922  }
923 
924  viewport()->setUpdatesEnabled(true);
925  viewport()->update();
926  }
927 
928  if (res == KFind::NoMatch) {
929  d->replace->displayFinalDialog();
930  d->replace->disconnect(this);
931  d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away
932  d->replace = 0;
933  ensureCursorVisible();
934  //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
935  } else {
936  //m_replace->closeReplaceNextDialog();
937  }
938 }
939 
940 
941 void KTextEdit::slotDoFind()
942 {
943  if (!d->findDlg) {
944  // Should really assert()
945  return;
946  }
947  if( d->findDlg->pattern().isEmpty())
948  {
949  delete d->find;
950  d->find = 0;
951  return;
952  }
953  delete d->find;
954  d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this);
955  d->findIndex = 0;
956  if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) {
957  d->findIndex = textCursor().anchor();
958  }
959 
960  // Connect highlight signal to code which handles highlighting
961  // of found text.
962  connect(d->find, SIGNAL(highlight(QString,int,int)),
963  this, SLOT(slotFindHighlight(QString,int,int)));
964  connect(d->find, SIGNAL(findNext()), this, SLOT(slotFindNext()));
965 
966  d->findDlg->close();
967  d->find->closeFindNextDialog();
968  slotFindNext();
969 }
970 
971 
972 void KTextEdit::slotFindNext()
973 {
974  if (!d->find)
975  return;
976  if(document()->isEmpty())
977  {
978  d->find->disconnect(this);
979  d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
980  d->find = 0;
981  return;
982  }
983 
984  KFind::Result res = KFind::NoMatch;
985  if (d->find->needData())
986  d->find->setData(toPlainText(), d->findIndex);
987  res = d->find->find();
988 
989  if (res == KFind::NoMatch) {
990  d->find->displayFinalDialog();
991  d->find->disconnect(this);
992  d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
993  d->find = 0;
994  //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); }
995  } else {
996  //m_find->closeFindNextDialog();
997  }
998 }
999 
1000 
1001 void KTextEdit::slotFind()
1002 {
1003  if ( document()->isEmpty() ) // saves having to track the text changes
1004  return;
1005 
1006  if ( d->findDlg ) {
1007  KWindowSystem::activateWindow( d->findDlg->winId() );
1008  } else {
1009  d->findDlg = new KFindDialog(this);
1010  connect( d->findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) );
1011  }
1012  d->findDlg->show();
1013 }
1014 
1015 
1016 void KTextEdit::slotReplace()
1017 {
1018  if ( document()->isEmpty() ) // saves having to track the text changes
1019  return;
1020 
1021  if ( d->repDlg ) {
1022  KWindowSystem::activateWindow( d->repDlg->winId() );
1023  } else {
1024  d->repDlg = new KReplaceDialog(this, 0,
1025  QStringList(), QStringList(), false);
1026  connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) );
1027  }
1028  d->repDlg->show();
1029 }
1030 
1031 void KTextEdit::enableFindReplace( bool enabled )
1032 {
1033  d->findReplaceEnabled = enabled;
1034 }
1035 
1036 void KTextEdit::showTabAction( bool show )
1037 {
1038  d->showTabAction = show;
1039 }
1040 
1041 void KTextEdit::setSpellInterface(KTextEditSpellInterface *spellInterface)
1042 {
1043  d->spellInterface = spellInterface;
1044 }
1045 
1046 bool KTextEdit::Private::overrideShortcut(const QKeyEvent* event)
1047 {
1048  const int key = event->key() | event->modifiers();
1049 
1050  if ( KStandardShortcut::copy().contains( key ) ) {
1051  return true;
1052  } else if ( KStandardShortcut::paste().contains( key ) ) {
1053  return true;
1054  } else if ( KStandardShortcut::cut().contains( key ) ) {
1055  return true;
1056  } else if ( KStandardShortcut::undo().contains( key ) ) {
1057  return true;
1058  } else if ( KStandardShortcut::redo().contains( key ) ) {
1059  return true;
1060  } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) {
1061  return true;
1062  } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) {
1063  return true;
1064  } else if ( KStandardShortcut::backwardWord().contains( key ) ) {
1065  return true;
1066  } else if ( KStandardShortcut::forwardWord().contains( key ) ) {
1067  return true;
1068  } else if ( KStandardShortcut::next().contains( key ) ) {
1069  return true;
1070  } else if ( KStandardShortcut::prior().contains( key ) ) {
1071  return true;
1072  } else if ( KStandardShortcut::begin().contains( key ) ) {
1073  return true;
1074  } else if ( KStandardShortcut::end().contains( key ) ) {
1075  return true;
1076  } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) {
1077  return true;
1078  } else if ( KStandardShortcut::endOfLine().contains( key ) ) {
1079  return true;
1080  } else if ( KStandardShortcut::pasteSelection().contains( key ) ) {
1081  return true;
1082  } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
1083  return true;
1084  } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
1085  return true;
1086  } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
1087  return true;
1088  } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
1089  return true;
1090  } else if (event->modifiers() == Qt::ControlModifier &&
1091  (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) &&
1092  qobject_cast<KDialog*>(parent->window()) ) {
1093  // ignore Ctrl-Return so that KDialogs can close the dialog
1094  return true;
1095  }
1096  return false;
1097 }
1098 
1099 void KTextEdit::keyPressEvent( QKeyEvent *event )
1100 {
1101  if (d->handleShortcut(event)) {
1102  event->accept();
1103  }else if (event->modifiers() == Qt::ControlModifier &&
1104  (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) &&
1105  qobject_cast<KDialog*>(window()) ) {
1106  event->ignore();
1107  } else {
1108  QTextEdit::keyPressEvent(event);
1109  }
1110 }
1111 
1112 void KTextEdit::setClickMessage(const QString &msg)
1113 {
1114  if (msg != d->clickMessage) {
1115  if (!d->clickMessage.isEmpty()) {
1116  viewport()->update(d->clickMessageRect());
1117  }
1118  d->clickMessage = msg;
1119  if (!d->clickMessage.isEmpty()) {
1120  viewport()->update(d->clickMessageRect());
1121  }
1122  }
1123 }
1124 
1125 QString KTextEdit::clickMessage() const
1126 {
1127  return d->clickMessage;
1128 }
1129 
1130 void KTextEdit::paintEvent(QPaintEvent *ev)
1131 {
1132  QTextEdit::paintEvent(ev);
1133 
1134  if (!d->clickMessage.isEmpty() && document()->isEmpty()) {
1135  QPainter p(viewport());
1136 
1137  QFont f = font();
1138  f.setItalic(d->italicizePlaceholder);
1139  p.setFont(f);
1140 
1141  QColor color(palette().color(viewport()->foregroundRole()));
1142  color.setAlphaF(0.5);
1143  p.setPen(color);
1144 
1145  QRect cr = d->clickMessageRect();
1146  p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage);
1147  }
1148 }
1149 
1150 void KTextEdit::focusOutEvent(QFocusEvent *ev)
1151 {
1152  QTextEdit::focusOutEvent(ev);
1153 }
1154 
1155 void KTextEdit::showAutoCorrectButton(bool show)
1156 {
1157  d->showAutoCorrectionButton = show;
1158 }
1159 
1160 void KTextEdit::mousePopupMenuImplementation(const QPoint& pos)
1161 {
1162  QMenu *popup = mousePopupMenu();
1163  if ( popup ) {
1164  aboutToShowContextMenu(popup);
1165  popup->exec( pos );
1166  delete popup;
1167  }
1168 }
1169 
1170 #include "ktextedit.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Mon May 5 2014 18:10:36 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.11.5 API Reference

Skip menu "kdelibs-4.11.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal