Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "qquicktextcontrol_p.h"
++ #include "qquicktextcontrol_p_p.h"
++
++ #ifndef QT_NO_TEXTCONTROL
++
++ #include <qcoreapplication.h>
++ #include <qfont.h>
++ #include <qfontmetrics.h>
++ #include <qevent.h>
++ #include <qdebug.h>
++ #include <qclipboard.h>
++ #include <qtimer.h>
++ #include <qinputmethod.h>
++ #include "private/qtextdocumentlayout_p.h"
++ #include "private/qabstracttextdocumentlayout_p.h"
++ #include "qtextdocument.h"
++ #include "private/qtextdocument_p.h"
++ #include "qtextlist.h"
++ #include "qtextdocumentwriter.h"
++ #include "private/qtextcursor_p.h"
++ #include <QtCore/qloggingcategory.h>
++
++ #include <qtextformat.h>
++ #include <qtransform.h>
++ #include <qdatetime.h>
++ #include <qbuffer.h>
++ #include <qguiapplication.h>
++ #include <limits.h>
++ #include <qtexttable.h>
++ #include <qvariant.h>
++ #include <qurl.h>
++ #include <qstylehints.h>
++ #include <qmetaobject.h>
++
++ #include <private/qqmlglobal_p.h>
++ #include <private/qquickdeliveryagent_p_p.h>
++
++ // ### these should come from QStyleHints
++ const int textCursorWidth = 1;
++
++ QT_BEGIN_NAMESPACE
++
++ // could go into QTextCursor...
++ static QTextLine currentTextLine(const QTextCursor &cursor)
++ {
++ const QTextBlock block = cursor.block();
++ if (!block.isValid())
++ return QTextLine();
++
++ const QTextLayout *layout = block.layout();
++ if (!layout)
++ return QTextLine();
++
++ const int relativePos = cursor.position() - block.position();
++ return layout->lineForTextPosition(relativePos);
++ }
++
++ QQuickTextControlPrivate::QQuickTextControlPrivate()
++ : doc(nullptr),
++ #if QT_CONFIG(im)
++ preeditCursor(0),
++ #endif
++ interactionFlags(Qt::TextEditorInteraction),
++ cursorOn(false),
++ cursorIsFocusIndicator(false),
++ mousePressed(false),
++ lastSelectionState(false),
++ ignoreAutomaticScrollbarAdjustement(false),
++ overwriteMode(false),
++ acceptRichText(true),
++ cursorVisible(false),
++ cursorBlinkingEnabled(false),
++ hasFocus(false),
++ hadSelectionOnMousePress(false),
++ wordSelectionEnabled(false),
++ hasImState(false),
++ cursorRectangleChanged(false),
++ hoveredMarker(false),
++ selectByTouchDrag(false),
++ imSelectionAfterPress(false),
++ beingEdited(false),
++ lastSelectionStart(-1),
++ lastSelectionEnd(-1)
++ {}
++
++ bool QQuickTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e)
++ {
++ #if !QT_CONFIG(shortcut)
++ Q_UNUSED(e);
++ #endif
++
++ Q_Q(QQuickTextControl);
++ if (cursor.isNull())
++ return false;
++
++ const QTextCursor oldSelection = cursor;
++ const int oldCursorPos = cursor.position();
++
++ QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
++ QTextCursor::MoveOperation op = QTextCursor::NoMove;
++
++ if (false) {
++ }
++ #if QT_CONFIG(shortcut)
++ if (e == QKeySequence::MoveToNextChar) {
++ op = QTextCursor::Right;
++ }
++ else if (e == QKeySequence::MoveToPreviousChar) {
++ op = QTextCursor::Left;
++ }
++ else if (e == QKeySequence::SelectNextChar) {
++ op = QTextCursor::Right;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectPreviousChar) {
++ op = QTextCursor::Left;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectNextWord) {
++ op = QTextCursor::WordRight;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectPreviousWord) {
++ op = QTextCursor::WordLeft;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectStartOfLine) {
++ op = QTextCursor::StartOfLine;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectEndOfLine) {
++ op = QTextCursor::EndOfLine;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectStartOfBlock) {
++ op = QTextCursor::StartOfBlock;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectEndOfBlock) {
++ op = QTextCursor::EndOfBlock;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectStartOfDocument) {
++ op = QTextCursor::Start;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectEndOfDocument) {
++ op = QTextCursor::End;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectPreviousLine) {
++ op = QTextCursor::Up;
++ mode = QTextCursor::KeepAnchor;
++ }
++ else if (e == QKeySequence::SelectNextLine) {
++ op = QTextCursor::Down;
++ mode = QTextCursor::KeepAnchor;
++ {
++ QTextBlock block = cursor.block();
++ QTextLine line = currentTextLine(cursor);
++ if (!block.next().isValid()
++ && line.isValid()
++ && line.lineNumber() == block.layout()->lineCount() - 1)
++ op = QTextCursor::End;
++ }
++ }
++ else if (e == QKeySequence::MoveToNextWord) {
++ op = QTextCursor::WordRight;
++ }
++ else if (e == QKeySequence::MoveToPreviousWord) {
++ op = QTextCursor::WordLeft;
++ }
++ else if (e == QKeySequence::MoveToEndOfBlock) {
++ op = QTextCursor::EndOfBlock;
++ }
++ else if (e == QKeySequence::MoveToStartOfBlock) {
++ op = QTextCursor::StartOfBlock;
++ }
++ else if (e == QKeySequence::MoveToNextLine) {
++ op = QTextCursor::Down;
++ }
++ else if (e == QKeySequence::MoveToPreviousLine) {
++ op = QTextCursor::Up;
++ }
++ else if (e == QKeySequence::MoveToStartOfLine) {
++ op = QTextCursor::StartOfLine;
++ }
++ else if (e == QKeySequence::MoveToEndOfLine) {
++ op = QTextCursor::EndOfLine;
++ }
++ else if (e == QKeySequence::MoveToStartOfDocument) {
++ op = QTextCursor::Start;
++ }
++ else if (e == QKeySequence::MoveToEndOfDocument) {
++ op = QTextCursor::End;
++ }
++ #endif // shortcut
++ else {
++ return false;
++ }
++
++ // Except for pageup and pagedown, OS X has very different behavior, we don't do it all, but
++ // here's the breakdown:
++ // Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
++ // Alt (Option), or Meta (Control).
++ // Command/Control + Left/Right -- Move to left or right of the line
++ // + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
++ // Option + Left/Right -- Move one word Left/right.
++ // + Up/Down -- Begin/End of Paragraph.
++ // Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
++
++ bool visualNavigation = cursor.visualNavigation();
++ cursor.setVisualNavigation(true);
++ const bool moved = cursor.movePosition(op, mode);
++ cursor.setVisualNavigation(visualNavigation);
++
++ bool isNavigationEvent
++ = e->key() == Qt::Key_Up
++ || e->key() == Qt::Key_Down
++ || e->key() == Qt::Key_Left
++ || e->key() == Qt::Key_Right;
++
++ if (moved) {
++ if (cursor.position() != oldCursorPos)
++ emit q->cursorPositionChanged();
++ q->updateCursorRectangle(true);
++ } else if (isNavigationEvent && oldSelection.anchor() == cursor.anchor()) {
++ return false;
++ }
++
++ selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor));
++
++ repaintOldAndNewSelection(oldSelection);
++
++ return true;
++ }
++
++ void QQuickTextControlPrivate::updateCurrentCharFormat()
++ {
++ Q_Q(QQuickTextControl);
++
++ QTextCharFormat fmt = cursor.charFormat();
++ if (fmt == lastCharFormat)
++ return;
++ lastCharFormat = fmt;
++
++ emit q->currentCharFormatChanged(fmt);
++ cursorRectangleChanged = true;
++ }
++
++ void QQuickTextControlPrivate::setContent(Qt::TextFormat format, const QString &text)
++ {
++ Q_Q(QQuickTextControl);
++
++ #if QT_CONFIG(im)
++ cancelPreedit();
++ #endif
++
++ // for use when called from setPlainText. we may want to re-use the currently
++ // set char format then.
++ const QTextCharFormat charFormatForInsertion = cursor.charFormat();
++
++ bool previousUndoRedoState = doc->isUndoRedoEnabled();
++ doc->setUndoRedoEnabled(false);
++
++ const int oldCursorPos = cursor.position();
++
++ // avoid multiple textChanged() signals being emitted
++ QObject::disconnect(doc, &QTextDocument::contentsChanged, q, &QQuickTextControl::textChanged);
++
++ if (!text.isEmpty()) {
++ // clear 'our' cursor for insertion to prevent
++ // the emission of the cursorPositionChanged() signal.
++ // instead we emit it only once at the end instead of
++ // at the end of the document after loading and when
++ // positioning the cursor again to the start of the
++ // document.
++ cursor = QTextCursor();
++ if (format == Qt::PlainText) {
++ QTextCursor formatCursor(doc);
++ // put the setPlainText and the setCharFormat into one edit block,
++ // so that the syntax highlight triggers only /once/ for the entire
++ // document, not twice.
++ formatCursor.beginEditBlock();
++ doc->setPlainText(text);
++ doc->setUndoRedoEnabled(false);
++ formatCursor.select(QTextCursor::Document);
++ formatCursor.setCharFormat(charFormatForInsertion);
++ formatCursor.endEditBlock();
++ #if QT_CONFIG(textmarkdownreader)
++ } else if (format == Qt::MarkdownText) {
++ doc->setBaseUrl(doc->baseUrl().adjusted(QUrl::RemoveFilename));
++ doc->setMarkdown(text);
++ #endif
++ } else {
++ #if QT_CONFIG(texthtmlparser)
++ doc->setHtml(text);
++ #else
++ doc->setPlainText(text);
++ #endif
++ doc->setUndoRedoEnabled(false);
++ }
++ cursor = QTextCursor(doc);
++ } else {
++ doc->clear();
++ }
++ cursor.setCharFormat(charFormatForInsertion);
++
++ QObject::connect(doc, &QTextDocument::contentsChanged, q, &QQuickTextControl::textChanged);
++ emit q->textChanged();
++ doc->setUndoRedoEnabled(previousUndoRedoState);
++ _q_updateCurrentCharFormatAndSelection();
++ doc->setModified(false);
++
++ q->updateCursorRectangle(true);
++ if (cursor.position() != oldCursorPos)
++ emit q->cursorPositionChanged();
++ }
++
++ void QQuickTextControlPrivate::setCursorPosition(const QPointF &pos)
++ {
++ Q_Q(QQuickTextControl);
++ const int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
++ if (cursorPos == -1)
++ return;
++ cursor.setPosition(cursorPos);
++ }
++
++ void QQuickTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode)
++ {
++ cursor.setPosition(pos, mode);
++
++ if (mode != QTextCursor::KeepAnchor) {
++ selectedWordOnDoubleClick = QTextCursor();
++ selectedBlockOnTripleClick = QTextCursor();
++ }
++ }
++
++ void QQuickTextControlPrivate::repaintCursor()
++ {
++ Q_Q(QQuickTextControl);
++ emit q->updateCursorRequest();
++ }
++
++ void QQuickTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection)
++ {
++ Q_Q(QQuickTextControl);
++ if (cursor.hasSelection()
++ && oldSelection.hasSelection()
++ && cursor.currentFrame() == oldSelection.currentFrame()
++ && !cursor.hasComplexSelection()
++ && !oldSelection.hasComplexSelection()
++ && cursor.anchor() == oldSelection.anchor()
++ ) {
++ QTextCursor differenceSelection(doc);
++ differenceSelection.setPosition(oldSelection.position());
++ differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor);
++ emit q->updateRequest();
++ } else {
++ if (!oldSelection.hasSelection() && !cursor.hasSelection()) {
++ if (!oldSelection.isNull())
++ emit q->updateCursorRequest();
++ emit q->updateCursorRequest();
++
++ } else {
++ if (!oldSelection.isNull())
++ emit q->updateRequest();
++ emit q->updateRequest();
++ }
++ }
++ }
++
++ void QQuickTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/)
++ {
++ Q_Q(QQuickTextControl);
++ if (forceEmitSelectionChanged) {
++ #if QT_CONFIG(im)
++ if (hasFocus)
++ qGuiApp->inputMethod()->update(Qt::ImCurrentSelection);
++ #endif
++ emit q->selectionChanged();
++ }
++
++ bool current = cursor.hasSelection();
++ int selectionStart = cursor.selectionStart();
++ int selectionEnd = cursor.selectionEnd();
++ if (current == lastSelectionState && (!current || (selectionStart == lastSelectionStart && selectionEnd == lastSelectionEnd)))
++ return;
++
++ if (lastSelectionState != current) {
++ lastSelectionState = current;
++ emit q->copyAvailable(current);
++ }
++
++ lastSelectionStart = selectionStart;
++ lastSelectionEnd = selectionEnd;
++
++ if (!forceEmitSelectionChanged) {
++ #if QT_CONFIG(im)
++ if (hasFocus)
++ qGuiApp->inputMethod()->update(Qt::ImCurrentSelection);
++ #endif
++ emit q->selectionChanged();
++ }
++ q->updateCursorRectangle(true);
++ }
++
++ void QQuickTextControlPrivate::_q_updateCurrentCharFormatAndSelection()
++ {
++ updateCurrentCharFormat();
++ selectionChanged();
++ }
++
++ #if QT_CONFIG(clipboard)
++ void QQuickTextControlPrivate::setClipboardSelection()
++ {
++ QClipboard *clipboard = QGuiApplication::clipboard();
++ if (!cursor.hasSelection() || !clipboard->supportsSelection())
++ return;
++ Q_Q(QQuickTextControl);
++ QMimeData *data = q->createMimeDataFromSelection();
++ clipboard->setMimeData(data, QClipboard::Selection);
++ }
++ #endif
++
++ void QQuickTextControlPrivate::_q_updateCursorPosChanged(const QTextCursor &someCursor)
++ {
++ Q_Q(QQuickTextControl);
++ if (someCursor.isCopyOf(cursor)) {
++ emit q->cursorPositionChanged();
++ q->updateCursorRectangle(true);
++ }
++ }
++
++ void QQuickTextControlPrivate::setBlinkingCursorEnabled(bool enable)
++ {
++ if (cursorBlinkingEnabled == enable)
++ return;
++
++ cursorBlinkingEnabled = enable;
++ updateCursorFlashTime();
++
++ if (enable)
++ connect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextControlPrivate::updateCursorFlashTime);
++ else
++ disconnect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextControlPrivate::updateCursorFlashTime);
++ }
++
++ void QQuickTextControlPrivate::updateCursorFlashTime()
++ {
++ // Note: cursorOn represents the current blinking state controlled by a timer, and
++ // should not be confused with cursorVisible or cursorBlinkingEnabled. However, we
++ // interpretate a cursorFlashTime of 0 to mean "always on, never blink".
++ cursorOn = true;
++ int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
++
++ if (cursorBlinkingEnabled && flashTime >= 2)
++ cursorBlinkTimer.start(flashTime / 2, q_func());
++ else
++ cursorBlinkTimer.stop();
++
++ repaintCursor();
++ }
++
++ void QQuickTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition)
++ {
++ Q_Q(QQuickTextControl);
++
++ // if inside the initial selected word keep that
++ if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart()
++ && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) {
++ q->setTextCursor(selectedWordOnDoubleClick);
++ return;
++ }
++
++ QTextCursor curs = selectedWordOnDoubleClick;
++ curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
++
++ if (!curs.movePosition(QTextCursor::StartOfWord))
++ return;
++ const int wordStartPos = curs.position();
++
++ const int blockPos = curs.block().position();
++ const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft();
++
++ QTextLine line = currentTextLine(curs);
++ if (!line.isValid())
++ return;
++
++ const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
++
++ if (!curs.movePosition(QTextCursor::EndOfWord))
++ return;
++ const int wordEndPos = curs.position();
++
++ const QTextLine otherLine = currentTextLine(curs);
++ if (otherLine.textStart() != line.textStart()
++ || wordEndPos == wordStartPos)
++ return;
++
++ const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
++
++ if (!wordSelectionEnabled && (mouseXPosition < wordStartX || mouseXPosition > wordEndX))
++ return;
++
++ if (suggestedNewPosition < selectedWordOnDoubleClick.position()) {
++ cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
++ setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
++ } else {
++ cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
++ setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
++ }
++
++ if (interactionFlags & Qt::TextSelectableByMouse) {
++ #if QT_CONFIG(clipboard)
++ setClipboardSelection();
++ #endif
++ selectionChanged(true);
++ }
++ }
++
++ void QQuickTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition)
++ {
++ Q_Q(QQuickTextControl);
++
++ // if inside the initial selected line keep that
++ if (suggestedNewPosition >= selectedBlockOnTripleClick.selectionStart()
++ && suggestedNewPosition <= selectedBlockOnTripleClick.selectionEnd()) {
++ q->setTextCursor(selectedBlockOnTripleClick);
++ return;
++ }
++
++ if (suggestedNewPosition < selectedBlockOnTripleClick.position()) {
++ cursor.setPosition(selectedBlockOnTripleClick.selectionEnd());
++ cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
++ cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
++ } else {
++ cursor.setPosition(selectedBlockOnTripleClick.selectionStart());
++ cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
++ cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
++ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
++ }
++
++ if (interactionFlags & Qt::TextSelectableByMouse) {
++ #if QT_CONFIG(clipboard)
++ setClipboardSelection();
++ #endif
++ selectionChanged(true);
++ }
++ }
++
++ void QQuickTextControl::undo()
++ {
++ Q_D(QQuickTextControl);
++ d->repaintSelection();
++ const int oldCursorPos = d->cursor.position();
++ d->doc->undo(&d->cursor);
++ if (d->cursor.position() != oldCursorPos)
++ emit cursorPositionChanged();
++ updateCursorRectangle(true);
++ }
++
++ void QQuickTextControl::redo()
++ {
++ Q_D(QQuickTextControl);
++ d->repaintSelection();
++ const int oldCursorPos = d->cursor.position();
++ d->doc->redo(&d->cursor);
++ if (d->cursor.position() != oldCursorPos)
++ emit cursorPositionChanged();
++ updateCursorRectangle(true);
++ }
++
++ void QQuickTextControl::clear()
++ {
++ Q_D(QQuickTextControl);
++ d->cursor.select(QTextCursor::Document);
++ d->cursor.removeSelectedText();
++ }
++
++ QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
++ : QInputControl(TextEdit, *new QQuickTextControlPrivate, parent)
++ {
++ setDocument(doc);
++ }
++
++ QQuickTextControl::~QQuickTextControl()
++ {
++ }
++
++ QTextDocument *QQuickTextControl::document() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->doc;
++ }
++
++ void QQuickTextControl::setDocument(QTextDocument *doc)
++ {
++ Q_D(QQuickTextControl);
++ if (!doc || d->doc == doc)
++ return;
++
++ if (d->doc) {
++ QAbstractTextDocumentLayout *oldLayout = d->doc->documentLayout();
++ disconnect(oldLayout, nullptr, this, nullptr);
++ disconnect(d->doc, nullptr, this, nullptr);
++ }
++
++ d->doc = doc;
++ d->cursor = QTextCursor(doc);
++ d->lastCharFormat = d->cursor.charFormat();
++
++ QAbstractTextDocumentLayout *layout = doc->documentLayout();
++ connect(layout, &QAbstractTextDocumentLayout::update, this, &QQuickTextControl::updateRequest);
++ connect(layout, &QAbstractTextDocumentLayout::updateBlock, this, &QQuickTextControl::updateRequest);
++ connect(doc, &QTextDocument::contentsChanged, this, [d]() {
++ d->_q_updateCurrentCharFormatAndSelection();
++ });
++ connect(doc, &QTextDocument::contentsChanged, this, &QQuickTextControl::textChanged);
++ connect(doc, &QTextDocument::cursorPositionChanged, this, [d](const QTextCursor &cursor) {
++ d->_q_updateCursorPosChanged(cursor);
++ });
++ connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
++ if (auto *qtdlayout = qobject_cast<QTextDocumentLayout *>(layout))
++ qtdlayout->setCursorWidth(textCursorWidth);
++ }
++
++ void QQuickTextControl::updateCursorRectangle(bool force)
++ {
++ Q_D(QQuickTextControl);
++ const bool update = d->cursorRectangleChanged || force;
++ d->cursorRectangleChanged = false;
++ if (update)
++ emit cursorRectangleChanged();
++ }
++
++ void QQuickTextControl::setTextCursor(const QTextCursor &cursor)
++ {
++ Q_D(QQuickTextControl);
++ #if QT_CONFIG(im)
++ d->commitPreedit();
++ #endif
++ d->cursorIsFocusIndicator = false;
++ const bool posChanged = cursor.position() != d->cursor.position();
++ const QTextCursor oldSelection = d->cursor;
++ d->cursor = cursor;
++ d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable);
++ d->_q_updateCurrentCharFormatAndSelection();
++ updateCursorRectangle(true);
++ d->repaintOldAndNewSelection(oldSelection);
++ if (posChanged)
++ emit cursorPositionChanged();
++ }
++
++ QTextCursor QQuickTextControl::textCursor() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->cursor;
++ }
++
++ #if QT_CONFIG(clipboard)
++
++ void QQuickTextControl::cut()
++ {
++ Q_D(QQuickTextControl);
++ if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection())
++ return;
++ copy();
++ d->cursor.removeSelectedText();
++ }
++
++ void QQuickTextControl::copy()
++ {
++ Q_D(QQuickTextControl);
++ if (!d->cursor.hasSelection())
++ return;
++ QMimeData *data = createMimeDataFromSelection();
++ QGuiApplication::clipboard()->setMimeData(data);
++ }
++
++ void QQuickTextControl::paste(QClipboard::Mode mode)
++ {
++ const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode);
++ if (md)
++ insertFromMimeData(md);
++ }
++ #endif
++
++ void QQuickTextControl::selectAll()
++ {
++ Q_D(QQuickTextControl);
++ const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor());
++ d->cursor.select(QTextCursor::Document);
++ d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor()));
++ d->cursorIsFocusIndicator = false;
++ emit updateRequest();
++ }
++
++ void QQuickTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset)
++ {
++ QTransform t;
++ t.translate(coordinateOffset.x(), coordinateOffset.y());
++ processEvent(e, t);
++ }
++
++ void QQuickTextControl::processEvent(QEvent *e, const QTransform &transform)
++ {
++ Q_D(QQuickTextControl);
++ if (d->interactionFlags == Qt::NoTextInteraction) {
++ e->ignore();
++ return;
++ }
++
++ switch (e->type()) {
++ case QEvent::KeyPress:
++ d->keyPressEvent(static_cast<QKeyEvent *>(e));
++ break;
++ case QEvent::KeyRelease:
++ d->keyReleaseEvent(static_cast<QKeyEvent *>(e));
++ break;
++ case QEvent::MouseButtonPress: {
++ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
++ d->mousePressEvent(ev, transform.map(ev->position()));
++ break; }
++ case QEvent::MouseMove: {
++ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
++ d->mouseMoveEvent(ev, transform.map(ev->position()));
++ break; }
++ case QEvent::MouseButtonRelease: {
++ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
++ d->mouseReleaseEvent(ev, transform.map(ev->position()));
++ break; }
++ case QEvent::MouseButtonDblClick: {
++ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
++ d->mouseDoubleClickEvent(ev, transform.map(ev->position()));
++ break; }
++ case QEvent::HoverEnter:
++ case QEvent::HoverMove:
++ case QEvent::HoverLeave: {
++ QHoverEvent *ev = static_cast<QHoverEvent *>(e);
++ d->hoverEvent(ev, transform.map(ev->position()));
++ break; }
++ #if QT_CONFIG(im)
++ case QEvent::InputMethod:
++ d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
++ break;
++ #endif
++ case QEvent::FocusIn:
++ case QEvent::FocusOut:
++ d->focusEvent(static_cast<QFocusEvent *>(e));
++ break;
++
++ case QEvent::ShortcutOverride:
++ if (d->interactionFlags & Qt::TextEditable) {
++ QKeyEvent *ke = static_cast<QKeyEvent *>(e);
++ ke->setAccepted(isCommonTextEditShortcut(ke));
++ }
++ break;
++ default:
++ break;
++ }
++ }
++
++ bool QQuickTextControl::event(QEvent *e)
++ {
++ return QObject::event(e);
++ }
++
++ bool QQuickTextControl::isBeingEdited()
++ {
++ Q_D(QQuickTextControl);
++ return d->beingEdited;
++ }
++
++ void QQuickTextControl::timerEvent(QTimerEvent *e)
++ {
++ Q_D(QQuickTextControl);
++ if (e->timerId() == d->cursorBlinkTimer.timerId()) {
++ d->cursorOn = !d->cursorOn;
++
++ d->repaintCursor();
++ }
++ }
++
++ void QQuickTextControl::setPlainText(const QString &text)
++ {
++ Q_D(QQuickTextControl);
++ d->setContent(Qt::PlainText, text);
++ }
++
++ void QQuickTextControl::setMarkdownText(const QString &text)
++ {
++ Q_D(QQuickTextControl);
++ d->setContent(Qt::MarkdownText, text);
++ }
++
++ void QQuickTextControl::setHtml(const QString &text)
++ {
++ Q_D(QQuickTextControl);
++ d->setContent(Qt::RichText, text);
++ }
++
++
++ void QQuickTextControlPrivate::keyReleaseEvent(QKeyEvent *e)
++ {
++ e->ignore();
++ }
++
++ void QQuickTextControlPrivate::keyPressEvent(QKeyEvent *e)
++ {
++ Q_Q(QQuickTextControl);
++
++ QScopedValueRollback<bool> rollbackBeingEdited(beingEdited, true);
++ if (e->key() == Qt::Key_Back) {
++ e->ignore();
++ return;
++ }
++
++ #if QT_CONFIG(shortcut)
++ if (e == QKeySequence::SelectAll) {
++ e->accept();
++ q->selectAll();
++ #if QT_CONFIG(clipboard)
++ setClipboardSelection();
++ #endif
++ return;
++ }
++ #if QT_CONFIG(clipboard)
++ else if (e == QKeySequence::Copy) {
++ e->accept();
++ q->copy();
++ return;
++ }
++ #endif
++ #endif // shortcut
++
++ if (interactionFlags & Qt::TextSelectableByKeyboard
++ && cursorMoveKeyEvent(e))
++ goto accept;
++
++ if (!(interactionFlags & Qt::TextEditable)) {
++ e->ignore();
++ return;
++ }
++
++ if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) {
++ QTextBlockFormat fmt;
++ fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
++ cursor.mergeBlockFormat(fmt);
++ goto accept;
++ }
++
++ // schedule a repaint of the region of the cursor, as when we move it we
++ // want to make sure the old cursor disappears (not noticeable when moving
++ // only a few pixels but noticeable when jumping between cells in tables for
++ // example)
++ repaintSelection();
++
++ if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) {
++ QTextBlockFormat blockFmt = cursor.blockFormat();
++ QTextList *list = cursor.currentList();
++ if (list && cursor.atBlockStart() && !cursor.hasSelection()) {
++ list->remove(cursor.block());
++ } else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
++ blockFmt.setIndent(blockFmt.indent() - 1);
++ cursor.setBlockFormat(blockFmt);
++ } else {
++ QTextCursor localCursor = cursor;
++ localCursor.deletePreviousChar();
++ }
++ goto accept;
++ }
++ #if QT_CONFIG(shortcut)
++ else if (e == QKeySequence::InsertParagraphSeparator) {
++ cursor.insertBlock();
++ e->accept();
++ goto accept;
++ } else if (e == QKeySequence::InsertLineSeparator) {
++ cursor.insertText(QString(QChar::LineSeparator));
++ e->accept();
++ goto accept;
++ }
++ #endif
++ if (false) {
++ }
++ #if QT_CONFIG(shortcut)
++ else if (e == QKeySequence::Undo) {
++ q->undo();
++ }
++ else if (e == QKeySequence::Redo) {
++ q->redo();
++ }
++ #if QT_CONFIG(clipboard)
++ else if (e == QKeySequence::Cut) {
++ q->cut();
++ }
++ else if (e == QKeySequence::Paste) {
++ QClipboard::Mode mode = QClipboard::Clipboard;
++ q->paste(mode);
++ }
++ #endif
++ else if (e == QKeySequence::Delete) {
++ QTextCursor localCursor = cursor;
++ localCursor.deleteChar();
++ }
++ else if (e == QKeySequence::DeleteEndOfWord) {
++ if (!cursor.hasSelection())
++ cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
++ cursor.removeSelectedText();
++ }
++ else if (e == QKeySequence::DeleteStartOfWord) {
++ const int oldCursorPos = cursor.position();
++ if (!cursor.hasSelection())
++ cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
++ cursor.removeSelectedText();
++ if (cursor.position() != oldCursorPos)
++ emit q->cursorPositionChanged();
++ }
++ else if (e == QKeySequence::DeleteEndOfLine) {
++ QTextBlock block = cursor.block();
++ if (cursor.position() == block.position() + block.length() - 2)
++ cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
++ else
++ cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
++ cursor.removeSelectedText();
++ }
++ #endif // shortcut
++ else {
++ goto process;
++ }
++ goto accept;
++
++ process:
++ {
++ if (q->isAcceptableInput(e)) {
++ #if QT_CONFIG(im)
++ // QTBUG-90362
++ // Before key press event will be handled, pre-editing part should be finished
++ if (isPreediting())
++ commitPreedit();
++ #endif
++ if (overwriteMode
++ // no need to call deleteChar() if we have a selection, insertText
++ // does it already
++ && !cursor.hasSelection()
++ && !cursor.atBlockEnd()) {
++ cursor.deleteChar();
++ }
++
++ cursor.insertText(e->text());
++ selectionChanged();
++ } else {
++ e->ignore();
++ return;
++ }
++ }
++
++ accept:
++
++ #if QT_CONFIG(clipboard)
++ setClipboardSelection();
++ #endif
++
++ e->accept();
++ cursorOn = true;
++
++ q->updateCursorRectangle(true);
++ updateCurrentCharFormat();
++ }
++
++ QRectF QQuickTextControlPrivate::rectForPosition(int position) const
++ {
++ Q_Q(const QQuickTextControl);
++ const QTextBlock block = doc->findBlock(position);
++ if (!block.isValid())
++ return QRectF();
++ const QTextLayout *layout = block.layout();
++ const QPointF layoutPos = q->blockBoundingRect(block).topLeft();
++ int relativePos = position - block.position();
++ #if QT_CONFIG(im)
++ if (preeditCursor != 0) {
++ int preeditPos = layout->preeditAreaPosition();
++ if (relativePos == preeditPos)
++ relativePos += preeditCursor;
++ else if (relativePos > preeditPos)
++ relativePos += layout->preeditAreaText().size();
++ }
++ #endif
++ QTextLine line = layout->lineForTextPosition(relativePos);
++
++ QRectF r;
++
++ if (line.isValid()) {
++ qreal x = line.cursorToX(relativePos);
++ qreal w = 0;
++ if (overwriteMode) {
++ if (relativePos < line.textLength() - line.textStart())
++ w = line.cursorToX(relativePos + 1) - x;
++ else
++ w = QFontMetrics(block.layout()->font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
++ }
++ r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), textCursorWidth + w, line.height());
++ } else {
++ r = QRectF(layoutPos.x(), layoutPos.y(), textCursorWidth, 10); // #### correct height
++ }
++
++ return r;
++ }
++
++ void QQuickTextControlPrivate::mousePressEvent(QMouseEvent *e, const QPointF &pos)
++ {
++ Q_Q(QQuickTextControl);
++
++ mousePressed = (interactionFlags & Qt::TextSelectableByMouse) && (e->button() & Qt::LeftButton);
++ mousePressPos = pos.toPoint();
++ imSelectionAfterPress = false;
++
++ if (sendMouseEventToInputContext(e, pos))
++ return;
++
++ if (interactionFlags & Qt::LinksAccessibleByMouse) {
++ anchorOnMousePress = q->anchorAt(pos);
++
++ if (cursorIsFocusIndicator) {
++ cursorIsFocusIndicator = false;
++ repaintSelection();
++ cursor.clearSelection();
++ }
++ }
++
++ if (!selectByTouchDrag && !QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(e))
++ return;
++ if (interactionFlags & Qt::TextEditable)
++ blockWithMarkerUnderMousePress = q->blockWithMarkerAt(pos);
++ if (e->button() & Qt::MiddleButton) {
++ return;
++ } else if (!(e->button() & Qt::LeftButton)) {
++ e->ignore();
++ return;
++ } else if (!(interactionFlags & (Qt::TextSelectableByMouse | Qt::TextEditable))) {
++ if (!(interactionFlags & Qt::LinksAccessibleByMouse))
++ e->ignore();
++ return;
++ }
++
++ cursorIsFocusIndicator = false;
++ const QTextCursor oldSelection = cursor;
++ const int oldCursorPos = cursor.position();
++
++ #if QT_CONFIG(im)
++ commitPreedit();
++ #endif
++
++ if ((e->timestamp() < (timestampAtLastDoubleClick + QGuiApplication::styleHints()->mouseDoubleClickInterval()))
++ && ((pos - tripleClickPoint).toPoint().manhattanLength() < QGuiApplication::styleHints()->startDragDistance())) {
++
++ cursor.movePosition(QTextCursor::StartOfBlock);
++ cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
++ cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
++ selectedBlockOnTripleClick = cursor;
++
++ anchorOnMousePress = QString();
++
++ timestampAtLastDoubleClick = 0; // do not enter this condition in case of 4(!) rapid clicks
++ } else {
++ int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
++ if (cursorPos == -1) {
++ e->ignore();
++ return;
++ }
++
++ if (e->modifiers() == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) {
++ if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
++ selectedWordOnDoubleClick = cursor;
++ selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
++ }
++
++ if (selectedBlockOnTripleClick.hasSelection())
++ extendBlockwiseSelection(cursorPos);
++ else if (selectedWordOnDoubleClick.hasSelection())
++ extendWordwiseSelection(cursorPos, pos.x());
++ else if (!wordSelectionEnabled)
++ setCursorPosition(cursorPos, QTextCursor::KeepAnchor);
++ } else {
++ setCursorPosition(cursorPos);
++ }
++ }
++
++ if (cursor.position() != oldCursorPos) {
++ q->updateCursorRectangle(true);
++ emit q->cursorPositionChanged();
++ }
++ if (interactionFlags & Qt::TextEditable)
++ _q_updateCurrentCharFormatAndSelection();
++ else
++ selectionChanged();
++ repaintOldAndNewSelection(oldSelection);
++ hadSelectionOnMousePress = cursor.hasSelection();
++ }
++
++ void QQuickTextControlPrivate::mouseMoveEvent(QMouseEvent *e, const QPointF &mousePos)
++ {
++ if (!selectByTouchDrag && !QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(e))
++ return;
++
++ Q_Q(QQuickTextControl);
++
++ if ((e->buttons() & Qt::LeftButton)) {
++ const bool editable = interactionFlags & Qt::TextEditable;
++
++ if (!(mousePressed
++ || editable
++ || selectedWordOnDoubleClick.hasSelection()
++ || selectedBlockOnTripleClick.hasSelection()))
++ return;
++
++ const QTextCursor oldSelection = cursor;
++ const int oldCursorPos = cursor.position();
++
++ if (!mousePressed)
++ return;
++
++ const qreal mouseX = qreal(mousePos.x());
++
++ int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
++
++ #if QT_CONFIG(im)
++ if (isPreediting()) {
++ // note: oldCursorPos not including preedit
++ int selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
++ if (newCursorPos != selectionStartPos) {
++ commitPreedit();
++ // commit invalidates positions
++ newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
++ selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
++ setCursorPosition(selectionStartPos);
++ }
++ }
++ #endif
++
++ if (newCursorPos == -1)
++ return;
++
++ if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
++ selectedWordOnDoubleClick = cursor;
++ selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
++ }
++
++ if (selectedBlockOnTripleClick.hasSelection())
++ extendBlockwiseSelection(newCursorPos);
++ else if (selectedWordOnDoubleClick.hasSelection())
++ extendWordwiseSelection(newCursorPos, mouseX);
++ #if QT_CONFIG(im)
++ else if (!isPreediting())
++ setCursorPosition(newCursorPos, QTextCursor::KeepAnchor);
++ #endif
++
++ if (interactionFlags & Qt::TextEditable) {
++ if (cursor.position() != oldCursorPos) {
++ emit q->cursorPositionChanged();
++ }
++ _q_updateCurrentCharFormatAndSelection();
++ #if QT_CONFIG(im)
++ if (qGuiApp)
++ qGuiApp->inputMethod()->update(Qt::ImQueryInput);
++ #endif
++ } else if (cursor.position() != oldCursorPos) {
++ emit q->cursorPositionChanged();
++ }
++ selectionChanged(true);
++ repaintOldAndNewSelection(oldSelection);
++ }
++
++ sendMouseEventToInputContext(e, mousePos);
++ }
++
++ void QQuickTextControlPrivate::mouseReleaseEvent(QMouseEvent *e, const QPointF &pos)
++ {
++ Q_Q(QQuickTextControl);
++
++ if (sendMouseEventToInputContext(e, pos))
++ return;
++
++ const QTextCursor oldSelection = cursor;
++ const int oldCursorPos = cursor.position();
++ const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(e);
++
++ if (mousePressed) {
++ mousePressed = false;
++ #if QT_CONFIG(clipboard)
++ setClipboardSelection();
++ selectionChanged(true);
++ } else if (e->button() == Qt::MiddleButton
++ && (interactionFlags & Qt::TextEditable)
++ && QGuiApplication::clipboard()->supportsSelection()) {
++ setCursorPosition(pos);
++ const QMimeData *md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
++ if (md)
++ q->insertFromMimeData(md);
++ #endif
++ }
++ if (!isMouse && !selectByTouchDrag && !imSelectionAfterPress && interactionFlags.testFlag(Qt::TextEditable))
++ setCursorPosition(pos);
++
++ repaintOldAndNewSelection(oldSelection);
++
++ if (cursor.position() != oldCursorPos) {
++ emit q->cursorPositionChanged();
++ q->updateCursorRectangle(true);
++ }
++
++ if ((isMouse || selectByTouchDrag) && interactionFlags.testFlag(Qt::TextEditable) &&
++ (e->button() & Qt::LeftButton) && blockWithMarkerUnderMousePress.isValid()) {
++ QTextBlock block = q->blockWithMarkerAt(pos);
++ if (block == blockWithMarkerUnderMousePress) {
++ auto fmt = block.blockFormat();
++ fmt.setMarker(fmt.marker() == QTextBlockFormat::MarkerType::Unchecked ?
++ QTextBlockFormat::MarkerType::Checked : QTextBlockFormat::MarkerType::Unchecked);
++ cursor.setBlockFormat(fmt);
++ }
++ }
++
++ if (interactionFlags & Qt::LinksAccessibleByMouse) {
++ if (!(e->button() & Qt::LeftButton))
++ return;
++
++ const QString anchor = q->anchorAt(pos);
++
++ if (anchor.isEmpty())
++ return;
++
++ if (!cursor.hasSelection()
++ || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) {
++
++ const int anchorPos = q->hitTest(pos, Qt::ExactHit);
++ if (anchorPos != -1) {
++ cursor.setPosition(anchorPos);
++
++ QString anchor = anchorOnMousePress;
++ anchorOnMousePress = QString();
++ activateLinkUnderCursor(anchor);
++ }
++ }
++ }
++ }
++
++ void QQuickTextControlPrivate::mouseDoubleClickEvent(QMouseEvent *e, const QPointF &pos)
++ {
++ Q_Q(QQuickTextControl);
++
++ if (e->button() == Qt::LeftButton && (interactionFlags & Qt::TextSelectableByMouse)
++ && (selectByTouchDrag || QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(e))) {
++ #if QT_CONFIG(im)
++ commitPreedit();
++ #endif
++
++ const QTextCursor oldSelection = cursor;
++ setCursorPosition(pos);
++ QTextLine line = currentTextLine(cursor);
++ bool doEmit = false;
++ if (line.isValid() && line.textLength()) {
++ cursor.select(QTextCursor::WordUnderCursor);
++ doEmit = true;
++ }
++ repaintOldAndNewSelection(oldSelection);
++
++ cursorIsFocusIndicator = false;
++ selectedWordOnDoubleClick = cursor;
++
++ tripleClickPoint = pos;
++ timestampAtLastDoubleClick = e->timestamp();
++ if (doEmit) {
++ selectionChanged();
++ #if QT_CONFIG(clipboard)
++ setClipboardSelection();
++ #endif
++ emit q->cursorPositionChanged();
++ q->updateCursorRectangle(true);
++ }
++ } else if (!sendMouseEventToInputContext(e, pos)) {
++ e->ignore();
++ }
++ }
++
++ bool QQuickTextControlPrivate::sendMouseEventToInputContext(QMouseEvent *e, const QPointF &pos)
++ {
++ #if QT_CONFIG(im)
++ Q_Q(QQuickTextControl);
++
++ Q_UNUSED(e);
++
++ if (isPreediting()) {
++ QTextLayout *layout = cursor.block().layout();
++ int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position();
++
++ if (cursorPos >= 0 && cursorPos <= layout->preeditAreaText().size()) {
++ if (e->type() == QEvent::MouseButtonRelease) {
++ QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos);
++ }
++
++ return true;
++ }
++ }
++ #else
++ Q_UNUSED(e);
++ Q_UNUSED(pos);
++ #endif
++ return false;
++ }
++
++ #if QT_CONFIG(im)
++ void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
++ {
++ Q_Q(QQuickTextControl);
++ if (cursor.isNull()) {
++ e->ignore();
++ return;
++ }
++ bool textEditable = interactionFlags.testFlag(Qt::TextEditable);
++ bool isGettingInput = !e->commitString().isEmpty()
++ || e->preeditString() != cursor.block().layout()->preeditAreaText()
++ || e->replacementLength() > 0;
++ bool forceSelectionChanged = false;
++ int oldCursorPos = cursor.position();
++
++ // Input method commits (e.g. accented characters such as é, ç, à ) edit the document
++ // without going through keyPressEvent. Mark it edited here too, so that
++ // QQuickTextEdit::q_textChanged emits textEdited() for committed input-method text.
++ QScopedValueRollback<bool> rollbackBeingEdited(beingEdited, isGettingInput && textEditable);
++
++ cursor.beginEditBlock();
++ if (isGettingInput && textEditable) {
++ cursor.removeSelectedText();
++ }
++
++ QTextBlock block;
++
++ // insert commit string
++ if (textEditable && (!e->commitString().isEmpty() || e->replacementLength())) {
++ auto *mimeData = QInputControl::mimeDataForInputEvent(e);
++ if (mimeData && q->canInsertFromMimeData(mimeData)) {
++ q->insertFromMimeData(mimeData);
++ } else {
++ if (e->commitString().endsWith(QChar::LineFeed))
++ block = cursor.block(); // Remember the block where the preedit text is
++ QTextCursor c = cursor;
++ c.setPosition(c.position() + e->replacementStart());
++ c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor);
++ c.insertText(e->commitString());
++ }
++ }
++
++ if (interactionFlags & (Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse)) {
++ for (int i = 0; i < e->attributes().size(); ++i) {
++ const QInputMethodEvent::Attribute &a = e->attributes().at(i);
++ if (a.type == QInputMethodEvent::Selection) {
++ if (mousePressed)
++ imSelectionAfterPress = true;
++ QTextCursor oldCursor = cursor;
++ int blockStart = a.start + cursor.block().position();
++ cursor.setPosition(blockStart, QTextCursor::MoveAnchor);
++ cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor);
++ repaintOldAndNewSelection(oldCursor);
++ forceSelectionChanged = true;
++ }
++ }
++ }
++
++ if (!block.isValid())
++ block = cursor.block();
++
++ const int oldPreeditCursor = preeditCursor;
++ if (textEditable) {
++ QTextLayout *layout = block.layout();
++ if (isGettingInput) {
++ layout->setPreeditArea(cursor.position() - block.position(), e->preeditString());
++ emit q->preeditTextChanged();
++ }
++ QList<QTextLayout::FormatRange> overrides;
++ preeditCursor = e->preeditString().size();
++ hasImState = !e->preeditString().isEmpty();
++ cursorVisible = true;
++ for (int i = 0; i < e->attributes().size(); ++i) {
++ const QInputMethodEvent::Attribute &a = e->attributes().at(i);
++ if (a.type == QInputMethodEvent::Cursor) {
++ hasImState = true;
++ preeditCursor = a.start;
++ cursorVisible = a.length != 0;
++ } else if (a.type == QInputMethodEvent::TextFormat) {
++ hasImState = true;
++ QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
++ if (f.isValid()) {
++ QTextLayout::FormatRange o;
++ o.start = a.start + cursor.position() - block.position();
++ o.length = a.length;
++ o.format = f;
++ overrides.append(o);
++ }
++ }
++ }
++ layout->setFormats(overrides);
++ }
++
++ cursor.endEditBlock();
++
++ QTextCursorPrivate *cursor_d = QTextCursorPrivate::getPrivate(&cursor);
++ if (cursor_d)
++ cursor_d->setX();
++ if (cursor.position() != oldCursorPos)
++ emit q->cursorPositionChanged();
++ q->updateCursorRectangle(oldPreeditCursor != preeditCursor || forceSelectionChanged || isGettingInput);
++ selectionChanged(forceSelectionChanged);
++ }
++
++ QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property) const
++ {
++ return inputMethodQuery(property, QVariant());
++ }
++
++ QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property, const QVariant &argument) const
++ {
++ Q_D(const QQuickTextControl);
++ QTextBlock block = d->cursor.block();
++ switch (property) {
++ case Qt::ImCursorRectangle:
++ return cursorRect();
++ case Qt::ImAnchorRectangle:
++ return anchorRect();
++ case Qt::ImFont:
++ return QVariant(d->cursor.charFormat().font());
++ case Qt::ImCursorPosition: {
++ const QPointF pt = argument.toPointF();
++ if (!pt.isNull())
++ return QVariant(d->doc->documentLayout()->hitTest(pt, Qt::FuzzyHit) - block.position());
++ return QVariant(d->cursor.position() - block.position());
++ }
++ case Qt::ImSurroundingText:
++ return QVariant(block.text());
++ case Qt::ImCurrentSelection: {
++ QMimeData *mimeData = createMimeDataFromSelection();
++ mimeData->deleteLater();
++ return QInputControl::selectionWrapper(mimeData);
++ }
++ case Qt::ImMaximumTextLength:
++ return QVariant(); // No limit.
++ case Qt::ImAnchorPosition:
++ return QVariant(d->cursor.anchor() - block.position());
++ case Qt::ImAbsolutePosition:
++ return QVariant(d->cursor.position());
++ case Qt::ImTextAfterCursor:
++ {
++ int maxLength = argument.isValid() ? argument.toInt() : 1024;
++ QTextCursor tmpCursor = d->cursor;
++ int localPos = d->cursor.position() - block.position();
++ QString result = block.text().mid(localPos);
++ while (result.size() < maxLength) {
++ int currentBlock = tmpCursor.blockNumber();
++ tmpCursor.movePosition(QTextCursor::NextBlock);
++ if (tmpCursor.blockNumber() == currentBlock)
++ break;
++ result += QLatin1Char('\n') + tmpCursor.block().text();
++ }
++ return QVariant(result);
++ }
++ case Qt::ImTextBeforeCursor:
++ {
++ int maxLength = argument.isValid() ? argument.toInt() : 1024;
++ QTextCursor tmpCursor = d->cursor;
++ int localPos = d->cursor.position() - block.position();
++ int numBlocks = 0;
++ int resultLen = localPos;
++ while (resultLen < maxLength) {
++ int currentBlock = tmpCursor.blockNumber();
++ tmpCursor.movePosition(QTextCursor::PreviousBlock);
++ if (tmpCursor.blockNumber() == currentBlock)
++ break;
++ numBlocks++;
++ resultLen += tmpCursor.block().length();
++ }
++ QString result;
++ while (numBlocks) {
++ result += tmpCursor.block().text() + QLatin1Char('\n');
++ tmpCursor.movePosition(QTextCursor::NextBlock);
++ --numBlocks;
++ }
++ result += QStringView{block.text()}.mid(0,localPos);
++ return QVariant(result);
++ }
++ case Qt::ImReadOnly:
++ return QVariant(!d->interactionFlags.testFlag(Qt::TextEditable));
++ default:
++ return QVariant();
++ }
++ }
++ #endif // im
++
++ void QQuickTextControlPrivate::focusEvent(QFocusEvent *e)
++ {
++ Q_Q(QQuickTextControl);
++ emit q->updateRequest();
++ hasFocus = e->gotFocus();
++ if (e->gotFocus()) {
++ setBlinkingCursorEnabled(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
++ } else {
++ setBlinkingCursorEnabled(false);
++
++ if (cursorIsFocusIndicator
++ && e->reason() != Qt::ActiveWindowFocusReason
++ && e->reason() != Qt::PopupFocusReason
++ && cursor.hasSelection()) {
++ cursor.clearSelection();
++ // Use the private overload so lastSelectionStart/End are updated too;
++ // a bare emit would leave that cache stale and suppress a later
++ // programmatic select() to the same range (QTBUG-83224).
++ selectionChanged();
++ }
++ }
++ }
++
++ void QQuickTextControlPrivate::hoverEvent(QHoverEvent *e, const QPointF &pos)
++ {
++ Q_Q(QQuickTextControl);
++
++ QString link;
++ if (e->type() != QEvent::HoverLeave)
++ link = q->anchorAt(pos);
++
++ if (hoveredLink != link) {
++ hoveredLink = link;
++ emit q->linkHovered(link);
++ qCDebug(lcHoverTrace) << q << e->type() << pos << "hoveredLink" << hoveredLink;
++ } else {
++ QTextBlock block = q->blockWithMarkerAt(pos);
++ if (block.isValid() != hoveredMarker)
++ emit q->markerHovered(block.isValid());
++ hoveredMarker = block.isValid();
++ if (hoveredMarker)
++ qCDebug(lcHoverTrace) << q << e->type() << pos << "hovered marker" << int(block.blockFormat().marker()) << block.text();
++ }
++ const QString toolTip = e->type() != QEvent::HoverLeave ? q->charFormatAt(pos).toolTip()
++ : QString();
++ if (toolTip != hoveredToolTip) {
++ hoveredToolTip = toolTip;
++ emit q->hoveredToolTipChanged();
++ qCDebug(lcHoverTrace) << q << e->type() << pos << "hoveredToolTip" << hoveredToolTip;
++ }
++ }
++
++ bool QQuickTextControl::hasImState() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->hasImState;
++ }
++
++ bool QQuickTextControl::overwriteMode() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->overwriteMode;
++ }
++
++ void QQuickTextControl::setOverwriteMode(bool overwrite)
++ {
++ Q_D(QQuickTextControl);
++ if (d->overwriteMode == overwrite)
++ return;
++ d->overwriteMode = overwrite;
++ emit overwriteModeChanged(overwrite);
++ }
++
++ bool QQuickTextControl::cursorVisible() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->cursorVisible;
++ }
++
++ void QQuickTextControl::setCursorVisible(bool visible)
++ {
++ Q_D(QQuickTextControl);
++ d->cursorVisible = visible;
++ d->setBlinkingCursorEnabled(d->cursorVisible
++ && (d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard)));
++ }
++
++ QRectF QQuickTextControl::anchorRect() const
++ {
++ Q_D(const QQuickTextControl);
++ QRectF rect;
++ QTextCursor cursor = d->cursor;
++ if (!cursor.isNull()) {
++ rect = d->rectForPosition(cursor.anchor());
++ }
++ return rect;
++ }
++
++ QRectF QQuickTextControl::cursorRect(const QTextCursor &cursor) const
++ {
++ Q_D(const QQuickTextControl);
++ if (cursor.isNull())
++ return QRectF();
++
++ return d->rectForPosition(cursor.position());
++ }
++
++ QRectF QQuickTextControl::cursorRect() const
++ {
++ Q_D(const QQuickTextControl);
++ return cursorRect(d->cursor);
++ }
++
++ QString QQuickTextControl::hoveredLink() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->hoveredLink;
++ }
++
++ QString QQuickTextControl::hoveredToolTip() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->hoveredToolTip;
++ }
++
++ QString QQuickTextControl::anchorAt(const QPointF &pos) const
++ {
++ Q_D(const QQuickTextControl);
++ return d->doc->documentLayout()->anchorAt(pos);
++ }
++
++ QTextCharFormat QQuickTextControl::charFormatAt(const QPointF &pos) const
++ {
++ Q_D(const QQuickTextControl);
++ const QTextFormat fmt = d->doc->documentLayout()->formatAt(pos);
++ return fmt.toCharFormat();
++ }
++
++ QTextBlock QQuickTextControl::blockWithMarkerAt(const QPointF &pos) const
++ {
++ Q_D(const QQuickTextControl);
++ return d->doc->documentLayout()->blockWithMarkerAt(pos);
++ }
++
++ void QQuickTextControl::setAcceptRichText(bool accept)
++ {
++ Q_D(QQuickTextControl);
++ d->acceptRichText = accept;
++ }
++
++ void QQuickTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
++ {
++ Q_D(QQuickTextControl);
++ const QTextCursor oldSelection = d->cursor;
++ const bool moved = d->cursor.movePosition(op, mode);
++ d->_q_updateCurrentCharFormatAndSelection();
++ updateCursorRectangle(true);
++ d->repaintOldAndNewSelection(oldSelection);
++ if (moved)
++ emit cursorPositionChanged();
++ }
++
++ bool QQuickTextControl::canPaste() const
++ {
++ #if QT_CONFIG(clipboard)
++ Q_D(const QQuickTextControl);
++ if (d->interactionFlags & Qt::TextEditable) {
++ const QMimeData *md = QGuiApplication::clipboard()->mimeData();
++ return md && canInsertFromMimeData(md);
++ }
++ #endif
++ return false;
++ }
++
++ void QQuickTextControl::setCursorIsFocusIndicator(bool b)
++ {
++ Q_D(QQuickTextControl);
++ d->cursorIsFocusIndicator = b;
++ d->repaintCursor();
++ }
++
++ void QQuickTextControl::setWordSelectionEnabled(bool enabled)
++ {
++ Q_D(QQuickTextControl);
++ d->wordSelectionEnabled = enabled;
++ }
++
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ void QQuickTextControl::setTouchDragSelectionEnabled(bool enabled)
++ {
++ Q_D(QQuickTextControl);
++ d->selectByTouchDrag = enabled;
++ }
++ #endif
++
++ QMimeData *QQuickTextControl::createMimeDataFromSelection() const
++ {
++ Q_D(const QQuickTextControl);
++ const QTextDocumentFragment fragment(d->cursor);
++ return new QQuickTextEditMimeData(fragment);
++ }
++
++ bool QQuickTextControl::canInsertFromMimeData(const QMimeData *source) const
++ {
++ Q_D(const QQuickTextControl);
++ if (d->acceptRichText)
++ return source->hasText()
++ || source->hasHtml()
++ || source->hasFormat(QLatin1String("application/x-qrichtext"))
++ || source->hasFormat(QLatin1String("application/x-qt-richtext"));
++ else
++ return source->hasText();
++ }
++
++ void QQuickTextControl::insertFromMimeData(const QMimeData *source)
++ {
++ Q_D(QQuickTextControl);
++ if (!(d->interactionFlags & Qt::TextEditable) || !source)
++ return;
++
++ bool hasData = false;
++ QTextDocumentFragment fragment;
++ #if QT_CONFIG(texthtmlparser)
++ if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) {
++ // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
++ const QString richtext = QLatin1String("<meta name=\"qrichtext\" content=\"1\" />")
++ + QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext")));
++ fragment = QTextDocumentFragment::fromHtml(richtext, d->doc);
++ hasData = true;
++ } else if (source->hasHtml() && d->acceptRichText) {
++ fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc);
++ hasData = true;
++ } else {
++ QString text = source->text();
++ if (!text.isNull()) {
++ fragment = QTextDocumentFragment::fromPlainText(text);
++ hasData = true;
++ }
++ }
++ #else
++ fragment = QTextDocumentFragment::fromPlainText(source->text());
++ #endif // texthtmlparser
++
++ if (hasData)
++ d->cursor.insertFragment(fragment);
++ updateCursorRectangle(true);
++ }
++
++ void QQuickTextControlPrivate::activateLinkUnderCursor(QString href)
++ {
++ QTextCursor oldCursor = cursor;
++
++ if (href.isEmpty()) {
++ QTextCursor tmp = cursor;
++ if (tmp.selectionStart() != tmp.position())
++ tmp.setPosition(tmp.selectionStart());
++ tmp.movePosition(QTextCursor::NextCharacter);
++ href = tmp.charFormat().anchorHref();
++ }
++ if (href.isEmpty())
++ return;
++
++ if (!cursor.hasSelection()) {
++ QTextBlock block = cursor.block();
++ const int cursorPos = cursor.position();
++
++ QTextBlock::Iterator it = block.begin();
++ QTextBlock::Iterator linkFragment;
++
++ for (; !it.atEnd(); ++it) {
++ QTextFragment fragment = it.fragment();
++ const int fragmentPos = fragment.position();
++ if (fragmentPos <= cursorPos &&
++ fragmentPos + fragment.length() > cursorPos) {
++ linkFragment = it;
++ break;
++ }
++ }
++
++ if (!linkFragment.atEnd()) {
++ it = linkFragment;
++ cursor.setPosition(it.fragment().position());
++ if (it != block.begin()) {
++ do {
++ --it;
++ QTextFragment fragment = it.fragment();
++ if (fragment.charFormat().anchorHref() != href)
++ break;
++ cursor.setPosition(fragment.position());
++ } while (it != block.begin());
++ }
++
++ for (it = linkFragment; !it.atEnd(); ++it) {
++ QTextFragment fragment = it.fragment();
++ if (fragment.charFormat().anchorHref() != href)
++ break;
++ cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
++ }
++ }
++ }
++
++ if (hasFocus) {
++ cursorIsFocusIndicator = true;
++ } else {
++ cursorIsFocusIndicator = false;
++ cursor.clearSelection();
++ }
++ repaintOldAndNewSelection(oldCursor);
++
++ emit q_func()->linkActivated(href);
++ }
++
++ #if QT_CONFIG(im)
++ bool QQuickTextControlPrivate::isPreediting() const
++ {
++ QTextLayout *layout = cursor.block().layout();
++ if (layout && !layout->preeditAreaText().isEmpty())
++ return true;
++
++ return false;
++ }
++
++ void QQuickTextControlPrivate::commitPreedit()
++ {
++ Q_Q(QQuickTextControl);
++
++ if (!hasImState)
++ return;
++
++ QGuiApplication::inputMethod()->commit();
++
++ if (!hasImState)
++ return;
++
++ QInputMethodEvent event;
++ QCoreApplication::sendEvent(q->parent(), &event);
++ }
++
++ void QQuickTextControlPrivate::cancelPreedit()
++ {
++ Q_Q(QQuickTextControl);
++
++ if (!hasImState)
++ return;
++
++ QGuiApplication::inputMethod()->reset();
++
++ QInputMethodEvent event;
++ QCoreApplication::sendEvent(q->parent(), &event);
++ }
++ #endif // im
++
++ void QQuickTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
++ {
++ Q_D(QQuickTextControl);
++ if (flags == d->interactionFlags)
++ return;
++ d->interactionFlags = flags;
++
++ if (d->hasFocus)
++ d->setBlinkingCursorEnabled(flags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
++ }
++
++ Qt::TextInteractionFlags QQuickTextControl::textInteractionFlags() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->interactionFlags;
++ }
++
++ QString QQuickTextControl::toPlainText() const
++ {
++ return document()->toPlainText();
++ }
++
++ #if QT_CONFIG(texthtmlparser)
++ QString QQuickTextControl::toHtml() const
++ {
++ return document()->toHtml();
++ }
++ #endif
++
++ #if QT_CONFIG(textmarkdownwriter)
++ QString QQuickTextControl::toMarkdown() const
++ {
++ return document()->toMarkdown();
++ }
++ #endif
++
++ bool QQuickTextControl::cursorOn() const
++ {
++ Q_D(const QQuickTextControl);
++ return d->cursorOn;
++ }
++
++ int QQuickTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
++ {
++ Q_D(const QQuickTextControl);
++ return d->doc->documentLayout()->hitTest(point, accuracy);
++ }
++
++ QRectF QQuickTextControl::blockBoundingRect(const QTextBlock &block) const
++ {
++ Q_D(const QQuickTextControl);
++ return d->doc->documentLayout()->blockBoundingRect(block);
++ }
++
++ QString QQuickTextControl::preeditText() const
++ {
++ #if QT_CONFIG(im)
++ Q_D(const QQuickTextControl);
++ QTextLayout *layout = d->cursor.block().layout();
++ if (!layout)
++ return QString();
++
++ return layout->preeditAreaText();
++ #else
++ return QString();
++ #endif
++ }
++
++
++ QStringList QQuickTextEditMimeData::formats() const
++ {
++ if (!fragment.isEmpty())
++ return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html")
++ #if QT_CONFIG(textodfwriter)
++ << QString::fromLatin1("application/vnd.oasis.opendocument.text")
++ #endif
++ ;
++ else
++ return QMimeData::formats();
++ }
++
++ QVariant QQuickTextEditMimeData::retrieveData(const QString &mimeType, QMetaType type) const
++ {
++ if (!fragment.isEmpty())
++ setup();
++ return QMimeData::retrieveData(mimeType, type);
++ }
++
++ void QQuickTextEditMimeData::setup() const
++ {
++ QQuickTextEditMimeData *that = const_cast<QQuickTextEditMimeData *>(this);
++ #if QT_CONFIG(texthtmlparser)
++ that->setData(QLatin1String("text/html"), fragment.toHtml().toUtf8());
++ #endif
++ #if QT_CONFIG(textodfwriter)
++ {
++ QBuffer buffer;
++ QTextDocumentWriter writer(&buffer, "ODF");
++ writer.write(fragment);
++ buffer.close();
++ that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data());
++ }
++ #endif
++ that->setText(fragment.toPlainText());
++ fragment = QTextDocumentFragment();
++ }
++
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquicktextcontrol_p.cpp"
++
++ #endif // QT_NO_TEXTCONTROL