KDEUI
ktextedit.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2002 Carsten Pfeiffer <pfeiffer@kde.org> 00003 2005 Michael Brade <brade@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "ktextedit.h" 00022 #include <ktoolinvocation.h> 00023 #include <kdebug.h> 00024 00025 #include <QApplication> 00026 #include <QClipboard> 00027 #include <QKeyEvent> 00028 #include <QMenu> 00029 #include <QPainter> 00030 #include <QScrollBar> 00031 #include <QTextCursor> 00032 #include <QTextDocumentFragment> 00033 #include <QDBusInterface> 00034 #include <QDBusConnection> 00035 #include <QDBusConnectionInterface> 00036 00037 #include <configdialog.h> 00038 #include <dialog.h> 00039 #include "backgroundchecker.h" 00040 #include <kaction.h> 00041 #include <kcursor.h> 00042 #include <kglobalsettings.h> 00043 #include <kstandardaction.h> 00044 #include <kstandardshortcut.h> 00045 #include <kicon.h> 00046 #include <kiconloader.h> 00047 #include <klocale.h> 00048 #include <kdialog.h> 00049 #include <kreplacedialog.h> 00050 #include <kfinddialog.h> 00051 #include <kfind.h> 00052 #include <kreplace.h> 00053 #include <kmessagebox.h> 00054 #include <kmenu.h> 00055 #include <kwindowsystem.h> 00056 #include <QDebug> 00057 00058 class KTextEdit::Private 00059 { 00060 public: 00061 Private( KTextEdit *_parent ) 00062 : parent( _parent ), 00063 customPalette( false ), 00064 checkSpellingEnabled( false ), 00065 findReplaceEnabled(true), 00066 highlighter( 0 ), findDlg(0),find(0),repDlg(0),replace(0), findIndex(0), repIndex(0), 00067 lastReplacedPosition(-1) 00068 { 00069 //Check the default sonnet settings to see if spellchecking should be enabled. 00070 sonnetKConfig = new KConfig("sonnetrc"); 00071 KConfigGroup group(sonnetKConfig, "Spelling"); 00072 checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false); 00073 00074 // i18n: Placeholder text in text edit widgets is the text appearing 00075 // before any user input, briefly explaining to the user what to type 00076 // (e.g. "Enter message"). 00077 // By default the text is set in italic, which may not be appropriate 00078 // for some languages and scripts (e.g. for CJK ideographs). 00079 QString metaMsg = i18nc("Italic placeholder text in line edits: 0 no, 1 yes", "1"); 00080 italicizePlaceholder = (metaMsg.trimmed() != QString('0')); 00081 } 00082 00083 ~Private() 00084 { 00085 delete highlighter; 00086 delete findDlg; 00087 delete find; 00088 delete replace; 00089 delete repDlg; 00090 delete sonnetKConfig; 00091 } 00092 00098 bool overrideShortcut(const QKeyEvent* e); 00102 bool handleShortcut(const QKeyEvent* e); 00103 00104 void spellCheckerMisspelling( const QString &text, int pos ); 00105 void spellCheckerCorrected( const QString &, int,const QString &); 00106 void spellCheckerAutoCorrect(const QString&,const QString&); 00107 void spellCheckerCanceled(); 00108 void spellCheckerFinished(); 00109 void toggleAutoSpellCheck(); 00110 00111 void slotFindHighlight(const QString& text, int matchingIndex, int matchingLength); 00112 void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength); 00113 00118 void undoableClear(); 00119 00120 void slotAllowTab(); 00121 void menuActivated( QAction* action ); 00122 00123 void updateClickMessageRect(); 00124 00125 void init(); 00126 00127 KTextEdit *parent; 00128 KTextEditSpellInterface *spellInterface; 00129 QAction *autoSpellCheckAction; 00130 QAction *allowTab; 00131 QAction *spellCheckAction; 00132 QString clickMessage; 00133 bool italicizePlaceholder : 1; 00134 bool customPalette : 1; 00135 00136 bool checkSpellingEnabled : 1; 00137 bool findReplaceEnabled: 1; 00138 QTextDocumentFragment originalDoc; 00139 QString spellCheckingConfigFileName; 00140 QString spellCheckingLanguage; 00141 Sonnet::Highlighter *highlighter; 00142 KFindDialog *findDlg; 00143 KFind *find; 00144 KReplaceDialog *repDlg; 00145 KReplace *replace; 00146 int findIndex, repIndex; 00147 int lastReplacedPosition; 00148 KConfig *sonnetKConfig; 00149 }; 00150 00151 void KTextEdit::Private::spellCheckerCanceled() 00152 { 00153 QTextDocument *doc = parent->document(); 00154 doc->clear(); 00155 QTextCursor cursor(doc); 00156 cursor.insertFragment(originalDoc); 00157 spellCheckerFinished(); 00158 } 00159 00160 void KTextEdit::Private::spellCheckerAutoCorrect(const QString&,const QString&) 00161 { 00162 //TODO 00163 } 00164 00165 void KTextEdit::Private::spellCheckerMisspelling( const QString &text, int pos ) 00166 { 00167 //kDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<<text<<" pos :"<<pos; 00168 parent->highlightWord( text.length(), pos ); 00169 } 00170 00171 void KTextEdit::Private::spellCheckerCorrected( const QString& oldWord, int pos,const QString& newWord) 00172 { 00173 //kDebug()<<" oldWord :"<<oldWord<<" newWord :"<<newWord<<" pos : "<<pos; 00174 if (oldWord != newWord ) { 00175 QTextCursor cursor(parent->document()); 00176 cursor.setPosition(pos); 00177 cursor.setPosition(pos+oldWord.length(),QTextCursor::KeepAnchor); 00178 cursor.insertText(newWord); 00179 } 00180 } 00181 00182 void KTextEdit::Private::spellCheckerFinished() 00183 { 00184 QTextCursor cursor(parent->document()); 00185 cursor.clearSelection(); 00186 parent->setTextCursor(cursor); 00187 if (parent->highlighter()) 00188 parent->highlighter()->rehighlight(); 00189 } 00190 00191 void KTextEdit::Private::toggleAutoSpellCheck() 00192 { 00193 parent->setCheckSpellingEnabled( !parent->checkSpellingEnabled() ); 00194 } 00195 00196 void KTextEdit::Private::undoableClear() 00197 { 00198 QTextCursor cursor = parent->textCursor(); 00199 cursor.beginEditBlock(); 00200 cursor.movePosition(QTextCursor::Start); 00201 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 00202 cursor.removeSelectedText(); 00203 cursor.endEditBlock(); 00204 } 00205 00206 void KTextEdit::Private::slotAllowTab() 00207 { 00208 parent->setTabChangesFocus( !parent->tabChangesFocus() ); 00209 } 00210 00211 void KTextEdit::Private::menuActivated( QAction* action ) 00212 { 00213 if ( action == spellCheckAction ) 00214 parent->checkSpelling(); 00215 else if ( action == autoSpellCheckAction ) 00216 toggleAutoSpellCheck(); 00217 else if ( action == allowTab ) 00218 slotAllowTab(); 00219 } 00220 00221 00222 void KTextEdit::Private::slotFindHighlight(const QString& text, int matchingIndex, int matchingLength) 00223 { 00224 Q_UNUSED(text) 00225 //kDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength; 00226 QTextCursor tc = parent->textCursor(); 00227 tc.setPosition(matchingIndex); 00228 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength); 00229 parent->setTextCursor(tc); 00230 parent->ensureCursorVisible(); 00231 } 00232 00233 00234 void KTextEdit::Private::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength) { 00235 //kDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength; 00236 QTextCursor tc = parent->textCursor(); 00237 tc.setPosition(replacementIndex); 00238 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength); 00239 tc.removeSelectedText(); 00240 tc.insertText(text.mid(replacementIndex, replacedLength)); 00241 if (replace->options() & KReplaceDialog::PromptOnReplace) { 00242 parent->setTextCursor(tc); 00243 parent->ensureCursorVisible(); 00244 } 00245 lastReplacedPosition = replacementIndex; 00246 } 00247 00248 void KTextEdit::Private::updateClickMessageRect() 00249 { 00250 int margin = int(parent->document()->documentMargin()); 00251 QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin); 00252 rect = parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage); 00253 parent->viewport()->update(rect); 00254 } 00255 00256 void KTextEdit::Private::init() 00257 { 00258 spellInterface = 0; 00259 KCursor::setAutoHideCursor(parent, true, false); 00260 parent->connect(parent, SIGNAL(languageChanged(const QString&)), 00261 parent, SLOT(setSpellCheckingLanguage(const QString&))); 00262 } 00263 00264 KTextEdit::KTextEdit( const QString& text, QWidget *parent ) 00265 : QTextEdit( text, parent ), d( new Private( this ) ) 00266 { 00267 d->init(); 00268 } 00269 00270 KTextEdit::KTextEdit( QWidget *parent ) 00271 : QTextEdit( parent ), d( new Private( this ) ) 00272 { 00273 d->init(); 00274 } 00275 00276 KTextEdit::~KTextEdit() 00277 { 00278 delete d; 00279 } 00280 00281 void KTextEdit::setSpellCheckingConfigFileName(const QString &_fileName) 00282 { 00283 d->spellCheckingConfigFileName = _fileName; 00284 } 00285 00286 const QString& KTextEdit::spellCheckingLanguage() const 00287 { 00288 return d->spellCheckingLanguage; 00289 } 00290 00291 void KTextEdit::setSpellCheckingLanguage(const QString &_language) 00292 { 00293 if (highlighter()) { 00294 highlighter()->setCurrentLanguage(_language); 00295 highlighter()->rehighlight(); 00296 } 00297 00298 if (_language != d->spellCheckingLanguage) { 00299 d->spellCheckingLanguage = _language; 00300 emit languageChanged(_language); 00301 } 00302 else 00303 d->spellCheckingLanguage = _language; 00304 } 00305 00306 void KTextEdit::showSpellConfigDialog(const QString &configFileName, 00307 const QString &windowIcon) 00308 { 00309 KConfig config(configFileName); 00310 Sonnet::ConfigDialog dialog(&config, this); 00311 if (!d->spellCheckingLanguage.isEmpty()) 00312 dialog.setLanguage(d->spellCheckingLanguage); 00313 connect(&dialog, SIGNAL(languageChanged(const QString &)), 00314 this, SLOT(setSpellCheckingLanguage(const QString &))); 00315 if (!windowIcon.isEmpty()) 00316 dialog.setWindowIcon(KIcon(windowIcon)); 00317 dialog.exec(); 00318 } 00319 00320 bool KTextEdit::event(QEvent* ev) 00321 { 00322 if (ev->type() == QEvent::ShortcutOverride) { 00323 QKeyEvent *e = static_cast<QKeyEvent *>( ev ); 00324 if (d->overrideShortcut(e)) { 00325 e->accept(); 00326 return true; 00327 } 00328 } 00329 return QTextEdit::event(ev); 00330 } 00331 00332 bool KTextEdit::Private::handleShortcut(const QKeyEvent* event) 00333 { 00334 const int key = event->key() | event->modifiers(); 00335 00336 if ( KStandardShortcut::copy().contains( key ) ) { 00337 parent->copy(); 00338 return true; 00339 } else if ( KStandardShortcut::paste().contains( key ) ) { 00340 parent->paste(); 00341 return true; 00342 } else if ( KStandardShortcut::cut().contains( key ) ) { 00343 parent->cut(); 00344 return true; 00345 } else if ( KStandardShortcut::undo().contains( key ) ) { 00346 if(!parent->isReadOnly()) 00347 parent->undo(); 00348 return true; 00349 } else if ( KStandardShortcut::redo().contains( key ) ) { 00350 if(!parent->isReadOnly()) 00351 parent->redo(); 00352 return true; 00353 } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { 00354 parent->deleteWordBack(); 00355 return true; 00356 } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { 00357 parent->deleteWordForward(); 00358 return true; 00359 } else if ( KStandardShortcut::backwardWord().contains( key ) ) { 00360 QTextCursor cursor = parent->textCursor(); 00361 cursor.movePosition( QTextCursor::PreviousWord ); 00362 parent->setTextCursor( cursor ); 00363 return true; 00364 } else if ( KStandardShortcut::forwardWord().contains( key ) ) { 00365 QTextCursor cursor = parent->textCursor(); 00366 cursor.movePosition( QTextCursor::NextWord ); 00367 parent->setTextCursor( cursor ); 00368 return true; 00369 } else if ( KStandardShortcut::next().contains( key ) ) { 00370 QTextCursor cursor = parent->textCursor(); 00371 bool moved = false; 00372 qreal lastY = parent->cursorRect(cursor).bottom(); 00373 qreal distance = 0; 00374 do { 00375 qreal y = parent->cursorRect(cursor).bottom(); 00376 distance += qAbs(y - lastY); 00377 lastY = y; 00378 moved = cursor.movePosition(QTextCursor::Down); 00379 } while (moved && distance < parent->viewport()->height()); 00380 00381 if (moved) { 00382 cursor.movePosition(QTextCursor::Up); 00383 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); 00384 } 00385 parent->setTextCursor(cursor); 00386 return true; 00387 } else if ( KStandardShortcut::prior().contains( key ) ) { 00388 QTextCursor cursor = parent->textCursor(); 00389 bool moved = false; 00390 qreal lastY = parent->cursorRect(cursor).bottom(); 00391 qreal distance = 0; 00392 do { 00393 qreal y = parent->cursorRect(cursor).bottom(); 00394 distance += qAbs(y - lastY); 00395 lastY = y; 00396 moved = cursor.movePosition(QTextCursor::Up); 00397 } while (moved && distance < parent->viewport()->height()); 00398 00399 if (moved) { 00400 cursor.movePosition(QTextCursor::Down); 00401 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); 00402 } 00403 parent->setTextCursor(cursor); 00404 return true; 00405 } else if ( KStandardShortcut::begin().contains( key ) ) { 00406 QTextCursor cursor = parent->textCursor(); 00407 cursor.movePosition( QTextCursor::Start ); 00408 parent->setTextCursor( cursor ); 00409 return true; 00410 } else if ( KStandardShortcut::end().contains( key ) ) { 00411 QTextCursor cursor = parent->textCursor(); 00412 cursor.movePosition( QTextCursor::End ); 00413 parent->setTextCursor( cursor ); 00414 return true; 00415 } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { 00416 QTextCursor cursor = parent->textCursor(); 00417 cursor.movePosition( QTextCursor::StartOfLine ); 00418 parent->setTextCursor( cursor ); 00419 return true; 00420 } else if ( KStandardShortcut::endOfLine().contains( key ) ) { 00421 QTextCursor cursor = parent->textCursor(); 00422 cursor.movePosition( QTextCursor::EndOfLine ); 00423 parent->setTextCursor( cursor ); 00424 return true; 00425 } else if (KStandardShortcut::find().contains(key)) { 00426 if (findReplaceEnabled) 00427 parent->slotFind(); 00428 return true; 00429 } else if (KStandardShortcut::findNext().contains(key)) { 00430 if (findReplaceEnabled) 00431 parent->slotFindNext(); 00432 return true; 00433 } else if (KStandardShortcut::replace().contains(key)) { 00434 if (!parent->isReadOnly() && findReplaceEnabled) 00435 parent->slotReplace(); 00436 return true; 00437 } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { 00438 QString text = QApplication::clipboard()->text( QClipboard::Selection ); 00439 if ( !text.isEmpty() ) 00440 parent->insertPlainText( text ); // TODO: check if this is html? (MiB) 00441 return true; 00442 } 00443 return false; 00444 } 00445 00446 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op) 00447 { 00448 cursor.clearSelection(); 00449 cursor.movePosition( op, QTextCursor::KeepAnchor ); 00450 cursor.removeSelectedText(); 00451 } 00452 00453 void KTextEdit::deleteWordBack() 00454 { 00455 deleteWord(textCursor(), QTextCursor::PreviousWord); 00456 } 00457 00458 void KTextEdit::deleteWordForward() 00459 { 00460 deleteWord(textCursor(), QTextCursor::WordRight); 00461 } 00462 00463 QMenu *KTextEdit::mousePopupMenu() 00464 { 00465 QMenu *popup = createStandardContextMenu(); 00466 if (!popup) return 0; 00467 connect( popup, SIGNAL( triggered ( QAction* ) ), 00468 this, SLOT( menuActivated( QAction* ) ) ); 00469 00470 const bool emptyDocument = document()->isEmpty(); 00471 if( !isReadOnly() ) 00472 { 00473 QList<QAction *> actionList = popup->actions(); 00474 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; 00475 QAction *separatorAction = 0L; 00476 int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1; 00477 if ( idx < actionList.count() ) 00478 separatorAction = actionList.at( idx ); 00479 if ( separatorAction ) 00480 { 00481 KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup); 00482 if ( emptyDocument ) 00483 clearAllAction->setEnabled( false ); 00484 popup->insertAction( separatorAction, clearAllAction ); 00485 } 00486 } 00487 KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText 00488 : KIconTheme::TextEditor, 00489 popup->actions() ); 00490 00491 if( !isReadOnly() ) 00492 { 00493 popup->addSeparator(); 00494 d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ), 00495 i18n( "Check Spelling..." ) ); 00496 if ( emptyDocument ) 00497 d->spellCheckAction->setEnabled( false ); 00498 d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) ); 00499 d->autoSpellCheckAction->setCheckable( true ); 00500 d->autoSpellCheckAction->setChecked( checkSpellingEnabled() ); 00501 popup->addSeparator(); 00502 d->allowTab = popup->addAction( i18n("Allow Tabulations") ); 00503 d->allowTab->setCheckable( true ); 00504 d->allowTab->setChecked( !tabChangesFocus() ); 00505 00506 if (d->findReplaceEnabled) 00507 { 00508 KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup); 00509 KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup); 00510 KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup); 00511 if (emptyDocument) 00512 { 00513 findAction->setEnabled(false); 00514 findNextAction->setEnabled(false ); 00515 replaceAction->setEnabled(false); 00516 } 00517 else 00518 findNextAction->setEnabled(d->find != 0 ); 00519 popup->addSeparator(); 00520 popup->addAction(findAction); 00521 popup->addAction(findNextAction); 00522 popup->addAction(replaceAction); 00523 } 00524 } 00525 popup->addSeparator(); 00526 QAction *speakAction = popup->addAction(i18n("Speak Text")); 00527 speakAction->setIcon(KIcon("preferences-desktop-text-to-speech")); 00528 speakAction->setEnabled(!emptyDocument ); 00529 connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) ); 00530 return popup; 00531 } 00532 00533 void KTextEdit::slotSpeakText() 00534 { 00535 // If KTTSD not running, start it. 00536 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")) 00537 { 00538 QString error; 00539 if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error)) 00540 { 00541 KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error ); 00542 return; 00543 } 00544 } 00545 QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech"); 00546 QString text; 00547 if(textCursor().hasSelection()) 00548 text = textCursor().selectedText(); 00549 else 00550 text = toPlainText(); 00551 ktts.asyncCall("say", text, 0); 00552 } 00553 00554 void KTextEdit::contextMenuEvent(QContextMenuEvent *event) 00555 { 00556 // Obtain the cursor at the mouse position and the current cursor 00557 QTextCursor cursorAtMouse = cursorForPosition(event->pos()); 00558 const int mousePos = cursorAtMouse.position(); 00559 QTextCursor cursor = textCursor(); 00560 00561 // Check if the user clicked a selected word 00562 const bool selectedWordClicked = cursor.hasSelection() && 00563 mousePos >= cursor.selectionStart() && 00564 mousePos <= cursor.selectionEnd(); 00565 00566 // Get the word under the (mouse-)cursor and see if it is misspelled. 00567 // Don't include apostrophes at the start/end of the word in the selection. 00568 QTextCursor wordSelectCursor(cursorAtMouse); 00569 wordSelectCursor.clearSelection(); 00570 wordSelectCursor.select(QTextCursor::WordUnderCursor); 00571 QString selectedWord = wordSelectCursor.selectedText(); 00572 00573 bool isMouseCursorInsideWord = true; 00574 if ((mousePos < wordSelectCursor.selectionStart() || 00575 mousePos >= wordSelectCursor.selectionEnd()) 00576 && (selectedWord.length() > 1)) { 00577 isMouseCursorInsideWord = false; 00578 } 00579 00580 // Clear the selection again, we re-select it below (without the apostrophes). 00581 wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size()); 00582 if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) { 00583 selectedWord = selectedWord.right(selectedWord.size() - 1); 00584 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor); 00585 } 00586 if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"')) 00587 selectedWord.chop(1); 00588 00589 wordSelectCursor.movePosition(QTextCursor::NextCharacter, 00590 QTextCursor::KeepAnchor, selectedWord.size()); 00591 00592 const bool wordIsMisspelled = isMouseCursorInsideWord && 00593 checkSpellingEnabled() && 00594 !selectedWord.isEmpty() && 00595 highlighter() && 00596 highlighter()->isWordMisspelled(selectedWord); 00597 00598 // If the user clicked a selected word, do nothing. 00599 // If the user clicked somewhere else, move the cursor there. 00600 // If the user clicked on a misspelled word, select that word. 00601 // Same behavior as in OpenOffice Writer. 00602 bool inQuote = false; 00603 if (d->spellInterface && 00604 !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text())) 00605 inQuote = true; 00606 if (!selectedWordClicked) { 00607 if (wordIsMisspelled && !inQuote) 00608 setTextCursor(wordSelectCursor); 00609 else 00610 setTextCursor(cursorAtMouse); 00611 cursor = textCursor(); 00612 } 00613 00614 // Use standard context menu for already selected words, correctly spelled 00615 // words and words inside quotes. 00616 if (!wordIsMisspelled || selectedWordClicked || inQuote) { 00617 QMenu *popup = mousePopupMenu(); 00618 if ( popup ) { 00619 aboutToShowContextMenu(popup); 00620 popup->exec( event->globalPos() ); 00621 delete popup; 00622 } 00623 } 00624 else { 00625 QMenu menu; //don't use KMenu here we don't want auto management accelerator 00626 00627 //Add the suggestions to the menu 00628 const QStringList reps = highlighter()->suggestionsForWord(selectedWord); 00629 if (reps.isEmpty()) { 00630 QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord)); 00631 suggestionsAction->setEnabled(false); 00632 } 00633 else { 00634 for (QStringList::const_iterator it = reps.constBegin(); it != reps.constEnd(); ++it) { 00635 menu.addAction(*it); 00636 } 00637 } 00638 00639 menu.addSeparator(); 00640 00641 QAction *ignoreAction = menu.addAction(i18n("Ignore")); 00642 QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary")); 00643 //Execute the popup inline 00644 const QAction *selectedAction = menu.exec(event->globalPos()); 00645 00646 if (selectedAction) { 00647 Q_ASSERT(cursor.selectedText() == selectedWord); 00648 00649 if (selectedAction == ignoreAction) { 00650 highlighter()->ignoreWord(selectedWord); 00651 highlighter()->rehighlight(); 00652 } 00653 else if (selectedAction == addToDictAction) { 00654 highlighter()->addWordToDictionary(selectedWord); 00655 highlighter()->rehighlight(); 00656 } 00657 00658 // Other actions can only be one of the suggested words 00659 else { 00660 const QString replacement = selectedAction->text(); 00661 Q_ASSERT(reps.contains(replacement)); 00662 cursor.insertText(replacement); 00663 setTextCursor(cursor); 00664 } 00665 } 00666 } 00667 } 00668 00669 void KTextEdit::wheelEvent( QWheelEvent *event ) 00670 { 00671 if ( KGlobalSettings::wheelMouseZooms() ) 00672 QTextEdit::wheelEvent( event ); 00673 else // thanks, we don't want to zoom, so skip QTextEdit's impl. 00674 QAbstractScrollArea::wheelEvent( event ); 00675 } 00676 00677 void KTextEdit::createHighlighter() 00678 { 00679 setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName)); 00680 } 00681 00682 Sonnet::Highlighter* KTextEdit::highlighter() const 00683 { 00684 return d->highlighter; 00685 } 00686 00687 void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter) 00688 { 00689 delete d->highlighter; 00690 d->highlighter = _highLighter; 00691 } 00692 00693 void KTextEdit::setCheckSpellingEnabled(bool check) 00694 { 00695 if (d->spellInterface) 00696 d->spellInterface->setSpellCheckingEnabled(check); 00697 else 00698 setCheckSpellingEnabledInternal(check); 00699 } 00700 00701 void KTextEdit::setCheckSpellingEnabledInternal( bool check ) 00702 { 00703 emit checkSpellingChanged( check ); 00704 if ( check == d->checkSpellingEnabled ) 00705 return; 00706 00707 // From the above statment we know know that if we're turning checking 00708 // on that we need to create a new highlighter and if we're turning it 00709 // off we should remove the old one. 00710 00711 d->checkSpellingEnabled = check; 00712 if ( check ) 00713 { 00714 if ( hasFocus() ) { 00715 createHighlighter(); 00716 if (!spellCheckingLanguage().isEmpty()) 00717 setSpellCheckingLanguage(spellCheckingLanguage()); 00718 } 00719 } 00720 else 00721 { 00722 delete d->highlighter; 00723 d->highlighter = 0; 00724 } 00725 } 00726 00727 void KTextEdit::focusInEvent( QFocusEvent *event ) 00728 { 00729 if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter ) 00730 createHighlighter(); 00731 00732 if (!d->clickMessage.isEmpty()) { 00733 d->updateClickMessageRect(); 00734 } 00735 QTextEdit::focusInEvent( event ); 00736 } 00737 00738 bool KTextEdit::checkSpellingEnabled() const 00739 { 00740 if (d->spellInterface) 00741 return d->spellInterface->isSpellCheckingEnabled(); 00742 else 00743 return checkSpellingEnabledInternal(); 00744 } 00745 00746 bool KTextEdit::checkSpellingEnabledInternal() const 00747 { 00748 return d->checkSpellingEnabled; 00749 } 00750 00751 void KTextEdit::setReadOnly( bool readOnly ) 00752 { 00753 if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter ) 00754 createHighlighter(); 00755 00756 if ( readOnly == isReadOnly() ) 00757 return; 00758 00759 if ( readOnly ) { 00760 delete d->highlighter; 00761 d->highlighter = 0; 00762 00763 d->customPalette = testAttribute( Qt::WA_SetPalette ); 00764 QPalette p = palette(); 00765 QColor color = p.color( QPalette::Disabled, QPalette::Background ); 00766 p.setColor( QPalette::Base, color ); 00767 p.setColor( QPalette::Background, color ); 00768 setPalette( p ); 00769 } else { 00770 if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) { 00771 QPalette p = palette(); 00772 QColor color = p.color( QPalette::Normal, QPalette::Base ); 00773 p.setColor( QPalette::Base, color ); 00774 p.setColor( QPalette::Background, color ); 00775 setPalette( p ); 00776 } else 00777 setPalette( QPalette() ); 00778 } 00779 00780 QTextEdit::setReadOnly( readOnly ); 00781 } 00782 00783 void KTextEdit::checkSpelling() 00784 { 00785 if(document()->isEmpty()) 00786 { 00787 KMessageBox::information(this, i18n("Nothing to spell check.")); 00788 return; 00789 } 00790 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker(this); 00791 if(!d->spellCheckingLanguage.isEmpty()) 00792 backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage); 00793 Sonnet::Dialog *spellDialog = new Sonnet::Dialog( 00794 backgroundSpellCheck, 0); 00795 connect(spellDialog, SIGNAL(replace( const QString&, int,const QString&)), 00796 this, SLOT(spellCheckerCorrected( const QString&, int,const QString&))); 00797 connect(spellDialog, SIGNAL(misspelling( const QString&, int)), 00798 this, SLOT(spellCheckerMisspelling(const QString &,int))); 00799 connect(spellDialog, SIGNAL(autoCorrect(const QString&, const QString&)), 00800 this, SLOT(spellCheckerAutoCorrect(const QString&, const QString&))); 00801 connect(spellDialog, SIGNAL(done(const QString&)), 00802 this, SLOT(spellCheckerFinished())); 00803 connect(spellDialog, SIGNAL(cancel()), 00804 this, SLOT(spellCheckerCanceled())); 00805 connect(spellDialog, SIGNAL(stop()), 00806 this, SLOT(spellCheckerFinished())); 00807 connect(spellDialog, SIGNAL(spellCheckStatus(const QString &)), 00808 this,SIGNAL(spellCheckStatus(const QString &))); 00809 connect(spellDialog, SIGNAL(languageChanged(const QString &)), 00810 this, SIGNAL(languageChanged(const QString &))); 00811 d->originalDoc = QTextDocumentFragment(document()); 00812 spellDialog->setBuffer(toPlainText()); 00813 spellDialog->show(); 00814 } 00815 00816 void KTextEdit::highlightWord( int length, int pos ) 00817 { 00818 QTextCursor cursor(document()); 00819 cursor.setPosition(pos); 00820 cursor.setPosition(pos+length,QTextCursor::KeepAnchor); 00821 setTextCursor (cursor); 00822 ensureCursorVisible(); 00823 } 00824 00825 void KTextEdit::replace() 00826 { 00827 if( document()->isEmpty() ) // saves having to track the text changes 00828 return; 00829 00830 if ( d->repDlg ) { 00831 KWindowSystem::activateWindow( d->repDlg->winId() ); 00832 } else { 00833 d->repDlg = new KReplaceDialog(this, 0, 00834 QStringList(), QStringList(), false); 00835 connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); 00836 } 00837 d->repDlg->show(); 00838 } 00839 00840 void KTextEdit::slotDoReplace() 00841 { 00842 if (!d->repDlg) { 00843 // Should really assert() 00844 return; 00845 } 00846 00847 if(d->repDlg->pattern().isEmpty()) { 00848 delete d->replace; 00849 d->replace = 0; 00850 ensureCursorVisible(); 00851 return; 00852 } 00853 00854 delete d->replace; 00855 d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this); 00856 d->repIndex = 0; 00857 if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) { 00858 d->repIndex = textCursor().anchor(); 00859 } 00860 00861 // Connect highlight signal to code which handles highlighting 00862 // of found text. 00863 connect(d->replace, SIGNAL(highlight(const QString &, int, int)), 00864 this, SLOT(slotFindHighlight(const QString &, int, int))); 00865 connect(d->replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext())); 00866 connect(d->replace, SIGNAL(replace(const QString &, int, int, int)), 00867 this, SLOT(slotReplaceText(const QString &, int, int, int))); 00868 00869 d->repDlg->close(); 00870 slotReplaceNext(); 00871 } 00872 00873 00874 void KTextEdit::slotReplaceNext() 00875 { 00876 if (!d->replace) 00877 return; 00878 00879 d->lastReplacedPosition = -1; 00880 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { 00881 textCursor().beginEditBlock(); // #48541 00882 viewport()->setUpdatesEnabled(false); 00883 } 00884 00885 KFind::Result res = KFind::NoMatch; 00886 00887 if (d->replace->needData()) 00888 d->replace->setData(toPlainText(), d->repIndex); 00889 res = d->replace->replace(); 00890 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { 00891 textCursor().endEditBlock(); // #48541 00892 if (d->lastReplacedPosition >= 0) { 00893 QTextCursor tc = textCursor(); 00894 tc.setPosition(d->lastReplacedPosition); 00895 setTextCursor(tc); 00896 ensureCursorVisible(); 00897 } 00898 00899 viewport()->setUpdatesEnabled(true); 00900 viewport()->update(); 00901 } 00902 00903 if (res == KFind::NoMatch) { 00904 d->replace->displayFinalDialog(); 00905 d->replace->disconnect(this); 00906 d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away 00907 d->replace = 0; 00908 ensureCursorVisible(); 00909 //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } 00910 } else { 00911 //m_replace->closeReplaceNextDialog(); 00912 } 00913 } 00914 00915 00916 void KTextEdit::slotDoFind() 00917 { 00918 if (!d->findDlg) { 00919 // Should really assert() 00920 return; 00921 } 00922 if( d->findDlg->pattern().isEmpty()) 00923 { 00924 delete d->find; 00925 d->find = 0; 00926 return; 00927 } 00928 delete d->find; 00929 d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this); 00930 d->findIndex = 0; 00931 if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) { 00932 d->findIndex = textCursor().anchor(); 00933 } 00934 00935 // Connect highlight signal to code which handles highlighting 00936 // of found text. 00937 connect(d->find, SIGNAL(highlight(const QString &, int, int)), 00938 this, SLOT(slotFindHighlight(const QString &, int, int))); 00939 connect(d->find, SIGNAL(findNext()), this, SLOT(slotFindNext())); 00940 00941 d->findDlg->close(); 00942 d->find->closeFindNextDialog(); 00943 slotFindNext(); 00944 } 00945 00946 00947 void KTextEdit::slotFindNext() 00948 { 00949 if (!d->find) 00950 return; 00951 if(document()->isEmpty()) 00952 { 00953 d->find->disconnect(this); 00954 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away 00955 d->find = 0; 00956 return; 00957 } 00958 00959 KFind::Result res = KFind::NoMatch; 00960 if (d->find->needData()) 00961 d->find->setData(toPlainText(), d->findIndex); 00962 res = d->find->find(); 00963 00964 if (res == KFind::NoMatch) { 00965 d->find->displayFinalDialog(); 00966 d->find->disconnect(this); 00967 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away 00968 d->find = 0; 00969 //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } 00970 } else { 00971 //m_find->closeFindNextDialog(); 00972 } 00973 } 00974 00975 00976 void KTextEdit::slotFind() 00977 { 00978 if( document()->isEmpty() ) // saves having to track the text changes 00979 return; 00980 00981 if ( d->findDlg ) { 00982 KWindowSystem::activateWindow( d->findDlg->winId() ); 00983 } else { 00984 d->findDlg = new KFindDialog(this); 00985 connect( d->findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) ); 00986 } 00987 d->findDlg->show(); 00988 } 00989 00990 00991 void KTextEdit::slotReplace() 00992 { 00993 if( document()->isEmpty() ) // saves having to track the text changes 00994 return; 00995 00996 if ( d->repDlg ) { 00997 KWindowSystem::activateWindow( d->repDlg->winId() ); 00998 } else { 00999 d->repDlg = new KReplaceDialog(this, 0, 01000 QStringList(), QStringList(), false); 01001 connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); 01002 } 01003 d->repDlg->show(); 01004 } 01005 01006 void KTextEdit::enableFindReplace( bool enabled ) 01007 { 01008 d->findReplaceEnabled = enabled; 01009 } 01010 01011 void KTextEdit::setSpellInterface(KTextEditSpellInterface *spellInterface) 01012 { 01013 d->spellInterface = spellInterface; 01014 } 01015 01016 bool KTextEdit::Private::overrideShortcut(const QKeyEvent* event) 01017 { 01018 const int key = event->key() | event->modifiers(); 01019 01020 if ( KStandardShortcut::copy().contains( key ) ) { 01021 return true; 01022 } else if ( KStandardShortcut::paste().contains( key ) ) { 01023 return true; 01024 } else if ( KStandardShortcut::cut().contains( key ) ) { 01025 return true; 01026 } else if ( KStandardShortcut::undo().contains( key ) ) { 01027 return true; 01028 } else if ( KStandardShortcut::redo().contains( key ) ) { 01029 return true; 01030 } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { 01031 return true; 01032 } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { 01033 return true; 01034 } else if ( KStandardShortcut::backwardWord().contains( key ) ) { 01035 return true; 01036 } else if ( KStandardShortcut::forwardWord().contains( key ) ) { 01037 return true; 01038 } else if ( KStandardShortcut::next().contains( key ) ) { 01039 return true; 01040 } else if ( KStandardShortcut::prior().contains( key ) ) { 01041 return true; 01042 } else if ( KStandardShortcut::begin().contains( key ) ) { 01043 return true; 01044 } else if ( KStandardShortcut::end().contains( key ) ) { 01045 return true; 01046 } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { 01047 return true; 01048 } else if ( KStandardShortcut::endOfLine().contains( key ) ) { 01049 return true; 01050 } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { 01051 return true; 01052 } else if (KStandardShortcut::find().contains(key)) { 01053 return true; 01054 } else if (KStandardShortcut::findNext().contains(key)) { 01055 return true; 01056 } else if (KStandardShortcut::replace().contains(key)) { 01057 return true; 01058 } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit 01059 return true; 01060 } else if (event->modifiers() == Qt::ControlModifier && 01061 (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && 01062 qobject_cast<KDialog*>(parent->window()) ) { 01063 // ignore Ctrl-Return so that KDialogs can close the dialog 01064 return true; 01065 } 01066 return false; 01067 } 01068 01069 void KTextEdit::keyPressEvent( QKeyEvent *event ) 01070 { 01071 if (d->handleShortcut(event)) { 01072 event->accept(); 01073 }else if (event->modifiers() == Qt::ControlModifier && 01074 (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && 01075 qobject_cast<KDialog*>(window()) ) { 01076 event->ignore(); 01077 } else { 01078 QTextEdit::keyPressEvent(event); 01079 } 01080 } 01081 01082 void KTextEdit::setClickMessage(const QString &msg) 01083 { 01084 if (msg != d->clickMessage) { 01085 if (!d->clickMessage.isEmpty()) { 01086 d->updateClickMessageRect(); 01087 } 01088 d->clickMessage = msg; 01089 if (!d->clickMessage.isEmpty()) { 01090 d->updateClickMessageRect(); 01091 } 01092 } 01093 } 01094 01095 QString KTextEdit::clickMessage() const 01096 { 01097 return d->clickMessage; 01098 } 01099 01100 void KTextEdit::paintEvent(QPaintEvent *ev) 01101 { 01102 QTextEdit::paintEvent(ev); 01103 01104 if (!d->clickMessage.isEmpty() && !hasFocus() && document()->isEmpty()) { 01105 QPainter p(viewport()); 01106 01107 QFont f = font(); 01108 f.setItalic(d->italicizePlaceholder); 01109 p.setFont(f); 01110 01111 QColor color(palette().color(foregroundRole())); 01112 color.setAlphaF(0.5); 01113 p.setPen(color); 01114 01115 int margin = int(document()->documentMargin()); 01116 QRect cr = viewport()->rect().adjusted(margin, margin, -margin, -margin); 01117 01118 p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage); 01119 } 01120 } 01121 01122 void KTextEdit::focusOutEvent(QFocusEvent *ev) 01123 { 01124 if (!d->clickMessage.isEmpty()) { 01125 d->updateClickMessageRect(); 01126 } 01127 QTextEdit::focusOutEvent(ev); 01128 } 01129 01130 #include "ktextedit.moc"
KDE 4.6 API Reference