/* 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 #include #include #include #include #include #include #include #include #include static void deleteWord(QTextCursor cursor, const QTextCursor::MoveOperation op) { cursor.clearSelection(); cursor.movePosition(op, QTextCursor::KeepAnchor); cursor.removeSelectedText(); } class KTextEdit::Private { public: Private(KTextEdit *_parent) : parent(_parent), autoSpellCheckAction(nullptr), allowTab(nullptr), italicizePlaceholder(true), customPalette(false), checkSpellingEnabled(false), findReplaceEnabled(true), showTabAction(true), highlighter(nullptr), findDlg(nullptr), find(nullptr), repDlg(nullptr), replace(nullptr), findIndex(0), repIndex(0), lastReplacedPosition(-1) { // Check the default settings to see if spellchecking should be enabled. KConfigGroup spellgroup(KGlobal::config(), "Spelling"); checkSpellingEnabled = spellgroup.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 slotFindHighlight(const QString &text, int matchingIndex, int matchingLength); void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength); void menuActivated(QAction *action); QRect clickMessageRect() const; void init(); KTextEdit *parent; QAction *autoSpellCheckAction; QAction *allowTab; QString clickMessage; bool italicizePlaceholder; bool customPalette; bool checkSpellingEnabled; bool findReplaceEnabled; bool showTabAction; KSpellHighlighter *highlighter; KFindDialog *findDlg; KFind *find; KReplaceDialog *repDlg; KReplace *replace; int findIndex; int repIndex; int lastReplacedPosition; }; void KTextEdit::Private::menuActivated(QAction *action) { if (action == autoSpellCheckAction) { parent->setCheckSpellingEnabled(!parent->checkSpellingEnabled()); } else if (action == allowTab) { parent->setTabChangesFocus(!parent->tabChangesFocus()); } } 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() { KCursor::setAutoHideCursor(parent, true, false); } 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; } 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().matches(key) != QKeySequence::NoMatch) { parent->copy(); return true; } else if (KStandardShortcut::paste().matches(key) != QKeySequence::NoMatch) { parent->paste(); return true; } else if (KStandardShortcut::cut().matches(key) != QKeySequence::NoMatch) { parent->cut(); return true; } else if (KStandardShortcut::undo().matches(key) != QKeySequence::NoMatch) { if (!parent->isReadOnly()) { parent->undo(); } return true; } else if (KStandardShortcut::redo().matches(key) != QKeySequence::NoMatch) { if (!parent->isReadOnly()) { parent->redo(); } return true; } else if (KStandardShortcut::deleteWordBack().matches(key) != QKeySequence::NoMatch) { if (!parent->isReadOnly()) { parent->deleteWordBack(); } return true; } else if ( KStandardShortcut::deleteWordForward().matches(key) != QKeySequence::NoMatch) { if (!parent->isReadOnly()) { parent->deleteWordForward(); } return true; } else if ( KStandardShortcut::backwardWord().matches(key) != QKeySequence::NoMatch) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::PreviousWord); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::forwardWord().matches(key) != QKeySequence::NoMatch) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::NextWord); parent->setTextCursor(cursor); return true; } else if ( KStandardShortcut::next().matches(key) != QKeySequence::NoMatch) { 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().matches(key) != QKeySequence::NoMatch) { 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().matches(key) != QKeySequence::NoMatch) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::Start); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::end().matches(key) != QKeySequence::NoMatch) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::End); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::beginningOfLine().matches(key) != QKeySequence::NoMatch) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::StartOfLine); parent->setTextCursor(cursor); return true; } else if (KStandardShortcut::endOfLine().matches(key) != QKeySequence::NoMatch) { QTextCursor cursor = parent->textCursor(); cursor.movePosition(QTextCursor::EndOfLine); parent->setTextCursor(cursor); return true; } else if (findReplaceEnabled && KStandardShortcut::find().matches(key) != QKeySequence::NoMatch) { parent->slotFind(); return true; } else if (findReplaceEnabled && KStandardShortcut::findNext().matches(key) != QKeySequence::NoMatch) { parent->slotFindNext(); return true; } else if (findReplaceEnabled && KStandardShortcut::replace().matches(key) != QKeySequence::NoMatch) { if (!parent->isReadOnly()) { parent->slotReplace(); } return true; } else if (KStandardShortcut::pasteSelection().matches(key) != QKeySequence::NoMatch) { QString text = QApplication::clipboard()->text(QClipboard::Selection); if (!text.isEmpty()) { // TODO: check if this is html? (MiB) parent->insertPlainText(text); } return true; } return false; } void KTextEdit::deleteWordBack() { deleteWord(textCursor(), QTextCursor::PreviousWord); } void KTextEdit::deleteWordForward() { deleteWord(textCursor(), QTextCursor::NextWord); } QMenu *KTextEdit::mousePopupMenu() { QMenu *popup = createStandardContextMenu(); if (!popup) { return nullptr; } connect( popup, SIGNAL(triggered(QAction*)), this, SLOT(menuActivated(QAction*)) ); const bool emptyDocument = document()->isEmpty(); if (!isReadOnly()) { popup->addSeparator(); 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); } } return popup; } 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. if (!selectedWordClicked) { if (wordIsMisspelled) { 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) { 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(); } else { // Other actions can only be one of the suggested words const QString replacement = selectedAction->text(); Q_ASSERT(reps.contains(replacement)); cursor.insertText(replacement); setTextCursor(cursor); } } } } KSpellHighlighter* KTextEdit::highlighter() const { return d->highlighter; } void KTextEdit::setHighlighter(KSpellHighlighter *highLighter) { d->highlighter = highLighter; } void KTextEdit::setCheckSpellingEnabled(bool 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 (!isReadOnly() && !d->highlighter) { d->highlighter = new KSpellHighlighter(KGlobal::config().data(), this); } if (d->highlighter) { d->highlighter->setDocument(document()); } } else { if (d->highlighter) { d->highlighter->setDocument(nullptr); } } emit checkSpellingChanged(check); } bool KTextEdit::checkSpellingEnabled() const { return d->checkSpellingEnabled; } void KTextEdit::setReadOnly(bool readOnly) { if (readOnly == isReadOnly()) { return; } if (readOnly) { 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); setCheckSpellingEnabled(!readOnly); } 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(); // 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 = nullptr; 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(); // in a slot connected to m_find, don't delete right away d->find = nullptr; 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(); // in a slot connected to m_find, don't delete right away d->find = nullptr; // 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; } bool KTextEdit::Private::overrideShortcut(const QKeyEvent *event) { const QKeySequence key = QKeySequence(event->key() | event->modifiers()); if (KStandardShortcut::copy().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::paste().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::cut().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::undo().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::redo().matches(key) != QKeySequence::NoMatch) { return true; } else if ( KStandardShortcut::deleteWordBack().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::deleteWordForward().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::backwardWord().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::forwardWord().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::next().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::prior().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::begin().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::end().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::beginningOfLine().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::endOfLine().matches(key) != QKeySequence::NoMatch) { return true; } else if (KStandardShortcut::pasteSelection().matches(key) != QKeySequence::NoMatch) { return true; } else if (findReplaceEnabled && KStandardShortcut::find().matches(key) != QKeySequence::NoMatch) { return true; } else if (findReplaceEnabled && KStandardShortcut::findNext().matches(key) != QKeySequence::NoMatch) { return true; } else if (findReplaceEnabled && KStandardShortcut::replace().matches(key) != QKeySequence::NoMatch) { 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); p.drawText(d->clickMessageRect(), Qt::AlignTop | Qt::TextWordWrap, d->clickMessage); } } #include "moc_ktextedit.cpp"