1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquicktextcontrol_p.h"
43 #include "qquicktextcontrol_p_p.h"
45 #ifndef QT_NO_TEXTCONTROL
52 #include <qclipboard.h>
54 #include <qinputpanel.h>
55 #include "private/qtextdocumentlayout_p.h"
56 #include "private/qabstracttextdocumentlayout_p.h"
57 #include "qtextdocument.h"
58 #include "private/qtextdocument_p.h"
59 #include "qtextlist.h"
60 #include "qtextdocumentwriter.h"
61 #include "private/qtextcursor_p.h"
62 #include "qpagedpaintdevice.h"
63 #include "private/qpagedpaintdevice_p.h"
65 #include <qtextformat.h>
66 #include <qdatetime.h>
68 #include <qguiapplication.h>
70 #include <qtexttable.h>
73 #include <qstylehints.h>
75 // ### these should come from QStyleHints
76 const int textCursorWidth = 1;
77 const bool fullWidthSelection = true;
81 #ifndef QT_NO_CONTEXTMENU
84 // could go into QTextCursor...
85 static QTextLine currentTextLine(const QTextCursor &cursor)
87 const QTextBlock block = cursor.block();
91 const QTextLayout *layout = block.layout();
95 const int relativePos = cursor.position() - block.position();
96 return layout->lineForTextPosition(relativePos);
99 QQuickTextControlPrivate::QQuickTextControlPrivate()
100 : doc(0), cursorOn(false), cursorIsFocusIndicator(false),
101 interactionFlags(Qt::TextEditorInteraction),
103 lastSelectionState(false), ignoreAutomaticScrollbarAdjustement(false),
104 overwriteMode(false),
105 acceptRichText(true),
106 preeditCursor(0), hideCursor(false),
109 hadSelectionOnMousePress(false),
110 wordSelectionEnabled(false)
113 bool QQuickTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e)
115 #ifdef QT_NO_SHORTCUT
119 Q_Q(QQuickTextControl);
123 const QTextCursor oldSelection = cursor;
124 const int oldCursorPos = cursor.position();
126 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
127 QTextCursor::MoveOperation op = QTextCursor::NoMove;
131 #ifndef QT_NO_SHORTCUT
132 if (e == QKeySequence::MoveToNextChar) {
133 op = QTextCursor::Right;
135 else if (e == QKeySequence::MoveToPreviousChar) {
136 op = QTextCursor::Left;
138 else if (e == QKeySequence::SelectNextChar) {
139 op = QTextCursor::Right;
140 mode = QTextCursor::KeepAnchor;
142 else if (e == QKeySequence::SelectPreviousChar) {
143 op = QTextCursor::Left;
144 mode = QTextCursor::KeepAnchor;
146 else if (e == QKeySequence::SelectNextWord) {
147 op = QTextCursor::WordRight;
148 mode = QTextCursor::KeepAnchor;
150 else if (e == QKeySequence::SelectPreviousWord) {
151 op = QTextCursor::WordLeft;
152 mode = QTextCursor::KeepAnchor;
154 else if (e == QKeySequence::SelectStartOfLine) {
155 op = QTextCursor::StartOfLine;
156 mode = QTextCursor::KeepAnchor;
158 else if (e == QKeySequence::SelectEndOfLine) {
159 op = QTextCursor::EndOfLine;
160 mode = QTextCursor::KeepAnchor;
162 else if (e == QKeySequence::SelectStartOfBlock) {
163 op = QTextCursor::StartOfBlock;
164 mode = QTextCursor::KeepAnchor;
166 else if (e == QKeySequence::SelectEndOfBlock) {
167 op = QTextCursor::EndOfBlock;
168 mode = QTextCursor::KeepAnchor;
170 else if (e == QKeySequence::SelectStartOfDocument) {
171 op = QTextCursor::Start;
172 mode = QTextCursor::KeepAnchor;
174 else if (e == QKeySequence::SelectEndOfDocument) {
175 op = QTextCursor::End;
176 mode = QTextCursor::KeepAnchor;
178 else if (e == QKeySequence::SelectPreviousLine) {
179 op = QTextCursor::Up;
180 mode = QTextCursor::KeepAnchor;
182 else if (e == QKeySequence::SelectNextLine) {
183 op = QTextCursor::Down;
184 mode = QTextCursor::KeepAnchor;
186 QTextBlock block = cursor.block();
187 QTextLine line = currentTextLine(cursor);
188 if (!block.next().isValid()
190 && line.lineNumber() == block.layout()->lineCount() - 1)
191 op = QTextCursor::End;
194 else if (e == QKeySequence::MoveToNextWord) {
195 op = QTextCursor::WordRight;
197 else if (e == QKeySequence::MoveToPreviousWord) {
198 op = QTextCursor::WordLeft;
200 else if (e == QKeySequence::MoveToEndOfBlock) {
201 op = QTextCursor::EndOfBlock;
203 else if (e == QKeySequence::MoveToStartOfBlock) {
204 op = QTextCursor::StartOfBlock;
206 else if (e == QKeySequence::MoveToNextLine) {
207 op = QTextCursor::Down;
209 else if (e == QKeySequence::MoveToPreviousLine) {
210 op = QTextCursor::Up;
212 else if (e == QKeySequence::MoveToStartOfLine) {
213 op = QTextCursor::StartOfLine;
215 else if (e == QKeySequence::MoveToEndOfLine) {
216 op = QTextCursor::EndOfLine;
218 else if (e == QKeySequence::MoveToStartOfDocument) {
219 op = QTextCursor::Start;
221 else if (e == QKeySequence::MoveToEndOfDocument) {
222 op = QTextCursor::End;
224 #endif // QT_NO_SHORTCUT
229 // Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but
230 // here's the breakdown:
231 // Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
232 // Alt (Option), or Meta (Control).
233 // Command/Control + Left/Right -- Move to left or right of the line
234 // + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
235 // Option + Left/Right -- Move one word Left/right.
236 // + Up/Down -- Begin/End of Paragraph.
237 // Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
239 bool visualNavigation = cursor.visualNavigation();
240 cursor.setVisualNavigation(true);
241 const bool moved = cursor.movePosition(op, mode);
242 cursor.setVisualNavigation(visualNavigation);
243 q->ensureCursorVisible();
245 bool isNavigationEvent
246 = e->key() == Qt::Key_Up
247 || e->key() == Qt::Key_Down
248 || e->key() == Qt::Key_Left
249 || e->key() == Qt::Key_Right;
252 if (cursor.position() != oldCursorPos)
253 emit q->cursorPositionChanged();
254 emit q->microFocusChanged();
255 } else if (isNavigationEvent && oldSelection.anchor() == cursor.anchor()) {
259 selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor));
261 repaintOldAndNewSelection(oldSelection);
266 void QQuickTextControlPrivate::updateCurrentCharFormat()
268 Q_Q(QQuickTextControl);
270 QTextCharFormat fmt = cursor.charFormat();
271 if (fmt == lastCharFormat)
273 lastCharFormat = fmt;
275 emit q->currentCharFormatChanged(fmt);
276 emit q->microFocusChanged();
279 void QQuickTextControlPrivate::init(Qt::TextFormat format, const QString &text, QTextDocument *document)
281 Q_Q(QQuickTextControl);
282 setContent(format, text, document);
284 doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable);
285 q->setCursorWidth(-1);
288 void QQuickTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document)
290 Q_Q(QQuickTextControl);
292 // for use when called from setPlainText. we may want to re-use the currently
293 // set char format then.
294 const QTextCharFormat charFormatForInsertion = cursor.charFormat();
296 bool clearDocument = true;
300 clearDocument = false;
302 palette = QGuiApplication::palette();
303 doc = new QTextDocument(q);
305 _q_documentLayoutChanged();
306 cursor = QTextCursor(doc);
308 // #### doc->documentLayout()->setPaintDevice(viewport);
310 QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_updateCurrentCharFormatAndSelection()));
311 QObject::connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), q, SLOT(_q_emitCursorPosChanged(QTextCursor)));
312 QObject::connect(doc, SIGNAL(documentLayoutChanged()), q, SLOT(_q_documentLayoutChanged()));
314 // convenience signal forwards
315 QObject::connect(doc, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool)));
316 QObject::connect(doc, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool)));
317 QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool)));
318 QObject::connect(doc, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int)));
321 bool previousUndoRedoState = doc->isUndoRedoEnabled();
323 doc->setUndoRedoEnabled(false);
325 //Saving the index save some time.
326 static int contentsChangedIndex = QTextDocument::staticMetaObject.indexOfSignal("contentsChanged()");
327 static int textChangedIndex = QQuickTextControl::staticMetaObject.indexOfSignal("textChanged()");
328 // avoid multiple textChanged() signals being emitted
329 QMetaObject::disconnect(doc, contentsChangedIndex, q, textChangedIndex);
331 if (!text.isEmpty()) {
332 // clear 'our' cursor for insertion to prevent
333 // the emission of the cursorPositionChanged() signal.
334 // instead we emit it only once at the end instead of
335 // at the end of the document after loading and when
336 // positioning the cursor again to the start of the
338 cursor = QTextCursor();
339 if (format == Qt::PlainText) {
340 QTextCursor formatCursor(doc);
341 // put the setPlainText and the setCharFormat into one edit block,
342 // so that the syntax highlight triggers only /once/ for the entire
343 // document, not twice.
344 formatCursor.beginEditBlock();
345 doc->setPlainText(text);
346 doc->setUndoRedoEnabled(false);
347 formatCursor.select(QTextCursor::Document);
348 formatCursor.setCharFormat(charFormatForInsertion);
349 formatCursor.endEditBlock();
351 #ifndef QT_NO_TEXTHTMLPARSER
354 doc->setPlainText(text);
356 doc->setUndoRedoEnabled(false);
358 cursor = QTextCursor(doc);
359 } else if (clearDocument) {
362 cursor.setCharFormat(charFormatForInsertion);
364 QMetaObject::connect(doc, contentsChangedIndex, q, textChangedIndex);
365 emit q->textChanged();
367 doc->setUndoRedoEnabled(previousUndoRedoState);
368 _q_updateCurrentCharFormatAndSelection();
370 doc->setModified(false);
372 q->ensureCursorVisible();
373 emit q->cursorPositionChanged();
376 void QQuickTextControlPrivate::setCursorPosition(const QPointF &pos)
378 Q_Q(QQuickTextControl);
379 const int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
382 cursor.setPosition(cursorPos);
385 void QQuickTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode)
387 cursor.setPosition(pos, mode);
389 if (mode != QTextCursor::KeepAnchor) {
390 selectedWordOnDoubleClick = QTextCursor();
391 selectedBlockOnTrippleClick = QTextCursor();
395 void QQuickTextControlPrivate::repaintCursor()
397 Q_Q(QQuickTextControl);
398 emit q->updateCursorRequest(cursorRectPlusUnicodeDirectionMarkers(cursor));
401 void QQuickTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection)
403 Q_Q(QQuickTextControl);
404 if (cursor.hasSelection()
405 && oldSelection.hasSelection()
406 && cursor.currentFrame() == oldSelection.currentFrame()
407 && !cursor.hasComplexSelection()
408 && !oldSelection.hasComplexSelection()
409 && cursor.anchor() == oldSelection.anchor()
411 QTextCursor differenceSelection(doc);
412 differenceSelection.setPosition(oldSelection.position());
413 differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor);
414 emit q->updateRequest(q->selectionRect(differenceSelection));
416 if (!oldSelection.hasSelection() && !cursor.hasSelection()) {
417 if (!oldSelection.isNull())
418 emit q->updateCursorRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection));
419 emit q->updateCursorRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor));
422 if (!oldSelection.isNull())
423 emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection));
424 emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor));
429 void QQuickTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/)
431 Q_Q(QQuickTextControl);
432 if (forceEmitSelectionChanged)
433 emit q->selectionChanged();
435 bool current = cursor.hasSelection();
436 if (current == lastSelectionState)
439 lastSelectionState = current;
440 emit q->copyAvailable(current);
441 if (!forceEmitSelectionChanged)
442 emit q->selectionChanged();
443 emit q->microFocusChanged();
446 void QQuickTextControlPrivate::_q_updateCurrentCharFormatAndSelection()
448 updateCurrentCharFormat();
452 #ifndef QT_NO_CLIPBOARD
453 void QQuickTextControlPrivate::setClipboardSelection()
455 QClipboard *clipboard = QGuiApplication::clipboard();
456 if (!cursor.hasSelection() || !clipboard->supportsSelection())
458 Q_Q(QQuickTextControl);
459 QMimeData *data = q->createMimeDataFromSelection();
460 clipboard->setMimeData(data, QClipboard::Selection);
464 void QQuickTextControlPrivate::_q_emitCursorPosChanged(const QTextCursor &someCursor)
466 Q_Q(QQuickTextControl);
467 if (someCursor.isCopyOf(cursor)) {
468 emit q->cursorPositionChanged();
469 emit q->microFocusChanged();
473 void QQuickTextControlPrivate::_q_documentLayoutChanged()
475 Q_Q(QQuickTextControl);
476 QAbstractTextDocumentLayout *layout = doc->documentLayout();
477 QObject::connect(layout, SIGNAL(update(QRectF)), q, SIGNAL(updateRequest(QRectF)));
478 QObject::connect(layout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(_q_updateBlock(QTextBlock)));
479 QObject::connect(layout, SIGNAL(documentSizeChanged(QSizeF)), q, SIGNAL(documentSizeChanged(QSizeF)));
483 void QQuickTextControlPrivate::setBlinkingCursorEnabled(bool enable)
485 Q_Q(QQuickTextControl);
487 if (enable && qApp->styleHints()->cursorFlashTime() > 0)
488 cursorBlinkTimer.start(qApp->styleHints()->cursorFlashTime() / 2, q);
490 cursorBlinkTimer.stop();
497 void QQuickTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition)
499 Q_Q(QQuickTextControl);
501 // if inside the initial selected word keep that
502 if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart()
503 && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) {
504 q->setTextCursor(selectedWordOnDoubleClick);
508 QTextCursor curs = selectedWordOnDoubleClick;
509 curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
511 if (!curs.movePosition(QTextCursor::StartOfWord))
513 const int wordStartPos = curs.position();
515 const int blockPos = curs.block().position();
516 const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft();
518 QTextLine line = currentTextLine(curs);
522 const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
524 if (!curs.movePosition(QTextCursor::EndOfWord))
526 const int wordEndPos = curs.position();
528 const QTextLine otherLine = currentTextLine(curs);
529 if (otherLine.textStart() != line.textStart()
530 || wordEndPos == wordStartPos)
533 const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
535 if (!wordSelectionEnabled && (mouseXPosition < wordStartX || mouseXPosition > wordEndX))
538 if (wordSelectionEnabled) {
539 if (suggestedNewPosition < selectedWordOnDoubleClick.position()) {
540 cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
541 setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
543 cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
544 setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
547 // keep the already selected word even when moving to the left
549 if (suggestedNewPosition < selectedWordOnDoubleClick.position())
550 cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
552 cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
554 const qreal differenceToStart = mouseXPosition - wordStartX;
555 const qreal differenceToEnd = wordEndX - mouseXPosition;
557 if (differenceToStart < differenceToEnd)
558 setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
560 setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
563 if (interactionFlags & Qt::TextSelectableByMouse) {
564 #ifndef QT_NO_CLIPBOARD
565 setClipboardSelection();
567 selectionChanged(true);
571 void QQuickTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition)
573 Q_Q(QQuickTextControl);
575 // if inside the initial selected line keep that
576 if (suggestedNewPosition >= selectedBlockOnTrippleClick.selectionStart()
577 && suggestedNewPosition <= selectedBlockOnTrippleClick.selectionEnd()) {
578 q->setTextCursor(selectedBlockOnTrippleClick);
582 if (suggestedNewPosition < selectedBlockOnTrippleClick.position()) {
583 cursor.setPosition(selectedBlockOnTrippleClick.selectionEnd());
584 cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
585 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
587 cursor.setPosition(selectedBlockOnTrippleClick.selectionStart());
588 cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
589 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
590 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
593 if (interactionFlags & Qt::TextSelectableByMouse) {
594 #ifndef QT_NO_CLIPBOARD
595 setClipboardSelection();
597 selectionChanged(true);
601 void QQuickTextControlPrivate::_q_deleteSelected()
603 if (!(interactionFlags & Qt::TextEditable) || !cursor.hasSelection())
605 cursor.removeSelectedText();
608 void QQuickTextControl::undo()
610 Q_D(QQuickTextControl);
611 d->repaintSelection();
612 const int oldCursorPos = d->cursor.position();
613 d->doc->undo(&d->cursor);
614 if (d->cursor.position() != oldCursorPos)
615 emit cursorPositionChanged();
616 emit microFocusChanged();
617 ensureCursorVisible();
620 void QQuickTextControl::redo()
622 Q_D(QQuickTextControl);
623 d->repaintSelection();
624 const int oldCursorPos = d->cursor.position();
625 d->doc->redo(&d->cursor);
626 if (d->cursor.position() != oldCursorPos)
627 emit cursorPositionChanged();
628 emit microFocusChanged();
629 ensureCursorVisible();
632 QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
633 : QObject(*new QQuickTextControlPrivate, parent)
635 Q_D(QQuickTextControl);
636 d->init(Qt::RichText, QString(), doc);
639 QQuickTextControl::~QQuickTextControl()
643 void QQuickTextControl::setView(QObject *view)
645 Q_D(QQuickTextControl);
646 d->contextObject = view;
649 QObject *QQuickTextControl::view() const
651 Q_D(const QQuickTextControl);
652 return d->contextObject;
655 QTextDocument *QQuickTextControl::document() const
657 Q_D(const QQuickTextControl);
661 void QQuickTextControl::setTextCursor(const QTextCursor &cursor)
663 Q_D(QQuickTextControl);
665 d->cursorIsFocusIndicator = false;
666 const bool posChanged = cursor.position() != d->cursor.position();
667 const QTextCursor oldSelection = d->cursor;
669 d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable);
670 d->_q_updateCurrentCharFormatAndSelection();
671 ensureCursorVisible();
672 d->repaintOldAndNewSelection(oldSelection);
674 emit cursorPositionChanged();
677 QTextCursor QQuickTextControl::textCursor() const
679 Q_D(const QQuickTextControl);
683 #ifndef QT_NO_CLIPBOARD
685 void QQuickTextControl::cut()
687 Q_D(QQuickTextControl);
688 if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection())
691 d->cursor.removeSelectedText();
694 void QQuickTextControl::copy()
696 Q_D(QQuickTextControl);
697 if (!d->cursor.hasSelection())
699 QMimeData *data = createMimeDataFromSelection();
700 QGuiApplication::clipboard()->setMimeData(data);
703 void QQuickTextControl::paste(QClipboard::Mode mode)
705 const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode);
707 insertFromMimeData(md);
711 void QQuickTextControl::clear()
713 Q_D(QQuickTextControl);
714 // clears and sets empty content
719 void QQuickTextControl::selectAll()
721 Q_D(QQuickTextControl);
722 const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor());
723 d->cursor.select(QTextCursor::Document);
724 d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor()));
725 d->cursorIsFocusIndicator = false;
726 emit updateRequest();
729 void QQuickTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset)
732 m.translate(coordinateOffset.x(), coordinateOffset.y());
736 void QQuickTextControl::processEvent(QEvent *e, const QMatrix &matrix)
738 Q_D(QQuickTextControl);
739 if (d->interactionFlags == Qt::NoTextInteraction) {
745 case QEvent::KeyPress:
746 d->keyPressEvent(static_cast<QKeyEvent *>(e));
748 case QEvent::MouseButtonPress: {
749 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
750 d->mousePressEvent(ev, matrix.map(ev->localPos()));
752 case QEvent::MouseMove: {
753 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
754 d->mouseMoveEvent(ev, matrix.map(ev->localPos()));
756 case QEvent::MouseButtonRelease: {
757 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
758 d->mouseReleaseEvent(ev, matrix.map(ev->localPos()));
760 case QEvent::MouseButtonDblClick: {
761 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
762 d->mouseDoubleClickEvent(ev, matrix.map(ev->localPos()));
764 case QEvent::InputMethod:
765 d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
767 case QEvent::FocusIn:
768 case QEvent::FocusOut:
769 d->focusEvent(static_cast<QFocusEvent *>(e));
772 case QEvent::EnabledChange:
773 d->isEnabled = e->isAccepted();
776 case QEvent::ShortcutOverride:
777 if (d->interactionFlags & Qt::TextEditable) {
778 QKeyEvent* ke = static_cast<QKeyEvent *>(e);
779 if (ke->modifiers() == Qt::NoModifier
780 || ke->modifiers() == Qt::ShiftModifier
781 || ke->modifiers() == Qt::KeypadModifier) {
782 if (ke->key() < Qt::Key_Escape) {
791 case Qt::Key_Backspace:
802 #ifndef QT_NO_SHORTCUT
803 } else if (ke == QKeySequence::Copy
804 || ke == QKeySequence::Paste
805 || ke == QKeySequence::Cut
806 || ke == QKeySequence::Redo
807 || ke == QKeySequence::Undo
808 || ke == QKeySequence::MoveToNextWord
809 || ke == QKeySequence::MoveToPreviousWord
810 || ke == QKeySequence::MoveToStartOfDocument
811 || ke == QKeySequence::MoveToEndOfDocument
812 || ke == QKeySequence::SelectNextWord
813 || ke == QKeySequence::SelectPreviousWord
814 || ke == QKeySequence::SelectStartOfLine
815 || ke == QKeySequence::SelectEndOfLine
816 || ke == QKeySequence::SelectStartOfBlock
817 || ke == QKeySequence::SelectEndOfBlock
818 || ke == QKeySequence::SelectStartOfDocument
819 || ke == QKeySequence::SelectEndOfDocument
820 || ke == QKeySequence::SelectAll
832 bool QQuickTextControl::event(QEvent *e)
834 return QObject::event(e);
837 void QQuickTextControl::timerEvent(QTimerEvent *e)
839 Q_D(QQuickTextControl);
840 if (e->timerId() == d->cursorBlinkTimer.timerId()) {
841 d->cursorOn = !d->cursorOn;
844 // if (d->cursor.hasSelection())
845 // d->cursorOn &= (QGuiApplication::style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected)
849 } else if (e->timerId() == d->trippleClickTimer.timerId()) {
850 d->trippleClickTimer.stop();
854 void QQuickTextControl::setPlainText(const QString &text)
856 Q_D(QQuickTextControl);
857 d->setContent(Qt::PlainText, text);
860 void QQuickTextControl::setHtml(const QString &text)
862 Q_D(QQuickTextControl);
863 d->setContent(Qt::RichText, text);
866 void QQuickTextControlPrivate::keyPressEvent(QKeyEvent *e)
868 Q_Q(QQuickTextControl);
869 #ifndef QT_NO_SHORTCUT
870 if (e == QKeySequence::SelectAll) {
875 #ifndef QT_NO_CLIPBOARD
876 else if (e == QKeySequence::Copy) {
882 #endif // QT_NO_SHORTCUT
884 if (interactionFlags & Qt::TextSelectableByKeyboard
885 && cursorMoveKeyEvent(e))
888 if (interactionFlags & Qt::LinksAccessibleByKeyboard) {
889 if ((e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && cursor.hasSelection()) {
891 activateLinkUnderCursor();
896 if (!(interactionFlags & Qt::TextEditable)) {
901 if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) {
902 QTextBlockFormat fmt;
903 fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
904 cursor.mergeBlockFormat(fmt);
908 // schedule a repaint of the region of the cursor, as when we move it we
909 // want to make sure the old cursor disappears (not noticeable when moving
910 // only a few pixels but noticeable when jumping between cells in tables for
914 if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) {
915 QTextBlockFormat blockFmt = cursor.blockFormat();
916 QTextList *list = cursor.currentList();
917 if (list && cursor.atBlockStart() && !cursor.hasSelection()) {
918 list->remove(cursor.block());
919 } else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
920 blockFmt.setIndent(blockFmt.indent() - 1);
921 cursor.setBlockFormat(blockFmt);
923 QTextCursor localCursor = cursor;
924 localCursor.deletePreviousChar();
928 #ifndef QT_NO_SHORTCUT
929 else if (e == QKeySequence::InsertParagraphSeparator) {
930 cursor.insertBlock();
933 } else if (e == QKeySequence::InsertLineSeparator) {
934 cursor.insertText(QString(QChar::LineSeparator));
941 #ifndef QT_NO_SHORTCUT
942 else if (e == QKeySequence::Undo) {
945 else if (e == QKeySequence::Redo) {
948 #ifndef QT_NO_CLIPBOARD
949 else if (e == QKeySequence::Cut) {
952 else if (e == QKeySequence::Paste) {
953 QClipboard::Mode mode = QClipboard::Clipboard;
957 else if (e == QKeySequence::Delete) {
958 QTextCursor localCursor = cursor;
959 localCursor.deleteChar();
961 else if (e == QKeySequence::DeleteEndOfWord) {
962 if (!cursor.hasSelection())
963 cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
964 cursor.removeSelectedText();
966 else if (e == QKeySequence::DeleteStartOfWord) {
967 if (!cursor.hasSelection())
968 cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
969 cursor.removeSelectedText();
971 else if (e == QKeySequence::DeleteEndOfLine) {
972 QTextBlock block = cursor.block();
973 if (cursor.position() == block.position() + block.length() - 2)
974 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
976 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
977 cursor.removeSelectedText();
979 #endif // QT_NO_SHORTCUT
987 QString text = e->text();
988 if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) {
989 cursor.insertText(text);
1002 q->ensureCursorVisible();
1004 updateCurrentCharFormat();
1007 void QQuickTextControlPrivate::_q_updateBlock(const QTextBlock &block)
1009 Q_Q(QQuickTextControl);
1010 QRectF br = q->blockBoundingRect(block);
1011 br.setRight(qreal(INT_MAX)); // the block might have shrunk
1012 emit q->updateRequest(br);
1015 QRectF QQuickTextControlPrivate::rectForPosition(int position) const
1017 Q_Q(const QQuickTextControl);
1018 const QTextBlock block = doc->findBlock(position);
1019 if (!block.isValid())
1021 const QAbstractTextDocumentLayout *docLayout = doc->documentLayout();
1022 const QTextLayout *layout = block.layout();
1023 const QPointF layoutPos = q->blockBoundingRect(block).topLeft();
1024 int relativePos = position - block.position();
1025 if (preeditCursor != 0) {
1026 int preeditPos = layout->preeditAreaPosition();
1027 if (relativePos == preeditPos)
1028 relativePos += preeditCursor;
1029 else if (relativePos > preeditPos)
1030 relativePos += layout->preeditAreaText().length();
1032 QTextLine line = layout->lineForTextPosition(relativePos);
1037 #ifndef QT_NO_PROPERTIES
1038 cursorWidth = docLayout->property("cursorWidth").toInt(&ok);
1046 if (line.isValid()) {
1047 qreal x = line.cursorToX(relativePos);
1049 r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), cursorWidth + w, line.height());
1051 r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height
1057 static inline bool firstFramePosLessThanCursorPos(QTextFrame *frame, int position)
1059 return frame->firstPosition() < position;
1062 static inline bool cursorPosLessThanLastFramePos(int position, QTextFrame *frame)
1064 return position < frame->lastPosition();
1067 static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor)
1070 QTextFrame *frame = cursor.currentFrame();
1071 const QList<QTextFrame *> children = frame->childFrames();
1073 const QList<QTextFrame *>::ConstIterator firstFrame = qLowerBound(children.constBegin(), children.constEnd(),
1074 cursor.selectionStart(), firstFramePosLessThanCursorPos);
1075 const QList<QTextFrame *>::ConstIterator lastFrame = qUpperBound(children.constBegin(), children.constEnd(),
1076 cursor.selectionEnd(), cursorPosLessThanLastFramePos);
1077 for (QList<QTextFrame *>::ConstIterator it = firstFrame; it != lastFrame; ++it) {
1078 if ((*it)->frameFormat().position() != QTextFrameFormat::InFlow)
1079 r |= frame->document()->documentLayout()->frameBoundingRect(*it);
1084 QRectF QQuickTextControl::selectionRect(const QTextCursor &cursor) const
1086 Q_D(const QQuickTextControl);
1088 QRectF r = d->rectForPosition(cursor.selectionStart());
1090 if (cursor.hasComplexSelection() && cursor.currentTable()) {
1091 QTextTable *table = cursor.currentTable();
1093 r = d->doc->documentLayout()->frameBoundingRect(table);
1095 int firstRow, numRows, firstColumn, numColumns;
1096 cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
1098 const QTextTableCell firstCell = table->cellAt(firstRow, firstColumn);
1099 const QTextTableCell lastCell = table->cellAt(firstRow + numRows - 1, firstColumn + numColumns - 1);
1101 const QAbstractTextDocumentLayout * const layout = doc->documentLayout();
1103 QRectF tableSelRect = layout->blockBoundingRect(firstCell.firstCursorPosition().block());
1105 for (int col = firstColumn; col < firstColumn + numColumns; ++col) {
1106 const QTextTableCell cell = table->cellAt(firstRow, col);
1107 const qreal y = layout->blockBoundingRect(cell.firstCursorPosition().block()).top();
1109 tableSelRect.setTop(qMin(tableSelRect.top(), y));
1112 for (int row = firstRow; row < firstRow + numRows; ++row) {
1113 const QTextTableCell cell = table->cellAt(row, firstColumn);
1114 const qreal x = layout->blockBoundingRect(cell.firstCursorPosition().block()).left();
1116 tableSelRect.setLeft(qMin(tableSelRect.left(), x));
1119 for (int col = firstColumn; col < firstColumn + numColumns; ++col) {
1120 const QTextTableCell cell = table->cellAt(firstRow + numRows - 1, col);
1121 const qreal y = layout->blockBoundingRect(cell.lastCursorPosition().block()).bottom();
1123 tableSelRect.setBottom(qMax(tableSelRect.bottom(), y));
1126 for (int row = firstRow; row < firstRow + numRows; ++row) {
1127 const QTextTableCell cell = table->cellAt(row, firstColumn + numColumns - 1);
1128 const qreal x = layout->blockBoundingRect(cell.lastCursorPosition().block()).right();
1130 tableSelRect.setRight(qMax(tableSelRect.right(), x));
1133 r = tableSelRect.toRect();
1135 } else if (cursor.hasSelection()) {
1136 const int position = cursor.selectionStart();
1137 const int anchor = cursor.selectionEnd();
1138 const QTextBlock posBlock = d->doc->findBlock(position);
1139 const QTextBlock anchorBlock = d->doc->findBlock(anchor);
1140 if (posBlock == anchorBlock && posBlock.isValid() && posBlock.layout()->lineCount()) {
1141 const QTextLine posLine = posBlock.layout()->lineForTextPosition(position - posBlock.position());
1142 const QTextLine anchorLine = anchorBlock.layout()->lineForTextPosition(anchor - anchorBlock.position());
1144 const int firstLine = qMin(posLine.lineNumber(), anchorLine.lineNumber());
1145 const int lastLine = qMax(posLine.lineNumber(), anchorLine.lineNumber());
1146 const QTextLayout *layout = posBlock.layout();
1148 for (int i = firstLine; i <= lastLine; ++i) {
1149 r |= layout->lineAt(i).rect();
1150 r |= layout->lineAt(i).naturalTextRect(); // might be bigger in the case of wrap not enabled
1152 r.translate(blockBoundingRect(posBlock).topLeft());
1154 QRectF anchorRect = d->rectForPosition(cursor.selectionEnd());
1156 r |= boundingRectOfFloatsInSelection(cursor);
1157 QRectF frameRect(d->doc->documentLayout()->frameBoundingRect(cursor.currentFrame()));
1158 r.setLeft(frameRect.left());
1159 r.setRight(frameRect.right());
1162 r.adjust(-1, -1, 1, 1);
1168 QRectF QQuickTextControl::selectionRect() const
1170 Q_D(const QQuickTextControl);
1171 return selectionRect(d->cursor);
1174 void QQuickTextControlPrivate::mousePressEvent(QMouseEvent *e, const QPointF &pos)
1176 Q_Q(QQuickTextControl);
1178 mousePressed = (interactionFlags & Qt::TextSelectableByMouse);
1179 mousePressPos = pos.toPoint();
1181 if (sendMouseEventToInputContext(e, pos))
1184 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1185 anchorOnMousePress = q->anchorAt(pos);
1187 if (cursorIsFocusIndicator) {
1188 cursorIsFocusIndicator = false;
1190 cursor.clearSelection();
1193 if (!(e->button() & Qt::LeftButton) ||
1194 !((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) {
1199 cursorIsFocusIndicator = false;
1200 const QTextCursor oldSelection = cursor;
1201 const int oldCursorPos = cursor.position();
1205 if (trippleClickTimer.isActive()
1206 && ((pos - trippleClickPoint).toPoint().manhattanLength() < qApp->styleHints()->startDragDistance())) {
1208 cursor.movePosition(QTextCursor::StartOfBlock);
1209 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1210 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1211 selectedBlockOnTrippleClick = cursor;
1213 anchorOnMousePress = QString();
1215 trippleClickTimer.stop();
1217 int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
1218 if (cursorPos == -1) {
1223 if (e->modifiers() == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) {
1224 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1225 selectedWordOnDoubleClick = cursor;
1226 selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
1229 if (selectedBlockOnTrippleClick.hasSelection())
1230 extendBlockwiseSelection(cursorPos);
1231 else if (selectedWordOnDoubleClick.hasSelection())
1232 extendWordwiseSelection(cursorPos, pos.x());
1233 else if (!wordSelectionEnabled)
1234 setCursorPosition(cursorPos, QTextCursor::KeepAnchor);
1236 setCursorPosition(cursorPos);
1240 if (interactionFlags & Qt::TextEditable) {
1241 q->ensureCursorVisible();
1242 if (cursor.position() != oldCursorPos)
1243 emit q->cursorPositionChanged();
1244 _q_updateCurrentCharFormatAndSelection();
1246 if (cursor.position() != oldCursorPos) {
1247 emit q->cursorPositionChanged();
1248 emit q->microFocusChanged();
1252 repaintOldAndNewSelection(oldSelection);
1253 hadSelectionOnMousePress = cursor.hasSelection();
1256 void QQuickTextControlPrivate::mouseMoveEvent(QMouseEvent *e, const QPointF &mousePos)
1258 Q_Q(QQuickTextControl);
1260 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1261 QString anchor = q->anchorAt(mousePos);
1262 if (anchor != highlightedAnchor) {
1263 highlightedAnchor = anchor;
1264 emit q->linkHovered(anchor);
1268 if ((e->buttons() & Qt::LeftButton)) {
1269 const bool editable = interactionFlags & Qt::TextEditable;
1273 || selectedWordOnDoubleClick.hasSelection()
1274 || selectedBlockOnTrippleClick.hasSelection()))
1277 const QTextCursor oldSelection = cursor;
1278 const int oldCursorPos = cursor.position();
1283 const qreal mouseX = qreal(mousePos.x());
1285 int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
1287 if (isPreediting()) {
1288 // note: oldCursorPos not including preedit
1289 int selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
1290 if (newCursorPos != selectionStartPos) {
1292 // commit invalidates positions
1293 newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
1294 selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
1295 setCursorPosition(selectionStartPos);
1299 if (newCursorPos == -1)
1302 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1303 selectedWordOnDoubleClick = cursor;
1304 selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
1307 if (selectedBlockOnTrippleClick.hasSelection())
1308 extendBlockwiseSelection(newCursorPos);
1309 else if (selectedWordOnDoubleClick.hasSelection())
1310 extendWordwiseSelection(newCursorPos, mouseX);
1311 else if (!isPreediting())
1312 setCursorPosition(newCursorPos, QTextCursor::KeepAnchor);
1314 if (interactionFlags & Qt::TextEditable) {
1315 if (cursor.position() != oldCursorPos)
1316 emit q->cursorPositionChanged();
1317 _q_updateCurrentCharFormatAndSelection();
1319 qGuiApp->inputPanel()->update(Qt::ImQueryInput);
1321 if (cursor.position() != oldCursorPos) {
1322 emit q->cursorPositionChanged();
1323 emit q->microFocusChanged();
1326 selectionChanged(true);
1327 repaintOldAndNewSelection(oldSelection);
1330 sendMouseEventToInputContext(e, mousePos);
1333 void QQuickTextControlPrivate::mouseReleaseEvent(QMouseEvent *e, const QPointF &pos)
1335 Q_Q(QQuickTextControl);
1337 if (sendMouseEventToInputContext(e, pos))
1340 const QTextCursor oldSelection = cursor;
1341 const int oldCursorPos = cursor.position();
1344 mousePressed = false;
1345 #ifndef QT_NO_CLIPBOARD
1346 setClipboardSelection();
1347 selectionChanged(true);
1348 } else if (e->button() == Qt::MidButton
1349 && (interactionFlags & Qt::TextEditable)
1350 && QGuiApplication::clipboard()->supportsSelection()) {
1351 setCursorPosition(pos);
1352 const QMimeData *md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
1354 q->insertFromMimeData(md);
1358 repaintOldAndNewSelection(oldSelection);
1360 if (cursor.position() != oldCursorPos) {
1361 emit q->cursorPositionChanged();
1362 emit q->microFocusChanged();
1365 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1366 if (!(e->button() & Qt::LeftButton))
1369 const QString anchor = q->anchorAt(pos);
1371 if (anchor.isEmpty())
1374 if (!cursor.hasSelection()
1375 || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) {
1377 const int anchorPos = q->hitTest(pos, Qt::ExactHit);
1378 if (anchorPos != -1) {
1379 cursor.setPosition(anchorPos);
1381 QString anchor = anchorOnMousePress;
1382 anchorOnMousePress = QString();
1383 activateLinkUnderCursor(anchor);
1389 void QQuickTextControlPrivate::mouseDoubleClickEvent(QMouseEvent *e, const QPointF &pos)
1391 Q_Q(QQuickTextControl);
1393 if (e->button() == Qt::LeftButton && (interactionFlags & Qt::TextSelectableByMouse)) {
1396 const QTextCursor oldSelection = cursor;
1397 setCursorPosition(pos);
1398 QTextLine line = currentTextLine(cursor);
1399 bool doEmit = false;
1400 if (line.isValid() && line.textLength()) {
1401 cursor.select(QTextCursor::WordUnderCursor);
1404 repaintOldAndNewSelection(oldSelection);
1406 cursorIsFocusIndicator = false;
1407 selectedWordOnDoubleClick = cursor;
1409 trippleClickPoint = pos;
1410 trippleClickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), q);
1413 #ifndef QT_NO_CLIPBOARD
1414 setClipboardSelection();
1416 emit q->cursorPositionChanged();
1418 } else if (!sendMouseEventToInputContext(e, pos)) {
1423 bool QQuickTextControlPrivate::sendMouseEventToInputContext(QMouseEvent *e, const QPointF &pos)
1425 #if !defined(QT_NO_IM)
1426 Q_Q(QQuickTextControl);
1430 if (contextObject && isPreediting()) {
1431 QTextLayout *layout = cursor.block().layout();
1432 int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position();
1434 if (cursorPos >= 0 && cursorPos <= layout->preeditAreaText().length()) {
1435 if (e->type() == QEvent::MouseButtonRelease) {
1436 qApp->inputPanel()->invokeAction(QInputPanel::Click, cursorPos);
1449 void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
1451 Q_Q(QQuickTextControl);
1452 if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) {
1456 bool isGettingInput = !e->commitString().isEmpty()
1457 || e->preeditString() != cursor.block().layout()->preeditAreaText()
1458 || e->replacementLength() > 0;
1459 bool forceSelectionChanged = false;
1461 cursor.beginEditBlock();
1462 if (isGettingInput) {
1463 cursor.removeSelectedText();
1466 // insert commit string
1467 if (!e->commitString().isEmpty() || e->replacementLength()) {
1468 QTextCursor c = cursor;
1469 c.setPosition(c.position() + e->replacementStart());
1470 c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor);
1471 c.insertText(e->commitString());
1474 for (int i = 0; i < e->attributes().size(); ++i) {
1475 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
1476 if (a.type == QInputMethodEvent::Selection) {
1477 QTextCursor oldCursor = cursor;
1478 int blockStart = a.start + cursor.block().position();
1479 cursor.setPosition(blockStart, QTextCursor::MoveAnchor);
1480 cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor);
1481 q->ensureCursorVisible();
1482 repaintOldAndNewSelection(oldCursor);
1483 forceSelectionChanged = true;
1487 QTextBlock block = cursor.block();
1488 QTextLayout *layout = block.layout();
1490 layout->setPreeditArea(cursor.position() - block.position(), e->preeditString());
1491 QList<QTextLayout::FormatRange> overrides;
1492 const int oldPreeditCursor = preeditCursor;
1493 preeditCursor = e->preeditString().length();
1495 for (int i = 0; i < e->attributes().size(); ++i) {
1496 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
1497 if (a.type == QInputMethodEvent::Cursor) {
1498 preeditCursor = a.start;
1499 hideCursor = !a.length;
1500 } else if (a.type == QInputMethodEvent::TextFormat) {
1501 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
1503 QTextLayout::FormatRange o;
1504 o.start = a.start + cursor.position() - block.position();
1505 o.length = a.length;
1507 overrides.append(o);
1511 layout->setAdditionalFormats(overrides);
1512 tentativeCommit = e->tentativeCommitString();
1514 cursor.endEditBlock();
1516 QTextCursorPrivate *cursor_d = QTextCursorPrivate::getPrivate(&cursor);
1519 if (oldPreeditCursor != preeditCursor)
1520 emit q->microFocusChanged();
1521 selectionChanged(forceSelectionChanged);
1524 QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property) const
1526 Q_D(const QQuickTextControl);
1527 QTextBlock block = d->cursor.block();
1529 case Qt::ImCursorRectangle:
1530 return cursorRect();
1532 return QVariant(d->cursor.charFormat().font());
1533 case Qt::ImCursorPosition:
1534 return QVariant(d->cursor.position() - block.position());
1535 case Qt::ImSurroundingText:
1536 return QVariant(block.text());
1537 case Qt::ImCurrentSelection:
1538 return QVariant(d->cursor.selectedText());
1539 case Qt::ImMaximumTextLength:
1540 return QVariant(); // No limit.
1541 case Qt::ImAnchorPosition:
1542 return QVariant(d->cursor.anchor() - block.position());
1548 void QQuickTextControl::setFocus(bool focus, Qt::FocusReason reason)
1550 QFocusEvent ev(focus ? QEvent::FocusIn : QEvent::FocusOut,
1555 void QQuickTextControlPrivate::focusEvent(QFocusEvent *e)
1557 Q_Q(QQuickTextControl);
1558 emit q->updateRequest(q->selectionRect());
1559 if (e->gotFocus()) {
1560 setBlinkingCursorEnabled(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
1563 setBlinkingCursorEnabled(false);
1565 if (cursorIsFocusIndicator
1566 && e->reason() != Qt::ActiveWindowFocusReason
1567 && e->reason() != Qt::PopupFocusReason
1568 && cursor.hasSelection()) {
1569 cursor.clearSelection();
1572 hasFocus = e->gotFocus();
1575 QString QQuickTextControlPrivate::anchorForCursor(const QTextCursor &anchorCursor) const
1577 if (anchorCursor.hasSelection()) {
1578 QTextCursor cursor = anchorCursor;
1579 if (cursor.selectionStart() != cursor.position())
1580 cursor.setPosition(cursor.selectionStart());
1581 cursor.movePosition(QTextCursor::NextCharacter);
1582 QTextCharFormat fmt = cursor.charFormat();
1583 if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref))
1584 return fmt.stringProperty(QTextFormat::AnchorHref);
1589 QTextCursor QQuickTextControl::cursorForPosition(const QPointF &pos) const
1591 Q_D(const QQuickTextControl);
1592 int cursorPos = hitTest(pos, Qt::FuzzyHit);
1593 if (cursorPos == -1)
1595 QTextCursor c(d->doc);
1596 c.setPosition(cursorPos);
1600 QRectF QQuickTextControl::cursorRect(const QTextCursor &cursor) const
1602 Q_D(const QQuickTextControl);
1603 if (cursor.isNull())
1606 return d->rectForPosition(cursor.position());
1609 QRectF QQuickTextControl::cursorRect() const
1611 Q_D(const QQuickTextControl);
1612 return cursorRect(d->cursor);
1615 QRectF QQuickTextControlPrivate::cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const
1617 if (cursor.isNull())
1620 return rectForPosition(cursor.position()).adjusted(-4, 0, 4, 0);
1623 QString QQuickTextControl::anchorAt(const QPointF &pos) const
1625 Q_D(const QQuickTextControl);
1626 return d->doc->documentLayout()->anchorAt(pos);
1629 QString QQuickTextControl::anchorAtCursor() const
1631 Q_D(const QQuickTextControl);
1633 return d->anchorForCursor(d->cursor);
1636 int QQuickTextControl::cursorWidth() const
1638 #ifndef QT_NO_PROPERTIES
1639 Q_D(const QQuickTextControl);
1640 return d->doc->documentLayout()->property("cursorWidth").toInt();
1646 void QQuickTextControl::setCursorWidth(int width)
1648 Q_D(QQuickTextControl);
1649 #ifdef QT_NO_PROPERTIES
1653 width = textCursorWidth;
1654 d->doc->documentLayout()->setProperty("cursorWidth", width);
1659 bool QQuickTextControl::acceptRichText() const
1661 Q_D(const QQuickTextControl);
1662 return d->acceptRichText;
1665 void QQuickTextControl::setAcceptRichText(bool accept)
1667 Q_D(QQuickTextControl);
1668 d->acceptRichText = accept;
1671 void QQuickTextControl::setTextWidth(qreal width)
1673 Q_D(QQuickTextControl);
1674 d->doc->setTextWidth(width);
1677 qreal QQuickTextControl::textWidth() const
1679 Q_D(const QQuickTextControl);
1680 return d->doc->textWidth();
1683 QSizeF QQuickTextControl::size() const
1685 Q_D(const QQuickTextControl);
1686 return d->doc->size();
1689 void QQuickTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
1691 Q_D(QQuickTextControl);
1692 const QTextCursor oldSelection = d->cursor;
1693 const bool moved = d->cursor.movePosition(op, mode);
1694 d->_q_updateCurrentCharFormatAndSelection();
1695 ensureCursorVisible();
1696 d->repaintOldAndNewSelection(oldSelection);
1698 emit cursorPositionChanged();
1701 bool QQuickTextControl::canPaste() const
1703 #ifndef QT_NO_CLIPBOARD
1704 Q_D(const QQuickTextControl);
1705 if (d->interactionFlags & Qt::TextEditable) {
1706 const QMimeData *md = QGuiApplication::clipboard()->mimeData();
1707 return md && canInsertFromMimeData(md);
1713 void QQuickTextControl::setCursorIsFocusIndicator(bool b)
1715 Q_D(QQuickTextControl);
1716 d->cursorIsFocusIndicator = b;
1720 bool QQuickTextControl::cursorIsFocusIndicator() const
1722 Q_D(const QQuickTextControl);
1723 return d->cursorIsFocusIndicator;
1726 void QQuickTextControl::setWordSelectionEnabled(bool enabled)
1728 Q_D(QQuickTextControl);
1729 d->wordSelectionEnabled = enabled;
1732 bool QQuickTextControl::isWordSelectionEnabled() const
1734 Q_D(const QQuickTextControl);
1735 return d->wordSelectionEnabled;
1738 QMimeData *QQuickTextControl::createMimeDataFromSelection() const
1740 Q_D(const QQuickTextControl);
1741 const QTextDocumentFragment fragment(d->cursor);
1742 return new QQuickTextEditMimeData(fragment);
1745 bool QQuickTextControl::canInsertFromMimeData(const QMimeData *source) const
1747 Q_D(const QQuickTextControl);
1748 if (d->acceptRichText)
1749 return (source->hasText() && !source->text().isEmpty())
1750 || source->hasHtml()
1751 || source->hasFormat(QLatin1String("application/x-qrichtext"))
1752 || source->hasFormat(QLatin1String("application/x-qt-richtext"));
1754 return source->hasText() && !source->text().isEmpty();
1757 void QQuickTextControl::insertFromMimeData(const QMimeData *source)
1759 Q_D(QQuickTextControl);
1760 if (!(d->interactionFlags & Qt::TextEditable) || !source)
1763 bool hasData = false;
1764 QTextDocumentFragment fragment;
1765 #ifndef QT_NO_TEXTHTMLPARSER
1766 if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) {
1767 // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
1768 QString richtext = QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext")));
1769 richtext.prepend(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
1770 fragment = QTextDocumentFragment::fromHtml(richtext, d->doc);
1772 } else if (source->hasHtml() && d->acceptRichText) {
1773 fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc);
1776 QString text = source->text();
1777 if (!text.isNull()) {
1778 fragment = QTextDocumentFragment::fromPlainText(text);
1783 fragment = QTextDocumentFragment::fromPlainText(source->text());
1784 #endif // QT_NO_TEXTHTMLPARSER
1787 d->cursor.insertFragment(fragment);
1788 ensureCursorVisible();
1791 void QQuickTextControlPrivate::activateLinkUnderCursor(QString href)
1793 QTextCursor oldCursor = cursor;
1795 if (href.isEmpty()) {
1796 QTextCursor tmp = cursor;
1797 if (tmp.selectionStart() != tmp.position())
1798 tmp.setPosition(tmp.selectionStart());
1799 tmp.movePosition(QTextCursor::NextCharacter);
1800 href = tmp.charFormat().anchorHref();
1805 if (!cursor.hasSelection()) {
1806 QTextBlock block = cursor.block();
1807 const int cursorPos = cursor.position();
1809 QTextBlock::Iterator it = block.begin();
1810 QTextBlock::Iterator linkFragment;
1812 for (; !it.atEnd(); ++it) {
1813 QTextFragment fragment = it.fragment();
1814 const int fragmentPos = fragment.position();
1815 if (fragmentPos <= cursorPos &&
1816 fragmentPos + fragment.length() > cursorPos) {
1822 if (!linkFragment.atEnd()) {
1824 cursor.setPosition(it.fragment().position());
1825 if (it != block.begin()) {
1828 QTextFragment fragment = it.fragment();
1829 if (fragment.charFormat().anchorHref() != href)
1831 cursor.setPosition(fragment.position());
1832 } while (it != block.begin());
1835 for (it = linkFragment; !it.atEnd(); ++it) {
1836 QTextFragment fragment = it.fragment();
1837 if (fragment.charFormat().anchorHref() != href)
1839 cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
1845 cursorIsFocusIndicator = true;
1847 cursorIsFocusIndicator = false;
1848 cursor.clearSelection();
1850 repaintOldAndNewSelection(oldCursor);
1852 emit q_func()->linkActivated(href);
1855 bool QQuickTextControlPrivate::isPreediting() const
1857 QTextLayout *layout = cursor.block().layout();
1858 if (layout && !layout->preeditAreaText().isEmpty())
1864 void QQuickTextControlPrivate::commitPreedit()
1866 if (!isPreediting())
1869 cursor.beginEditBlock();
1870 qApp->inputPanel()->reset();
1872 if (!tentativeCommit.isEmpty()) {
1873 cursor.insertText(tentativeCommit);
1874 tentativeCommit.clear();
1878 QTextBlock block = cursor.block();
1879 QTextLayout *layout = block.layout();
1880 layout->setPreeditArea(-1, QString());
1881 layout->clearAdditionalFormats();
1882 cursor.endEditBlock();
1885 void QQuickTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
1887 Q_D(QQuickTextControl);
1888 if (flags == d->interactionFlags)
1890 d->interactionFlags = flags;
1893 d->setBlinkingCursorEnabled(flags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
1896 Qt::TextInteractionFlags QQuickTextControl::textInteractionFlags() const
1898 Q_D(const QQuickTextControl);
1899 return d->interactionFlags;
1902 QString QQuickTextControl::toPlainText() const
1904 Q_D(const QQuickTextControl);
1905 QString plainText = document()->toPlainText();
1906 if (!d->tentativeCommit.isEmpty())
1907 plainText.insert(textCursor().position(), d->tentativeCommit);
1911 #ifndef QT_NO_TEXTHTMLPARSER
1912 QString QQuickTextControl::toHtml() const
1914 // note: currently not including tentative commit
1915 return document()->toHtml();
1919 void QQuickTextControl::ensureCursorVisible()
1921 Q_D(QQuickTextControl);
1922 QRectF crect = d->rectForPosition(d->cursor.position()).adjusted(-5, 0, 5, 0);
1923 emit visibilityRequest(crect);
1924 emit microFocusChanged();
1927 QPalette QQuickTextControl::palette() const
1929 Q_D(const QQuickTextControl);
1933 void QQuickTextControl::setPalette(const QPalette &pal)
1935 Q_D(QQuickTextControl);
1939 bool QQuickTextControl::cursorOn() const
1941 Q_D(const QQuickTextControl);
1945 QAbstractTextDocumentLayout::PaintContext QQuickTextControl::getPaintContext() const
1947 Q_D(const QQuickTextControl);
1949 QAbstractTextDocumentLayout::PaintContext ctx;
1951 ctx.palette = d->palette;
1952 if (d->cursorOn && d->isEnabled) {
1954 ctx.cursorPosition = -1;
1955 else if (d->preeditCursor != 0)
1956 ctx.cursorPosition = - (d->preeditCursor + 2);
1958 ctx.cursorPosition = d->cursor.position();
1961 if (d->cursor.hasSelection()) {
1962 QAbstractTextDocumentLayout::Selection selection;
1963 selection.cursor = d->cursor;
1964 if (0 && d->cursorIsFocusIndicator) {
1968 opt.palette = ctx.palette;
1969 QStyleHintReturnVariant ret;
1970 QStyle *style = QGuiApplication::style();
1972 style = widget->style();
1973 style->styleHint(QStyle::SH_TextControl_FocusIndicatorTextCharFormat, &opt, widget, &ret);
1974 selection.format = qvariant_cast<QTextFormat>(ret.variant).toCharFormat();
1977 QPalette::ColorGroup cg = d->hasFocus ? QPalette::Active : QPalette::Inactive;
1978 selection.format.setBackground(ctx.palette.brush(cg, QPalette::Highlight));
1979 selection.format.setForeground(ctx.palette.brush(cg, QPalette::HighlightedText));
1980 if (fullWidthSelection)
1981 selection.format.setProperty(QTextFormat::FullWidthSelection, true);
1983 ctx.selections.append(selection);
1989 void QQuickTextControl::drawContents(QPainter *p, const QRectF &rect)
1991 Q_D(QQuickTextControl);
1993 QAbstractTextDocumentLayout::PaintContext ctx = getPaintContext();
1995 p->setClipRect(rect, Qt::IntersectClip);
1998 d->doc->documentLayout()->draw(p, ctx);
2002 int QQuickTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
2004 Q_D(const QQuickTextControl);
2005 return d->doc->documentLayout()->hitTest(point, accuracy);
2008 QRectF QQuickTextControl::blockBoundingRect(const QTextBlock &block) const
2010 Q_D(const QQuickTextControl);
2011 return d->doc->documentLayout()->blockBoundingRect(block);
2016 QStringList QQuickTextEditMimeData::formats() const
2018 if (!fragment.isEmpty())
2019 return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html")
2020 #ifndef QT_NO_TEXTODFWRITER
2021 << QString::fromLatin1("application/vnd.oasis.opendocument.text")
2025 return QMimeData::formats();
2028 QVariant QQuickTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
2030 if (!fragment.isEmpty())
2032 return QMimeData::retrieveData(mimeType, type);
2035 void QQuickTextEditMimeData::setup() const
2037 QQuickTextEditMimeData *that = const_cast<QQuickTextEditMimeData *>(this);
2038 #ifndef QT_NO_TEXTHTMLPARSER
2039 that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8());
2041 #ifndef QT_NO_TEXTODFWRITER
2044 QTextDocumentWriter writer(&buffer, "ODF");
2045 writer.write(fragment);
2047 that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data());
2050 that->setText(fragment.toPlainText());
2051 fragment = QTextDocumentFragment();
2057 #include "moc_qquicktextcontrol_p.cpp"
2059 #endif // QT_NO_TEXTCONTROL