/* This file is part of the KDE libraries Copyright (C) 2002 Carsten Pfeiffer 2005 Michael Brade 2012 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktextedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundchecker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KTextEdit::Private { public: Private( KTextEdit *_parent ) : parent( _parent ), customPalette( false ), checkSpellingEnabled( false ), findReplaceEnabled(true), showTabAction(true), showAutoCorrectionButton(false), highlighter( 0 ), findDlg(0),find(0),repDlg(0),replace(0), findIndex(0), repIndex(0), lastReplacedPosition(-1) { //Check the default sonnet settings to see if spellchecking should be enabled. KConfig sonnetKConfig("sonnetrc"); KConfigGroup group(&sonnetKConfig, "Spelling"); checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false); // i18n: Placeholder text in text edit widgets is the text appearing // before any user input, briefly explaining to the user what to type // (e.g. "Enter message"). // By default the text is set in italic, which may not be appropriate // for some languages and scripts (e.g. for CJK ideographs). QString metaMsg = i18nc("Italic placeholder text in line edits: 0 no, 1 yes", "1"); italicizePlaceholder = (metaMsg.trimmed() != QString('0')); } ~Private() { delete highlighter; delete findDlg; delete find; delete replace; delete repDlg; } /** * Checks whether we should/should not consume a key used as a shortcut. * This makes it possible to handle shortcuts in the focused widget before any * window-global QAction is triggered. */ bool overrideShortcut(const QKeyEvent* e); /** * Actually handle a shortcut event. */ bool handleShortcut(const QKeyEvent* e); void spellCheckerMisspelling( const QString &text, int pos ); void spellCheckerCorrected( const QString &, int,const QString &); void spellCheckerAutoCorrect(const QString&,const QString&); void spellCheckerCanceled(); void spellCheckerFinished(); void toggleAutoSpellCheck(); void slotFindHighlight(const QString& text, int matchingIndex, int matchingLength); void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength); /** * Similar to QTextEdit::clear(), only that it is possible to undo this * action. */ void undoableClear(); void slotAllowTab(); void menuActivated( QAction* action ); QRect clickMessageRect() const; void init(); void checkSpelling(bool force); KTextEdit *parent; KTextEditSpellInterface *spellInterface; QAction *autoSpellCheckAction; QAction *allowTab; QAction *spellCheckAction; QString clickMessage; bool italicizePlaceholder : 1; bool customPalette : 1; bool checkSpellingEnabled : 1; bool findReplaceEnabled: 1; bool showTabAction: 1; bool showAutoCorrectionButton: 1; QTextDocumentFragment originalDoc; QString spellCheckingConfigFileName; QString spellCheckingLanguage; Sonnet::Highlighter *highlighter; KFindDialog *findDlg; KFind *find; KReplaceDialog *repDlg; KReplace *replace; int findIndex, repIndex; int lastReplacedPosition; KConfig *sonnetKConfig; }; void KTextEdit::Private::checkSpelling(bool force) { if(parent->document()->isEmpty()) { KMessageBox::information(parent, i18n("Nothing to spell check.")); if(force) { emit parent->spellCheckingFinished(); } return; } Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker; if(!spellCheckingLanguage.isEmpty()) backgroundSpellCheck->changeLanguage(spellCheckingLanguage); Sonnet::Dialog *spellDialog = new Sonnet::Dialog( backgroundSpellCheck, force ? parent : 0); backgroundSpellCheck->setParent(spellDialog); spellDialog->setAttribute(Qt::WA_DeleteOnClose, true); spellDialog->activeAutoCorrect(showAutoCorrectionButton); connect(spellDialog, SIGNAL(replace(QString,int,QString)), parent, SLOT(spellCheckerCorrected(QString,int,QString))); connect(spellDialog, SIGNAL(misspelling(QString,int)), parent, SLOT(spellCheckerMisspelling(QString,int))); connect(spellDialog, SIGNAL(autoCorrect(QString,QString)), parent, SLOT(spellCheckerAutoCorrect(QString,QString))); connect(spellDialog, SIGNAL(done(QString)), parent, SLOT(spellCheckerFinished())); connect(spellDialog, SIGNAL(cancel()), parent, SLOT(spellCheckerCanceled())); //Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary /* connect(spellDialog, SIGNAL(stop()), parent, SLOT(spellCheckerFinished())); */ connect(spellDialog, SIGNAL(spellCheckStatus(QString)), parent, SIGNAL(spellCheckStatus(QString))); connect(spellDialog, SIGNAL(languageChanged(QString)), parent, SIGNAL(languageChanged(QString))); if(force) { connect(spellDialog, SIGNAL(done(QString)),parent, SIGNAL(spellCheckingFinished())); connect(spellDialog, SIGNAL(cancel()), parent, SIGNAL(spellCheckingCanceled())); //Laurent in sonnet/dialog.cpp we emit done(QString) too => it calls here twice spellCheckerFinished not necessary //connect(spellDialog, SIGNAL(stop()), parent, SIGNAL(spellCheckingFinished())); } originalDoc = QTextDocumentFragment(parent->document()); spellDialog->setBuffer(parent->toPlainText()); spellDialog->show(); } void KTextEdit::Private::spellCheckerCanceled() { QTextDocument *doc = parent->document(); doc->clear(); QTextCursor cursor(doc); cursor.insertFragment(originalDoc); spellCheckerFinished(); } void KTextEdit::Private::spellCheckerAutoCorrect(const QString& currentWord,const QString& autoCorrectWord) { emit parent->spellCheckerAutoCorrect(currentWord, autoCorrectWord); } void KTextEdit::Private::spellCheckerMisspelling( const QString &text, int pos ) { //kDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<highlightWord( text.length(), pos ); } void KTextEdit::Private::spellCheckerCorrected( const QString& oldWord, int pos,const QString& newWord) { //kDebug()<<" oldWord :"<document()); cursor.setPosition(pos); cursor.setPosition(pos+oldWord.length(),QTextCursor::KeepAnchor); cursor.insertText(newWord); } } void KTextEdit::Private::spellCheckerFinished() { QTextCursor cursor(parent->document()); cursor.clearSelection(); parent->setTextCursor(cursor); if (parent->highlighter()) parent->highlighter()->rehighlight(); } void KTextEdit::Private::toggleAutoSpellCheck() { parent->setCheckSpellingEnabled( !parent->checkSpellingEnabled() ); } void KTextEdit::Private::undoableClear() { QTextCursor cursor = parent->textCursor(); cursor.beginEditBlock(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.endEditBlock(); } void KTextEdit::Private::slotAllowTab() { parent->setTabChangesFocus( !parent->tabChangesFocus() ); } void KTextEdit::Private::menuActivated( QAction* action ) { if ( action == spellCheckAction ) parent->checkSpelling(); else if ( action == autoSpellCheckAction ) toggleAutoSpellCheck(); else if ( action == allowTab ) slotAllowTab(); } void KTextEdit::Private::slotFindHighlight(const QString& text, int matchingIndex, int matchingLength) { Q_UNUSED(text) //kDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength; QTextCursor tc = parent->textCursor(); tc.setPosition(matchingIndex); tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength); parent->setTextCursor(tc); parent->ensureCursorVisible(); } void KTextEdit::Private::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength) { //kDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength; QTextCursor tc = parent->textCursor(); tc.setPosition(replacementIndex); tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength); tc.removeSelectedText(); tc.insertText(text.mid(replacementIndex, replacedLength)); if (replace->options() & KReplaceDialog::PromptOnReplace) { parent->setTextCursor(tc); parent->ensureCursorVisible(); } lastReplacedPosition = replacementIndex; } QRect KTextEdit::Private::clickMessageRect() const { int margin = int(parent->document()->documentMargin()); QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin); return parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage); } void KTextEdit::Private::init() { spellInterface = 0; KCursor::setAutoHideCursor(parent, true, false); parent->connect(parent, SIGNAL(languageChanged(QString)), parent, SLOT(setSpellCheckingLanguage(QString))); } KTextEdit::KTextEdit( const QString& text, QWidget *parent ) : QTextEdit( text, parent ), d( new Private( this ) ) { d->init(); } KTextEdit::KTextEdit( QWidget *parent ) : QTextEdit( parent ), d( new Private( this ) ) { d->init(); } KTextEdit::~KTextEdit() { delete d; } void KTextEdit::setSpellCheckingConfigFileName(const QString &_fileName) { d->spellCheckingConfigFileName = _fileName; } const QString& KTextEdit::spellCheckingLanguage() const { return d->spellCheckingLanguage; } void KTextEdit::setSpellCheckingLanguage(const QString &_language) { if (highlighter()) { highlighter()->setCurrentLanguage(_language); highlighter()->rehighlight(); } if (_language != d->spellCheckingLanguage) { d->spellCheckingLanguage = _language; emit languageChanged(_language); } } void KTextEdit::showSpellConfigDialog(const QString &configFileName, const QString &windowIcon) { KConfig config(configFileName); Sonnet::ConfigDialog dialog(&config, this); if (!d->spellCheckingLanguage.isEmpty()) dialog.setLanguage(d->spellCheckingLanguage); if (!windowIcon.isEmpty()) dialog.setWindowIcon(KIcon(windowIcon)); if(dialog.exec()) { setSpellCheckingLanguage( dialog.language() ); } } bool KTextEdit::event(QEvent* ev) { if (ev->type() == QEvent::ShortcutOverride) { QKeyEvent *e = static_cast( ev ); if (d->overrideShortcut(e)) { e->accept(); return true; } } return QTextEdit::event(ev); } bool KTextEdit::Private::handleShortcut(const QKeyEvent* event) { const int key = event->key() | event->modifiers(); if ( KStandardShortcut::copy().contains( key ) ) { parent->copy(); return true; } else if ( KStandardShortcut::paste().contains( key ) ) { parent->paste(); return true; } else if ( KStandardShortcut::cut().contains( key ) ) { parent->cut(); return true; } else if ( KStandardShortcut::undo().contains( key ) ) { if(!parent->isReadOnly()) parent->undo(); return true; } else if ( KStandardShortcut::redo().contains( key ) ) { if(!parent->isReadOnly()) parent->redo(); return true; } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { if (!parent->isReadOnly()) parent->deleteWordBack(); return true; } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { if (!parent->isReadOnly()) parent->deleteWordForward(); return true; } else if ( KStandardShortcut::backwardWord().contains( key ) ) { QTextCursor cursor = parent->textCursor(); cursor.movePosition( QTextCursor::PreviousWord ); parent->setTextCursor( cursor ); return true; } else if ( KStandardShortcut::forwardWord().contains( key ) ) { QTextCursor cursor = parent->textCursor(); cursor.movePosition( QTextCursor::NextWord ); parent->setTextCursor( cursor ); return true; } else if ( KStandardShortcut::next().contains( key ) ) { QTextCursor cursor = parent->textCursor(); bool moved = false; qreal lastY = parent->cursorRect(cursor).bottom(); qreal distance = 0; do { qreal y = parent->cursorRect(cursor).bottom(); distance += qAbs(y - lastY); lastY = y; moved = cursor.movePosition(QTextCursor::Down); } while (moved && distance < parent->viewport()->height()); if (moved) { cursor.movePosition(QTextCursor::Up); parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); } parent->setTextCursor(cursor); return true; } else if ( KStandardShortcut::prior().contains( key ) ) { QTextCursor cursor = parent->textCursor(); bool moved = false; qreal lastY = parent->cursorRect(cursor).bottom(); qreal distance = 0; do { qreal y = parent->cursorRect(cursor).bottom(); distance += qAbs(y - lastY); lastY = y; moved = cursor.movePosition(QTextCursor::Up); } while (moved && distance < parent->viewport()->height()); if (moved) { cursor.movePosition(QTextCursor::Down); parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); } parent->setTextCursor(cursor); return true; } else if ( KStandardShortcut::begin().contains( key ) ) { QTextCursor cursor = parent->textCursor(); cursor.movePosition( QTextCursor::Start ); parent->setTextCursor( cursor ); return true; } else if ( KStandardShortcut::end().contains( key ) ) { QTextCursor cursor = parent->textCursor(); cursor.movePosition( QTextCursor::End ); parent->setTextCursor( cursor ); return true; } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { QTextCursor cursor = parent->textCursor(); cursor.movePosition( QTextCursor::StartOfLine ); parent->setTextCursor( cursor ); return true; } else if ( KStandardShortcut::endOfLine().contains( key ) ) { QTextCursor cursor = parent->textCursor(); cursor.movePosition( QTextCursor::EndOfLine ); parent->setTextCursor( cursor ); return true; } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { parent->slotFind(); return true; } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { parent->slotFindNext(); return true; } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { if (!parent->isReadOnly()) parent->slotReplace(); return true; } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { QString text = QApplication::clipboard()->text( QClipboard::Selection ); if ( !text.isEmpty() ) parent->insertPlainText( text ); // TODO: check if this is html? (MiB) return true; } return false; } static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op) { cursor.clearSelection(); cursor.movePosition( op, QTextCursor::KeepAnchor ); cursor.removeSelectedText(); } void KTextEdit::deleteWordBack() { deleteWord(textCursor(), QTextCursor::PreviousWord); } void KTextEdit::deleteWordForward() { deleteWord(textCursor(), QTextCursor::NextWord); } QMenu *KTextEdit::mousePopupMenu() { QMenu *popup = createStandardContextMenu(); if (!popup) return 0; connect( popup, SIGNAL(triggered(QAction*)), this, SLOT(menuActivated(QAction*)) ); const bool emptyDocument = document()->isEmpty(); if( !isReadOnly() ) { QList actionList = popup->actions(); enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; QAction *separatorAction = 0L; int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1; if ( idx < actionList.count() ) separatorAction = actionList.at( idx ); if ( separatorAction ) { KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup); if ( emptyDocument ) clearAllAction->setEnabled( false ); popup->insertAction( separatorAction, clearAllAction ); } } KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText : KIconTheme::TextEditor, popup->actions() ); if( !isReadOnly() ) { popup->addSeparator(); d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ), i18n( "Check Spelling..." ) ); if ( emptyDocument ) d->spellCheckAction->setEnabled( false ); d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) ); d->autoSpellCheckAction->setCheckable( true ); d->autoSpellCheckAction->setChecked( checkSpellingEnabled() ); popup->addSeparator(); if (d->showTabAction) { d->allowTab = popup->addAction( i18n("Allow Tabulations") ); d->allowTab->setCheckable( true ); d->allowTab->setChecked( !tabChangesFocus() ); } } if (d->findReplaceEnabled) { KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup); KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup); if (emptyDocument) { findAction->setEnabled(false); findNextAction->setEnabled(false); } else { findNextAction->setEnabled(d->find != 0); } popup->addSeparator(); popup->addAction(findAction); popup->addAction(findNextAction); if (!isReadOnly()) { KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup); if (emptyDocument) { replaceAction->setEnabled(false); } popup->addAction(replaceAction); } } popup->addSeparator(); QAction *speakAction = popup->addAction(i18n("Speak Text")); speakAction->setIcon(KIcon("preferences-desktop-text-to-speech")); speakAction->setEnabled(!emptyDocument ); connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) ); return popup; } void KTextEdit::slotSpeakText() { // If KTTSD not running, start it. if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd")) { QString error; if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error)) { KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error ); return; } } QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech"); QString text; if(textCursor().hasSelection()) text = textCursor().selectedText(); else text = toPlainText(); ktts.asyncCall("say", text, 0); } void KTextEdit::contextMenuEvent(QContextMenuEvent *event) { // Obtain the cursor at the mouse position and the current cursor QTextCursor cursorAtMouse = cursorForPosition(event->pos()); const int mousePos = cursorAtMouse.position(); QTextCursor cursor = textCursor(); // Check if the user clicked a selected word const bool selectedWordClicked = cursor.hasSelection() && mousePos >= cursor.selectionStart() && mousePos <= cursor.selectionEnd(); // Get the word under the (mouse-)cursor and see if it is misspelled. // Don't include apostrophes at the start/end of the word in the selection. QTextCursor wordSelectCursor(cursorAtMouse); wordSelectCursor.clearSelection(); wordSelectCursor.select(QTextCursor::WordUnderCursor); QString selectedWord = wordSelectCursor.selectedText(); bool isMouseCursorInsideWord = true; if ((mousePos < wordSelectCursor.selectionStart() || mousePos >= wordSelectCursor.selectionEnd()) && (selectedWord.length() > 1)) { isMouseCursorInsideWord = false; } // Clear the selection again, we re-select it below (without the apostrophes). wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size()); if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) { selectedWord = selectedWord.right(selectedWord.size() - 1); wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor); } if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"')) selectedWord.chop(1); wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectedWord.size()); const bool wordIsMisspelled = isMouseCursorInsideWord && checkSpellingEnabled() && !selectedWord.isEmpty() && highlighter() && highlighter()->isWordMisspelled(selectedWord); // If the user clicked a selected word, do nothing. // If the user clicked somewhere else, move the cursor there. // If the user clicked on a misspelled word, select that word. // Same behavior as in OpenOffice Writer. bool inQuote = false; if (d->spellInterface && !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text())) inQuote = true; if (!selectedWordClicked) { if (wordIsMisspelled && !inQuote) setTextCursor(wordSelectCursor); else setTextCursor(cursorAtMouse); cursor = textCursor(); } // Use standard context menu for already selected words, correctly spelled // words and words inside quotes. if (!wordIsMisspelled || selectedWordClicked || inQuote) { QMenu *popup = mousePopupMenu(); if (popup) { aboutToShowContextMenu(popup); popup->exec(event->globalPos()); delete popup; } } else { QMenu menu; //don't use KMenu here we don't want auto management accelerator //Add the suggestions to the menu const QStringList reps = highlighter()->suggestionsForWord(selectedWord); if (reps.isEmpty()) { QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord)); suggestionsAction->setEnabled(false); } else { QStringList::const_iterator end(reps.constEnd()); for (QStringList::const_iterator it = reps.constBegin(); it != end; ++it) { menu.addAction(*it); } } menu.addSeparator(); QAction *ignoreAction = menu.addAction(i18n("Ignore")); QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary")); //Execute the popup inline const QAction *selectedAction = menu.exec(event->globalPos()); if (selectedAction) { Q_ASSERT(cursor.selectedText() == selectedWord); if (selectedAction == ignoreAction) { highlighter()->ignoreWord(selectedWord); highlighter()->rehighlight(); } else if (selectedAction == addToDictAction) { highlighter()->addWordToDictionary(selectedWord); highlighter()->rehighlight(); } // Other actions can only be one of the suggested words else { const QString replacement = selectedAction->text(); Q_ASSERT(reps.contains(replacement)); cursor.insertText(replacement); setTextCursor(cursor); } } } } void KTextEdit::wheelEvent( QWheelEvent *event ) { if ( KGlobalSettings::wheelMouseZooms() ) QTextEdit::wheelEvent( event ); else // thanks, we don't want to zoom, so skip QTextEdit's impl. QAbstractScrollArea::wheelEvent( event ); } void KTextEdit::createHighlighter() { setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName)); } Sonnet::Highlighter* KTextEdit::highlighter() const { return d->highlighter; } void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter) { delete d->highlighter; d->highlighter = _highLighter; } void KTextEdit::setCheckSpellingEnabled(bool check) { if (d->spellInterface) d->spellInterface->setSpellCheckingEnabled(check); else setCheckSpellingEnabledInternal(check); } void KTextEdit::setCheckSpellingEnabledInternal( bool check ) { emit checkSpellingChanged( check ); if ( check == d->checkSpellingEnabled ) return; // From the above statment we know know that if we're turning checking // on that we need to create a new highlighter and if we're turning it // off we should remove the old one. d->checkSpellingEnabled = check; if ( check ) { if ( hasFocus() ) { createHighlighter(); if (!spellCheckingLanguage().isEmpty()) setSpellCheckingLanguage(spellCheckingLanguage()); } } else { delete d->highlighter; d->highlighter = 0; } } void KTextEdit::focusInEvent( QFocusEvent *event ) { if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter ) createHighlighter(); QTextEdit::focusInEvent( event ); } bool KTextEdit::checkSpellingEnabled() const { if (d->spellInterface) return d->spellInterface->isSpellCheckingEnabled(); else return checkSpellingEnabledInternal(); } bool KTextEdit::checkSpellingEnabledInternal() const { return d->checkSpellingEnabled; } void KTextEdit::setReadOnly( bool readOnly ) { if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter ) createHighlighter(); if ( readOnly == isReadOnly() ) return; if ( readOnly ) { delete d->highlighter; d->highlighter = 0; d->customPalette = testAttribute( Qt::WA_SetPalette ); QPalette p = palette(); QColor color = p.color( QPalette::Disabled, QPalette::Background ); p.setColor( QPalette::Base, color ); p.setColor( QPalette::Background, color ); setPalette( p ); } else { if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) { QPalette p = palette(); QColor color = p.color( QPalette::Normal, QPalette::Base ); p.setColor( QPalette::Base, color ); p.setColor( QPalette::Background, color ); setPalette( p ); } else setPalette( QPalette() ); } QTextEdit::setReadOnly( readOnly ); } void KTextEdit::checkSpelling() { d->checkSpelling(false); } void KTextEdit::forceSpellChecking() { d->checkSpelling(true); } void KTextEdit::highlightWord( int length, int pos ) { QTextCursor cursor(document()); cursor.setPosition(pos); cursor.setPosition(pos+length,QTextCursor::KeepAnchor); setTextCursor (cursor); ensureCursorVisible(); } void KTextEdit::replace() { if ( document()->isEmpty() ) // saves having to track the text changes return; if ( d->repDlg ) { KWindowSystem::activateWindow( d->repDlg->winId() ); } else { d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false); connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); } d->repDlg->show(); } void KTextEdit::slotDoReplace() { if (!d->repDlg) { // Should really assert() return; } if(d->repDlg->pattern().isEmpty()) { delete d->replace; d->replace = 0; ensureCursorVisible(); return; } delete d->replace; d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this); d->repIndex = 0; if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) { d->repIndex = textCursor().anchor(); } // Connect highlight signal to code which handles highlighting // of found text. connect(d->replace, SIGNAL(highlight(QString,int,int)), this, SLOT(slotFindHighlight(QString,int,int))); connect(d->replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext())); connect(d->replace, SIGNAL(replace(QString,int,int,int)), this, SLOT(slotReplaceText(QString,int,int,int))); d->repDlg->close(); slotReplaceNext(); } void KTextEdit::slotReplaceNext() { if (!d->replace) return; d->lastReplacedPosition = -1; if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { textCursor().beginEditBlock(); // #48541 viewport()->setUpdatesEnabled(false); } KFind::Result res = KFind::NoMatch; if (d->replace->needData()) d->replace->setData(toPlainText(), d->repIndex); res = d->replace->replace(); if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) { textCursor().endEditBlock(); // #48541 if (d->lastReplacedPosition >= 0) { QTextCursor tc = textCursor(); tc.setPosition(d->lastReplacedPosition); setTextCursor(tc); ensureCursorVisible(); } viewport()->setUpdatesEnabled(true); viewport()->update(); } if (res == KFind::NoMatch) { d->replace->displayFinalDialog(); d->replace->disconnect(this); d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away d->replace = 0; ensureCursorVisible(); //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } } else { //m_replace->closeReplaceNextDialog(); } } void KTextEdit::slotDoFind() { if (!d->findDlg) { // Should really assert() return; } if( d->findDlg->pattern().isEmpty()) { delete d->find; d->find = 0; return; } delete d->find; d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this); d->findIndex = 0; if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) { d->findIndex = textCursor().anchor(); } // Connect highlight signal to code which handles highlighting // of found text. connect(d->find, SIGNAL(highlight(QString,int,int)), this, SLOT(slotFindHighlight(QString,int,int))); connect(d->find, SIGNAL(findNext()), this, SLOT(slotFindNext())); d->findDlg->close(); d->find->closeFindNextDialog(); slotFindNext(); } void KTextEdit::slotFindNext() { if (!d->find) return; if(document()->isEmpty()) { d->find->disconnect(this); d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away d->find = 0; return; } KFind::Result res = KFind::NoMatch; if (d->find->needData()) d->find->setData(toPlainText(), d->findIndex); res = d->find->find(); if (res == KFind::NoMatch) { d->find->displayFinalDialog(); d->find->disconnect(this); d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away d->find = 0; //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } } else { //m_find->closeFindNextDialog(); } } void KTextEdit::slotFind() { if ( document()->isEmpty() ) // saves having to track the text changes return; if ( d->findDlg ) { KWindowSystem::activateWindow( d->findDlg->winId() ); } else { d->findDlg = new KFindDialog(this); connect( d->findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) ); } d->findDlg->show(); } void KTextEdit::slotReplace() { if ( document()->isEmpty() ) // saves having to track the text changes return; if ( d->repDlg ) { KWindowSystem::activateWindow( d->repDlg->winId() ); } else { d->repDlg = new KReplaceDialog(this, 0, QStringList(), QStringList(), false); connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); } d->repDlg->show(); } void KTextEdit::enableFindReplace( bool enabled ) { d->findReplaceEnabled = enabled; } void KTextEdit::showTabAction( bool show ) { d->showTabAction = show; } void KTextEdit::setSpellInterface(KTextEditSpellInterface *spellInterface) { d->spellInterface = spellInterface; } bool KTextEdit::Private::overrideShortcut(const QKeyEvent* event) { const int key = event->key() | event->modifiers(); if ( KStandardShortcut::copy().contains( key ) ) { return true; } else if ( KStandardShortcut::paste().contains( key ) ) { return true; } else if ( KStandardShortcut::cut().contains( key ) ) { return true; } else if ( KStandardShortcut::undo().contains( key ) ) { return true; } else if ( KStandardShortcut::redo().contains( key ) ) { return true; } else if ( KStandardShortcut::deleteWordBack().contains( key ) ) { return true; } else if ( KStandardShortcut::deleteWordForward().contains( key ) ) { return true; } else if ( KStandardShortcut::backwardWord().contains( key ) ) { return true; } else if ( KStandardShortcut::forwardWord().contains( key ) ) { return true; } else if ( KStandardShortcut::next().contains( key ) ) { return true; } else if ( KStandardShortcut::prior().contains( key ) ) { return true; } else if ( KStandardShortcut::begin().contains( key ) ) { return true; } else if ( KStandardShortcut::end().contains( key ) ) { return true; } else if ( KStandardShortcut::beginningOfLine().contains( key ) ) { return true; } else if ( KStandardShortcut::endOfLine().contains( key ) ) { return true; } else if ( KStandardShortcut::pasteSelection().contains( key ) ) { return true; } else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) { return true; } else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) { return true; } else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) { return true; } else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit return true; } else if (event->modifiers() == Qt::ControlModifier && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && qobject_cast(parent->window()) ) { // ignore Ctrl-Return so that KDialogs can close the dialog return true; } return false; } void KTextEdit::keyPressEvent( QKeyEvent *event ) { if (d->handleShortcut(event)) { event->accept(); }else if (event->modifiers() == Qt::ControlModifier && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && qobject_cast(window()) ) { event->ignore(); } else { QTextEdit::keyPressEvent(event); } } void KTextEdit::setClickMessage(const QString &msg) { if (msg != d->clickMessage) { if (!d->clickMessage.isEmpty()) { viewport()->update(d->clickMessageRect()); } d->clickMessage = msg; if (!d->clickMessage.isEmpty()) { viewport()->update(d->clickMessageRect()); } } } QString KTextEdit::clickMessage() const { return d->clickMessage; } void KTextEdit::paintEvent(QPaintEvent *ev) { QTextEdit::paintEvent(ev); if (!d->clickMessage.isEmpty() && document()->isEmpty()) { QPainter p(viewport()); QFont f = font(); f.setItalic(d->italicizePlaceholder); p.setFont(f); QColor color(palette().color(viewport()->foregroundRole())); color.setAlphaF(0.5); p.setPen(color); QRect cr = d->clickMessageRect(); p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage); } } void KTextEdit::showAutoCorrectButton(bool show) { d->showAutoCorrectionButton = show; } #include "moc_ktextedit.cpp"