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