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 } 00303 00304 void KTextEdit::showSpellConfigDialog(const QString &configFileName, 00305 const QString &windowIcon) 00306 { 00307 KConfig config(configFileName); 00308 Sonnet::ConfigDialog dialog(&config, this); 00309 if (!d->spellCheckingLanguage.isEmpty()) 00310 dialog.setLanguage(d->spellCheckingLanguage); 00311 if (!windowIcon.isEmpty()) 00312 dialog.setWindowIcon(KIcon(windowIcon)); 00313 if(dialog.exec()) { 00314 setSpellCheckingLanguage( dialog.language() ); 00315 } 00316 } 00317 00318 bool KTextEdit::event(QEvent* ev) 00319 { 00320 if (ev->type() == QEvent::ShortcutOverride) { 00321 QKeyEvent *e = static_cast<QKeyEvent *>( ev ); 00322 if (d->overrideShortcut(e)) { 00323 e->accept(); 00324 return true; 00325 } 00326 } 00327 return QTextEdit::event(ev); 00328 } 00329 00330 bool KTextEdit::Private::handleShortcut(const QKeyEvent* event) 00331 { 00332 const int key = event->key() | event->modifiers(); 00333 00334 if ( KStandardShortcut::copy().contains( key ) ) { 00335 parent->copy(); 00336 return true; 00337 } else if ( KStandardShortcut::paste().contains( key ) ) { 00338 parent->paste(); 00339 return true; 00340 } else if ( KStandardShortcut::cut().contains( key ) ) { 00341 parent->cut(); 00342 return true; 00343 } else if ( KStandardShortcut::undo().contains( key ) ) { 00344 if(!parent->isReadOnly()) 00345 parent->undo(); 00346 return true; 00347 } else if ( KStandardShortcut::redo().contains( key ) ) { 00348 if(!parent->isReadOnly()) 00349 parent->redo(); 00350 return true; 00351 } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { 00352 parent->deleteWordBack(); 00353 return true; 00354 } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { 00355 parent->deleteWordForward(); 00356 return true; 00357 } else if ( KStandardShortcut::backwardWord().contains( key ) ) { 00358 QTextCursor cursor = parent->textCursor(); 00359 cursor.movePosition( QTextCursor::PreviousWord ); 00360 parent->setTextCursor( cursor ); 00361 return true; 00362 } else if ( KStandardShortcut::forwardWord().contains( key ) ) { 00363 QTextCursor cursor = parent->textCursor(); 00364 cursor.movePosition( QTextCursor::NextWord ); 00365 parent->setTextCursor( cursor ); 00366 return true; 00367 } else if ( KStandardShortcut::next().contains( key ) ) { 00368 QTextCursor cursor = parent->textCursor(); 00369 bool moved = false; 00370 qreal lastY = parent->cursorRect(cursor).bottom(); 00371 qreal distance = 0; 00372 do { 00373 qreal y = parent->cursorRect(cursor).bottom(); 00374 distance += qAbs(y - lastY); 00375 lastY = y; 00376 moved = cursor.movePosition(QTextCursor::Down); 00377 } while (moved && distance < parent->viewport()->height()); 00378 00379 if (moved) { 00380 cursor.movePosition(QTextCursor::Up); 00381 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); 00382 } 00383 parent->setTextCursor(cursor); 00384 return true; 00385 } else if ( KStandardShortcut::prior().contains( key ) ) { 00386 QTextCursor cursor = parent->textCursor(); 00387 bool moved = false; 00388 qreal lastY = parent->cursorRect(cursor).bottom(); 00389 qreal distance = 0; 00390 do { 00391 qreal y = parent->cursorRect(cursor).bottom(); 00392 distance += qAbs(y - lastY); 00393 lastY = y; 00394 moved = cursor.movePosition(QTextCursor::Up); 00395 } while (moved && distance < parent->viewport()->height()); 00396 00397 if (moved) { 00398 cursor.movePosition(QTextCursor::Down); 00399 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); 00400 } 00401 parent->setTextCursor(cursor); 00402 return true; 00403 } else if ( KStandardShortcut::begin().contains( key ) ) { 00404 QTextCursor cursor = parent->textCursor(); 00405 cursor.movePosition( QTextCursor::Start ); 00406 parent->setTextCursor( cursor ); 00407 return true; 00408 } else if ( KStandardShortcut::end().contains( key ) ) { 00409 QTextCursor cursor = parent->textCursor(); 00410 cursor.movePosition( QTextCursor::End ); 00411 parent->setTextCursor( cursor ); 00412 return true; 00413 } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { 00414 QTextCursor cursor = parent->textCursor(); 00415 cursor.movePosition( QTextCursor::StartOfLine ); 00416 parent->setTextCursor( cursor ); 00417 return true; 00418 } else if ( KStandardShortcut::endOfLine().contains( key ) ) { 00419 QTextCursor cursor = parent->textCursor(); 00420 cursor.movePosition( QTextCursor::EndOfLine ); 00421 parent->setTextCursor( cursor ); 00422 return true; 00423 } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { 00424 parent->slotFind(); 00425 return true; 00426 } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { 00427 parent->slotFindNext(); 00428 return true; 00429 } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { 00430 if (!parent->isReadOnly()) 00431 parent->slotReplace(); 00432 return true; 00433 } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { 00434 QString text = QApplication::clipboard()->text( QClipboard::Selection ); 00435 if ( !text.isEmpty() ) 00436 parent->insertPlainText( text ); // TODO: check if this is html? (MiB) 00437 return true; 00438 } 00439 return false; 00440 } 00441 00442 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op) 00443 { 00444 cursor.clearSelection(); 00445 cursor.movePosition( op, QTextCursor::KeepAnchor ); 00446 cursor.removeSelectedText(); 00447 } 00448 00449 void KTextEdit::deleteWordBack() 00450 { 00451 deleteWord(textCursor(), QTextCursor::PreviousWord); 00452 } 00453 00454 void KTextEdit::deleteWordForward() 00455 { 00456 deleteWord(textCursor(), QTextCursor::WordRight); 00457 } 00458 00459 QMenu *KTextEdit::mousePopupMenu() 00460 { 00461 QMenu *popup = createStandardContextMenu(); 00462 if (!popup) return 0; 00463 connect( popup, SIGNAL(triggered(QAction*)), 00464 this, SLOT(menuActivated(QAction*)) ); 00465 00466 const bool emptyDocument = document()->isEmpty(); 00467 if( !isReadOnly() ) 00468 { 00469 QList<QAction *> actionList = popup->actions(); 00470 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; 00471 QAction *separatorAction = 0L; 00472 int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1; 00473 if ( idx < actionList.count() ) 00474 separatorAction = actionList.at( idx ); 00475 if ( separatorAction ) 00476 { 00477 KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup); 00478 if ( emptyDocument ) 00479 clearAllAction->setEnabled( false ); 00480 popup->insertAction( separatorAction, clearAllAction ); 00481 } 00482 } 00483 KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText 00484 : KIconTheme::TextEditor, 00485 popup->actions() ); 00486 00487 if( !isReadOnly() ) 00488 { 00489 popup->addSeparator(); 00490 d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ), 00491 i18n( "Check Spelling..." ) ); 00492 if ( emptyDocument ) 00493 d->spellCheckAction->setEnabled( false ); 00494 d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) ); 00495 d->autoSpellCheckAction->setCheckable( true ); 00496 d->autoSpellCheckAction->setChecked( checkSpellingEnabled() ); 00497 popup->addSeparator(); 00498 d->allowTab = popup->addAction( i18n("Allow Tabulations") ); 00499 d->allowTab->setCheckable( true ); 00500 d->allowTab->setChecked( !tabChangesFocus() ); 00501 } 00502 00503 if (d->findReplaceEnabled) { 00504 KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup); 00505 KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup); 00506 if (emptyDocument) { 00507 findAction->setEnabled(false); 00508 findNextAction->setEnabled(false); 00509 } else { 00510 findNextAction->setEnabled(d->find != 0); 00511 } 00512 popup->addSeparator(); 00513 popup->addAction(findAction); 00514 popup->addAction(findNextAction); 00515 00516 if (!isReadOnly()) { 00517 KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup); 00518 if (emptyDocument) { 00519 replaceAction->setEnabled(false); 00520 } 00521 popup->addAction(replaceAction); 00522 } 00523 } 00524 popup->addSeparator(); 00525 QAction *speakAction = popup->addAction(i18n("Speak Text")); 00526 speakAction->setIcon(KIcon("preferences-desktop-text-to-speech")); 00527 speakAction->setEnabled(!emptyDocument ); 00528 connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) ); 00529 return popup; 00530 } 00531 00532 void KTextEdit::slotSpeakText() 00533 { 00534 // If KTTSD not running, start it. 00535 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")) 00536 { 00537 QString error; 00538 if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error)) 00539 { 00540 KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error ); 00541 return; 00542 } 00543 } 00544 QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech"); 00545 QString text; 00546 if(textCursor().hasSelection()) 00547 text = textCursor().selectedText(); 00548 else 00549 text = toPlainText(); 00550 ktts.asyncCall("say", text, 0); 00551 } 00552 00553 void KTextEdit::contextMenuEvent(QContextMenuEvent *event) 00554 { 00555 // Obtain the cursor at the mouse position and the current cursor 00556 QTextCursor cursorAtMouse = cursorForPosition(event->pos()); 00557 const int mousePos = cursorAtMouse.position(); 00558 QTextCursor cursor = textCursor(); 00559 00560 // Check if the user clicked a selected word 00561 const bool selectedWordClicked = cursor.hasSelection() && 00562 mousePos >= cursor.selectionStart() && 00563 mousePos <= cursor.selectionEnd(); 00564 00565 // Get the word under the (mouse-)cursor and see if it is misspelled. 00566 // Don't include apostrophes at the start/end of the word in the selection. 00567 QTextCursor wordSelectCursor(cursorAtMouse); 00568 wordSelectCursor.clearSelection(); 00569 wordSelectCursor.select(QTextCursor::WordUnderCursor); 00570 QString selectedWord = wordSelectCursor.selectedText(); 00571 00572 bool isMouseCursorInsideWord = true; 00573 if ((mousePos < wordSelectCursor.selectionStart() || 00574 mousePos >= wordSelectCursor.selectionEnd()) 00575 && (selectedWord.length() > 1)) { 00576 isMouseCursorInsideWord = false; 00577 } 00578 00579 // Clear the selection again, we re-select it below (without the apostrophes). 00580 wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size()); 00581 if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) { 00582 selectedWord = selectedWord.right(selectedWord.size() - 1); 00583 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor); 00584 } 00585 if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"')) 00586 selectedWord.chop(1); 00587 00588 wordSelectCursor.movePosition(QTextCursor::NextCharacter, 00589 QTextCursor::KeepAnchor, selectedWord.size()); 00590 00591 const bool wordIsMisspelled = isMouseCursorInsideWord && 00592 checkSpellingEnabled() && 00593 !selectedWord.isEmpty() && 00594 highlighter() && 00595 highlighter()->isWordMisspelled(selectedWord); 00596 00597 // If the user clicked a selected word, do nothing. 00598 // If the user clicked somewhere else, move the cursor there. 00599 // If the user clicked on a misspelled word, select that word. 00600 // Same behavior as in OpenOffice Writer. 00601 bool inQuote = false; 00602 if (d->spellInterface && 00603 !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text())) 00604 inQuote = true; 00605 if (!selectedWordClicked) { 00606 if (wordIsMisspelled && !inQuote) 00607 setTextCursor(wordSelectCursor); 00608 else 00609 setTextCursor(cursorAtMouse); 00610 cursor = textCursor(); 00611 } 00612 00613 // Use standard context menu for already selected words, correctly spelled 00614 // words and words inside quotes. 00615 if (!wordIsMisspelled || selectedWordClicked || inQuote) { 00616 QMenu *popup = mousePopupMenu(); 00617 if ( popup ) { 00618 aboutToShowContextMenu(popup); 00619 popup->exec( event->globalPos() ); 00620 delete popup; 00621 } 00622 } 00623 else { 00624 QMenu menu; //don't use KMenu here we don't want auto management accelerator 00625 00626 //Add the suggestions to the menu 00627 const QStringList reps = highlighter()->suggestionsForWord(selectedWord); 00628 if (reps.isEmpty()) { 00629 QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord)); 00630 suggestionsAction->setEnabled(false); 00631 } 00632 else { 00633 for (QStringList::const_iterator it = reps.constBegin(); it != reps.constEnd(); ++it) { 00634 menu.addAction(*it); 00635 } 00636 } 00637 00638 menu.addSeparator(); 00639 00640 QAction *ignoreAction = menu.addAction(i18n("Ignore")); 00641 QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary")); 00642 //Execute the popup inline 00643 const QAction *selectedAction = menu.exec(event->globalPos()); 00644 00645 if (selectedAction) { 00646 Q_ASSERT(cursor.selectedText() == selectedWord); 00647 00648 if (selectedAction == ignoreAction) { 00649 highlighter()->ignoreWord(selectedWord); 00650 highlighter()->rehighlight(); 00651 } 00652 else if (selectedAction == addToDictAction) { 00653 highlighter()->addWordToDictionary(selectedWord); 00654 highlighter()->rehighlight(); 00655 } 00656 00657 // Other actions can only be one of the suggested words 00658 else { 00659 const QString replacement = selectedAction->text(); 00660 Q_ASSERT(reps.contains(replacement)); 00661 cursor.insertText(replacement); 00662 setTextCursor(cursor); 00663 } 00664 } 00665 } 00666 } 00667 00668 void KTextEdit::wheelEvent( QWheelEvent *event ) 00669 { 00670 if ( KGlobalSettings::wheelMouseZooms() ) 00671 QTextEdit::wheelEvent( event ); 00672 else // thanks, we don't want to zoom, so skip QTextEdit's impl. 00673 QAbstractScrollArea::wheelEvent( event ); 00674 } 00675 00676 void KTextEdit::createHighlighter() 00677 { 00678 setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName)); 00679 } 00680 00681 Sonnet::Highlighter* KTextEdit::highlighter() const 00682 { 00683 return d->highlighter; 00684 } 00685 00686 void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter) 00687 { 00688 delete d->highlighter; 00689 d->highlighter = _highLighter; 00690 } 00691 00692 void KTextEdit::setCheckSpellingEnabled(bool check) 00693 { 00694 if (d->spellInterface) 00695 d->spellInterface->setSpellCheckingEnabled(check); 00696 else 00697 setCheckSpellingEnabledInternal(check); 00698 } 00699 00700 void KTextEdit::setCheckSpellingEnabledInternal( bool check ) 00701 { 00702 emit checkSpellingChanged( check ); 00703 if ( check == d->checkSpellingEnabled ) 00704 return; 00705 00706 // From the above statment we know know that if we're turning checking 00707 // on that we need to create a new highlighter and if we're turning it 00708 // off we should remove the old one. 00709 00710 d->checkSpellingEnabled = check; 00711 if ( check ) 00712 { 00713 if ( hasFocus() ) { 00714 createHighlighter(); 00715 if (!spellCheckingLanguage().isEmpty()) 00716 setSpellCheckingLanguage(spellCheckingLanguage()); 00717 } 00718 } 00719 else 00720 { 00721 delete d->highlighter; 00722 d->highlighter = 0; 00723 } 00724 } 00725 00726 void KTextEdit::focusInEvent( QFocusEvent *event ) 00727 { 00728 if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter ) 00729 createHighlighter(); 00730 00731 if (!d->clickMessage.isEmpty()) { 00732 d->updateClickMessageRect(); 00733 } 00734 QTextEdit::focusInEvent( event ); 00735 } 00736 00737 bool KTextEdit::checkSpellingEnabled() const 00738 { 00739 if (d->spellInterface) 00740 return d->spellInterface->isSpellCheckingEnabled(); 00741 else 00742 return checkSpellingEnabledInternal(); 00743 } 00744 00745 bool KTextEdit::checkSpellingEnabledInternal() const 00746 { 00747 return d->checkSpellingEnabled; 00748 } 00749 00750 void KTextEdit::setReadOnly( bool readOnly ) 00751 { 00752 if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter ) 00753 createHighlighter(); 00754 00755 if ( readOnly == isReadOnly() ) 00756 return; 00757 00758 if ( readOnly ) { 00759 delete d->highlighter; 00760 d->highlighter = 0; 00761 00762 d->customPalette = testAttribute( Qt::WA_SetPalette ); 00763 QPalette p = palette(); 00764 QColor color = p.color( QPalette::Disabled, QPalette::Background ); 00765 p.setColor( QPalette::Base, color ); 00766 p.setColor( QPalette::Background, color ); 00767 setPalette( p ); 00768 } else { 00769 if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) { 00770 QPalette p = palette(); 00771 QColor color = p.color( QPalette::Normal, QPalette::Base ); 00772 p.setColor( QPalette::Base, color ); 00773 p.setColor( QPalette::Background, color ); 00774 setPalette( p ); 00775 } else 00776 setPalette( QPalette() ); 00777 } 00778 00779 QTextEdit::setReadOnly( readOnly ); 00780 } 00781 00782 void KTextEdit::checkSpelling() 00783 { 00784 if(document()->isEmpty()) 00785 { 00786 KMessageBox::information(this, i18n("Nothing to spell check.")); 00787 return; 00788 } 00789 Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker; 00790 if(!d->spellCheckingLanguage.isEmpty()) 00791 backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage); 00792 Sonnet::Dialog *spellDialog = new Sonnet::Dialog( 00793 backgroundSpellCheck, 0); 00794 backgroundSpellCheck->setParent(spellDialog); 00795 spellDialog->setAttribute(Qt::WA_DeleteOnClose, true); 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(viewport()->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-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:32:44 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:32:44 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.