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(QString)), 00261 parent, SLOT(setSpellCheckingLanguage(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 if (!windowIcon.isEmpty()) 00314 dialog.setWindowIcon(KIcon(windowIcon)); 00315 if(dialog.exec()) { 00316 setSpellCheckingLanguage( dialog.language() ); 00317 } 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 (findReplaceEnabled && KStandardShortcut::find().contains(key)) { 00426 parent->slotFind(); 00427 return true; 00428 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { 00429 parent->slotFindNext(); 00430 return true; 00431 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { 00432 if (!parent->isReadOnly()) 00433 parent->slotReplace(); 00434 return true; 00435 } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { 00436 QString text = QApplication::clipboard()->text( QClipboard::Selection ); 00437 if ( !text.isEmpty() ) 00438 parent->insertPlainText( text ); // TODO: check if this is html? (MiB) 00439 return true; 00440 } 00441 return false; 00442 } 00443 00444 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op) 00445 { 00446 cursor.clearSelection(); 00447 cursor.movePosition( op, QTextCursor::KeepAnchor ); 00448 cursor.removeSelectedText(); 00449 } 00450 00451 void KTextEdit::deleteWordBack() 00452 { 00453 deleteWord(textCursor(), QTextCursor::PreviousWord); 00454 } 00455 00456 void KTextEdit::deleteWordForward() 00457 { 00458 deleteWord(textCursor(), QTextCursor::WordRight); 00459 } 00460 00461 QMenu *KTextEdit::mousePopupMenu() 00462 { 00463 QMenu *popup = createStandardContextMenu(); 00464 if (!popup) return 0; 00465 connect( popup, SIGNAL(triggered(QAction*)), 00466 this, SLOT(menuActivated(QAction*)) ); 00467 00468 const bool emptyDocument = document()->isEmpty(); 00469 if( !isReadOnly() ) 00470 { 00471 QList<QAction *> actionList = popup->actions(); 00472 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; 00473 QAction *separatorAction = 0L; 00474 int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1; 00475 if ( idx < actionList.count() ) 00476 separatorAction = actionList.at( idx ); 00477 if ( separatorAction ) 00478 { 00479 KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup); 00480 if ( emptyDocument ) 00481 clearAllAction->setEnabled( false ); 00482 popup->insertAction( separatorAction, clearAllAction ); 00483 } 00484 } 00485 KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText 00486 : KIconTheme::TextEditor, 00487 popup->actions() ); 00488 00489 if( !isReadOnly() ) 00490 { 00491 popup->addSeparator(); 00492 d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ), 00493 i18n( "Check Spelling..." ) ); 00494 if ( emptyDocument ) 00495 d->spellCheckAction->setEnabled( false ); 00496 d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) ); 00497 d->autoSpellCheckAction->setCheckable( true ); 00498 d->autoSpellCheckAction->setChecked( checkSpellingEnabled() ); 00499 popup->addSeparator(); 00500 d->allowTab = popup->addAction( i18n("Allow Tabulations") ); 00501 d->allowTab->setCheckable( true ); 00502 d->allowTab->setChecked( !tabChangesFocus() ); 00503 } 00504 00505 if (d->findReplaceEnabled) { 00506 KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup); 00507 KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup); 00508 if (emptyDocument) { 00509 findAction->setEnabled(false); 00510 findNextAction->setEnabled(false); 00511 } else { 00512 findNextAction->setEnabled(d->find != 0); 00513 } 00514 popup->addSeparator(); 00515 popup->addAction(findAction); 00516 popup->addAction(findNextAction); 00517 00518 if (!isReadOnly()) { 00519 KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup); 00520 if (emptyDocument) { 00521 replaceAction->setEnabled(false); 00522 } 00523 popup->addAction(replaceAction); 00524 } 00525 } 00526 popup->addSeparator(); 00527 QAction *speakAction = popup->addAction(i18n("Speak Text")); 00528 speakAction->setIcon(KIcon("preferences-desktop-text-to-speech")); 00529 speakAction->setEnabled(!emptyDocument ); 00530 connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) ); 00531 return popup; 00532 } 00533 00534 void KTextEdit::slotSpeakText() 00535 { 00536 // If KTTSD not running, start it. 00537 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")) 00538 { 00539 QString error; 00540 if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error)) 00541 { 00542 KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error ); 00543 return; 00544 } 00545 } 00546 QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech"); 00547 QString text; 00548 if(textCursor().hasSelection()) 00549 text = textCursor().selectedText(); 00550 else 00551 text = toPlainText(); 00552 ktts.asyncCall("say", text, 0); 00553 } 00554 00555 void KTextEdit::contextMenuEvent(QContextMenuEvent *event) 00556 { 00557 // Obtain the cursor at the mouse position and the current cursor 00558 QTextCursor cursorAtMouse = cursorForPosition(event->pos()); 00559 const int mousePos = cursorAtMouse.position(); 00560 QTextCursor cursor = textCursor(); 00561 00562 // Check if the user clicked a selected word 00563 const bool selectedWordClicked = cursor.hasSelection() && 00564 mousePos >= cursor.selectionStart() && 00565 mousePos <= cursor.selectionEnd(); 00566 00567 // Get the word under the (mouse-)cursor and see if it is misspelled. 00568 // Don't include apostrophes at the start/end of the word in the selection. 00569 QTextCursor wordSelectCursor(cursorAtMouse); 00570 wordSelectCursor.clearSelection(); 00571 wordSelectCursor.select(QTextCursor::WordUnderCursor); 00572 QString selectedWord = wordSelectCursor.selectedText(); 00573 00574 bool isMouseCursorInsideWord = true; 00575 if ((mousePos < wordSelectCursor.selectionStart() || 00576 mousePos >= wordSelectCursor.selectionEnd()) 00577 && (selectedWord.length() > 1)) { 00578 isMouseCursorInsideWord = false; 00579 } 00580 00581 // Clear the selection again, we re-select it below (without the apostrophes). 00582 wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size()); 00583 if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) { 00584 selectedWord = selectedWord.right(selectedWord.size() - 1); 00585 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor); 00586 } 00587 if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"')) 00588 selectedWord.chop(1); 00589 00590 wordSelectCursor.movePosition(QTextCursor::NextCharacter, 00591 QTextCursor::KeepAnchor, selectedWord.size()); 00592 00593 const bool wordIsMisspelled = isMouseCursorInsideWord && 00594 checkSpellingEnabled() && 00595 !selectedWord.isEmpty() && 00596 highlighter() && 00597 highlighter()->isWordMisspelled(selectedWord); 00598 00599 // If the user clicked a selected word, do nothing. 00600 // If the user clicked somewhere else, move the cursor there. 00601 // If the user clicked on a misspelled word, select that word. 00602 // Same behavior as in OpenOffice Writer. 00603 bool inQuote = false; 00604 if (d->spellInterface && 00605 !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text())) 00606 inQuote = true; 00607 if (!selectedWordClicked) { 00608 if (wordIsMisspelled && !inQuote) 00609 setTextCursor(wordSelectCursor); 00610 else 00611 setTextCursor(cursorAtMouse); 00612 cursor = textCursor(); 00613 } 00614 00615 // Use standard context menu for already selected words, correctly spelled 00616 // words and words inside quotes. 00617 if (!wordIsMisspelled || selectedWordClicked || inQuote) { 00618 QMenu *popup = mousePopupMenu(); 00619 if ( popup ) { 00620 aboutToShowContextMenu(popup); 00621 popup->exec( event->globalPos() ); 00622 delete popup; 00623 } 00624 } 00625 else { 00626 QMenu menu; //don't use KMenu here we don't want auto management accelerator 00627 00628 //Add the suggestions to the menu 00629 const QStringList reps = highlighter()->suggestionsForWord(selectedWord); 00630 if (reps.isEmpty()) { 00631 QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord)); 00632 suggestionsAction->setEnabled(false); 00633 } 00634 else { 00635 for (QStringList::const_iterator it = reps.constBegin(); it != reps.constEnd(); ++it) { 00636 menu.addAction(*it); 00637 } 00638 } 00639 00640 menu.addSeparator(); 00641 00642 QAction *ignoreAction = menu.addAction(i18n("Ignore")); 00643 QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary")); 00644 //Execute the popup inline 00645 const QAction *selectedAction = menu.exec(event->globalPos()); 00646 00647 if (selectedAction) { 00648 Q_ASSERT(cursor.selectedText() == selectedWord); 00649 00650 if (selectedAction == ignoreAction) { 00651 highlighter()->ignoreWord(selectedWord); 00652 highlighter()->rehighlight(); 00653 } 00654 else if (selectedAction == addToDictAction) { 00655 highlighter()->addWordToDictionary(selectedWord); 00656 highlighter()->rehighlight(); 00657 } 00658 00659 // Other actions can only be one of the suggested words 00660 else { 00661 const QString replacement = selectedAction->text(); 00662 Q_ASSERT(reps.contains(replacement)); 00663 cursor.insertText(replacement); 00664 setTextCursor(cursor); 00665 } 00666 } 00667 } 00668 } 00669 00670 void KTextEdit::wheelEvent( QWheelEvent *event ) 00671 { 00672 if ( KGlobalSettings::wheelMouseZooms() ) 00673 QTextEdit::wheelEvent( event ); 00674 else // thanks, we don't want to zoom, so skip QTextEdit's impl. 00675 QAbstractScrollArea::wheelEvent( event ); 00676 } 00677 00678 void KTextEdit::createHighlighter() 00679 { 00680 setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName)); 00681 } 00682 00683 Sonnet::Highlighter* KTextEdit::highlighter() const 00684 { 00685 return d->highlighter; 00686 } 00687 00688 void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter) 00689 { 00690 delete d->highlighter; 00691 d->highlighter = _highLighter; 00692 } 00693 00694 void KTextEdit::setCheckSpellingEnabled(bool check) 00695 { 00696 if (d->spellInterface) 00697 d->spellInterface->setSpellCheckingEnabled(check); 00698 else 00699 setCheckSpellingEnabledInternal(check); 00700 } 00701 00702 void KTextEdit::setCheckSpellingEnabledInternal( bool check ) 00703 { 00704 emit checkSpellingChanged( check ); 00705 if ( check == d->checkSpellingEnabled ) 00706 return; 00707 00708 // From the above statment we know know that if we're turning checking 00709 // on that we need to create a new highlighter and if we're turning it 00710 // off we should remove the old one. 00711 00712 d->checkSpellingEnabled = check; 00713 if ( check ) 00714 { 00715 if ( hasFocus() ) { 00716 createHighlighter(); 00717 if (!spellCheckingLanguage().isEmpty()) 00718 setSpellCheckingLanguage(spellCheckingLanguage()); 00719 } 00720 } 00721 else 00722 { 00723 delete d->highlighter; 00724 d->highlighter = 0; 00725 } 00726 } 00727 00728 void KTextEdit::focusInEvent( QFocusEvent *event ) 00729 { 00730 if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter ) 00731 createHighlighter(); 00732 00733 if (!d->clickMessage.isEmpty()) { 00734 d->updateClickMessageRect(); 00735 } 00736 QTextEdit::focusInEvent( event ); 00737 } 00738 00739 bool KTextEdit::checkSpellingEnabled() const 00740 { 00741 if (d->spellInterface) 00742 return d->spellInterface->isSpellCheckingEnabled(); 00743 else 00744 return checkSpellingEnabledInternal(); 00745 } 00746 00747 bool KTextEdit::checkSpellingEnabledInternal() const 00748 { 00749 return d->checkSpellingEnabled; 00750 } 00751 00752 void KTextEdit::setReadOnly( bool readOnly ) 00753 { 00754 if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter ) 00755 createHighlighter(); 00756 00757 if ( readOnly == isReadOnly() ) 00758 return; 00759 00760 if ( readOnly ) { 00761 delete d->highlighter; 00762 d->highlighter = 0; 00763 00764 d->customPalette = testAttribute( Qt::WA_SetPalette ); 00765 QPalette p = palette(); 00766 QColor color = p.color( QPalette::Disabled, QPalette::Background ); 00767 p.setColor( QPalette::Base, color ); 00768 p.setColor( QPalette::Background, color ); 00769 setPalette( p ); 00770 } else { 00771 if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) { 00772 QPalette p = palette(); 00773 QColor color = p.color( QPalette::Normal, QPalette::Base ); 00774 p.setColor( QPalette::Base, color ); 00775 p.setColor( QPalette::Background, color ); 00776 setPalette( p ); 00777 } else 00778 setPalette( QPalette() ); 00779 } 00780 00781 QTextEdit::setReadOnly( readOnly ); 00782 } 00783 00784 void KTextEdit::checkSpelling() 00785 { 00786 if(document()->isEmpty()) 00787 { 00788 KMessageBox::information(this, i18n("Nothing to spell check.")); 00789 return; 00790 } 00791 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker(this); 00792 if(!d->spellCheckingLanguage.isEmpty()) 00793 backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage); 00794 Sonnet::Dialog *spellDialog = new Sonnet::Dialog( 00795 backgroundSpellCheck, 0); 00796 connect(spellDialog, SIGNAL(replace(QString,int,QString)), 00797 this, SLOT(spellCheckerCorrected(QString,int,QString))); 00798 connect(spellDialog, SIGNAL(misspelling(QString,int)), 00799 this, SLOT(spellCheckerMisspelling(QString,int))); 00800 connect(spellDialog, SIGNAL(autoCorrect(QString,QString)), 00801 this, SLOT(spellCheckerAutoCorrect(QString,QString))); 00802 connect(spellDialog, SIGNAL(done(QString)), 00803 this, SLOT(spellCheckerFinished())); 00804 connect(spellDialog, SIGNAL(cancel()), 00805 this, SLOT(spellCheckerCanceled())); 00806 connect(spellDialog, SIGNAL(stop()), 00807 this, SLOT(spellCheckerFinished())); 00808 connect(spellDialog, SIGNAL(spellCheckStatus(QString)), 00809 this,SIGNAL(spellCheckStatus(QString))); 00810 connect(spellDialog, SIGNAL(languageChanged(QString)), 00811 this, SIGNAL(languageChanged(QString))); 00812 d->originalDoc = QTextDocumentFragment(document()); 00813 spellDialog->setBuffer(toPlainText()); 00814 spellDialog->show(); 00815 } 00816 00817 void KTextEdit::highlightWord( int length, int pos ) 00818 { 00819 QTextCursor cursor(document()); 00820 cursor.setPosition(pos); 00821 cursor.setPosition(pos+length,QTextCursor::KeepAnchor); 00822 setTextCursor (cursor); 00823 ensureCursorVisible(); 00824 } 00825 00826 void KTextEdit::replace() 00827 { 00828 if( document()->isEmpty() ) // saves having to track the text changes 00829 return; 00830 00831 if ( d->repDlg ) { 00832 KWindowSystem::activateWindow( d->repDlg->winId() ); 00833 } else { 00834 d->repDlg = new KReplaceDialog(this, 0, 00835 QStringList(), QStringList(), false); 00836 connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); 00837 } 00838 d->repDlg->show(); 00839 } 00840 00841 void KTextEdit::slotDoReplace() 00842 { 00843 if (!d->repDlg) { 00844 // Should really assert() 00845 return; 00846 } 00847 00848 if(d->repDlg->pattern().isEmpty()) { 00849 delete d->replace; 00850 d->replace = 0; 00851 ensureCursorVisible(); 00852 return; 00853 } 00854 00855 delete d->replace; 00856 d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this); 00857 d->repIndex = 0; 00858 if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) { 00859 d->repIndex = textCursor().anchor(); 00860 } 00861 00862 // Connect highlight signal to code which handles highlighting 00863 // of found text. 00864 connect(d->replace, SIGNAL(highlight(QString,int,int)), 00865 this, SLOT(slotFindHighlight(QString,int,int))); 00866 connect(d->replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext())); 00867 connect(d->replace, SIGNAL(replace(QString,int,int,int)), 00868 this, SLOT(slotReplaceText(QString,int,int,int))); 00869 00870 d->repDlg->close(); 00871 slotReplaceNext(); 00872 } 00873 00874 00875 void KTextEdit::slotReplaceNext() 00876 { 00877 if (!d->replace) 00878 return; 00879 00880 d->lastReplacedPosition = -1; 00881 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { 00882 textCursor().beginEditBlock(); // #48541 00883 viewport()->setUpdatesEnabled(false); 00884 } 00885 00886 KFind::Result res = KFind::NoMatch; 00887 00888 if (d->replace->needData()) 00889 d->replace->setData(toPlainText(), d->repIndex); 00890 res = d->replace->replace(); 00891 if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { 00892 textCursor().endEditBlock(); // #48541 00893 if (d->lastReplacedPosition >= 0) { 00894 QTextCursor tc = textCursor(); 00895 tc.setPosition(d->lastReplacedPosition); 00896 setTextCursor(tc); 00897 ensureCursorVisible(); 00898 } 00899 00900 viewport()->setUpdatesEnabled(true); 00901 viewport()->update(); 00902 } 00903 00904 if (res == KFind::NoMatch) { 00905 d->replace->displayFinalDialog(); 00906 d->replace->disconnect(this); 00907 d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away 00908 d->replace = 0; 00909 ensureCursorVisible(); 00910 //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } 00911 } else { 00912 //m_replace->closeReplaceNextDialog(); 00913 } 00914 } 00915 00916 00917 void KTextEdit::slotDoFind() 00918 { 00919 if (!d->findDlg) { 00920 // Should really assert() 00921 return; 00922 } 00923 if( d->findDlg->pattern().isEmpty()) 00924 { 00925 delete d->find; 00926 d->find = 0; 00927 return; 00928 } 00929 delete d->find; 00930 d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this); 00931 d->findIndex = 0; 00932 if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) { 00933 d->findIndex = textCursor().anchor(); 00934 } 00935 00936 // Connect highlight signal to code which handles highlighting 00937 // of found text. 00938 connect(d->find, SIGNAL(highlight(QString,int,int)), 00939 this, SLOT(slotFindHighlight(QString,int,int))); 00940 connect(d->find, SIGNAL(findNext()), this, SLOT(slotFindNext())); 00941 00942 d->findDlg->close(); 00943 d->find->closeFindNextDialog(); 00944 slotFindNext(); 00945 } 00946 00947 00948 void KTextEdit::slotFindNext() 00949 { 00950 if (!d->find) 00951 return; 00952 if(document()->isEmpty()) 00953 { 00954 d->find->disconnect(this); 00955 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away 00956 d->find = 0; 00957 return; 00958 } 00959 00960 KFind::Result res = KFind::NoMatch; 00961 if (d->find->needData()) 00962 d->find->setData(toPlainText(), d->findIndex); 00963 res = d->find->find(); 00964 00965 if (res == KFind::NoMatch) { 00966 d->find->displayFinalDialog(); 00967 d->find->disconnect(this); 00968 d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away 00969 d->find = 0; 00970 //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } 00971 } else { 00972 //m_find->closeFindNextDialog(); 00973 } 00974 } 00975 00976 00977 void KTextEdit::slotFind() 00978 { 00979 if( document()->isEmpty() ) // saves having to track the text changes 00980 return; 00981 00982 if ( d->findDlg ) { 00983 KWindowSystem::activateWindow( d->findDlg->winId() ); 00984 } else { 00985 d->findDlg = new KFindDialog(this); 00986 connect( d->findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) ); 00987 } 00988 d->findDlg->show(); 00989 } 00990 00991 00992 void KTextEdit::slotReplace() 00993 { 00994 if( document()->isEmpty() ) // saves having to track the text changes 00995 return; 00996 00997 if ( d->repDlg ) { 00998 KWindowSystem::activateWindow( d->repDlg->winId() ); 00999 } else { 01000 d->repDlg = new KReplaceDialog(this, 0, 01001 QStringList(), QStringList(), false); 01002 connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); 01003 } 01004 d->repDlg->show(); 01005 } 01006 01007 void KTextEdit::enableFindReplace( bool enabled ) 01008 { 01009 d->findReplaceEnabled = enabled; 01010 } 01011 01012 void KTextEdit::setSpellInterface(KTextEditSpellInterface *spellInterface) 01013 { 01014 d->spellInterface = spellInterface; 01015 } 01016 01017 bool KTextEdit::Private::overrideShortcut(const QKeyEvent* event) 01018 { 01019 const int key = event->key() | event->modifiers(); 01020 01021 if ( KStandardShortcut::copy().contains( key ) ) { 01022 return true; 01023 } else if ( KStandardShortcut::paste().contains( key ) ) { 01024 return true; 01025 } else if ( KStandardShortcut::cut().contains( key ) ) { 01026 return true; 01027 } else if ( KStandardShortcut::undo().contains( key ) ) { 01028 return true; 01029 } else if ( KStandardShortcut::redo().contains( key ) ) { 01030 return true; 01031 } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { 01032 return true; 01033 } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { 01034 return true; 01035 } else if ( KStandardShortcut::backwardWord().contains( key ) ) { 01036 return true; 01037 } else if ( KStandardShortcut::forwardWord().contains( key ) ) { 01038 return true; 01039 } else if ( KStandardShortcut::next().contains( key ) ) { 01040 return true; 01041 } else if ( KStandardShortcut::prior().contains( key ) ) { 01042 return true; 01043 } else if ( KStandardShortcut::begin().contains( key ) ) { 01044 return true; 01045 } else if ( KStandardShortcut::end().contains( key ) ) { 01046 return true; 01047 } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { 01048 return true; 01049 } else if ( KStandardShortcut::endOfLine().contains( key ) ) { 01050 return true; 01051 } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { 01052 return true; 01053 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { 01054 return true; 01055 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { 01056 return true; 01057 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { 01058 return true; 01059 } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit 01060 return true; 01061 } else if (event->modifiers() == Qt::ControlModifier && 01062 (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && 01063 qobject_cast<KDialog*>(parent->window()) ) { 01064 // ignore Ctrl-Return so that KDialogs can close the dialog 01065 return true; 01066 } 01067 return false; 01068 } 01069 01070 void KTextEdit::keyPressEvent( QKeyEvent *event ) 01071 { 01072 if (d->handleShortcut(event)) { 01073 event->accept(); 01074 }else if (event->modifiers() == Qt::ControlModifier && 01075 (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && 01076 qobject_cast<KDialog*>(window()) ) { 01077 event->ignore(); 01078 } else { 01079 QTextEdit::keyPressEvent(event); 01080 } 01081 } 01082 01083 void KTextEdit::setClickMessage(const QString &msg) 01084 { 01085 if (msg != d->clickMessage) { 01086 if (!d->clickMessage.isEmpty()) { 01087 d->updateClickMessageRect(); 01088 } 01089 d->clickMessage = msg; 01090 if (!d->clickMessage.isEmpty()) { 01091 d->updateClickMessageRect(); 01092 } 01093 } 01094 } 01095 01096 QString KTextEdit::clickMessage() const 01097 { 01098 return d->clickMessage; 01099 } 01100 01101 void KTextEdit::paintEvent(QPaintEvent *ev) 01102 { 01103 QTextEdit::paintEvent(ev); 01104 01105 if (!d->clickMessage.isEmpty() && !hasFocus() && document()->isEmpty()) { 01106 QPainter p(viewport()); 01107 01108 QFont f = font(); 01109 f.setItalic(d->italicizePlaceholder); 01110 p.setFont(f); 01111 01112 QColor color(palette().color(foregroundRole())); 01113 color.setAlphaF(0.5); 01114 p.setPen(color); 01115 01116 int margin = int(document()->documentMargin()); 01117 QRect cr = viewport()->rect().adjusted(margin, margin, -margin, -margin); 01118 01119 p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage); 01120 } 01121 } 01122 01123 void KTextEdit::focusOutEvent(QFocusEvent *ev) 01124 { 01125 if (!d->clickMessage.isEmpty()) { 01126 d->updateClickMessageRect(); 01127 } 01128 QTextEdit::focusOutEvent(ev); 01129 } 01130 01131 #include "ktextedit.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 17:57:53 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 17:57:53 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.