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

source code of qtdeclarative/src/quick/items/qquicktextcontrol.cpp