1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquicktextcontrol_p.h"
41#include "qquicktextcontrol_p_p.h"
42
43#ifndef QT_NO_TEXTCONTROL
44
45#include <qcoreapplication.h>
46#include <qfont.h>
47#include <qfontmetrics.h>
48#include <qevent.h>
49#include <qdebug.h>
50#include <qclipboard.h>
51#include <qtimer.h>
52#include <qinputmethod.h>
53#include "private/qtextdocumentlayout_p.h"
54#include "private/qabstracttextdocumentlayout_p.h"
55#include "qtextdocument.h"
56#include "private/qtextdocument_p.h"
57#include "qtextlist.h"
58#include "qtextdocumentwriter.h"
59#include "private/qtextcursor_p.h"
60#include <QtCore/qloggingcategory.h>
61
62#include <qtextformat.h>
63#include <qdatetime.h>
64#include <qbuffer.h>
65#include <qguiapplication.h>
66#include <limits.h>
67#include <qtexttable.h>
68#include <qvariant.h>
69#include <qurl.h>
70#include <qstylehints.h>
71#include <qmetaobject.h>
72
73#include <private/qqmlglobal_p.h>
74
75// ### these should come from QStyleHints
76const int textCursorWidth = 1;
77
78QT_BEGIN_NAMESPACE
79Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE)
80
81// could go into QTextCursor...
82static QTextLine currentTextLine(const QTextCursor &cursor)
83{
84 const QTextBlock block = cursor.block();
85 if (!block.isValid())
86 return QTextLine();
87
88 const QTextLayout *layout = block.layout();
89 if (!layout)
90 return QTextLine();
91
92 const int relativePos = cursor.position() - block.position();
93 return layout->lineForTextPosition(relativePos);
94}
95
96QQuickTextControlPrivate::QQuickTextControlPrivate()
97 : doc(nullptr),
98#if QT_CONFIG(im)
99 preeditCursor(0),
100#endif
101 interactionFlags(Qt::TextEditorInteraction),
102 cursorOn(false),
103 cursorIsFocusIndicator(false),
104 mousePressed(false),
105 lastSelectionState(false),
106 ignoreAutomaticScrollbarAdjustement(false),
107 overwriteMode(false),
108 acceptRichText(true),
109 cursorVisible(false),
110 cursorBlinkingEnabled(false),
111 hasFocus(false),
112 hadSelectionOnMousePress(false),
113 wordSelectionEnabled(false),
114 hasImState(false),
115 cursorRectangleChanged(false),
116 hoveredMarker(false),
117 lastSelectionStart(-1),
118 lastSelectionEnd(-1)
119{}
120
121bool QQuickTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e)
122{
123#if !QT_CONFIG(shortcut)
124 Q_UNUSED(e);
125#endif
126
127 Q_Q(QQuickTextControl);
128 if (cursor.isNull())
129 return false;
130
131 const QTextCursor oldSelection = cursor;
132 const int oldCursorPos = cursor.position();
133
134 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
135 QTextCursor::MoveOperation op = QTextCursor::NoMove;
136
137 if (false) {
138 }
139#if QT_CONFIG(shortcut)
140 if (e == QKeySequence::MoveToNextChar) {
141 op = QTextCursor::Right;
142 }
143 else if (e == QKeySequence::MoveToPreviousChar) {
144 op = QTextCursor::Left;
145 }
146 else if (e == QKeySequence::SelectNextChar) {
147 op = QTextCursor::Right;
148 mode = QTextCursor::KeepAnchor;
149 }
150 else if (e == QKeySequence::SelectPreviousChar) {
151 op = QTextCursor::Left;
152 mode = QTextCursor::KeepAnchor;
153 }
154 else if (e == QKeySequence::SelectNextWord) {
155 op = QTextCursor::WordRight;
156 mode = QTextCursor::KeepAnchor;
157 }
158 else if (e == QKeySequence::SelectPreviousWord) {
159 op = QTextCursor::WordLeft;
160 mode = QTextCursor::KeepAnchor;
161 }
162 else if (e == QKeySequence::SelectStartOfLine) {
163 op = QTextCursor::StartOfLine;
164 mode = QTextCursor::KeepAnchor;
165 }
166 else if (e == QKeySequence::SelectEndOfLine) {
167 op = QTextCursor::EndOfLine;
168 mode = QTextCursor::KeepAnchor;
169 }
170 else if (e == QKeySequence::SelectStartOfBlock) {
171 op = QTextCursor::StartOfBlock;
172 mode = QTextCursor::KeepAnchor;
173 }
174 else if (e == QKeySequence::SelectEndOfBlock) {
175 op = QTextCursor::EndOfBlock;
176 mode = QTextCursor::KeepAnchor;
177 }
178 else if (e == QKeySequence::SelectStartOfDocument) {
179 op = QTextCursor::Start;
180 mode = QTextCursor::KeepAnchor;
181 }
182 else if (e == QKeySequence::SelectEndOfDocument) {
183 op = QTextCursor::End;
184 mode = QTextCursor::KeepAnchor;
185 }
186 else if (e == QKeySequence::SelectPreviousLine) {
187 op = QTextCursor::Up;
188 mode = QTextCursor::KeepAnchor;
189 }
190 else if (e == QKeySequence::SelectNextLine) {
191 op = QTextCursor::Down;
192 mode = QTextCursor::KeepAnchor;
193 {
194 QTextBlock block = cursor.block();
195 QTextLine line = currentTextLine(cursor);
196 if (!block.next().isValid()
197 && line.isValid()
198 && line.lineNumber() == block.layout()->lineCount() - 1)
199 op = QTextCursor::End;
200 }
201 }
202 else if (e == QKeySequence::MoveToNextWord) {
203 op = QTextCursor::WordRight;
204 }
205 else if (e == QKeySequence::MoveToPreviousWord) {
206 op = QTextCursor::WordLeft;
207 }
208 else if (e == QKeySequence::MoveToEndOfBlock) {
209 op = QTextCursor::EndOfBlock;
210 }
211 else if (e == QKeySequence::MoveToStartOfBlock) {
212 op = QTextCursor::StartOfBlock;
213 }
214 else if (e == QKeySequence::MoveToNextLine) {
215 op = QTextCursor::Down;
216 }
217 else if (e == QKeySequence::MoveToPreviousLine) {
218 op = QTextCursor::Up;
219 }
220 else if (e == QKeySequence::MoveToStartOfLine) {
221 op = QTextCursor::StartOfLine;
222 }
223 else if (e == QKeySequence::MoveToEndOfLine) {
224 op = QTextCursor::EndOfLine;
225 }
226 else if (e == QKeySequence::MoveToStartOfDocument) {
227 op = QTextCursor::Start;
228 }
229 else if (e == QKeySequence::MoveToEndOfDocument) {
230 op = QTextCursor::End;
231 }
232#endif // shortcut
233 else {
234 return false;
235 }
236
237// Except for pageup and pagedown, OS X has very different behavior, we don't do it all, but
238// here's the breakdown:
239// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
240// Alt (Option), or Meta (Control).
241// Command/Control + Left/Right -- Move to left or right of the line
242// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
243// Option + Left/Right -- Move one word Left/right.
244// + Up/Down -- Begin/End of Paragraph.
245// Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
246
247 bool visualNavigation = cursor.visualNavigation();
248 cursor.setVisualNavigation(true);
249 const bool moved = cursor.movePosition(op, mode);
250 cursor.setVisualNavigation(visualNavigation);
251
252 bool isNavigationEvent
253 = e->key() == Qt::Key_Up
254 || e->key() == Qt::Key_Down
255 || e->key() == Qt::Key_Left
256 || e->key() == Qt::Key_Right;
257
258 if (moved) {
259 if (cursor.position() != oldCursorPos)
260 emit q->cursorPositionChanged();
261 q->updateCursorRectangle(true);
262 } else if (isNavigationEvent && oldSelection.anchor() == cursor.anchor()) {
263 return false;
264 }
265
266 selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor));
267
268 repaintOldAndNewSelection(oldSelection);
269
270 return true;
271}
272
273void QQuickTextControlPrivate::updateCurrentCharFormat()
274{
275 Q_Q(QQuickTextControl);
276
277 QTextCharFormat fmt = cursor.charFormat();
278 if (fmt == lastCharFormat)
279 return;
280 lastCharFormat = fmt;
281
282 emit q->currentCharFormatChanged(fmt);
283 cursorRectangleChanged = true;
284}
285
286void QQuickTextControlPrivate::setContent(Qt::TextFormat format, const QString &text)
287{
288 Q_Q(QQuickTextControl);
289
290#if QT_CONFIG(im)
291 cancelPreedit();
292#endif
293
294 // for use when called from setPlainText. we may want to re-use the currently
295 // set char format then.
296 const QTextCharFormat charFormatForInsertion = cursor.charFormat();
297
298 bool previousUndoRedoState = doc->isUndoRedoEnabled();
299 doc->setUndoRedoEnabled(false);
300
301 const int oldCursorPos = cursor.position();
302
303 // avoid multiple textChanged() signals being emitted
304 qmlobject_disconnect(doc, QTextDocument, SIGNAL(contentsChanged()), q, QQuickTextControl, SIGNAL(textChanged()));
305
306 if (!text.isEmpty()) {
307 // clear 'our' cursor for insertion to prevent
308 // the emission of the cursorPositionChanged() signal.
309 // instead we emit it only once at the end instead of
310 // at the end of the document after loading and when
311 // positioning the cursor again to the start of the
312 // document.
313 cursor = QTextCursor();
314 if (format == Qt::PlainText) {
315 QTextCursor formatCursor(doc);
316 // put the setPlainText and the setCharFormat into one edit block,
317 // so that the syntax highlight triggers only /once/ for the entire
318 // document, not twice.
319 formatCursor.beginEditBlock();
320 doc->setPlainText(text);
321 doc->setUndoRedoEnabled(false);
322 formatCursor.select(QTextCursor::Document);
323 formatCursor.setCharFormat(charFormatForInsertion);
324 formatCursor.endEditBlock();
325 } else if (format == Qt::MarkdownText) {
326 doc->setBaseUrl(doc->baseUrl().adjusted(QUrl::RemoveFilename));
327 doc->setMarkdown(text);
328 } else {
329#if QT_CONFIG(texthtmlparser)
330 doc->setHtml(text);
331#else
332 doc->setPlainText(text);
333#endif
334 doc->setUndoRedoEnabled(false);
335 }
336 cursor = QTextCursor(doc);
337 } else {
338 doc->clear();
339 }
340 cursor.setCharFormat(charFormatForInsertion);
341
342 qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), q, QQuickTextControl, SIGNAL(textChanged()));
343 emit q->textChanged();
344 doc->setUndoRedoEnabled(previousUndoRedoState);
345 _q_updateCurrentCharFormatAndSelection();
346 doc->setModified(false);
347
348 q->updateCursorRectangle(true);
349 if (cursor.position() != oldCursorPos)
350 emit q->cursorPositionChanged();
351}
352
353void QQuickTextControlPrivate::setCursorPosition(const QPointF &pos)
354{
355 Q_Q(QQuickTextControl);
356 const int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
357 if (cursorPos == -1)
358 return;
359 cursor.setPosition(cursorPos);
360}
361
362void QQuickTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode)
363{
364 cursor.setPosition(pos, mode);
365
366 if (mode != QTextCursor::KeepAnchor) {
367 selectedWordOnDoubleClick = QTextCursor();
368 selectedBlockOnTripleClick = QTextCursor();
369 }
370}
371
372void QQuickTextControlPrivate::repaintCursor()
373{
374 Q_Q(QQuickTextControl);
375 emit q->updateCursorRequest();
376}
377
378void QQuickTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection)
379{
380 Q_Q(QQuickTextControl);
381 if (cursor.hasSelection()
382 && oldSelection.hasSelection()
383 && cursor.currentFrame() == oldSelection.currentFrame()
384 && !cursor.hasComplexSelection()
385 && !oldSelection.hasComplexSelection()
386 && cursor.anchor() == oldSelection.anchor()
387 ) {
388 QTextCursor differenceSelection(doc);
389 differenceSelection.setPosition(oldSelection.position());
390 differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor);
391 emit q->updateRequest();
392 } else {
393 if (!oldSelection.hasSelection() && !cursor.hasSelection()) {
394 if (!oldSelection.isNull())
395 emit q->updateCursorRequest();
396 emit q->updateCursorRequest();
397
398 } else {
399 if (!oldSelection.isNull())
400 emit q->updateRequest();
401 emit q->updateRequest();
402 }
403 }
404}
405
406void QQuickTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/)
407{
408 Q_Q(QQuickTextControl);
409 if (forceEmitSelectionChanged) {
410#if QT_CONFIG(im)
411 if (hasFocus)
412 qGuiApp->inputMethod()->update(Qt::ImCurrentSelection);
413#endif
414 emit q->selectionChanged();
415 }
416
417 bool current = cursor.hasSelection();
418 int selectionStart = cursor.selectionStart();
419 int selectionEnd = cursor.selectionEnd();
420 if (current == lastSelectionState && (!current || (selectionStart == lastSelectionStart && selectionEnd == lastSelectionEnd)))
421 return;
422
423 if (lastSelectionState != current) {
424 lastSelectionState = current;
425 emit q->copyAvailable(current);
426 }
427
428 lastSelectionStart = selectionStart;
429 lastSelectionEnd = selectionEnd;
430
431 if (!forceEmitSelectionChanged) {
432#if QT_CONFIG(im)
433 if (hasFocus)
434 qGuiApp->inputMethod()->update(Qt::ImCurrentSelection);
435#endif
436 emit q->selectionChanged();
437 }
438 q->updateCursorRectangle(true);
439}
440
441void QQuickTextControlPrivate::_q_updateCurrentCharFormatAndSelection()
442{
443 updateCurrentCharFormat();
444 selectionChanged();
445}
446
447#if QT_CONFIG(clipboard)
448void QQuickTextControlPrivate::setClipboardSelection()
449{
450 QClipboard *clipboard = QGuiApplication::clipboard();
451 if (!cursor.hasSelection() || !clipboard->supportsSelection())
452 return;
453 Q_Q(QQuickTextControl);
454 QMimeData *data = q->createMimeDataFromSelection();
455 clipboard->setMimeData(data, QClipboard::Selection);
456}
457#endif
458
459void QQuickTextControlPrivate::_q_updateCursorPosChanged(const QTextCursor &someCursor)
460{
461 Q_Q(QQuickTextControl);
462 if (someCursor.isCopyOf(cursor)) {
463 emit q->cursorPositionChanged();
464 q->updateCursorRectangle(true);
465 }
466}
467
468void QQuickTextControlPrivate::setBlinkingCursorEnabled(bool enable)
469{
470 if (cursorBlinkingEnabled == enable)
471 return;
472
473 cursorBlinkingEnabled = enable;
474 updateCursorFlashTime();
475
476 if (enable)
477 connect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextControlPrivate::updateCursorFlashTime);
478 else
479 disconnect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextControlPrivate::updateCursorFlashTime);
480}
481
482void QQuickTextControlPrivate::updateCursorFlashTime()
483{
484 // Note: cursorOn represents the current blinking state controlled by a timer, and
485 // should not be confused with cursorVisible or cursorBlinkingEnabled. However, we
486 // interpretate a cursorFlashTime of 0 to mean "always on, never blink".
487 cursorOn = true;
488 int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
489
490 if (cursorBlinkingEnabled && flashTime >= 2)
491 cursorBlinkTimer.start(flashTime / 2, q_func());
492 else
493 cursorBlinkTimer.stop();
494
495 repaintCursor();
496}
497
498void QQuickTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition)
499{
500 Q_Q(QQuickTextControl);
501
502 // if inside the initial selected word keep that
503 if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart()
504 && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) {
505 q->setTextCursor(selectedWordOnDoubleClick);
506 return;
507 }
508
509 QTextCursor curs = selectedWordOnDoubleClick;
510 curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
511
512 if (!curs.movePosition(QTextCursor::StartOfWord))
513 return;
514 const int wordStartPos = curs.position();
515
516 const int blockPos = curs.block().position();
517 const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft();
518
519 QTextLine line = currentTextLine(curs);
520 if (!line.isValid())
521 return;
522
523 const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
524
525 if (!curs.movePosition(QTextCursor::EndOfWord))
526 return;
527 const int wordEndPos = curs.position();
528
529 const QTextLine otherLine = currentTextLine(curs);
530 if (otherLine.textStart() != line.textStart()
531 || wordEndPos == wordStartPos)
532 return;
533
534 const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
535
536 if (!wordSelectionEnabled && (mouseXPosition < wordStartX || mouseXPosition > wordEndX))
537 return;
538
539 if (suggestedNewPosition < selectedWordOnDoubleClick.position()) {
540 cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
541 setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
542 } else {
543 cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
544 setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
545 }
546
547 if (interactionFlags & Qt::TextSelectableByMouse) {
548#if QT_CONFIG(clipboard)
549 setClipboardSelection();
550#endif
551 selectionChanged(true);
552 }
553}
554
555void QQuickTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition)
556{
557 Q_Q(QQuickTextControl);
558
559 // if inside the initial selected line keep that
560 if (suggestedNewPosition >= selectedBlockOnTripleClick.selectionStart()
561 && suggestedNewPosition <= selectedBlockOnTripleClick.selectionEnd()) {
562 q->setTextCursor(selectedBlockOnTripleClick);
563 return;
564 }
565
566 if (suggestedNewPosition < selectedBlockOnTripleClick.position()) {
567 cursor.setPosition(selectedBlockOnTripleClick.selectionEnd());
568 cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
569 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
570 } else {
571 cursor.setPosition(selectedBlockOnTripleClick.selectionStart());
572 cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
573 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
574 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
575 }
576
577 if (interactionFlags & Qt::TextSelectableByMouse) {
578#if QT_CONFIG(clipboard)
579 setClipboardSelection();
580#endif
581 selectionChanged(true);
582 }
583}
584
585void QQuickTextControl::undo()
586{
587 Q_D(QQuickTextControl);
588 d->repaintSelection();
589 const int oldCursorPos = d->cursor.position();
590 d->doc->undo(&d->cursor);
591 if (d->cursor.position() != oldCursorPos)
592 emit cursorPositionChanged();
593 updateCursorRectangle(true);
594}
595
596void QQuickTextControl::redo()
597{
598 Q_D(QQuickTextControl);
599 d->repaintSelection();
600 const int oldCursorPos = d->cursor.position();
601 d->doc->redo(&d->cursor);
602 if (d->cursor.position() != oldCursorPos)
603 emit cursorPositionChanged();
604 updateCursorRectangle(true);
605}
606
607void QQuickTextControl::clear()
608{
609 Q_D(QQuickTextControl);
610 d->cursor.select(QTextCursor::Document);
611 d->cursor.removeSelectedText();
612}
613
614QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
615 : QInputControl(TextEdit, *new QQuickTextControlPrivate, parent)
616{
617 Q_D(QQuickTextControl);
618 Q_ASSERT(doc);
619
620 QAbstractTextDocumentLayout *layout = doc->documentLayout();
621 qmlobject_connect(layout, QAbstractTextDocumentLayout, SIGNAL(update(QRectF)), this, QQuickTextControl, SIGNAL(updateRequest()));
622 qmlobject_connect(layout, QAbstractTextDocumentLayout, SIGNAL(updateBlock(QTextBlock)), this, QQuickTextControl, SIGNAL(updateRequest()));
623 qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SIGNAL(textChanged()));
624 qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SLOT(_q_updateCurrentCharFormatAndSelection()));
625 qmlobject_connect(doc, QTextDocument, SIGNAL(cursorPositionChanged(QTextCursor)), this, QQuickTextControl, SLOT(_q_updateCursorPosChanged(QTextCursor)));
626 connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
627
628 layout->setProperty("cursorWidth", textCursorWidth);
629
630 d->doc = doc;
631 d->cursor = QTextCursor(doc);
632 d->lastCharFormat = d->cursor.charFormat();
633 doc->setPageSize(QSizeF(0, 0));
634 doc->setModified(false);
635 doc->setUndoRedoEnabled(true);
636}
637
638QQuickTextControl::~QQuickTextControl()
639{
640}
641
642QTextDocument *QQuickTextControl::document() const
643{
644 Q_D(const QQuickTextControl);
645 return d->doc;
646}
647
648void QQuickTextControl::updateCursorRectangle(bool force)
649{
650 Q_D(QQuickTextControl);
651 const bool update = d->cursorRectangleChanged || force;
652 d->cursorRectangleChanged = false;
653 if (update)
654 emit cursorRectangleChanged();
655}
656
657void QQuickTextControl::setTextCursor(const QTextCursor &cursor)
658{
659 Q_D(QQuickTextControl);
660#if QT_CONFIG(im)
661 d->commitPreedit();
662#endif
663 d->cursorIsFocusIndicator = false;
664 const bool posChanged = cursor.position() != d->cursor.position();
665 const QTextCursor oldSelection = d->cursor;
666 d->cursor = cursor;
667 d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable);
668 d->_q_updateCurrentCharFormatAndSelection();
669 updateCursorRectangle(true);
670 d->repaintOldAndNewSelection(oldSelection);
671 if (posChanged)
672 emit cursorPositionChanged();
673}
674
675QTextCursor QQuickTextControl::textCursor() const
676{
677 Q_D(const QQuickTextControl);
678 return d->cursor;
679}
680
681#if QT_CONFIG(clipboard)
682
683void QQuickTextControl::cut()
684{
685 Q_D(QQuickTextControl);
686 if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection())
687 return;
688 copy();
689 d->cursor.removeSelectedText();
690}
691
692void QQuickTextControl::copy()
693{
694 Q_D(QQuickTextControl);
695 if (!d->cursor.hasSelection())
696 return;
697 QMimeData *data = createMimeDataFromSelection();
698 QGuiApplication::clipboard()->setMimeData(data);
699}
700
701void QQuickTextControl::paste(QClipboard::Mode mode)
702{
703 const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode);
704 if (md)
705 insertFromMimeData(md);
706}
707#endif
708
709void QQuickTextControl::selectAll()
710{
711 Q_D(QQuickTextControl);
712 const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor());
713 d->cursor.select(QTextCursor::Document);
714 d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor()));
715 d->cursorIsFocusIndicator = false;
716 emit updateRequest();
717}
718
719void QQuickTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset)
720{
721 QMatrix m;
722 m.translate(coordinateOffset.x(), coordinateOffset.y());
723 processEvent(e, m);
724}
725
726void QQuickTextControl::processEvent(QEvent *e, const QMatrix &matrix)
727{
728 Q_D(QQuickTextControl);
729 if (d->interactionFlags == Qt::NoTextInteraction) {
730 e->ignore();
731 return;
732 }
733
734 switch (e->type()) {
735 case QEvent::KeyPress:
736 d->keyPressEvent(static_cast<QKeyEvent *>(e));
737 break;
738 case QEvent::KeyRelease:
739 d->keyReleaseEvent(static_cast<QKeyEvent *>(e));
740 break;
741 case QEvent::MouseButtonPress: {
742 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
743 d->mousePressEvent(ev, matrix.map(ev->localPos()));
744 break; }
745 case QEvent::MouseMove: {
746 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
747 d->mouseMoveEvent(ev, matrix.map(ev->localPos()));
748 break; }
749 case QEvent::MouseButtonRelease: {
750 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
751 d->mouseReleaseEvent(ev, matrix.map(ev->localPos()));
752 break; }
753 case QEvent::MouseButtonDblClick: {
754 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
755 d->mouseDoubleClickEvent(ev, matrix.map(ev->localPos()));
756 break; }
757 case QEvent::HoverEnter:
758 case QEvent::HoverMove:
759 case QEvent::HoverLeave: {
760 QHoverEvent *ev = static_cast<QHoverEvent *>(e);
761 d->hoverEvent(ev, matrix.map(ev->posF()));
762 break; }
763#if QT_CONFIG(im)
764 case QEvent::InputMethod:
765 d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
766 break;
767#endif
768 case QEvent::FocusIn:
769 case QEvent::FocusOut:
770 d->focusEvent(static_cast<QFocusEvent *>(e));
771 break;
772
773 case QEvent::ShortcutOverride:
774 if (d->interactionFlags & Qt::TextEditable) {
775 QKeyEvent* ke = static_cast<QKeyEvent *>(e);
776 if (isCommonTextEditShortcut(ke))
777 ke->accept();
778 }
779 break;
780 default:
781 break;
782 }
783}
784
785bool QQuickTextControl::event(QEvent *e)
786{
787 return QObject::event(e);
788}
789
790void QQuickTextControl::timerEvent(QTimerEvent *e)
791{
792 Q_D(QQuickTextControl);
793 if (e->timerId() == d->cursorBlinkTimer.timerId()) {
794 d->cursorOn = !d->cursorOn;
795
796 d->repaintCursor();
797 } else if (e->timerId() == d->tripleClickTimer.timerId()) {
798 d->tripleClickTimer.stop();
799 }
800}
801
802void QQuickTextControl::setPlainText(const QString &text)
803{
804 Q_D(QQuickTextControl);
805 d->setContent(Qt::PlainText, text);
806}
807
808void QQuickTextControl::setMarkdownText(const QString &text)
809{
810 Q_D(QQuickTextControl);
811 d->setContent(Qt::MarkdownText, text);
812}
813
814void QQuickTextControl::setHtml(const QString &text)
815{
816 Q_D(QQuickTextControl);
817 d->setContent(Qt::RichText, text);
818}
819
820
821void QQuickTextControlPrivate::keyReleaseEvent(QKeyEvent *e)
822{
823 if (e->key() == Qt::Key_Back) {
824 e->ignore();
825 return;
826 }
827 return;
828}
829
830void QQuickTextControlPrivate::keyPressEvent(QKeyEvent *e)
831{
832 Q_Q(QQuickTextControl);
833
834 if (e->key() == Qt::Key_Back) {
835 e->ignore();
836 return;
837 }
838
839#if QT_CONFIG(shortcut)
840 if (e == QKeySequence::SelectAll) {
841 e->accept();
842 q->selectAll();
843#if QT_CONFIG(clipboard)
844 setClipboardSelection();
845#endif
846 return;
847 }
848#if QT_CONFIG(clipboard)
849 else if (e == QKeySequence::Copy) {
850 e->accept();
851 q->copy();
852 return;
853 }
854#endif
855#endif // shortcut
856
857 if (interactionFlags & Qt::TextSelectableByKeyboard
858 && cursorMoveKeyEvent(e))
859 goto accept;
860
861 if (!(interactionFlags & Qt::TextEditable)) {
862 e->ignore();
863 return;
864 }
865
866 if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) {
867 QTextBlockFormat fmt;
868 fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
869 cursor.mergeBlockFormat(fmt);
870 goto accept;
871 }
872
873 // schedule a repaint of the region of the cursor, as when we move it we
874 // want to make sure the old cursor disappears (not noticeable when moving
875 // only a few pixels but noticeable when jumping between cells in tables for
876 // example)
877 repaintSelection();
878
879 if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) {
880 QTextBlockFormat blockFmt = cursor.blockFormat();
881 QTextList *list = cursor.currentList();
882 if (list && cursor.atBlockStart() && !cursor.hasSelection()) {
883 list->remove(cursor.block());
884 } else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
885 blockFmt.setIndent(blockFmt.indent() - 1);
886 cursor.setBlockFormat(blockFmt);
887 } else {
888 QTextCursor localCursor = cursor;
889 localCursor.deletePreviousChar();
890 }
891 goto accept;
892 }
893#if QT_CONFIG(shortcut)
894 else if (e == QKeySequence::InsertParagraphSeparator) {
895 cursor.insertBlock();
896 e->accept();
897 goto accept;
898 } else if (e == QKeySequence::InsertLineSeparator) {
899 cursor.insertText(QString(QChar::LineSeparator));
900 e->accept();
901 goto accept;
902 }
903#endif
904 if (false) {
905 }
906#if QT_CONFIG(shortcut)
907 else if (e == QKeySequence::Undo) {
908 q->undo();
909 }
910 else if (e == QKeySequence::Redo) {
911 q->redo();
912 }
913#if QT_CONFIG(clipboard)
914 else if (e == QKeySequence::Cut) {
915 q->cut();
916 }
917 else if (e == QKeySequence::Paste) {
918 QClipboard::Mode mode = QClipboard::Clipboard;
919 q->paste(mode);
920 }
921#endif
922 else if (e == QKeySequence::Delete) {
923 QTextCursor localCursor = cursor;
924 localCursor.deleteChar();
925 }
926 else if (e == QKeySequence::DeleteEndOfWord) {
927 if (!cursor.hasSelection())
928 cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
929 cursor.removeSelectedText();
930 }
931 else if (e == QKeySequence::DeleteStartOfWord) {
932 if (!cursor.hasSelection())
933 cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
934 cursor.removeSelectedText();
935 }
936 else if (e == QKeySequence::DeleteEndOfLine) {
937 QTextBlock block = cursor.block();
938 if (cursor.position() == block.position() + block.length() - 2)
939 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
940 else
941 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
942 cursor.removeSelectedText();
943 }
944#endif // shortcut
945 else {
946 goto process;
947 }
948 goto accept;
949
950process:
951 {
952 if (q->isAcceptableInput(e)) {
953 if (overwriteMode
954 // no need to call deleteChar() if we have a selection, insertText
955 // does it already
956 && !cursor.hasSelection()
957 && !cursor.atBlockEnd()) {
958 cursor.deleteChar();
959 }
960
961 cursor.insertText(e->text());
962 selectionChanged();
963 } else {
964 e->ignore();
965 return;
966 }
967 }
968
969 accept:
970
971#if QT_CONFIG(clipboard)
972 setClipboardSelection();
973#endif
974
975 e->accept();
976 cursorOn = true;
977
978 q->updateCursorRectangle(true);
979 updateCurrentCharFormat();
980}
981
982QRectF QQuickTextControlPrivate::rectForPosition(int position) const
983{
984 Q_Q(const QQuickTextControl);
985 const QTextBlock block = doc->findBlock(position);
986 if (!block.isValid())
987 return QRectF();
988 const QTextLayout *layout = block.layout();
989 const QPointF layoutPos = q->blockBoundingRect(block).topLeft();
990 int relativePos = position - block.position();
991#if QT_CONFIG(im)
992 if (preeditCursor != 0) {
993 int preeditPos = layout->preeditAreaPosition();
994 if (relativePos == preeditPos)
995 relativePos += preeditCursor;
996 else if (relativePos > preeditPos)
997 relativePos += layout->preeditAreaText().length();
998 }
999#endif
1000 QTextLine line = layout->lineForTextPosition(relativePos);
1001
1002 QRectF r;
1003
1004 if (line.isValid()) {
1005 qreal x = line.cursorToX(relativePos);
1006 qreal w = 0;
1007 if (overwriteMode) {
1008 if (relativePos < line.textLength() - line.textStart())
1009 w = line.cursorToX(relativePos + 1) - x;
1010 else
1011 w = QFontMetrics(block.layout()->font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
1012 }
1013 r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), textCursorWidth + w, line.height());
1014 } else {
1015 r = QRectF(layoutPos.x(), layoutPos.y(), textCursorWidth, 10); // #### correct height
1016 }
1017
1018 return r;
1019}
1020
1021void QQuickTextControlPrivate::mousePressEvent(QMouseEvent *e, const QPointF &pos)
1022{
1023 Q_Q(QQuickTextControl);
1024
1025 mousePressed = (interactionFlags & Qt::TextSelectableByMouse) && (e->button() & Qt::LeftButton);
1026 mousePressPos = pos.toPoint();
1027
1028 if (sendMouseEventToInputContext(e, pos))
1029 return;
1030
1031 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1032 anchorOnMousePress = q->anchorAt(pos);
1033
1034 if (cursorIsFocusIndicator) {
1035 cursorIsFocusIndicator = false;
1036 repaintSelection();
1037 cursor.clearSelection();
1038 }
1039 }
1040 if (interactionFlags & Qt::TextEditable)
1041 blockWithMarkerUnderMousePress = q->blockWithMarkerAt(pos);
1042 if (e->button() & Qt::MiddleButton) {
1043 return;
1044 } else if (!(e->button() & Qt::LeftButton)) {
1045 e->ignore();
1046 return;
1047 } else if (!(interactionFlags & (Qt::TextSelectableByMouse | Qt::TextEditable))) {
1048 if (!(interactionFlags & Qt::LinksAccessibleByMouse))
1049 e->ignore();
1050 return;
1051 }
1052
1053 cursorIsFocusIndicator = false;
1054 const QTextCursor oldSelection = cursor;
1055 const int oldCursorPos = cursor.position();
1056
1057#if QT_CONFIG(im)
1058 commitPreedit();
1059#endif
1060
1061 if (tripleClickTimer.isActive()
1062 && ((pos - tripleClickPoint).toPoint().manhattanLength() < QGuiApplication::styleHints()->startDragDistance())) {
1063
1064 cursor.movePosition(QTextCursor::StartOfBlock);
1065 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1066 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1067 selectedBlockOnTripleClick = cursor;
1068
1069 anchorOnMousePress = QString();
1070
1071 tripleClickTimer.stop();
1072 } else {
1073 int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
1074 if (cursorPos == -1) {
1075 e->ignore();
1076 return;
1077 }
1078
1079 if (e->modifiers() == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) {
1080 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1081 selectedWordOnDoubleClick = cursor;
1082 selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
1083 }
1084
1085 if (selectedBlockOnTripleClick.hasSelection())
1086 extendBlockwiseSelection(cursorPos);
1087 else if (selectedWordOnDoubleClick.hasSelection())
1088 extendWordwiseSelection(cursorPos, pos.x());
1089 else if (!wordSelectionEnabled)
1090 setCursorPosition(cursorPos, QTextCursor::KeepAnchor);
1091 } else {
1092 setCursorPosition(cursorPos);
1093 }
1094 }
1095
1096 if (cursor.position() != oldCursorPos) {
1097 q->updateCursorRectangle(true);
1098 emit q->cursorPositionChanged();
1099 }
1100 if (interactionFlags & Qt::TextEditable)
1101 _q_updateCurrentCharFormatAndSelection();
1102 else
1103 selectionChanged();
1104 repaintOldAndNewSelection(oldSelection);
1105 hadSelectionOnMousePress = cursor.hasSelection();
1106}
1107
1108void QQuickTextControlPrivate::mouseMoveEvent(QMouseEvent *e, const QPointF &mousePos)
1109{
1110 Q_Q(QQuickTextControl);
1111
1112 if ((e->buttons() & Qt::LeftButton)) {
1113 const bool editable = interactionFlags & Qt::TextEditable;
1114
1115 if (!(mousePressed
1116 || editable
1117 || selectedWordOnDoubleClick.hasSelection()
1118 || selectedBlockOnTripleClick.hasSelection()))
1119 return;
1120
1121 const QTextCursor oldSelection = cursor;
1122 const int oldCursorPos = cursor.position();
1123
1124 if (!mousePressed)
1125 return;
1126
1127 const qreal mouseX = qreal(mousePos.x());
1128
1129 int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
1130
1131#if QT_CONFIG(im)
1132 if (isPreediting()) {
1133 // note: oldCursorPos not including preedit
1134 int selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
1135 if (newCursorPos != selectionStartPos) {
1136 commitPreedit();
1137 // commit invalidates positions
1138 newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
1139 selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
1140 setCursorPosition(selectionStartPos);
1141 }
1142 }
1143#endif
1144
1145 if (newCursorPos == -1)
1146 return;
1147
1148 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1149 selectedWordOnDoubleClick = cursor;
1150 selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
1151 }
1152
1153 if (selectedBlockOnTripleClick.hasSelection())
1154 extendBlockwiseSelection(newCursorPos);
1155 else if (selectedWordOnDoubleClick.hasSelection())
1156 extendWordwiseSelection(newCursorPos, mouseX);
1157#if QT_CONFIG(im)
1158 else if (!isPreediting())
1159 setCursorPosition(newCursorPos, QTextCursor::KeepAnchor);
1160#endif
1161
1162 if (interactionFlags & Qt::TextEditable) {
1163 if (cursor.position() != oldCursorPos) {
1164 emit q->cursorPositionChanged();
1165 }
1166 _q_updateCurrentCharFormatAndSelection();
1167#if QT_CONFIG(im)
1168 if (qGuiApp)
1169 qGuiApp->inputMethod()->update(Qt::ImQueryInput);
1170#endif
1171 } else if (cursor.position() != oldCursorPos) {
1172 emit q->cursorPositionChanged();
1173 }
1174 selectionChanged(true);
1175 repaintOldAndNewSelection(oldSelection);
1176 }
1177
1178 sendMouseEventToInputContext(e, mousePos);
1179}
1180
1181void QQuickTextControlPrivate::mouseReleaseEvent(QMouseEvent *e, const QPointF &pos)
1182{
1183 Q_Q(QQuickTextControl);
1184
1185 if (sendMouseEventToInputContext(e, pos))
1186 return;
1187
1188 const QTextCursor oldSelection = cursor;
1189 const int oldCursorPos = cursor.position();
1190
1191 if (mousePressed) {
1192 mousePressed = false;
1193#if QT_CONFIG(clipboard)
1194 setClipboardSelection();
1195 selectionChanged(true);
1196 } else if (e->button() == Qt::MidButton
1197 && (interactionFlags & Qt::TextEditable)
1198 && QGuiApplication::clipboard()->supportsSelection()) {
1199 setCursorPosition(pos);
1200 const QMimeData *md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
1201 if (md)
1202 q->insertFromMimeData(md);
1203#endif
1204 }
1205
1206 repaintOldAndNewSelection(oldSelection);
1207
1208 if (cursor.position() != oldCursorPos) {
1209 emit q->cursorPositionChanged();
1210 q->updateCursorRectangle(true);
1211 }
1212
1213 if ((interactionFlags & Qt::TextEditable) && (e->button() & Qt::LeftButton) && blockWithMarkerUnderMousePress.isValid()) {
1214 QTextBlock block = q->blockWithMarkerAt(pos);
1215 if (block == blockWithMarkerUnderMousePress) {
1216 auto fmt = block.blockFormat();
1217 fmt.setMarker(fmt.marker() == QTextBlockFormat::Unchecked ?
1218 QTextBlockFormat::Checked : QTextBlockFormat::Unchecked);
1219 cursor.setBlockFormat(fmt);
1220 }
1221 }
1222
1223 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1224 if (!(e->button() & Qt::LeftButton))
1225 return;
1226
1227 const QString anchor = q->anchorAt(pos);
1228
1229 if (anchor.isEmpty())
1230 return;
1231
1232 if (!cursor.hasSelection()
1233 || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) {
1234
1235 const int anchorPos = q->hitTest(pos, Qt::ExactHit);
1236 if (anchorPos != -1) {
1237 cursor.setPosition(anchorPos);
1238
1239 QString anchor = anchorOnMousePress;
1240 anchorOnMousePress = QString();
1241 activateLinkUnderCursor(anchor);
1242 }
1243 }
1244 }
1245}
1246
1247void QQuickTextControlPrivate::mouseDoubleClickEvent(QMouseEvent *e, const QPointF &pos)
1248{
1249 Q_Q(QQuickTextControl);
1250
1251 if (e->button() == Qt::LeftButton && (interactionFlags & Qt::TextSelectableByMouse)) {
1252#if QT_CONFIG(im)
1253 commitPreedit();
1254#endif
1255
1256 const QTextCursor oldSelection = cursor;
1257 setCursorPosition(pos);
1258 QTextLine line = currentTextLine(cursor);
1259 bool doEmit = false;
1260 if (line.isValid() && line.textLength()) {
1261 cursor.select(QTextCursor::WordUnderCursor);
1262 doEmit = true;
1263 }
1264 repaintOldAndNewSelection(oldSelection);
1265
1266 cursorIsFocusIndicator = false;
1267 selectedWordOnDoubleClick = cursor;
1268
1269 tripleClickPoint = pos;
1270 tripleClickTimer.start(QGuiApplication::styleHints()->mouseDoubleClickInterval(), q);
1271 if (doEmit) {
1272 selectionChanged();
1273#if QT_CONFIG(clipboard)
1274 setClipboardSelection();
1275#endif
1276 emit q->cursorPositionChanged();
1277 q->updateCursorRectangle(true);
1278 }
1279 } else if (!sendMouseEventToInputContext(e, pos)) {
1280 e->ignore();
1281 }
1282}
1283
1284bool QQuickTextControlPrivate::sendMouseEventToInputContext(QMouseEvent *e, const QPointF &pos)
1285{
1286#if QT_CONFIG(im)
1287 Q_Q(QQuickTextControl);
1288
1289 Q_UNUSED(e);
1290
1291 if (isPreediting()) {
1292 QTextLayout *layout = cursor.block().layout();
1293 int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position();
1294
1295 if (cursorPos >= 0 && cursorPos <= layout->preeditAreaText().length()) {
1296 if (e->type() == QEvent::MouseButtonRelease) {
1297 QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos);
1298 }
1299
1300 return true;
1301 }
1302 }
1303#else
1304 Q_UNUSED(e);
1305 Q_UNUSED(pos);
1306#endif
1307 return false;
1308}
1309
1310#if QT_CONFIG(im)
1311void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
1312{
1313 Q_Q(QQuickTextControl);
1314 if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) {
1315 e->ignore();
1316 return;
1317 }
1318 bool isGettingInput = !e->commitString().isEmpty()
1319 || e->preeditString() != cursor.block().layout()->preeditAreaText()
1320 || e->replacementLength() > 0;
1321 bool forceSelectionChanged = false;
1322 int oldCursorPos = cursor.position();
1323
1324 cursor.beginEditBlock();
1325 if (isGettingInput) {
1326 cursor.removeSelectedText();
1327 }
1328
1329 QTextBlock block;
1330
1331 // insert commit string
1332 if (!e->commitString().isEmpty() || e->replacementLength()) {
1333 if (e->commitString().endsWith(QChar::LineFeed))
1334 block = cursor.block(); // Remember the block where the preedit text is
1335 QTextCursor c = cursor;
1336 c.setPosition(c.position() + e->replacementStart());
1337 c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor);
1338 c.insertText(e->commitString());
1339 }
1340
1341 for (int i = 0; i < e->attributes().size(); ++i) {
1342 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
1343 if (a.type == QInputMethodEvent::Selection) {
1344 QTextCursor oldCursor = cursor;
1345 int blockStart = a.start + cursor.block().position();
1346 cursor.setPosition(blockStart, QTextCursor::MoveAnchor);
1347 cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor);
1348 repaintOldAndNewSelection(oldCursor);
1349 forceSelectionChanged = true;
1350 }
1351 }
1352
1353 if (!block.isValid())
1354 block = cursor.block();
1355
1356 QTextLayout *layout = block.layout();
1357 if (isGettingInput) {
1358 layout->setPreeditArea(cursor.position() - block.position(), e->preeditString());
1359 emit q->preeditTextChanged();
1360 }
1361 QVector<QTextLayout::FormatRange> overrides;
1362 const int oldPreeditCursor = preeditCursor;
1363 preeditCursor = e->preeditString().length();
1364 hasImState = !e->preeditString().isEmpty();
1365 cursorVisible = true;
1366 for (int i = 0; i < e->attributes().size(); ++i) {
1367 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
1368 if (a.type == QInputMethodEvent::Cursor) {
1369 hasImState = true;
1370 preeditCursor = a.start;
1371 cursorVisible = a.length != 0;
1372 } else if (a.type == QInputMethodEvent::TextFormat) {
1373 hasImState = true;
1374 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
1375 if (f.isValid()) {
1376 QTextLayout::FormatRange o;
1377 o.start = a.start + cursor.position() - block.position();
1378 o.length = a.length;
1379 o.format = f;
1380 overrides.append(o);
1381 }
1382 }
1383 }
1384 layout->setFormats(overrides);
1385
1386 cursor.endEditBlock();
1387
1388 QTextCursorPrivate *cursor_d = QTextCursorPrivate::getPrivate(&cursor);
1389 if (cursor_d)
1390 cursor_d->setX();
1391 if (cursor.position() != oldCursorPos)
1392 emit q->cursorPositionChanged();
1393 q->updateCursorRectangle(oldPreeditCursor != preeditCursor || forceSelectionChanged || isGettingInput);
1394 selectionChanged(forceSelectionChanged);
1395}
1396
1397QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property) const
1398{
1399 return inputMethodQuery(property, QVariant());
1400}
1401
1402QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
1403{
1404 Q_D(const QQuickTextControl);
1405 QTextBlock block = d->cursor.block();
1406 switch (property) {
1407 case Qt::ImCursorRectangle:
1408 return cursorRect();
1409 case Qt::ImAnchorRectangle:
1410 return anchorRect();
1411 case Qt::ImFont:
1412 return QVariant(d->cursor.charFormat().font());
1413 case Qt::ImCursorPosition: {
1414 const QPointF pt = argument.toPointF();
1415 if (!pt.isNull())
1416 return QVariant(d->doc->documentLayout()->hitTest(pt, Qt::FuzzyHit) - block.position());
1417 return QVariant(d->cursor.position() - block.position());
1418 }
1419 case Qt::ImSurroundingText:
1420 return QVariant(block.text());
1421 case Qt::ImCurrentSelection:
1422 return QVariant(d->cursor.selectedText());
1423 case Qt::ImMaximumTextLength:
1424 return QVariant(); // No limit.
1425 case Qt::ImAnchorPosition:
1426 return QVariant(d->cursor.anchor() - block.position());
1427 case Qt::ImAbsolutePosition:
1428 return QVariant(d->cursor.position());
1429 case Qt::ImTextAfterCursor:
1430 {
1431 int maxLength = argument.isValid() ? argument.toInt() : 1024;
1432 QTextCursor tmpCursor = d->cursor;
1433 int localPos = d->cursor.position() - block.position();
1434 QString result = block.text().mid(localPos);
1435 while (result.length() < maxLength) {
1436 int currentBlock = tmpCursor.blockNumber();
1437 tmpCursor.movePosition(QTextCursor::NextBlock);
1438 if (tmpCursor.blockNumber() == currentBlock)
1439 break;
1440 result += QLatin1Char('\n') + tmpCursor.block().text();
1441 }
1442 return QVariant(result);
1443 }
1444 case Qt::ImTextBeforeCursor:
1445 {
1446 int maxLength = argument.isValid() ? argument.toInt() : 1024;
1447 QTextCursor tmpCursor = d->cursor;
1448 int localPos = d->cursor.position() - block.position();
1449 int numBlocks = 0;
1450 int resultLen = localPos;
1451 while (resultLen < maxLength) {
1452 int currentBlock = tmpCursor.blockNumber();
1453 tmpCursor.movePosition(QTextCursor::PreviousBlock);
1454 if (tmpCursor.blockNumber() == currentBlock)
1455 break;
1456 numBlocks++;
1457 resultLen += tmpCursor.block().length();
1458 }
1459 QString result;
1460 while (numBlocks) {
1461 result += tmpCursor.block().text() + QLatin1Char('\n');
1462 tmpCursor.movePosition(QTextCursor::NextBlock);
1463 --numBlocks;
1464 }
1465 result += block.text().midRef(0,localPos);
1466 return QVariant(result);
1467 }
1468 default:
1469 return QVariant();
1470 }
1471}
1472#endif // im
1473
1474void QQuickTextControlPrivate::focusEvent(QFocusEvent *e)
1475{
1476 Q_Q(QQuickTextControl);
1477 emit q->updateRequest();
1478 hasFocus = e->gotFocus();
1479 if (e->gotFocus()) {
1480 setBlinkingCursorEnabled(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
1481 } else {
1482 setBlinkingCursorEnabled(false);
1483
1484 if (cursorIsFocusIndicator
1485 && e->reason() != Qt::ActiveWindowFocusReason
1486 && e->reason() != Qt::PopupFocusReason
1487 && cursor.hasSelection()) {
1488 cursor.clearSelection();
1489 emit q->selectionChanged();
1490 }
1491 }
1492}
1493
1494void QQuickTextControlPrivate::hoverEvent(QHoverEvent *e, const QPointF &pos)
1495{
1496 Q_Q(QQuickTextControl);
1497
1498 QString link;
1499 if (e->type() != QEvent::HoverLeave)
1500 link = q->anchorAt(pos);
1501
1502 if (hoveredLink != link) {
1503 hoveredLink = link;
1504 emit q->linkHovered(link);
1505 qCDebug(DBG_HOVER_TRACE) << q << e->type() << pos << "hoveredLink" << hoveredLink;
1506 } else {
1507 QTextBlock block = q->blockWithMarkerAt(pos);
1508 if (block.isValid() != hoveredMarker)
1509 emit q->markerHovered(block.isValid());
1510 hoveredMarker = block.isValid();
1511 if (hoveredMarker)
1512 qCDebug(DBG_HOVER_TRACE) << q << e->type() << pos << "hovered marker" << block.blockFormat().marker() << block.text();
1513 }
1514}
1515
1516bool QQuickTextControl::hasImState() const
1517{
1518 Q_D(const QQuickTextControl);
1519 return d->hasImState;
1520}
1521
1522bool QQuickTextControl::overwriteMode() const
1523{
1524 Q_D(const QQuickTextControl);
1525 return d->overwriteMode;
1526}
1527
1528void QQuickTextControl::setOverwriteMode(bool overwrite)
1529{
1530 Q_D(QQuickTextControl);
1531 if (d->overwriteMode == overwrite)
1532 return;
1533 d->overwriteMode = overwrite;
1534 emit overwriteModeChanged(overwrite);
1535}
1536
1537bool QQuickTextControl::cursorVisible() const
1538{
1539 Q_D(const QQuickTextControl);
1540 return d->cursorVisible;
1541}
1542
1543void QQuickTextControl::setCursorVisible(bool visible)
1544{
1545 Q_D(QQuickTextControl);
1546 d->cursorVisible = visible;
1547 d->setBlinkingCursorEnabled(d->cursorVisible
1548 && (d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard)));
1549}
1550
1551QRectF QQuickTextControl::anchorRect() const
1552{
1553 Q_D(const QQuickTextControl);
1554 QRectF rect;
1555 QTextCursor cursor = d->cursor;
1556 if (!cursor.isNull()) {
1557 rect = d->rectForPosition(cursor.anchor());
1558 }
1559 return rect;
1560}
1561
1562QRectF QQuickTextControl::cursorRect(const QTextCursor &cursor) const
1563{
1564 Q_D(const QQuickTextControl);
1565 if (cursor.isNull())
1566 return QRectF();
1567
1568 return d->rectForPosition(cursor.position());
1569}
1570
1571QRectF QQuickTextControl::cursorRect() const
1572{
1573 Q_D(const QQuickTextControl);
1574 return cursorRect(d->cursor);
1575}
1576
1577QString QQuickTextControl::hoveredLink() const
1578{
1579 Q_D(const QQuickTextControl);
1580 return d->hoveredLink;
1581}
1582
1583QString QQuickTextControl::anchorAt(const QPointF &pos) const
1584{
1585 Q_D(const QQuickTextControl);
1586 return d->doc->documentLayout()->anchorAt(pos);
1587}
1588
1589QTextBlock QQuickTextControl::blockWithMarkerAt(const QPointF &pos) const
1590{
1591 Q_D(const QQuickTextControl);
1592 return d->doc->documentLayout()->blockWithMarkerAt(pos);
1593}
1594
1595void QQuickTextControl::setAcceptRichText(bool accept)
1596{
1597 Q_D(QQuickTextControl);
1598 d->acceptRichText = accept;
1599}
1600
1601void QQuickTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
1602{
1603 Q_D(QQuickTextControl);
1604 const QTextCursor oldSelection = d->cursor;
1605 const bool moved = d->cursor.movePosition(op, mode);
1606 d->_q_updateCurrentCharFormatAndSelection();
1607 updateCursorRectangle(true);
1608 d->repaintOldAndNewSelection(oldSelection);
1609 if (moved)
1610 emit cursorPositionChanged();
1611}
1612
1613bool QQuickTextControl::canPaste() const
1614{
1615#if QT_CONFIG(clipboard)
1616 Q_D(const QQuickTextControl);
1617 if (d->interactionFlags & Qt::TextEditable) {
1618 const QMimeData *md = QGuiApplication::clipboard()->mimeData();
1619 return md && canInsertFromMimeData(md);
1620 }
1621#endif
1622 return false;
1623}
1624
1625void QQuickTextControl::setCursorIsFocusIndicator(bool b)
1626{
1627 Q_D(QQuickTextControl);
1628 d->cursorIsFocusIndicator = b;
1629 d->repaintCursor();
1630}
1631
1632void QQuickTextControl::setWordSelectionEnabled(bool enabled)
1633{
1634 Q_D(QQuickTextControl);
1635 d->wordSelectionEnabled = enabled;
1636}
1637
1638QMimeData *QQuickTextControl::createMimeDataFromSelection() const
1639{
1640 Q_D(const QQuickTextControl);
1641 const QTextDocumentFragment fragment(d->cursor);
1642 return new QQuickTextEditMimeData(fragment);
1643}
1644
1645bool QQuickTextControl::canInsertFromMimeData(const QMimeData *source) const
1646{
1647 Q_D(const QQuickTextControl);
1648 if (d->acceptRichText)
1649 return source->hasText()
1650 || source->hasHtml()
1651 || source->hasFormat(QLatin1String("application/x-qrichtext"))
1652 || source->hasFormat(QLatin1String("application/x-qt-richtext"));
1653 else
1654 return source->hasText();
1655}
1656
1657void QQuickTextControl::insertFromMimeData(const QMimeData *source)
1658{
1659 Q_D(QQuickTextControl);
1660 if (!(d->interactionFlags & Qt::TextEditable) || !source)
1661 return;
1662
1663 bool hasData = false;
1664 QTextDocumentFragment fragment;
1665#if QT_CONFIG(texthtmlparser)
1666 if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) {
1667 // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
1668 const QString richtext = QLatin1String("<meta name=\"qrichtext\" content=\"1\" />")
1669 + QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext")));
1670 fragment = QTextDocumentFragment::fromHtml(richtext, d->doc);
1671 hasData = true;
1672 } else if (source->hasHtml() && d->acceptRichText) {
1673 fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc);
1674 hasData = true;
1675 } else {
1676 QString text = source->text();
1677 if (!text.isNull()) {
1678 fragment = QTextDocumentFragment::fromPlainText(text);
1679 hasData = true;
1680 }
1681 }
1682#else
1683 fragment = QTextDocumentFragment::fromPlainText(source->text());
1684#endif // texthtmlparser
1685
1686 if (hasData)
1687 d->cursor.insertFragment(fragment);
1688 updateCursorRectangle(true);
1689}
1690
1691void QQuickTextControlPrivate::activateLinkUnderCursor(QString href)
1692{
1693 QTextCursor oldCursor = cursor;
1694
1695 if (href.isEmpty()) {
1696 QTextCursor tmp = cursor;
1697 if (tmp.selectionStart() != tmp.position())
1698 tmp.setPosition(tmp.selectionStart());
1699 tmp.movePosition(QTextCursor::NextCharacter);
1700 href = tmp.charFormat().anchorHref();
1701 }
1702 if (href.isEmpty())
1703 return;
1704
1705 if (!cursor.hasSelection()) {
1706 QTextBlock block = cursor.block();
1707 const int cursorPos = cursor.position();
1708
1709 QTextBlock::Iterator it = block.begin();
1710 QTextBlock::Iterator linkFragment;
1711
1712 for (; !it.atEnd(); ++it) {
1713 QTextFragment fragment = it.fragment();
1714 const int fragmentPos = fragment.position();
1715 if (fragmentPos <= cursorPos &&
1716 fragmentPos + fragment.length() > cursorPos) {
1717 linkFragment = it;
1718 break;
1719 }
1720 }
1721
1722 if (!linkFragment.atEnd()) {
1723 it = linkFragment;
1724 cursor.setPosition(it.fragment().position());
1725 if (it != block.begin()) {
1726 do {
1727 --it;
1728 QTextFragment fragment = it.fragment();
1729 if (fragment.charFormat().anchorHref() != href)
1730 break;
1731 cursor.setPosition(fragment.position());
1732 } while (it != block.begin());
1733 }
1734
1735 for (it = linkFragment; !it.atEnd(); ++it) {
1736 QTextFragment fragment = it.fragment();
1737 if (fragment.charFormat().anchorHref() != href)
1738 break;
1739 cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
1740 }
1741 }
1742 }
1743
1744 if (hasFocus) {
1745 cursorIsFocusIndicator = true;
1746 } else {
1747 cursorIsFocusIndicator = false;
1748 cursor.clearSelection();
1749 }
1750 repaintOldAndNewSelection(oldCursor);
1751
1752 emit q_func()->linkActivated(href);
1753}
1754
1755#if QT_CONFIG(im)
1756bool QQuickTextControlPrivate::isPreediting() const
1757{
1758 QTextLayout *layout = cursor.block().layout();
1759 if (layout && !layout->preeditAreaText().isEmpty())
1760 return true;
1761
1762 return false;
1763}
1764
1765void QQuickTextControlPrivate::commitPreedit()
1766{
1767 Q_Q(QQuickTextControl);
1768
1769 if (!hasImState)
1770 return;
1771
1772 QGuiApplication::inputMethod()->commit();
1773
1774 if (!hasImState)
1775 return;
1776
1777 QInputMethodEvent event;
1778 QCoreApplication::sendEvent(q->parent(), &event);
1779}
1780
1781void QQuickTextControlPrivate::cancelPreedit()
1782{
1783 Q_Q(QQuickTextControl);
1784
1785 if (!hasImState)
1786 return;
1787
1788 QGuiApplication::inputMethod()->reset();
1789
1790 QInputMethodEvent event;
1791 QCoreApplication::sendEvent(q->parent(), &event);
1792}
1793#endif // im
1794
1795void QQuickTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
1796{
1797 Q_D(QQuickTextControl);
1798 if (flags == d->interactionFlags)
1799 return;
1800 d->interactionFlags = flags;
1801
1802 if (d->hasFocus)
1803 d->setBlinkingCursorEnabled(flags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
1804}
1805
1806Qt::TextInteractionFlags QQuickTextControl::textInteractionFlags() const
1807{
1808 Q_D(const QQuickTextControl);
1809 return d->interactionFlags;
1810}
1811
1812QString QQuickTextControl::toPlainText() const
1813{
1814 return document()->toPlainText();
1815}
1816
1817#if QT_CONFIG(texthtmlparser)
1818QString QQuickTextControl::toHtml() const
1819{
1820 return document()->toHtml();
1821}
1822#endif
1823
1824#if QT_CONFIG(textmarkdownwriter)
1825QString QQuickTextControl::toMarkdown() const
1826{
1827 return document()->toMarkdown();
1828}
1829#endif
1830
1831bool QQuickTextControl::cursorOn() const
1832{
1833 Q_D(const QQuickTextControl);
1834 return d->cursorOn;
1835}
1836
1837int QQuickTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
1838{
1839 Q_D(const QQuickTextControl);
1840 return d->doc->documentLayout()->hitTest(point, accuracy);
1841}
1842
1843QRectF QQuickTextControl::blockBoundingRect(const QTextBlock &block) const
1844{
1845 Q_D(const QQuickTextControl);
1846 return d->doc->documentLayout()->blockBoundingRect(block);
1847}
1848
1849QString QQuickTextControl::preeditText() const
1850{
1851#if QT_CONFIG(im)
1852 Q_D(const QQuickTextControl);
1853 QTextLayout *layout = d->cursor.block().layout();
1854 if (!layout)
1855 return QString();
1856
1857 return layout->preeditAreaText();
1858#else
1859 return QString();
1860#endif
1861}
1862
1863
1864QStringList QQuickTextEditMimeData::formats() const
1865{
1866 if (!fragment.isEmpty())
1867 return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html")
1868#if QT_CONFIG(textodfwriter)
1869 << QString::fromLatin1("application/vnd.oasis.opendocument.text")
1870#endif
1871 ;
1872 else
1873 return QMimeData::formats();
1874}
1875
1876QVariant QQuickTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
1877{
1878 if (!fragment.isEmpty())
1879 setup();
1880 return QMimeData::retrieveData(mimeType, type);
1881}
1882
1883void QQuickTextEditMimeData::setup() const
1884{
1885 QQuickTextEditMimeData *that = const_cast<QQuickTextEditMimeData *>(this);
1886#if QT_CONFIG(texthtmlparser)
1887 that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8());
1888#endif
1889#if QT_CONFIG(textodfwriter)
1890 {
1891 QBuffer buffer;
1892 QTextDocumentWriter writer(&buffer, "ODF");
1893 writer.write(fragment);
1894 buffer.close();
1895 that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data());
1896 }
1897#endif
1898 that->setText(fragment.toPlainText());
1899 fragment = QTextDocumentFragment();
1900}
1901
1902
1903QT_END_NAMESPACE
1904
1905#include "moc_qquicktextcontrol_p.cpp"
1906
1907#endif // QT_NO_TEXTCONTROL
1908