mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
894 lines
29 KiB
C++
894 lines
29 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (C) 2002 Carsten Pfeiffer <pfeiffer@kde.org>
|
|
2005 Michael Brade <brade@kde.org>
|
|
2012 Laurent Montel <montel@kde.org>
|
|
|
|
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 <QApplication>
|
|
#include <QClipboard>
|
|
#include <QtGui/qevent.h>
|
|
#include <QMenu>
|
|
#include <QPainter>
|
|
#include <QScrollBar>
|
|
#include <QTextCursor>
|
|
|
|
#include <kdebug.h>
|
|
#include <kaction.h>
|
|
#include <kcursor.h>
|
|
#include <kstandardaction.h>
|
|
#include <kstandardshortcut.h>
|
|
#include <kicon.h>
|
|
#include <kiconloader.h>
|
|
#include <klocale.h>
|
|
#include <kdialog.h>
|
|
#include <kreplacedialog.h>
|
|
#include <kfinddialog.h>
|
|
#include <kfind.h>
|
|
#include <kreplace.h>
|
|
#include <kmessagebox.h>
|
|
#include <kmenu.h>
|
|
#include <kwindowsystem.h>
|
|
#include <kspellhighlighter.h>
|
|
#include <QDebug>
|
|
|
|
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<QKeyEvent*>(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 {
|
|
foreach (const QString &rep, reps) {
|
|
menu.addAction(rep);
|
|
}
|
|
}
|
|
|
|
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<KDialog*>(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<KDialog*>(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"
|