26 #include <QApplication>
32 #include <QTextCursor>
33 #include <QTextDocumentFragment>
34 #include <QDBusInterface>
35 #include <QDBusConnection>
36 #include <QDBusConnectionInterface>
59 class KTextEdit::Private
64 customPalette( false ),
66 findReplaceEnabled(true),
68 showAutoCorrectionButton(false),
70 lastReplacedPosition(-1)
73 sonnetKConfig =
new KConfig(
"sonnetrc");
82 QString metaMsg =
i18nc(
"Italic placeholder text in line edits: 0 no, 1 yes",
"1");
83 italicizePlaceholder = (metaMsg.trimmed() !=
QString(
'0'));
101 bool overrideShortcut(
const QKeyEvent* e);
105 bool handleShortcut(
const QKeyEvent* e);
107 void spellCheckerMisspelling(
const QString &text,
int pos );
108 void spellCheckerCorrected(
const QString &,
int,
const QString &);
110 void spellCheckerCanceled();
111 void spellCheckerFinished();
112 void toggleAutoSpellCheck();
114 void slotFindHighlight(
const QString& text,
int matchingIndex,
int matchingLength);
115 void slotReplaceText(
const QString &text,
int replacementIndex,
int ,
int matchedLength);
121 void undoableClear();
124 void menuActivated(
QAction* action );
126 void updateClickMessageRect();
137 bool italicizePlaceholder : 1;
138 bool customPalette : 1;
141 bool findReplaceEnabled: 1;
143 bool showAutoCorrectionButton: 1;
144 QTextDocumentFragment originalDoc;
145 QString spellCheckingConfigFileName;
152 int findIndex, repIndex;
153 int lastReplacedPosition;
157 void KTextEdit::Private::checkSpelling(
bool force)
159 if(parent->document()->isEmpty())
163 emit parent->spellCheckingFinished();
168 if(!spellCheckingLanguage.isEmpty())
171 backgroundSpellCheck, force ? parent : 0);
172 backgroundSpellCheck->setParent(spellDialog);
173 spellDialog->setAttribute(Qt::WA_DeleteOnClose,
true);
177 connect(spellDialog, SIGNAL(misspelling(
QString,
int)),
178 parent, SLOT(spellCheckerMisspelling(
QString,
int)));
181 connect(spellDialog, SIGNAL(done(
QString)),
182 parent, SLOT(spellCheckerFinished()));
183 connect(spellDialog, SIGNAL(
cancel()),
184 parent, SLOT(spellCheckerCanceled()));
190 connect(spellDialog, SIGNAL(spellCheckStatus(
QString)),
191 parent, SIGNAL(spellCheckStatus(
QString)));
192 connect(spellDialog, SIGNAL(languageChanged(
QString)),
193 parent, SIGNAL(languageChanged(
QString)));
195 connect(spellDialog, SIGNAL(done(
QString)),parent, SIGNAL(spellCheckingFinished()));
196 connect(spellDialog, SIGNAL(
cancel()), parent, SIGNAL(spellCheckingCanceled()));
200 originalDoc = QTextDocumentFragment(parent->document());
201 spellDialog->
setBuffer(parent->toPlainText());
206 void KTextEdit::Private::spellCheckerCanceled()
208 QTextDocument *doc = parent->document();
210 QTextCursor cursor(doc);
211 cursor.insertFragment(originalDoc);
212 spellCheckerFinished();
215 void KTextEdit::Private::spellCheckerAutoCorrect(
const QString& currentWord,
const QString& autoCorrectWord)
217 emit parent->spellCheckerAutoCorrect(currentWord, autoCorrectWord);
220 void KTextEdit::Private::spellCheckerMisspelling(
const QString &text,
int pos )
223 parent->highlightWord( text.length(), pos );
226 void KTextEdit::Private::spellCheckerCorrected(
const QString& oldWord,
int pos,
const QString& newWord)
229 if (oldWord != newWord ) {
230 QTextCursor cursor(parent->document());
231 cursor.setPosition(pos);
232 cursor.setPosition(pos+oldWord.length(),QTextCursor::KeepAnchor);
233 cursor.insertText(newWord);
237 void KTextEdit::Private::spellCheckerFinished()
239 QTextCursor cursor(parent->document());
240 cursor.clearSelection();
241 parent->setTextCursor(cursor);
242 if (parent->highlighter())
243 parent->highlighter()->rehighlight();
246 void KTextEdit::Private::toggleAutoSpellCheck()
248 parent->setCheckSpellingEnabled( !parent->checkSpellingEnabled() );
251 void KTextEdit::Private::undoableClear()
253 QTextCursor cursor = parent->textCursor();
254 cursor.beginEditBlock();
255 cursor.movePosition(QTextCursor::Start);
257 cursor.removeSelectedText();
258 cursor.endEditBlock();
261 void KTextEdit::Private::slotAllowTab()
263 parent->setTabChangesFocus( !parent->tabChangesFocus() );
266 void KTextEdit::Private::menuActivated(
QAction* action )
268 if ( action == spellCheckAction )
269 parent->checkSpelling();
270 else if ( action == autoSpellCheckAction )
271 toggleAutoSpellCheck();
272 else if ( action == allowTab )
277 void KTextEdit::Private::slotFindHighlight(
const QString& text,
int matchingIndex,
int matchingLength)
281 QTextCursor tc = parent->textCursor();
282 tc.setPosition(matchingIndex);
283 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength);
284 parent->setTextCursor(tc);
285 parent->ensureCursorVisible();
289 void KTextEdit::Private::slotReplaceText(const
QString &text,
int replacementIndex,
int replacedLength,
int matchedLength) {
291 QTextCursor tc = parent->textCursor();
292 tc.setPosition(replacementIndex);
293 tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength);
294 tc.removeSelectedText();
295 tc.insertText(text.mid(replacementIndex, replacedLength));
297 parent->setTextCursor(tc);
298 parent->ensureCursorVisible();
300 lastReplacedPosition = replacementIndex;
303 void KTextEdit::Private::updateClickMessageRect()
305 int margin = int(parent->document()->documentMargin());
306 QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin);
307 rect = parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage);
308 parent->viewport()->update(rect);
311 void KTextEdit::Private::init()
315 parent->connect(parent, SIGNAL(languageChanged(
QString)),
316 parent, SLOT(setSpellCheckingLanguage(
QString)));
320 :
QTextEdit( text, parent ), d( new Private( this ) )
326 :
QTextEdit( parent ), d( new Private( this ) )
338 d->spellCheckingConfigFileName = _fileName;
343 return d->spellCheckingLanguage;
353 if (_language != d->spellCheckingLanguage) {
354 d->spellCheckingLanguage = _language;
364 if (!d->spellCheckingLanguage.isEmpty())
366 if (!windowIcon.isEmpty())
367 dialog.setWindowIcon(
KIcon(windowIcon));
375 if (ev->type() == QEvent::ShortcutOverride) {
376 QKeyEvent *e =
static_cast<QKeyEvent *
>( ev );
377 if (d->overrideShortcut(e)) {
382 return QTextEdit::event(ev);
385 bool KTextEdit::Private::handleShortcut(
const QKeyEvent* event)
387 const int key =
event->key() |
event->modifiers();
399 if(!parent->isReadOnly())
403 if(!parent->isReadOnly())
407 parent->deleteWordBack();
410 parent->deleteWordForward();
413 QTextCursor cursor = parent->textCursor();
414 cursor.movePosition( QTextCursor::PreviousWord );
415 parent->setTextCursor( cursor );
418 QTextCursor cursor = parent->textCursor();
419 cursor.movePosition( QTextCursor::NextWord );
420 parent->setTextCursor( cursor );
423 QTextCursor cursor = parent->textCursor();
425 qreal lastY = parent->cursorRect(cursor).bottom();
428 qreal y = parent->cursorRect(cursor).bottom();
429 distance += qAbs(y - lastY);
431 moved = cursor.movePosition(QTextCursor::Down);
432 }
while (moved && distance < parent->viewport()->height());
436 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
438 parent->setTextCursor(cursor);
441 QTextCursor cursor = parent->textCursor();
443 qreal lastY = parent->cursorRect(cursor).bottom();
446 qreal y = parent->cursorRect(cursor).bottom();
447 distance += qAbs(y - lastY);
450 }
while (moved && distance < parent->viewport()->height());
453 cursor.movePosition(QTextCursor::Down);
454 parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
456 parent->setTextCursor(cursor);
459 QTextCursor cursor = parent->textCursor();
460 cursor.movePosition( QTextCursor::Start );
461 parent->setTextCursor( cursor );
464 QTextCursor cursor = parent->textCursor();
466 parent->setTextCursor( cursor );
469 QTextCursor cursor = parent->textCursor();
470 cursor.movePosition( QTextCursor::StartOfLine );
471 parent->setTextCursor( cursor );
474 QTextCursor cursor = parent->textCursor();
476 parent->setTextCursor( cursor );
482 parent->slotFindNext();
485 if (!parent->isReadOnly())
486 parent->slotReplace();
489 QString text = QApplication::clipboard()->text( QClipboard::Selection );
490 if ( !text.isEmpty() )
491 parent->insertPlainText( text );
497 static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
499 cursor.clearSelection();
500 cursor.movePosition( op, QTextCursor::KeepAnchor );
501 cursor.removeSelectedText();
506 deleteWord(textCursor(), QTextCursor::PreviousWord);
511 deleteWord(textCursor(), QTextCursor::WordRight);
516 QMenu *popup = createStandardContextMenu();
517 if (!popup)
return 0;
518 connect( popup, SIGNAL(triggered(
QAction*)),
519 this, SLOT(menuActivated(
QAction*)) );
521 const bool emptyDocument = document()->isEmpty();
525 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
527 int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1;
528 if ( idx < actionList.count() )
529 separatorAction = actionList.at( idx );
530 if ( separatorAction )
534 clearAllAction->setEnabled(
false );
535 popup->insertAction( separatorAction, clearAllAction );
544 popup->addSeparator();
545 d->spellCheckAction = popup->addAction(
KIcon(
"tools-check-spelling" ),
546 i18n(
"Check Spelling..." ) );
548 d->spellCheckAction->setEnabled(
false );
549 d->autoSpellCheckAction = popup->addAction(
i18n(
"Auto Spell Check" ) );
550 d->autoSpellCheckAction->setCheckable(
true );
552 popup->addSeparator();
553 if (d->showTabAction) {
554 d->allowTab = popup->addAction(
i18n(
"Allow Tabulations") );
555 d->allowTab->setCheckable(
true );
556 d->allowTab->setChecked( !tabChangesFocus() );
560 if (d->findReplaceEnabled) {
564 findAction->setEnabled(
false);
565 findNextAction->setEnabled(
false);
567 findNextAction->setEnabled(d->find != 0);
569 popup->addSeparator();
570 popup->addAction(findAction);
571 popup->addAction(findNextAction);
576 replaceAction->setEnabled(
false);
578 popup->addAction(replaceAction);
581 popup->addSeparator();
582 QAction *speakAction = popup->addAction(
i18n(
"Speak Text"));
583 speakAction->setIcon(
KIcon(
"preferences-desktop-text-to-speech"));
584 speakAction->setEnabled(!emptyDocument );
585 connect( speakAction, SIGNAL(triggered(
bool)),
this, SLOT(
slotSpeakText()) );
592 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(
"org.kde.kttsd"))
601 QDBusInterface ktts(
"org.kde.kttsd",
"/KSpeech",
"org.kde.KSpeech");
603 if(textCursor().hasSelection())
604 text = textCursor().selectedText();
606 text = toPlainText();
607 ktts.asyncCall(
"say", text, 0);
613 QTextCursor cursorAtMouse = cursorForPosition(event->pos());
614 const int mousePos = cursorAtMouse.position();
615 QTextCursor cursor = textCursor();
618 const bool selectedWordClicked = cursor.hasSelection() &&
619 mousePos >= cursor.selectionStart() &&
620 mousePos <= cursor.selectionEnd();
624 QTextCursor wordSelectCursor(cursorAtMouse);
625 wordSelectCursor.clearSelection();
626 wordSelectCursor.select(QTextCursor::WordUnderCursor);
627 QString selectedWord = wordSelectCursor.selectedText();
629 bool isMouseCursorInsideWord =
true;
630 if ((mousePos < wordSelectCursor.selectionStart() ||
631 mousePos >= wordSelectCursor.selectionEnd())
632 && (selectedWord.length() > 1)) {
633 isMouseCursorInsideWord =
false;
637 wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size());
638 if (selectedWord.startsWith(
'\'') || selectedWord.startsWith(
'\"')) {
639 selectedWord = selectedWord.right(selectedWord.size() - 1);
640 wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
642 if (selectedWord.endsWith(
'\'') || selectedWord.endsWith(
'\"'))
643 selectedWord.chop(1);
645 wordSelectCursor.movePosition(QTextCursor::NextCharacter,
646 QTextCursor::KeepAnchor, selectedWord.size());
648 const bool wordIsMisspelled = isMouseCursorInsideWord &&
650 !selectedWord.isEmpty() &&
658 bool inQuote =
false;
659 if (d->spellInterface &&
660 !d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text()))
662 if (!selectedWordClicked) {
663 if (wordIsMisspelled && !inQuote)
664 setTextCursor(wordSelectCursor);
666 setTextCursor(cursorAtMouse);
667 cursor = textCursor();
672 if (!wordIsMisspelled || selectedWordClicked || inQuote) {
673 QMetaObject::invokeMethod(
this,
"mousePopupMenuImplementation", Q_ARG(
QPoint, event->globalPos()));
680 if (reps.isEmpty()) {
681 QAction *suggestionsAction = menu.addAction(
i18n(
"No suggestions for %1", selectedWord));
682 suggestionsAction->setEnabled(
false);
685 QStringList::const_iterator
end(reps.constEnd());
686 for (QStringList::const_iterator it = reps.constBegin(); it !=
end; ++it) {
693 QAction *ignoreAction = menu.addAction(
i18n(
"Ignore"));
694 QAction *addToDictAction = menu.addAction(
i18n(
"Add to Dictionary"));
696 const QAction *selectedAction = menu.exec(event->globalPos());
698 if (selectedAction) {
699 Q_ASSERT(cursor.selectedText() == selectedWord);
701 if (selectedAction == ignoreAction) {
705 else if (selectedAction == addToDictAction) {
712 const QString replacement = selectedAction->text();
713 Q_ASSERT(reps.contains(replacement));
714 cursor.insertText(replacement);
715 setTextCursor(cursor);
724 QTextEdit::wheelEvent( event );
726 QAbstractScrollArea::wheelEvent( event );
736 return d->highlighter;
741 delete d->highlighter;
742 d->highlighter = _highLighter;
747 if (d->spellInterface)
748 d->spellInterface->setSpellCheckingEnabled(check);
756 if ( check == d->checkSpellingEnabled )
763 d->checkSpellingEnabled = check;
774 delete d->highlighter;
781 if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter )
784 if (!d->clickMessage.isEmpty()) {
785 d->updateClickMessageRect();
787 QTextEdit::focusInEvent( event );
792 if (d->spellInterface)
793 return d->spellInterface->isSpellCheckingEnabled();
800 return d->checkSpellingEnabled;
805 if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter )
808 if ( readOnly == isReadOnly() )
812 delete d->highlighter;
815 d->customPalette = testAttribute( Qt::WA_SetPalette );
816 QPalette p = palette();
817 QColor color = p.color( QPalette::Disabled, QPalette::Background );
818 p.setColor( QPalette::Base, color );
819 p.setColor( QPalette::Background, color );
822 if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) {
823 QPalette p = palette();
824 QColor color = p.color( QPalette::Normal, QPalette::Base );
825 p.setColor( QPalette::Base, color );
826 p.setColor( QPalette::Background, color );
829 setPalette( QPalette() );
832 QTextEdit::setReadOnly( readOnly );
837 d->checkSpelling(
false);
842 d->checkSpelling(
true);
847 QTextCursor cursor(document());
848 cursor.setPosition(pos);
849 cursor.setPosition(pos+length,QTextCursor::KeepAnchor);
850 setTextCursor (cursor);
851 ensureCursorVisible();
856 if ( document()->isEmpty() )
864 connect( d->repDlg, SIGNAL(okClicked()),
this, SLOT(
slotDoReplace()) );
876 if(d->repDlg->pattern().isEmpty()) {
879 ensureCursorVisible();
884 d->replace =
new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(),
this);
887 d->repIndex = textCursor().anchor();
892 connect(d->replace, SIGNAL(highlight(
QString,
int,
int)),
893 this, SLOT(slotFindHighlight(
QString,
int,
int)));
896 this, SLOT(slotReplaceText(
QString,
int,
int,
int)));
908 d->lastReplacedPosition = -1;
910 textCursor().beginEditBlock();
911 viewport()->setUpdatesEnabled(
false);
916 if (d->replace->needData())
917 d->replace->setData(toPlainText(), d->repIndex);
918 res = d->replace->replace();
920 textCursor().endEditBlock();
921 if (d->lastReplacedPosition >= 0) {
922 QTextCursor tc = textCursor();
923 tc.setPosition(d->lastReplacedPosition);
925 ensureCursorVisible();
928 viewport()->setUpdatesEnabled(
true);
929 viewport()->update();
933 d->replace->displayFinalDialog();
934 d->replace->disconnect(
this);
935 d->replace->deleteLater();
937 ensureCursorVisible();
951 if( d->findDlg->pattern().isEmpty())
958 d->find =
new KFind(d->findDlg->pattern(), d->findDlg->options(),
this);
961 d->findIndex = textCursor().anchor();
966 connect(d->find, SIGNAL(highlight(
QString,
int,
int)),
967 this, SLOT(slotFindHighlight(
QString,
int,
int)));
971 d->find->closeFindNextDialog();
980 if(document()->isEmpty())
982 d->find->disconnect(
this);
983 d->find->deleteLater();
989 if (d->find->needData())
990 d->find->setData(toPlainText(), d->findIndex);
991 res = d->find->find();
994 d->find->displayFinalDialog();
995 d->find->disconnect(
this);
996 d->find->deleteLater();
1007 if ( document()->isEmpty() )
1014 connect( d->findDlg, SIGNAL(okClicked()),
this, SLOT(
slotDoFind()) );
1022 if ( document()->isEmpty() )
1030 connect( d->repDlg, SIGNAL(okClicked()),
this, SLOT(
slotDoReplace()) );
1037 d->findReplaceEnabled = enabled;
1042 d->showTabAction = show;
1047 d->spellInterface = spellInterface;
1050 bool KTextEdit::Private::overrideShortcut(
const QKeyEvent* event)
1052 const int key =
event->key() |
event->modifiers();
1094 }
else if (event->modifiers() == Qt::ControlModifier &&
1095 (
event->key() == Qt::Key_Return ||
event->key() == Qt::Key_Enter) &&
1096 qobject_cast<KDialog*>(parent->window()) ) {
1105 if (d->handleShortcut(event)) {
1107 }
else if (event->modifiers() == Qt::ControlModifier &&
1108 (
event->key() == Qt::Key_Return ||
event->key() == Qt::Key_Enter) &&
1109 qobject_cast<KDialog*>(window()) ) {
1112 QTextEdit::keyPressEvent(event);
1118 if (msg != d->clickMessage) {
1119 if (!d->clickMessage.isEmpty()) {
1120 d->updateClickMessageRect();
1122 d->clickMessage = msg;
1123 if (!d->clickMessage.isEmpty()) {
1124 d->updateClickMessageRect();
1131 return d->clickMessage;
1136 QTextEdit::paintEvent(ev);
1138 if (!d->clickMessage.isEmpty() && !hasFocus() && document()->isEmpty()) {
1139 QPainter p(viewport());
1142 f.setItalic(d->italicizePlaceholder);
1145 QColor color(palette().color(viewport()->foregroundRole()));
1146 color.setAlphaF(0.5);
1149 int margin = int(document()->documentMargin());
1150 QRect cr = viewport()->rect().adjusted(margin, margin, -margin, -margin);
1152 p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage);
1158 if (!d->clickMessage.isEmpty()) {
1159 d->updateClickMessageRect();
1161 QTextEdit::focusOutEvent(ev);
1166 d->showAutoCorrectionButton = show;
1179 #include "ktextedit.moc"