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 QObject::disconnect(sender: doc, signal: &QTextDocument::contentsChanged, receiver: q, slot: &QQuickTextControl::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 QObject::connect(sender: doc, signal: &QTextDocument::contentsChanged, context: q, slot: &QQuickTextControl::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 setDocument(doc);
588}
589
590QQuickTextControl::~QQuickTextControl()
591{
592}
593
594QTextDocument *QQuickTextControl::document() const
595{
596 Q_D(const QQuickTextControl);
597 return d->doc;
598}
599
600void QQuickTextControl::setDocument(QTextDocument *doc)
601{
602 Q_D(QQuickTextControl);
603 if (!doc || d->doc == doc)
604 return;
605
606 if (d->doc) {
607 QAbstractTextDocumentLayout *oldLayout = d->doc->documentLayout();
608 disconnect(sender: oldLayout, signal: nullptr, receiver: this, member: nullptr);
609 disconnect(sender: d->doc, signal: nullptr, receiver: this, member: nullptr);
610 }
611
612 d->doc = doc;
613 d->cursor = QTextCursor(doc);
614 d->lastCharFormat = d->cursor.charFormat();
615
616 QAbstractTextDocumentLayout *layout = doc->documentLayout();
617 connect(sender: layout, signal: &QAbstractTextDocumentLayout::update, context: this, slot: &QQuickTextControl::updateRequest);
618 connect(sender: layout, signal: &QAbstractTextDocumentLayout::updateBlock, context: this, slot: &QQuickTextControl::updateRequest);
619 connect(sender: doc, signal: &QTextDocument::contentsChanged, context: this, slot: [d]() {
620 d->_q_updateCurrentCharFormatAndSelection();
621 });
622 connect(sender: doc, signal: &QTextDocument::contentsChanged, context: this, slot: &QQuickTextControl::textChanged);
623 connect(sender: doc, signal: &QTextDocument::cursorPositionChanged, context: this, slot: [d](const QTextCursor &cursor) {
624 d->_q_updateCursorPosChanged(someCursor: cursor);
625 });
626 connect(sender: doc, signal: &QTextDocument::contentsChange, context: this, slot: &QQuickTextControl::contentsChange);
627 if (auto *qtdlayout = qobject_cast<QTextDocumentLayout *>(object: layout))
628 qtdlayout->setCursorWidth(textCursorWidth);
629}
630
631void QQuickTextControl::updateCursorRectangle(bool force)
632{
633 Q_D(QQuickTextControl);
634 const bool update = d->cursorRectangleChanged || force;
635 d->cursorRectangleChanged = false;
636 if (update)
637 emit cursorRectangleChanged();
638}
639
640void QQuickTextControl::setTextCursor(const QTextCursor &cursor)
641{
642 Q_D(QQuickTextControl);
643#if QT_CONFIG(im)
644 d->commitPreedit();
645#endif
646 d->cursorIsFocusIndicator = false;
647 const bool posChanged = cursor.position() != d->cursor.position();
648 const QTextCursor oldSelection = d->cursor;
649 d->cursor = cursor;
650 d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable);
651 d->_q_updateCurrentCharFormatAndSelection();
652 updateCursorRectangle(force: true);
653 d->repaintOldAndNewSelection(oldSelection);
654 if (posChanged)
655 emit cursorPositionChanged();
656}
657
658QTextCursor QQuickTextControl::textCursor() const
659{
660 Q_D(const QQuickTextControl);
661 return d->cursor;
662}
663
664#if QT_CONFIG(clipboard)
665
666void QQuickTextControl::cut()
667{
668 Q_D(QQuickTextControl);
669 if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection())
670 return;
671 copy();
672 d->cursor.removeSelectedText();
673}
674
675void QQuickTextControl::copy()
676{
677 Q_D(QQuickTextControl);
678 if (!d->cursor.hasSelection())
679 return;
680 QMimeData *data = createMimeDataFromSelection();
681 QGuiApplication::clipboard()->setMimeData(data);
682}
683
684void QQuickTextControl::paste(QClipboard::Mode mode)
685{
686 const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode);
687 if (md)
688 insertFromMimeData(source: md);
689}
690#endif
691
692void QQuickTextControl::selectAll()
693{
694 Q_D(QQuickTextControl);
695 const int selectionLength = qAbs(t: d->cursor.position() - d->cursor.anchor());
696 d->cursor.select(selection: QTextCursor::Document);
697 d->selectionChanged(forceEmitSelectionChanged: selectionLength != qAbs(t: d->cursor.position() - d->cursor.anchor()));
698 d->cursorIsFocusIndicator = false;
699 emit updateRequest();
700}
701
702void QQuickTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset)
703{
704 QTransform t;
705 t.translate(dx: coordinateOffset.x(), dy: coordinateOffset.y());
706 processEvent(e, transform: t);
707}
708
709void QQuickTextControl::processEvent(QEvent *e, const QTransform &transform)
710{
711 Q_D(QQuickTextControl);
712 if (d->interactionFlags == Qt::NoTextInteraction) {
713 e->ignore();
714 return;
715 }
716
717 switch (e->type()) {
718 case QEvent::KeyPress:
719 d->keyPressEvent(e: static_cast<QKeyEvent *>(e));
720 break;
721 case QEvent::KeyRelease:
722 d->keyReleaseEvent(e: static_cast<QKeyEvent *>(e));
723 break;
724 case QEvent::MouseButtonPress: {
725 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
726 d->mousePressEvent(event: ev, pos: transform.map(p: ev->position()));
727 break; }
728 case QEvent::MouseMove: {
729 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
730 d->mouseMoveEvent(event: ev, pos: transform.map(p: ev->position()));
731 break; }
732 case QEvent::MouseButtonRelease: {
733 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
734 d->mouseReleaseEvent(event: ev, pos: transform.map(p: ev->position()));
735 break; }
736 case QEvent::MouseButtonDblClick: {
737 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
738 d->mouseDoubleClickEvent(event: ev, pos: transform.map(p: ev->position()));
739 break; }
740 case QEvent::HoverEnter:
741 case QEvent::HoverMove:
742 case QEvent::HoverLeave: {
743 QHoverEvent *ev = static_cast<QHoverEvent *>(e);
744 d->hoverEvent(e: ev, pos: transform.map(p: ev->position()));
745 break; }
746#if QT_CONFIG(im)
747 case QEvent::InputMethod:
748 d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
749 break;
750#endif
751 case QEvent::FocusIn:
752 case QEvent::FocusOut:
753 d->focusEvent(e: static_cast<QFocusEvent *>(e));
754 break;
755
756 case QEvent::ShortcutOverride:
757 if (d->interactionFlags & Qt::TextEditable) {
758 QKeyEvent* ke = static_cast<QKeyEvent *>(e);
759 ke->setAccepted(isCommonTextEditShortcut(ke));
760 }
761 break;
762 default:
763 break;
764 }
765}
766
767bool QQuickTextControl::event(QEvent *e)
768{
769 return QObject::event(event: e);
770}
771
772void QQuickTextControl::timerEvent(QTimerEvent *e)
773{
774 Q_D(QQuickTextControl);
775 if (e->timerId() == d->cursorBlinkTimer.timerId()) {
776 d->cursorOn = !d->cursorOn;
777
778 d->repaintCursor();
779 }
780}
781
782void QQuickTextControl::setPlainText(const QString &text)
783{
784 Q_D(QQuickTextControl);
785 d->setContent(format: Qt::PlainText, text);
786}
787
788void QQuickTextControl::setMarkdownText(const QString &text)
789{
790 Q_D(QQuickTextControl);
791 d->setContent(format: Qt::MarkdownText, text);
792}
793
794void QQuickTextControl::setHtml(const QString &text)
795{
796 Q_D(QQuickTextControl);
797 d->setContent(format: Qt::RichText, text);
798}
799
800
801void QQuickTextControlPrivate::keyReleaseEvent(QKeyEvent *e)
802{
803 e->ignore();
804}
805
806void QQuickTextControlPrivate::keyPressEvent(QKeyEvent *e)
807{
808 Q_Q(QQuickTextControl);
809
810 if (e->key() == Qt::Key_Back) {
811 e->ignore();
812 return;
813 }
814
815#if QT_CONFIG(shortcut)
816 if (e == QKeySequence::SelectAll) {
817 e->accept();
818 q->selectAll();
819#if QT_CONFIG(clipboard)
820 setClipboardSelection();
821#endif
822 return;
823 }
824#if QT_CONFIG(clipboard)
825 else if (e == QKeySequence::Copy) {
826 e->accept();
827 q->copy();
828 return;
829 }
830#endif
831#endif // shortcut
832
833 if (interactionFlags & Qt::TextSelectableByKeyboard
834 && cursorMoveKeyEvent(e))
835 goto accept;
836
837 if (!(interactionFlags & Qt::TextEditable)) {
838 e->ignore();
839 return;
840 }
841
842 if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) {
843 QTextBlockFormat fmt;
844 fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
845 cursor.mergeBlockFormat(modifier: fmt);
846 goto accept;
847 }
848
849 // schedule a repaint of the region of the cursor, as when we move it we
850 // want to make sure the old cursor disappears (not noticeable when moving
851 // only a few pixels but noticeable when jumping between cells in tables for
852 // example)
853 repaintSelection();
854
855 if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) {
856 QTextBlockFormat blockFmt = cursor.blockFormat();
857 QTextList *list = cursor.currentList();
858 if (list && cursor.atBlockStart() && !cursor.hasSelection()) {
859 list->remove(cursor.block());
860 } else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
861 blockFmt.setIndent(blockFmt.indent() - 1);
862 cursor.setBlockFormat(blockFmt);
863 } else {
864 QTextCursor localCursor = cursor;
865 localCursor.deletePreviousChar();
866 }
867 goto accept;
868 }
869#if QT_CONFIG(shortcut)
870 else if (e == QKeySequence::InsertParagraphSeparator) {
871 cursor.insertBlock();
872 e->accept();
873 goto accept;
874 } else if (e == QKeySequence::InsertLineSeparator) {
875 cursor.insertText(text: QString(QChar::LineSeparator));
876 e->accept();
877 goto accept;
878 }
879#endif
880 if (false) {
881 }
882#if QT_CONFIG(shortcut)
883 else if (e == QKeySequence::Undo) {
884 q->undo();
885 }
886 else if (e == QKeySequence::Redo) {
887 q->redo();
888 }
889#if QT_CONFIG(clipboard)
890 else if (e == QKeySequence::Cut) {
891 q->cut();
892 }
893 else if (e == QKeySequence::Paste) {
894 QClipboard::Mode mode = QClipboard::Clipboard;
895 q->paste(mode);
896 }
897#endif
898 else if (e == QKeySequence::Delete) {
899 QTextCursor localCursor = cursor;
900 localCursor.deleteChar();
901 }
902 else if (e == QKeySequence::DeleteEndOfWord) {
903 if (!cursor.hasSelection())
904 cursor.movePosition(op: QTextCursor::NextWord, QTextCursor::KeepAnchor);
905 cursor.removeSelectedText();
906 }
907 else if (e == QKeySequence::DeleteStartOfWord) {
908 if (!cursor.hasSelection())
909 cursor.movePosition(op: QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
910 cursor.removeSelectedText();
911 }
912 else if (e == QKeySequence::DeleteEndOfLine) {
913 QTextBlock block = cursor.block();
914 if (cursor.position() == block.position() + block.length() - 2)
915 cursor.movePosition(op: QTextCursor::Right, QTextCursor::KeepAnchor);
916 else
917 cursor.movePosition(op: QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
918 cursor.removeSelectedText();
919 }
920#endif // shortcut
921 else {
922 goto process;
923 }
924 goto accept;
925
926process:
927 {
928 if (q->isAcceptableInput(event: e)) {
929#if QT_CONFIG(im)
930 // QTBUG-90362
931 // Before key press event will be handled, pre-editing part should be finished
932 if (isPreediting())
933 commitPreedit();
934#endif
935 if (overwriteMode
936 // no need to call deleteChar() if we have a selection, insertText
937 // does it already
938 && !cursor.hasSelection()
939 && !cursor.atBlockEnd()) {
940 cursor.deleteChar();
941 }
942
943 cursor.insertText(text: e->text());
944 selectionChanged();
945 } else {
946 e->ignore();
947 return;
948 }
949 }
950
951 accept:
952
953#if QT_CONFIG(clipboard)
954 setClipboardSelection();
955#endif
956
957 e->accept();
958 cursorOn = true;
959
960 q->updateCursorRectangle(force: true);
961 updateCurrentCharFormat();
962}
963
964QRectF QQuickTextControlPrivate::rectForPosition(int position) const
965{
966 Q_Q(const QQuickTextControl);
967 const QTextBlock block = doc->findBlock(pos: position);
968 if (!block.isValid())
969 return QRectF();
970 const QTextLayout *layout = block.layout();
971 const QPointF layoutPos = q->blockBoundingRect(block).topLeft();
972 int relativePos = position - block.position();
973#if QT_CONFIG(im)
974 if (preeditCursor != 0) {
975 int preeditPos = layout->preeditAreaPosition();
976 if (relativePos == preeditPos)
977 relativePos += preeditCursor;
978 else if (relativePos > preeditPos)
979 relativePos += layout->preeditAreaText().size();
980 }
981#endif
982 QTextLine line = layout->lineForTextPosition(pos: relativePos);
983
984 QRectF r;
985
986 if (line.isValid()) {
987 qreal x = line.cursorToX(cursorPos: relativePos);
988 qreal w = 0;
989 if (overwriteMode) {
990 if (relativePos < line.textLength() - line.textStart())
991 w = line.cursorToX(cursorPos: relativePos + 1) - x;
992 else
993 w = QFontMetrics(block.layout()->font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
994 }
995 r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), textCursorWidth + w, line.height());
996 } else {
997 r = QRectF(layoutPos.x(), layoutPos.y(), textCursorWidth, 10); // #### correct height
998 }
999
1000 return r;
1001}
1002
1003void QQuickTextControlPrivate::mousePressEvent(QMouseEvent *e, const QPointF &pos)
1004{
1005 Q_Q(QQuickTextControl);
1006
1007 mousePressed = (interactionFlags & Qt::TextSelectableByMouse) && (e->button() & Qt::LeftButton);
1008 mousePressPos = pos.toPoint();
1009 imSelectionAfterPress = false;
1010
1011 if (sendMouseEventToInputContext(event: e, pos))
1012 return;
1013
1014 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1015 anchorOnMousePress = q->anchorAt(pos);
1016
1017 if (cursorIsFocusIndicator) {
1018 cursorIsFocusIndicator = false;
1019 repaintSelection();
1020 cursor.clearSelection();
1021 }
1022 }
1023
1024 if (!selectByTouchDrag && !QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: e))
1025 return;
1026 if (interactionFlags & Qt::TextEditable)
1027 blockWithMarkerUnderMousePress = q->blockWithMarkerAt(pos);
1028 if (e->button() & Qt::MiddleButton) {
1029 return;
1030 } else if (!(e->button() & Qt::LeftButton)) {
1031 e->ignore();
1032 return;
1033 } else if (!(interactionFlags & (Qt::TextSelectableByMouse | Qt::TextEditable))) {
1034 if (!(interactionFlags & Qt::LinksAccessibleByMouse))
1035 e->ignore();
1036 return;
1037 }
1038
1039 cursorIsFocusIndicator = false;
1040 const QTextCursor oldSelection = cursor;
1041 const int oldCursorPos = cursor.position();
1042
1043#if QT_CONFIG(im)
1044 commitPreedit();
1045#endif
1046
1047 if ((e->timestamp() < (timestampAtLastDoubleClick + QGuiApplication::styleHints()->mouseDoubleClickInterval()))
1048 && ((pos - tripleClickPoint).toPoint().manhattanLength() < QGuiApplication::styleHints()->startDragDistance())) {
1049
1050 cursor.movePosition(op: QTextCursor::StartOfBlock);
1051 cursor.movePosition(op: QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1052 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1053 selectedBlockOnTripleClick = cursor;
1054
1055 anchorOnMousePress = QString();
1056
1057 timestampAtLastDoubleClick = 0; // do not enter this condition in case of 4(!) rapid clicks
1058 } else {
1059 int cursorPos = q->hitTest(point: pos, accuracy: Qt::FuzzyHit);
1060 if (cursorPos == -1) {
1061 e->ignore();
1062 return;
1063 }
1064
1065 if (e->modifiers() == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) {
1066 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1067 selectedWordOnDoubleClick = cursor;
1068 selectedWordOnDoubleClick.select(selection: QTextCursor::WordUnderCursor);
1069 }
1070
1071 if (selectedBlockOnTripleClick.hasSelection())
1072 extendBlockwiseSelection(suggestedNewPosition: cursorPos);
1073 else if (selectedWordOnDoubleClick.hasSelection())
1074 extendWordwiseSelection(suggestedNewPosition: cursorPos, mouseXPosition: pos.x());
1075 else if (!wordSelectionEnabled)
1076 setCursorPosition(pos: cursorPos, mode: QTextCursor::KeepAnchor);
1077 } else {
1078 setCursorPosition(pos: cursorPos);
1079 }
1080 }
1081
1082 if (cursor.position() != oldCursorPos) {
1083 q->updateCursorRectangle(force: true);
1084 emit q->cursorPositionChanged();
1085 }
1086 if (interactionFlags & Qt::TextEditable)
1087 _q_updateCurrentCharFormatAndSelection();
1088 else
1089 selectionChanged();
1090 repaintOldAndNewSelection(oldSelection);
1091 hadSelectionOnMousePress = cursor.hasSelection();
1092}
1093
1094void QQuickTextControlPrivate::mouseMoveEvent(QMouseEvent *e, const QPointF &mousePos)
1095{
1096 if (!selectByTouchDrag && !QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: e))
1097 return;
1098
1099 Q_Q(QQuickTextControl);
1100
1101 if ((e->buttons() & Qt::LeftButton)) {
1102 const bool editable = interactionFlags & Qt::TextEditable;
1103
1104 if (!(mousePressed
1105 || editable
1106 || selectedWordOnDoubleClick.hasSelection()
1107 || selectedBlockOnTripleClick.hasSelection()))
1108 return;
1109
1110 const QTextCursor oldSelection = cursor;
1111 const int oldCursorPos = cursor.position();
1112
1113 if (!mousePressed)
1114 return;
1115
1116 const qreal mouseX = qreal(mousePos.x());
1117
1118 int newCursorPos = q->hitTest(point: mousePos, accuracy: Qt::FuzzyHit);
1119
1120#if QT_CONFIG(im)
1121 if (isPreediting()) {
1122 // note: oldCursorPos not including preedit
1123 int selectionStartPos = q->hitTest(point: mousePressPos, accuracy: Qt::FuzzyHit);
1124 if (newCursorPos != selectionStartPos) {
1125 commitPreedit();
1126 // commit invalidates positions
1127 newCursorPos = q->hitTest(point: mousePos, accuracy: Qt::FuzzyHit);
1128 selectionStartPos = q->hitTest(point: mousePressPos, accuracy: Qt::FuzzyHit);
1129 setCursorPosition(pos: selectionStartPos);
1130 }
1131 }
1132#endif
1133
1134 if (newCursorPos == -1)
1135 return;
1136
1137 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1138 selectedWordOnDoubleClick = cursor;
1139 selectedWordOnDoubleClick.select(selection: QTextCursor::WordUnderCursor);
1140 }
1141
1142 if (selectedBlockOnTripleClick.hasSelection())
1143 extendBlockwiseSelection(suggestedNewPosition: newCursorPos);
1144 else if (selectedWordOnDoubleClick.hasSelection())
1145 extendWordwiseSelection(suggestedNewPosition: newCursorPos, mouseXPosition: mouseX);
1146#if QT_CONFIG(im)
1147 else if (!isPreediting())
1148 setCursorPosition(pos: newCursorPos, mode: QTextCursor::KeepAnchor);
1149#endif
1150
1151 if (interactionFlags & Qt::TextEditable) {
1152 if (cursor.position() != oldCursorPos) {
1153 emit q->cursorPositionChanged();
1154 }
1155 _q_updateCurrentCharFormatAndSelection();
1156#if QT_CONFIG(im)
1157 if (qGuiApp)
1158 qGuiApp->inputMethod()->update(queries: Qt::ImQueryInput);
1159#endif
1160 } else if (cursor.position() != oldCursorPos) {
1161 emit q->cursorPositionChanged();
1162 }
1163 selectionChanged(forceEmitSelectionChanged: true);
1164 repaintOldAndNewSelection(oldSelection);
1165 }
1166
1167 sendMouseEventToInputContext(event: e, pos: mousePos);
1168}
1169
1170void QQuickTextControlPrivate::mouseReleaseEvent(QMouseEvent *e, const QPointF &pos)
1171{
1172 Q_Q(QQuickTextControl);
1173
1174 if (sendMouseEventToInputContext(event: e, pos))
1175 return;
1176
1177 const QTextCursor oldSelection = cursor;
1178 const int oldCursorPos = cursor.position();
1179 const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: e);
1180
1181 if (mousePressed) {
1182 mousePressed = false;
1183#if QT_CONFIG(clipboard)
1184 setClipboardSelection();
1185 selectionChanged(forceEmitSelectionChanged: true);
1186 } else if (e->button() == Qt::MiddleButton
1187 && (interactionFlags & Qt::TextEditable)
1188 && QGuiApplication::clipboard()->supportsSelection()) {
1189 setCursorPosition(pos);
1190 const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode: QClipboard::Selection);
1191 if (md)
1192 q->insertFromMimeData(source: md);
1193#endif
1194 }
1195 if (!isMouse && !selectByTouchDrag && !imSelectionAfterPress && interactionFlags.testFlag(flag: Qt::TextEditable))
1196 setCursorPosition(pos);
1197
1198 repaintOldAndNewSelection(oldSelection);
1199
1200 if (cursor.position() != oldCursorPos) {
1201 emit q->cursorPositionChanged();
1202 q->updateCursorRectangle(force: true);
1203 }
1204
1205 if ((isMouse || selectByTouchDrag) && interactionFlags.testFlag(flag: Qt::TextEditable) &&
1206 (e->button() & Qt::LeftButton) && blockWithMarkerUnderMousePress.isValid()) {
1207 QTextBlock block = q->blockWithMarkerAt(pos);
1208 if (block == blockWithMarkerUnderMousePress) {
1209 auto fmt = block.blockFormat();
1210 fmt.setMarker(fmt.marker() == QTextBlockFormat::MarkerType::Unchecked ?
1211 QTextBlockFormat::MarkerType::Checked : QTextBlockFormat::MarkerType::Unchecked);
1212 cursor.setBlockFormat(fmt);
1213 }
1214 }
1215
1216 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1217 if (!(e->button() & Qt::LeftButton))
1218 return;
1219
1220 const QString anchor = q->anchorAt(pos);
1221
1222 if (anchor.isEmpty())
1223 return;
1224
1225 if (!cursor.hasSelection()
1226 || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) {
1227
1228 const int anchorPos = q->hitTest(point: pos, accuracy: Qt::ExactHit);
1229 if (anchorPos != -1) {
1230 cursor.setPosition(pos: anchorPos);
1231
1232 QString anchor = anchorOnMousePress;
1233 anchorOnMousePress = QString();
1234 activateLinkUnderCursor(href: anchor);
1235 }
1236 }
1237 }
1238}
1239
1240void QQuickTextControlPrivate::mouseDoubleClickEvent(QMouseEvent *e, const QPointF &pos)
1241{
1242 Q_Q(QQuickTextControl);
1243
1244 if (e->button() == Qt::LeftButton && (interactionFlags & Qt::TextSelectableByMouse)
1245 && (selectByTouchDrag || QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: e))) {
1246#if QT_CONFIG(im)
1247 commitPreedit();
1248#endif
1249
1250 const QTextCursor oldSelection = cursor;
1251 setCursorPosition(pos);
1252 QTextLine line = currentTextLine(cursor);
1253 bool doEmit = false;
1254 if (line.isValid() && line.textLength()) {
1255 cursor.select(selection: QTextCursor::WordUnderCursor);
1256 doEmit = true;
1257 }
1258 repaintOldAndNewSelection(oldSelection);
1259
1260 cursorIsFocusIndicator = false;
1261 selectedWordOnDoubleClick = cursor;
1262
1263 tripleClickPoint = pos;
1264 timestampAtLastDoubleClick = e->timestamp();
1265 if (doEmit) {
1266 selectionChanged();
1267#if QT_CONFIG(clipboard)
1268 setClipboardSelection();
1269#endif
1270 emit q->cursorPositionChanged();
1271 q->updateCursorRectangle(force: true);
1272 }
1273 } else if (!sendMouseEventToInputContext(event: e, pos)) {
1274 e->ignore();
1275 }
1276}
1277
1278bool QQuickTextControlPrivate::sendMouseEventToInputContext(QMouseEvent *e, const QPointF &pos)
1279{
1280#if QT_CONFIG(im)
1281 Q_Q(QQuickTextControl);
1282
1283 Q_UNUSED(e);
1284
1285 if (isPreediting()) {
1286 QTextLayout *layout = cursor.block().layout();
1287 int cursorPos = q->hitTest(point: pos, accuracy: Qt::FuzzyHit) - cursor.position();
1288
1289 if (cursorPos >= 0 && cursorPos <= layout->preeditAreaText().size()) {
1290 if (e->type() == QEvent::MouseButtonRelease) {
1291 QGuiApplication::inputMethod()->invokeAction(a: QInputMethod::Click, cursorPosition: cursorPos);
1292 }
1293
1294 return true;
1295 }
1296 }
1297#else
1298 Q_UNUSED(e);
1299 Q_UNUSED(pos);
1300#endif
1301 return false;
1302}
1303
1304#if QT_CONFIG(im)
1305void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
1306{
1307 Q_Q(QQuickTextControl);
1308 if (cursor.isNull()) {
1309 e->ignore();
1310 return;
1311 }
1312 bool textEditable = interactionFlags.testFlag(flag: Qt::TextEditable);
1313 bool isGettingInput = !e->commitString().isEmpty()
1314 || e->preeditString() != cursor.block().layout()->preeditAreaText()
1315 || e->replacementLength() > 0;
1316 bool forceSelectionChanged = false;
1317 int oldCursorPos = cursor.position();
1318
1319 cursor.beginEditBlock();
1320 if (isGettingInput && textEditable) {
1321 cursor.removeSelectedText();
1322 }
1323
1324 QTextBlock block;
1325
1326 // insert commit string
1327 if (textEditable && (!e->commitString().isEmpty() || e->replacementLength())) {
1328 if (e->commitString().endsWith(c: QChar::LineFeed))
1329 block = cursor.block(); // Remember the block where the preedit text is
1330 QTextCursor c = cursor;
1331 c.setPosition(pos: c.position() + e->replacementStart());
1332 c.setPosition(pos: c.position() + e->replacementLength(), mode: QTextCursor::KeepAnchor);
1333 c.insertText(text: e->commitString());
1334 }
1335
1336 if (interactionFlags & (Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse)) {
1337 for (int i = 0; i < e->attributes().size(); ++i) {
1338 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
1339 if (a.type == QInputMethodEvent::Selection) {
1340 if (mousePressed)
1341 imSelectionAfterPress = true;
1342 QTextCursor oldCursor = cursor;
1343 int blockStart = a.start + cursor.block().position();
1344 cursor.setPosition(pos: blockStart, mode: QTextCursor::MoveAnchor);
1345 cursor.setPosition(pos: blockStart + a.length, mode: QTextCursor::KeepAnchor);
1346 repaintOldAndNewSelection(oldSelection: oldCursor);
1347 forceSelectionChanged = true;
1348 }
1349 }
1350 }
1351
1352 if (!block.isValid())
1353 block = cursor.block();
1354
1355 const int oldPreeditCursor = preeditCursor;
1356 if (textEditable) {
1357 QTextLayout *layout = block.layout();
1358 if (isGettingInput) {
1359 layout->setPreeditArea(position: cursor.position() - block.position(), text: e->preeditString());
1360 emit q->preeditTextChanged();
1361 }
1362 QVector<QTextLayout::FormatRange> overrides;
1363 preeditCursor = e->preeditString().size();
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>(v: 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(t: o);
1381 }
1382 }
1383 }
1384 layout->setFormats(overrides);
1385 }
1386
1387 cursor.endEditBlock();
1388
1389 QTextCursorPrivate *cursor_d = QTextCursorPrivate::getPrivate(c: &cursor);
1390 if (cursor_d)
1391 cursor_d->setX();
1392 if (cursor.position() != oldCursorPos)
1393 emit q->cursorPositionChanged();
1394 q->updateCursorRectangle(force: oldPreeditCursor != preeditCursor || forceSelectionChanged || isGettingInput);
1395 selectionChanged(forceEmitSelectionChanged: forceSelectionChanged);
1396}
1397
1398QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property) const
1399{
1400 return inputMethodQuery(query: property, argument: QVariant());
1401}
1402
1403QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property, const QVariant &argument) const
1404{
1405 Q_D(const QQuickTextControl);
1406 QTextBlock block = d->cursor.block();
1407 switch (property) {
1408 case Qt::ImCursorRectangle:
1409 return cursorRect();
1410 case Qt::ImAnchorRectangle:
1411 return anchorRect();
1412 case Qt::ImFont:
1413 return QVariant(d->cursor.charFormat().font());
1414 case Qt::ImCursorPosition: {
1415 const QPointF pt = argument.toPointF();
1416 if (!pt.isNull())
1417 return QVariant(d->doc->documentLayout()->hitTest(point: pt, accuracy: Qt::FuzzyHit) - block.position());
1418 return QVariant(d->cursor.position() - block.position());
1419 }
1420 case Qt::ImSurroundingText:
1421 return QVariant(block.text());
1422 case Qt::ImCurrentSelection:
1423 return QVariant(d->cursor.selectedText());
1424 case Qt::ImMaximumTextLength:
1425 return QVariant(); // No limit.
1426 case Qt::ImAnchorPosition:
1427 return QVariant(d->cursor.anchor() - block.position());
1428 case Qt::ImAbsolutePosition:
1429 return QVariant(d->cursor.position());
1430 case Qt::ImTextAfterCursor:
1431 {
1432 int maxLength = argument.isValid() ? argument.toInt() : 1024;
1433 QTextCursor tmpCursor = d->cursor;
1434 int localPos = d->cursor.position() - block.position();
1435 QString result = block.text().mid(position: localPos);
1436 while (result.size() < maxLength) {
1437 int currentBlock = tmpCursor.blockNumber();
1438 tmpCursor.movePosition(op: QTextCursor::NextBlock);
1439 if (tmpCursor.blockNumber() == currentBlock)
1440 break;
1441 result += QLatin1Char('\n') + tmpCursor.block().text();
1442 }
1443 return QVariant(result);
1444 }
1445 case Qt::ImTextBeforeCursor:
1446 {
1447 int maxLength = argument.isValid() ? argument.toInt() : 1024;
1448 QTextCursor tmpCursor = d->cursor;
1449 int localPos = d->cursor.position() - block.position();
1450 int numBlocks = 0;
1451 int resultLen = localPos;
1452 while (resultLen < maxLength) {
1453 int currentBlock = tmpCursor.blockNumber();
1454 tmpCursor.movePosition(op: QTextCursor::PreviousBlock);
1455 if (tmpCursor.blockNumber() == currentBlock)
1456 break;
1457 numBlocks++;
1458 resultLen += tmpCursor.block().length();
1459 }
1460 QString result;
1461 while (numBlocks) {
1462 result += tmpCursor.block().text() + QLatin1Char('\n');
1463 tmpCursor.movePosition(op: QTextCursor::NextBlock);
1464 --numBlocks;
1465 }
1466 result += QStringView{block.text()}.mid(pos: 0,n: localPos);
1467 return QVariant(result);
1468 }
1469 case Qt::ImReadOnly:
1470 return QVariant(!d->interactionFlags.testFlag(flag: Qt::TextEditable));
1471 default:
1472 return QVariant();
1473 }
1474}
1475#endif // im
1476
1477void QQuickTextControlPrivate::focusEvent(QFocusEvent *e)
1478{
1479 Q_Q(QQuickTextControl);
1480 emit q->updateRequest();
1481 hasFocus = e->gotFocus();
1482 if (e->gotFocus()) {
1483 setBlinkingCursorEnabled(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
1484 } else {
1485 setBlinkingCursorEnabled(false);
1486
1487 if (cursorIsFocusIndicator
1488 && e->reason() != Qt::ActiveWindowFocusReason
1489 && e->reason() != Qt::PopupFocusReason
1490 && cursor.hasSelection()) {
1491 cursor.clearSelection();
1492 emit q->selectionChanged();
1493 }
1494 }
1495}
1496
1497void QQuickTextControlPrivate::hoverEvent(QHoverEvent *e, const QPointF &pos)
1498{
1499 Q_Q(QQuickTextControl);
1500
1501 QString link;
1502 if (e->type() != QEvent::HoverLeave)
1503 link = q->anchorAt(pos);
1504
1505 if (hoveredLink != link) {
1506 hoveredLink = link;
1507 emit q->linkHovered(link);
1508 qCDebug(lcHoverTrace) << q << e->type() << pos << "hoveredLink" << hoveredLink;
1509 } else {
1510 QTextBlock block = q->blockWithMarkerAt(pos);
1511 if (block.isValid() != hoveredMarker)
1512 emit q->markerHovered(marker: block.isValid());
1513 hoveredMarker = block.isValid();
1514 if (hoveredMarker)
1515 qCDebug(lcHoverTrace) << q << e->type() << pos << "hovered marker" << int(block.blockFormat().marker()) << block.text();
1516 }
1517}
1518
1519bool QQuickTextControl::hasImState() const
1520{
1521 Q_D(const QQuickTextControl);
1522 return d->hasImState;
1523}
1524
1525bool QQuickTextControl::overwriteMode() const
1526{
1527 Q_D(const QQuickTextControl);
1528 return d->overwriteMode;
1529}
1530
1531void QQuickTextControl::setOverwriteMode(bool overwrite)
1532{
1533 Q_D(QQuickTextControl);
1534 if (d->overwriteMode == overwrite)
1535 return;
1536 d->overwriteMode = overwrite;
1537 emit overwriteModeChanged(overwriteMode: overwrite);
1538}
1539
1540bool QQuickTextControl::cursorVisible() const
1541{
1542 Q_D(const QQuickTextControl);
1543 return d->cursorVisible;
1544}
1545
1546void QQuickTextControl::setCursorVisible(bool visible)
1547{
1548 Q_D(QQuickTextControl);
1549 d->cursorVisible = visible;
1550 d->setBlinkingCursorEnabled(d->cursorVisible
1551 && (d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard)));
1552}
1553
1554QRectF QQuickTextControl::anchorRect() const
1555{
1556 Q_D(const QQuickTextControl);
1557 QRectF rect;
1558 QTextCursor cursor = d->cursor;
1559 if (!cursor.isNull()) {
1560 rect = d->rectForPosition(position: cursor.anchor());
1561 }
1562 return rect;
1563}
1564
1565QRectF QQuickTextControl::cursorRect(const QTextCursor &cursor) const
1566{
1567 Q_D(const QQuickTextControl);
1568 if (cursor.isNull())
1569 return QRectF();
1570
1571 return d->rectForPosition(position: cursor.position());
1572}
1573
1574QRectF QQuickTextControl::cursorRect() const
1575{
1576 Q_D(const QQuickTextControl);
1577 return cursorRect(cursor: d->cursor);
1578}
1579
1580QString QQuickTextControl::hoveredLink() const
1581{
1582 Q_D(const QQuickTextControl);
1583 return d->hoveredLink;
1584}
1585
1586QString QQuickTextControl::anchorAt(const QPointF &pos) const
1587{
1588 Q_D(const QQuickTextControl);
1589 return d->doc->documentLayout()->anchorAt(pos);
1590}
1591
1592QTextBlock QQuickTextControl::blockWithMarkerAt(const QPointF &pos) const
1593{
1594 Q_D(const QQuickTextControl);
1595 return d->doc->documentLayout()->blockWithMarkerAt(pos);
1596}
1597
1598void QQuickTextControl::setAcceptRichText(bool accept)
1599{
1600 Q_D(QQuickTextControl);
1601 d->acceptRichText = accept;
1602}
1603
1604void QQuickTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
1605{
1606 Q_D(QQuickTextControl);
1607 const QTextCursor oldSelection = d->cursor;
1608 const bool moved = d->cursor.movePosition(op, mode);
1609 d->_q_updateCurrentCharFormatAndSelection();
1610 updateCursorRectangle(force: true);
1611 d->repaintOldAndNewSelection(oldSelection);
1612 if (moved)
1613 emit cursorPositionChanged();
1614}
1615
1616bool QQuickTextControl::canPaste() const
1617{
1618#if QT_CONFIG(clipboard)
1619 Q_D(const QQuickTextControl);
1620 if (d->interactionFlags & Qt::TextEditable) {
1621 const QMimeData *md = QGuiApplication::clipboard()->mimeData();
1622 return md && canInsertFromMimeData(source: md);
1623 }
1624#endif
1625 return false;
1626}
1627
1628void QQuickTextControl::setCursorIsFocusIndicator(bool b)
1629{
1630 Q_D(QQuickTextControl);
1631 d->cursorIsFocusIndicator = b;
1632 d->repaintCursor();
1633}
1634
1635void QQuickTextControl::setWordSelectionEnabled(bool enabled)
1636{
1637 Q_D(QQuickTextControl);
1638 d->wordSelectionEnabled = enabled;
1639}
1640
1641#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
1642void QQuickTextControl::setTouchDragSelectionEnabled(bool enabled)
1643{
1644 Q_D(QQuickTextControl);
1645 d->selectByTouchDrag = enabled;
1646}
1647#endif
1648
1649QMimeData *QQuickTextControl::createMimeDataFromSelection() const
1650{
1651 Q_D(const QQuickTextControl);
1652 const QTextDocumentFragment fragment(d->cursor);
1653 return new QQuickTextEditMimeData(fragment);
1654}
1655
1656bool QQuickTextControl::canInsertFromMimeData(const QMimeData *source) const
1657{
1658 Q_D(const QQuickTextControl);
1659 if (d->acceptRichText)
1660 return source->hasText()
1661 || source->hasHtml()
1662 || source->hasFormat(mimetype: QLatin1String("application/x-qrichtext"))
1663 || source->hasFormat(mimetype: QLatin1String("application/x-qt-richtext"));
1664 else
1665 return source->hasText();
1666}
1667
1668void QQuickTextControl::insertFromMimeData(const QMimeData *source)
1669{
1670 Q_D(QQuickTextControl);
1671 if (!(d->interactionFlags & Qt::TextEditable) || !source)
1672 return;
1673
1674 bool hasData = false;
1675 QTextDocumentFragment fragment;
1676#if QT_CONFIG(texthtmlparser)
1677 if (source->hasFormat(mimetype: QLatin1String("application/x-qrichtext")) && d->acceptRichText) {
1678 // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
1679 const QString richtext = QLatin1String("<meta name=\"qrichtext\" content=\"1\" />")
1680 + QString::fromUtf8(ba: source->data(mimetype: QLatin1String("application/x-qrichtext")));
1681 fragment = QTextDocumentFragment::fromHtml(html: richtext, resourceProvider: d->doc);
1682 hasData = true;
1683 } else if (source->hasHtml() && d->acceptRichText) {
1684 fragment = QTextDocumentFragment::fromHtml(html: source->html(), resourceProvider: d->doc);
1685 hasData = true;
1686 } else {
1687 QString text = source->text();
1688 if (!text.isNull()) {
1689 fragment = QTextDocumentFragment::fromPlainText(plainText: text);
1690 hasData = true;
1691 }
1692 }
1693#else
1694 fragment = QTextDocumentFragment::fromPlainText(source->text());
1695#endif // texthtmlparser
1696
1697 if (hasData)
1698 d->cursor.insertFragment(fragment);
1699 updateCursorRectangle(force: true);
1700}
1701
1702void QQuickTextControlPrivate::activateLinkUnderCursor(QString href)
1703{
1704 QTextCursor oldCursor = cursor;
1705
1706 if (href.isEmpty()) {
1707 QTextCursor tmp = cursor;
1708 if (tmp.selectionStart() != tmp.position())
1709 tmp.setPosition(pos: tmp.selectionStart());
1710 tmp.movePosition(op: QTextCursor::NextCharacter);
1711 href = tmp.charFormat().anchorHref();
1712 }
1713 if (href.isEmpty())
1714 return;
1715
1716 if (!cursor.hasSelection()) {
1717 QTextBlock block = cursor.block();
1718 const int cursorPos = cursor.position();
1719
1720 QTextBlock::Iterator it = block.begin();
1721 QTextBlock::Iterator linkFragment;
1722
1723 for (; !it.atEnd(); ++it) {
1724 QTextFragment fragment = it.fragment();
1725 const int fragmentPos = fragment.position();
1726 if (fragmentPos <= cursorPos &&
1727 fragmentPos + fragment.length() > cursorPos) {
1728 linkFragment = it;
1729 break;
1730 }
1731 }
1732
1733 if (!linkFragment.atEnd()) {
1734 it = linkFragment;
1735 cursor.setPosition(pos: it.fragment().position());
1736 if (it != block.begin()) {
1737 do {
1738 --it;
1739 QTextFragment fragment = it.fragment();
1740 if (fragment.charFormat().anchorHref() != href)
1741 break;
1742 cursor.setPosition(pos: fragment.position());
1743 } while (it != block.begin());
1744 }
1745
1746 for (it = linkFragment; !it.atEnd(); ++it) {
1747 QTextFragment fragment = it.fragment();
1748 if (fragment.charFormat().anchorHref() != href)
1749 break;
1750 cursor.setPosition(pos: fragment.position() + fragment.length(), mode: QTextCursor::KeepAnchor);
1751 }
1752 }
1753 }
1754
1755 if (hasFocus) {
1756 cursorIsFocusIndicator = true;
1757 } else {
1758 cursorIsFocusIndicator = false;
1759 cursor.clearSelection();
1760 }
1761 repaintOldAndNewSelection(oldSelection: oldCursor);
1762
1763 emit q_func()->linkActivated(link: href);
1764}
1765
1766#if QT_CONFIG(im)
1767bool QQuickTextControlPrivate::isPreediting() const
1768{
1769 QTextLayout *layout = cursor.block().layout();
1770 if (layout && !layout->preeditAreaText().isEmpty())
1771 return true;
1772
1773 return false;
1774}
1775
1776void QQuickTextControlPrivate::commitPreedit()
1777{
1778 Q_Q(QQuickTextControl);
1779
1780 if (!hasImState)
1781 return;
1782
1783 QGuiApplication::inputMethod()->commit();
1784
1785 if (!hasImState)
1786 return;
1787
1788 QInputMethodEvent event;
1789 QCoreApplication::sendEvent(receiver: q->parent(), event: &event);
1790}
1791
1792void QQuickTextControlPrivate::cancelPreedit()
1793{
1794 Q_Q(QQuickTextControl);
1795
1796 if (!hasImState)
1797 return;
1798
1799 QGuiApplication::inputMethod()->reset();
1800
1801 QInputMethodEvent event;
1802 QCoreApplication::sendEvent(receiver: q->parent(), event: &event);
1803}
1804#endif // im
1805
1806void QQuickTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
1807{
1808 Q_D(QQuickTextControl);
1809 if (flags == d->interactionFlags)
1810 return;
1811 d->interactionFlags = flags;
1812
1813 if (d->hasFocus)
1814 d->setBlinkingCursorEnabled(flags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
1815}
1816
1817Qt::TextInteractionFlags QQuickTextControl::textInteractionFlags() const
1818{
1819 Q_D(const QQuickTextControl);
1820 return d->interactionFlags;
1821}
1822
1823QString QQuickTextControl::toPlainText() const
1824{
1825 return document()->toPlainText();
1826}
1827
1828#if QT_CONFIG(texthtmlparser)
1829QString QQuickTextControl::toHtml() const
1830{
1831 return document()->toHtml();
1832}
1833#endif
1834
1835#if QT_CONFIG(textmarkdownwriter)
1836QString QQuickTextControl::toMarkdown() const
1837{
1838 return document()->toMarkdown();
1839}
1840#endif
1841
1842bool QQuickTextControl::cursorOn() const
1843{
1844 Q_D(const QQuickTextControl);
1845 return d->cursorOn;
1846}
1847
1848int QQuickTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
1849{
1850 Q_D(const QQuickTextControl);
1851 return d->doc->documentLayout()->hitTest(point, accuracy);
1852}
1853
1854QRectF QQuickTextControl::blockBoundingRect(const QTextBlock &block) const
1855{
1856 Q_D(const QQuickTextControl);
1857 return d->doc->documentLayout()->blockBoundingRect(block);
1858}
1859
1860QString QQuickTextControl::preeditText() const
1861{
1862#if QT_CONFIG(im)
1863 Q_D(const QQuickTextControl);
1864 QTextLayout *layout = d->cursor.block().layout();
1865 if (!layout)
1866 return QString();
1867
1868 return layout->preeditAreaText();
1869#else
1870 return QString();
1871#endif
1872}
1873
1874
1875QStringList QQuickTextEditMimeData::formats() const
1876{
1877 if (!fragment.isEmpty())
1878 return QStringList() << QString::fromLatin1(ba: "text/plain") << QString::fromLatin1(ba: "text/html")
1879#if QT_CONFIG(textodfwriter)
1880 << QString::fromLatin1(ba: "application/vnd.oasis.opendocument.text")
1881#endif
1882 ;
1883 else
1884 return QMimeData::formats();
1885}
1886
1887QVariant QQuickTextEditMimeData::retrieveData(const QString &mimeType, QMetaType type) const
1888{
1889 if (!fragment.isEmpty())
1890 setup();
1891 return QMimeData::retrieveData(mimetype: mimeType, preferredType: type);
1892}
1893
1894void QQuickTextEditMimeData::setup() const
1895{
1896 QQuickTextEditMimeData *that = const_cast<QQuickTextEditMimeData *>(this);
1897#if QT_CONFIG(texthtmlparser)
1898 that->setData(mimetype: QLatin1String("text/html"), data: fragment.toHtml().toUtf8());
1899#endif
1900#if QT_CONFIG(textodfwriter)
1901 {
1902 QBuffer buffer;
1903 QTextDocumentWriter writer(&buffer, "ODF");
1904 writer.write(fragment);
1905 buffer.close();
1906 that->setData(mimetype: QLatin1String("application/vnd.oasis.opendocument.text"), data: buffer.data());
1907 }
1908#endif
1909 that->setText(fragment.toPlainText());
1910 fragment = QTextDocumentFragment();
1911}
1912
1913
1914QT_END_NAMESPACE
1915
1916#include "moc_qquicktextcontrol_p.cpp"
1917
1918#endif // QT_NO_TEXTCONTROL
1919

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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