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:significant reason:default
++
++ #include "qquicktextinput_p.h"
++ #include "qquicktextinput_p_p.h"
++ #include "qquickwindow.h"
++
++ #include <private/qqmlglobal_p.h>
++ #include <private/qv4scopedvalue_p.h>
++
++ #include <QtCore/qcoreapplication.h>
++ #include <QtCore/qmimedata.h>
++ #include <QtQml/qqmlinfo.h>
++ #include <QtGui/qevent.h>
++ #include <QTextBoundaryFinder>
++ #include "qsginternaltextnode_p.h"
++ #include <QtQuick/qsgsimplerectnode.h>
++
++ #include <QtGui/qstylehints.h>
++ #include <QtGui/qinputmethod.h>
++ #include <QtCore/qmath.h>
++
++ #if QT_CONFIG(accessibility)
++ #include "qaccessible.h"
++ #include "qquickaccessibleattached_p.h"
++ #endif
++
++ #include <QtGui/private/qtextengine_p.h>
++ #include <QtGui/private/qinputcontrol_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
++ Q_STATIC_LOGGING_CATEGORY(lcQuickTextInput, "qt.quick.textInput")
++
++ /*!
++ \qmltype TextInput
++ \nativetype QQuickTextInput
++ \inqmlmodule QtQuick
++ \ingroup qtquick-visual
++ \ingroup qtquick-input
++ \inherits Item
++ \brief Displays an editable line of text.
++
++ The TextInput type displays a single line of editable plain text.
++
++ TextInput is used to accept a line of text input. Input constraints
++ can be placed on a TextInput item (for example, through a \l validator or \l inputMask),
++ and setting \l echoMode to an appropriate value enables TextInput to be used for
++ a password input field.
++
++ To react to the user accepting text via the Return or Enter keys, handle
++ the \l accepted() signal. When Return or Enter are pressed and the text
++ input loses focus, \l editingFinished() will be emitted. When the text is
++ edited in any way by the user, \l textEdited() is emitted. These signals
++ \l {Property change signals}{should be preferred} over \c textChanged()
++ in most cases.
++
++ On \macos, the Up/Down key bindings for Home/End are explicitly disabled.
++ If you want such bindings (on any platform), you will need to construct them in QML.
++
++ \sa TextEdit, Text
++ */
++ QQuickTextInput::QQuickTextInput(QQuickItem* parent)
++ : QQuickImplicitSizeItem(*(new QQuickTextInputPrivate), parent)
++ {
++ Q_D(QQuickTextInput);
++ d->init();
++ }
++
++ QQuickTextInput::QQuickTextInput(QQuickTextInputPrivate &dd, QQuickItem *parent)
++ : QQuickImplicitSizeItem(dd, parent)
++ {
++ Q_D(QQuickTextInput);
++ d->init();
++ }
++
++ QQuickTextInput::~QQuickTextInput()
++ {
++ }
++
++ void QQuickTextInput::componentComplete()
++ {
++ Q_D(QQuickTextInput);
++
++ QQuickImplicitSizeItem::componentComplete();
++
++ d->checkIsValid();
++ d->updateLayout();
++ updateCursorRectangle();
++ if (d->cursorComponent && isCursorVisible())
++ QQuickTextUtil::createCursor(d);
++ #if QT_CONFIG(accessibility)
++ if (QAccessible::isActive())
++ d->accessibilityActiveChanged(true);
++ #endif
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::text
++
++ The text in the TextInput.
++
++ Note that some keyboards use a predictive function. In this case,
++ the text being composed by the input method is not part of this property.
++ The part of the text related to the predictions is underlined and stored in
++ the \l preeditText property. To get whole text displayed in the TextInput
++ use \l displayText property.
++
++ \sa clear(), displayText, preeditText, accepted(), editingFinished(),
++ textEdited()
++ */
++ QString QQuickTextInput::text() const
++ {
++ Q_D(const QQuickTextInput);
++
++ QString content = d->m_text;
++ QString res = d->m_maskData ? d->stripString(content) : content;
++ return (res.isNull() ? QString::fromLatin1("") : res);
++ }
++
++ void QQuickTextInput::invalidate()
++ {
++ Q_D(QQuickTextInput);
++ d->updateLayout();
++ invalidateFontCaches();
++ }
++
++ void QQuickTextInput::setText(const QString &s)
++ {
++ Q_D(QQuickTextInput);
++ if (s == text())
++ return;
++
++ #if QT_CONFIG(im)
++ d->cancelPreedit();
++ #endif
++ d->internalSetText(s, -1, false);
++ }
++
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::renderType
++
++ Override the default rendering type for this component.
++
++ Supported render types are:
++
++ \value TextInput.QtRendering Text is rendered using a scalable distance field for each glyph.
++ \value TextInput.NativeRendering Text is rendered using a platform-specific technique.
++ \value TextInput.CurveRendering Text is rendered using a curve rasterizer running directly on
++ the graphics hardware. (Introduced in Qt 6.7.0.)
++
++ Select \c TextInput.NativeRendering if you prefer text to look native on the target platform and do
++ not require advanced features such as transformation of the text. Using such features in
++ combination with the NativeRendering render type will lend poor and sometimes pixelated
++ results.
++
++ Both \c TextInput.QtRendering and \c TextInput.CurveRendering are hardware-accelerated techniques.
++ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
++ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
++ where \c QtRendering does not give good visual results or where reducing graphics memory
++ consumption is a priority.
++
++ The default rendering type is determined by \l QQuickWindow::textRenderType().
++ */
++ QQuickTextInput::RenderType QQuickTextInput::renderType() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->renderType;
++ }
++
++ void QQuickTextInput::setRenderType(QQuickTextInput::RenderType renderType)
++ {
++ Q_D(QQuickTextInput);
++ if (d->renderType == renderType)
++ return;
++
++ d->renderType = renderType;
++ emit renderTypeChanged();
++
++ if (isComponentComplete())
++ d->updateLayout();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::length
++
++ Returns the total number of characters in the TextInput item.
++
++ If the TextInput has an inputMask the length will include mask characters and may differ
++ from the length of the string returned by the \l text property.
++
++ This property can be faster than querying the length the \l text property as it doesn't
++ require any copying or conversion of the TextInput's internal string data.
++ */
++
++ int QQuickTextInput::length() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_text.size();
++ }
++
++ /*!
++ \qmlmethod string QtQuick::TextInput::getText(int start, int end)
++
++ Returns the section of text that is between the \a start and \a end positions.
++
++ If the TextInput has an inputMask the length will include mask characters.
++ */
++
++ QString QQuickTextInput::getText(int start, int end) const
++ {
++ Q_D(const QQuickTextInput);
++
++ if (start > end)
++ qSwap(start, end);
++
++ return d->m_text.mid(start, end - start);
++ }
++
++ QString QQuickTextInputPrivate::realText() const
++ {
++ QString res = m_maskData ? stripString(m_text) : m_text;
++ return (res.isNull() ? QString::fromLatin1("") : res);
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::font.family
++
++ Sets the family name of the font.
++
++ \include qmltypereference.qdoc qml-font-family
++ */
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::font.styleName
++ \since 5.6
++
++ Sets the style name of the font.
++
++ The style name is case insensitive. If set, the font will be matched against style name instead
++ of the font properties \l font.weight, \l font.bold and \l font.italic.
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.bold
++
++ Sets whether the font weight is bold.
++ */
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::font.weight
++
++ \include qmltypereference.qdoc qml-font-weight
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.italic
++
++ Sets whether the font has an italic style.
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.underline
++
++ Sets whether the text is underlined.
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.strikeout
++
++ Sets whether the font has a strikeout style.
++ */
++
++ /*!
++ \qmlproperty real QtQuick::TextInput::font.pointSize
++
++ Sets the font size in points. The point size must be greater than zero.
++ */
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::font.pixelSize
++
++ Sets the font size in pixels.
++
++ Using this function makes the font device dependent.
++ Use \c pointSize to set the size of the font in a device independent manner.
++ */
++
++ /*!
++ \qmlproperty real QtQuick::TextInput::font.letterSpacing
++
++ Sets the letter spacing for the font.
++
++ \include qmltypereference.qdoc qml-font-letter-spacing
++ */
++
++ /*!
++ \qmlproperty real QtQuick::TextInput::font.wordSpacing
++
++ Sets the word spacing for the font.
++
++ \include qmltypereference.qdoc qml-font-word-spacing
++ */
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::font.capitalization
++
++ Sets the capitalization for the text.
++
++ \include qmltypereference.qdoc qml-font-capitalization
++ */
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::font.hintingPreference
++ \since 5.8
++
++ Sets the preferred hinting on the text.
++
++ \include qmltypereference.qdoc qml-font-hinting-preference
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.kerning
++ \since 5.10
++
++ \include qmltypereference.qdoc qml-font-kerning
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.preferShaping
++ \since 5.10
++
++ \include qmltypereference.qdoc qml-font-prefer-shaping
++ */
++
++ /*!
++ \qmlproperty object QtQuick::TextInput::font.variableAxes
++ \since 6.7
++
++ \include qmltypereference.qdoc qml-font-variable-axes
++ */
++
++ /*!
++ \qmlproperty object QtQuick::TextInput::font.features
++ \since 6.6
++
++ \include qmltypereference.qdoc qml-font-features
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.contextFontMerging
++ \since 6.8
++
++ \include qmltypereference.qdoc qml-font-context-font-merging
++ */
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::font.preferTypoLineMetrics
++ \since 6.8
++
++ \include qmltypereference.qdoc qml-font-prefer-typo-line-metrics
++ */
++ QFont QQuickTextInput::font() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->sourceFont;
++ }
++
++ void QQuickTextInput::setFont(const QFont &font)
++ {
++ Q_D(QQuickTextInput);
++ if (d->sourceFont == font)
++ return;
++
++ d->sourceFont = font;
++ QFont oldFont = d->font;
++ d->font = font;
++ if (d->font.pointSizeF() != -1) {
++ // 0.5pt resolution
++ qreal size = qRound(d->font.pointSizeF()*2.0);
++ d->font.setPointSizeF(size/2.0);
++ }
++ if (oldFont != d->font) {
++ d->updateLayout();
++ updateCursorRectangle();
++ #if QT_CONFIG(im)
++ updateInputMethod(Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle);
++ #endif
++ }
++ emit fontChanged(d->sourceFont);
++ }
++
++ /*!
++ \qmlproperty color QtQuick::TextInput::color
++
++ The text color.
++ */
++ QColor QQuickTextInput::color() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->color;
++ }
++
++ void QQuickTextInput::setColor(const QColor &c)
++ {
++ Q_D(QQuickTextInput);
++ if (c != d->color) {
++ d->color = c;
++ d->textLayoutDirty = true;
++ d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
++ polish();
++ update();
++ emit colorChanged();
++ }
++ }
++
++
++ /*!
++ \qmlproperty color QtQuick::TextInput::selectionColor
++
++ The text highlight color, used behind selections.
++ */
++ QColor QQuickTextInput::selectionColor() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->selectionColor;
++ }
++
++ void QQuickTextInput::setSelectionColor(const QColor &color)
++ {
++ Q_D(QQuickTextInput);
++ if (d->selectionColor == color)
++ return;
++
++ d->selectionColor = color;
++ if (d->hasSelectedText()) {
++ d->textLayoutDirty = true;
++ d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
++ polish();
++ update();
++ }
++ emit selectionColorChanged();
++ }
++ /*!
++ \qmlproperty color QtQuick::TextInput::selectedTextColor
++
++ The highlighted text color, used in selections.
++ */
++ QColor QQuickTextInput::selectedTextColor() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->selectedTextColor;
++ }
++
++ void QQuickTextInput::setSelectedTextColor(const QColor &color)
++ {
++ Q_D(QQuickTextInput);
++ if (d->selectedTextColor == color)
++ return;
++
++ d->selectedTextColor = color;
++ if (d->hasSelectedText()) {
++ d->textLayoutDirty = true;
++ d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
++ polish();
++ update();
++ }
++ emit selectedTextColorChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::effectiveHorizontalAlignment
++ \readonly
++
++ When using the attached property LayoutMirroring::enabled to mirror application
++ layouts, the horizontal alignment of text will also be mirrored. However, the property
++ \l horizontalAlignment will remain unchanged. To query the effective horizontal alignment
++ of TextInput, use the read-only property \c effectiveHorizontalAlignment.
++ */
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::horizontalAlignment
++ \qmlproperty enumeration QtQuick::TextInput::verticalAlignment
++
++ Sets the horizontal alignment of the text within the TextInput item's
++ width and height. By default, the text alignment follows the natural alignment
++ of the text, for example text that is read from left to right will be aligned to
++ the left.
++
++ TextInput does not have vertical alignment, as the natural height is
++ exactly the height of the single line of text. If you set the height
++ manually to something larger, TextInput will always be top aligned
++ vertically. You can use anchors to align it however you want within
++ another item.
++
++ The valid values for \c horizontalAlignment are \c TextInput.AlignLeft, \c TextInput.AlignRight and
++ \c TextInput.AlignHCenter.
++
++ Valid values for \c verticalAlignment are \c TextInput.AlignTop (default),
++ \c TextInput.AlignBottom \c TextInput.AlignVCenter.
++
++ When using the attached property LayoutMirroring::enabled to mirror application
++ layouts, the horizontal alignment of text will also be mirrored. However, the property
++ \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
++ of TextInput, use the read-only property \l effectiveHorizontalAlignment.
++ */
++ QQuickTextInput::HAlignment QQuickTextInput::hAlign() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->hAlign;
++ }
++
++ void QQuickTextInput::setHAlign(HAlignment align)
++ {
++ Q_D(QQuickTextInput);
++
++ if (d->setHAlign(align, true) && isComponentComplete()) {
++ d->updateLayout();
++ updateCursorRectangle();
++ }
++ }
++
++ void QQuickTextInput::resetHAlign()
++ {
++ Q_D(QQuickTextInput);
++ d->hAlignImplicit = true;
++ if (d->determineHorizontalAlignment() && isComponentComplete()) {
++ d->updateLayout();
++ updateCursorRectangle();
++ }
++ }
++
++ QQuickTextInput::HAlignment QQuickTextInput::effectiveHAlign() const
++ {
++ Q_D(const QQuickTextInput);
++ QQuickTextInput::HAlignment effectiveAlignment = d->hAlign;
++ if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
++ switch (d->hAlign) {
++ case QQuickTextInput::AlignLeft:
++ effectiveAlignment = QQuickTextInput::AlignRight;
++ break;
++ case QQuickTextInput::AlignRight:
++ effectiveAlignment = QQuickTextInput::AlignLeft;
++ break;
++ default:
++ break;
++ }
++ }
++ return effectiveAlignment;
++ }
++
++ bool QQuickTextInputPrivate::setHAlign(QQuickTextInput::HAlignment align, bool forceAlign)
++ {
++ Q_Q(QQuickTextInput);
++ if (align > QQuickTextInput::AlignHCenter)
++ return false; // justify is not supported
++
++ if (hAlign == align && !forceAlign)
++ return false;
++
++ const bool wasImplicit = hAlignImplicit;
++ const auto oldEffectiveHAlign = q->effectiveHAlign();
++
++ hAlignImplicit = !forceAlign;
++ if (hAlign != align) {
++ hAlign = align;
++ emit q->horizontalAlignmentChanged(align);
++ }
++
++ if (q->effectiveHAlign() != oldEffectiveHAlign) {
++ emit q->effectiveHorizontalAlignmentChanged();
++ return true;
++ }
++
++ if (forceAlign && wasImplicit) {
++ // QTBUG-120052 - when horizontal text alignment is set explicitly,
++ // we need notify any other controls that may depend on it, like QQuickPlaceholderText
++ emit q->effectiveHorizontalAlignmentChanged();
++ }
++ return false;
++ }
++
++ Qt::LayoutDirection QQuickTextInputPrivate::textDirection() const
++ {
++ QString text = m_text;
++ #if QT_CONFIG(im)
++ if (text.isEmpty())
++ text = m_textLayout.preeditAreaText();
++ #endif
++
++ const QChar *character = text.constData();
++ while (!character->isNull()) {
++ switch (character->direction()) {
++ case QChar::DirL:
++ return Qt::LeftToRight;
++ case QChar::DirR:
++ case QChar::DirAL:
++ case QChar::DirAN:
++ return Qt::RightToLeft;
++ default:
++ break;
++ }
++ character++;
++ }
++ return Qt::LayoutDirectionAuto;
++ }
++
++ Qt::LayoutDirection QQuickTextInputPrivate::layoutDirection() const
++ {
++ Qt::LayoutDirection direction = m_layoutDirection;
++ if (direction == Qt::LayoutDirectionAuto) {
++ direction = textDirection();
++ #if QT_CONFIG(im)
++ if (direction == Qt::LayoutDirectionAuto)
++ direction = QGuiApplication::inputMethod()->inputDirection();
++ #endif
++ }
++ return (direction == Qt::LayoutDirectionAuto) ? Qt::LeftToRight : direction;
++ }
++
++ bool QQuickTextInputPrivate::determineHorizontalAlignment()
++ {
++ if (!hAlignImplicit)
++ return false;
++
++ // if no explicit alignment has been set, follow the natural layout direction of the text
++ Qt::LayoutDirection direction = textDirection();
++ #if QT_CONFIG(im)
++ if (direction == Qt::LayoutDirectionAuto)
++ direction = QGuiApplication::inputMethod()->inputDirection();
++ #endif
++
++ const auto implicitHAlign = direction == Qt::RightToLeft ?
++ QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft;
++ return setHAlign(implicitHAlign);
++ }
++
++ QQuickTextInput::VAlignment QQuickTextInput::vAlign() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->vAlign;
++ }
++
++ void QQuickTextInput::setVAlign(QQuickTextInput::VAlignment alignment)
++ {
++ Q_D(QQuickTextInput);
++ if (alignment == d->vAlign)
++ return;
++ d->vAlign = alignment;
++ emit verticalAlignmentChanged(d->vAlign);
++ if (isComponentComplete()) {
++ updateCursorRectangle();
++ d->updateBaselineOffset();
++ }
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::wrapMode
++
++ Set this property to wrap the text to the TextInput item's width.
++ The text will only wrap if an explicit width has been set.
++
++ \value TextInput.NoWrap
++ (default) no wrapping will be performed. If the text contains
++ insufficient newlines, then \l contentWidth will exceed a set width.
++ \value TextInput.WordWrap
++ wrapping is done on word boundaries only. If a word is too long,
++ \l contentWidth will exceed a set width.
++ \value TextInput.WrapAnywhere
++ wrapping is done at any point on a line, even if it occurs in the middle of a word.
++ \value TextInput.Wrap
++ if possible, wrapping occurs at a word boundary; otherwise it will occur
++ at the appropriate point on the line, even in the middle of a word.
++
++ The default is TextInput.NoWrap. If you set a width, consider using TextInput.Wrap.
++ */
++ QQuickTextInput::WrapMode QQuickTextInput::wrapMode() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->wrapMode;
++ }
++
++ void QQuickTextInput::setWrapMode(WrapMode mode)
++ {
++ Q_D(QQuickTextInput);
++ if (mode == d->wrapMode)
++ return;
++ d->wrapMode = mode;
++ d->updateLayout();
++ updateCursorRectangle();
++ emit wrapModeChanged();
++ }
++
++ void QQuickTextInputPrivate::mirrorChange()
++ {
++ Q_Q(QQuickTextInput);
++ if (q->isComponentComplete()) {
++ if (!hAlignImplicit && (hAlign == QQuickTextInput::AlignRight || hAlign == QQuickTextInput::AlignLeft)) {
++ q->updateCursorRectangle();
++ emit q->effectiveHorizontalAlignmentChanged();
++ }
++ }
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::readOnly
++
++ Sets whether user input can modify the contents of the TextInput.
++
++ If readOnly is set to true, then user input will not affect the text
++ property. Any bindings or attempts to set the text property will still
++ work.
++ */
++ bool QQuickTextInput::isReadOnly() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_readOnly;
++ }
++
++ void QQuickTextInput::setReadOnly(bool ro)
++ {
++ Q_D(QQuickTextInput);
++ if (d->m_readOnly == ro)
++ return;
++
++ #if QT_CONFIG(im)
++ setFlag(QQuickItem::ItemAcceptsInputMethod, !ro);
++ #endif
++ d->m_readOnly = ro;
++ d->setCursorPosition(d->end());
++ #if QT_CONFIG(im)
++ updateInputMethod(Qt::ImEnabled);
++ #endif
++ q_canPasteChanged();
++ d->emitUndoRedoChanged();
++ emit readOnlyChanged(ro);
++ if (ro) {
++ setCursorVisible(false);
++ } else if (hasActiveFocus()) {
++ setCursorVisible(true);
++ }
++ update();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::maximumLength
++ The maximum permitted length of the text in the TextInput.
++
++ If the text is too long, it is truncated at the limit.
++
++ By default, this property contains a value of 32767.
++ */
++ int QQuickTextInput::maxLength() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_maxLength;
++ }
++
++ void QQuickTextInput::setMaxLength(int ml)
++ {
++ Q_D(QQuickTextInput);
++ if (d->m_maxLength == ml || d->m_maskData)
++ return;
++
++ d->m_maxLength = ml;
++ d->internalSetText(d->m_text, -1, false);
++
++ emit maximumLengthChanged(ml);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::cursorVisible
++ Set to true when the TextInput shows a cursor.
++
++ This property is set and unset when the TextInput gets active focus, so that other
++ properties can be bound to whether the cursor is currently showing. As it
++ gets set and unset automatically, when you set the value yourself you must
++ keep in mind that your value may be overwritten.
++
++ It can be set directly in script, for example if a KeyProxy might
++ forward keys to it and you desire it to look active when this happens
++ (but without actually giving it active focus).
++
++ It should not be set directly on the item, like in the below QML,
++ as the specified value will be overridden and lost on focus changes.
++
++ \code
++ TextInput {
++ text: "Text"
++ cursorVisible: false
++ }
++ \endcode
++
++ In the above snippet the cursor will still become visible when the
++ TextInput gains active focus.
++ */
++ bool QQuickTextInput::isCursorVisible() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->cursorVisible;
++ }
++
++ void QQuickTextInput::setCursorVisible(bool on)
++ {
++ Q_D(QQuickTextInput);
++ if (d->cursorVisible == on)
++ return;
++ d->cursorVisible = on;
++ if (on && isComponentComplete())
++ QQuickTextUtil::createCursor(d);
++ if (!d->cursorItem)
++ d->updateCursorBlinking();
++ emit cursorVisibleChanged(d->cursorVisible);
++ }
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::cursorPosition
++ The position of the cursor in the TextInput. The cursor is positioned between
++ characters.
++
++ \note The \e characters in this case refer to the string of \l QChar objects,
++ therefore 16-bit Unicode characters, and the position is considered an index
++ into this string. This does not necessarily correspond to individual graphemes
++ in the writing system, as a single grapheme may be represented by multiple
++ Unicode characters, such as in the case of surrogate pairs, linguistic
++ ligatures or diacritics.
++
++ \l displayText is different if echoMode is set to \c {TextInput.Password}:
++ then each passwordCharacter is a "narrow" character (the cursorPosition always
++ moves by 1), even if the text in the TextInput is not.
++ */
++ int QQuickTextInput::cursorPosition() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_cursor;
++ }
++
++ void QQuickTextInput::setCursorPosition(int cp)
++ {
++ Q_D(QQuickTextInput);
++ if (cp < 0 || cp > text().size())
++ return;
++ d->moveCursor(cp);
++ }
++
++ /*!
++ \qmlproperty rectangle QtQuick::TextInput::cursorRectangle
++ \readonly
++
++ The rectangle where the standard text cursor is rendered within the text input. Read only.
++
++ The position and height of a custom cursorDelegate are updated to follow the cursorRectangle
++ automatically when it changes. The width of the delegate is unaffected by changes in the
++ cursor rectangle.
++ */
++
++ QRectF QQuickTextInput::cursorRectangle() const
++ {
++ Q_D(const QQuickTextInput);
++
++ int c = d->m_cursor;
++ #if QT_CONFIG(im)
++ c += d->m_preeditCursor;
++ #endif
++ if (d->m_echoMode == NoEcho)
++ c = 0;
++ QTextLine l = d->m_textLayout.lineForTextPosition(c);
++ if (!l.isValid())
++ return QRectF();
++ qreal x = l.cursorToX(c) - d->hscroll + leftPadding();
++ qreal y = l.y() - d->vscroll + topPadding();
++ qreal w = 1;
++ if (d->overwriteMode) {
++ if (c < text().size())
++ w = l.cursorToX(c + 1) - x;
++ else
++ w = QFontMetrics(font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
++ }
++ return QRectF(x, y, w, l.height());
++ }
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::selectionStart
++
++ The cursor position before the first character in the current selection.
++
++ This property is read-only. To change the selection, use select(start,end),
++ selectAll(), or selectWord().
++
++ \readonly
++ \sa selectionEnd, cursorPosition, selectedText
++ */
++ int QQuickTextInput::selectionStart() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->lastSelectionStart;
++ }
++ /*!
++ \qmlproperty int QtQuick::TextInput::selectionEnd
++
++ The cursor position after the last character in the current selection.
++
++ This property is read-only. To change the selection, use select(start,end),
++ selectAll(), or selectWord().
++
++ \readonly
++ \sa selectionStart, cursorPosition, selectedText
++ */
++ int QQuickTextInput::selectionEnd() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->lastSelectionEnd;
++ }
++ /*!
++ \qmlmethod void QtQuick::TextInput::select(int start, int end)
++
++ Causes the text from \a start to \a end to be selected.
++
++ If either start or end is out of range, the selection is not changed.
++
++ After calling this, selectionStart will become the lesser
++ and selectionEnd will become the greater (regardless of the order passed
++ to this method).
++
++ \sa selectionStart, selectionEnd
++ */
++ void QQuickTextInput::select(int start, int end)
++ {
++ Q_D(QQuickTextInput);
++ if (start < 0 || end < 0 || start > d->m_text.size() || end > d->m_text.size())
++ return;
++ d->setSelection(start, end-start);
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::selectedText
++ \readonly
++
++ This read-only property provides the text currently selected in the
++ text input.
++
++ It is equivalent to the following snippet, but is faster and easier
++ to use.
++
++ \qml
++ myTextInput.text.toString().substring(myTextInput.selectionStart,
++ myTextInput.selectionEnd);
++ \endqml
++ */
++ QString QQuickTextInput::selectedText() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->selectedText();
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::activeFocusOnPress
++
++ Whether the TextInput should gain active focus on a mouse press. By default this is
++ set to true.
++ */
++ bool QQuickTextInput::focusOnPress() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->focusOnPress;
++ }
++
++ void QQuickTextInput::setFocusOnPress(bool b)
++ {
++ Q_D(QQuickTextInput);
++ if (d->focusOnPress == b)
++ return;
++
++ d->focusOnPress = b;
++
++ emit activeFocusOnPressChanged(d->focusOnPress);
++ }
++ /*!
++ \qmlproperty bool QtQuick::TextInput::autoScroll
++
++ Whether the TextInput should scroll when the text is longer than the width. By default this is
++ set to true.
++
++ \sa ensureVisible()
++ */
++ bool QQuickTextInput::autoScroll() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->autoScroll;
++ }
++
++ void QQuickTextInput::setAutoScroll(bool b)
++ {
++ Q_D(QQuickTextInput);
++ if (d->autoScroll == b)
++ return;
++
++ d->autoScroll = b;
++ //We need to repaint so that the scrolling is taking into account.
++ updateCursorRectangle();
++ emit autoScrollChanged(d->autoScroll);
++ }
++
++ #if QT_CONFIG(validator)
++ /*!
++ \qmlproperty Validator QtQuick::TextInput::validator
++
++ Allows you to set a validator on the TextInput. When a validator is set
++ the TextInput will only accept input which leaves the text property in
++ an acceptable or intermediate state. The accepted signal will only be sent
++ if the text is in an acceptable state when enter is pressed.
++
++ Currently supported validators are IntValidator, DoubleValidator
++ and RegularExpressionValidator. An example of using validators is shown
++ below, which allows input of integers between 11 and 31 into the
++ text input:
++
++ \code
++ import QtQuick 2.0
++ TextInput{
++ validator: IntValidator{bottom: 11; top: 31;}
++ focus: true
++ }
++ \endcode
++
++ \sa acceptableInput, inputMask
++ */
++
++ QValidator* QQuickTextInput::validator() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_validator;
++ }
++
++ void QQuickTextInput::setValidator(QValidator* v)
++ {
++ Q_D(QQuickTextInput);
++ if (d->m_validator == v)
++ return;
++
++ if (d->m_validator) {
++ qmlobject_disconnect(
++ d->m_validator, QValidator, SIGNAL(changed()),
++ this, QQuickTextInput, SLOT(q_validatorChanged()));
++ }
++
++ d->m_validator = v;
++
++ if (d->m_validator) {
++ qmlobject_connect(
++ d->m_validator, QValidator, SIGNAL(changed()),
++ this, QQuickTextInput, SLOT(q_validatorChanged()));
++ }
++
++ if (isComponentComplete())
++ d->checkIsValid();
++
++ emit validatorChanged();
++ }
++
++ void QQuickTextInput::q_validatorChanged()
++ {
++ Q_D(QQuickTextInput);
++ d->checkIsValid();
++ }
++ #endif // validator
++
++ QRectF QQuickTextInputPrivate::anchorRectangle() const
++ {
++ Q_Q(const QQuickTextInput);
++ QRectF rect;
++ int a;
++ // Unfortunately we cannot use selectionStart() and selectionEnd()
++ // since they always assume that the selectionStart is logically before selectionEnd.
++ // To rely on that would cause havoc if the user was interactively moving the end selection
++ // handle to become before the start selection
++ if (m_selstart == m_selend)
++ // This is to handle the case when there is "no selection" while moving the handle onto the
++ // same position as the other handle (in which case it would hide the selection handles)
++ a = m_cursor;
++ else
++ a = m_selstart == m_cursor ? m_selend : m_selstart;
++ if (a >= 0) {
++ #if QT_CONFIG(im)
++ a += m_preeditCursor;
++ #endif
++ if (m_echoMode == QQuickTextInput::NoEcho)
++ a = 0;
++ QTextLine l = m_textLayout.lineForTextPosition(a);
++ if (l.isValid()) {
++ qreal x = l.cursorToX(a) - hscroll + q->leftPadding();
++ qreal y = l.y() - vscroll + q->topPadding();
++ rect.setRect(x, y, 1, l.height());
++ }
++ }
++ return rect;
++ }
++
++ void QQuickTextInputPrivate::checkIsValid()
++ {
++ Q_Q(QQuickTextInput);
++
++ ValidatorState state = hasAcceptableInput(m_text);
++ if (!m_maskData)
++ m_validInput = state != InvalidInput;
++ if (state != AcceptableInput) {
++ if (m_acceptableInput) {
++ m_acceptableInput = false;
++ emit q->acceptableInputChanged();
++ }
++ } else if (!m_acceptableInput) {
++ m_acceptableInput = true;
++ emit q->acceptableInputChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::inputMask
++
++ Allows you to set an input mask on the TextInput, restricting the allowable
++ text inputs. See QLineEdit::inputMask for further details, as the exact
++ same mask strings are used by TextInput.
++
++ \sa acceptableInput, validator
++ */
++ QString QQuickTextInput::inputMask() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->inputMask();
++ }
++
++ void QQuickTextInput::setInputMask(const QString &im)
++ {
++ Q_D(QQuickTextInput);
++ QString canonicalInputMask = im;
++ if (im.lastIndexOf(QLatin1Char(';')) == -1)
++ canonicalInputMask.append(QLatin1String("; "));
++ if (d->inputMask() == canonicalInputMask)
++ return;
++
++ d->setInputMask(im);
++ emit inputMaskChanged(d->inputMask());
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::acceptableInput
++ \readonly
++
++ This property is always true unless a validator or input mask has been set.
++ If a validator or input mask has been set, this property will only be true
++ if the current text is acceptable to the validator or input mask as a final
++ string (not as an intermediate string).
++ */
++ bool QQuickTextInput::hasAcceptableInput() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_acceptableInput;
++ }
++
++ /*!
++ \qmlsignal QtQuick::TextInput::accepted()
++
++ This signal is emitted when the Return or Enter key is pressed.
++ Note that if there is a \l validator or \l inputMask set on the text
++ input, the signal will only be emitted if the input is in an acceptable
++ state.
++
++ \sa editingFinished(), textEdited()
++ */
++
++ /*!
++ \qmlsignal QtQuick::TextInput::editingFinished()
++ \since 5.2
++
++ This signal is emitted when the Return or Enter key is pressed or
++ the text input loses focus. Note that if there is a validator or
++ inputMask set on the text input and enter/return is pressed, this
++ signal will only be emitted if the input follows
++ the inputMask and the validator returns an acceptable state.
++
++ \sa accepted(), textEdited()
++ */
++
++ /*!
++ \qmlsignal QtQuick::TextInput::textEdited()
++ \since 5.9
++
++ This signal is emitted whenever the text is edited. Unlike \c textChanged(),
++ this signal is not emitted when the text is changed programmatically, for example,
++ by changing the value of the \c text property or by calling \c clear().
++
++ \sa accepted(), editingFinished()
++ */
++
++ #if QT_CONFIG(im)
++ Qt::InputMethodHints QQuickTextInputPrivate::effectiveInputMethodHints() const
++ {
++ Qt::InputMethodHints hints = inputMethodHints;
++ if (m_echoMode == QQuickTextInput::NoEcho)
++ hints |= Qt::ImhHiddenText;
++ else if (m_echoMode == QQuickTextInput::Password || m_echoMode == QQuickTextInput::PasswordEchoOnEdit)
++ hints &= ~Qt::ImhHiddenText;
++ if (m_echoMode != QQuickTextInput::Normal)
++ hints |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
++ return hints;
++ }
++ #endif
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::echoMode
++
++ Specifies how the text should be displayed in the TextInput.
++
++ \value TextInput.Normal Displays the text as it is. (Default)
++ \value TextInput.Password Displays the \l passwordCharacter instead of the actual characters.
++ While editing, newly entered characters are displayed in clear text
++ for a short period specified by the \l passwordMaskDelay property.
++ \value TextInput.NoEcho Displays nothing.
++ \value TextInput.PasswordEchoOnEdit Content is masked as with \c TextInput.Password. During
++ editing, newly entered characters are displayed in clear text as
++ long as the TextInput has active focus.
++ */
++ QQuickTextInput::EchoMode QQuickTextInput::echoMode() const
++ {
++ Q_D(const QQuickTextInput);
++ return QQuickTextInput::EchoMode(d->m_echoMode);
++ }
++
++ void QQuickTextInput::setEchoMode(QQuickTextInput::EchoMode echo)
++ {
++ Q_D(QQuickTextInput);
++ if (echoMode() == echo)
++ return;
++ d->cancelPasswordEchoTimer();
++ d->m_echoMode = echo;
++ d->m_passwordEchoEditing = false;
++ #if QT_CONFIG(im)
++ updateInputMethod(Qt::ImHints);
++ #endif
++ d->updateDisplayText();
++ updateCursorRectangle();
++
++ // If this control is used for password input, we want to minimize
++ // the possibility of string reallocation not to leak (parts of)
++ // the password.
++ if (d->m_echoMode != QQuickTextInput::Normal)
++ d->m_text.reserve(30);
++
++ emit echoModeChanged(echoMode());
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::inputMethodHints
++
++ Provides hints to the input method about the expected content of the text input and how it
++ should operate.
++
++ The value is a bit-wise combination of flags, or Qt.ImhNone if no hints are set.
++
++ Flags that alter behaviour are:
++
++ \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords.
++
++ Note that Android IMEs deliberately only
++ allow Latin/ASCII character input for password fields.
++ \value Qt.ImhSensitiveData Typed text should not be stored by the active input method
++ in any persistent storage like predictive user dictionary.
++
++ Note that the Qt Framework treats this as a visible password
++ and in this case Android IMEs deliberately only
++ allow Latin/ASCII character input.
++ \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to
++ upper case when a sentence ends.
++ \value Qt.ImhPreferNumbers Numbers are preferred (but not required).
++ \value Qt.ImhPreferUppercase Upper case letters are preferred (but not required).
++ \value Qt.ImhPreferLowercase Lower case letters are preferred (but not required).
++ \value Qt.ImhNoPredictiveText Do not use predictive text (i.e. dictionary lookup) while typing.
++ \value Qt.ImhDate The text editor functions as a date field.
++ \value Qt.ImhTime The text editor functions as a time field.
++
++ Flags that restrict input (exclusive flags) are:
++
++ \value Qt.ImhDigitsOnly Only digits are allowed.
++ \value Qt.ImhFormattedNumbersOnly Only number input is allowed. This includes decimal point and minus sign.
++ \value Qt.ImhUppercaseOnly Only upper case letter input is allowed.
++ \value Qt.ImhLowercaseOnly Only lower case letter input is allowed.
++ \value Qt.ImhDialableCharactersOnly Only characters suitable for phone dialing are allowed.
++ \value Qt.ImhEmailCharactersOnly Only characters suitable for email addresses are allowed.
++ \value Qt.ImhUrlCharactersOnly Only characters suitable for URLs are allowed.
++
++ Masks:
++
++ \value Qt.ImhExclusiveInputMask This mask yields nonzero if any of the exclusive flags are used.
++ */
++
++ Qt::InputMethodHints QQuickTextInput::inputMethodHints() const
++ {
++ #if !QT_CONFIG(im)
++ return Qt::ImhNone;
++ #else
++ Q_D(const QQuickTextInput);
++ return d->inputMethodHints;
++ #endif // im
++ }
++
++ void QQuickTextInput::setInputMethodHints(Qt::InputMethodHints hints)
++ {
++ #if !QT_CONFIG(im)
++ Q_UNUSED(hints);
++ #else
++ Q_D(QQuickTextInput);
++
++ if (hints == d->inputMethodHints)
++ return;
++
++ d->inputMethodHints = hints;
++ updateInputMethod(Qt::ImHints);
++ emit inputMethodHintsChanged();
++ #endif // im
++ }
++
++ /*!
++ \qmlproperty Component QtQuick::TextInput::cursorDelegate
++ The delegate for the cursor in the TextInput.
++
++ If you set a cursorDelegate for a TextInput, this delegate will be used for
++ drawing the cursor instead of the standard cursor. An instance of the
++ delegate will be created and managed by the TextInput when a cursor is
++ needed, and the x property of the delegate instance will be set so as
++ to be one pixel before the top left of the current character.
++
++ Note that the root item of the delegate component must be a QQuickItem or
++ QQuickItem derived item.
++ */
++ QQmlComponent* QQuickTextInput::cursorDelegate() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->cursorComponent;
++ }
++
++ void QQuickTextInput::setCursorDelegate(QQmlComponent* c)
++ {
++ Q_D(QQuickTextInput);
++ QQuickTextUtil::setCursorDelegate(d, c);
++ }
++
++ void QQuickTextInput::createCursor()
++ {
++ Q_D(QQuickTextInput);
++ d->cursorPending = true;
++ QQuickTextUtil::createCursor(d);
++ }
++
++ /*!
++ \qmlmethod rect QtQuick::TextInput::positionToRectangle(int pos)
++
++ This function takes a character position \a pos and returns the rectangle
++ that the cursor would occupy, if it was placed at that character position.
++
++ This is similar to setting the cursorPosition, and then querying the cursor
++ rectangle, but the cursorPosition is not changed.
++ */
++ QRectF QQuickTextInput::positionToRectangle(int pos) const
++ {
++ Q_D(const QQuickTextInput);
++ if (d->m_echoMode == NoEcho)
++ pos = 0;
++ #if QT_CONFIG(im)
++ else if (pos > d->m_cursor)
++ pos += d->preeditAreaText().size();
++ #endif
++ QTextLine l = d->m_textLayout.lineForTextPosition(pos);
++ if (!l.isValid())
++ return QRectF();
++ qreal x = l.cursorToX(pos) - d->hscroll;
++ qreal y = l.y() - d->vscroll;
++ qreal w = 1;
++ if (d->overwriteMode) {
++ if (pos < text().size())
++ w = l.cursorToX(pos + 1) - x;
++ else
++ w = QFontMetrics(font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
++ }
++ return QRectF(x, y, w, l.height());
++ }
++
++ /*!
++ \qmlmethod int QtQuick::TextInput::positionAt(real x, real y, CursorPosition position)
++
++ This function returns the character position at
++ \a x and \a y pixels from the top left of the textInput. Position 0 is before the
++ first character, position 1 is after the first character but before the second,
++ and so on until position text.length, which is after all characters.
++
++ This means that for all x values before the first character this function returns 0,
++ and for all x values after the last character this function returns text.length. If
++ the y value is above the text the position will be that of the nearest character on
++ the first line and if it is below the text the position of the nearest character
++ on the last line will be returned.
++
++ The cursor \a position parameter specifies how the cursor position should be resolved:
++
++ \value TextInput.CursorBetweenCharacters
++ Returns the position between characters that is nearest x.
++ This is the default value.
++ \value TextInput.CursorOnCharacter
++ Returns the position before the character that is nearest x.
++ */
++
++ int QQuickTextInput::positionAt(qreal x, qreal y, QQuickTextInput::CursorPosition positionQuick) const
++ {
++ Q_D(const QQuickTextInput);
++
++ QTextLine::CursorPosition position = QTextLine::CursorPosition(positionQuick);
++
++ int pos = d->positionAt(x, y, position);
++ const int cursor = d->m_cursor;
++ if (pos > cursor) {
++ #if QT_CONFIG(im)
++ const int preeditLength = d->preeditAreaText().size();
++ pos = pos > cursor + preeditLength
++ ? pos - preeditLength
++ : cursor;
++ #else
++ pos = cursor;
++ #endif
++ }
++ return pos;
++ }
++
++ int QQuickTextInputPrivate::positionAt(qreal x, qreal y, QTextLine::CursorPosition position) const
++ {
++ Q_Q(const QQuickTextInput);
++ x += hscroll - q->leftPadding();
++ y += vscroll - q->topPadding();
++ QTextLine line = m_textLayout.lineAt(0);
++ for (int i = 1; i < m_textLayout.lineCount(); ++i) {
++ QTextLine nextLine = m_textLayout.lineAt(i);
++
++ if (y < (line.rect().bottom() + nextLine.y()) / 2)
++ break;
++ line = nextLine;
++ }
++ return line.isValid() ? line.xToCursor(x, position) : 0;
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::overwriteMode
++ \since 5.8
++
++ Whether text entered by the user will overwrite existing text.
++
++ As with many text editors, the text editor widget can be configured
++ to insert or overwrite existing text with new text entered by the user.
++
++ If this property is \c true, existing text is overwritten, character-for-character
++ by new text; otherwise, text is inserted at the cursor position, displacing
++ existing text.
++
++ By default, this property is \c false (new text does not overwrite existing text).
++ */
++ bool QQuickTextInput::overwriteMode() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->overwriteMode;
++ }
++
++ void QQuickTextInput::setOverwriteMode(bool overwrite)
++ {
++ Q_D(QQuickTextInput);
++ if (d->overwriteMode == overwrite)
++ return;
++ d->overwriteMode = overwrite;
++ emit overwriteModeChanged(overwrite);
++ }
++
++ void QQuickTextInput::keyPressEvent(QKeyEvent* ev)
++ {
++ Q_D(QQuickTextInput);
++ // Don't allow MacOSX up/down support, and we don't allow a completer.
++ bool ignore = (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) && ev->modifiers() == Qt::NoModifier;
++ if (!ignore && (d->lastSelectionStart == d->lastSelectionEnd) && (ev->key() == Qt::Key_Right || ev->key() == Qt::Key_Left)) {
++ // Ignore when moving off the end unless there is a selection,
++ // because then moving will do something (deselect).
++ int cursorPosition = d->m_cursor;
++ if (cursorPosition == 0)
++ ignore = ev->key() == (d->layoutDirection() == Qt::LeftToRight ? Qt::Key_Left : Qt::Key_Right);
++ if (!ignore && cursorPosition == d->m_text.size())
++ ignore = ev->key() == (d->layoutDirection() == Qt::LeftToRight ? Qt::Key_Right : Qt::Key_Left);
++ }
++ if (ignore) {
++ ev->ignore();
++ } else {
++ d->processKeyEvent(ev);
++ }
++ if (!ev->isAccepted())
++ QQuickImplicitSizeItem::keyPressEvent(ev);
++ }
++
++ #if QT_CONFIG(im)
++ void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev)
++ {
++ Q_D(QQuickTextInput);
++ const bool wasComposing = d->hasImState;
++ d->processInputMethodEvent(ev);
++ if (!ev->isAccepted())
++ QQuickImplicitSizeItem::inputMethodEvent(ev);
++
++ if (wasComposing != d->hasImState)
++ emit inputMethodComposingChanged();
++ }
++ #endif
++
++ void QQuickTextInput::mouseDoubleClickEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickTextInput);
++
++ if (d->selectByMouse && event->button() == Qt::LeftButton &&
++ QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event)) {
++ #if QT_CONFIG(im)
++ d->commitPreedit();
++ #endif
++ int cursor = d->positionAt(event->position());
++ d->selectWordAtPos(cursor);
++ event->setAccepted(true);
++ if (!d->hasPendingTripleClick()) {
++ d->tripleClickStartPoint = event->position();
++ d->tripleClickTimer.start();
++ }
++ } else {
++ if (d->sendMouseEventToInputContext(event))
++ return;
++ QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
++ }
++ }
++
++ void QQuickTextInput::mousePressEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickTextInput);
++
++ d->pressPos = event->position();
++
++ if (d->sendMouseEventToInputContext(event))
++ return;
++
++ d->hadSelectionOnMousePress = d->hasSelectedText();
++
++ const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event);
++ if (d->selectByMouse &&
++ (isMouse
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ || d->selectByTouchDrag
++ #endif
++ )) {
++ setKeepMouseGrab(false);
++ d->selectPressed = true;
++ QPointF distanceVector = d->pressPos - d->tripleClickStartPoint;
++ if (d->hasPendingTripleClick()
++ && distanceVector.manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) {
++ event->setAccepted(true);
++ selectAll();
++ return;
++ }
++ }
++
++ if (isMouse
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ || d->selectByTouchDrag
++ #endif
++ ) {
++ bool mark = (event->modifiers() & Qt::ShiftModifier) && d->selectByMouse;
++ int cursor = d->positionAt(event->position());
++ d->moveCursor(cursor, mark);
++ }
++
++ if (d->focusOnPress && !qGuiApp->styleHints()->setFocusOnTouchRelease())
++ ensureActiveFocus(Qt::MouseFocusReason);
++
++ event->setAccepted(true);
++ }
++
++ void QQuickTextInput::mouseMoveEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickTextInput);
++ if (!QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event)
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ && ! d->selectByTouchDrag
++ #endif
++ )
++ return;
++
++ if (d->selectPressed) {
++ if (qAbs(int(event->position().x() - d->pressPos.x())) > QGuiApplication::styleHints()->startDragDistance())
++ setKeepMouseGrab(true);
++
++ #if QT_CONFIG(im)
++ if (d->composeMode()) {
++ // start selection
++ int startPos = d->positionAt(d->pressPos);
++ int currentPos = d->positionAt(event->position());
++ if (startPos != currentPos)
++ d->setSelection(startPos, currentPos - startPos);
++ } else
++ #endif
++ {
++ moveCursorSelection(d->positionAt(event->position()), d->mouseSelectionMode);
++ }
++ event->setAccepted(true);
++ } else {
++ QQuickImplicitSizeItem::mouseMoveEvent(event);
++ }
++ }
++
++ void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickTextInput);
++ if (d->sendMouseEventToInputContext(event))
++ return;
++ if (d->selectPressed) {
++ d->selectPressed = false;
++ setKeepMouseGrab(false);
++ }
++ const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event)
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ || d->selectByTouchDrag
++ #endif
++ ;
++
++ #if QT_CONFIG(clipboard)
++ if (isMouse && QGuiApplication::clipboard()->supportsSelection()) {
++ if (event->button() == Qt::LeftButton) {
++ d->copy(QClipboard::Selection);
++ } else if (!d->m_readOnly && event->button() == Qt::MiddleButton) {
++ d->deselect();
++ d->insert(QGuiApplication::clipboard()->text(QClipboard::Selection));
++ }
++ }
++ #endif
++
++ // On a touchscreen or with a stylus, set cursor position and focus on release, not on press;
++ // if Flickable steals the grab in the meantime, the cursor won't move.
++ // Check d->hasSelectedText() to keep touch-and-hold word selection working.
++ // But if text was selected already on press, deselect it on release.
++ if (!isMouse && (!d->hasSelectedText() || d->hadSelectionOnMousePress))
++ d->moveCursor(d->positionAt(event->position()), false);
++ // On Android, after doing a long-press to start selection, we see a release event,
++ // even though there was no press event. So reset hadSelectionOnMousePress to avoid
++ // it getting stuck in true state.
++ d->hadSelectionOnMousePress = false;
++
++ if (d->focusOnPress && qGuiApp->styleHints()->setFocusOnTouchRelease())
++ ensureActiveFocus(Qt::MouseFocusReason);
++
++ if (!event->isAccepted())
++ QQuickImplicitSizeItem::mouseReleaseEvent(event);
++ }
++
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ bool QQuickTextInputPrivate::handleContextMenuEvent(QContextMenuEvent *event)
++ #else
++ bool QQuickTextInput::contextMenuEvent(QContextMenuEvent *event)
++ #endif
++ {
++ Q_Q(QQuickTextInput);
++ QContextMenuEvent mapped(event->reason(),
++ q->mapToScene(q->cursorRectangle().center()).toPoint(), event->globalPos(),
++ event->modifiers());
++ const bool eventProcessed = QQuickItemPrivate::handleContextMenuEvent(&mapped);
++ event->setAccepted(mapped.isAccepted());
++ return eventProcessed;
++ }
++
++ bool QQuickTextInputPrivate::sendMouseEventToInputContext(QMouseEvent *event)
++ {
++ #if QT_CONFIG(im)
++ if (composeMode()) {
++ int tmp_cursor = positionAt(event->position());
++ int mousePos = tmp_cursor - m_cursor;
++ if (mousePos >= 0 && mousePos <= m_textLayout.preeditAreaText().size()) {
++ if (event->type() == QEvent::MouseButtonRelease) {
++ QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, mousePos);
++ }
++ return true;
++ }
++ }
++ #else
++ Q_UNUSED(event);
++ #endif
++
++ return false;
++ }
++
++ void QQuickTextInput::mouseUngrabEvent()
++ {
++ Q_D(QQuickTextInput);
++ d->selectPressed = false;
++ setKeepMouseGrab(false);
++ }
++
++ bool QQuickTextInput::event(QEvent* ev)
++ {
++ #if QT_CONFIG(shortcut)
++ Q_D(QQuickTextInput);
++ if (ev->type() == QEvent::ShortcutOverride) {
++ if (d->m_readOnly) {
++ ev->ignore();
++ return false;
++ }
++ QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
++ if (ke == QKeySequence::Copy
++ || ke == QKeySequence::Paste
++ || ke == QKeySequence::Cut
++ || ke == QKeySequence::Redo
++ || ke == QKeySequence::Undo
++ || ke == QKeySequence::MoveToNextWord
++ || ke == QKeySequence::MoveToPreviousWord
++ || ke == QKeySequence::MoveToStartOfDocument
++ || ke == QKeySequence::MoveToEndOfDocument
++ || ke == QKeySequence::SelectNextWord
++ || ke == QKeySequence::SelectPreviousWord
++ || ke == QKeySequence::SelectStartOfLine
++ || ke == QKeySequence::SelectEndOfLine
++ || ke == QKeySequence::SelectStartOfBlock
++ || ke == QKeySequence::SelectEndOfBlock
++ || ke == QKeySequence::SelectStartOfDocument
++ || ke == QKeySequence::SelectAll
++ || ke == QKeySequence::SelectEndOfDocument
++ || ke == QKeySequence::DeleteCompleteLine) {
++ ke->accept();
++ return true;
++ } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier
++ || ke->modifiers() == Qt::KeypadModifier) {
++ if (ke->key() < Qt::Key_Escape) {
++ ke->accept();
++ return true;
++ } else {
++ switch (ke->key()) {
++ case Qt::Key_Delete:
++ case Qt::Key_Home:
++ case Qt::Key_End:
++ case Qt::Key_Backspace:
++ case Qt::Key_Left:
++ case Qt::Key_Right:
++ ke->accept();
++ return true;
++ default:
++ break;
++ }
++ }
++ }
++ ev->ignore();
++ }
++ #endif
++
++ return QQuickImplicitSizeItem::event(ev);
++ }
++
++ void QQuickTextInput::geometryChange(const QRectF &newGeometry,
++ const QRectF &oldGeometry)
++ {
++ Q_D(QQuickTextInput);
++ if (!d->inLayout) {
++ if (newGeometry.width() != oldGeometry.width())
++ d->updateLayout();
++ else if (newGeometry.height() != oldGeometry.height() && d->vAlign != QQuickTextInput::AlignTop)
++ d->updateBaselineOffset();
++ updateCursorRectangle();
++ }
++ QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
++ }
++
++ void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value)
++ {
++ Q_D(QQuickTextInput);
++ Q_UNUSED(value);
++ switch (change) {
++ case ItemDevicePixelRatioHasChanged:
++ if (d->containsUnscalableGlyphs) {
++ // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
++ // Text layout code respects the current device pixel ratio automatically, we only need
++ // to rerun layout after the ratio changed.
++ d->updateLayout();
++ }
++ break;
++
++ default:
++ break;
++ }
++ QQuickImplicitSizeItem::itemChange(change, value);
++ }
++
++ void QQuickTextInputPrivate::ensureVisible(int position, int preeditCursor, int preeditLength)
++ {
++ Q_Q(QQuickTextInput);
++ QTextLine textLine = m_textLayout.lineForTextPosition(position + preeditCursor);
++ const qreal width = qMax<qreal>(0, q->width() - q->leftPadding() - q->rightPadding());
++ qreal cix = 0;
++ qreal widthUsed = 0;
++ if (textLine.isValid()) {
++ cix = textLine.cursorToX(position + preeditLength);
++ const qreal cursorWidth = cix >= 0 ? cix : width - cix;
++ widthUsed = qMax(textLine.naturalTextWidth(), cursorWidth);
++ }
++ int previousScroll = hscroll;
++
++ if (widthUsed <= width) {
++ hscroll = 0;
++ } else {
++ Q_ASSERT(textLine.isValid());
++ if (cix - hscroll >= width) {
++ // text doesn't fit, cursor is to the right of br (scroll right)
++ hscroll = cix - width;
++ } else if (cix - hscroll < 0 && hscroll < widthUsed) {
++ // text doesn't fit, cursor is to the left of br (scroll left)
++ hscroll = cix;
++ } else if (widthUsed - hscroll < width) {
++ // text doesn't fit, text document is to the left of br; align
++ // right
++ hscroll = widthUsed - width;
++ } else if (width - hscroll > widthUsed) {
++ // text doesn't fit, text document is to the right of br; align
++ // left
++ hscroll = width - widthUsed;
++ }
++ #if QT_CONFIG(im)
++ if (preeditLength > 0) {
++ // check to ensure long pre-edit text doesn't push the cursor
++ // off to the left
++ cix = textLine.cursorToX(position + qMax(0, preeditCursor - 1));
++ if (cix < hscroll)
++ hscroll = cix;
++ }
++ #endif
++ }
++ if (previousScroll != hscroll)
++ textLayoutDirty = true;
++ }
++
++ void QQuickTextInputPrivate::updateHorizontalScroll()
++ {
++ if (autoScroll && m_echoMode != QQuickTextInput::NoEcho) {
++ #if QT_CONFIG(im)
++ const int preeditLength = m_textLayout.preeditAreaText().size();
++ ensureVisible(m_cursor, m_preeditCursor, preeditLength);
++ #else
++ ensureVisible(m_cursor);
++ #endif
++ } else {
++ hscroll = 0;
++ }
++ }
++
++ void QQuickTextInputPrivate::updateVerticalScroll()
++ {
++ Q_Q(QQuickTextInput);
++ #if QT_CONFIG(im)
++ const int preeditLength = m_textLayout.preeditAreaText().size();
++ #endif
++ const qreal height = qMax<qreal>(0, q->height() - q->topPadding() - q->bottomPadding());
++ qreal heightUsed = contentSize.height();
++ qreal previousScroll = vscroll;
++
++ if (!autoScroll || heightUsed <= height) {
++ // text fits in br; use vscroll for alignment
++ vscroll = -QQuickTextUtil::alignedY(
++ heightUsed, height, vAlign & ~(Qt::AlignAbsolute|Qt::AlignHorizontal_Mask));
++ } else {
++ #if QT_CONFIG(im)
++ QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor + preeditLength);
++ #else
++ QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor);
++ #endif
++ QRectF r = currentLine.isValid() ? currentLine.rect() : QRectF();
++ qreal top = r.top();
++ int bottom = r.bottom();
++
++ if (bottom - vscroll >= height) {
++ // text doesn't fit, cursor is to the below the br (scroll down)
++ vscroll = bottom - height;
++ } else if (top - vscroll < 0 && vscroll < heightUsed) {
++ // text doesn't fit, cursor is above br (scroll up)
++ vscroll = top;
++ } else if (heightUsed - vscroll < height) {
++ // text doesn't fit, text document is to the left of br; align
++ // right
++ vscroll = heightUsed - height;
++ }
++ #if QT_CONFIG(im)
++ if (preeditLength > 0) {
++ // check to ensure long pre-edit text doesn't push the cursor
++ // off the top
++ currentLine = m_textLayout.lineForTextPosition(m_cursor + qMax(0, m_preeditCursor - 1));
++ top = currentLine.isValid() ? currentLine.rect().top() : 0;
++ if (top < vscroll)
++ vscroll = top;
++ }
++ #endif
++ }
++ if (previousScroll != vscroll)
++ textLayoutDirty = true;
++ }
++
++ void QQuickTextInput::triggerPreprocess()
++ {
++ Q_D(QQuickTextInput);
++ if (d->updateType == QQuickTextInputPrivate::UpdateNone)
++ d->updateType = QQuickTextInputPrivate::UpdateOnlyPreprocess;
++ polish();
++ update();
++ }
++
++ void QQuickTextInput::updatePolish()
++ {
++ invalidateFontCaches();
++ }
++
++ void QQuickTextInput::invalidateFontCaches()
++ {
++ Q_D(QQuickTextInput);
++
++ if (d->m_textLayout.engine() != nullptr)
++ d->m_textLayout.engine()->resetFontEngineCache();
++ }
++
++ void QQuickTextInput::ensureActiveFocus(Qt::FocusReason reason)
++ {
++ bool hadActiveFocus = hasActiveFocus();
++ forceActiveFocus(reason);
++ #if QT_CONFIG(im)
++ Q_D(QQuickTextInput);
++ // re-open input panel on press if already focused
++ if (hasActiveFocus() && hadActiveFocus && !d->m_readOnly)
++ qGuiApp->inputMethod()->show();
++ #else
++ Q_UNUSED(hadActiveFocus);
++ #endif
++ }
++
++ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
++ {
++ Q_UNUSED(data);
++ Q_D(QQuickTextInput);
++
++ if (d->updateType != QQuickTextInputPrivate::UpdatePaintNode && oldNode != nullptr) {
++ // Update done in preprocess() in the nodes
++ d->updateType = QQuickTextInputPrivate::UpdateNone;
++ return oldNode;
++ }
++
++ d->updateType = QQuickTextInputPrivate::UpdateNone;
++
++ QSGInternalTextNode *node = static_cast<QSGInternalTextNode *>(oldNode);
++ if (node == nullptr)
++ node = d->sceneGraphContext()->createInternalTextNode(d->sceneGraphRenderContext());
++ d->textNode = node;
++
++ const bool showCursor = !isReadOnly() && d->cursorItem == nullptr && d->cursorVisible && d->m_blinkStatus;
++
++ if (!d->textLayoutDirty && oldNode != nullptr) {
++ if (showCursor)
++ node->setCursor(cursorRectangle(), d->color, nullptr);
++ else
++ node->clearCursor();
++ } else {
++ node->setRenderType(QSGTextNode::RenderType(d->renderType));
++
++ QSGInternalTextNode::RecycleBin recycleBin;
++ node->recycle(&recycleBin);
++ node->setMatrix(QMatrix4x4());
++ node->setTextStyle(QSGInternalTextNode::Normal);
++ node->setColor(d->color);
++ node->setSelectionTextColor(d->selectedTextColor);
++ node->setSelectionColor(d->selectionColor);
++ node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
++
++ if (flags().testFlag(ItemObservesViewport))
++ node->setViewport(clipRect());
++ else
++ node->setViewport(QRectF{});
++
++ QPointF offset(leftPadding(), topPadding());
++ offset -= QPointF(d->hscroll, d->vscroll);
++
++ if (!d->m_textLayout.text().isEmpty()
++ #if QT_CONFIG(im)
++ || !d->m_textLayout.preeditAreaText().isEmpty()
++ #endif
++ ) {
++ node->addTextLayout(offset,
++ &d->m_textLayout,
++ &recycleBin,
++ d->selectionStart(),
++ d->selectionEnd() - 1); // selectionEnd() returns first char after
++ // selection
++ }
++
++ if (showCursor)
++ node->setCursor(cursorRectangle(), d->color, &recycleBin);
++ node->discardUnusedNodes(&recycleBin);
++
++ d->textLayoutDirty = false;
++ }
++
++ d->containsUnscalableGlyphs = node->containsUnscalableGlyphs();
++
++ invalidateFontCaches();
++
++ return node;
++ }
++
++ #if QT_CONFIG(im)
++ QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property) const
++ {
++ #ifdef Q_OS_ANDROID
++ // QTBUG-61652
++ if (property == Qt::ImEnterKeyType) {
++ Q_D(const QQuickItem);
++ // Do not change if type was set manually
++ if (!d->extra.isAllocated()
++ || d->extra->enterKeyAttached == nullptr
++ || d->extra->enterKeyAttached->type() == Qt::EnterKeyDefault) {
++
++ QQuickItem *next = const_cast<QQuickTextInput*>(this)->nextItemInFocusChain();
++ QQuickItem *originalNext = next;
++ while (next && next != this && !next->activeFocusOnTab()) {
++ next = next->nextItemInFocusChain();
++ if (next == originalNext) {
++ // There seems to be no suitable element in the focus chain
++ next = nullptr;
++ }
++ }
++ if (next) {
++ const auto nextYPos = next->mapToGlobal(QPoint(0, 0)).y();
++ const auto currentYPos = this->mapToGlobal(QPoint(0, 0)).y();
++ if (currentYPos < nextYPos)
++ // Set EnterKey to KeyNext type only if the next item
++ // in the focus chain is below current QQuickTextInput
++ return Qt::EnterKeyNext;
++ }
++ }
++ }
++ #endif
++ return inputMethodQuery(property, QVariant());
++ }
++
++ QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property, const QVariant &argument) const
++ {
++ Q_D(const QQuickTextInput);
++ switch (property) {
++ case Qt::ImEnabled:
++ return QVariant((bool)(flags() & ItemAcceptsInputMethod));
++ case Qt::ImHints:
++ return QVariant((int) d->effectiveInputMethodHints());
++ case Qt::ImCursorRectangle:
++ return cursorRectangle();
++ case Qt::ImAnchorRectangle:
++ return d->anchorRectangle();
++ case Qt::ImFont:
++ return font();
++ case Qt::ImCursorPosition: {
++ const QPointF pt = argument.toPointF();
++ if (!pt.isNull())
++ return QVariant(d->positionAt(pt));
++ return QVariant(d->m_cursor);
++ }
++ case Qt::ImSurroundingText:
++ if (d->m_echoMode == PasswordEchoOnEdit && !d->m_passwordEchoEditing) {
++ return QVariant(displayText());
++ } else {
++ return QVariant(d->realText());
++ }
++ case Qt::ImCurrentSelection:
++ return QVariant(selectedText());
++ case Qt::ImMaximumTextLength:
++ return QVariant(maxLength());
++ case Qt::ImAnchorPosition:
++ if (d->selectionStart() == d->selectionEnd())
++ return QVariant(d->m_cursor);
++ else if (d->selectionStart() == d->m_cursor)
++ return QVariant(d->selectionEnd());
++ else
++ return QVariant(d->selectionStart());
++ case Qt::ImAbsolutePosition:
++ return QVariant(d->m_cursor);
++ case Qt::ImTextAfterCursor:
++ if (argument.isValid())
++ return QVariant(d->m_text.mid(d->m_cursor, argument.toInt()));
++ return QVariant(d->m_text.mid(d->m_cursor));
++ case Qt::ImTextBeforeCursor:
++ if (argument.isValid())
++ return QVariant(QStringView{d->m_text}.left(d->m_cursor).right(argument.toInt()).toString());
++ return QVariant(d->m_text.left(d->m_cursor));
++ case Qt::ImReadOnly:
++ return QVariant(d->m_readOnly);
++ default:
++ return QQuickItem::inputMethodQuery(property);
++ }
++ }
++ #endif // im
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::deselect()
++
++ Removes active text selection.
++ */
++ void QQuickTextInput::deselect()
++ {
++ Q_D(QQuickTextInput);
++ d->deselect();
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::selectAll()
++
++ Causes all text to be selected.
++ */
++ void QQuickTextInput::selectAll()
++ {
++ Q_D(QQuickTextInput);
++ d->setSelection(0, text().size());
++ }
++
++ /*!
++ \qmlmethod bool QtQuick::TextInput::isRightToLeft(int start, int end)
++
++ Returns \c true if the natural reading direction of the editor text
++ found between positions \a start and \a end is right to left.
++ */
++ bool QQuickTextInput::isRightToLeft(int start, int end)
++ {
++ if (start > end) {
++ qmlWarning(this) << "isRightToLeft(start, end) called with the end property being smaller than the start.";
++ return false;
++ } else {
++ return QStringView{text()}.mid(start, end - start).isRightToLeft();
++ }
++ }
++
++ #if QT_CONFIG(clipboard)
++ /*!
++ \qmlmethod void QtQuick::TextInput::cut()
++
++ Moves the currently selected text to the system clipboard.
++
++ \note If the echo mode is set to a mode other than Normal then cut
++ will not work. This is to prevent using cut as a method of bypassing
++ password features of the line control.
++ */
++ void QQuickTextInput::cut()
++ {
++ Q_D(QQuickTextInput);
++ if (!d->m_readOnly && d->m_echoMode == QQuickTextInput::Normal) {
++ d->copy();
++ d->del();
++ }
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::copy()
++
++ Copies the currently selected text to the system clipboard.
++
++ \note If the echo mode is set to a mode other than Normal then copy
++ will not work. This is to prevent using copy as a method of bypassing
++ password features of the line control.
++ */
++ void QQuickTextInput::copy()
++ {
++ Q_D(QQuickTextInput);
++ d->copy();
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::paste()
++
++ Replaces the currently selected text by the contents of the system clipboard.
++ */
++ void QQuickTextInput::paste()
++ {
++ Q_D(QQuickTextInput);
++ if (!d->m_readOnly)
++ d->paste();
++ }
++ #endif // clipboard
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::undo()
++
++ Undoes the last operation if undo is \l {canUndo}{available}. Deselects any
++ current selection, and updates the selection start to the current cursor
++ position.
++ */
++
++ void QQuickTextInput::undo()
++ {
++ Q_D(QQuickTextInput);
++ if (!d->m_readOnly) {
++ d->cancelInput();
++ d->internalUndo();
++ d->finishChange(-1, true);
++ }
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::redo()
++
++ Redoes the last operation if redo is \l {canRedo}{available}.
++ */
++
++ void QQuickTextInput::redo()
++ {
++ Q_D(QQuickTextInput);
++ if (!d->m_readOnly) {
++ d->cancelInput();
++ d->internalRedo();
++ d->finishChange();
++ }
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::insert(int position, string text)
++
++ Inserts \a text into the TextInput at \a position.
++ */
++
++ void QQuickTextInput::insert(int position, const QString &text)
++ {
++ Q_D(QQuickTextInput);
++ if (d->m_echoMode == QQuickTextInput::Password) {
++ if (d->m_passwordMaskDelay > 0)
++ d->m_passwordEchoTimer.start(d->m_passwordMaskDelay, this);
++ }
++ if (position < 0 || position > d->m_text.size())
++ return;
++
++ const int priorState = d->m_undoState;
++
++ QString insertText = text;
++
++ if (d->hasSelectedText()) {
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
++ }
++ if (d->m_maskData) {
++ insertText = d->maskString(position, insertText);
++ for (int i = 0; i < insertText.size(); ++i) {
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::DeleteSelection, position + i, d->m_text.at(position + i), -1, -1));
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::Insert, position + i, insertText.at(i), -1, -1));
++ }
++ d->m_text.replace(position, insertText.size(), insertText);
++ if (!insertText.isEmpty())
++ d->m_textDirty = true;
++ if (position < d->m_selend && position + insertText.size() > d->m_selstart)
++ d->m_selDirty = true;
++ } else {
++ int remaining = d->m_maxLength - d->m_text.size();
++ if (remaining != 0) {
++ insertText = insertText.left(remaining);
++ d->m_text.insert(position, insertText);
++ for (int i = 0; i < insertText.size(); ++i)
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::Insert, position + i, insertText.at(i), -1, -1));
++ if (d->m_cursor >= position)
++ d->m_cursor += insertText.size();
++ if (d->m_selstart >= position)
++ d->m_selstart += insertText.size();
++ if (d->m_selend >= position)
++ d->m_selend += insertText.size();
++ d->m_textDirty = true;
++ if (position >= d->m_selstart && position <= d->m_selend)
++ d->m_selDirty = true;
++ }
++ }
++
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
++ d->finishChange(priorState);
++
++ if (d->lastSelectionStart != d->lastSelectionEnd) {
++ if (d->m_selstart != d->lastSelectionStart) {
++ d->lastSelectionStart = d->m_selstart;
++ emit selectionStartChanged();
++ }
++ if (d->m_selend != d->lastSelectionEnd) {
++ d->lastSelectionEnd = d->m_selend;
++ emit selectionEndChanged();
++ }
++ }
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::remove(int start, int end)
++
++ Removes the section of text that is between the \a start and \a end positions from the TextInput.
++ */
++
++ void QQuickTextInput::remove(int start, int end)
++ {
++ Q_D(QQuickTextInput);
++
++ start = qBound(0, start, d->m_text.size());
++ end = qBound(0, end, d->m_text.size());
++
++ if (start > end)
++ qSwap(start, end);
++ else if (start == end)
++ return;
++
++ if (start < d->m_selend && end > d->m_selstart)
++ d->m_selDirty = true;
++
++ const int priorState = d->m_undoState;
++
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
++
++ if (start <= d->m_cursor && d->m_cursor < end) {
++ // cursor is within the selection. Split up the commands
++ // to be able to restore the correct cursor position
++ for (int i = d->m_cursor; i >= start; --i) {
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::DeleteSelection, i, d->m_text.at(i), -1, 1));
++ }
++ for (int i = end - 1; i > d->m_cursor; --i) {
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::DeleteSelection, i - d->m_cursor + start - 1, d->m_text.at(i), -1, -1));
++ }
++ } else {
++ for (int i = end - 1; i >= start; --i) {
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::RemoveSelection, i, d->m_text.at(i), -1, -1));
++ }
++ }
++ if (d->m_maskData) {
++ d->m_text.replace(start, end - start, d->clearString(start, end - start));
++ for (int i = 0; i < end - start; ++i) {
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::Insert, start + i, d->m_text.at(start + i), -1, -1));
++ }
++ } else {
++ d->m_text.remove(start, end - start);
++
++ if (d->m_cursor > start)
++ d->m_cursor -= qMin(d->m_cursor, end) - start;
++ if (d->m_selstart > start)
++ d->m_selstart -= qMin(d->m_selstart, end) - start;
++ if (d->m_selend >= end)
++ d->m_selend -= end - start;
++ }
++ d->addCommand(QQuickTextInputPrivate::Command(
++ QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
++
++ d->m_textDirty = true;
++ d->finishChange(priorState);
++
++ if (d->lastSelectionStart != d->lastSelectionEnd) {
++ if (d->m_selstart != d->lastSelectionStart) {
++ d->lastSelectionStart = d->m_selstart;
++ emit selectionStartChanged();
++ }
++ if (d->m_selend != d->lastSelectionEnd) {
++ d->lastSelectionEnd = d->m_selend;
++ emit selectionEndChanged();
++ }
++ }
++ }
++
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::selectWord()
++
++ Causes the word closest to the current cursor position to be selected.
++ */
++ void QQuickTextInput::selectWord()
++ {
++ Q_D(QQuickTextInput);
++ d->selectWordAtPos(d->m_cursor);
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::passwordCharacter
++
++ This is the character displayed when echoMode is set to Password or
++ PasswordEchoOnEdit. By default it is the password character used by
++ the platform theme.
++
++ If this property is set to a string with more than one character,
++ the first character is used. If the string is empty, the value
++ is ignored and the property is not set.
++ */
++ QString QQuickTextInput::passwordCharacter() const
++ {
++ Q_D(const QQuickTextInput);
++ return QString(d->m_passwordCharacter);
++ }
++
++ void QQuickTextInput::setPasswordCharacter(const QString &str)
++ {
++ Q_D(QQuickTextInput);
++ if (str.size() < 1)
++ return;
++ d->m_passwordCharacter = str.constData()[0];
++ if (d->m_echoMode == Password || d->m_echoMode == PasswordEchoOnEdit)
++ d->updateDisplayText();
++ emit passwordCharacterChanged();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::TextInput::passwordMaskDelay
++ \since 5.4
++
++ Sets the delay before visible character is masked with password character, in milliseconds.
++
++ The reset method will be called by assigning undefined.
++ */
++ int QQuickTextInput::passwordMaskDelay() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_passwordMaskDelay;
++ }
++
++ void QQuickTextInput::setPasswordMaskDelay(int delay)
++ {
++ Q_D(QQuickTextInput);
++ if (d->m_passwordMaskDelay != delay) {
++ d->m_passwordMaskDelay = delay;
++ emit passwordMaskDelayChanged(delay);
++ }
++ }
++
++ void QQuickTextInput::resetPasswordMaskDelay()
++ {
++ setPasswordMaskDelay(qGuiApp->styleHints()->passwordMaskDelay());
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::displayText
++
++ This is the text displayed in the TextInput.
++
++ If \l echoMode is set to TextInput::Normal, this holds the
++ same value as the TextInput::text property. Otherwise,
++ this property holds the text visible to the user, while
++ the \l text property holds the actual entered text.
++
++ \note Unlike the TextInput::text property, this contains
++ partial text input from an input method.
++
++ \readonly
++ \sa preeditText
++ */
++ QString QQuickTextInput::displayText() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_textLayout.text().insert(d->m_textLayout.preeditAreaPosition(), d->m_textLayout.preeditAreaText());
++ }
++
++ /*!
++ \qmlproperty string QtQuick::TextInput::preeditText
++ \readonly
++ \since 5.7
++
++ This property contains partial text input from an input method.
++
++ To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText
++ flag in inputMethodHints.
++
++ \sa displayText, inputMethodHints
++ */
++ QString QQuickTextInput::preeditText() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->m_textLayout.preeditAreaText();
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::selectByMouse
++
++ Defaults to \c true.
++
++ If true, the user can use the mouse to select text in the usual way.
++
++ \note In versions prior to 6.4, the default was \c false; but if you
++ enabled this property, you could also select text on a touchscreen by
++ dragging your finger across it. This interfered with flicking when
++ TextInput was used inside a Flickable. For consistency with TextField,
++ selectByMouse now really means what it says: if \c true, you can select
++ text by dragging \e only with a mouse. If this change does not suit your
++ application, you can set \c selectByMouse to \c false, or import an older
++ API version (for example \c {import QtQuick 6.3}) to revert to the previous
++ behavior. The option to revert behavior by changing the import version will
++ be removed in a later version of Qt.
++ */
++ bool QQuickTextInput::selectByMouse() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->selectByMouse;
++ }
++
++ void QQuickTextInput::setSelectByMouse(bool on)
++ {
++ Q_D(QQuickTextInput);
++ if (d->selectByMouse != on) {
++ d->selectByMouse = on;
++ emit selectByMouseChanged(on);
++ }
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::TextInput::mouseSelectionMode
++
++ Specifies how text should be selected using a mouse.
++
++ \value TextInput.SelectCharacters (default) The selection is updated with individual characters.
++ \value TextInput.SelectWords The selection is updated with whole words.
++
++ This property only applies when \l selectByMouse is true.
++ */
++
++ QQuickTextInput::SelectionMode QQuickTextInput::mouseSelectionMode() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->mouseSelectionMode;
++ }
++
++ void QQuickTextInput::setMouseSelectionMode(SelectionMode mode)
++ {
++ Q_D(QQuickTextInput);
++ if (d->mouseSelectionMode != mode) {
++ d->mouseSelectionMode = mode;
++ emit mouseSelectionModeChanged(mode);
++ }
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::persistentSelection
++
++ Whether the TextInput should keep its selection when it loses active focus to another
++ item in the scene. By default this is set to false;
++ */
++
++ bool QQuickTextInput::persistentSelection() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->persistentSelection;
++ }
++
++ void QQuickTextInput::setPersistentSelection(bool on)
++ {
++ Q_D(QQuickTextInput);
++ if (d->persistentSelection == on)
++ return;
++ d->persistentSelection = on;
++ emit persistentSelectionChanged();
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::canPaste
++ \readonly
++
++ Returns true if the TextInput is writable and the content of the clipboard is
++ suitable for pasting into the TextInput.
++ */
++ bool QQuickTextInput::canPaste() const
++ {
++ #if QT_CONFIG(clipboard)
++ Q_D(const QQuickTextInput);
++ if (!d->canPasteValid) {
++ bool canPaste = false;
++ if (!d->m_readOnly) {
++ if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData())
++ canPaste = mimeData->hasText();
++ }
++ const_cast<QQuickTextInputPrivate *>(d)->canPaste = canPaste;
++ const_cast<QQuickTextInputPrivate *>(d)->canPasteValid = true;
++ }
++ return d->canPaste;
++ #else
++ return false;
++ #endif
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::canUndo
++ \readonly
++
++ Returns true if the TextInput is writable and there are previous operations
++ that can be undone.
++ */
++
++ bool QQuickTextInput::canUndo() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->canUndo;
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::canRedo
++ \readonly
++
++ Returns true if the TextInput is writable and there are \l {undo}{undone}
++ operations that can be redone.
++ */
++
++ bool QQuickTextInput::canRedo() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->canRedo;
++ }
++
++ /*!
++ \qmlproperty real QtQuick::TextInput::contentWidth
++ \readonly
++
++ Returns the width of the text, including the width past the width
++ which is covered due to insufficient wrapping if \l wrapMode is set.
++ */
++
++ qreal QQuickTextInput::contentWidth() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->contentSize.width();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::TextInput::contentHeight
++ \readonly
++
++ Returns the height of the text, including the height past the height
++ that is covered if the text does not fit within the set height.
++ */
++
++ qreal QQuickTextInput::contentHeight() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->contentSize.height();
++ }
++
++ void QQuickTextInput::moveCursorSelection(int position)
++ {
++ Q_D(QQuickTextInput);
++ d->moveCursor(position, true);
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::moveCursorSelection(int position, SelectionMode mode)
++
++ Moves the cursor to \a position and updates the selection according to the optional \a mode
++ parameter. (To only move the cursor, set the \l cursorPosition property.)
++
++ When this method is called it additionally sets either the
++ selectionStart or the selectionEnd (whichever was at the previous cursor position)
++ to the specified position. This allows you to easily extend and contract the selected
++ text range.
++
++ The selection mode specifies whether the selection is updated on a per character or a per word
++ basis. If not specified the selection mode will default to \c {TextInput.SelectCharacters}.
++
++ \value TextInput.SelectCharacters
++ Sets either the selectionStart or selectionEnd (whichever was at the previous cursor position)
++ to the specified position.
++ \value TextInput.SelectWords
++ Sets the selectionStart and selectionEnd to include all words between the specified position
++ and the previous cursor position. Words partially in the range are included.
++
++ For example, take this sequence of calls:
++
++ \code
++ cursorPosition = 5
++ moveCursorSelection(9, TextInput.SelectCharacters)
++ moveCursorSelection(7, TextInput.SelectCharacters)
++ \endcode
++
++ This moves the cursor to position 5, extend the selection end from 5 to 9
++ and then retract the selection end from 9 to 7, leaving the text from position 5 to 7
++ selected (the 6th and 7th characters).
++
++ The same sequence with TextInput.SelectWords will extend the selection start to a word boundary
++ before or on position 5 and extend the selection end to a word boundary on or past position 9.
++ */
++ void QQuickTextInput::moveCursorSelection(int pos, SelectionMode mode)
++ {
++ Q_D(QQuickTextInput);
++
++ if (mode == SelectCharacters) {
++ d->moveCursor(pos, true);
++ } else if (pos != d->m_cursor) {
++ const int cursor = d->m_cursor;
++ int anchor;
++ if (!d->hasSelectedText())
++ anchor = d->m_cursor;
++ else if (d->selectionStart() == d->m_cursor)
++ anchor = d->selectionEnd();
++ else
++ anchor = d->selectionStart();
++
++ if (anchor < pos || (anchor == pos && cursor < pos)) {
++ const QString text = this->text();
++ QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text);
++ finder.setPosition(anchor);
++
++ const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons();
++ if (anchor < text.size() && (reasons == QTextBoundaryFinder::NotAtBoundary
++ || (reasons & QTextBoundaryFinder::EndOfItem))) {
++ finder.toPreviousBoundary();
++ }
++ anchor = finder.position() != -1 ? finder.position() : 0;
++
++ finder.setPosition(pos);
++ if (pos > 0 && !finder.boundaryReasons())
++ finder.toNextBoundary();
++ const int cursor = finder.position() != -1 ? finder.position() : text.size();
++
++ d->setSelection(anchor, cursor - anchor);
++ } else if (anchor > pos || (anchor == pos && cursor > pos)) {
++ const QString text = this->text();
++ QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text);
++ finder.setPosition(anchor);
++
++ const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons();
++ if (anchor > 0 && (reasons == QTextBoundaryFinder::NotAtBoundary
++ || (reasons & QTextBoundaryFinder::StartOfItem))) {
++ finder.toNextBoundary();
++ }
++ anchor = finder.position() != -1 ? finder.position() : text.size();
++
++ finder.setPosition(pos);
++ if (pos < text.size() && !finder.boundaryReasons())
++ finder.toPreviousBoundary();
++ const int cursor = finder.position() != -1 ? finder.position() : 0;
++
++ d->setSelection(anchor, cursor - anchor);
++ }
++ }
++ }
++
++ void QQuickTextInput::focusInEvent(QFocusEvent *event)
++ {
++ Q_D(QQuickTextInput);
++ d->handleFocusEvent(event);
++ QQuickImplicitSizeItem::focusInEvent(event);
++ }
++
++ void QQuickTextInputPrivate::handleFocusEvent(QFocusEvent *event)
++ {
++ Q_Q(QQuickTextInput);
++ bool focus = event->gotFocus();
++ if (!m_readOnly) {
++ q->setCursorVisible(focus);
++ setBlinkingCursorEnabled(focus);
++ }
++ if (focus) {
++ q->q_updateAlignment();
++ #if QT_CONFIG(im)
++ if (focusOnPress && !m_readOnly)
++ qGuiApp->inputMethod()->show();
++ q->connect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
++ q, SLOT(q_updateAlignment()));
++ #endif
++ } else {
++ if ((m_passwordEchoEditing || m_passwordEchoTimer.isActive())) {
++ updatePasswordEchoEditing(false);//QQuickTextInputPrivate sets it on key events, but doesn't deal with focus events
++ }
++
++ if (event->reason() != Qt::ActiveWindowFocusReason
++ && event->reason() != Qt::PopupFocusReason
++ && hasSelectedText()
++ && !persistentSelection)
++ deselect();
++
++ if (hasAcceptableInput(m_text) == AcceptableInput || fixup())
++ emit q->editingFinished();
++
++ #if QT_CONFIG(im)
++ q->disconnect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
++ q, SLOT(q_updateAlignment()));
++ #endif
++ }
++ }
++
++ void QQuickTextInput::focusOutEvent(QFocusEvent *event)
++ {
++ Q_D(QQuickTextInput);
++ d->handleFocusEvent(event);
++ QQuickImplicitSizeItem::focusOutEvent(event);
++ }
++
++ void QQuickTextInputPrivate::readOnlyChanged(bool isReadOnly)
++ {
++ Q_UNUSED(isReadOnly);
++ #if QT_CONFIG(accessibility)
++ if (QQuickAccessibleAttached *accessibleAttached =
++ QQuickAccessibleAttached::attachedProperties(q_func()))
++ accessibleAttached->set_readOnly(isReadOnly);
++ #endif
++ }
++
++ void QQuickTextInputPrivate::echoModeChanged(QQuickTextInput::EchoMode echoMode)
++ {
++ #if QT_CONFIG(accessibility)
++ if (!QAccessible::isActive())
++ return;
++
++ if (QQuickAccessibleAttached *accessibleAttached =
++ QQuickAccessibleAttached::attachedProperties(q_func()))
++ accessibleAttached->set_passwordEdit((echoMode == QQuickTextInput::Password
++ || echoMode == QQuickTextInput::PasswordEchoOnEdit)
++ ? true
++ : false);
++ #else
++ Q_UNUSED(echoMode);
++ #endif
++ }
++
++ #if QT_CONFIG(accessibility)
++ void QQuickTextInputPrivate::accessibilityActiveChanged(bool active)
++ {
++ if (!active)
++ return;
++
++ Q_Q(QQuickTextInput);
++ QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(
++ qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true));
++ Q_ASSERT(accessibleAttached);
++ accessibleAttached->setRole(effectiveAccessibleRole());
++ accessibleAttached->set_readOnly(m_readOnly);
++ accessibleAttached->set_passwordEdit((m_echoMode == QQuickTextInput::Password
++ || m_echoMode == QQuickTextInput::PasswordEchoOnEdit)
++ ? true
++ : false);
++ }
++
++ QAccessible::Role QQuickTextInputPrivate::accessibleRole() const
++ {
++ return QAccessible::EditableText;
++ }
++ #endif
++
++ /*!
++ \qmlproperty bool QtQuick::TextInput::inputMethodComposing
++ \readonly
++
++ This property holds whether the TextInput has partial text input from an
++ input method.
++
++ While it is composing an input method may rely on mouse or key events from
++ the TextInput to edit or commit the partial text. This property can be
++ used to determine when to disable events handlers that may interfere with
++ the correct operation of an input method.
++ */
++ bool QQuickTextInput::isInputMethodComposing() const
++ {
++ #if !QT_CONFIG(im)
++ return false;
++ #else
++ Q_D(const QQuickTextInput);
++ return d->hasImState;
++ #endif
++ }
++
++ QQuickTextInputPrivate::ExtraData::ExtraData()
++ : padding(0)
++ , topPadding(0)
++ , leftPadding(0)
++ , rightPadding(0)
++ , bottomPadding(0)
++ , explicitTopPadding(false)
++ , explicitLeftPadding(false)
++ , explicitRightPadding(false)
++ , explicitBottomPadding(false)
++ , implicitResize(true)
++ {
++ }
++
++ void QQuickTextInputPrivate::init()
++ {
++ Q_Q(QQuickTextInput);
++ #if QT_CONFIG(clipboard)
++ if (QGuiApplication::clipboard()->supportsSelection())
++ q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
++ else
++ #endif
++ q->setAcceptedMouseButtons(Qt::LeftButton);
++
++ #if QT_CONFIG(im)
++ q->setFlag(QQuickItem::ItemAcceptsInputMethod);
++ #endif
++ q->setFlag(QQuickItem::ItemHasContents);
++ #if QT_CONFIG(clipboard)
++ qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()),
++ q, QQuickTextInput, SLOT(q_canPasteChanged()));
++ #endif // clipboard
++
++ lastSelectionStart = 0;
++ lastSelectionEnd = 0;
++ determineHorizontalAlignment();
++
++ if (!qmlDisableDistanceField()) {
++ QTextOption option = m_textLayout.textOption();
++ option.setUseDesignMetrics(renderType != QQuickTextInput::NativeRendering);
++ m_textLayout.setTextOption(option);
++ }
++
++ m_inputControl = new QInputControl(QInputControl::LineEdit, q);
++ setSizePolicy(QLayoutPolicy::Expanding, QLayoutPolicy::Fixed);
++
++ QObjectPrivate::connect(q, &QQuickTextInput::readOnlyChanged, this,
++ &QQuickTextInputPrivate::readOnlyChanged);
++ QObjectPrivate::connect(q, &QQuickTextInput::echoModeChanged, this,
++ &QQuickTextInputPrivate::echoModeChanged);
++ }
++
++ void QQuickTextInputPrivate::cancelInput()
++ {
++ #if QT_CONFIG(im)
++ Q_Q(QQuickTextInput);
++ if (!m_readOnly && q->hasActiveFocus() && qGuiApp)
++ cancelPreedit();
++ #endif // im
++ }
++
++ void QQuickTextInput::updateCursorRectangle(bool scroll)
++ {
++ Q_D(QQuickTextInput);
++ if (!isComponentComplete())
++ return;
++
++ if (scroll) {
++ d->updateHorizontalScroll();
++ d->updateVerticalScroll();
++ }
++ d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
++ polish();
++ update();
++ emit cursorRectangleChanged();
++ if (d->cursorItem) {
++ QRectF r = cursorRectangle();
++ d->cursorItem->setPosition(r.topLeft());
++ d->cursorItem->setHeight(r.height());
++ }
++ #if QT_CONFIG(im)
++ updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorRectangle);
++ #endif
++ }
++
++ void QQuickTextInput::selectionChanged()
++ {
++ Q_D(QQuickTextInput);
++ d->textLayoutDirty = true; //TODO: Only update rect in selection
++ d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
++ polish();
++ update();
++ emit selectedTextChanged();
++
++ if (d->lastSelectionStart != d->selectionStart()) {
++ d->lastSelectionStart = d->selectionStart();
++ if (d->lastSelectionStart == -1)
++ d->lastSelectionStart = d->m_cursor;
++ emit selectionStartChanged();
++ }
++ if (d->lastSelectionEnd != d->selectionEnd()) {
++ d->lastSelectionEnd = d->selectionEnd();
++ if (d->lastSelectionEnd == -1)
++ d->lastSelectionEnd = d->m_cursor;
++ emit selectionEndChanged();
++ }
++ }
++
++ QRectF QQuickTextInput::boundingRect() const
++ {
++ Q_D(const QQuickTextInput);
++
++ int cursorWidth = d->cursorItem ? 0 : 1;
++
++ qreal hscroll = d->hscroll;
++ if (!d->autoScroll || d->contentSize.width() < width())
++ hscroll -= QQuickTextUtil::alignedX(d->contentSize.width(), width(), effectiveHAlign());
++
++ // Could include font max left/right bearings to either side of rectangle.
++ QRectF r(-hscroll, -d->vscroll, d->contentSize.width(), d->contentSize.height());
++ r.setRight(r.right() + cursorWidth);
++ return r;
++ }
++
++ QRectF QQuickTextInput::clipRect() const
++ {
++ Q_D(const QQuickTextInput);
++
++ int cursorWidth = d->cursorItem ? d->cursorItem->width() : 1;
++
++ // Could include font max left/right bearings to either side of rectangle.
++ QRectF r = QQuickImplicitSizeItem::clipRect();
++ r.setRight(r.right() + cursorWidth);
++ return r;
++ }
++
++ void QQuickTextInput::q_canPasteChanged()
++ {
++ Q_D(QQuickTextInput);
++ bool old = d->canPaste;
++ #if QT_CONFIG(clipboard)
++ bool canPaste = false;
++ if (!d->m_readOnly) {
++ if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData())
++ canPaste = mimeData->hasText();
++ }
++ d->canPaste = canPaste;
++ #endif
++
++ bool changed = d->canPaste != old || !d->canPasteValid;
++ d->canPasteValid = true;
++ if (changed)
++ emit canPasteChanged();
++
++ }
++
++ void QQuickTextInput::q_updateAlignment()
++ {
++ Q_D(QQuickTextInput);
++ if (d->determineHorizontalAlignment()) {
++ d->updateLayout();
++ updateCursorRectangle();
++ }
++ }
++
++ /*!
++ \internal
++
++ Updates the display text based of the current edit text
++ If the text has changed will emit displayTextChanged()
++ */
++ void QQuickTextInputPrivate::updateDisplayText(bool forceUpdate)
++ {
++ QString orig = m_textLayout.text();
++ QString str;
++ if (m_echoMode == QQuickTextInput::NoEcho)
++ str = QString::fromLatin1("");
++ else
++ str = m_text;
++
++ if (m_echoMode == QQuickTextInput::Password) {
++ str.fill(m_passwordCharacter);
++ if (m_passwordEchoTimer.isActive() && m_cursor > 0 && m_cursor <= m_text.size()) {
++ int cursor = m_cursor - 1;
++ QChar uc = m_text.at(cursor);
++ str[cursor] = uc;
++ if (cursor > 0 && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) {
++ // second half of a surrogate, check if we have the first half as well,
++ // if yes restore both at once
++ uc = m_text.at(cursor - 1);
++ if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00)
++ str[cursor - 1] = uc;
++ }
++ }
++ } else if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) {
++ str.fill(m_passwordCharacter);
++ }
++
++ // replace certain non-printable characters with spaces (to avoid
++ // drawing boxes when using fonts that don't have glyphs for such
++ // characters)
++ QChar* uc = str.data();
++ for (int i = 0; i < str.size(); ++i) {
++ if (uc[i] == QChar::LineSeparator
++ || uc[i] == QChar::ParagraphSeparator
++ || uc[i] == QChar::ObjectReplacementCharacter)
++ uc[i] = QChar(0x0020);
++ }
++
++ if (str != orig || forceUpdate) {
++ m_textLayout.setText(str);
++ updateLayout(); // polish?
++ emit q_func()->displayTextChanged();
++ }
++ }
++
++ qreal QQuickTextInputPrivate::calculateImplicitWidthForText(const QString &text) const
++ {
++ Q_Q(const QQuickTextInput);
++ QTextLayout layout(text);
++
++ QTextOption option = m_textLayout.textOption();
++ option.setTextDirection(m_layoutDirection);
++ option.setFlags(QTextOption::IncludeTrailingSpaces);
++ option.setWrapMode(QTextOption::WrapMode(wrapMode));
++ option.setAlignment(Qt::Alignment(q->effectiveHAlign()));
++ layout.setTextOption(option);
++ layout.setFont(font);
++ #if QT_CONFIG(im)
++ layout.setPreeditArea(m_textLayout.preeditAreaPosition(), m_textLayout.preeditAreaText());
++ #endif
++ layout.beginLayout();
++
++ QTextLine line = layout.createLine();
++ line.setLineWidth(qreal(INT_MAX));
++ const qreal theImplicitWidth = qCeil(line.naturalTextWidth()) + q->leftPadding() + q->rightPadding();
++
++ layout.endLayout();
++ return theImplicitWidth;
++ }
++
++ qreal QQuickTextInputPrivate::getImplicitWidth() const
++ {
++ Q_Q(const QQuickTextInput);
++ if (!requireImplicitWidth) {
++ QQuickTextInputPrivate *d = const_cast<QQuickTextInputPrivate *>(this);
++ d->requireImplicitWidth = true;
++
++ if (q->isComponentComplete())
++ d->implicitWidth = calculateImplicitWidthForText(m_text);
++ }
++ return implicitWidth;
++ }
++
++ void QQuickTextInputPrivate::setTopPadding(qreal value, bool reset)
++ {
++ Q_Q(QQuickTextInput);
++ qreal oldPadding = q->topPadding();
++ if (!reset || extra.isAllocated()) {
++ extra.value().topPadding = value;
++ extra.value().explicitTopPadding = !reset;
++ }
++ if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
++ updateLayout();
++ q->updateCursorRectangle();
++ emit q->topPaddingChanged();
++ }
++ }
++
++ void QQuickTextInputPrivate::setLeftPadding(qreal value, bool reset)
++ {
++ Q_Q(QQuickTextInput);
++ qreal oldPadding = q->leftPadding();
++ if (!reset || extra.isAllocated()) {
++ extra.value().leftPadding = value;
++ extra.value().explicitLeftPadding = !reset;
++ }
++ if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
++ updateLayout();
++ q->updateCursorRectangle();
++ emit q->leftPaddingChanged();
++ }
++ }
++
++ void QQuickTextInputPrivate::setRightPadding(qreal value, bool reset)
++ {
++ Q_Q(QQuickTextInput);
++ qreal oldPadding = q->rightPadding();
++ if (!reset || extra.isAllocated()) {
++ extra.value().rightPadding = value;
++ extra.value().explicitRightPadding = !reset;
++ }
++ if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
++ updateLayout();
++ q->updateCursorRectangle();
++ emit q->rightPaddingChanged();
++ }
++ }
++
++ void QQuickTextInputPrivate::setBottomPadding(qreal value, bool reset)
++ {
++ Q_Q(QQuickTextInput);
++ qreal oldPadding = q->bottomPadding();
++ if (!reset || extra.isAllocated()) {
++ extra.value().bottomPadding = value;
++ extra.value().explicitBottomPadding = !reset;
++ }
++ if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
++ updateLayout();
++ q->updateCursorRectangle();
++ emit q->bottomPaddingChanged();
++ }
++ }
++
++ bool QQuickTextInputPrivate::isImplicitResizeEnabled() const
++ {
++ return !extra.isAllocated() || extra->implicitResize;
++ }
++
++ void QQuickTextInputPrivate::setImplicitResizeEnabled(bool enabled)
++ {
++ if (!enabled)
++ extra.value().implicitResize = false;
++ else if (extra.isAllocated())
++ extra->implicitResize = true;
++ }
++
++ void QQuickTextInputPrivate::updateLayout()
++ {
++ Q_Q(QQuickTextInput);
++
++ if (!q->isComponentComplete())
++ return;
++
++
++ QTextOption option = m_textLayout.textOption();
++ option.setTextDirection(layoutDirection());
++ option.setWrapMode(QTextOption::WrapMode(wrapMode));
++ option.setAlignment(Qt::Alignment(q->effectiveHAlign()));
++ if (!qmlDisableDistanceField())
++ option.setUseDesignMetrics(renderType != QQuickTextInput::NativeRendering);
++
++ m_textLayout.setTextOption(option);
++ m_textLayout.setFont(font);
++
++ m_textLayout.beginLayout();
++
++ QTextLine line = m_textLayout.createLine();
++ if (requireImplicitWidth) {
++ line.setLineWidth(qreal(INT_MAX));
++ const bool wasInLayout = inLayout;
++ inLayout = true;
++ if (isImplicitResizeEnabled())
++ q->setImplicitWidth(qCeil(line.naturalTextWidth()) + q->leftPadding() + q->rightPadding());
++ inLayout = wasInLayout;
++ if (inLayout) // probably the result of a binding loop, but by letting it
++ return; // get this far we'll get a warning to that effect.
++ }
++ qreal lineWidth = q->widthValid() || !isImplicitResizeEnabled() ? q->width() - q->leftPadding() - q->rightPadding() : qreal(INT_MAX);
++ qreal height = 0;
++ qreal width = 0;
++ do {
++ line.setLineWidth(lineWidth);
++ line.setPosition(QPointF(0, height));
++
++ height += line.height();
++ width = qMax(width, line.naturalTextWidth());
++
++ line = m_textLayout.createLine();
++ } while (line.isValid());
++ m_textLayout.endLayout();
++
++ option.setWrapMode(QTextOption::NoWrap);
++ m_textLayout.setTextOption(option);
++
++ textLayoutDirty = true;
++
++ const QSizeF previousSize = contentSize;
++ contentSize = QSizeF(width, height);
++
++ updateType = UpdatePaintNode;
++ q->polish();
++ q->update();
++
++ if (isImplicitResizeEnabled()) {
++ if (!requireImplicitWidth && !q->widthValid())
++ q->setImplicitSize(width + q->leftPadding() + q->rightPadding(), height + q->topPadding() + q->bottomPadding());
++ else
++ q->setImplicitHeight(height + q->topPadding() + q->bottomPadding());
++ }
++
++ updateBaselineOffset();
++
++ if (previousSize != contentSize)
++ emit q->contentSizeChanged();
++ }
++
++ /*!
++ \internal
++ \brief QQuickTextInputPrivate::updateBaselineOffset
++
++ Assumes contentSize.height() is already calculated.
++ */
++ void QQuickTextInputPrivate::updateBaselineOffset()
++ {
++ Q_Q(QQuickTextInput);
++ if (!q->isComponentComplete())
++ return;
++ QFontMetricsF fm(font);
++ qreal yoff = 0;
++ if (q->heightValid()) {
++ const qreal surplusHeight = q->height() - contentSize.height() - q->topPadding() - q->bottomPadding();
++ if (vAlign == QQuickTextInput::AlignBottom)
++ yoff = surplusHeight;
++ else if (vAlign == QQuickTextInput::AlignVCenter)
++ yoff = surplusHeight/2;
++ }
++ qreal ascent;
++ if (m_textLayout.lineCount() > 0) {
++ QTextLine line = m_textLayout.lineAt(0);
++ ascent = line.y() + line.ascent();
++ } else {
++ ascent = fm.ascent();
++ }
++ q->setBaselineOffset(ascent + yoff + q->topPadding());
++ }
++
++ #if QT_CONFIG(clipboard)
++ /*!
++ \internal
++
++ Copies the currently selected text into the clipboard using the given
++ \a mode.
++
++ \note If the echo mode is set to a mode other than Normal then copy
++ will not work. This is to prevent using copy as a method of bypassing
++ password features of the line control.
++ */
++ void QQuickTextInputPrivate::copy(QClipboard::Mode mode) const
++ {
++ QString t = selectedText();
++ if (!t.isEmpty() && m_echoMode == QQuickTextInput::Normal) {
++ QGuiApplication::clipboard()->setText(t, mode);
++ }
++ }
++
++ /*!
++ \internal
++
++ Inserts the text stored in the application clipboard into the line
++ control.
++
++ \sa insert()
++ */
++ void QQuickTextInputPrivate::paste(QClipboard::Mode clipboardMode)
++ {
++ QString clip = QGuiApplication::clipboard()->text(clipboardMode);
++ if (!clip.isEmpty() || hasSelectedText()) {
++ separate(); //make it a separate undo/redo command
++ insert(clip);
++ separate();
++ }
++ }
++
++ #endif // clipboard
++
++ #if QT_CONFIG(im)
++ /*!
++ \internal
++ */
++ void QQuickTextInputPrivate::commitPreedit()
++ {
++ Q_Q(QQuickTextInput);
++
++ if (!hasImState)
++ return;
++
++ QGuiApplication::inputMethod()->commit();
++
++ if (!hasImState)
++ return;
++
++ QInputMethodEvent ev;
++ QCoreApplication::sendEvent(q, &ev);
++ }
++
++ void QQuickTextInputPrivate::cancelPreedit()
++ {
++ Q_Q(QQuickTextInput);
++
++ if (!hasImState)
++ return;
++
++ QGuiApplication::inputMethod()->reset();
++
++ QInputMethodEvent ev;
++ QCoreApplication::sendEvent(q, &ev);
++ }
++ #endif // im
++
++ /*!
++ \internal
++
++ Handles the behavior for the backspace key or function.
++ Removes the current selection if there is a selection, otherwise
++ removes the character prior to the cursor position.
++
++ \sa del()
++ */
++ void QQuickTextInputPrivate::backspace()
++ {
++ int priorState = m_undoState;
++ if (separateSelection()) {
++ removeSelectedText();
++ } else if (m_cursor) {
++ --m_cursor;
++ if (m_maskData)
++ m_cursor = prevMaskBlank(m_cursor);
++ QChar uc = m_text.at(m_cursor);
++ if (m_cursor > 0 && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) {
++ // second half of a surrogate, check if we have the first half as well,
++ // if yes delete both at once
++ uc = m_text.at(m_cursor - 1);
++ if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00) {
++ internalDelete(true);
++ --m_cursor;
++ }
++ }
++ internalDelete(true);
++ }
++ finishChange(priorState);
++ }
++
++ /*!
++ \internal
++
++ Handles the behavior for the delete key or function.
++ Removes the current selection if there is a selection, otherwise
++ removes the character after the cursor position.
++
++ \sa del()
++ */
++ void QQuickTextInputPrivate::del()
++ {
++ int priorState = m_undoState;
++ if (separateSelection()) {
++ removeSelectedText();
++ } else {
++ int n = m_textLayout.nextCursorPosition(m_cursor) - m_cursor;
++ while (n--)
++ internalDelete();
++ }
++ finishChange(priorState);
++ }
++
++ /*!
++ \internal
++
++ Inserts the given \a newText at the current cursor position.
++ If there is any selected text it is removed prior to insertion of
++ the new text.
++ */
++ void QQuickTextInputPrivate::insert(const QString &newText)
++ {
++ int priorState = m_undoState;
++ if (separateSelection())
++ removeSelectedText();
++ internalInsert(newText);
++ finishChange(priorState);
++ }
++
++ /*!
++ \internal
++
++ Clears the line control text.
++ */
++ void QQuickTextInputPrivate::clear()
++ {
++ int priorState = m_undoState;
++ separateSelection();
++ m_selstart = 0;
++ m_selend = m_text.size();
++ removeSelectedText();
++ separate();
++ finishChange(priorState, /*update*/false, /*edited*/false);
++ }
++
++ /*!
++ \internal
++
++ Sets \a length characters from the given \a start position as selected.
++ The given \a start position must be within the current text for
++ the line control. If \a length characters cannot be selected, then
++ the selection will extend to the end of the current text.
++ */
++ void QQuickTextInputPrivate::setSelection(int start, int length)
++ {
++ Q_Q(QQuickTextInput);
++ #if QT_CONFIG(im)
++ commitPreedit();
++ #endif
++
++ if (start < 0 || start > m_text.size()) {
++ qWarning("QQuickTextInputPrivate::setSelection: Invalid start position");
++ return;
++ }
++
++ if (length > 0) {
++ if (start == m_selstart && start + length == m_selend && m_cursor == m_selend)
++ return;
++ m_selstart = start;
++ m_selend = qMin(start + length, m_text.size());
++ m_cursor = m_selend;
++ } else if (length < 0) {
++ if (start == m_selend && start + length == m_selstart && m_cursor == m_selstart)
++ return;
++ m_selstart = qMax(start + length, 0);
++ m_selend = start;
++ m_cursor = m_selstart;
++ } else if (m_selstart != m_selend) {
++ m_selstart = 0;
++ m_selend = 0;
++ m_cursor = start;
++ } else {
++ m_cursor = start;
++ emitCursorPositionChanged();
++ return;
++ }
++ emit q->selectionChanged();
++ emitCursorPositionChanged();
++ #if QT_CONFIG(im)
++ q->updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorRectangle | Qt::ImCursorPosition | Qt::ImAnchorPosition
++ | Qt::ImCurrentSelection);
++ #endif
++ }
++
++ /*!
++ \internal
++
++ Sets the password echo editing to \a editing. If password echo editing
++ is true, then the text of the password is displayed even if the echo
++ mode is set to QLineEdit::PasswordEchoOnEdit. Password echoing editing
++ does not affect other echo modes.
++ */
++ void QQuickTextInputPrivate::updatePasswordEchoEditing(bool editing)
++ {
++ cancelPasswordEchoTimer();
++ m_passwordEchoEditing = editing;
++ updateDisplayText();
++ }
++
++ /*!
++ \internal
++
++ Fixes the current text so that it is valid given any set validators.
++
++ Returns true if the text was changed. Otherwise returns false.
++ */
++ bool QQuickTextInputPrivate::fixup() // this function assumes that validate currently returns != Acceptable
++ {
++ #if QT_CONFIG(validator)
++ if (m_validator) {
++ QString textCopy = m_text;
++ int cursorCopy = m_cursor;
++ m_validator->fixup(textCopy);
++ if (m_validator->validate(textCopy, cursorCopy) == QValidator::Acceptable) {
++ if (textCopy != m_text || cursorCopy != m_cursor)
++ internalSetText(textCopy, cursorCopy);
++ return true;
++ }
++ }
++ #endif
++ return false;
++ }
++
++ /*!
++ \internal
++
++ Moves the cursor to the given position \a pos. If \a mark is true will
++ adjust the currently selected text.
++ */
++ void QQuickTextInputPrivate::moveCursor(int pos, bool mark)
++ {
++ Q_Q(QQuickTextInput);
++ #if QT_CONFIG(im)
++ commitPreedit();
++ #endif
++
++ if (pos != m_cursor) {
++ separate();
++ if (m_maskData)
++ pos = pos > m_cursor ? nextMaskBlank(pos) : prevMaskBlank(pos);
++ }
++ if (mark) {
++ int anchor;
++ if (m_selend > m_selstart && m_cursor == m_selstart)
++ anchor = m_selend;
++ else if (m_selend > m_selstart && m_cursor == m_selend)
++ anchor = m_selstart;
++ else
++ anchor = m_cursor;
++ m_selstart = qMin(anchor, pos);
++ m_selend = qMax(anchor, pos);
++ } else {
++ internalDeselect();
++ }
++ m_cursor = pos;
++ if (mark || m_selDirty) {
++ m_selDirty = false;
++ emit q->selectionChanged();
++ }
++ emitCursorPositionChanged();
++ #if QT_CONFIG(im)
++ q->updateInputMethod();
++ #endif
++ }
++
++ #if QT_CONFIG(im)
++ /*!
++ \internal
++
++ Applies the given input method event \a event to the text of the line
++ control
++ */
++ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
++ {
++ Q_Q(QQuickTextInput);
++
++ int priorState = -1;
++ bool isGettingInput = !event->commitString().isEmpty()
++ || event->preeditString() != preeditAreaText()
++ || event->replacementLength() > 0;
++ bool cursorPositionChanged = false;
++ bool selectionChange = false;
++ m_preeditDirty = event->preeditString() != preeditAreaText();
++
++ if (isGettingInput) {
++ // If any text is being input, remove selected text.
++ priorState = m_undoState;
++ separateSelection();
++ if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) {
++ updatePasswordEchoEditing(true);
++ m_selstart = 0;
++ m_selend = m_text.size();
++ }
++ removeSelectedText();
++ }
++
++ int c = m_cursor; // cursor position after insertion of commit string
++ if (event->replacementStart() <= 0)
++ c += event->commitString().size() - qMin(-event->replacementStart(), event->replacementLength());
++
++ int cursorInsertPos = m_cursor + event->replacementStart();
++ if (cursorInsertPos < 0)
++ cursorInsertPos = 0;
++
++ // insert commit string
++ if (event->replacementLength()) {
++ m_selstart = cursorInsertPos;
++ m_selend = m_selstart + event->replacementLength();
++ m_selend = qMin(m_selend, m_text.size());
++ removeSelectedText();
++ }
++ m_cursor = cursorInsertPos;
++
++ if (!event->commitString().isEmpty()) {
++ internalInsert(event->commitString());
++ cursorPositionChanged = true;
++ } else {
++ m_cursor = qBound(0, c, m_text.size());
++ }
++
++ for (int i = 0; i < event->attributes().size(); ++i) {
++ const QInputMethodEvent::Attribute &a = event->attributes().at(i);
++ if (a.type == QInputMethodEvent::Selection) {
++ // If we already called internalInsert(), the cursor position will
++ // already be adjusted correctly. The attribute.start does
++ // not seem to take the mask into account, so it will reset the cursor
++ // to an invalid position in such case. However, when the input mask
++ // is not active, we must apply the cursor position regardless of the
++ // commit string.
++ if (!cursorPositionChanged || !m_maskData)
++ m_cursor = qBound(0, a.start + a.length, m_text.size());
++ if (a.length) {
++ m_selstart = qMax(0, qMin(a.start, m_text.size()));
++ m_selend = m_cursor;
++ if (m_selend < m_selstart) {
++ qSwap(m_selstart, m_selend);
++ }
++ selectionChange = true;
++ } else {
++ selectionChange = m_selstart != m_selend;
++ m_selstart = m_selend = 0;
++ }
++ cursorPositionChanged = true;
++ }
++ }
++ QString oldPreeditString = m_textLayout.preeditAreaText();
++ m_textLayout.setPreeditArea(m_cursor, event->preeditString());
++ if (oldPreeditString != m_textLayout.preeditAreaText()) {
++ emit q->preeditTextChanged();
++ if (!event->preeditString().isEmpty() && m_undoPreeditState == -1)
++ // Pre-edit text started. Remember state for undo purpose.
++ m_undoPreeditState = priorState;
++ }
++ const int oldPreeditCursor = m_preeditCursor;
++ m_preeditCursor = event->preeditString().size();
++ hasImState = !event->preeditString().isEmpty();
++ bool cursorVisible = true;
++ QList<QTextLayout::FormatRange> formats;
++ for (int i = 0; i < event->attributes().size(); ++i) {
++ const QInputMethodEvent::Attribute &a = event->attributes().at(i);
++ if (a.type == QInputMethodEvent::Cursor) {
++ hasImState = true;
++ m_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 + m_cursor;
++ o.length = a.length;
++ o.format = f;
++ formats.append(o);
++ }
++ }
++ }
++ m_textLayout.setFormats(formats);
++
++ // Set cursor visible state. Do this before updating the text,
++ // since user code connected to onTextChanged may set a different
++ // cursor visible state (for instance by setting the focus), which
++ // we don't want to overwrite.
++ q->setCursorVisible(cursorVisible);
++
++ updateDisplayText(/*force*/ true);
++ if (cursorPositionChanged && emitCursorPositionChanged())
++ q->updateInputMethod(Qt::ImCursorPosition | Qt::ImAnchorPosition);
++ else if (m_preeditCursor != oldPreeditCursor || isGettingInput)
++ q->updateCursorRectangle();
++
++ if (isGettingInput)
++ finishChange(priorState);
++
++ if (selectionChange) {
++ emit q->selectionChanged();
++ q->updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorRectangle
++ | Qt::ImCurrentSelection);
++ }
++
++ // Empty pre-edit text handled. Clean m_undoPreeditState
++ if (event->preeditString().isEmpty())
++ m_undoPreeditState = -1;
++
++ }
++ #endif // im
++
++ /*!
++ \internal
++
++ Sets the selection to cover the word at the given cursor position.
++ The word boundaries are defined by the behavior of QTextLayout::SkipWords
++ cursor mode.
++ */
++ void QQuickTextInputPrivate::selectWordAtPos(int cursor)
++ {
++ int next = cursor + 1;
++ if (next > end())
++ --next;
++ int c = m_textLayout.previousCursorPosition(next, QTextLayout::SkipWords);
++ moveCursor(c, false);
++ // ## text layout should support end of words.
++ int end = m_textLayout.nextCursorPosition(c, QTextLayout::SkipWords);
++ while (end > cursor && m_text.at(end - 1).isSpace())
++ --end;
++ moveCursor(end, true);
++ }
++
++ /*!
++ \internal
++
++ Completes a change to the line control text. If the change is not valid
++ will undo the line control state back to the given \a validateFromState.
++
++ If \a edited is true and the change is valid, will emit textEdited() in
++ addition to textChanged(). Otherwise only emits textChanged() on a valid
++ change.
++
++ The \a update value is currently unused.
++ */
++ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bool edited)
++ {
++ Q_Q(QQuickTextInput);
++
++ Q_UNUSED(update);
++ #if QT_CONFIG(im)
++ bool inputMethodAttributesChanged = m_textDirty || m_selDirty;
++ #endif
++ bool alignmentChanged = false;
++ bool textChanged = false;
++
++ if (m_textDirty) {
++ // do validation
++ bool wasValidInput = m_validInput;
++ bool wasAcceptable = m_acceptableInput;
++ m_validInput = true;
++ m_acceptableInput = true;
++ #if QT_CONFIG(validator)
++ if (m_validator) {
++ QString textCopy = m_text;
++ if (m_maskData)
++ textCopy = maskString(0, m_text, true);
++ int cursorCopy = m_cursor;
++ QValidator::State state = m_validator->validate(textCopy, cursorCopy);
++ if (m_maskData)
++ textCopy = m_text;
++ m_validInput = state != QValidator::Invalid;
++ m_acceptableInput = state == QValidator::Acceptable;
++ if (m_validInput && !m_maskData) {
++ if (m_text != textCopy) {
++ internalSetText(textCopy, cursorCopy);
++ return true;
++ }
++ m_cursor = cursorCopy;
++ }
++ }
++ #endif
++ if (m_maskData)
++ checkIsValid();
++
++ #if QT_CONFIG(im)
++ // If we were during pre-edit, validateFromState should point to the state before pre-edit
++ // has been started. Choose the correct oldest remembered state
++ if (m_undoPreeditState >= 0 && (m_undoPreeditState < validateFromState || validateFromState < 0))
++ validateFromState = m_undoPreeditState;
++ #endif
++ if (validateFromState >= 0 && wasValidInput && !m_validInput) {
++ if (m_transactions.size())
++ return false;
++ internalUndo(validateFromState);
++ m_history.resize(m_undoState);
++ m_validInput = true;
++ m_acceptableInput = wasAcceptable;
++ m_textDirty = false;
++ }
++
++ if (m_textDirty) {
++ textChanged = true;
++ m_textDirty = false;
++ #if QT_CONFIG(im)
++ m_preeditDirty = false;
++ #endif
++ alignmentChanged = determineHorizontalAlignment();
++ if (edited)
++ emit q->textEdited();
++ emit q->textChanged();
++ }
++
++ updateDisplayText(alignmentChanged);
++
++ if (m_acceptableInput != wasAcceptable)
++ emit q->acceptableInputChanged();
++ }
++ #if QT_CONFIG(im)
++ if (m_preeditDirty) {
++ m_preeditDirty = false;
++ if (determineHorizontalAlignment()) {
++ alignmentChanged = true;
++ updateLayout();
++ }
++ }
++ #endif
++
++ if (m_selDirty) {
++ m_selDirty = false;
++ emit q->selectionChanged();
++ }
++
++ #if QT_CONFIG(im)
++ inputMethodAttributesChanged |= (m_cursor != m_lastCursorPos);
++ if (inputMethodAttributesChanged)
++ q->updateInputMethod();
++ #endif
++ emitUndoRedoChanged();
++
++ if (!emitCursorPositionChanged() && (alignmentChanged || textChanged))
++ q->updateCursorRectangle();
++
++ return true;
++ }
++
++ /*!
++ \internal
++
++ An internal function for setting the text of the line control.
++ */
++ void QQuickTextInputPrivate::internalSetText(const QString &txt, int pos, bool edited)
++ {
++ internalDeselect();
++ QString oldText = m_text;
++ if (m_maskData) {
++ m_text = maskString(0, txt, true);
++ m_text += clearString(m_text.size(), m_maxLength - m_text.size());
++ } else {
++ m_text = txt.isEmpty() ? txt : txt.left(m_maxLength);
++ }
++ m_history.clear();
++ m_undoState = 0;
++ #if QT_CONFIG(im)
++ m_undoPreeditState = -1;
++ #endif
++ m_cursor = (pos < 0 || pos > m_text.size()) ? m_text.size() : pos;
++ m_textDirty = (oldText != m_text);
++
++ bool changed = finishChange(-1, true, edited);
++ #if !QT_CONFIG(accessibility)
++ Q_UNUSED(changed);
++ #else
++ Q_Q(QQuickTextInput);
++ if (changed && QAccessible::isActive()) {
++ if (QObject *acc = QQuickAccessibleAttached::findAccessible(q, QAccessible::EditableText)) {
++ QAccessibleTextUpdateEvent ev(acc, 0, oldText, m_text);
++ QAccessible::updateAccessibility(&ev);
++ }
++ }
++ #endif
++ }
++
++
++ /*!
++ \internal
++
++ Adds the given \a command to the undo history
++ of the line control. Does not apply the command.
++ */
++ void QQuickTextInputPrivate::addCommand(const Command &cmd)
++ {
++ if (m_separator && m_undoState && m_history[m_undoState - 1].type != Separator) {
++ m_history.resize(m_undoState + 2);
++ m_history[m_undoState++] = Command(Separator, m_cursor, u'\0', m_selstart, m_selend);
++ } else {
++ m_history.resize(m_undoState + 1);
++ }
++ m_separator = false;
++ m_history[m_undoState++] = cmd;
++ }
++
++ /*!
++ \internal
++
++ Inserts the given string \a s into the line
++ control.
++
++ Also adds the appropriate commands into the undo history.
++ This function does not call finishChange(), and may leave the text
++ in an invalid state.
++ */
++ void QQuickTextInputPrivate::internalInsert(const QString &s)
++ {
++ Q_Q(QQuickTextInput);
++ if (m_echoMode == QQuickTextInput::Password) {
++ if (m_passwordMaskDelay > 0)
++ m_passwordEchoTimer.start(m_passwordMaskDelay, q);
++ }
++ Q_ASSERT(!hasSelectedText()); // insert(), processInputMethodEvent() call removeSelectedText() first.
++ if (m_maskData) {
++ QString ms = maskString(m_cursor, s);
++ for (int i = 0; i < ms.size(); ++i) {
++ addCommand (Command(DeleteSelection, m_cursor + i, m_text.at(m_cursor + i), -1, -1));
++ addCommand(Command(Insert, m_cursor + i, ms.at(i), -1, -1));
++ }
++ m_text.replace(m_cursor, ms.size(), ms);
++ m_cursor += ms.size();
++ m_cursor = nextMaskBlank(m_cursor);
++ m_textDirty = true;
++ } else {
++ int remaining = m_maxLength - m_text.size();
++ if (remaining != 0) {
++ const QStringView remainingStr = QStringView{s}.left(remaining);
++ m_text.insert(m_cursor, remainingStr);
++ for (auto e : remainingStr)
++ addCommand(Command(Insert, m_cursor++, e, -1, -1));
++ m_textDirty = true;
++ }
++ }
++ }
++
++ /*!
++ \internal
++
++ deletes a single character from the current text. If \a wasBackspace,
++ the character prior to the cursor is removed. Otherwise the character
++ after the cursor is removed.
++
++ Also adds the appropriate commands into the undo history.
++ This function does not call finishChange(), and may leave the text
++ in an invalid state.
++ */
++ void QQuickTextInputPrivate::internalDelete(bool wasBackspace)
++ {
++ if (m_cursor < m_text.size()) {
++ cancelPasswordEchoTimer();
++ Q_ASSERT(!hasSelectedText()); // del(), backspace() call removeSelectedText() first.
++ addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)),
++ m_cursor, m_text.at(m_cursor), -1, -1));
++ if (m_maskData) {
++ m_text.replace(m_cursor, 1, clearString(m_cursor, 1));
++ addCommand(Command(Insert, m_cursor, m_text.at(m_cursor), -1, -1));
++ } else {
++ m_text.remove(m_cursor, 1);
++ }
++ m_textDirty = true;
++ }
++ }
++
++ /*!
++ \internal
++
++ removes the currently selected text from the line control.
++
++ Also adds the appropriate commands into the undo history.
++ This function does not call finishChange(), and may leave the text
++ in an invalid state.
++ */
++ void QQuickTextInputPrivate::removeSelectedText()
++ {
++ if (m_selstart < m_selend && m_selend <= m_text.size()) {
++ cancelPasswordEchoTimer();
++ int i ;
++ if (m_selstart <= m_cursor && m_cursor < m_selend) {
++ // cursor is within the selection. Split up the commands
++ // to be able to restore the correct cursor position
++ for (i = m_cursor; i >= m_selstart; --i)
++ addCommand (Command(DeleteSelection, i, m_text.at(i), -1, 1));
++ for (i = m_selend - 1; i > m_cursor; --i)
++ addCommand (Command(DeleteSelection, i - m_cursor + m_selstart - 1, m_text.at(i), -1, -1));
++ } else {
++ for (i = m_selend-1; i >= m_selstart; --i)
++ addCommand (Command(RemoveSelection, i, m_text.at(i), -1, -1));
++ }
++ if (m_maskData) {
++ m_text.replace(m_selstart, m_selend - m_selstart, clearString(m_selstart, m_selend - m_selstart));
++ for (int i = 0; i < m_selend - m_selstart; ++i)
++ addCommand(Command(Insert, m_selstart + i, m_text.at(m_selstart + i), -1, -1));
++ } else {
++ m_text.remove(m_selstart, m_selend - m_selstart);
++ }
++ if (m_cursor > m_selstart)
++ m_cursor -= qMin(m_cursor, m_selend) - m_selstart;
++ internalDeselect();
++ m_textDirty = true;
++ }
++ }
++
++ /*!
++ \internal
++
++ Adds the current selection to the undo history.
++
++ Returns true if there is a current selection and false otherwise.
++ */
++
++ bool QQuickTextInputPrivate::separateSelection()
++ {
++ if (hasSelectedText()) {
++ separate();
++ addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
++ return true;
++ } else {
++ return false;
++ }
++ }
++
++ /*!
++ \internal
++
++ Parses the input mask specified by \a maskFields to generate
++ the mask data used to handle input masks.
++ */
++ void QQuickTextInputPrivate::parseInputMask(const QString &maskFields)
++ {
++ int delimiter = maskFields.indexOf(QLatin1Char(';'));
++ if (maskFields.isEmpty() || delimiter == 0) {
++ if (m_maskData) {
++ m_maskData.reset(nullptr);
++ m_maxLength = 32767;
++ internalSetText(QString());
++ }
++ return;
++ }
++
++ if (delimiter == -1) {
++ m_blank = QLatin1Char(' ');
++ m_inputMask = maskFields;
++ } else {
++ m_inputMask = maskFields.left(delimiter);
++ m_blank = (delimiter + 1 < maskFields.size()) ? maskFields[delimiter + 1] : QLatin1Char(' ');
++ }
++
++ // calculate m_maxLength / m_maskData length
++ m_maxLength = 0;
++ QChar c = u'\0';
++ for (int i=0; i<m_inputMask.size(); i++) {
++ c = m_inputMask.at(i);
++ if (i > 0 && m_inputMask.at(i-1) == QLatin1Char('\\')) {
++ m_maxLength++;
++ continue;
++ }
++ if (c != QLatin1Char('\\') && c != QLatin1Char('!') &&
++ c != QLatin1Char('<') && c != QLatin1Char('>') &&
++ c != QLatin1Char('{') && c != QLatin1Char('}') &&
++ c != QLatin1Char('[') && c != QLatin1Char(']'))
++ m_maxLength++;
++ }
++
++ m_maskData.reset(new MaskInputData[m_maxLength]);
++
++ MaskInputData::Casemode m = MaskInputData::NoCaseMode;
++ c = u'\0';
++ bool s;
++ bool escape = false;
++ int index = 0;
++ for (int i = 0; i < m_inputMask.size(); i++) {
++ c = m_inputMask.at(i);
++ if (escape) {
++ s = true;
++ m_maskData[index].maskChar = c;
++ m_maskData[index].separator = s;
++ m_maskData[index].caseMode = m;
++ index++;
++ escape = false;
++ } else if (c == QLatin1Char('<')) {
++ m = MaskInputData::Lower;
++ } else if (c == QLatin1Char('>')) {
++ m = MaskInputData::Upper;
++ } else if (c == QLatin1Char('!')) {
++ m = MaskInputData::NoCaseMode;
++ } else if (c != QLatin1Char('{') && c != QLatin1Char('}') && c != QLatin1Char('[') && c != QLatin1Char(']')) {
++ switch (c.unicode()) {
++ case 'A':
++ case 'a':
++ case 'N':
++ case 'n':
++ case 'X':
++ case 'x':
++ case '9':
++ case '0':
++ case 'D':
++ case 'd':
++ case '#':
++ case 'H':
++ case 'h':
++ case 'B':
++ case 'b':
++ s = false;
++ break;
++ case '\\':
++ escape = true;
++ Q_FALLTHROUGH();
++ default:
++ s = true;
++ break;
++ }
++
++ if (!escape) {
++ m_maskData[index].maskChar = c;
++ m_maskData[index].separator = s;
++ m_maskData[index].caseMode = m;
++ index++;
++ }
++ }
++ }
++ internalSetText(m_text);
++ }
++
++
++ /*!
++ \internal
++
++ checks if the key is valid compared to the inputMask
++ */
++ bool QQuickTextInputPrivate::isValidInput(QChar key, QChar mask) const
++ {
++ switch (mask.unicode()) {
++ case 'A':
++ if (key.isLetter())
++ return true;
++ break;
++ case 'a':
++ if (key.isLetter() || key == m_blank)
++ return true;
++ break;
++ case 'N':
++ if (key.isLetterOrNumber())
++ return true;
++ break;
++ case 'n':
++ if (key.isLetterOrNumber() || key == m_blank)
++ return true;
++ break;
++ case 'X':
++ if (key.isPrint() && key != m_blank)
++ return true;
++ break;
++ case 'x':
++ if (key.isPrint() || key == m_blank)
++ return true;
++ break;
++ case '9':
++ if (key.isNumber())
++ return true;
++ break;
++ case '0':
++ if (key.isNumber() || key == m_blank)
++ return true;
++ break;
++ case 'D':
++ if (key.isNumber() && key.digitValue() > 0)
++ return true;
++ break;
++ case 'd':
++ if ((key.isNumber() && key.digitValue() > 0) || key == m_blank)
++ return true;
++ break;
++ case '#':
++ if (key.isNumber() || key == QLatin1Char('+') || key == QLatin1Char('-') || key == m_blank)
++ return true;
++ break;
++ case 'B':
++ if (key == QLatin1Char('0') || key == QLatin1Char('1'))
++ return true;
++ break;
++ case 'b':
++ if (key == QLatin1Char('0') || key == QLatin1Char('1') || key == m_blank)
++ return true;
++ break;
++ case 'H':
++ if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')))
++ return true;
++ break;
++ case 'h':
++ if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')) || key == m_blank)
++ return true;
++ break;
++ default:
++ break;
++ }
++ return false;
++ }
++
++ /*!
++ \internal
++
++ Returns true if the given text \a str is valid for any
++ validator or input mask set for the line control.
++
++ Otherwise returns false
++ */
++ QQuickTextInputPrivate::ValidatorState QQuickTextInputPrivate::hasAcceptableInput(const QString &str) const
++ {
++ #if QT_CONFIG(validator)
++ QString textCopy = str;
++ int cursorCopy = m_cursor;
++ if (m_validator) {
++ QValidator::State state = m_validator->validate(textCopy, cursorCopy);
++ if (state != QValidator::Acceptable)
++ return ValidatorState(state);
++ }
++ #endif
++
++ if (!m_maskData)
++ return AcceptableInput;
++
++ if (str.size() != m_maxLength)
++ return InvalidInput;
++
++ for (int i=0; i < m_maxLength; ++i) {
++ if (m_maskData[i].separator) {
++ if (str.at(i) != m_maskData[i].maskChar)
++ return InvalidInput;
++ } else {
++ if (!isValidInput(str.at(i), m_maskData[i].maskChar))
++ return InvalidInput;
++ }
++ }
++ return AcceptableInput;
++ }
++
++ /*!
++ \internal
++
++ Applies the inputMask on \a str starting from position \a pos in the mask. \a clear
++ specifies from where characters should be gotten when a separator is met in \a str - true means
++ that blanks will be used, false that previous input is used.
++ Calling this when no inputMask is set is undefined.
++ */
++ QString QQuickTextInputPrivate::maskString(uint pos, const QString &str, bool clear) const
++ {
++ if (pos >= (uint)m_maxLength)
++ return QString::fromLatin1("");
++
++ QString fill;
++ fill = clear ? clearString(0, m_maxLength) : m_text;
++
++ int strIndex = 0;
++ QString s = QString::fromLatin1("");
++ int i = pos;
++ while (i < m_maxLength) {
++ if (strIndex < str.size()) {
++ if (m_maskData[i].separator) {
++ s += m_maskData[i].maskChar;
++ if (str[strIndex] == m_maskData[i].maskChar)
++ strIndex++;
++ ++i;
++ } else {
++ if (isValidInput(str[strIndex], m_maskData[i].maskChar)) {
++ switch (m_maskData[i].caseMode) {
++ case MaskInputData::Upper:
++ s += str[strIndex].toUpper();
++ break;
++ case MaskInputData::Lower:
++ s += str[strIndex].toLower();
++ break;
++ default:
++ s += str[strIndex];
++ }
++ ++i;
++ } else {
++ // search for separator first
++ int n = findInMask(i, true, true, str[strIndex]);
++ if (n != -1) {
++ if (str.size() != 1 || i == 0 || (i > 0 && (!m_maskData[i-1].separator || m_maskData[i-1].maskChar != str[strIndex]))) {
++ s += QStringView{fill}.mid(i, n-i+1);
++ i = n + 1; // update i to find + 1
++ }
++ } else {
++ // search for valid m_blank if not
++ n = findInMask(i, true, false, str[strIndex]);
++ if (n != -1) {
++ s += QStringView{fill}.mid(i, n-i);
++ switch (m_maskData[n].caseMode) {
++ case MaskInputData::Upper:
++ s += str[strIndex].toUpper();
++ break;
++ case MaskInputData::Lower:
++ s += str[strIndex].toLower();
++ break;
++ default:
++ s += str[strIndex];
++ }
++ i = n + 1; // updates i to find + 1
++ }
++ }
++ }
++ ++strIndex;
++ }
++ } else
++ break;
++ }
++
++ return s;
++ }
++
++
++
++ /*!
++ \internal
++
++ Returns a "cleared" string with only separators and blank chars.
++ Calling this when no inputMask is set is undefined.
++ */
++ QString QQuickTextInputPrivate::clearString(uint pos, uint len) const
++ {
++ if (pos >= (uint)m_maxLength)
++ return QString();
++
++ QString s;
++ int end = qMin((uint)m_maxLength, pos + len);
++ for (int i = pos; i < end; ++i)
++ if (m_maskData[i].separator)
++ s += m_maskData[i].maskChar;
++ else
++ s += m_blank;
++
++ return s;
++ }
++
++ /*!
++ \internal
++
++ Strips blank parts of the input in a QQuickTextInputPrivate when an inputMask is set,
++ separators are still included. Typically "127.0__.0__.1__" becomes "127.0.0.1".
++ */
++ QString QQuickTextInputPrivate::stripString(const QString &str) const
++ {
++ if (!m_maskData)
++ return str;
++
++ QString s;
++ int end = qMin(m_maxLength, str.size());
++ for (int i = 0; i < end; ++i) {
++ if (m_maskData[i].separator)
++ s += m_maskData[i].maskChar;
++ else if (str[i] != m_blank)
++ s += str[i];
++ }
++
++ return s;
++ }
++
++ /*!
++ \internal
++ searches forward/backward in m_maskData for either a separator or a m_blank
++ */
++ int QQuickTextInputPrivate::findInMask(int pos, bool forward, bool findSeparator, QChar searchChar) const
++ {
++ if (pos >= m_maxLength || pos < 0)
++ return -1;
++
++ int end = forward ? m_maxLength : -1;
++ int step = forward ? 1 : -1;
++ int i = pos;
++
++ while (i != end) {
++ if (findSeparator) {
++ if (m_maskData[i].separator && m_maskData[i].maskChar == searchChar)
++ return i;
++ } else {
++ if (!m_maskData[i].separator) {
++ if (searchChar.isNull())
++ return i;
++ else if (isValidInput(searchChar, m_maskData[i].maskChar))
++ return i;
++ }
++ }
++ i += step;
++ }
++ return -1;
++ }
++
++ void QQuickTextInputPrivate::internalUndo(int until)
++ {
++ if (!isUndoAvailable())
++ return;
++ cancelPasswordEchoTimer();
++ internalDeselect();
++ while (m_undoState && m_undoState > until) {
++ Command& cmd = m_history[--m_undoState];
++ switch (cmd.type) {
++ case Insert:
++ m_text.remove(cmd.pos, 1);
++ m_cursor = cmd.pos;
++ break;
++ case SetSelection:
++ m_selstart = cmd.selStart;
++ m_selend = cmd.selEnd;
++ m_cursor = cmd.pos;
++ break;
++ case Remove:
++ case RemoveSelection:
++ m_text.insert(cmd.pos, cmd.uc);
++ m_cursor = cmd.pos + 1;
++ break;
++ case Delete:
++ case DeleteSelection:
++ m_text.insert(cmd.pos, cmd.uc);
++ m_cursor = cmd.pos;
++ break;
++ case Separator:
++ continue;
++ }
++ if (until < 0 && m_undoState) {
++ Command& next = m_history[m_undoState-1];
++ if (next.type != cmd.type
++ && next.type < RemoveSelection
++ && (cmd.type < RemoveSelection || next.type == Separator)) {
++ break;
++ }
++ }
++ }
++ separate();
++ m_textDirty = true;
++ }
++
++ void QQuickTextInputPrivate::internalRedo()
++ {
++ if (!isRedoAvailable())
++ return;
++ internalDeselect();
++ while (m_undoState < m_history.size()) {
++ Command& cmd = m_history[m_undoState++];
++ switch (cmd.type) {
++ case Insert:
++ m_text.insert(cmd.pos, cmd.uc);
++ m_cursor = cmd.pos + 1;
++ break;
++ case SetSelection:
++ m_selstart = cmd.selStart;
++ m_selend = cmd.selEnd;
++ m_cursor = cmd.pos;
++ break;
++ case Remove:
++ case Delete:
++ case RemoveSelection:
++ case DeleteSelection:
++ m_text.remove(cmd.pos, 1);
++ m_selstart = cmd.selStart;
++ m_selend = cmd.selEnd;
++ m_cursor = cmd.pos;
++ break;
++ case Separator:
++ m_selstart = cmd.selStart;
++ m_selend = cmd.selEnd;
++ m_cursor = cmd.pos;
++ break;
++ }
++ if (m_undoState < m_history.size()) {
++ Command& next = m_history[m_undoState];
++ if (next.type != cmd.type
++ && cmd.type < RemoveSelection
++ && next.type != Separator
++ && (next.type < RemoveSelection || cmd.type == Separator)) {
++ break;
++ }
++ }
++ }
++ m_textDirty = true;
++ }
++
++ void QQuickTextInputPrivate::emitUndoRedoChanged()
++ {
++ Q_Q(QQuickTextInput);
++ const bool previousUndo = canUndo;
++ const bool previousRedo = canRedo;
++
++ canUndo = isUndoAvailable();
++ canRedo = isRedoAvailable();
++
++ if (previousUndo != canUndo)
++ emit q->canUndoChanged();
++ if (previousRedo != canRedo)
++ emit q->canRedoChanged();
++ }
++
++ /*!
++ \internal
++
++ If the current cursor position differs from the last emitted cursor
++ position, emits cursorPositionChanged().
++ */
++ bool QQuickTextInputPrivate::emitCursorPositionChanged()
++ {
++ Q_Q(QQuickTextInput);
++ if (m_cursor != m_lastCursorPos) {
++ m_lastCursorPos = m_cursor;
++
++ q->updateCursorRectangle();
++ emit q->cursorPositionChanged();
++
++ if (!hasSelectedText()) {
++ if (lastSelectionStart != m_cursor) {
++ lastSelectionStart = m_cursor;
++ emit q->selectionStartChanged();
++ }
++ if (lastSelectionEnd != m_cursor) {
++ lastSelectionEnd = m_cursor;
++ emit q->selectionEndChanged();
++ }
++ }
++
++ #if QT_CONFIG(accessibility)
++ if (QAccessible::isActive()) {
++ if (QObject *acc = QQuickAccessibleAttached::findAccessible(q, QAccessible::EditableText)) {
++ QAccessibleTextCursorEvent ev(acc, m_cursor);
++ QAccessible::updateAccessibility(&ev);
++ }
++ }
++ #endif
++
++ return true;
++ }
++ return false;
++ }
++
++
++ void QQuickTextInputPrivate::setBlinkingCursorEnabled(bool enable)
++ {
++ if (enable == m_blinkEnabled)
++ return;
++
++ m_blinkEnabled = enable;
++ updateCursorBlinking();
++
++ if (enable)
++ connect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextInputPrivate::updateCursorBlinking);
++ else
++ disconnect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextInputPrivate::updateCursorBlinking);
++ }
++
++ void QQuickTextInputPrivate::updateCursorBlinking()
++ {
++ Q_Q(QQuickTextInput);
++
++ if (m_blinkTimer) {
++ q->killTimer(m_blinkTimer);
++ m_blinkTimer = 0;
++ }
++
++ if (m_blinkEnabled && cursorVisible && !cursorItem && !m_readOnly) {
++ int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
++ if (flashTime >= 2)
++ m_blinkTimer = q->startTimer(flashTime / 2);
++ }
++
++ m_blinkStatus = 1;
++ updateType = UpdatePaintNode;
++ q->polish();
++ q->update();
++ }
++
++ void QQuickTextInput::timerEvent(QTimerEvent *event)
++ {
++ Q_D(QQuickTextInput);
++ if (event->timerId() == d->m_blinkTimer) {
++ d->m_blinkStatus = !d->m_blinkStatus;
++ d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
++ polish();
++ update();
++ } else if (event->timerId() == d->m_passwordEchoTimer.timerId()) {
++ d->m_passwordEchoTimer.stop();
++ d->updateDisplayText();
++ updateCursorRectangle();
++ }
++ }
++
++ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
++ {
++ Q_Q(QQuickTextInput);
++
++ if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
++ if (hasAcceptableInput(m_text) == AcceptableInput || fixup()) {
++
++ QInputMethod *inputMethod = QGuiApplication::inputMethod();
++ inputMethod->commit();
++
++ if (activeFocus) {
++ // If we lost focus after hiding the virtual keyboard, we've already emitted
++ // editingFinished from handleFocusEvent. Otherwise we emit it now.
++ emit q->editingFinished();
++ }
++
++ emit q->accepted();
++ }
++ event->ignore();
++ return;
++ }
++
++ if (m_blinkEnabled)
++ updateCursorBlinking();
++
++ if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit
++ && !m_passwordEchoEditing
++ && !m_readOnly
++ && !event->text().isEmpty()
++ && !(event->modifiers() & Qt::ControlModifier)) {
++ // Clear the edit and reset to normal echo mode while editing; the
++ // echo mode switches back when the edit loses focus
++ // ### resets current content. dubious code; you can
++ // navigate with keys up, down, back, and select(?), but if you press
++ // "left" or "right" it clears?
++ updatePasswordEchoEditing(true);
++ clear();
++ }
++
++ bool unknown = false;
++ #if QT_CONFIG(shortcut)
++ bool visual = cursorMoveStyle() == Qt::VisualMoveStyle;
++ #endif
++
++ if (false) {
++ }
++ #if QT_CONFIG(shortcut)
++ else if (event == QKeySequence::Undo) {
++ q->undo();
++ }
++ else if (event == QKeySequence::Redo) {
++ q->redo();
++ }
++ else if (event == QKeySequence::SelectAll) {
++ selectAll();
++ }
++ #if QT_CONFIG(clipboard)
++ else if (event == QKeySequence::Copy) {
++ copy();
++ }
++ else if (event == QKeySequence::Paste) {
++ if (!m_readOnly) {
++ QClipboard::Mode mode = QClipboard::Clipboard;
++ paste(mode);
++ }
++ }
++ else if (event == QKeySequence::Cut) {
++ q->cut();
++ }
++ else if (event == QKeySequence::DeleteEndOfLine) {
++ if (!m_readOnly)
++ deleteEndOfLine();
++ }
++ #endif // clipboard
++ else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) {
++ home(0);
++ }
++ else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) {
++ end(0);
++ }
++ else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) {
++ home(1);
++ }
++ else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) {
++ end(1);
++ }
++ else if (event == QKeySequence::MoveToNextChar) {
++ if (hasSelectedText()) {
++ moveCursor(selectionEnd(), false);
++ } else {
++ cursorForward(0, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
++ }
++ }
++ else if (event == QKeySequence::SelectNextChar) {
++ cursorForward(1, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
++ }
++ else if (event == QKeySequence::MoveToPreviousChar) {
++ if (hasSelectedText()) {
++ moveCursor(selectionStart(), false);
++ } else {
++ cursorForward(0, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
++ }
++ }
++ else if (event == QKeySequence::SelectPreviousChar) {
++ cursorForward(1, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
++ }
++ else if (event == QKeySequence::MoveToNextWord) {
++ if (m_echoMode == QQuickTextInput::Normal)
++ layoutDirection() == Qt::LeftToRight ? cursorWordForward(0) : cursorWordBackward(0);
++ else
++ layoutDirection() == Qt::LeftToRight ? end(0) : home(0);
++ }
++ else if (event == QKeySequence::MoveToPreviousWord) {
++ if (m_echoMode == QQuickTextInput::Normal)
++ layoutDirection() == Qt::LeftToRight ? cursorWordBackward(0) : cursorWordForward(0);
++ else if (!m_readOnly) {
++ layoutDirection() == Qt::LeftToRight ? home(0) : end(0);
++ }
++ }
++ else if (event == QKeySequence::SelectNextWord) {
++ if (m_echoMode == QQuickTextInput::Normal)
++ layoutDirection() == Qt::LeftToRight ? cursorWordForward(1) : cursorWordBackward(1);
++ else
++ layoutDirection() == Qt::LeftToRight ? end(1) : home(1);
++ }
++ else if (event == QKeySequence::SelectPreviousWord) {
++ if (m_echoMode == QQuickTextInput::Normal)
++ layoutDirection() == Qt::LeftToRight ? cursorWordBackward(1) : cursorWordForward(1);
++ else
++ layoutDirection() == Qt::LeftToRight ? home(1) : end(1);
++ }
++ else if (event == QKeySequence::Delete) {
++ if (!m_readOnly)
++ del();
++ }
++ else if (event == QKeySequence::DeleteEndOfWord) {
++ if (!m_readOnly)
++ deleteEndOfWord();
++ }
++ else if (event == QKeySequence::DeleteStartOfWord) {
++ if (!m_readOnly)
++ deleteStartOfWord();
++ } else if (event == QKeySequence::DeleteCompleteLine) {
++ if (!m_readOnly) {
++ selectAll();
++ #if QT_CONFIG(clipboard)
++ copy();
++ #endif
++ del();
++ }
++ }
++ #endif // shortcut
++ else {
++ bool handled = false;
++ if (event->modifiers() & Qt::ControlModifier) {
++ switch (event->key()) {
++ case Qt::Key_Backspace:
++ if (!m_readOnly)
++ deleteStartOfWord();
++ break;
++ default:
++ if (!handled)
++ unknown = true;
++ }
++ } else { // ### check for *no* modifier
++ switch (event->key()) {
++ case Qt::Key_Backspace:
++ if (!m_readOnly) {
++ backspace();
++ }
++ break;
++ default:
++ if (!handled)
++ unknown = true;
++ }
++ }
++ }
++
++ if (event->key() == Qt::Key_Direction_L || event->key() == Qt::Key_Direction_R) {
++ setLayoutDirection((event->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
++ unknown = false;
++ }
++
++ if (unknown && !m_readOnly) {
++ if (m_inputControl->isAcceptableInput(event)) {
++ if (overwriteMode
++ // no need to call del() if we have a selection, insert
++ // does it already
++ && !hasSelectedText()
++ && !(m_cursor == q_func()->text().size())) {
++ del();
++ }
++
++ insert(event->text());
++ event->accept();
++ return;
++ }
++ }
++
++ if (unknown)
++ event->ignore();
++ else
++ event->accept();
++ }
++
++ /*!
++ \internal
++
++ Deletes the portion of the word before the current cursor position.
++ */
++
++ void QQuickTextInputPrivate::deleteStartOfWord()
++ {
++ int priorState = m_undoState;
++
++ if (!separateSelection()) {
++ Command cmd(SetSelection, m_cursor, u'\0', m_selstart, m_selend);
++ separate();
++ cursorWordBackward(true);
++ addCommand(cmd);
++ }
++
++ removeSelectedText();
++ finishChange(priorState);
++ }
++
++ /*!
++ \internal
++
++ Deletes the portion of the word after the current cursor position.
++ */
++
++ void QQuickTextInputPrivate::deleteEndOfWord()
++ {
++ int priorState = m_undoState;
++ Command cmd(SetSelection, m_cursor, u'\0', m_selstart, m_selend);
++ separate();
++ cursorWordForward(true);
++ // moveCursor (sometimes) calls separate() so we need to add the command after that so the
++ // cursor position and selection are restored in the same undo operation as the remove.
++ addCommand(cmd);
++ removeSelectedText();
++ finishChange(priorState);
++ }
++
++ /*!
++ \internal
++
++ Deletes all text from the cursor position to the end of the line.
++ */
++
++ void QQuickTextInputPrivate::deleteEndOfLine()
++ {
++ int priorState = m_undoState;
++ Command cmd(SetSelection, m_cursor, u'\0', m_selstart, m_selend);
++ separate();
++ setSelection(m_cursor, end());
++ addCommand(cmd);
++ removeSelectedText();
++ finishChange(priorState);
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::ensureVisible(int position)
++ \since 5.4
++
++ Scrolls the contents of the text input so that the specified character
++ \a position is visible inside the boundaries of the text input.
++
++ \sa autoScroll
++ */
++ void QQuickTextInput::ensureVisible(int position)
++ {
++ Q_D(QQuickTextInput);
++ d->ensureVisible(position);
++ updateCursorRectangle(false);
++ }
++
++ /*!
++ \qmlmethod void QtQuick::TextInput::clear()
++ \since 5.7
++
++ Clears the contents of the text input
++ and resets partial text input from an input method.
++
++ Use this method instead of setting the \l text property to an empty string.
++
++ \sa QInputMethod::reset()
++ */
++ void QQuickTextInput::clear()
++ {
++ Q_D(QQuickTextInput);
++ d->cancelInput();
++ d->clear();
++ }
++
++ /*!
++ \since 5.6
++ \qmlproperty real QtQuick::TextInput::padding
++ \qmlproperty real QtQuick::TextInput::topPadding
++ \qmlproperty real QtQuick::TextInput::leftPadding
++ \qmlproperty real QtQuick::TextInput::bottomPadding
++ \qmlproperty real QtQuick::TextInput::rightPadding
++
++ These properties hold the padding around the content. This space is reserved
++ in addition to the contentWidth and contentHeight.
++
++ The individual padding properties assume the value of the \c padding
++ property unless they are set explicitly. For example, if \c padding is
++ set to \c 4 and \c leftPadding to \c 8, \c 8 will be used as the left
++ padding.
++
++ \note If an explicit width or height is given to a TextInput, care must be
++ taken to ensure it is large enough to accommodate the relevant padding
++ values. For example: if \c topPadding and \c bottomPadding are set to
++ \c 10, but the height of the TextInput is only set to \c 20, the text will
++ not have enough vertical space in which to be rendered, and will appear
++ clipped.
++ */
++ qreal QQuickTextInput::padding() const
++ {
++ Q_D(const QQuickTextInput);
++ return d->padding();
++ }
++
++ void QQuickTextInput::setPadding(qreal padding)
++ {
++ Q_D(QQuickTextInput);
++ if (qFuzzyCompare(d->padding(), padding))
++ return;
++
++ d->extra.value().padding = padding;
++ d->updateLayout();
++ updateCursorRectangle();
++ emit paddingChanged();
++ if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
++ emit topPaddingChanged();
++ if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
++ emit leftPaddingChanged();
++ if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
++ emit rightPaddingChanged();
++ if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
++ emit bottomPaddingChanged();
++ }
++
++ void QQuickTextInput::resetPadding()
++ {
++ setPadding(0);
++ }
++
++ qreal QQuickTextInput::topPadding() const
++ {
++ Q_D(const QQuickTextInput);
++ if (d->extra.isAllocated() && d->extra->explicitTopPadding)
++ return d->extra->topPadding;
++ return d->padding();
++ }
++
++ void QQuickTextInput::setTopPadding(qreal padding)
++ {
++ Q_D(QQuickTextInput);
++ d->setTopPadding(padding);
++ }
++
++ void QQuickTextInput::resetTopPadding()
++ {
++ Q_D(QQuickTextInput);
++ d->setTopPadding(0, true);
++ }
++
++ qreal QQuickTextInput::leftPadding() const
++ {
++ Q_D(const QQuickTextInput);
++ if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
++ return d->extra->leftPadding;
++ return d->padding();
++ }
++
++ void QQuickTextInput::setLeftPadding(qreal padding)
++ {
++ Q_D(QQuickTextInput);
++ d->setLeftPadding(padding);
++ }
++
++ void QQuickTextInput::resetLeftPadding()
++ {
++ Q_D(QQuickTextInput);
++ d->setLeftPadding(0, true);
++ }
++
++ qreal QQuickTextInput::rightPadding() const
++ {
++ Q_D(const QQuickTextInput);
++ if (d->extra.isAllocated() && d->extra->explicitRightPadding)
++ return d->extra->rightPadding;
++ return d->padding();
++ }
++
++ void QQuickTextInput::setRightPadding(qreal padding)
++ {
++ Q_D(QQuickTextInput);
++ d->setRightPadding(padding);
++ }
++
++ void QQuickTextInput::resetRightPadding()
++ {
++ Q_D(QQuickTextInput);
++ d->setRightPadding(0, true);
++ }
++
++ qreal QQuickTextInput::bottomPadding() const
++ {
++ Q_D(const QQuickTextInput);
++ if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
++ return d->extra->bottomPadding;
++ return d->padding();
++ }
++
++ void QQuickTextInput::setBottomPadding(qreal padding)
++ {
++ Q_D(QQuickTextInput);
++ d->setBottomPadding(padding);
++ }
++
++ void QQuickTextInput::resetBottomPadding()
++ {
++ Q_D(QQuickTextInput);
++ d->setBottomPadding(0, true);
++ }
++
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ void QQuickTextInput::setOldSelectionDefault()
++ {
++ Q_D(QQuickTextInput);
++ d->selectByMouse = false;
++ d->selectByTouchDrag = true;
++ qCDebug(lcQuickTextInput, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
++ }
++
++ // TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
++ QQuickPre64TextInput::QQuickPre64TextInput(QQuickItem *parent)
++ : QQuickTextInput(parent)
++ {
++ setOldSelectionDefault();
++ }
++ #endif
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquicktextinput_p.cpp"