• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.10.0 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  void updateClickMessageRect();
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 void KTextEdit::Private::updateClickMessageRect()
304 {
305  int margin = int(parent->document()->documentMargin());
306  QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin);
307  rect = parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage);
308  parent->viewport()->update(rect);
309 }
310 
311 void KTextEdit::Private::init()
312 {
313  spellInterface = 0;
314  KCursor::setAutoHideCursor(parent, true, false);
315  parent->connect(parent, SIGNAL(languageChanged(QString)),
316  parent, SLOT(setSpellCheckingLanguage(QString)));
317 }
318 
319 KTextEdit::KTextEdit( const QString& text, QWidget *parent )
320  : QTextEdit( text, parent ), d( new Private( this ) )
321 {
322  d->init();
323 }
324 
325 KTextEdit::KTextEdit( QWidget *parent )
326  : QTextEdit( parent ), d( new Private( this ) )
327 {
328  d->init();
329 }
330 
331 KTextEdit::~KTextEdit()
332 {
333  delete d;
334 }
335 
336 void KTextEdit::setSpellCheckingConfigFileName(const QString &_fileName)
337 {
338  d->spellCheckingConfigFileName = _fileName;
339 }
340 
341 const QString& KTextEdit::spellCheckingLanguage() const
342 {
343  return d->spellCheckingLanguage;
344 }
345 
346 void KTextEdit::setSpellCheckingLanguage(const QString &_language)
347 {
348  if (highlighter()) {
349  highlighter()->setCurrentLanguage(_language);
350  highlighter()->rehighlight();
351  }
352 
353  if (_language != d->spellCheckingLanguage) {
354  d->spellCheckingLanguage = _language;
355  emit languageChanged(_language);
356  }
357 }
358 
359 void KTextEdit::showSpellConfigDialog(const QString &configFileName,
360  const QString &windowIcon)
361 {
362  KConfig config(configFileName);
363  Sonnet::ConfigDialog dialog(&config, this);
364  if (!d->spellCheckingLanguage.isEmpty())
365  dialog.setLanguage(d->spellCheckingLanguage);
366  if (!windowIcon.isEmpty())
367  dialog.setWindowIcon(KIcon(windowIcon));
368  if(dialog.exec()) {
369  setSpellCheckingLanguage( dialog.language() );
370  }
371 }
372 
373 bool KTextEdit::event(QEvent* ev)
374 {
375  if (ev->type() == QEvent::ShortcutOverride) {
376  QKeyEvent *e = static_cast<QKeyEvent *>( ev );
377  if (d->overrideShortcut(e)) {
378  e->accept();
379  return true;
380  }
381  }
382  return QTextEdit::event(ev);
383 }
384 
385 bool KTextEdit::Private::handleShortcut(const QKeyEvent* event)
386 {
387  const int key = event->key() | event->modifiers();
388 
389  if ( KStandardShortcut::copy().contains( key ) ) {
390  parent->copy();
391  return true;
392  } else if ( KStandardShortcut::paste().contains( key ) ) {
393  parent->paste();
394  return true;
395  } else if ( KStandardShortcut::cut().contains( key ) ) {
396  parent->cut();
397  return true;
398  } else if ( KStandardShortcut::undo().contains( key ) ) {
399  if(!parent->isReadOnly())
400  parent->undo();
401  return true;
402  } else if ( KStandardShortcut::redo().contains( key ) ) {
403  if(!parent->isReadOnly())
404  parent->redo();
405  return true;
406  } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) {
407  parent->deleteWordBack();
408  return true;
409  } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) {
410  parent->deleteWordForward();
411  return true;
412  } else if ( KStandardShortcut::backwardWord().contains( key ) ) {
413  QTextCursor cursor = parent->textCursor();
414  cursor.movePosition( QTextCursor::PreviousWord );
415  parent->setTextCursor( cursor );
416  return true;
417  } else if ( KStandardShortcut::forwardWord().contains( key ) ) {
418  QTextCursor cursor = parent->textCursor();
419  cursor.movePosition( QTextCursor::NextWord );
420  parent->setTextCursor( cursor );
421  return true;
422  } else if ( KStandardShortcut::next().contains( key ) ) {
423  QTextCursor cursor = parent->textCursor();
424  bool moved = false;
425  qreal lastY = parent->cursorRect(cursor).bottom();
426  qreal distance = 0;
427  do {
428  qreal y = parent->cursorRect(cursor).bottom();
429  distance += qAbs(y - lastY);
430  lastY = y;
431  moved = cursor.movePosition(QTextCursor::Down);
432  } while (moved && distance < parent->viewport()->height());
433 
434  if (moved) {
435  cursor.movePosition(QTextCursor::Up);
436  parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
437  }
438  parent->setTextCursor(cursor);
439  return true;
440  } else if ( KStandardShortcut::prior().contains( key ) ) {
441  QTextCursor cursor = parent->textCursor();
442  bool moved = false;
443  qreal lastY = parent->cursorRect(cursor).bottom();
444  qreal distance = 0;
445  do {
446  qreal y = parent->cursorRect(cursor).bottom();
447  distance += qAbs(y - lastY);
448  lastY = y;
449  moved = cursor.movePosition(QTextCursor::Up);
450  } while (moved && distance < parent->viewport()->height());
451 
452  if (moved) {
453  cursor.movePosition(QTextCursor::Down);
454  parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
455  }
456  parent->setTextCursor(cursor);
457  return true;
458  } else if ( KStandardShortcut::begin().contains( key ) ) {
459  QTextCursor cursor = parent->textCursor();
460  cursor.movePosition( QTextCursor::Start );
461  parent->setTextCursor( cursor );
462  return true;
463  } else if ( KStandardShortcut::end().contains( key ) ) {
464  QTextCursor cursor = parent->textCursor();
465  cursor.movePosition( QTextCursor::End );
466  parent->setTextCursor( cursor );
467  return true;
468  } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) {
469  QTextCursor cursor = parent->textCursor();
470  cursor.movePosition( QTextCursor::StartOfLine );
471  parent->setTextCursor( cursor );
472  return true;
473  } else if ( KStandardShortcut::endOfLine().contains( key ) ) {
474  QTextCursor cursor = parent->textCursor();
475  cursor.movePosition( QTextCursor::EndOfLine );
476  parent->setTextCursor( cursor );
477  return true;
478  } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
479  parent->slotFind();
480  return true;
481  } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
482  parent->slotFindNext();
483  return true;
484  } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
485  if (!parent->isReadOnly())
486  parent->slotReplace();
487  return true;
488  } else if ( KStandardShortcut::pasteSelection().contains( key ) ) {
489  QString text = QApplication::clipboard()->text( QClipboard::Selection );
490  if ( !text.isEmpty() )
491  parent->insertPlainText( text ); // TODO: check if this is html? (MiB)
492  return true;
493  }
494  return false;
495 }
496 
497 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
498 {
499  cursor.clearSelection();
500  cursor.movePosition( op, QTextCursor::KeepAnchor );
501  cursor.removeSelectedText();
502 }
503 
504 void KTextEdit::deleteWordBack()
505 {
506  deleteWord(textCursor(), QTextCursor::PreviousWord);
507 }
508 
509 void KTextEdit::deleteWordForward()
510 {
511  deleteWord(textCursor(), QTextCursor::WordRight);
512 }
513 
514 QMenu *KTextEdit::mousePopupMenu()
515 {
516  QMenu *popup = createStandardContextMenu();
517  if (!popup) return 0;
518  connect( popup, SIGNAL(triggered(QAction*)),
519  this, SLOT(menuActivated(QAction*)) );
520 
521  const bool emptyDocument = document()->isEmpty();
522  if( !isReadOnly() )
523  {
524  QList<QAction *> actionList = popup->actions();
525  enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
526  QAction *separatorAction = 0L;
527  int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1;
528  if ( idx < actionList.count() )
529  separatorAction = actionList.at( idx );
530  if ( separatorAction )
531  {
532  KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup);
533  if ( emptyDocument )
534  clearAllAction->setEnabled( false );
535  popup->insertAction( separatorAction, clearAllAction );
536  }
537  }
538  KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText
539  : KIconTheme::TextEditor,
540  popup->actions() );
541 
542  if( !isReadOnly() )
543  {
544  popup->addSeparator();
545  d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ),
546  i18n( "Check Spelling..." ) );
547  if ( emptyDocument )
548  d->spellCheckAction->setEnabled( false );
549  d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) );
550  d->autoSpellCheckAction->setCheckable( true );
551  d->autoSpellCheckAction->setChecked( checkSpellingEnabled() );
552  popup->addSeparator();
553  if (d->showTabAction) {
554  d->allowTab = popup->addAction( i18n("Allow Tabulations") );
555  d->allowTab->setCheckable( true );
556  d->allowTab->setChecked( !tabChangesFocus() );
557  }
558  }
559 
560  if (d->findReplaceEnabled) {
561  KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup);
562  KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup);
563  if (emptyDocument) {
564  findAction->setEnabled(false);
565  findNextAction->setEnabled(false);
566  } else {
567  findNextAction->setEnabled(d->find != 0);
568  }
569  popup->addSeparator();
570  popup->addAction(findAction);
571  popup->addAction(findNextAction);
572 
573  if (!isReadOnly()) {
574  KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup);
575  if (emptyDocument) {
576  replaceAction->setEnabled(false);
577  }
578  popup->addAction(replaceAction);
579  }
580  }
581  popup->addSeparator();
582  QAction *speakAction = popup->addAction(i18n("Speak Text"));
583  speakAction->setIcon(KIcon("preferences-desktop-text-to-speech"));
584  speakAction->setEnabled(!emptyDocument );
585  connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) );
586  return popup;
587 }
588 
589 void KTextEdit::slotSpeakText()
590 {
591  // If KTTSD not running, start it.
592  if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd"))
593  {
594  QString error;
595  if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error))
596  {
597  KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error );
598  return;
599  }
600  }
601  QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech");
602  QString text;
603  if(textCursor().hasSelection())
604  text = textCursor().selectedText();
605  else
606  text = toPlainText();
607  ktts.asyncCall("say", text, 0);
608 }
609 
610 void KTextEdit::contextMenuEvent(QContextMenuEvent *event)
611 {
612  // Obtain the cursor at the mouse position and the current cursor
613  QTextCursor cursorAtMouse = cursorForPosition(event->pos());
614  const int mousePos = cursorAtMouse.position();
615  QTextCursor cursor = textCursor();
616 
617  // Check if the user clicked a selected word
618  const bool selectedWordClicked = cursor.hasSelection() &&
619  mousePos >= cursor.selectionStart() &&
620  mousePos <= cursor.selectionEnd();
621 
622  // Get the word under the (mouse-)cursor and see if it is misspelled.
623  // Don't include apostrophes at the start/end of the word in the selection.
624  QTextCursor wordSelectCursor(cursorAtMouse);
625  wordSelectCursor.clearSelection();
626  wordSelectCursor.select(QTextCursor::WordUnderCursor);
627  QString selectedWord = wordSelectCursor.selectedText();
628 
629  bool isMouseCursorInsideWord = true;
630  if ((mousePos < wordSelectCursor.selectionStart() ||
631  mousePos >= wordSelectCursor.selectionEnd())
632  && (selectedWord.length() > 1)) {
633  isMouseCursorInsideWord = false;
634  }
635 
636  // Clear the selection again, we re-select it below (without the apostrophes).
637  wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size());
638  if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) {
639  selectedWord = selectedWord.right(selectedWord.size() - 1);
640  wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
641  }
642  if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"'))
643  selectedWord.chop(1);
644 
645  wordSelectCursor.movePosition(QTextCursor::NextCharacter,
646  QTextCursor::KeepAnchor, selectedWord.size());
647 
648  const bool wordIsMisspelled = isMouseCursorInsideWord &&
649  checkSpellingEnabled() &&
650  !selectedWord.isEmpty() &&
651  highlighter() &&
652  highlighter()->isWordMisspelled(selectedWord);
653 
654  // If the user clicked a selected word, do nothing.
655  // If the user clicked somewhere else, move the cursor there.
656  // If the user clicked on a misspelled word, select that word.
657  // Same behavior as in OpenOffice Writer.
658  bool inQuote = false;
659  if (d->spellInterface &&
660  !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text()))
661  inQuote = true;
662  if (!selectedWordClicked) {
663  if (wordIsMisspelled && !inQuote)
664  setTextCursor(wordSelectCursor);
665  else
666  setTextCursor(cursorAtMouse);
667  cursor = textCursor();
668  }
669 
670  // Use standard context menu for already selected words, correctly spelled
671  // words and words inside quotes.
672  if (!wordIsMisspelled || selectedWordClicked || inQuote) {
673  QMetaObject::invokeMethod(this, "mousePopupMenuImplementation", Q_ARG(QPoint, event->globalPos()));
674  }
675  else {
676  QMenu menu; //don't use KMenu here we don't want auto management accelerator
677 
678  //Add the suggestions to the menu
679  const QStringList reps = highlighter()->suggestionsForWord(selectedWord);
680  if (reps.isEmpty()) {
681  QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord));
682  suggestionsAction->setEnabled(false);
683  }
684  else {
685  QStringList::const_iterator end(reps.constEnd());
686  for (QStringList::const_iterator it = reps.constBegin(); it != end; ++it) {
687  menu.addAction(*it);
688  }
689  }
690 
691  menu.addSeparator();
692 
693  QAction *ignoreAction = menu.addAction(i18n("Ignore"));
694  QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary"));
695  //Execute the popup inline
696  const QAction *selectedAction = menu.exec(event->globalPos());
697 
698  if (selectedAction) {
699  Q_ASSERT(cursor.selectedText() == selectedWord);
700 
701  if (selectedAction == ignoreAction) {
702  highlighter()->ignoreWord(selectedWord);
703  highlighter()->rehighlight();
704  }
705  else if (selectedAction == addToDictAction) {
706  highlighter()->addWordToDictionary(selectedWord);
707  highlighter()->rehighlight();
708  }
709 
710  // Other actions can only be one of the suggested words
711  else {
712  const QString replacement = selectedAction->text();
713  Q_ASSERT(reps.contains(replacement));
714  cursor.insertText(replacement);
715  setTextCursor(cursor);
716  }
717  }
718  }
719 }
720 
721 void KTextEdit::wheelEvent( QWheelEvent *event )
722 {
723  if ( KGlobalSettings::wheelMouseZooms() )
724  QTextEdit::wheelEvent( event );
725  else // thanks, we don't want to zoom, so skip QTextEdit's impl.
726  QAbstractScrollArea::wheelEvent( event );
727 }
728 
729 void KTextEdit::createHighlighter()
730 {
731  setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName));
732 }
733 
734 Sonnet::Highlighter* KTextEdit::highlighter() const
735 {
736  return d->highlighter;
737 }
738 
739 void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter)
740 {
741  delete d->highlighter;
742  d->highlighter = _highLighter;
743 }
744 
745 void KTextEdit::setCheckSpellingEnabled(bool check)
746 {
747  if (d->spellInterface)
748  d->spellInterface->setSpellCheckingEnabled(check);
749  else
750  setCheckSpellingEnabledInternal(check);
751 }
752 
753 void KTextEdit::setCheckSpellingEnabledInternal( bool check )
754 {
755  emit checkSpellingChanged( check );
756  if ( check == d->checkSpellingEnabled )
757  return;
758 
759  // From the above statment we know know that if we're turning checking
760  // on that we need to create a new highlighter and if we're turning it
761  // off we should remove the old one.
762 
763  d->checkSpellingEnabled = check;
764  if ( check )
765  {
766  if ( hasFocus() ) {
767  createHighlighter();
768  if (!spellCheckingLanguage().isEmpty())
769  setSpellCheckingLanguage(spellCheckingLanguage());
770  }
771  }
772  else
773  {
774  delete d->highlighter;
775  d->highlighter = 0;
776  }
777 }
778 
779 void KTextEdit::focusInEvent( QFocusEvent *event )
780 {
781  if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter )
782  createHighlighter();
783 
784  if (!d->clickMessage.isEmpty()) {
785  d->updateClickMessageRect();
786  }
787  QTextEdit::focusInEvent( event );
788 }
789 
790 bool KTextEdit::checkSpellingEnabled() const
791 {
792  if (d->spellInterface)
793  return d->spellInterface->isSpellCheckingEnabled();
794  else
795  return checkSpellingEnabledInternal();
796 }
797 
798 bool KTextEdit::checkSpellingEnabledInternal() const
799 {
800  return d->checkSpellingEnabled;
801 }
802 
803 void KTextEdit::setReadOnly( bool readOnly )
804 {
805  if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter )
806  createHighlighter();
807 
808  if ( readOnly == isReadOnly() )
809  return;
810 
811  if ( readOnly ) {
812  delete d->highlighter;
813  d->highlighter = 0;
814 
815  d->customPalette = testAttribute( Qt::WA_SetPalette );
816  QPalette p = palette();
817  QColor color = p.color( QPalette::Disabled, QPalette::Background );
818  p.setColor( QPalette::Base, color );
819  p.setColor( QPalette::Background, color );
820  setPalette( p );
821  } else {
822  if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) {
823  QPalette p = palette();
824  QColor color = p.color( QPalette::Normal, QPalette::Base );
825  p.setColor( QPalette::Base, color );
826  p.setColor( QPalette::Background, color );
827  setPalette( p );
828  } else
829  setPalette( QPalette() );
830  }
831 
832  QTextEdit::setReadOnly( readOnly );
833 }
834 
835 void KTextEdit::checkSpelling()
836 {
837  d->checkSpelling(false);
838 }
839 
840 void KTextEdit::forceSpellChecking()
841 {
842  d->checkSpelling(true);
843 }
844 
845 void KTextEdit::highlightWord( int length, int pos )
846 {
847  QTextCursor cursor(document());
848  cursor.setPosition(pos);
849  cursor.setPosition(pos+length,QTextCursor::KeepAnchor);
850  setTextCursor (cursor);
851  ensureCursorVisible();
852 }
853 
854 void KTextEdit::replace()
855 {
856  if ( document()->isEmpty() ) // saves having to track the text changes
857  return;
858 
859  if ( d->repDlg ) {
860  KWindowSystem::activateWindow( d->repDlg->winId() );
861  } else {
862  d->repDlg = new KReplaceDialog(this, 0,
863  QStringList(), QStringList(), false);
864  connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) );
865  }
866  d->repDlg->show();
867 }
868 
869 void KTextEdit::slotDoReplace()
870 {
871  if (!d->repDlg) {
872  // Should really assert()
873  return;
874  }
875 
876  if(d->repDlg->pattern().isEmpty()) {
877  delete d->replace;
878  d->replace = 0;
879  ensureCursorVisible();
880  return;
881  }
882 
883  delete d->replace;
884  d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this);
885  d->repIndex = 0;
886  if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) {
887  d->repIndex = textCursor().anchor();
888  }
889 
890  // Connect highlight signal to code which handles highlighting
891  // of found text.
892  connect(d->replace, SIGNAL(highlight(QString,int,int)),
893  this, SLOT(slotFindHighlight(QString,int,int)));
894  connect(d->replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext()));
895  connect(d->replace, SIGNAL(replace(QString,int,int,int)),
896  this, SLOT(slotReplaceText(QString,int,int,int)));
897 
898  d->repDlg->close();
899  slotReplaceNext();
900 }
901 
902 
903 void KTextEdit::slotReplaceNext()
904 {
905  if (!d->replace)
906  return;
907 
908  d->lastReplacedPosition = -1;
909  if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
910  textCursor().beginEditBlock(); // #48541
911  viewport()->setUpdatesEnabled(false);
912  }
913 
914  KFind::Result res = KFind::NoMatch;
915 
916  if (d->replace->needData())
917  d->replace->setData(toPlainText(), d->repIndex);
918  res = d->replace->replace();
919  if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
920  textCursor().endEditBlock(); // #48541
921  if (d->lastReplacedPosition >= 0) {
922  QTextCursor tc = textCursor();
923  tc.setPosition(d->lastReplacedPosition);
924  setTextCursor(tc);
925  ensureCursorVisible();
926  }
927 
928  viewport()->setUpdatesEnabled(true);
929  viewport()->update();
930  }
931 
932  if (res == KFind::NoMatch) {
933  d->replace->displayFinalDialog();
934  d->replace->disconnect(this);
935  d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away
936  d->replace = 0;
937  ensureCursorVisible();
938  //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
939  } else {
940  //m_replace->closeReplaceNextDialog();
941  }
942 }
943 
944 
945 void KTextEdit::slotDoFind()
946 {
947  if (!d->findDlg) {
948  // Should really assert()
949  return;
950  }
951  if( d->findDlg->pattern().isEmpty())
952  {
953  delete d->find;
954  d->find = 0;
955  return;
956  }
957  delete d->find;
958  d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this);
959  d->findIndex = 0;
960  if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) {
961  d->findIndex = textCursor().anchor();
962  }
963 
964  // Connect highlight signal to code which handles highlighting
965  // of found text.
966  connect(d->find, SIGNAL(highlight(QString,int,int)),
967  this, SLOT(slotFindHighlight(QString,int,int)));
968  connect(d->find, SIGNAL(findNext()), this, SLOT(slotFindNext()));
969 
970  d->findDlg->close();
971  d->find->closeFindNextDialog();
972  slotFindNext();
973 }
974 
975 
976 void KTextEdit::slotFindNext()
977 {
978  if (!d->find)
979  return;
980  if(document()->isEmpty())
981  {
982  d->find->disconnect(this);
983  d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
984  d->find = 0;
985  return;
986  }
987 
988  KFind::Result res = KFind::NoMatch;
989  if (d->find->needData())
990  d->find->setData(toPlainText(), d->findIndex);
991  res = d->find->find();
992 
993  if (res == KFind::NoMatch) {
994  d->find->displayFinalDialog();
995  d->find->disconnect(this);
996  d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
997  d->find = 0;
998  //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); }
999  } else {
1000  //m_find->closeFindNextDialog();
1001  }
1002 }
1003 
1004 
1005 void KTextEdit::slotFind()
1006 {
1007  if ( document()->isEmpty() ) // saves having to track the text changes
1008  return;
1009 
1010  if ( d->findDlg ) {
1011  KWindowSystem::activateWindow( d->findDlg->winId() );
1012  } else {
1013  d->findDlg = new KFindDialog(this);
1014  connect( d->findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) );
1015  }
1016  d->findDlg->show();
1017 }
1018 
1019 
1020 void KTextEdit::slotReplace()
1021 {
1022  if ( document()->isEmpty() ) // saves having to track the text changes
1023  return;
1024 
1025  if ( d->repDlg ) {
1026  KWindowSystem::activateWindow( d->repDlg->winId() );
1027  } else {
1028  d->repDlg = new KReplaceDialog(this, 0,
1029  QStringList(), QStringList(), false);
1030  connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) );
1031  }
1032  d->repDlg->show();
1033 }
1034 
1035 void KTextEdit::enableFindReplace( bool enabled )
1036 {
1037  d->findReplaceEnabled = enabled;
1038 }
1039 
1040 void KTextEdit::showTabAction( bool show )
1041 {
1042  d->showTabAction = show;
1043 }
1044 
1045 void KTextEdit::setSpellInterface(KTextEditSpellInterface *spellInterface)
1046 {
1047  d->spellInterface = spellInterface;
1048 }
1049 
1050 bool KTextEdit::Private::overrideShortcut(const QKeyEvent* event)
1051 {
1052  const int key = event->key() | event->modifiers();
1053 
1054  if ( KStandardShortcut::copy().contains( key ) ) {
1055  return true;
1056  } else if ( KStandardShortcut::paste().contains( key ) ) {
1057  return true;
1058  } else if ( KStandardShortcut::cut().contains( key ) ) {
1059  return true;
1060  } else if ( KStandardShortcut::undo().contains( key ) ) {
1061  return true;
1062  } else if ( KStandardShortcut::redo().contains( key ) ) {
1063  return true;
1064  } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) {
1065  return true;
1066  } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) {
1067  return true;
1068  } else if ( KStandardShortcut::backwardWord().contains( key ) ) {
1069  return true;
1070  } else if ( KStandardShortcut::forwardWord().contains( key ) ) {
1071  return true;
1072  } else if ( KStandardShortcut::next().contains( key ) ) {
1073  return true;
1074  } else if ( KStandardShortcut::prior().contains( key ) ) {
1075  return true;
1076  } else if ( KStandardShortcut::begin().contains( key ) ) {
1077  return true;
1078  } else if ( KStandardShortcut::end().contains( key ) ) {
1079  return true;
1080  } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) {
1081  return true;
1082  } else if ( KStandardShortcut::endOfLine().contains( key ) ) {
1083  return true;
1084  } else if ( KStandardShortcut::pasteSelection().contains( key ) ) {
1085  return true;
1086  } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
1087  return true;
1088  } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
1089  return true;
1090  } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
1091  return true;
1092  } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
1093  return true;
1094  } else if (event->modifiers() == Qt::ControlModifier &&
1095  (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) &&
1096  qobject_cast<KDialog*>(parent->window()) ) {
1097  // ignore Ctrl-Return so that KDialogs can close the dialog
1098  return true;
1099  }
1100  return false;
1101 }
1102 
1103 void KTextEdit::keyPressEvent( QKeyEvent *event )
1104 {
1105  if (d->handleShortcut(event)) {
1106  event->accept();
1107  }else if (event->modifiers() == Qt::ControlModifier &&
1108  (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) &&
1109  qobject_cast<KDialog*>(window()) ) {
1110  event->ignore();
1111  } else {
1112  QTextEdit::keyPressEvent(event);
1113  }
1114 }
1115 
1116 void KTextEdit::setClickMessage(const QString &msg)
1117 {
1118  if (msg != d->clickMessage) {
1119  if (!d->clickMessage.isEmpty()) {
1120  d->updateClickMessageRect();
1121  }
1122  d->clickMessage = msg;
1123  if (!d->clickMessage.isEmpty()) {
1124  d->updateClickMessageRect();
1125  }
1126  }
1127 }
1128 
1129 QString KTextEdit::clickMessage() const
1130 {
1131  return d->clickMessage;
1132 }
1133 
1134 void KTextEdit::paintEvent(QPaintEvent *ev)
1135 {
1136  QTextEdit::paintEvent(ev);
1137 
1138  if (!d->clickMessage.isEmpty() && !hasFocus() && document()->isEmpty()) {
1139  QPainter p(viewport());
1140 
1141  QFont f = font();
1142  f.setItalic(d->italicizePlaceholder);
1143  p.setFont(f);
1144 
1145  QColor color(palette().color(viewport()->foregroundRole()));
1146  color.setAlphaF(0.5);
1147  p.setPen(color);
1148 
1149  int margin = int(document()->documentMargin());
1150  QRect cr = viewport()->rect().adjusted(margin, margin, -margin, -margin);
1151 
1152  p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage);
1153  }
1154 }
1155 
1156 void KTextEdit::focusOutEvent(QFocusEvent *ev)
1157 {
1158  if (!d->clickMessage.isEmpty()) {
1159  d->updateClickMessageRect();
1160  }
1161  QTextEdit::focusOutEvent(ev);
1162 }
1163 
1164 void KTextEdit::showAutoCorrectButton(bool show)
1165 {
1166  d->showAutoCorrectionButton = show;
1167 }
1168 
1169 void KTextEdit::mousePopupMenuImplementation(const QPoint& pos)
1170 {
1171  QMenu *popup = mousePopupMenu();
1172  if ( popup ) {
1173  aboutToShowContextMenu(popup);
1174  popup->exec( pos );
1175  delete popup;
1176  }
1177 }
1178 
1179 #include "ktextedit.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Feb 9 2013 12:06:07 by doxygen 1.8.2 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.10.0 API Reference

Skip menu "kdelibs-4.10.0 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