25 #include <QApplication>
31 #include <QTextCursor>
32 #include <QTextDocumentFragment>
33 #include <QDBusInterface>
34 #include <QDBusConnection>
35 #include <QDBusConnectionInterface>
58 class KTextEdit::Private
63 customPalette( false ),
65 findReplaceEnabled(true),
67 lastReplacedPosition(-1)
70 sonnetKConfig =
new KConfig(
"sonnetrc");
79 QString metaMsg =
i18nc(
"Italic placeholder text in line edits: 0 no, 1 yes",
"1");
80 italicizePlaceholder = (metaMsg.trimmed() !=
QString(
'0'));
98 bool overrideShortcut(
const QKeyEvent* e);
102 bool handleShortcut(
const QKeyEvent* e);
104 void spellCheckerMisspelling(
const QString &text,
int pos );
105 void spellCheckerCorrected(
const QString &,
int,
const QString &);
107 void spellCheckerCanceled();
108 void spellCheckerFinished();
109 void toggleAutoSpellCheck();
111 void slotFindHighlight(
const QString& text,
int matchingIndex,
int matchingLength);
112 void slotReplaceText(
const QString &text,
int replacementIndex,
int ,
int matchedLength);
118 void undoableClear();
121 void menuActivated(
QAction* action );
123 void updateClickMessageRect();
133 bool italicizePlaceholder : 1;
134 bool customPalette : 1;
137 bool findReplaceEnabled: 1;
138 QTextDocumentFragment originalDoc;
139 QString spellCheckingConfigFileName;
146 int findIndex, repIndex;
147 int lastReplacedPosition;
151 void KTextEdit::Private::spellCheckerCanceled()
153 QTextDocument *doc = parent->document();
155 QTextCursor cursor(doc);
156 cursor.insertFragment(originalDoc);
157 spellCheckerFinished();
160 void KTextEdit::Private::spellCheckerAutoCorrect(
const QString&,
const QString&)
165 void KTextEdit::Private::spellCheckerMisspelling(
const QString &text,
int pos )
168 parent->highlightWord( text.length(), pos );
171 void KTextEdit::Private::spellCheckerCorrected(
const QString& oldWord,
int pos,
const QString& newWord)
174 if (oldWord != newWord ) {
175 QTextCursor cursor(parent->document());
176 cursor.setPosition(pos);
177 cursor.setPosition(pos+oldWord.length(),QTextCursor::KeepAnchor);
178 cursor.insertText(newWord);
182 void KTextEdit::Private::spellCheckerFinished()
184 QTextCursor cursor(parent->document());
185 cursor.clearSelection();
186 parent->setTextCursor(cursor);
187 if (parent->highlighter())
188 parent->highlighter()->rehighlight();
191 void KTextEdit::Private::toggleAutoSpellCheck()
193 parent->setCheckSpellingEnabled( !parent->checkSpellingEnabled() );
196 void KTextEdit::Private::undoableClear()
198 QTextCursor cursor = parent->textCursor();
199 cursor.beginEditBlock();
200 cursor.movePosition(QTextCursor::Start);
202 cursor.removeSelectedText();
203 cursor.endEditBlock();
206 void KTextEdit::Private::slotAllowTab()
208 parent->setTabChangesFocus( !parent->tabChangesFocus() );
211 void KTextEdit::Private::menuActivated(
QAction* action )
213 if ( action == spellCheckAction )
214 parent->checkSpelling();
215 else if ( action == autoSpellCheckAction )
216 toggleAutoSpellCheck();
217 else if ( action == allowTab )
222 void KTextEdit::Private::slotFindHighlight(
const QString& text,
int matchingIndex,
int matchingLength)
226 QTextCursor tc = parent->textCursor();
227 tc.setPosition(matchingIndex);
228 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength);
229 parent->setTextCursor(tc);
230 parent->ensureCursorVisible();
234 void KTextEdit::Private::slotReplaceText(const
QString &text,
int replacementIndex,
int replacedLength,
int matchedLength) {
236 QTextCursor tc = parent->textCursor();
237 tc.setPosition(replacementIndex);
238 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength);
239 tc.removeSelectedText();
240 tc.insertText(text.mid(replacementIndex, replacedLength));
242 parent->setTextCursor(tc);
243 parent->ensureCursorVisible();
245 lastReplacedPosition = replacementIndex;
248 void KTextEdit::Private::updateClickMessageRect()
250 int margin = int(parent->document()->documentMargin());
251 QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin);
252 rect = parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage);
253 parent->viewport()->update(rect);
256 void KTextEdit::Private::init()
260 parent->connect(parent, SIGNAL(languageChanged(
QString)),
261 parent, SLOT(setSpellCheckingLanguage(
QString)));
265 :
QTextEdit( text, parent ), d( new Private( this ) )
271 :
QTextEdit( parent ), d( new Private( this ) )
283 d->spellCheckingConfigFileName = _fileName;
288 return d->spellCheckingLanguage;
298 if (_language != d->spellCheckingLanguage) {
299 d->spellCheckingLanguage = _language;
303 d->spellCheckingLanguage = _language;
311 if (!d->spellCheckingLanguage.isEmpty())
313 if (!windowIcon.isEmpty())
314 dialog.setWindowIcon(
KIcon(windowIcon));
322 if (ev->type() == QEvent::ShortcutOverride) {
323 QKeyEvent *e =
static_cast<QKeyEvent *
>( ev );
324 if (d->overrideShortcut(e)) {
332 bool KTextEdit::Private::handleShortcut(
const QKeyEvent* event)
334 const int key =
event->key() |
event->modifiers();
346 if(!parent->isReadOnly())
350 if(!parent->isReadOnly())
354 parent->deleteWordBack();
357 parent->deleteWordForward();
360 QTextCursor cursor = parent->textCursor();
361 cursor.movePosition( QTextCursor::PreviousWord );
362 parent->setTextCursor( cursor );
365 QTextCursor cursor = parent->textCursor();
366 cursor.movePosition( QTextCursor::NextWord );
367 parent->setTextCursor( cursor );
370 QTextCursor cursor = parent->textCursor();
372 qreal lastY = parent->cursorRect(cursor).bottom();
375 qreal y = parent->cursorRect(cursor).bottom();
376 distance += qAbs(y - lastY);
378 moved = cursor.movePosition(QTextCursor::Down);
379 }
while (moved && distance < parent->viewport()->height());
383 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
385 parent->setTextCursor(cursor);
388 QTextCursor cursor = parent->textCursor();
390 qreal lastY = parent->cursorRect(cursor).bottom();
393 qreal y = parent->cursorRect(cursor).bottom();
394 distance += qAbs(y - lastY);
397 }
while (moved && distance < parent->viewport()->height());
400 cursor.movePosition(QTextCursor::Down);
401 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
403 parent->setTextCursor(cursor);
406 QTextCursor cursor = parent->textCursor();
407 cursor.movePosition( QTextCursor::Start );
408 parent->setTextCursor( cursor );
411 QTextCursor cursor = parent->textCursor();
413 parent->setTextCursor( cursor );
416 QTextCursor cursor = parent->textCursor();
417 cursor.movePosition( QTextCursor::StartOfLine );
418 parent->setTextCursor( cursor );
421 QTextCursor cursor = parent->textCursor();
423 parent->setTextCursor( cursor );
429 parent->slotFindNext();
432 if (!parent->isReadOnly())
433 parent->slotReplace();
436 QString text = QApplication::clipboard()->text( QClipboard::Selection );
437 if ( !text.isEmpty() )
438 parent->insertPlainText( text );
444 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
446 cursor.clearSelection();
447 cursor.movePosition( op, QTextCursor::KeepAnchor );
448 cursor.removeSelectedText();
453 deleteWord(textCursor(), QTextCursor::PreviousWord);
458 deleteWord(textCursor(), QTextCursor::WordRight);
463 QMenu *popup = createStandardContextMenu();
464 if (!popup)
return 0;
465 connect( popup, SIGNAL(triggered(
QAction*)),
466 this, SLOT(menuActivated(
QAction*)) );
468 const bool emptyDocument = document()->isEmpty();
471 QList<QAction *> actionList = popup->actions();
472 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
474 int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1;
475 if ( idx < actionList.count() )
476 separatorAction = actionList.at( idx );
477 if ( separatorAction )
481 clearAllAction->setEnabled(
false );
482 popup->insertAction( separatorAction, clearAllAction );
491 popup->addSeparator();
492 d->spellCheckAction = popup->addAction(
KIcon(
"tools-check-spelling" ),
493 i18n(
"Check Spelling..." ) );
495 d->spellCheckAction->setEnabled(
false );
496 d->autoSpellCheckAction = popup->addAction(
i18n(
"Auto Spell Check" ) );
497 d->autoSpellCheckAction->setCheckable(
true );
499 popup->addSeparator();
500 d->allowTab = popup->addAction(
i18n(
"Allow Tabulations") );
501 d->allowTab->setCheckable(
true );
502 d->allowTab->setChecked( !tabChangesFocus() );
505 if (d->findReplaceEnabled) {
509 findAction->setEnabled(
false);
510 findNextAction->setEnabled(
false);
512 findNextAction->setEnabled(d->find != 0);
514 popup->addSeparator();
515 popup->addAction(findAction);
516 popup->addAction(findNextAction);
521 replaceAction->setEnabled(
false);
523 popup->addAction(replaceAction);
526 popup->addSeparator();
527 QAction *speakAction = popup->addAction(
i18n(
"Speak Text"));
528 speakAction->setIcon(
KIcon(
"preferences-desktop-text-to-speech"));
529 speakAction->setEnabled(!emptyDocument );
530 connect( speakAction, SIGNAL(triggered(
bool)),
this, SLOT(
slotSpeakText()) );
537 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(
"org.kde.kttsd"))
546 QDBusInterface ktts(
"org.kde.kttsd",
"/KSpeech",
"org.kde.KSpeech");
548 if(textCursor().hasSelection())
549 text = textCursor().selectedText();
551 text = toPlainText();
552 ktts.asyncCall(
"say", text, 0);
558 QTextCursor cursorAtMouse = cursorForPosition(event->pos());
559 const int mousePos = cursorAtMouse.position();
560 QTextCursor cursor = textCursor();
563 const bool selectedWordClicked = cursor.hasSelection() &&
564 mousePos >= cursor.selectionStart() &&
565 mousePos <= cursor.selectionEnd();
569 QTextCursor wordSelectCursor(cursorAtMouse);
570 wordSelectCursor.clearSelection();
571 wordSelectCursor.select(QTextCursor::WordUnderCursor);
572 QString selectedWord = wordSelectCursor.selectedText();
574 bool isMouseCursorInsideWord =
true;
575 if ((mousePos < wordSelectCursor.selectionStart() ||
576 mousePos >= wordSelectCursor.selectionEnd())
577 && (selectedWord.length() > 1)) {
578 isMouseCursorInsideWord =
false;
582 wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size());
583 if (selectedWord.startsWith(
'\'') || selectedWord.startsWith(
'\"')) {
584 selectedWord = selectedWord.right(selectedWord.size() - 1);
585 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
587 if (selectedWord.endsWith(
'\'') || selectedWord.endsWith(
'\"'))
588 selectedWord.chop(1);
590 wordSelectCursor.movePosition(QTextCursor::NextCharacter,
591 QTextCursor::KeepAnchor, selectedWord.size());
593 const bool wordIsMisspelled = isMouseCursorInsideWord &&
595 !selectedWord.isEmpty() &&
603 bool inQuote =
false;
604 if (d->spellInterface &&
605 !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text()))
607 if (!selectedWordClicked) {
608 if (wordIsMisspelled && !inQuote)
609 setTextCursor(wordSelectCursor);
611 setTextCursor(cursorAtMouse);
612 cursor = textCursor();
617 if (!wordIsMisspelled || selectedWordClicked || inQuote) {
621 popup->exec( event->globalPos() );
630 if (reps.isEmpty()) {
631 QAction *suggestionsAction = menu.addAction(
i18n(
"No suggestions for %1", selectedWord));
632 suggestionsAction->setEnabled(
false);
635 for (QStringList::const_iterator it = reps.constBegin(); it != reps.constEnd(); ++it) {
642 QAction *ignoreAction = menu.addAction(
i18n(
"Ignore"));
643 QAction *addToDictAction = menu.addAction(
i18n(
"Add to Dictionary"));
645 const QAction *selectedAction = menu.exec(event->globalPos());
647 if (selectedAction) {
648 Q_ASSERT(cursor.selectedText() == selectedWord);
650 if (selectedAction == ignoreAction) {
654 else if (selectedAction == addToDictAction) {
661 const QString replacement = selectedAction->text();
662 Q_ASSERT(reps.contains(replacement));
663 cursor.insertText(replacement);
664 setTextCursor(cursor);
685 return d->highlighter;
690 delete d->highlighter;
691 d->highlighter = _highLighter;
696 if (d->spellInterface)
697 d->spellInterface->setSpellCheckingEnabled(check);
705 if ( check == d->checkSpellingEnabled )
712 d->checkSpellingEnabled = check;
723 delete d->highlighter;
730 if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter )
733 if (!d->clickMessage.isEmpty()) {
734 d->updateClickMessageRect();
741 if (d->spellInterface)
742 return d->spellInterface->isSpellCheckingEnabled();
749 return d->checkSpellingEnabled;
754 if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter )
757 if ( readOnly == isReadOnly() )
761 delete d->highlighter;
764 d->customPalette = testAttribute( Qt::WA_SetPalette );
765 QPalette p = palette();
766 QColor color = p.color( QPalette::Disabled, QPalette::Background );
767 p.setColor( QPalette::Base, color );
768 p.setColor( QPalette::Background, color );
771 if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) {
772 QPalette p = palette();
773 QColor color = p.color( QPalette::Normal, QPalette::Base );
774 p.setColor( QPalette::Base, color );
775 p.setColor( QPalette::Background, color );
778 setPalette( QPalette() );
786 if(document()->isEmpty())
792 if(!d->spellCheckingLanguage.isEmpty())
795 backgroundSpellCheck, 0);
798 connect(spellDialog, SIGNAL(misspelling(
QString,
int)),
799 this, SLOT(spellCheckerMisspelling(
QString,
int)));
802 connect(spellDialog, SIGNAL(done(
QString)),
803 this, SLOT(spellCheckerFinished()));
804 connect(spellDialog, SIGNAL(
cancel()),
805 this, SLOT(spellCheckerCanceled()));
806 connect(spellDialog, SIGNAL(
stop()),
807 this, SLOT(spellCheckerFinished()));
812 d->originalDoc = QTextDocumentFragment(document());
819 QTextCursor cursor(document());
820 cursor.setPosition(pos);
821 cursor.setPosition(pos+length,QTextCursor::KeepAnchor);
822 setTextCursor (cursor);
823 ensureCursorVisible();
828 if( document()->isEmpty() )
836 connect( d->repDlg, SIGNAL(okClicked()),
this, SLOT(
slotDoReplace()) );
848 if(d->repDlg->pattern().isEmpty()) {
851 ensureCursorVisible();
856 d->replace =
new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(),
this);
859 d->repIndex = textCursor().anchor();
864 connect(d->replace, SIGNAL(highlight(
QString,
int,
int)),
865 this, SLOT(slotFindHighlight(
QString,
int,
int)));
868 this, SLOT(slotReplaceText(
QString,
int,
int,
int)));
880 d->lastReplacedPosition = -1;
882 textCursor().beginEditBlock();
883 viewport()->setUpdatesEnabled(
false);
888 if (d->replace->needData())
889 d->replace->setData(toPlainText(), d->repIndex);
890 res = d->replace->replace();
892 textCursor().endEditBlock();
893 if (d->lastReplacedPosition >= 0) {
894 QTextCursor tc = textCursor();
895 tc.setPosition(d->lastReplacedPosition);
897 ensureCursorVisible();
900 viewport()->setUpdatesEnabled(
true);
901 viewport()->update();
905 d->replace->displayFinalDialog();
906 d->replace->disconnect(
this);
907 d->replace->deleteLater();
909 ensureCursorVisible();
923 if( d->findDlg->pattern().isEmpty())
930 d->find =
new KFind(d->findDlg->pattern(), d->findDlg->options(),
this);
933 d->findIndex = textCursor().anchor();
938 connect(d->find, SIGNAL(highlight(
QString,
int,
int)),
939 this, SLOT(slotFindHighlight(
QString,
int,
int)));
943 d->find->closeFindNextDialog();
952 if(document()->isEmpty())
954 d->find->disconnect(
this);
955 d->find->deleteLater();
961 if (d->find->needData())
962 d->find->setData(toPlainText(), d->findIndex);
963 res = d->find->find();
966 d->find->displayFinalDialog();
967 d->find->disconnect(
this);
968 d->find->deleteLater();
979 if( document()->isEmpty() )
986 connect( d->findDlg, SIGNAL(okClicked()),
this, SLOT(
slotDoFind()) );
994 if( document()->isEmpty() )
1002 connect( d->repDlg, SIGNAL(okClicked()),
this, SLOT(
slotDoReplace()) );
1009 d->findReplaceEnabled = enabled;
1014 d->spellInterface = spellInterface;
1017 bool KTextEdit::Private::overrideShortcut(
const QKeyEvent* event)
1019 const int key =
event->key() |
event->modifiers();
1061 }
else if (event->modifiers() == Qt::ControlModifier &&
1062 (
event->key() == Qt::Key_Return ||
event->key() == Qt::Key_Enter) &&
1063 qobject_cast<KDialog*>(parent->window()) ) {
1072 if (d->handleShortcut(event)) {
1074 }
else if (event->modifiers() == Qt::ControlModifier &&
1075 (
event->key() == Qt::Key_Return ||
event->key() == Qt::Key_Enter) &&
1076 qobject_cast<KDialog*>(window()) ) {
1085 if (msg != d->clickMessage) {
1086 if (!d->clickMessage.isEmpty()) {
1087 d->updateClickMessageRect();
1089 d->clickMessage = msg;
1090 if (!d->clickMessage.isEmpty()) {
1091 d->updateClickMessageRect();
1098 return d->clickMessage;
1105 if (!d->clickMessage.isEmpty() && !hasFocus() && document()->isEmpty()) {
1106 QPainter p(viewport());
1109 f.setItalic(d->italicizePlaceholder);
1112 QColor color(palette().color(foregroundRole()));
1113 color.setAlphaF(0.5);
1116 int margin = int(document()->documentMargin());
1117 QRect cr = viewport()->rect().adjusted(margin, margin, -margin, -margin);
1119 p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage);
1125 if (!d->clickMessage.isEmpty()) {
1126 d->updateClickMessageRect();
1131 #include "ktextedit.moc"