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

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