1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquicktextinput_p.h"
5#include "qquicktextinput_p_p.h"
6#include "qquickwindow.h"
7
8#include <private/qqmlglobal_p.h>
9#include <private/qv4scopedvalue_p.h>
10
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qmimedata.h>
13#include <QtQml/qqmlinfo.h>
14#include <QtGui/qevent.h>
15#include <QTextBoundaryFinder>
16#include "qsginternaltextnode_p.h"
17#include <QtQuick/qsgsimplerectnode.h>
18
19#include <QtGui/qstylehints.h>
20#include <QtGui/qinputmethod.h>
21#include <QtCore/qmath.h>
22
23#if QT_CONFIG(accessibility)
24#include "qaccessible.h"
25#include "qquickaccessibleattached_p.h"
26#endif
27
28#include <QtGui/private/qtextengine_p.h>
29#include <QtGui/private/qinputcontrol_p.h>
30
31QT_BEGIN_NAMESPACE
32
33DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
34Q_STATIC_LOGGING_CATEGORY(lcQuickTextInput, "qt.quick.textInput")
35
36/*!
37 \qmltype TextInput
38 \nativetype QQuickTextInput
39 \inqmlmodule QtQuick
40 \ingroup qtquick-visual
41 \ingroup qtquick-input
42 \inherits Item
43 \brief Displays an editable line of text.
44
45 The TextInput type displays a single line of editable plain text.
46
47 TextInput is used to accept a line of text input. Input constraints
48 can be placed on a TextInput item (for example, through a \l validator or \l inputMask),
49 and setting \l echoMode to an appropriate value enables TextInput to be used for
50 a password input field.
51
52 To react to the user accepting text via the Return or Enter keys, handle
53 the \l accepted() signal. When Return or Enter are pressed and the text
54 input loses focus, \l editingFinished() will be emitted. When the text is
55 edited in any way by the user, \l textEdited() is emitted. These signals
56 \l {Property change signals}{should be preferred} over \c textChanged()
57 in most cases.
58
59 On \macos, the Up/Down key bindings for Home/End are explicitly disabled.
60 If you want such bindings (on any platform), you will need to construct them in QML.
61
62 \sa TextEdit, Text
63*/
64QQuickTextInput::QQuickTextInput(QQuickItem* parent)
65: QQuickImplicitSizeItem(*(new QQuickTextInputPrivate), parent)
66{
67 Q_D(QQuickTextInput);
68 d->init();
69}
70
71QQuickTextInput::QQuickTextInput(QQuickTextInputPrivate &dd, QQuickItem *parent)
72: QQuickImplicitSizeItem(dd, parent)
73{
74 Q_D(QQuickTextInput);
75 d->init();
76}
77
78QQuickTextInput::~QQuickTextInput()
79{
80}
81
82void QQuickTextInput::componentComplete()
83{
84 Q_D(QQuickTextInput);
85
86 QQuickImplicitSizeItem::componentComplete();
87
88 d->checkIsValid();
89 d->updateLayout();
90 updateCursorRectangle();
91 if (d->cursorComponent && isCursorVisible())
92 QQuickTextUtil::createCursor(d);
93}
94
95/*!
96 \qmlproperty string QtQuick::TextInput::text
97
98 The text in the TextInput.
99
100 Note that some keyboards use a predictive function. In this case,
101 the text being composed by the input method is not part of this property.
102 The part of the text related to the predictions is underlined and stored in
103 the \l preeditText property. To get whole text displayed in the TextInput
104 use \l displayText property.
105
106 \sa clear(), displayText, preeditText, accepted(), editingFinished(),
107 textEdited()
108*/
109QString QQuickTextInput::text() const
110{
111 Q_D(const QQuickTextInput);
112
113 QString content = d->m_text;
114 QString res = d->m_maskData ? d->stripString(str: content) : content;
115 return (res.isNull() ? QString::fromLatin1(ba: "") : res);
116}
117
118void QQuickTextInput::invalidate()
119{
120 Q_D(QQuickTextInput);
121 d->updateLayout();
122 invalidateFontCaches();
123}
124
125void QQuickTextInput::setText(const QString &s)
126{
127 Q_D(QQuickTextInput);
128 if (s == text())
129 return;
130
131#if QT_CONFIG(im)
132 d->cancelPreedit();
133#endif
134 d->internalSetText(txt: s, pos: -1, edited: false);
135}
136
137
138/*!
139 \qmlproperty enumeration QtQuick::TextInput::renderType
140
141 Override the default rendering type for this component.
142
143 Supported render types are:
144
145 \value TextInput.QtRendering Text is rendered using a scalable distance field for each glyph.
146 \value TextInput.NativeRendering Text is rendered using a platform-specific technique.
147 \value TextInput.CurveRendering Text is rendered using a curve rasterizer running directly on
148 the graphics hardware. (Introduced in Qt 6.7.0.)
149
150 Select \c TextInput.NativeRendering if you prefer text to look native on the target platform and do
151 not require advanced features such as transformation of the text. Using such features in
152 combination with the NativeRendering render type will lend poor and sometimes pixelated
153 results.
154
155 Both \c TextInput.QtRendering and \c TextInput.CurveRendering are hardware-accelerated techniques.
156 \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
157 artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
158 where \c QtRendering does not give good visual results or where reducing graphics memory
159 consumption is a priority.
160
161 The default rendering type is determined by \l QQuickWindow::textRenderType().
162*/
163QQuickTextInput::RenderType QQuickTextInput::renderType() const
164{
165 Q_D(const QQuickTextInput);
166 return d->renderType;
167}
168
169void QQuickTextInput::setRenderType(QQuickTextInput::RenderType renderType)
170{
171 Q_D(QQuickTextInput);
172 if (d->renderType == renderType)
173 return;
174
175 d->renderType = renderType;
176 emit renderTypeChanged();
177
178 if (isComponentComplete())
179 d->updateLayout();
180}
181
182/*!
183 \qmlproperty int QtQuick::TextInput::length
184
185 Returns the total number of characters in the TextInput item.
186
187 If the TextInput has an inputMask the length will include mask characters and may differ
188 from the length of the string returned by the \l text property.
189
190 This property can be faster than querying the length the \l text property as it doesn't
191 require any copying or conversion of the TextInput's internal string data.
192*/
193
194int QQuickTextInput::length() const
195{
196 Q_D(const QQuickTextInput);
197 return d->m_text.size();
198}
199
200/*!
201 \qmlmethod string QtQuick::TextInput::getText(int start, int end)
202
203 Returns the section of text that is between the \a start and \a end positions.
204
205 If the TextInput has an inputMask the length will include mask characters.
206*/
207
208QString QQuickTextInput::getText(int start, int end) const
209{
210 Q_D(const QQuickTextInput);
211
212 if (start > end)
213 qSwap(value1&: start, value2&: end);
214
215 return d->m_text.mid(position: start, n: end - start);
216}
217
218QString QQuickTextInputPrivate::realText() const
219{
220 QString res = m_maskData ? stripString(str: m_text) : m_text;
221 return (res.isNull() ? QString::fromLatin1(ba: "") : res);
222}
223
224/*!
225 \qmlproperty string QtQuick::TextInput::font.family
226
227 Sets the family name of the font.
228
229 The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
230 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
231 If the family isn't available a family will be set using the font matching algorithm.
232*/
233
234/*!
235 \qmlproperty string QtQuick::TextInput::font.styleName
236 \since 5.6
237
238 Sets the style name of the font.
239
240 The style name is case insensitive. If set, the font will be matched against style name instead
241 of the font properties \l font.weight, \l font.bold and \l font.italic.
242*/
243
244/*!
245 \qmlproperty bool QtQuick::TextInput::font.bold
246
247 Sets whether the font weight is bold.
248*/
249
250/*!
251 \qmlproperty int QtQuick::TextInput::font.weight
252
253 The requested weight of the font. The weight requested must be an integer
254 between 1 and 1000, or one of the predefined values:
255
256 \value Font.Thin 100
257 \value Font.ExtraLight 200
258 \value Font.Light 300
259 \value Font.Normal 400 (default)
260 \value Font.Medium 500
261 \value Font.DemiBold 600
262 \value Font.Bold 700
263 \value Font.ExtraBold 800
264 \value Font.Black 900
265
266 \qml
267 TextInput { text: "Hello"; font.weight: Font.DemiBold }
268 \endqml
269*/
270
271/*!
272 \qmlproperty bool QtQuick::TextInput::font.italic
273
274 Sets whether the font has an italic style.
275*/
276
277/*!
278 \qmlproperty bool QtQuick::TextInput::font.underline
279
280 Sets whether the text is underlined.
281*/
282
283/*!
284 \qmlproperty bool QtQuick::TextInput::font.strikeout
285
286 Sets whether the font has a strikeout style.
287*/
288
289/*!
290 \qmlproperty real QtQuick::TextInput::font.pointSize
291
292 Sets the font size in points. The point size must be greater than zero.
293*/
294
295/*!
296 \qmlproperty int QtQuick::TextInput::font.pixelSize
297
298 Sets the font size in pixels.
299
300 Using this function makes the font device dependent.
301 Use \c pointSize to set the size of the font in a device independent manner.
302*/
303
304/*!
305 \qmlproperty real QtQuick::TextInput::font.letterSpacing
306
307 Sets the letter spacing for the font.
308
309 Letter spacing changes the default spacing between individual letters in the font.
310 A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
311*/
312
313/*!
314 \qmlproperty real QtQuick::TextInput::font.wordSpacing
315
316 Sets the word spacing for the font.
317
318 Word spacing changes the default spacing between individual words.
319 A positive value increases the word spacing by a corresponding amount of pixels,
320 while a negative value decreases the inter-word spacing accordingly.
321*/
322
323/*!
324 \qmlproperty enumeration QtQuick::TextInput::font.capitalization
325
326 Sets the capitalization for the text.
327
328 \value Font.MixedCase the normal case: no capitalization change is applied
329 \value Font.AllUppercase alters the text to be rendered in all uppercase type
330 \value Font.AllLowercase alters the text to be rendered in all lowercase type
331 \value Font.SmallCaps alters the text to be rendered in small-caps type
332 \value Font.Capitalize alters the text to be rendered with the first character of
333 each word as an uppercase character
334
335 \qml
336 TextInput { text: "Hello"; font.capitalization: Font.AllLowercase }
337 \endqml
338*/
339
340/*!
341 \qmlproperty enumeration QtQuick::TextInput::font.hintingPreference
342 \since 5.8
343
344 Sets the preferred hinting on the text. This is a hint to the underlying text rendering system
345 to use a certain level of hinting, and has varying support across platforms. See the table in
346 the documentation for QFont::HintingPreference for more details.
347
348 \note This property only has an effect when used together with render type TextInput.NativeRendering.
349
350 \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
351 \value Font.PreferNoHinting If possible, render text without hinting the outlines
352 of the glyphs. The text layout will be typographically accurate, using the same metrics
353 as are used e.g. when printing.
354 \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
355 but align glyphs to the pixel grid in the vertical direction. The text will appear
356 crisper on displays where the density is too low to give an accurate rendering
357 of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
358 layout will be scalable to higher density devices (such as printers) without impacting
359 details such as line breaks.
360 \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
361 vertical directions. The text will be altered to optimize legibility on the target
362 device, but since the metrics will depend on the target size of the text, the positions
363 of glyphs, line breaks, and other typographical detail will not scale, meaning that a
364 text layout may look different on devices with different pixel densities.
365
366 \qml
367 TextInput { text: "Hello"; renderType: TextInput.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
368 \endqml
369*/
370
371/*!
372 \qmlproperty bool QtQuick::TextInput::font.kerning
373 \since 5.10
374
375 Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
376 improve performance when creating or changing the text, at the expense of some cosmetic
377 features. The default value is true.
378
379 \qml
380 TextInput { text: "OATS FLAVOUR WAY"; font.kerning: false }
381 \endqml
382*/
383
384/*!
385 \qmlproperty bool QtQuick::TextInput::font.preferShaping
386 \since 5.10
387
388 Sometimes, a font will apply complex rules to a set of characters in order to
389 display them correctly. In some writing systems, such as Brahmic scripts, this is
390 required in order for the text to be legible, but in e.g. Latin script, it is merely
391 a cosmetic feature. Setting the \c preferShaping property to false will disable all
392 such features when they are not required, which will improve performance in most cases.
393
394 The default value is true.
395
396 \qml
397 TextInput { text: "Some text"; font.preferShaping: false }
398 \endqml
399*/
400
401/*!
402 \qmlproperty object QtQuick::TextInput::font.variableAxes
403 \since 6.7
404
405 \include qquicktext.cpp qml-font-variable-axes
406*/
407
408/*!
409 \qmlproperty object QtQuick::TextInput::font.features
410 \since 6.6
411
412 \include qquicktext.cpp qml-font-features
413*/
414
415/*!
416 \qmlproperty bool QtQuick::TextInput::font.contextFontMerging
417 \since 6.8
418
419 \include qquicktext.cpp qml-font-context-font-merging
420*/
421
422/*!
423 \qmlproperty bool QtQuick::TextInput::font.preferTypoLineMetrics
424 \since 6.8
425
426 \include qquicktext.cpp qml-font-prefer-typo-line-metrics
427*/
428QFont QQuickTextInput::font() const
429{
430 Q_D(const QQuickTextInput);
431 return d->sourceFont;
432}
433
434void QQuickTextInput::setFont(const QFont &font)
435{
436 Q_D(QQuickTextInput);
437 if (d->sourceFont == font)
438 return;
439
440 d->sourceFont = font;
441 QFont oldFont = d->font;
442 d->font = font;
443 if (d->font.pointSizeF() != -1) {
444 // 0.5pt resolution
445 qreal size = qRound(d: d->font.pointSizeF()*2.0);
446 d->font.setPointSizeF(size/2.0);
447 }
448 if (oldFont != d->font) {
449 d->updateLayout();
450 updateCursorRectangle();
451#if QT_CONFIG(im)
452 updateInputMethod(queries: Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle);
453#endif
454 }
455 emit fontChanged(font: d->sourceFont);
456}
457
458/*!
459 \qmlproperty color QtQuick::TextInput::color
460
461 The text color.
462*/
463QColor QQuickTextInput::color() const
464{
465 Q_D(const QQuickTextInput);
466 return d->color;
467}
468
469void QQuickTextInput::setColor(const QColor &c)
470{
471 Q_D(QQuickTextInput);
472 if (c != d->color) {
473 d->color = c;
474 d->textLayoutDirty = true;
475 d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
476 polish();
477 update();
478 emit colorChanged();
479 }
480}
481
482
483/*!
484 \qmlproperty color QtQuick::TextInput::selectionColor
485
486 The text highlight color, used behind selections.
487*/
488QColor QQuickTextInput::selectionColor() const
489{
490 Q_D(const QQuickTextInput);
491 return d->selectionColor;
492}
493
494void QQuickTextInput::setSelectionColor(const QColor &color)
495{
496 Q_D(QQuickTextInput);
497 if (d->selectionColor == color)
498 return;
499
500 d->selectionColor = color;
501 if (d->hasSelectedText()) {
502 d->textLayoutDirty = true;
503 d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
504 polish();
505 update();
506 }
507 emit selectionColorChanged();
508}
509/*!
510 \qmlproperty color QtQuick::TextInput::selectedTextColor
511
512 The highlighted text color, used in selections.
513*/
514QColor QQuickTextInput::selectedTextColor() const
515{
516 Q_D(const QQuickTextInput);
517 return d->selectedTextColor;
518}
519
520void QQuickTextInput::setSelectedTextColor(const QColor &color)
521{
522 Q_D(QQuickTextInput);
523 if (d->selectedTextColor == color)
524 return;
525
526 d->selectedTextColor = color;
527 if (d->hasSelectedText()) {
528 d->textLayoutDirty = true;
529 d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
530 polish();
531 update();
532 }
533 emit selectedTextColorChanged();
534}
535
536/*!
537 \qmlproperty enumeration QtQuick::TextInput::effectiveHorizontalAlignment
538 \readonly
539
540 When using the attached property LayoutMirroring::enabled to mirror application
541 layouts, the horizontal alignment of text will also be mirrored. However, the property
542 \l horizontalAlignment will remain unchanged. To query the effective horizontal alignment
543 of TextInput, use the read-only property \c effectiveHorizontalAlignment.
544*/
545/*!
546 \qmlproperty enumeration QtQuick::TextInput::horizontalAlignment
547 \qmlproperty enumeration QtQuick::TextInput::verticalAlignment
548
549 Sets the horizontal alignment of the text within the TextInput item's
550 width and height. By default, the text alignment follows the natural alignment
551 of the text, for example text that is read from left to right will be aligned to
552 the left.
553
554 TextInput does not have vertical alignment, as the natural height is
555 exactly the height of the single line of text. If you set the height
556 manually to something larger, TextInput will always be top aligned
557 vertically. You can use anchors to align it however you want within
558 another item.
559
560 The valid values for \c horizontalAlignment are \c TextInput.AlignLeft, \c TextInput.AlignRight and
561 \c TextInput.AlignHCenter.
562
563 Valid values for \c verticalAlignment are \c TextInput.AlignTop (default),
564 \c TextInput.AlignBottom \c TextInput.AlignVCenter.
565
566 When using the attached property LayoutMirroring::enabled to mirror application
567 layouts, the horizontal alignment of text will also be mirrored. However, the property
568 \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
569 of TextInput, use the read-only property \l effectiveHorizontalAlignment.
570*/
571QQuickTextInput::HAlignment QQuickTextInput::hAlign() const
572{
573 Q_D(const QQuickTextInput);
574 return d->hAlign;
575}
576
577void QQuickTextInput::setHAlign(HAlignment align)
578{
579 Q_D(QQuickTextInput);
580
581 if (d->setHAlign(align, forceAlign: true) && isComponentComplete()) {
582 d->updateLayout();
583 updateCursorRectangle();
584 }
585}
586
587void QQuickTextInput::resetHAlign()
588{
589 Q_D(QQuickTextInput);
590 d->hAlignImplicit = true;
591 if (d->determineHorizontalAlignment() && isComponentComplete()) {
592 d->updateLayout();
593 updateCursorRectangle();
594 }
595}
596
597QQuickTextInput::HAlignment QQuickTextInput::effectiveHAlign() const
598{
599 Q_D(const QQuickTextInput);
600 QQuickTextInput::HAlignment effectiveAlignment = d->hAlign;
601 if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
602 switch (d->hAlign) {
603 case QQuickTextInput::AlignLeft:
604 effectiveAlignment = QQuickTextInput::AlignRight;
605 break;
606 case QQuickTextInput::AlignRight:
607 effectiveAlignment = QQuickTextInput::AlignLeft;
608 break;
609 default:
610 break;
611 }
612 }
613 return effectiveAlignment;
614}
615
616bool QQuickTextInputPrivate::setHAlign(QQuickTextInput::HAlignment align, bool forceAlign)
617{
618 Q_Q(QQuickTextInput);
619 if (align > QQuickTextInput::AlignHCenter)
620 return false; // justify is not supported
621
622 if (hAlign == align && !forceAlign)
623 return false;
624
625 const bool wasImplicit = hAlignImplicit;
626 const auto oldEffectiveHAlign = q->effectiveHAlign();
627
628 hAlignImplicit = !forceAlign;
629 if (hAlign != align) {
630 hAlign = align;
631 emit q->horizontalAlignmentChanged(alignment: align);
632 }
633
634 if (q->effectiveHAlign() != oldEffectiveHAlign) {
635 emit q->effectiveHorizontalAlignmentChanged();
636 return true;
637 }
638
639 if (forceAlign && wasImplicit) {
640 // QTBUG-120052 - when horizontal text alignment is set explicitly,
641 // we need notify any other controls that may depend on it, like QQuickPlaceholderText
642 emit q->effectiveHorizontalAlignmentChanged();
643 }
644 return false;
645}
646
647Qt::LayoutDirection QQuickTextInputPrivate::textDirection() const
648{
649 QString text = m_text;
650#if QT_CONFIG(im)
651 if (text.isEmpty())
652 text = m_textLayout.preeditAreaText();
653#endif
654
655 const QChar *character = text.constData();
656 while (!character->isNull()) {
657 switch (character->direction()) {
658 case QChar::DirL:
659 return Qt::LeftToRight;
660 case QChar::DirR:
661 case QChar::DirAL:
662 case QChar::DirAN:
663 return Qt::RightToLeft;
664 default:
665 break;
666 }
667 character++;
668 }
669 return Qt::LayoutDirectionAuto;
670}
671
672Qt::LayoutDirection QQuickTextInputPrivate::layoutDirection() const
673{
674 Qt::LayoutDirection direction = m_layoutDirection;
675 if (direction == Qt::LayoutDirectionAuto) {
676 direction = textDirection();
677#if QT_CONFIG(im)
678 if (direction == Qt::LayoutDirectionAuto)
679 direction = QGuiApplication::inputMethod()->inputDirection();
680#endif
681 }
682 return (direction == Qt::LayoutDirectionAuto) ? Qt::LeftToRight : direction;
683}
684
685bool QQuickTextInputPrivate::determineHorizontalAlignment()
686{
687 if (!hAlignImplicit)
688 return false;
689
690 // if no explicit alignment has been set, follow the natural layout direction of the text
691 Qt::LayoutDirection direction = textDirection();
692#if QT_CONFIG(im)
693 if (direction == Qt::LayoutDirectionAuto)
694 direction = QGuiApplication::inputMethod()->inputDirection();
695#endif
696
697 const auto implicitHAlign = direction == Qt::RightToLeft ?
698 QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft;
699 return setHAlign(align: implicitHAlign);
700}
701
702QQuickTextInput::VAlignment QQuickTextInput::vAlign() const
703{
704 Q_D(const QQuickTextInput);
705 return d->vAlign;
706}
707
708void QQuickTextInput::setVAlign(QQuickTextInput::VAlignment alignment)
709{
710 Q_D(QQuickTextInput);
711 if (alignment == d->vAlign)
712 return;
713 d->vAlign = alignment;
714 emit verticalAlignmentChanged(alignment: d->vAlign);
715 if (isComponentComplete()) {
716 updateCursorRectangle();
717 d->updateBaselineOffset();
718 }
719}
720
721/*!
722 \qmlproperty enumeration QtQuick::TextInput::wrapMode
723
724 Set this property to wrap the text to the TextInput item's width.
725 The text will only wrap if an explicit width has been set.
726
727 \value TextInput.NoWrap
728 (default) no wrapping will be performed. If the text contains
729 insufficient newlines, then \l contentWidth will exceed a set width.
730 \value TextInput.WordWrap
731 wrapping is done on word boundaries only. If a word is too long,
732 \l contentWidth will exceed a set width.
733 \value TextInput.WrapAnywhere
734 wrapping is done at any point on a line, even if it occurs in the middle of a word.
735 \value TextInput.Wrap
736 if possible, wrapping occurs at a word boundary; otherwise it will occur
737 at the appropriate point on the line, even in the middle of a word.
738
739 The default is TextInput.NoWrap. If you set a width, consider using TextInput.Wrap.
740*/
741QQuickTextInput::WrapMode QQuickTextInput::wrapMode() const
742{
743 Q_D(const QQuickTextInput);
744 return d->wrapMode;
745}
746
747void QQuickTextInput::setWrapMode(WrapMode mode)
748{
749 Q_D(QQuickTextInput);
750 if (mode == d->wrapMode)
751 return;
752 d->wrapMode = mode;
753 d->updateLayout();
754 updateCursorRectangle();
755 emit wrapModeChanged();
756}
757
758void QQuickTextInputPrivate::mirrorChange()
759{
760 Q_Q(QQuickTextInput);
761 if (q->isComponentComplete()) {
762 if (!hAlignImplicit && (hAlign == QQuickTextInput::AlignRight || hAlign == QQuickTextInput::AlignLeft)) {
763 q->updateCursorRectangle();
764 emit q->effectiveHorizontalAlignmentChanged();
765 }
766 }
767}
768
769/*!
770 \qmlproperty bool QtQuick::TextInput::readOnly
771
772 Sets whether user input can modify the contents of the TextInput.
773
774 If readOnly is set to true, then user input will not affect the text
775 property. Any bindings or attempts to set the text property will still
776 work.
777*/
778bool QQuickTextInput::isReadOnly() const
779{
780 Q_D(const QQuickTextInput);
781 return d->m_readOnly;
782}
783
784void QQuickTextInput::setReadOnly(bool ro)
785{
786 Q_D(QQuickTextInput);
787 if (d->m_readOnly == ro)
788 return;
789
790#if QT_CONFIG(im)
791 setFlag(flag: QQuickItem::ItemAcceptsInputMethod, enabled: !ro);
792#endif
793 d->m_readOnly = ro;
794 d->setCursorPosition(d->end());
795#if QT_CONFIG(im)
796 updateInputMethod(queries: Qt::ImEnabled);
797#endif
798 q_canPasteChanged();
799 d->emitUndoRedoChanged();
800 emit readOnlyChanged(isReadOnly: ro);
801 if (ro) {
802 setCursorVisible(false);
803 } else if (hasActiveFocus()) {
804 setCursorVisible(true);
805 }
806 update();
807}
808
809/*!
810 \qmlproperty int QtQuick::TextInput::maximumLength
811 The maximum permitted length of the text in the TextInput.
812
813 If the text is too long, it is truncated at the limit.
814
815 By default, this property contains a value of 32767.
816*/
817int QQuickTextInput::maxLength() const
818{
819 Q_D(const QQuickTextInput);
820 return d->m_maxLength;
821}
822
823void QQuickTextInput::setMaxLength(int ml)
824{
825 Q_D(QQuickTextInput);
826 if (d->m_maxLength == ml || d->m_maskData)
827 return;
828
829 d->m_maxLength = ml;
830 d->internalSetText(txt: d->m_text, pos: -1, edited: false);
831
832 emit maximumLengthChanged(maximumLength: ml);
833}
834
835/*!
836 \qmlproperty bool QtQuick::TextInput::cursorVisible
837 Set to true when the TextInput shows a cursor.
838
839 This property is set and unset when the TextInput gets active focus, so that other
840 properties can be bound to whether the cursor is currently showing. As it
841 gets set and unset automatically, when you set the value yourself you must
842 keep in mind that your value may be overwritten.
843
844 It can be set directly in script, for example if a KeyProxy might
845 forward keys to it and you desire it to look active when this happens
846 (but without actually giving it active focus).
847
848 It should not be set directly on the item, like in the below QML,
849 as the specified value will be overridden and lost on focus changes.
850
851 \code
852 TextInput {
853 text: "Text"
854 cursorVisible: false
855 }
856 \endcode
857
858 In the above snippet the cursor will still become visible when the
859 TextInput gains active focus.
860*/
861bool QQuickTextInput::isCursorVisible() const
862{
863 Q_D(const QQuickTextInput);
864 return d->cursorVisible;
865}
866
867void QQuickTextInput::setCursorVisible(bool on)
868{
869 Q_D(QQuickTextInput);
870 if (d->cursorVisible == on)
871 return;
872 d->cursorVisible = on;
873 if (on && isComponentComplete())
874 QQuickTextUtil::createCursor(d);
875 if (!d->cursorItem)
876 d->updateCursorBlinking();
877 emit cursorVisibleChanged(isCursorVisible: d->cursorVisible);
878}
879
880/*!
881 \qmlproperty int QtQuick::TextInput::cursorPosition
882 The position of the cursor in the TextInput. The cursor is positioned between
883 characters.
884
885 \note The \e characters in this case refer to the string of \l QChar objects,
886 therefore 16-bit Unicode characters, and the position is considered an index
887 into this string. This does not necessarily correspond to individual graphemes
888 in the writing system, as a single grapheme may be represented by multiple
889 Unicode characters, such as in the case of surrogate pairs, linguistic
890 ligatures or diacritics.
891
892 \l displayText is different if echoMode is set to \c {TextInput.Password}:
893 then each passwordCharacter is a "narrow" character (the cursorPosition always
894 moves by 1), even if the text in the TextInput is not.
895*/
896int QQuickTextInput::cursorPosition() const
897{
898 Q_D(const QQuickTextInput);
899 return d->m_cursor;
900}
901
902void QQuickTextInput::setCursorPosition(int cp)
903{
904 Q_D(QQuickTextInput);
905 if (cp < 0 || cp > text().size())
906 return;
907 d->moveCursor(pos: cp);
908}
909
910/*!
911 \qmlproperty rectangle QtQuick::TextInput::cursorRectangle
912 \readonly
913
914 The rectangle where the standard text cursor is rendered within the text input. Read only.
915
916 The position and height of a custom cursorDelegate are updated to follow the cursorRectangle
917 automatically when it changes. The width of the delegate is unaffected by changes in the
918 cursor rectangle.
919*/
920
921QRectF QQuickTextInput::cursorRectangle() const
922{
923 Q_D(const QQuickTextInput);
924
925 int c = d->m_cursor;
926#if QT_CONFIG(im)
927 c += d->m_preeditCursor;
928#endif
929 if (d->m_echoMode == NoEcho)
930 c = 0;
931 QTextLine l = d->m_textLayout.lineForTextPosition(pos: c);
932 if (!l.isValid())
933 return QRectF();
934 qreal x = l.cursorToX(cursorPos: c) - d->hscroll + leftPadding();
935 qreal y = l.y() - d->vscroll + topPadding();
936 qreal w = 1;
937 if (d->overwriteMode) {
938 if (c < text().size())
939 w = l.cursorToX(cursorPos: c + 1) - x;
940 else
941 w = QFontMetrics(font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
942 }
943 return QRectF(x, y, w, l.height());
944}
945
946/*!
947 \qmlproperty int QtQuick::TextInput::selectionStart
948
949 The cursor position before the first character in the current selection.
950
951 This property is read-only. To change the selection, use select(start,end),
952 selectAll(), or selectWord().
953
954 \readonly
955 \sa selectionEnd, cursorPosition, selectedText
956*/
957int QQuickTextInput::selectionStart() const
958{
959 Q_D(const QQuickTextInput);
960 return d->lastSelectionStart;
961}
962/*!
963 \qmlproperty int QtQuick::TextInput::selectionEnd
964
965 The cursor position after the last character in the current selection.
966
967 This property is read-only. To change the selection, use select(start,end),
968 selectAll(), or selectWord().
969
970 \readonly
971 \sa selectionStart, cursorPosition, selectedText
972*/
973int QQuickTextInput::selectionEnd() const
974{
975 Q_D(const QQuickTextInput);
976 return d->lastSelectionEnd;
977}
978/*!
979 \qmlmethod QtQuick::TextInput::select(int start, int end)
980
981 Causes the text from \a start to \a end to be selected.
982
983 If either start or end is out of range, the selection is not changed.
984
985 After calling this, selectionStart will become the lesser
986 and selectionEnd will become the greater (regardless of the order passed
987 to this method).
988
989 \sa selectionStart, selectionEnd
990*/
991void QQuickTextInput::select(int start, int end)
992{
993 Q_D(QQuickTextInput);
994 if (start < 0 || end < 0 || start > d->m_text.size() || end > d->m_text.size())
995 return;
996 d->setSelection(start, length: end-start);
997}
998
999/*!
1000 \qmlproperty string QtQuick::TextInput::selectedText
1001 \readonly
1002
1003 This read-only property provides the text currently selected in the
1004 text input.
1005
1006 It is equivalent to the following snippet, but is faster and easier
1007 to use.
1008
1009 \qml
1010 myTextInput.text.toString().substring(myTextInput.selectionStart,
1011 myTextInput.selectionEnd);
1012 \endqml
1013*/
1014QString QQuickTextInput::selectedText() const
1015{
1016 Q_D(const QQuickTextInput);
1017 return d->selectedText();
1018}
1019
1020/*!
1021 \qmlproperty bool QtQuick::TextInput::activeFocusOnPress
1022
1023 Whether the TextInput should gain active focus on a mouse press. By default this is
1024 set to true.
1025*/
1026bool QQuickTextInput::focusOnPress() const
1027{
1028 Q_D(const QQuickTextInput);
1029 return d->focusOnPress;
1030}
1031
1032void QQuickTextInput::setFocusOnPress(bool b)
1033{
1034 Q_D(QQuickTextInput);
1035 if (d->focusOnPress == b)
1036 return;
1037
1038 d->focusOnPress = b;
1039
1040 emit activeFocusOnPressChanged(activeFocusOnPress: d->focusOnPress);
1041}
1042/*!
1043 \qmlproperty bool QtQuick::TextInput::autoScroll
1044
1045 Whether the TextInput should scroll when the text is longer than the width. By default this is
1046 set to true.
1047
1048 \sa ensureVisible()
1049*/
1050bool QQuickTextInput::autoScroll() const
1051{
1052 Q_D(const QQuickTextInput);
1053 return d->autoScroll;
1054}
1055
1056void QQuickTextInput::setAutoScroll(bool b)
1057{
1058 Q_D(QQuickTextInput);
1059 if (d->autoScroll == b)
1060 return;
1061
1062 d->autoScroll = b;
1063 //We need to repaint so that the scrolling is taking into account.
1064 updateCursorRectangle();
1065 emit autoScrollChanged(autoScroll: d->autoScroll);
1066}
1067
1068#if QT_CONFIG(validator)
1069/*!
1070 \qmlproperty Validator QtQuick::TextInput::validator
1071
1072 Allows you to set a validator on the TextInput. When a validator is set
1073 the TextInput will only accept input which leaves the text property in
1074 an acceptable or intermediate state. The accepted signal will only be sent
1075 if the text is in an acceptable state when enter is pressed.
1076
1077 Currently supported validators are IntValidator, DoubleValidator
1078 and RegularExpressionValidator. An example of using validators is shown
1079 below, which allows input of integers between 11 and 31 into the
1080 text input:
1081
1082 \code
1083 import QtQuick 2.0
1084 TextInput{
1085 validator: IntValidator{bottom: 11; top: 31;}
1086 focus: true
1087 }
1088 \endcode
1089
1090 \sa acceptableInput, inputMask
1091*/
1092
1093QValidator* QQuickTextInput::validator() const
1094{
1095 Q_D(const QQuickTextInput);
1096 return d->m_validator;
1097}
1098
1099void QQuickTextInput::setValidator(QValidator* v)
1100{
1101 Q_D(QQuickTextInput);
1102 if (d->m_validator == v)
1103 return;
1104
1105 if (d->m_validator) {
1106 qmlobject_disconnect(
1107 d->m_validator, QValidator, SIGNAL(changed()),
1108 this, QQuickTextInput, SLOT(q_validatorChanged()));
1109 }
1110
1111 d->m_validator = v;
1112
1113 if (d->m_validator) {
1114 qmlobject_connect(
1115 d->m_validator, QValidator, SIGNAL(changed()),
1116 this, QQuickTextInput, SLOT(q_validatorChanged()));
1117 }
1118
1119 if (isComponentComplete())
1120 d->checkIsValid();
1121
1122 emit validatorChanged();
1123}
1124
1125void QQuickTextInput::q_validatorChanged()
1126{
1127 Q_D(QQuickTextInput);
1128 d->checkIsValid();
1129}
1130#endif // validator
1131
1132QRectF QQuickTextInputPrivate::anchorRectangle() const
1133{
1134 Q_Q(const QQuickTextInput);
1135 QRectF rect;
1136 int a;
1137 // Unfortunately we cannot use selectionStart() and selectionEnd()
1138 // since they always assume that the selectionStart is logically before selectionEnd.
1139 // To rely on that would cause havoc if the user was interactively moving the end selection
1140 // handle to become before the start selection
1141 if (m_selstart == m_selend)
1142 // This is to handle the case when there is "no selection" while moving the handle onto the
1143 // same position as the other handle (in which case it would hide the selection handles)
1144 a = m_cursor;
1145 else
1146 a = m_selstart == m_cursor ? m_selend : m_selstart;
1147 if (a >= 0) {
1148#if QT_CONFIG(im)
1149 a += m_preeditCursor;
1150#endif
1151 if (m_echoMode == QQuickTextInput::NoEcho)
1152 a = 0;
1153 QTextLine l = m_textLayout.lineForTextPosition(pos: a);
1154 if (l.isValid()) {
1155 qreal x = l.cursorToX(cursorPos: a) - hscroll + q->leftPadding();
1156 qreal y = l.y() - vscroll + q->topPadding();
1157 rect.setRect(ax: x, ay: y, aaw: 1, aah: l.height());
1158 }
1159 }
1160 return rect;
1161}
1162
1163void QQuickTextInputPrivate::checkIsValid()
1164{
1165 Q_Q(QQuickTextInput);
1166
1167 ValidatorState state = hasAcceptableInput(text: m_text);
1168 if (!m_maskData)
1169 m_validInput = state != InvalidInput;
1170 if (state != AcceptableInput) {
1171 if (m_acceptableInput) {
1172 m_acceptableInput = false;
1173 emit q->acceptableInputChanged();
1174 }
1175 } else if (!m_acceptableInput) {
1176 m_acceptableInput = true;
1177 emit q->acceptableInputChanged();
1178 }
1179}
1180
1181/*!
1182 \qmlproperty string QtQuick::TextInput::inputMask
1183
1184 Allows you to set an input mask on the TextInput, restricting the allowable
1185 text inputs. See QLineEdit::inputMask for further details, as the exact
1186 same mask strings are used by TextInput.
1187
1188 \sa acceptableInput, validator
1189*/
1190QString QQuickTextInput::inputMask() const
1191{
1192 Q_D(const QQuickTextInput);
1193 return d->inputMask();
1194}
1195
1196void QQuickTextInput::setInputMask(const QString &im)
1197{
1198 Q_D(QQuickTextInput);
1199 QString canonicalInputMask = im;
1200 if (im.lastIndexOf(c: QLatin1Char(';')) == -1)
1201 canonicalInputMask.append(s: QLatin1String("; "));
1202 if (d->inputMask() == canonicalInputMask)
1203 return;
1204
1205 d->setInputMask(im);
1206 emit inputMaskChanged(inputMask: d->inputMask());
1207}
1208
1209/*!
1210 \qmlproperty bool QtQuick::TextInput::acceptableInput
1211 \readonly
1212
1213 This property is always true unless a validator or input mask has been set.
1214 If a validator or input mask has been set, this property will only be true
1215 if the current text is acceptable to the validator or input mask as a final
1216 string (not as an intermediate string).
1217*/
1218bool QQuickTextInput::hasAcceptableInput() const
1219{
1220 Q_D(const QQuickTextInput);
1221 return d->m_acceptableInput;
1222}
1223
1224/*!
1225 \qmlsignal QtQuick::TextInput::accepted()
1226
1227 This signal is emitted when the Return or Enter key is pressed.
1228 Note that if there is a \l validator or \l inputMask set on the text
1229 input, the signal will only be emitted if the input is in an acceptable
1230 state.
1231
1232 \sa editingFinished(), textEdited()
1233*/
1234
1235/*!
1236 \qmlsignal QtQuick::TextInput::editingFinished()
1237 \since 5.2
1238
1239 This signal is emitted when the Return or Enter key is pressed or
1240 the text input loses focus. Note that if there is a validator or
1241 inputMask set on the text input and enter/return is pressed, this
1242 signal will only be emitted if the input follows
1243 the inputMask and the validator returns an acceptable state.
1244
1245 \sa accepted(), textEdited()
1246*/
1247
1248/*!
1249 \qmlsignal QtQuick::TextInput::textEdited()
1250 \since 5.9
1251
1252 This signal is emitted whenever the text is edited. Unlike \c textChanged(),
1253 this signal is not emitted when the text is changed programmatically, for example,
1254 by changing the value of the \c text property or by calling \c clear().
1255
1256 \sa accepted(), editingFinished()
1257*/
1258
1259#if QT_CONFIG(im)
1260Qt::InputMethodHints QQuickTextInputPrivate::effectiveInputMethodHints() const
1261{
1262 Qt::InputMethodHints hints = inputMethodHints;
1263 if (m_echoMode == QQuickTextInput::Password || m_echoMode == QQuickTextInput::NoEcho)
1264 hints |= Qt::ImhHiddenText;
1265 else if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit)
1266 hints &= ~Qt::ImhHiddenText;
1267 if (m_echoMode != QQuickTextInput::Normal)
1268 hints |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
1269 return hints;
1270}
1271#endif
1272
1273/*!
1274 \qmlproperty enumeration QtQuick::TextInput::echoMode
1275
1276 Specifies how the text should be displayed in the TextInput.
1277
1278 \value TextInput.Normal Displays the text as it is. (Default)
1279 \value TextInput.Password Displays the \l passwordCharacter instead of the actual characters.
1280 While editing, newly entered characters are displayed in clear text
1281 for a short period specified by the \l passwordMaskDelay property.
1282 \value TextInput.NoEcho Displays nothing.
1283 \value TextInput.PasswordEchoOnEdit Content is masked as with \c TextInput.Password. During
1284 editing, newly entered characters are displayed in clear text as
1285 long as the TextInput has active focus.
1286*/
1287QQuickTextInput::EchoMode QQuickTextInput::echoMode() const
1288{
1289 Q_D(const QQuickTextInput);
1290 return QQuickTextInput::EchoMode(d->m_echoMode);
1291}
1292
1293void QQuickTextInput::setEchoMode(QQuickTextInput::EchoMode echo)
1294{
1295 Q_D(QQuickTextInput);
1296 if (echoMode() == echo)
1297 return;
1298 d->cancelPasswordEchoTimer();
1299 d->m_echoMode = echo;
1300 d->m_passwordEchoEditing = false;
1301#if QT_CONFIG(im)
1302 updateInputMethod(queries: Qt::ImHints);
1303#endif
1304 d->updateDisplayText();
1305 updateCursorRectangle();
1306
1307 // If this control is used for password input, we want to minimize
1308 // the possibility of string reallocation not to leak (parts of)
1309 // the password.
1310 if (d->m_echoMode != QQuickTextInput::Normal)
1311 d->m_text.reserve(asize: 30);
1312
1313 emit echoModeChanged(echoMode: echoMode());
1314}
1315
1316/*!
1317 \qmlproperty enumeration QtQuick::TextInput::inputMethodHints
1318
1319 Provides hints to the input method about the expected content of the text input and how it
1320 should operate.
1321
1322 The value is a bit-wise combination of flags, or Qt.ImhNone if no hints are set.
1323
1324 Flags that alter behaviour are:
1325
1326 \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords.
1327 \value Qt.ImhSensitiveData Typed text should not be stored by the active input method
1328 in any persistent storage like predictive user dictionary.
1329 \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to
1330 upper case when a sentence ends.
1331 \value Qt.ImhPreferNumbers Numbers are preferred (but not required).
1332 \value Qt.ImhPreferUppercase Upper case letters are preferred (but not required).
1333 \value Qt.ImhPreferLowercase Lower case letters are preferred (but not required).
1334 \value Qt.ImhNoPredictiveText Do not use predictive text (i.e. dictionary lookup) while typing.
1335 \value Qt.ImhDate The text editor functions as a date field.
1336 \value Qt.ImhTime The text editor functions as a time field.
1337
1338 Flags that restrict input (exclusive flags) are:
1339
1340 \value Qt.ImhDigitsOnly Only digits are allowed.
1341 \value Qt.ImhFormattedNumbersOnly Only number input is allowed. This includes decimal point and minus sign.
1342 \value Qt.ImhUppercaseOnly Only upper case letter input is allowed.
1343 \value Qt.ImhLowercaseOnly Only lower case letter input is allowed.
1344 \value Qt.ImhDialableCharactersOnly Only characters suitable for phone dialing are allowed.
1345 \value Qt.ImhEmailCharactersOnly Only characters suitable for email addresses are allowed.
1346 \value Qt.ImhUrlCharactersOnly Only characters suitable for URLs are allowed.
1347
1348 Masks:
1349
1350 \value Qt.ImhExclusiveInputMask This mask yields nonzero if any of the exclusive flags are used.
1351*/
1352
1353Qt::InputMethodHints QQuickTextInput::inputMethodHints() const
1354{
1355#if !QT_CONFIG(im)
1356 return Qt::ImhNone;
1357#else
1358 Q_D(const QQuickTextInput);
1359 return d->inputMethodHints;
1360#endif // im
1361}
1362
1363void QQuickTextInput::setInputMethodHints(Qt::InputMethodHints hints)
1364{
1365#if !QT_CONFIG(im)
1366 Q_UNUSED(hints);
1367#else
1368 Q_D(QQuickTextInput);
1369
1370 if (hints == d->inputMethodHints)
1371 return;
1372
1373 d->inputMethodHints = hints;
1374 updateInputMethod(queries: Qt::ImHints);
1375 emit inputMethodHintsChanged();
1376#endif // im
1377}
1378
1379/*!
1380 \qmlproperty Component QtQuick::TextInput::cursorDelegate
1381 The delegate for the cursor in the TextInput.
1382
1383 If you set a cursorDelegate for a TextInput, this delegate will be used for
1384 drawing the cursor instead of the standard cursor. An instance of the
1385 delegate will be created and managed by the TextInput when a cursor is
1386 needed, and the x property of the delegate instance will be set so as
1387 to be one pixel before the top left of the current character.
1388
1389 Note that the root item of the delegate component must be a QQuickItem or
1390 QQuickItem derived item.
1391*/
1392QQmlComponent* QQuickTextInput::cursorDelegate() const
1393{
1394 Q_D(const QQuickTextInput);
1395 return d->cursorComponent;
1396}
1397
1398void QQuickTextInput::setCursorDelegate(QQmlComponent* c)
1399{
1400 Q_D(QQuickTextInput);
1401 QQuickTextUtil::setCursorDelegate(d, delegate: c);
1402}
1403
1404void QQuickTextInput::createCursor()
1405{
1406 Q_D(QQuickTextInput);
1407 d->cursorPending = true;
1408 QQuickTextUtil::createCursor(d);
1409}
1410
1411/*!
1412 \qmlmethod rect QtQuick::TextInput::positionToRectangle(int pos)
1413
1414 This function takes a character position \a pos and returns the rectangle
1415 that the cursor would occupy, if it was placed at that character position.
1416
1417 This is similar to setting the cursorPosition, and then querying the cursor
1418 rectangle, but the cursorPosition is not changed.
1419*/
1420QRectF QQuickTextInput::positionToRectangle(int pos) const
1421{
1422 Q_D(const QQuickTextInput);
1423 if (d->m_echoMode == NoEcho)
1424 pos = 0;
1425#if QT_CONFIG(im)
1426 else if (pos > d->m_cursor)
1427 pos += d->preeditAreaText().size();
1428#endif
1429 QTextLine l = d->m_textLayout.lineForTextPosition(pos);
1430 if (!l.isValid())
1431 return QRectF();
1432 qreal x = l.cursorToX(cursorPos: pos) - d->hscroll;
1433 qreal y = l.y() - d->vscroll;
1434 qreal w = 1;
1435 if (d->overwriteMode) {
1436 if (pos < text().size())
1437 w = l.cursorToX(cursorPos: pos + 1) - x;
1438 else
1439 w = QFontMetrics(font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
1440 }
1441 return QRectF(x, y, w, l.height());
1442}
1443
1444/*!
1445 \qmlmethod int QtQuick::TextInput::positionAt(real x, real y, CursorPosition position)
1446
1447 This function returns the character position at
1448 \a x and \a y pixels from the top left of the textInput. Position 0 is before the
1449 first character, position 1 is after the first character but before the second,
1450 and so on until position text.length, which is after all characters.
1451
1452 This means that for all x values before the first character this function returns 0,
1453 and for all x values after the last character this function returns text.length. If
1454 the y value is above the text the position will be that of the nearest character on
1455 the first line and if it is below the text the position of the nearest character
1456 on the last line will be returned.
1457
1458 The cursor \a position parameter specifies how the cursor position should be resolved:
1459
1460 \value TextInput.CursorBetweenCharacters
1461 Returns the position between characters that is nearest x.
1462 This is the default value.
1463 \value TextInput.CursorOnCharacter
1464 Returns the position before the character that is nearest x.
1465*/
1466
1467void QQuickTextInput::positionAt(QQmlV4FunctionPtr args) const
1468{
1469 Q_D(const QQuickTextInput);
1470
1471 qreal x = 0;
1472 qreal y = 0;
1473 QTextLine::CursorPosition position = QTextLine::CursorBetweenCharacters;
1474
1475 if (args->length() < 1)
1476 return;
1477
1478 int i = 0;
1479 QV4::Scope scope(args->v4engine());
1480 QV4::ScopedValue arg(scope, (*args)[0]);
1481 x = arg->toNumber();
1482
1483 if (++i < args->length()) {
1484 arg = (*args)[i];
1485 y = arg->toNumber();
1486 }
1487
1488 if (++i < args->length()) {
1489 arg = (*args)[i];
1490 position = QTextLine::CursorPosition(arg->toInt32());
1491 }
1492
1493 int pos = d->positionAt(x, y, position);
1494 const int cursor = d->m_cursor;
1495 if (pos > cursor) {
1496#if QT_CONFIG(im)
1497 const int preeditLength = d->preeditAreaText().size();
1498 pos = pos > cursor + preeditLength
1499 ? pos - preeditLength
1500 : cursor;
1501#else
1502 pos = cursor;
1503#endif
1504 }
1505 args->setReturnValue(QV4::Encode(pos));
1506}
1507
1508int QQuickTextInputPrivate::positionAt(qreal x, qreal y, QTextLine::CursorPosition position) const
1509{
1510 Q_Q(const QQuickTextInput);
1511 x += hscroll - q->leftPadding();
1512 y += vscroll - q->topPadding();
1513 QTextLine line = m_textLayout.lineAt(i: 0);
1514 for (int i = 1; i < m_textLayout.lineCount(); ++i) {
1515 QTextLine nextLine = m_textLayout.lineAt(i);
1516
1517 if (y < (line.rect().bottom() + nextLine.y()) / 2)
1518 break;
1519 line = nextLine;
1520 }
1521 return line.isValid() ? line.xToCursor(x, position) : 0;
1522}
1523
1524/*!
1525 \qmlproperty bool QtQuick::TextInput::overwriteMode
1526 \since 5.8
1527
1528 Whether text entered by the user will overwrite existing text.
1529
1530 As with many text editors, the text editor widget can be configured
1531 to insert or overwrite existing text with new text entered by the user.
1532
1533 If this property is \c true, existing text is overwritten, character-for-character
1534 by new text; otherwise, text is inserted at the cursor position, displacing
1535 existing text.
1536
1537 By default, this property is \c false (new text does not overwrite existing text).
1538*/
1539bool QQuickTextInput::overwriteMode() const
1540{
1541 Q_D(const QQuickTextInput);
1542 return d->overwriteMode;
1543}
1544
1545void QQuickTextInput::setOverwriteMode(bool overwrite)
1546{
1547 Q_D(QQuickTextInput);
1548 if (d->overwriteMode == overwrite)
1549 return;
1550 d->overwriteMode = overwrite;
1551 emit overwriteModeChanged(overwriteMode: overwrite);
1552}
1553
1554void QQuickTextInput::keyPressEvent(QKeyEvent* ev)
1555{
1556 Q_D(QQuickTextInput);
1557 // Don't allow MacOSX up/down support, and we don't allow a completer.
1558 bool ignore = (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) && ev->modifiers() == Qt::NoModifier;
1559 if (!ignore && (d->lastSelectionStart == d->lastSelectionEnd) && (ev->key() == Qt::Key_Right || ev->key() == Qt::Key_Left)) {
1560 // Ignore when moving off the end unless there is a selection,
1561 // because then moving will do something (deselect).
1562 int cursorPosition = d->m_cursor;
1563 if (cursorPosition == 0)
1564 ignore = ev->key() == (d->layoutDirection() == Qt::LeftToRight ? Qt::Key_Left : Qt::Key_Right);
1565 if (!ignore && cursorPosition == d->m_text.size())
1566 ignore = ev->key() == (d->layoutDirection() == Qt::LeftToRight ? Qt::Key_Right : Qt::Key_Left);
1567 }
1568 if (ignore) {
1569 ev->ignore();
1570 } else {
1571 d->processKeyEvent(ev);
1572 }
1573 if (!ev->isAccepted())
1574 QQuickImplicitSizeItem::keyPressEvent(event: ev);
1575}
1576
1577#if QT_CONFIG(im)
1578void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev)
1579{
1580 Q_D(QQuickTextInput);
1581 const bool wasComposing = d->hasImState;
1582 d->processInputMethodEvent(event: ev);
1583 if (!ev->isAccepted())
1584 QQuickImplicitSizeItem::inputMethodEvent(ev);
1585
1586 if (wasComposing != d->hasImState)
1587 emit inputMethodComposingChanged();
1588}
1589#endif
1590
1591void QQuickTextInput::mouseDoubleClickEvent(QMouseEvent *event)
1592{
1593 Q_D(QQuickTextInput);
1594
1595 if (d->selectByMouse && event->button() == Qt::LeftButton &&
1596 QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: event)) {
1597#if QT_CONFIG(im)
1598 d->commitPreedit();
1599#endif
1600 int cursor = d->positionAt(point: event->position());
1601 d->selectWordAtPos(cursor);
1602 event->setAccepted(true);
1603 if (!d->hasPendingTripleClick()) {
1604 d->tripleClickStartPoint = event->position();
1605 d->tripleClickTimer.start();
1606 }
1607 } else {
1608 if (d->sendMouseEventToInputContext(event))
1609 return;
1610 QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
1611 }
1612}
1613
1614void QQuickTextInput::mousePressEvent(QMouseEvent *event)
1615{
1616 Q_D(QQuickTextInput);
1617
1618 d->pressPos = event->position();
1619
1620 if (d->sendMouseEventToInputContext(event))
1621 return;
1622
1623 d->hadSelectionOnMousePress = d->hasSelectedText();
1624
1625 const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: event);
1626 if (d->selectByMouse &&
1627 (isMouse
1628#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
1629 || d->selectByTouchDrag
1630#endif
1631 )) {
1632 setKeepMouseGrab(false);
1633 d->selectPressed = true;
1634 QPointF distanceVector = d->pressPos - d->tripleClickStartPoint;
1635 if (d->hasPendingTripleClick()
1636 && distanceVector.manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) {
1637 event->setAccepted(true);
1638 selectAll();
1639 return;
1640 }
1641 }
1642
1643 if (isMouse
1644#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
1645 || d->selectByTouchDrag
1646#endif
1647 ) {
1648 bool mark = (event->modifiers() & Qt::ShiftModifier) && d->selectByMouse;
1649 int cursor = d->positionAt(point: event->position());
1650 d->moveCursor(pos: cursor, mark);
1651 }
1652
1653 if (d->focusOnPress && !qGuiApp->styleHints()->setFocusOnTouchRelease())
1654 ensureActiveFocus(reason: Qt::MouseFocusReason);
1655
1656 event->setAccepted(true);
1657}
1658
1659void QQuickTextInput::mouseMoveEvent(QMouseEvent *event)
1660{
1661 Q_D(QQuickTextInput);
1662 if (!QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: event)
1663#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
1664 && ! d->selectByTouchDrag
1665#endif
1666 )
1667 return;
1668
1669 if (d->selectPressed) {
1670 if (qAbs(t: int(event->position().x() - d->pressPos.x())) > QGuiApplication::styleHints()->startDragDistance())
1671 setKeepMouseGrab(true);
1672
1673#if QT_CONFIG(im)
1674 if (d->composeMode()) {
1675 // start selection
1676 int startPos = d->positionAt(point: d->pressPos);
1677 int currentPos = d->positionAt(point: event->position());
1678 if (startPos != currentPos)
1679 d->setSelection(start: startPos, length: currentPos - startPos);
1680 } else
1681#endif
1682 {
1683 moveCursorSelection(pos: d->positionAt(point: event->position()), mode: d->mouseSelectionMode);
1684 }
1685 event->setAccepted(true);
1686 } else {
1687 QQuickImplicitSizeItem::mouseMoveEvent(event);
1688 }
1689}
1690
1691void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event)
1692{
1693 Q_D(QQuickTextInput);
1694 if (d->sendMouseEventToInputContext(event))
1695 return;
1696 if (d->selectPressed) {
1697 d->selectPressed = false;
1698 setKeepMouseGrab(false);
1699 }
1700 const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: event)
1701#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
1702 || d->selectByTouchDrag
1703#endif
1704 ;
1705
1706#if QT_CONFIG(clipboard)
1707 if (isMouse && QGuiApplication::clipboard()->supportsSelection()) {
1708 if (event->button() == Qt::LeftButton) {
1709 d->copy(mode: QClipboard::Selection);
1710 } else if (!d->m_readOnly && event->button() == Qt::MiddleButton) {
1711 d->deselect();
1712 d->insert(QGuiApplication::clipboard()->text(mode: QClipboard::Selection));
1713 }
1714 }
1715#endif
1716
1717 // On a touchscreen or with a stylus, set cursor position and focus on release, not on press;
1718 // if Flickable steals the grab in the meantime, the cursor won't move.
1719 // Check d->hasSelectedText() to keep touch-and-hold word selection working.
1720 // But if text was selected already on press, deselect it on release.
1721 if (!isMouse && (!d->hasSelectedText() || d->hadSelectionOnMousePress))
1722 d->moveCursor(pos: d->positionAt(point: event->position()), mark: false);
1723 // On Android, after doing a long-press to start selection, we see a release event,
1724 // even though there was no press event. So reset hadSelectionOnMousePress to avoid
1725 // it getting stuck in true state.
1726 d->hadSelectionOnMousePress = false;
1727
1728 if (d->focusOnPress && qGuiApp->styleHints()->setFocusOnTouchRelease())
1729 ensureActiveFocus(reason: Qt::MouseFocusReason);
1730
1731 if (!event->isAccepted())
1732 QQuickImplicitSizeItem::mouseReleaseEvent(event);
1733}
1734
1735bool QQuickTextInputPrivate::sendMouseEventToInputContext(QMouseEvent *event)
1736{
1737#if QT_CONFIG(im)
1738 if (composeMode()) {
1739 int tmp_cursor = positionAt(point: event->position());
1740 int mousePos = tmp_cursor - m_cursor;
1741 if (mousePos >= 0 && mousePos <= m_textLayout.preeditAreaText().size()) {
1742 if (event->type() == QEvent::MouseButtonRelease) {
1743 QGuiApplication::inputMethod()->invokeAction(a: QInputMethod::Click, cursorPosition: mousePos);
1744 }
1745 return true;
1746 }
1747 }
1748#else
1749 Q_UNUSED(event);
1750#endif
1751
1752 return false;
1753}
1754
1755void QQuickTextInput::mouseUngrabEvent()
1756{
1757 Q_D(QQuickTextInput);
1758 d->selectPressed = false;
1759 setKeepMouseGrab(false);
1760}
1761
1762bool QQuickTextInput::event(QEvent* ev)
1763{
1764#if QT_CONFIG(shortcut)
1765 Q_D(QQuickTextInput);
1766 if (ev->type() == QEvent::ShortcutOverride) {
1767 if (d->m_readOnly) {
1768 ev->ignore();
1769 return false;
1770 }
1771 QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
1772 if (ke == QKeySequence::Copy
1773 || ke == QKeySequence::Paste
1774 || ke == QKeySequence::Cut
1775 || ke == QKeySequence::Redo
1776 || ke == QKeySequence::Undo
1777 || ke == QKeySequence::MoveToNextWord
1778 || ke == QKeySequence::MoveToPreviousWord
1779 || ke == QKeySequence::MoveToStartOfDocument
1780 || ke == QKeySequence::MoveToEndOfDocument
1781 || ke == QKeySequence::SelectNextWord
1782 || ke == QKeySequence::SelectPreviousWord
1783 || ke == QKeySequence::SelectStartOfLine
1784 || ke == QKeySequence::SelectEndOfLine
1785 || ke == QKeySequence::SelectStartOfBlock
1786 || ke == QKeySequence::SelectEndOfBlock
1787 || ke == QKeySequence::SelectStartOfDocument
1788 || ke == QKeySequence::SelectAll
1789 || ke == QKeySequence::SelectEndOfDocument
1790 || ke == QKeySequence::DeleteCompleteLine) {
1791 ke->accept();
1792 return true;
1793 } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier
1794 || ke->modifiers() == Qt::KeypadModifier) {
1795 if (ke->key() < Qt::Key_Escape) {
1796 ke->accept();
1797 return true;
1798 } else {
1799 switch (ke->key()) {
1800 case Qt::Key_Delete:
1801 case Qt::Key_Home:
1802 case Qt::Key_End:
1803 case Qt::Key_Backspace:
1804 case Qt::Key_Left:
1805 case Qt::Key_Right:
1806 ke->accept();
1807 return true;
1808 default:
1809 break;
1810 }
1811 }
1812 }
1813 ev->ignore();
1814 }
1815#endif
1816
1817 return QQuickImplicitSizeItem::event(ev);
1818}
1819
1820void QQuickTextInput::geometryChange(const QRectF &newGeometry,
1821 const QRectF &oldGeometry)
1822{
1823 Q_D(QQuickTextInput);
1824 if (!d->inLayout) {
1825 if (newGeometry.width() != oldGeometry.width())
1826 d->updateLayout();
1827 else if (newGeometry.height() != oldGeometry.height() && d->vAlign != QQuickTextInput::AlignTop)
1828 d->updateBaselineOffset();
1829 updateCursorRectangle();
1830 }
1831 QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
1832}
1833
1834void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value)
1835{
1836 Q_D(QQuickTextInput);
1837 Q_UNUSED(value);
1838 switch (change) {
1839 case ItemDevicePixelRatioHasChanged:
1840 if (d->containsUnscalableGlyphs) {
1841 // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
1842 // Text layout code respects the current device pixel ratio automatically, we only need
1843 // to rerun layout after the ratio changed.
1844 d->updateLayout();
1845 }
1846 break;
1847
1848 default:
1849 break;
1850 }
1851 QQuickImplicitSizeItem::itemChange(change, value);
1852}
1853
1854void QQuickTextInputPrivate::ensureVisible(int position, int preeditCursor, int preeditLength)
1855{
1856 Q_Q(QQuickTextInput);
1857 QTextLine textLine = m_textLayout.lineForTextPosition(pos: position + preeditCursor);
1858 const qreal width = qMax<qreal>(a: 0, b: q->width() - q->leftPadding() - q->rightPadding());
1859 qreal cix = 0;
1860 qreal widthUsed = 0;
1861 if (textLine.isValid()) {
1862 cix = textLine.cursorToX(cursorPos: position + preeditLength);
1863 const qreal cursorWidth = cix >= 0 ? cix : width - cix;
1864 widthUsed = qMax(a: textLine.naturalTextWidth(), b: cursorWidth);
1865 }
1866 int previousScroll = hscroll;
1867
1868 if (widthUsed <= width) {
1869 hscroll = 0;
1870 } else {
1871 Q_ASSERT(textLine.isValid());
1872 if (cix - hscroll >= width) {
1873 // text doesn't fit, cursor is to the right of br (scroll right)
1874 hscroll = cix - width;
1875 } else if (cix - hscroll < 0 && hscroll < widthUsed) {
1876 // text doesn't fit, cursor is to the left of br (scroll left)
1877 hscroll = cix;
1878 } else if (widthUsed - hscroll < width) {
1879 // text doesn't fit, text document is to the left of br; align
1880 // right
1881 hscroll = widthUsed - width;
1882 } else if (width - hscroll > widthUsed) {
1883 // text doesn't fit, text document is to the right of br; align
1884 // left
1885 hscroll = width - widthUsed;
1886 }
1887#if QT_CONFIG(im)
1888 if (preeditLength > 0) {
1889 // check to ensure long pre-edit text doesn't push the cursor
1890 // off to the left
1891 cix = textLine.cursorToX(cursorPos: position + qMax(a: 0, b: preeditCursor - 1));
1892 if (cix < hscroll)
1893 hscroll = cix;
1894 }
1895#endif
1896 }
1897 if (previousScroll != hscroll)
1898 textLayoutDirty = true;
1899}
1900
1901void QQuickTextInputPrivate::updateHorizontalScroll()
1902{
1903 if (autoScroll && m_echoMode != QQuickTextInput::NoEcho) {
1904#if QT_CONFIG(im)
1905 const int preeditLength = m_textLayout.preeditAreaText().size();
1906 ensureVisible(position: m_cursor, preeditCursor: m_preeditCursor, preeditLength);
1907#else
1908 ensureVisible(m_cursor);
1909#endif
1910 } else {
1911 hscroll = 0;
1912 }
1913}
1914
1915void QQuickTextInputPrivate::updateVerticalScroll()
1916{
1917 Q_Q(QQuickTextInput);
1918#if QT_CONFIG(im)
1919 const int preeditLength = m_textLayout.preeditAreaText().size();
1920#endif
1921 const qreal height = qMax<qreal>(a: 0, b: q->height() - q->topPadding() - q->bottomPadding());
1922 qreal heightUsed = contentSize.height();
1923 qreal previousScroll = vscroll;
1924
1925 if (!autoScroll || heightUsed <= height) {
1926 // text fits in br; use vscroll for alignment
1927 vscroll = -QQuickTextUtil::alignedY(
1928 textHeight: heightUsed, itemHeight: height, alignment: vAlign & ~(Qt::AlignAbsolute|Qt::AlignHorizontal_Mask));
1929 } else {
1930#if QT_CONFIG(im)
1931 QTextLine currentLine = m_textLayout.lineForTextPosition(pos: m_cursor + preeditLength);
1932#else
1933 QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor);
1934#endif
1935 QRectF r = currentLine.isValid() ? currentLine.rect() : QRectF();
1936 qreal top = r.top();
1937 int bottom = r.bottom();
1938
1939 if (bottom - vscroll >= height) {
1940 // text doesn't fit, cursor is to the below the br (scroll down)
1941 vscroll = bottom - height;
1942 } else if (top - vscroll < 0 && vscroll < heightUsed) {
1943 // text doesn't fit, cursor is above br (scroll up)
1944 vscroll = top;
1945 } else if (heightUsed - vscroll < height) {
1946 // text doesn't fit, text document is to the left of br; align
1947 // right
1948 vscroll = heightUsed - height;
1949 }
1950#if QT_CONFIG(im)
1951 if (preeditLength > 0) {
1952 // check to ensure long pre-edit text doesn't push the cursor
1953 // off the top
1954 currentLine = m_textLayout.lineForTextPosition(pos: m_cursor + qMax(a: 0, b: m_preeditCursor - 1));
1955 top = currentLine.isValid() ? currentLine.rect().top() : 0;
1956 if (top < vscroll)
1957 vscroll = top;
1958 }
1959#endif
1960 }
1961 if (previousScroll != vscroll)
1962 textLayoutDirty = true;
1963}
1964
1965void QQuickTextInput::triggerPreprocess()
1966{
1967 Q_D(QQuickTextInput);
1968 if (d->updateType == QQuickTextInputPrivate::UpdateNone)
1969 d->updateType = QQuickTextInputPrivate::UpdateOnlyPreprocess;
1970 polish();
1971 update();
1972}
1973
1974void QQuickTextInput::updatePolish()
1975{
1976 invalidateFontCaches();
1977}
1978
1979void QQuickTextInput::invalidateFontCaches()
1980{
1981 Q_D(QQuickTextInput);
1982
1983 if (d->m_textLayout.engine() != nullptr)
1984 d->m_textLayout.engine()->resetFontEngineCache();
1985}
1986
1987void QQuickTextInput::ensureActiveFocus(Qt::FocusReason reason)
1988{
1989 bool hadActiveFocus = hasActiveFocus();
1990 forceActiveFocus(reason);
1991#if QT_CONFIG(im)
1992 Q_D(QQuickTextInput);
1993 // re-open input panel on press if already focused
1994 if (hasActiveFocus() && hadActiveFocus && !d->m_readOnly)
1995 qGuiApp->inputMethod()->show();
1996#else
1997 Q_UNUSED(hadActiveFocus);
1998#endif
1999}
2000
2001QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
2002{
2003 Q_UNUSED(data);
2004 Q_D(QQuickTextInput);
2005
2006 if (d->updateType != QQuickTextInputPrivate::UpdatePaintNode && oldNode != nullptr) {
2007 // Update done in preprocess() in the nodes
2008 d->updateType = QQuickTextInputPrivate::UpdateNone;
2009 return oldNode;
2010 }
2011
2012 d->updateType = QQuickTextInputPrivate::UpdateNone;
2013
2014 QSGInternalTextNode *node = static_cast<QSGInternalTextNode *>(oldNode);
2015 if (node == nullptr)
2016 node = d->sceneGraphContext()->createInternalTextNode(renderContext: d->sceneGraphRenderContext());
2017 d->textNode = node;
2018
2019 const bool showCursor = !isReadOnly() && d->cursorItem == nullptr && d->cursorVisible && d->m_blinkStatus;
2020
2021 if (!d->textLayoutDirty && oldNode != nullptr) {
2022 if (showCursor)
2023 node->setCursor(rect: cursorRectangle(), color: d->color);
2024 else
2025 node->clearCursor();
2026 } else {
2027 node->setRenderType(QSGTextNode::RenderType(d->renderType));
2028 node->clear();
2029 node->setMatrix(QMatrix4x4());
2030 node->setTextStyle(QSGInternalTextNode::Normal);
2031 node->setColor(d->color);
2032 node->setSelectionTextColor(d->selectedTextColor);
2033 node->setSelectionColor(d->selectionColor);
2034 node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
2035
2036 if (flags().testFlag(flag: ItemObservesViewport))
2037 node->setViewport(clipRect());
2038 else
2039 node->setViewport(QRectF{});
2040
2041 QPointF offset(leftPadding(), topPadding());
2042 if (d->autoScroll && d->m_textLayout.lineCount() > 0) {
2043 QFontMetricsF fm(d->font);
2044 // the y offset is there to keep the baseline constant in case we have script changes in the text.
2045 offset += -QPointF(d->hscroll, d->vscroll + d->m_textLayout.lineAt(i: 0).ascent() - fm.ascent());
2046 } else {
2047 offset += -QPointF(d->hscroll, d->vscroll);
2048 }
2049
2050 if (!d->m_textLayout.text().isEmpty()
2051#if QT_CONFIG(im)
2052 || !d->m_textLayout.preeditAreaText().isEmpty()
2053#endif
2054 ) {
2055 node->addTextLayout(position: offset, layout: &d->m_textLayout,
2056 selectionStart: d->selectionStart(),
2057 selectionCount: d->selectionEnd() - 1); // selectionEnd() returns first char after
2058 // selection
2059 }
2060
2061 if (showCursor)
2062 node->setCursor(rect: cursorRectangle(), color: d->color);
2063
2064 d->textLayoutDirty = false;
2065 }
2066
2067 d->containsUnscalableGlyphs = node->containsUnscalableGlyphs();
2068
2069 invalidateFontCaches();
2070
2071 return node;
2072}
2073
2074#if QT_CONFIG(im)
2075QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property) const
2076{
2077#ifdef Q_OS_ANDROID
2078 // QTBUG-61652
2079 if (property == Qt::ImEnterKeyType) {
2080 Q_D(const QQuickItem);
2081 // Do not change if type was set manually
2082 if (!d->extra.isAllocated()
2083 || d->extra->enterKeyAttached == nullptr
2084 || d->extra->enterKeyAttached->type() == Qt::EnterKeyDefault) {
2085
2086 QQuickItem *next = const_cast<QQuickTextInput*>(this)->nextItemInFocusChain();
2087 QQuickItem *originalNext = next;
2088 while (next && next != this && !next->activeFocusOnTab()) {
2089 next = next->nextItemInFocusChain();
2090 if (next == originalNext) {
2091 // There seems to be no suitable element in the focus chain
2092 next = nullptr;
2093 }
2094 }
2095 if (next) {
2096 const auto nextYPos = next->mapToGlobal(QPoint(0, 0)).y();
2097 const auto currentYPos = this->mapToGlobal(QPoint(0, 0)).y();
2098 if (currentYPos < nextYPos)
2099 // Set EnterKey to KeyNext type only if the next item
2100 // in the focus chain is below current QQuickTextInput
2101 return Qt::EnterKeyNext;
2102 }
2103 }
2104 }
2105#endif
2106 return inputMethodQuery(query: property, argument: QVariant());
2107}
2108
2109QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property, const QVariant &argument) const
2110{
2111 Q_D(const QQuickTextInput);
2112 switch (property) {
2113 case Qt::ImEnabled:
2114 return QVariant((bool)(flags() & ItemAcceptsInputMethod));
2115 case Qt::ImHints:
2116 return QVariant((int) d->effectiveInputMethodHints());
2117 case Qt::ImCursorRectangle:
2118 return cursorRectangle();
2119 case Qt::ImAnchorRectangle:
2120 return d->anchorRectangle();
2121 case Qt::ImFont:
2122 return font();
2123 case Qt::ImCursorPosition: {
2124 const QPointF pt = argument.toPointF();
2125 if (!pt.isNull())
2126 return QVariant(d->positionAt(point: pt));
2127 return QVariant(d->m_cursor);
2128 }
2129 case Qt::ImSurroundingText:
2130 if (d->m_echoMode == PasswordEchoOnEdit && !d->m_passwordEchoEditing) {
2131 return QVariant(displayText());
2132 } else {
2133 return QVariant(d->realText());
2134 }
2135 case Qt::ImCurrentSelection:
2136 return QVariant(selectedText());
2137 case Qt::ImMaximumTextLength:
2138 return QVariant(maxLength());
2139 case Qt::ImAnchorPosition:
2140 if (d->selectionStart() == d->selectionEnd())
2141 return QVariant(d->m_cursor);
2142 else if (d->selectionStart() == d->m_cursor)
2143 return QVariant(d->selectionEnd());
2144 else
2145 return QVariant(d->selectionStart());
2146 case Qt::ImAbsolutePosition:
2147 return QVariant(d->m_cursor);
2148 case Qt::ImTextAfterCursor:
2149 if (argument.isValid())
2150 return QVariant(d->m_text.mid(position: d->m_cursor, n: argument.toInt()));
2151 return QVariant(d->m_text.mid(position: d->m_cursor));
2152 case Qt::ImTextBeforeCursor:
2153 if (argument.isValid())
2154 return QVariant(QStringView{d->m_text}.left(n: d->m_cursor).right(n: argument.toInt()).toString());
2155 return QVariant(d->m_text.left(n: d->m_cursor));
2156 case Qt::ImReadOnly:
2157 return QVariant(d->m_readOnly);
2158 default:
2159 return QQuickItem::inputMethodQuery(query: property);
2160 }
2161}
2162#endif // im
2163
2164/*!
2165 \qmlmethod QtQuick::TextInput::deselect()
2166
2167 Removes active text selection.
2168*/
2169void QQuickTextInput::deselect()
2170{
2171 Q_D(QQuickTextInput);
2172 d->deselect();
2173}
2174
2175/*!
2176 \qmlmethod QtQuick::TextInput::selectAll()
2177
2178 Causes all text to be selected.
2179*/
2180void QQuickTextInput::selectAll()
2181{
2182 Q_D(QQuickTextInput);
2183 d->setSelection(start: 0, length: text().size());
2184}
2185
2186/*!
2187 \qmlmethod QtQuick::TextInput::isRightToLeft(int start, int end)
2188
2189 Returns true if the natural reading direction of the editor text
2190 found between positions \a start and \a end is right to left.
2191*/
2192bool QQuickTextInput::isRightToLeft(int start, int end)
2193{
2194 if (start > end) {
2195 qmlWarning(me: this) << "isRightToLeft(start, end) called with the end property being smaller than the start.";
2196 return false;
2197 } else {
2198 return QStringView{text()}.mid(pos: start, n: end - start).isRightToLeft();
2199 }
2200}
2201
2202#if QT_CONFIG(clipboard)
2203/*!
2204 \qmlmethod QtQuick::TextInput::cut()
2205
2206 Moves the currently selected text to the system clipboard.
2207
2208 \note If the echo mode is set to a mode other than Normal then cut
2209 will not work. This is to prevent using cut as a method of bypassing
2210 password features of the line control.
2211*/
2212void QQuickTextInput::cut()
2213{
2214 Q_D(QQuickTextInput);
2215 if (!d->m_readOnly && d->m_echoMode == QQuickTextInput::Normal) {
2216 d->copy();
2217 d->del();
2218 }
2219}
2220
2221/*!
2222 \qmlmethod QtQuick::TextInput::copy()
2223
2224 Copies the currently selected text to the system clipboard.
2225
2226 \note If the echo mode is set to a mode other than Normal then copy
2227 will not work. This is to prevent using copy as a method of bypassing
2228 password features of the line control.
2229*/
2230void QQuickTextInput::copy()
2231{
2232 Q_D(QQuickTextInput);
2233 d->copy();
2234}
2235
2236/*!
2237 \qmlmethod QtQuick::TextInput::paste()
2238
2239 Replaces the currently selected text by the contents of the system clipboard.
2240*/
2241void QQuickTextInput::paste()
2242{
2243 Q_D(QQuickTextInput);
2244 if (!d->m_readOnly)
2245 d->paste();
2246}
2247#endif // clipboard
2248
2249/*!
2250 \qmlmethod QtQuick::TextInput::undo()
2251
2252 Undoes the last operation if undo is \l {canUndo}{available}. Deselects any
2253 current selection, and updates the selection start to the current cursor
2254 position.
2255*/
2256
2257void QQuickTextInput::undo()
2258{
2259 Q_D(QQuickTextInput);
2260 if (!d->m_readOnly) {
2261 d->cancelInput();
2262 d->internalUndo();
2263 d->finishChange(validateFromState: -1, update: true);
2264 }
2265}
2266
2267/*!
2268 \qmlmethod QtQuick::TextInput::redo()
2269
2270 Redoes the last operation if redo is \l {canRedo}{available}.
2271*/
2272
2273void QQuickTextInput::redo()
2274{
2275 Q_D(QQuickTextInput);
2276 if (!d->m_readOnly) {
2277 d->cancelInput();
2278 d->internalRedo();
2279 d->finishChange();
2280 }
2281}
2282
2283/*!
2284 \qmlmethod QtQuick::TextInput::insert(int position, string text)
2285
2286 Inserts \a text into the TextInput at \a position.
2287*/
2288
2289void QQuickTextInput::insert(int position, const QString &text)
2290{
2291 Q_D(QQuickTextInput);
2292 if (d->m_echoMode == QQuickTextInput::Password) {
2293 if (d->m_passwordMaskDelay > 0)
2294 d->m_passwordEchoTimer.start(msec: d->m_passwordMaskDelay, obj: this);
2295 }
2296 if (position < 0 || position > d->m_text.size())
2297 return;
2298
2299 const int priorState = d->m_undoState;
2300
2301 QString insertText = text;
2302
2303 if (d->hasSelectedText()) {
2304 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2305 QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
2306 }
2307 if (d->m_maskData) {
2308 insertText = d->maskString(pos: position, str: insertText);
2309 for (int i = 0; i < insertText.size(); ++i) {
2310 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2311 QQuickTextInputPrivate::DeleteSelection, position + i, d->m_text.at(i: position + i), -1, -1));
2312 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2313 QQuickTextInputPrivate::Insert, position + i, insertText.at(i), -1, -1));
2314 }
2315 d->m_text.replace(i: position, len: insertText.size(), after: insertText);
2316 if (!insertText.isEmpty())
2317 d->m_textDirty = true;
2318 if (position < d->m_selend && position + insertText.size() > d->m_selstart)
2319 d->m_selDirty = true;
2320 } else {
2321 int remaining = d->m_maxLength - d->m_text.size();
2322 if (remaining != 0) {
2323 insertText = insertText.left(n: remaining);
2324 d->m_text.insert(i: position, s: insertText);
2325 for (int i = 0; i < insertText.size(); ++i)
2326 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2327 QQuickTextInputPrivate::Insert, position + i, insertText.at(i), -1, -1));
2328 if (d->m_cursor >= position)
2329 d->m_cursor += insertText.size();
2330 if (d->m_selstart >= position)
2331 d->m_selstart += insertText.size();
2332 if (d->m_selend >= position)
2333 d->m_selend += insertText.size();
2334 d->m_textDirty = true;
2335 if (position >= d->m_selstart && position <= d->m_selend)
2336 d->m_selDirty = true;
2337 }
2338 }
2339
2340 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2341 QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
2342 d->finishChange(validateFromState: priorState);
2343
2344 if (d->lastSelectionStart != d->lastSelectionEnd) {
2345 if (d->m_selstart != d->lastSelectionStart) {
2346 d->lastSelectionStart = d->m_selstart;
2347 emit selectionStartChanged();
2348 }
2349 if (d->m_selend != d->lastSelectionEnd) {
2350 d->lastSelectionEnd = d->m_selend;
2351 emit selectionEndChanged();
2352 }
2353 }
2354}
2355
2356/*!
2357 \qmlmethod QtQuick::TextInput::remove(int start, int end)
2358
2359 Removes the section of text that is between the \a start and \a end positions from the TextInput.
2360*/
2361
2362void QQuickTextInput::remove(int start, int end)
2363{
2364 Q_D(QQuickTextInput);
2365
2366 start = qBound(min: 0, val: start, max: d->m_text.size());
2367 end = qBound(min: 0, val: end, max: d->m_text.size());
2368
2369 if (start > end)
2370 qSwap(value1&: start, value2&: end);
2371 else if (start == end)
2372 return;
2373
2374 if (start < d->m_selend && end > d->m_selstart)
2375 d->m_selDirty = true;
2376
2377 const int priorState = d->m_undoState;
2378
2379 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2380 QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
2381
2382 if (start <= d->m_cursor && d->m_cursor < end) {
2383 // cursor is within the selection. Split up the commands
2384 // to be able to restore the correct cursor position
2385 for (int i = d->m_cursor; i >= start; --i) {
2386 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2387 QQuickTextInputPrivate::DeleteSelection, i, d->m_text.at(i), -1, 1));
2388 }
2389 for (int i = end - 1; i > d->m_cursor; --i) {
2390 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2391 QQuickTextInputPrivate::DeleteSelection, i - d->m_cursor + start - 1, d->m_text.at(i), -1, -1));
2392 }
2393 } else {
2394 for (int i = end - 1; i >= start; --i) {
2395 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2396 QQuickTextInputPrivate::RemoveSelection, i, d->m_text.at(i), -1, -1));
2397 }
2398 }
2399 if (d->m_maskData) {
2400 d->m_text.replace(i: start, len: end - start, after: d->clearString(pos: start, len: end - start));
2401 for (int i = 0; i < end - start; ++i) {
2402 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2403 QQuickTextInputPrivate::Insert, start + i, d->m_text.at(i: start + i), -1, -1));
2404 }
2405 } else {
2406 d->m_text.remove(i: start, len: end - start);
2407
2408 if (d->m_cursor > start)
2409 d->m_cursor -= qMin(a: d->m_cursor, b: end) - start;
2410 if (d->m_selstart > start)
2411 d->m_selstart -= qMin(a: d->m_selstart, b: end) - start;
2412 if (d->m_selend >= end)
2413 d->m_selend -= end - start;
2414 }
2415 d->addCommand(cmd: QQuickTextInputPrivate::Command(
2416 QQuickTextInputPrivate::SetSelection, d->m_cursor, u'\0', d->m_selstart, d->m_selend));
2417
2418 d->m_textDirty = true;
2419 d->finishChange(validateFromState: priorState);
2420
2421 if (d->lastSelectionStart != d->lastSelectionEnd) {
2422 if (d->m_selstart != d->lastSelectionStart) {
2423 d->lastSelectionStart = d->m_selstart;
2424 emit selectionStartChanged();
2425 }
2426 if (d->m_selend != d->lastSelectionEnd) {
2427 d->lastSelectionEnd = d->m_selend;
2428 emit selectionEndChanged();
2429 }
2430 }
2431}
2432
2433
2434/*!
2435 \qmlmethod QtQuick::TextInput::selectWord()
2436
2437 Causes the word closest to the current cursor position to be selected.
2438*/
2439void QQuickTextInput::selectWord()
2440{
2441 Q_D(QQuickTextInput);
2442 d->selectWordAtPos(d->m_cursor);
2443}
2444
2445/*!
2446 \qmlproperty string QtQuick::TextInput::passwordCharacter
2447
2448 This is the character displayed when echoMode is set to Password or
2449 PasswordEchoOnEdit. By default it is the password character used by
2450 the platform theme.
2451
2452 If this property is set to a string with more than one character,
2453 the first character is used. If the string is empty, the value
2454 is ignored and the property is not set.
2455*/
2456QString QQuickTextInput::passwordCharacter() const
2457{
2458 Q_D(const QQuickTextInput);
2459 return QString(d->m_passwordCharacter);
2460}
2461
2462void QQuickTextInput::setPasswordCharacter(const QString &str)
2463{
2464 Q_D(QQuickTextInput);
2465 if (str.size() < 1)
2466 return;
2467 d->m_passwordCharacter = str.constData()[0];
2468 if (d->m_echoMode == Password || d->m_echoMode == PasswordEchoOnEdit)
2469 d->updateDisplayText();
2470 emit passwordCharacterChanged();
2471}
2472
2473/*!
2474 \qmlproperty int QtQuick::TextInput::passwordMaskDelay
2475 \since 5.4
2476
2477 Sets the delay before visible character is masked with password character, in milliseconds.
2478
2479 The reset method will be called by assigning undefined.
2480*/
2481int QQuickTextInput::passwordMaskDelay() const
2482{
2483 Q_D(const QQuickTextInput);
2484 return d->m_passwordMaskDelay;
2485}
2486
2487void QQuickTextInput::setPasswordMaskDelay(int delay)
2488{
2489 Q_D(QQuickTextInput);
2490 if (d->m_passwordMaskDelay != delay) {
2491 d->m_passwordMaskDelay = delay;
2492 emit passwordMaskDelayChanged(delay);
2493 }
2494}
2495
2496void QQuickTextInput::resetPasswordMaskDelay()
2497{
2498 setPasswordMaskDelay(qGuiApp->styleHints()->passwordMaskDelay());
2499}
2500
2501/*!
2502 \qmlproperty string QtQuick::TextInput::displayText
2503
2504 This is the text displayed in the TextInput.
2505
2506 If \l echoMode is set to TextInput::Normal, this holds the
2507 same value as the TextInput::text property. Otherwise,
2508 this property holds the text visible to the user, while
2509 the \l text property holds the actual entered text.
2510
2511 \note Unlike the TextInput::text property, this contains
2512 partial text input from an input method.
2513
2514 \readonly
2515 \sa preeditText
2516*/
2517QString QQuickTextInput::displayText() const
2518{
2519 Q_D(const QQuickTextInput);
2520 return d->m_textLayout.text().insert(i: d->m_textLayout.preeditAreaPosition(), s: d->m_textLayout.preeditAreaText());
2521}
2522
2523/*!
2524 \qmlproperty string QtQuick::TextInput::preeditText
2525 \readonly
2526 \since 5.7
2527
2528 This property contains partial text input from an input method.
2529
2530 To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText
2531 flag in inputMethodHints.
2532
2533 \sa displayText, inputMethodHints
2534*/
2535QString QQuickTextInput::preeditText() const
2536{
2537 Q_D(const QQuickTextInput);
2538 return d->m_textLayout.preeditAreaText();
2539}
2540
2541/*!
2542 \qmlproperty bool QtQuick::TextInput::selectByMouse
2543
2544 Defaults to \c true.
2545
2546 If true, the user can use the mouse to select text in the usual way.
2547
2548 \note In versions prior to 6.4, the default was \c false; but if you
2549 enabled this property, you could also select text on a touchscreen by
2550 dragging your finger across it. This interfered with flicking when
2551 TextInput was used inside a Flickable. For consistency with TextField,
2552 selectByMouse now really means what it says: if \c true, you can select
2553 text by dragging \e only with a mouse. If this change does not suit your
2554 application, you can set \c selectByMouse to \c false, or import an older
2555 API version (for example \c {import QtQuick 6.3}) to revert to the previous
2556 behavior. The option to revert behavior by changing the import version will
2557 be removed in a later version of Qt.
2558*/
2559bool QQuickTextInput::selectByMouse() const
2560{
2561 Q_D(const QQuickTextInput);
2562 return d->selectByMouse;
2563}
2564
2565void QQuickTextInput::setSelectByMouse(bool on)
2566{
2567 Q_D(QQuickTextInput);
2568 if (d->selectByMouse != on) {
2569 d->selectByMouse = on;
2570 emit selectByMouseChanged(selectByMouse: on);
2571 }
2572}
2573
2574/*!
2575 \qmlproperty enumeration QtQuick::TextInput::mouseSelectionMode
2576
2577 Specifies how text should be selected using a mouse.
2578
2579 \value TextInput.SelectCharacters (default) The selection is updated with individual characters.
2580 \value TextInput.SelectWords The selection is updated with whole words.
2581
2582 This property only applies when \l selectByMouse is true.
2583*/
2584
2585QQuickTextInput::SelectionMode QQuickTextInput::mouseSelectionMode() const
2586{
2587 Q_D(const QQuickTextInput);
2588 return d->mouseSelectionMode;
2589}
2590
2591void QQuickTextInput::setMouseSelectionMode(SelectionMode mode)
2592{
2593 Q_D(QQuickTextInput);
2594 if (d->mouseSelectionMode != mode) {
2595 d->mouseSelectionMode = mode;
2596 emit mouseSelectionModeChanged(mode);
2597 }
2598}
2599
2600/*!
2601 \qmlproperty bool QtQuick::TextInput::persistentSelection
2602
2603 Whether the TextInput should keep its selection when it loses active focus to another
2604 item in the scene. By default this is set to false;
2605*/
2606
2607bool QQuickTextInput::persistentSelection() const
2608{
2609 Q_D(const QQuickTextInput);
2610 return d->persistentSelection;
2611}
2612
2613void QQuickTextInput::setPersistentSelection(bool on)
2614{
2615 Q_D(QQuickTextInput);
2616 if (d->persistentSelection == on)
2617 return;
2618 d->persistentSelection = on;
2619 emit persistentSelectionChanged();
2620}
2621
2622/*!
2623 \qmlproperty bool QtQuick::TextInput::canPaste
2624 \readonly
2625
2626 Returns true if the TextInput is writable and the content of the clipboard is
2627 suitable for pasting into the TextInput.
2628*/
2629bool QQuickTextInput::canPaste() const
2630{
2631#if QT_CONFIG(clipboard)
2632 Q_D(const QQuickTextInput);
2633 if (!d->canPasteValid) {
2634 if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData())
2635 const_cast<QQuickTextInputPrivate *>(d)->canPaste = !d->m_readOnly && mimeData->hasText() && !mimeData->text().isEmpty();
2636 const_cast<QQuickTextInputPrivate *>(d)->canPasteValid = true;
2637 }
2638 return d->canPaste;
2639#else
2640 return false;
2641#endif
2642}
2643
2644/*!
2645 \qmlproperty bool QtQuick::TextInput::canUndo
2646 \readonly
2647
2648 Returns true if the TextInput is writable and there are previous operations
2649 that can be undone.
2650*/
2651
2652bool QQuickTextInput::canUndo() const
2653{
2654 Q_D(const QQuickTextInput);
2655 return d->canUndo;
2656}
2657
2658/*!
2659 \qmlproperty bool QtQuick::TextInput::canRedo
2660 \readonly
2661
2662 Returns true if the TextInput is writable and there are \l {undo}{undone}
2663 operations that can be redone.
2664*/
2665
2666bool QQuickTextInput::canRedo() const
2667{
2668 Q_D(const QQuickTextInput);
2669 return d->canRedo;
2670}
2671
2672/*!
2673 \qmlproperty real QtQuick::TextInput::contentWidth
2674 \readonly
2675
2676 Returns the width of the text, including the width past the width
2677 which is covered due to insufficient wrapping if \l wrapMode is set.
2678*/
2679
2680qreal QQuickTextInput::contentWidth() const
2681{
2682 Q_D(const QQuickTextInput);
2683 return d->contentSize.width();
2684}
2685
2686/*!
2687 \qmlproperty real QtQuick::TextInput::contentHeight
2688 \readonly
2689
2690 Returns the height of the text, including the height past the height
2691 that is covered if the text does not fit within the set height.
2692*/
2693
2694qreal QQuickTextInput::contentHeight() const
2695{
2696 Q_D(const QQuickTextInput);
2697 return d->contentSize.height();
2698}
2699
2700void QQuickTextInput::moveCursorSelection(int position)
2701{
2702 Q_D(QQuickTextInput);
2703 d->moveCursor(pos: position, mark: true);
2704}
2705
2706/*!
2707 \qmlmethod QtQuick::TextInput::moveCursorSelection(int position, SelectionMode mode)
2708
2709 Moves the cursor to \a position and updates the selection according to the optional \a mode
2710 parameter. (To only move the cursor, set the \l cursorPosition property.)
2711
2712 When this method is called it additionally sets either the
2713 selectionStart or the selectionEnd (whichever was at the previous cursor position)
2714 to the specified position. This allows you to easily extend and contract the selected
2715 text range.
2716
2717 The selection mode specifies whether the selection is updated on a per character or a per word
2718 basis. If not specified the selection mode will default to \c {TextInput.SelectCharacters}.
2719
2720 \value TextInput.SelectCharacters
2721 Sets either the selectionStart or selectionEnd (whichever was at the previous cursor position)
2722 to the specified position.
2723 \value TextInput.SelectWords
2724 Sets the selectionStart and selectionEnd to include all words between the specified position
2725 and the previous cursor position. Words partially in the range are included.
2726
2727 For example, take this sequence of calls:
2728
2729 \code
2730 cursorPosition = 5
2731 moveCursorSelection(9, TextInput.SelectCharacters)
2732 moveCursorSelection(7, TextInput.SelectCharacters)
2733 \endcode
2734
2735 This moves the cursor to position 5, extend the selection end from 5 to 9
2736 and then retract the selection end from 9 to 7, leaving the text from position 5 to 7
2737 selected (the 6th and 7th characters).
2738
2739 The same sequence with TextInput.SelectWords will extend the selection start to a word boundary
2740 before or on position 5 and extend the selection end to a word boundary on or past position 9.
2741*/
2742void QQuickTextInput::moveCursorSelection(int pos, SelectionMode mode)
2743{
2744 Q_D(QQuickTextInput);
2745
2746 if (mode == SelectCharacters) {
2747 d->moveCursor(pos, mark: true);
2748 } else if (pos != d->m_cursor) {
2749 const int cursor = d->m_cursor;
2750 int anchor;
2751 if (!d->hasSelectedText())
2752 anchor = d->m_cursor;
2753 else if (d->selectionStart() == d->m_cursor)
2754 anchor = d->selectionEnd();
2755 else
2756 anchor = d->selectionStart();
2757
2758 if (anchor < pos || (anchor == pos && cursor < pos)) {
2759 const QString text = this->text();
2760 QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text);
2761 finder.setPosition(anchor);
2762
2763 const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons();
2764 if (anchor < text.size() && (reasons == QTextBoundaryFinder::NotAtBoundary
2765 || (reasons & QTextBoundaryFinder::EndOfItem))) {
2766 finder.toPreviousBoundary();
2767 }
2768 anchor = finder.position() != -1 ? finder.position() : 0;
2769
2770 finder.setPosition(pos);
2771 if (pos > 0 && !finder.boundaryReasons())
2772 finder.toNextBoundary();
2773 const int cursor = finder.position() != -1 ? finder.position() : text.size();
2774
2775 d->setSelection(start: anchor, length: cursor - anchor);
2776 } else if (anchor > pos || (anchor == pos && cursor > pos)) {
2777 const QString text = this->text();
2778 QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text);
2779 finder.setPosition(anchor);
2780
2781 const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons();
2782 if (anchor > 0 && (reasons == QTextBoundaryFinder::NotAtBoundary
2783 || (reasons & QTextBoundaryFinder::StartOfItem))) {
2784 finder.toNextBoundary();
2785 }
2786 anchor = finder.position() != -1 ? finder.position() : text.size();
2787
2788 finder.setPosition(pos);
2789 if (pos < text.size() && !finder.boundaryReasons())
2790 finder.toPreviousBoundary();
2791 const int cursor = finder.position() != -1 ? finder.position() : 0;
2792
2793 d->setSelection(start: anchor, length: cursor - anchor);
2794 }
2795 }
2796}
2797
2798void QQuickTextInput::focusInEvent(QFocusEvent *event)
2799{
2800 Q_D(QQuickTextInput);
2801 d->handleFocusEvent(event);
2802 QQuickImplicitSizeItem::focusInEvent(event);
2803}
2804
2805void QQuickTextInputPrivate::handleFocusEvent(QFocusEvent *event)
2806{
2807 Q_Q(QQuickTextInput);
2808 bool focus = event->gotFocus();
2809 if (!m_readOnly) {
2810 q->setCursorVisible(focus);
2811 setBlinkingCursorEnabled(focus);
2812 }
2813 if (focus) {
2814 q->q_updateAlignment();
2815#if QT_CONFIG(im)
2816 if (focusOnPress && !m_readOnly)
2817 qGuiApp->inputMethod()->show();
2818 q->connect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
2819 receiver: q, SLOT(q_updateAlignment()));
2820#endif
2821 } else {
2822 if ((m_passwordEchoEditing || m_passwordEchoTimer.isActive())) {
2823 updatePasswordEchoEditing(editing: false);//QQuickTextInputPrivate sets it on key events, but doesn't deal with focus events
2824 }
2825
2826 if (event->reason() != Qt::ActiveWindowFocusReason
2827 && event->reason() != Qt::PopupFocusReason
2828 && hasSelectedText()
2829 && !persistentSelection)
2830 deselect();
2831
2832 if (hasAcceptableInput(text: m_text) == AcceptableInput || fixup())
2833 emit q->editingFinished();
2834
2835#if QT_CONFIG(im)
2836 q->disconnect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
2837 receiver: q, SLOT(q_updateAlignment()));
2838#endif
2839 }
2840}
2841
2842void QQuickTextInput::focusOutEvent(QFocusEvent *event)
2843{
2844 Q_D(QQuickTextInput);
2845 d->handleFocusEvent(event);
2846 QQuickImplicitSizeItem::focusOutEvent(event);
2847}
2848
2849/*!
2850 \qmlproperty bool QtQuick::TextInput::inputMethodComposing
2851 \readonly
2852
2853 This property holds whether the TextInput has partial text input from an
2854 input method.
2855
2856 While it is composing an input method may rely on mouse or key events from
2857 the TextInput to edit or commit the partial text. This property can be
2858 used to determine when to disable events handlers that may interfere with
2859 the correct operation of an input method.
2860*/
2861bool QQuickTextInput::isInputMethodComposing() const
2862{
2863#if !QT_CONFIG(im)
2864 return false;
2865#else
2866 Q_D(const QQuickTextInput);
2867 return d->hasImState;
2868#endif
2869}
2870
2871QQuickTextInputPrivate::ExtraData::ExtraData()
2872 : padding(0)
2873 , topPadding(0)
2874 , leftPadding(0)
2875 , rightPadding(0)
2876 , bottomPadding(0)
2877 , explicitTopPadding(false)
2878 , explicitLeftPadding(false)
2879 , explicitRightPadding(false)
2880 , explicitBottomPadding(false)
2881 , implicitResize(true)
2882{
2883}
2884
2885void QQuickTextInputPrivate::init()
2886{
2887 Q_Q(QQuickTextInput);
2888#if QT_CONFIG(clipboard)
2889 if (QGuiApplication::clipboard()->supportsSelection())
2890 q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
2891 else
2892#endif
2893 q->setAcceptedMouseButtons(Qt::LeftButton);
2894
2895#if QT_CONFIG(im)
2896 q->setFlag(flag: QQuickItem::ItemAcceptsInputMethod);
2897#endif
2898 q->setFlag(flag: QQuickItem::ItemHasContents);
2899#if QT_CONFIG(clipboard)
2900 qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()),
2901 q, QQuickTextInput, SLOT(q_canPasteChanged()));
2902#endif // clipboard
2903
2904 lastSelectionStart = 0;
2905 lastSelectionEnd = 0;
2906 determineHorizontalAlignment();
2907
2908 if (!qmlDisableDistanceField()) {
2909 QTextOption option = m_textLayout.textOption();
2910 option.setUseDesignMetrics(renderType != QQuickTextInput::NativeRendering);
2911 m_textLayout.setTextOption(option);
2912 }
2913
2914 m_inputControl = new QInputControl(QInputControl::LineEdit, q);
2915 setSizePolicy(horizontalPolicy: QLayoutPolicy::Expanding, verticalPolicy: QLayoutPolicy::Fixed);
2916}
2917
2918void QQuickTextInputPrivate::cancelInput()
2919{
2920#if QT_CONFIG(im)
2921 Q_Q(QQuickTextInput);
2922 if (!m_readOnly && q->hasActiveFocus() && qGuiApp)
2923 cancelPreedit();
2924#endif // im
2925}
2926
2927void QQuickTextInput::updateCursorRectangle(bool scroll)
2928{
2929 Q_D(QQuickTextInput);
2930 if (!isComponentComplete())
2931 return;
2932
2933 if (scroll) {
2934 d->updateHorizontalScroll();
2935 d->updateVerticalScroll();
2936 }
2937 d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
2938 polish();
2939 update();
2940 emit cursorRectangleChanged();
2941 if (d->cursorItem) {
2942 QRectF r = cursorRectangle();
2943 d->cursorItem->setPosition(r.topLeft());
2944 d->cursorItem->setHeight(r.height());
2945 }
2946#if QT_CONFIG(im)
2947 updateInputMethod(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle);
2948#endif
2949}
2950
2951void QQuickTextInput::selectionChanged()
2952{
2953 Q_D(QQuickTextInput);
2954 d->textLayoutDirty = true; //TODO: Only update rect in selection
2955 d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
2956 polish();
2957 update();
2958 emit selectedTextChanged();
2959
2960 if (d->lastSelectionStart != d->selectionStart()) {
2961 d->lastSelectionStart = d->selectionStart();
2962 if (d->lastSelectionStart == -1)
2963 d->lastSelectionStart = d->m_cursor;
2964 emit selectionStartChanged();
2965 }
2966 if (d->lastSelectionEnd != d->selectionEnd()) {
2967 d->lastSelectionEnd = d->selectionEnd();
2968 if (d->lastSelectionEnd == -1)
2969 d->lastSelectionEnd = d->m_cursor;
2970 emit selectionEndChanged();
2971 }
2972}
2973
2974QRectF QQuickTextInput::boundingRect() const
2975{
2976 Q_D(const QQuickTextInput);
2977
2978 int cursorWidth = d->cursorItem ? 0 : 1;
2979
2980 qreal hscroll = d->hscroll;
2981 if (!d->autoScroll || d->contentSize.width() < width())
2982 hscroll -= QQuickTextUtil::alignedX(textWidth: d->contentSize.width(), itemWidth: width(), alignment: effectiveHAlign());
2983
2984 // Could include font max left/right bearings to either side of rectangle.
2985 QRectF r(-hscroll, -d->vscroll, d->contentSize.width(), d->contentSize.height());
2986 r.setRight(r.right() + cursorWidth);
2987 return r;
2988}
2989
2990QRectF QQuickTextInput::clipRect() const
2991{
2992 Q_D(const QQuickTextInput);
2993
2994 int cursorWidth = d->cursorItem ? d->cursorItem->width() : 1;
2995
2996 // Could include font max left/right bearings to either side of rectangle.
2997 QRectF r = QQuickImplicitSizeItem::clipRect();
2998 r.setRight(r.right() + cursorWidth);
2999 return r;
3000}
3001
3002void QQuickTextInput::q_canPasteChanged()
3003{
3004 Q_D(QQuickTextInput);
3005 bool old = d->canPaste;
3006#if QT_CONFIG(clipboard)
3007 if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData())
3008 d->canPaste = !d->m_readOnly && mimeData->hasText();
3009 else
3010 d->canPaste = false;
3011#endif
3012
3013 bool changed = d->canPaste != old || !d->canPasteValid;
3014 d->canPasteValid = true;
3015 if (changed)
3016 emit canPasteChanged();
3017
3018}
3019
3020void QQuickTextInput::q_updateAlignment()
3021{
3022 Q_D(QQuickTextInput);
3023 if (d->determineHorizontalAlignment()) {
3024 d->updateLayout();
3025 updateCursorRectangle();
3026 }
3027}
3028
3029/*!
3030 \internal
3031
3032 Updates the display text based of the current edit text
3033 If the text has changed will emit displayTextChanged()
3034*/
3035void QQuickTextInputPrivate::updateDisplayText(bool forceUpdate)
3036{
3037 QString orig = m_textLayout.text();
3038 QString str;
3039 if (m_echoMode == QQuickTextInput::NoEcho)
3040 str = QString::fromLatin1(ba: "");
3041 else
3042 str = m_text;
3043
3044 if (m_echoMode == QQuickTextInput::Password) {
3045 str.fill(c: m_passwordCharacter);
3046 if (m_passwordEchoTimer.isActive() && m_cursor > 0 && m_cursor <= m_text.size()) {
3047 int cursor = m_cursor - 1;
3048 QChar uc = m_text.at(i: cursor);
3049 str[cursor] = uc;
3050 if (cursor > 0 && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) {
3051 // second half of a surrogate, check if we have the first half as well,
3052 // if yes restore both at once
3053 uc = m_text.at(i: cursor - 1);
3054 if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00)
3055 str[cursor - 1] = uc;
3056 }
3057 }
3058 } else if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) {
3059 str.fill(c: m_passwordCharacter);
3060 }
3061
3062 // replace certain non-printable characters with spaces (to avoid
3063 // drawing boxes when using fonts that don't have glyphs for such
3064 // characters)
3065 QChar* uc = str.data();
3066 for (int i = 0; i < str.size(); ++i) {
3067 if (uc[i] == QChar::LineSeparator
3068 || uc[i] == QChar::ParagraphSeparator
3069 || uc[i] == QChar::ObjectReplacementCharacter)
3070 uc[i] = QChar(0x0020);
3071 }
3072
3073 if (str != orig || forceUpdate) {
3074 m_textLayout.setText(str);
3075 updateLayout(); // polish?
3076 emit q_func()->displayTextChanged();
3077 }
3078}
3079
3080qreal QQuickTextInputPrivate::calculateImplicitWidthForText(const QString &text) const
3081{
3082 Q_Q(const QQuickTextInput);
3083 QTextLayout layout(text);
3084
3085 QTextOption option = m_textLayout.textOption();
3086 option.setTextDirection(m_layoutDirection);
3087 option.setFlags(QTextOption::IncludeTrailingSpaces);
3088 option.setWrapMode(QTextOption::WrapMode(wrapMode));
3089 option.setAlignment(Qt::Alignment(q->effectiveHAlign()));
3090 layout.setTextOption(option);
3091 layout.setFont(font);
3092#if QT_CONFIG(im)
3093 layout.setPreeditArea(position: m_textLayout.preeditAreaPosition(), text: m_textLayout.preeditAreaText());
3094#endif
3095 layout.beginLayout();
3096
3097 QTextLine line = layout.createLine();
3098 line.setLineWidth(INT_MAX);
3099 const qreal theImplicitWidth = qCeil(v: line.naturalTextWidth()) + q->leftPadding() + q->rightPadding();
3100
3101 layout.endLayout();
3102 return theImplicitWidth;
3103}
3104
3105qreal QQuickTextInputPrivate::getImplicitWidth() const
3106{
3107 Q_Q(const QQuickTextInput);
3108 if (!requireImplicitWidth) {
3109 QQuickTextInputPrivate *d = const_cast<QQuickTextInputPrivate *>(this);
3110 d->requireImplicitWidth = true;
3111
3112 if (q->isComponentComplete())
3113 d->implicitWidth = calculateImplicitWidthForText(text: m_text);
3114 }
3115 return implicitWidth;
3116}
3117
3118void QQuickTextInputPrivate::setTopPadding(qreal value, bool reset)
3119{
3120 Q_Q(QQuickTextInput);
3121 qreal oldPadding = q->topPadding();
3122 if (!reset || extra.isAllocated()) {
3123 extra.value().topPadding = value;
3124 extra.value().explicitTopPadding = !reset;
3125 }
3126 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
3127 updateLayout();
3128 q->updateCursorRectangle();
3129 emit q->topPaddingChanged();
3130 }
3131}
3132
3133void QQuickTextInputPrivate::setLeftPadding(qreal value, bool reset)
3134{
3135 Q_Q(QQuickTextInput);
3136 qreal oldPadding = q->leftPadding();
3137 if (!reset || extra.isAllocated()) {
3138 extra.value().leftPadding = value;
3139 extra.value().explicitLeftPadding = !reset;
3140 }
3141 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
3142 updateLayout();
3143 q->updateCursorRectangle();
3144 emit q->leftPaddingChanged();
3145 }
3146}
3147
3148void QQuickTextInputPrivate::setRightPadding(qreal value, bool reset)
3149{
3150 Q_Q(QQuickTextInput);
3151 qreal oldPadding = q->rightPadding();
3152 if (!reset || extra.isAllocated()) {
3153 extra.value().rightPadding = value;
3154 extra.value().explicitRightPadding = !reset;
3155 }
3156 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
3157 updateLayout();
3158 q->updateCursorRectangle();
3159 emit q->rightPaddingChanged();
3160 }
3161}
3162
3163void QQuickTextInputPrivate::setBottomPadding(qreal value, bool reset)
3164{
3165 Q_Q(QQuickTextInput);
3166 qreal oldPadding = q->bottomPadding();
3167 if (!reset || extra.isAllocated()) {
3168 extra.value().bottomPadding = value;
3169 extra.value().explicitBottomPadding = !reset;
3170 }
3171 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
3172 updateLayout();
3173 q->updateCursorRectangle();
3174 emit q->bottomPaddingChanged();
3175 }
3176}
3177
3178bool QQuickTextInputPrivate::isImplicitResizeEnabled() const
3179{
3180 return !extra.isAllocated() || extra->implicitResize;
3181}
3182
3183void QQuickTextInputPrivate::setImplicitResizeEnabled(bool enabled)
3184{
3185 if (!enabled)
3186 extra.value().implicitResize = false;
3187 else if (extra.isAllocated())
3188 extra->implicitResize = true;
3189}
3190
3191void QQuickTextInputPrivate::updateLayout()
3192{
3193 Q_Q(QQuickTextInput);
3194
3195 if (!q->isComponentComplete())
3196 return;
3197
3198
3199 QTextOption option = m_textLayout.textOption();
3200 option.setTextDirection(layoutDirection());
3201 option.setWrapMode(QTextOption::WrapMode(wrapMode));
3202 option.setAlignment(Qt::Alignment(q->effectiveHAlign()));
3203 if (!qmlDisableDistanceField())
3204 option.setUseDesignMetrics(renderType != QQuickTextInput::NativeRendering);
3205
3206 m_textLayout.setTextOption(option);
3207 m_textLayout.setFont(font);
3208
3209 m_textLayout.beginLayout();
3210
3211 QTextLine line = m_textLayout.createLine();
3212 if (requireImplicitWidth) {
3213 line.setLineWidth(INT_MAX);
3214 const bool wasInLayout = inLayout;
3215 inLayout = true;
3216 if (isImplicitResizeEnabled())
3217 q->setImplicitWidth(qCeil(v: line.naturalTextWidth()) + q->leftPadding() + q->rightPadding());
3218 inLayout = wasInLayout;
3219 if (inLayout) // probably the result of a binding loop, but by letting it
3220 return; // get this far we'll get a warning to that effect.
3221 }
3222 qreal lineWidth = q->widthValid() || !isImplicitResizeEnabled() ? q->width() - q->leftPadding() - q->rightPadding() : INT_MAX;
3223 qreal height = 0;
3224 qreal width = 0;
3225 do {
3226 line.setLineWidth(lineWidth);
3227 line.setPosition(QPointF(0, height));
3228
3229 height += line.height();
3230 width = qMax(a: width, b: line.naturalTextWidth());
3231
3232 line = m_textLayout.createLine();
3233 } while (line.isValid());
3234 m_textLayout.endLayout();
3235
3236 option.setWrapMode(QTextOption::NoWrap);
3237 m_textLayout.setTextOption(option);
3238
3239 textLayoutDirty = true;
3240
3241 const QSizeF previousSize = contentSize;
3242 contentSize = QSizeF(width, height);
3243
3244 updateType = UpdatePaintNode;
3245 q->polish();
3246 q->update();
3247
3248 if (isImplicitResizeEnabled()) {
3249 if (!requireImplicitWidth && !q->widthValid())
3250 q->setImplicitSize(width + q->leftPadding() + q->rightPadding(), height + q->topPadding() + q->bottomPadding());
3251 else
3252 q->setImplicitHeight(height + q->topPadding() + q->bottomPadding());
3253 }
3254
3255 updateBaselineOffset();
3256
3257 if (previousSize != contentSize)
3258 emit q->contentSizeChanged();
3259}
3260
3261/*!
3262 \internal
3263 \brief QQuickTextInputPrivate::updateBaselineOffset
3264
3265 Assumes contentSize.height() is already calculated.
3266 */
3267void QQuickTextInputPrivate::updateBaselineOffset()
3268{
3269 Q_Q(QQuickTextInput);
3270 if (!q->isComponentComplete())
3271 return;
3272 QFontMetricsF fm(font);
3273 qreal yoff = 0;
3274 if (q->heightValid()) {
3275 const qreal surplusHeight = q->height() - contentSize.height() - q->topPadding() - q->bottomPadding();
3276 if (vAlign == QQuickTextInput::AlignBottom)
3277 yoff = surplusHeight;
3278 else if (vAlign == QQuickTextInput::AlignVCenter)
3279 yoff = surplusHeight/2;
3280 }
3281 q->setBaselineOffset(fm.ascent() + yoff + q->topPadding());
3282}
3283
3284#if QT_CONFIG(clipboard)
3285/*!
3286 \internal
3287
3288 Copies the currently selected text into the clipboard using the given
3289 \a mode.
3290
3291 \note If the echo mode is set to a mode other than Normal then copy
3292 will not work. This is to prevent using copy as a method of bypassing
3293 password features of the line control.
3294*/
3295void QQuickTextInputPrivate::copy(QClipboard::Mode mode) const
3296{
3297 QString t = selectedText();
3298 if (!t.isEmpty() && m_echoMode == QQuickTextInput::Normal) {
3299 QGuiApplication::clipboard()->setText(t, mode);
3300 }
3301}
3302
3303/*!
3304 \internal
3305
3306 Inserts the text stored in the application clipboard into the line
3307 control.
3308
3309 \sa insert()
3310*/
3311void QQuickTextInputPrivate::paste(QClipboard::Mode clipboardMode)
3312{
3313 QString clip = QGuiApplication::clipboard()->text(mode: clipboardMode);
3314 if (!clip.isEmpty() || hasSelectedText()) {
3315 separate(); //make it a separate undo/redo command
3316 insert(clip);
3317 separate();
3318 }
3319}
3320
3321#endif // clipboard
3322
3323#if QT_CONFIG(im)
3324/*!
3325 \internal
3326*/
3327void QQuickTextInputPrivate::commitPreedit()
3328{
3329 Q_Q(QQuickTextInput);
3330
3331 if (!hasImState)
3332 return;
3333
3334 QGuiApplication::inputMethod()->commit();
3335
3336 if (!hasImState)
3337 return;
3338
3339 QInputMethodEvent ev;
3340 QCoreApplication::sendEvent(receiver: q, event: &ev);
3341}
3342
3343void QQuickTextInputPrivate::cancelPreedit()
3344{
3345 Q_Q(QQuickTextInput);
3346
3347 if (!hasImState)
3348 return;
3349
3350 QGuiApplication::inputMethod()->reset();
3351
3352 QInputMethodEvent ev;
3353 QCoreApplication::sendEvent(receiver: q, event: &ev);
3354}
3355#endif // im
3356
3357/*!
3358 \internal
3359
3360 Handles the behavior for the backspace key or function.
3361 Removes the current selection if there is a selection, otherwise
3362 removes the character prior to the cursor position.
3363
3364 \sa del()
3365*/
3366void QQuickTextInputPrivate::backspace()
3367{
3368 int priorState = m_undoState;
3369 if (separateSelection()) {
3370 removeSelectedText();
3371 } else if (m_cursor) {
3372 --m_cursor;
3373 if (m_maskData)
3374 m_cursor = prevMaskBlank(pos: m_cursor);
3375 QChar uc = m_text.at(i: m_cursor);
3376 if (m_cursor > 0 && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) {
3377 // second half of a surrogate, check if we have the first half as well,
3378 // if yes delete both at once
3379 uc = m_text.at(i: m_cursor - 1);
3380 if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00) {
3381 internalDelete(wasBackspace: true);
3382 --m_cursor;
3383 }
3384 }
3385 internalDelete(wasBackspace: true);
3386 }
3387 finishChange(validateFromState: priorState);
3388}
3389
3390/*!
3391 \internal
3392
3393 Handles the behavior for the delete key or function.
3394 Removes the current selection if there is a selection, otherwise
3395 removes the character after the cursor position.
3396
3397 \sa del()
3398*/
3399void QQuickTextInputPrivate::del()
3400{
3401 int priorState = m_undoState;
3402 if (separateSelection()) {
3403 removeSelectedText();
3404 } else {
3405 int n = m_textLayout.nextCursorPosition(oldPos: m_cursor) - m_cursor;
3406 while (n--)
3407 internalDelete();
3408 }
3409 finishChange(validateFromState: priorState);
3410}
3411
3412/*!
3413 \internal
3414
3415 Inserts the given \a newText at the current cursor position.
3416 If there is any selected text it is removed prior to insertion of
3417 the new text.
3418*/
3419void QQuickTextInputPrivate::insert(const QString &newText)
3420{
3421 int priorState = m_undoState;
3422 if (separateSelection())
3423 removeSelectedText();
3424 internalInsert(s: newText);
3425 finishChange(validateFromState: priorState);
3426}
3427
3428/*!
3429 \internal
3430
3431 Clears the line control text.
3432*/
3433void QQuickTextInputPrivate::clear()
3434{
3435 int priorState = m_undoState;
3436 separateSelection();
3437 m_selstart = 0;
3438 m_selend = m_text.size();
3439 removeSelectedText();
3440 separate();
3441 finishChange(validateFromState: priorState, /*update*/false, /*edited*/false);
3442}
3443
3444/*!
3445 \internal
3446
3447 Sets \a length characters from the given \a start position as selected.
3448 The given \a start position must be within the current text for
3449 the line control. If \a length characters cannot be selected, then
3450 the selection will extend to the end of the current text.
3451*/
3452void QQuickTextInputPrivate::setSelection(int start, int length)
3453{
3454 Q_Q(QQuickTextInput);
3455#if QT_CONFIG(im)
3456 commitPreedit();
3457#endif
3458
3459 if (start < 0 || start > m_text.size()) {
3460 qWarning(msg: "QQuickTextInputPrivate::setSelection: Invalid start position");
3461 return;
3462 }
3463
3464 if (length > 0) {
3465 if (start == m_selstart && start + length == m_selend && m_cursor == m_selend)
3466 return;
3467 m_selstart = start;
3468 m_selend = qMin(a: start + length, b: m_text.size());
3469 m_cursor = m_selend;
3470 } else if (length < 0) {
3471 if (start == m_selend && start + length == m_selstart && m_cursor == m_selstart)
3472 return;
3473 m_selstart = qMax(a: start + length, b: 0);
3474 m_selend = start;
3475 m_cursor = m_selstart;
3476 } else if (m_selstart != m_selend) {
3477 m_selstart = 0;
3478 m_selend = 0;
3479 m_cursor = start;
3480 } else {
3481 m_cursor = start;
3482 emitCursorPositionChanged();
3483 return;
3484 }
3485 emit q->selectionChanged();
3486 emitCursorPositionChanged();
3487#if QT_CONFIG(im)
3488 q->updateInputMethod(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle | Qt::ImCursorPosition | Qt::ImAnchorPosition
3489 | Qt::ImCurrentSelection);
3490#endif
3491}
3492
3493/*!
3494 \internal
3495
3496 Sets the password echo editing to \a editing. If password echo editing
3497 is true, then the text of the password is displayed even if the echo
3498 mode is set to QLineEdit::PasswordEchoOnEdit. Password echoing editing
3499 does not affect other echo modes.
3500*/
3501void QQuickTextInputPrivate::updatePasswordEchoEditing(bool editing)
3502{
3503 cancelPasswordEchoTimer();
3504 m_passwordEchoEditing = editing;
3505 updateDisplayText();
3506}
3507
3508/*!
3509 \internal
3510
3511 Fixes the current text so that it is valid given any set validators.
3512
3513 Returns true if the text was changed. Otherwise returns false.
3514*/
3515bool QQuickTextInputPrivate::fixup() // this function assumes that validate currently returns != Acceptable
3516{
3517#if QT_CONFIG(validator)
3518 if (m_validator) {
3519 QString textCopy = m_text;
3520 int cursorCopy = m_cursor;
3521 m_validator->fixup(textCopy);
3522 if (m_validator->validate(textCopy, cursorCopy) == QValidator::Acceptable) {
3523 if (textCopy != m_text || cursorCopy != m_cursor)
3524 internalSetText(txt: textCopy, pos: cursorCopy);
3525 return true;
3526 }
3527 }
3528#endif
3529 return false;
3530}
3531
3532/*!
3533 \internal
3534
3535 Moves the cursor to the given position \a pos. If \a mark is true will
3536 adjust the currently selected text.
3537*/
3538void QQuickTextInputPrivate::moveCursor(int pos, bool mark)
3539{
3540 Q_Q(QQuickTextInput);
3541#if QT_CONFIG(im)
3542 commitPreedit();
3543#endif
3544
3545 if (pos != m_cursor) {
3546 separate();
3547 if (m_maskData)
3548 pos = pos > m_cursor ? nextMaskBlank(pos) : prevMaskBlank(pos);
3549 }
3550 if (mark) {
3551 int anchor;
3552 if (m_selend > m_selstart && m_cursor == m_selstart)
3553 anchor = m_selend;
3554 else if (m_selend > m_selstart && m_cursor == m_selend)
3555 anchor = m_selstart;
3556 else
3557 anchor = m_cursor;
3558 m_selstart = qMin(a: anchor, b: pos);
3559 m_selend = qMax(a: anchor, b: pos);
3560 } else {
3561 internalDeselect();
3562 }
3563 m_cursor = pos;
3564 if (mark || m_selDirty) {
3565 m_selDirty = false;
3566 emit q->selectionChanged();
3567 }
3568 emitCursorPositionChanged();
3569#if QT_CONFIG(im)
3570 q->updateInputMethod();
3571#endif
3572}
3573
3574#if QT_CONFIG(im)
3575/*!
3576 \internal
3577
3578 Applies the given input method event \a event to the text of the line
3579 control
3580*/
3581void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
3582{
3583 Q_Q(QQuickTextInput);
3584
3585 int priorState = -1;
3586 bool isGettingInput = !event->commitString().isEmpty()
3587 || event->preeditString() != preeditAreaText()
3588 || event->replacementLength() > 0;
3589 bool cursorPositionChanged = false;
3590 bool selectionChange = false;
3591 m_preeditDirty = event->preeditString() != preeditAreaText();
3592
3593 if (isGettingInput) {
3594 // If any text is being input, remove selected text.
3595 priorState = m_undoState;
3596 separateSelection();
3597 if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) {
3598 updatePasswordEchoEditing(editing: true);
3599 m_selstart = 0;
3600 m_selend = m_text.size();
3601 }
3602 removeSelectedText();
3603 }
3604
3605 int c = m_cursor; // cursor position after insertion of commit string
3606 if (event->replacementStart() <= 0)
3607 c += event->commitString().size() - qMin(a: -event->replacementStart(), b: event->replacementLength());
3608
3609 int cursorInsertPos = m_cursor + event->replacementStart();
3610 if (cursorInsertPos < 0)
3611 cursorInsertPos = 0;
3612
3613 // insert commit string
3614 if (event->replacementLength()) {
3615 m_selstart = cursorInsertPos;
3616 m_selend = m_selstart + event->replacementLength();
3617 m_selend = qMin(a: m_selend, b: m_text.size());
3618 removeSelectedText();
3619 }
3620 m_cursor = cursorInsertPos;
3621
3622 if (!event->commitString().isEmpty()) {
3623 internalInsert(s: event->commitString());
3624 cursorPositionChanged = true;
3625 } else {
3626 m_cursor = qBound(min: 0, val: c, max: m_text.size());
3627 }
3628
3629 for (int i = 0; i < event->attributes().size(); ++i) {
3630 const QInputMethodEvent::Attribute &a = event->attributes().at(i);
3631 if (a.type == QInputMethodEvent::Selection) {
3632 // If we already called internalInsert(), the cursor position will
3633 // already be adjusted correctly. The attribute.start does
3634 // not seem to take the mask into account, so it will reset cursor
3635 // to an invalid position in such case.
3636 if (!cursorPositionChanged)
3637 m_cursor = qBound(min: 0, val: a.start + a.length, max: m_text.size());
3638 if (a.length) {
3639 m_selstart = qMax(a: 0, b: qMin(a: a.start, b: m_text.size()));
3640 m_selend = m_cursor;
3641 if (m_selend < m_selstart) {
3642 qSwap(value1&: m_selstart, value2&: m_selend);
3643 }
3644 selectionChange = true;
3645 } else {
3646 selectionChange = m_selstart != m_selend;
3647 m_selstart = m_selend = 0;
3648 }
3649 cursorPositionChanged = true;
3650 }
3651 }
3652 QString oldPreeditString = m_textLayout.preeditAreaText();
3653 m_textLayout.setPreeditArea(position: m_cursor, text: event->preeditString());
3654 if (oldPreeditString != m_textLayout.preeditAreaText()) {
3655 emit q->preeditTextChanged();
3656 if (!event->preeditString().isEmpty() && m_undoPreeditState == -1)
3657 // Pre-edit text started. Remember state for undo purpose.
3658 m_undoPreeditState = priorState;
3659 }
3660 const int oldPreeditCursor = m_preeditCursor;
3661 m_preeditCursor = event->preeditString().size();
3662 hasImState = !event->preeditString().isEmpty();
3663 bool cursorVisible = true;
3664 QVector<QTextLayout::FormatRange> formats;
3665 for (int i = 0; i < event->attributes().size(); ++i) {
3666 const QInputMethodEvent::Attribute &a = event->attributes().at(i);
3667 if (a.type == QInputMethodEvent::Cursor) {
3668 hasImState = true;
3669 m_preeditCursor = a.start;
3670 cursorVisible = a.length != 0;
3671 } else if (a.type == QInputMethodEvent::TextFormat) {
3672 hasImState = true;
3673 QTextCharFormat f = qvariant_cast<QTextFormat>(v: a.value).toCharFormat();
3674 if (f.isValid()) {
3675 QTextLayout::FormatRange o;
3676 o.start = a.start + m_cursor;
3677 o.length = a.length;
3678 o.format = f;
3679 formats.append(t: o);
3680 }
3681 }
3682 }
3683 m_textLayout.setFormats(formats);
3684
3685 updateDisplayText(/*force*/ forceUpdate: true);
3686 if (cursorPositionChanged && emitCursorPositionChanged())
3687 q->updateInputMethod(queries: Qt::ImCursorPosition | Qt::ImAnchorPosition);
3688 else if (m_preeditCursor != oldPreeditCursor || isGettingInput)
3689 q->updateCursorRectangle();
3690
3691 if (isGettingInput)
3692 finishChange(validateFromState: priorState);
3693
3694 q->setCursorVisible(cursorVisible);
3695
3696 if (selectionChange) {
3697 emit q->selectionChanged();
3698 q->updateInputMethod(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle
3699 | Qt::ImCurrentSelection);
3700 }
3701
3702 // Empty pre-edit text handled. Clean m_undoPreeditState
3703 if (event->preeditString().isEmpty())
3704 m_undoPreeditState = -1;
3705
3706}
3707#endif // im
3708
3709/*!
3710 \internal
3711
3712 Sets the selection to cover the word at the given cursor position.
3713 The word boundaries are defined by the behavior of QTextLayout::SkipWords
3714 cursor mode.
3715*/
3716void QQuickTextInputPrivate::selectWordAtPos(int cursor)
3717{
3718 int next = cursor + 1;
3719 if (next > end())
3720 --next;
3721 int c = m_textLayout.previousCursorPosition(oldPos: next, mode: QTextLayout::SkipWords);
3722 moveCursor(pos: c, mark: false);
3723 // ## text layout should support end of words.
3724 int end = m_textLayout.nextCursorPosition(oldPos: c, mode: QTextLayout::SkipWords);
3725 while (end > cursor && m_text.at(i: end - 1).isSpace())
3726 --end;
3727 moveCursor(pos: end, mark: true);
3728}
3729
3730/*!
3731 \internal
3732
3733 Completes a change to the line control text. If the change is not valid
3734 will undo the line control state back to the given \a validateFromState.
3735
3736 If \a edited is true and the change is valid, will emit textEdited() in
3737 addition to textChanged(). Otherwise only emits textChanged() on a valid
3738 change.
3739
3740 The \a update value is currently unused.
3741*/
3742bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bool edited)
3743{
3744 Q_Q(QQuickTextInput);
3745
3746 Q_UNUSED(update);
3747#if QT_CONFIG(im)
3748 bool inputMethodAttributesChanged = m_textDirty || m_selDirty;
3749#endif
3750 bool alignmentChanged = false;
3751 bool textChanged = false;
3752
3753 if (m_textDirty) {
3754 // do validation
3755 bool wasValidInput = m_validInput;
3756 bool wasAcceptable = m_acceptableInput;
3757 m_validInput = true;
3758 m_acceptableInput = true;
3759#if QT_CONFIG(validator)
3760 if (m_validator) {
3761 QString textCopy = m_text;
3762 if (m_maskData)
3763 textCopy = maskString(pos: 0, str: m_text, clear: true);
3764 int cursorCopy = m_cursor;
3765 QValidator::State state = m_validator->validate(textCopy, cursorCopy);
3766 if (m_maskData)
3767 textCopy = m_text;
3768 m_validInput = state != QValidator::Invalid;
3769 m_acceptableInput = state == QValidator::Acceptable;
3770 if (m_validInput && !m_maskData) {
3771 if (m_text != textCopy) {
3772 internalSetText(txt: textCopy, pos: cursorCopy);
3773 return true;
3774 }
3775 m_cursor = cursorCopy;
3776 }
3777 }
3778#endif
3779 if (m_maskData)
3780 checkIsValid();
3781
3782#if QT_CONFIG(im)
3783 // If we were during pre-edit, validateFromState should point to the state before pre-edit
3784 // has been started. Choose the correct oldest remembered state
3785 if (m_undoPreeditState >= 0 && (m_undoPreeditState < validateFromState || validateFromState < 0))
3786 validateFromState = m_undoPreeditState;
3787#endif
3788 if (validateFromState >= 0 && wasValidInput && !m_validInput) {
3789 if (m_transactions.size())
3790 return false;
3791 internalUndo(until: validateFromState);
3792 m_history.resize(size: m_undoState);
3793 m_validInput = true;
3794 m_acceptableInput = wasAcceptable;
3795 m_textDirty = false;
3796 }
3797
3798 if (m_textDirty) {
3799 textChanged = true;
3800 m_textDirty = false;
3801#if QT_CONFIG(im)
3802 m_preeditDirty = false;
3803#endif
3804 alignmentChanged = determineHorizontalAlignment();
3805 if (edited)
3806 emit q->textEdited();
3807 emit q->textChanged();
3808 }
3809
3810 updateDisplayText(forceUpdate: alignmentChanged);
3811
3812 if (m_acceptableInput != wasAcceptable)
3813 emit q->acceptableInputChanged();
3814 }
3815#if QT_CONFIG(im)
3816 if (m_preeditDirty) {
3817 m_preeditDirty = false;
3818 if (determineHorizontalAlignment()) {
3819 alignmentChanged = true;
3820 updateLayout();
3821 }
3822 }
3823#endif
3824
3825 if (m_selDirty) {
3826 m_selDirty = false;
3827 emit q->selectionChanged();
3828 }
3829
3830#if QT_CONFIG(im)
3831 inputMethodAttributesChanged |= (m_cursor != m_lastCursorPos);
3832 if (inputMethodAttributesChanged)
3833 q->updateInputMethod();
3834#endif
3835 emitUndoRedoChanged();
3836
3837 if (!emitCursorPositionChanged() && (alignmentChanged || textChanged))
3838 q->updateCursorRectangle();
3839
3840 return true;
3841}
3842
3843/*!
3844 \internal
3845
3846 An internal function for setting the text of the line control.
3847*/
3848void QQuickTextInputPrivate::internalSetText(const QString &txt, int pos, bool edited)
3849{
3850 internalDeselect();
3851 QString oldText = m_text;
3852 if (m_maskData) {
3853 m_text = maskString(pos: 0, str: txt, clear: true);
3854 m_text += clearString(pos: m_text.size(), len: m_maxLength - m_text.size());
3855 } else {
3856 m_text = txt.isEmpty() ? txt : txt.left(n: m_maxLength);
3857 }
3858 m_history.clear();
3859 m_undoState = 0;
3860#if QT_CONFIG(im)
3861 m_undoPreeditState = -1;
3862#endif
3863 m_cursor = (pos < 0 || pos > m_text.size()) ? m_text.size() : pos;
3864 m_textDirty = (oldText != m_text);
3865
3866 bool changed = finishChange(validateFromState: -1, update: true, edited);
3867#if !QT_CONFIG(accessibility)
3868 Q_UNUSED(changed);
3869#else
3870 Q_Q(QQuickTextInput);
3871 if (changed && QAccessible::isActive()) {
3872 if (QObject *acc = QQuickAccessibleAttached::findAccessible(object: q, role: QAccessible::EditableText)) {
3873 QAccessibleTextUpdateEvent ev(acc, 0, oldText, m_text);
3874 QAccessible::updateAccessibility(event: &ev);
3875 }
3876 }
3877#endif
3878}
3879
3880
3881/*!
3882 \internal
3883
3884 Adds the given \a command to the undo history
3885 of the line control. Does not apply the command.
3886*/
3887void QQuickTextInputPrivate::addCommand(const Command &cmd)
3888{
3889 if (m_separator && m_undoState && m_history[m_undoState - 1].type != Separator) {
3890 m_history.resize(size: m_undoState + 2);
3891 m_history[m_undoState++] = Command(Separator, m_cursor, u'\0', m_selstart, m_selend);
3892 } else {
3893 m_history.resize(size: m_undoState + 1);
3894 }
3895 m_separator = false;
3896 m_history[m_undoState++] = cmd;
3897}
3898
3899/*!
3900 \internal
3901
3902 Inserts the given string \a s into the line
3903 control.
3904
3905 Also adds the appropriate commands into the undo history.
3906 This function does not call finishChange(), and may leave the text
3907 in an invalid state.
3908*/
3909void QQuickTextInputPrivate::internalInsert(const QString &s)
3910{
3911 Q_Q(QQuickTextInput);
3912 if (m_echoMode == QQuickTextInput::Password) {
3913 if (m_passwordMaskDelay > 0)
3914 m_passwordEchoTimer.start(msec: m_passwordMaskDelay, obj: q);
3915 }
3916 Q_ASSERT(!hasSelectedText()); // insert(), processInputMethodEvent() call removeSelectedText() first.
3917 if (m_maskData) {
3918 QString ms = maskString(pos: m_cursor, str: s);
3919 for (int i = 0; i < ms.size(); ++i) {
3920 addCommand (cmd: Command(DeleteSelection, m_cursor + i, m_text.at(i: m_cursor + i), -1, -1));
3921 addCommand(cmd: Command(Insert, m_cursor + i, ms.at(i), -1, -1));
3922 }
3923 m_text.replace(i: m_cursor, len: ms.size(), after: ms);
3924 m_cursor += ms.size();
3925 m_cursor = nextMaskBlank(pos: m_cursor);
3926 m_textDirty = true;
3927 } else {
3928 int remaining = m_maxLength - m_text.size();
3929 if (remaining != 0) {
3930 const QStringView remainingStr = QStringView{s}.left(n: remaining);
3931 m_text.insert(i: m_cursor, v: remainingStr);
3932 for (auto e : remainingStr)
3933 addCommand(cmd: Command(Insert, m_cursor++, e, -1, -1));
3934 m_textDirty = true;
3935 }
3936 }
3937}
3938
3939/*!
3940 \internal
3941
3942 deletes a single character from the current text. If \a wasBackspace,
3943 the character prior to the cursor is removed. Otherwise the character
3944 after the cursor is removed.
3945
3946 Also adds the appropriate commands into the undo history.
3947 This function does not call finishChange(), and may leave the text
3948 in an invalid state.
3949*/
3950void QQuickTextInputPrivate::internalDelete(bool wasBackspace)
3951{
3952 if (m_cursor < m_text.size()) {
3953 cancelPasswordEchoTimer();
3954 Q_ASSERT(!hasSelectedText()); // del(), backspace() call removeSelectedText() first.
3955 addCommand(cmd: Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)),
3956 m_cursor, m_text.at(i: m_cursor), -1, -1));
3957 if (m_maskData) {
3958 m_text.replace(i: m_cursor, len: 1, after: clearString(pos: m_cursor, len: 1));
3959 addCommand(cmd: Command(Insert, m_cursor, m_text.at(i: m_cursor), -1, -1));
3960 } else {
3961 m_text.remove(i: m_cursor, len: 1);
3962 }
3963 m_textDirty = true;
3964 }
3965}
3966
3967/*!
3968 \internal
3969
3970 removes the currently selected text from the line control.
3971
3972 Also adds the appropriate commands into the undo history.
3973 This function does not call finishChange(), and may leave the text
3974 in an invalid state.
3975*/
3976void QQuickTextInputPrivate::removeSelectedText()
3977{
3978 if (m_selstart < m_selend && m_selend <= m_text.size()) {
3979 cancelPasswordEchoTimer();
3980 int i ;
3981 if (m_selstart <= m_cursor && m_cursor < m_selend) {
3982 // cursor is within the selection. Split up the commands
3983 // to be able to restore the correct cursor position
3984 for (i = m_cursor; i >= m_selstart; --i)
3985 addCommand (cmd: Command(DeleteSelection, i, m_text.at(i), -1, 1));
3986 for (i = m_selend - 1; i > m_cursor; --i)
3987 addCommand (cmd: Command(DeleteSelection, i - m_cursor + m_selstart - 1, m_text.at(i), -1, -1));
3988 } else {
3989 for (i = m_selend-1; i >= m_selstart; --i)
3990 addCommand (cmd: Command(RemoveSelection, i, m_text.at(i), -1, -1));
3991 }
3992 if (m_maskData) {
3993 m_text.replace(i: m_selstart, len: m_selend - m_selstart, after: clearString(pos: m_selstart, len: m_selend - m_selstart));
3994 for (int i = 0; i < m_selend - m_selstart; ++i)
3995 addCommand(cmd: Command(Insert, m_selstart + i, m_text.at(i: m_selstart + i), -1, -1));
3996 } else {
3997 m_text.remove(i: m_selstart, len: m_selend - m_selstart);
3998 }
3999 if (m_cursor > m_selstart)
4000 m_cursor -= qMin(a: m_cursor, b: m_selend) - m_selstart;
4001 internalDeselect();
4002 m_textDirty = true;
4003 }
4004}
4005
4006/*!
4007 \internal
4008
4009 Adds the current selection to the undo history.
4010
4011 Returns true if there is a current selection and false otherwise.
4012*/
4013
4014bool QQuickTextInputPrivate::separateSelection()
4015{
4016 if (hasSelectedText()) {
4017 separate();
4018 addCommand(cmd: Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
4019 return true;
4020 } else {
4021 return false;
4022 }
4023}
4024
4025/*!
4026 \internal
4027
4028 Parses the input mask specified by \a maskFields to generate
4029 the mask data used to handle input masks.
4030*/
4031void QQuickTextInputPrivate::parseInputMask(const QString &maskFields)
4032{
4033 int delimiter = maskFields.indexOf(ch: QLatin1Char(';'));
4034 if (maskFields.isEmpty() || delimiter == 0) {
4035 if (m_maskData) {
4036 m_maskData.reset(nullptr);
4037 m_maxLength = 32767;
4038 internalSetText(txt: QString());
4039 }
4040 return;
4041 }
4042
4043 if (delimiter == -1) {
4044 m_blank = QLatin1Char(' ');
4045 m_inputMask = maskFields;
4046 } else {
4047 m_inputMask = maskFields.left(n: delimiter);
4048 m_blank = (delimiter + 1 < maskFields.size()) ? maskFields[delimiter + 1] : QLatin1Char(' ');
4049 }
4050
4051 // calculate m_maxLength / m_maskData length
4052 m_maxLength = 0;
4053 QChar c = u'\0';
4054 for (int i=0; i<m_inputMask.size(); i++) {
4055 c = m_inputMask.at(i);
4056 if (i > 0 && m_inputMask.at(i: i-1) == QLatin1Char('\\')) {
4057 m_maxLength++;
4058 continue;
4059 }
4060 if (c != QLatin1Char('\\') && c != QLatin1Char('!') &&
4061 c != QLatin1Char('<') && c != QLatin1Char('>') &&
4062 c != QLatin1Char('{') && c != QLatin1Char('}') &&
4063 c != QLatin1Char('[') && c != QLatin1Char(']'))
4064 m_maxLength++;
4065 }
4066
4067 m_maskData.reset(p: new MaskInputData[m_maxLength]);
4068
4069 MaskInputData::Casemode m = MaskInputData::NoCaseMode;
4070 c = u'\0';
4071 bool s;
4072 bool escape = false;
4073 int index = 0;
4074 for (int i = 0; i < m_inputMask.size(); i++) {
4075 c = m_inputMask.at(i);
4076 if (escape) {
4077 s = true;
4078 m_maskData[index].maskChar = c;
4079 m_maskData[index].separator = s;
4080 m_maskData[index].caseMode = m;
4081 index++;
4082 escape = false;
4083 } else if (c == QLatin1Char('<')) {
4084 m = MaskInputData::Lower;
4085 } else if (c == QLatin1Char('>')) {
4086 m = MaskInputData::Upper;
4087 } else if (c == QLatin1Char('!')) {
4088 m = MaskInputData::NoCaseMode;
4089 } else if (c != QLatin1Char('{') && c != QLatin1Char('}') && c != QLatin1Char('[') && c != QLatin1Char(']')) {
4090 switch (c.unicode()) {
4091 case 'A':
4092 case 'a':
4093 case 'N':
4094 case 'n':
4095 case 'X':
4096 case 'x':
4097 case '9':
4098 case '0':
4099 case 'D':
4100 case 'd':
4101 case '#':
4102 case 'H':
4103 case 'h':
4104 case 'B':
4105 case 'b':
4106 s = false;
4107 break;
4108 case '\\':
4109 escape = true;
4110 Q_FALLTHROUGH();
4111 default:
4112 s = true;
4113 break;
4114 }
4115
4116 if (!escape) {
4117 m_maskData[index].maskChar = c;
4118 m_maskData[index].separator = s;
4119 m_maskData[index].caseMode = m;
4120 index++;
4121 }
4122 }
4123 }
4124 internalSetText(txt: m_text);
4125}
4126
4127
4128/*!
4129 \internal
4130
4131 checks if the key is valid compared to the inputMask
4132*/
4133bool QQuickTextInputPrivate::isValidInput(QChar key, QChar mask) const
4134{
4135 switch (mask.unicode()) {
4136 case 'A':
4137 if (key.isLetter())
4138 return true;
4139 break;
4140 case 'a':
4141 if (key.isLetter() || key == m_blank)
4142 return true;
4143 break;
4144 case 'N':
4145 if (key.isLetterOrNumber())
4146 return true;
4147 break;
4148 case 'n':
4149 if (key.isLetterOrNumber() || key == m_blank)
4150 return true;
4151 break;
4152 case 'X':
4153 if (key.isPrint() && key != m_blank)
4154 return true;
4155 break;
4156 case 'x':
4157 if (key.isPrint() || key == m_blank)
4158 return true;
4159 break;
4160 case '9':
4161 if (key.isNumber())
4162 return true;
4163 break;
4164 case '0':
4165 if (key.isNumber() || key == m_blank)
4166 return true;
4167 break;
4168 case 'D':
4169 if (key.isNumber() && key.digitValue() > 0)
4170 return true;
4171 break;
4172 case 'd':
4173 if ((key.isNumber() && key.digitValue() > 0) || key == m_blank)
4174 return true;
4175 break;
4176 case '#':
4177 if (key.isNumber() || key == QLatin1Char('+') || key == QLatin1Char('-') || key == m_blank)
4178 return true;
4179 break;
4180 case 'B':
4181 if (key == QLatin1Char('0') || key == QLatin1Char('1'))
4182 return true;
4183 break;
4184 case 'b':
4185 if (key == QLatin1Char('0') || key == QLatin1Char('1') || key == m_blank)
4186 return true;
4187 break;
4188 case 'H':
4189 if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')))
4190 return true;
4191 break;
4192 case 'h':
4193 if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')) || key == m_blank)
4194 return true;
4195 break;
4196 default:
4197 break;
4198 }
4199 return false;
4200}
4201
4202/*!
4203 \internal
4204
4205 Returns true if the given text \a str is valid for any
4206 validator or input mask set for the line control.
4207
4208 Otherwise returns false
4209*/
4210QQuickTextInputPrivate::ValidatorState QQuickTextInputPrivate::hasAcceptableInput(const QString &str) const
4211{
4212#if QT_CONFIG(validator)
4213 QString textCopy = str;
4214 int cursorCopy = m_cursor;
4215 if (m_validator) {
4216 QValidator::State state = m_validator->validate(textCopy, cursorCopy);
4217 if (state != QValidator::Acceptable)
4218 return ValidatorState(state);
4219 }
4220#endif
4221
4222 if (!m_maskData)
4223 return AcceptableInput;
4224
4225 if (str.size() != m_maxLength)
4226 return InvalidInput;
4227
4228 for (int i=0; i < m_maxLength; ++i) {
4229 if (m_maskData[i].separator) {
4230 if (str.at(i) != m_maskData[i].maskChar)
4231 return InvalidInput;
4232 } else {
4233 if (!isValidInput(key: str.at(i), mask: m_maskData[i].maskChar))
4234 return InvalidInput;
4235 }
4236 }
4237 return AcceptableInput;
4238}
4239
4240/*!
4241 \internal
4242
4243 Applies the inputMask on \a str starting from position \a pos in the mask. \a clear
4244 specifies from where characters should be gotten when a separator is met in \a str - true means
4245 that blanks will be used, false that previous input is used.
4246 Calling this when no inputMask is set is undefined.
4247*/
4248QString QQuickTextInputPrivate::maskString(uint pos, const QString &str, bool clear) const
4249{
4250 if (pos >= (uint)m_maxLength)
4251 return QString::fromLatin1(ba: "");
4252
4253 QString fill;
4254 fill = clear ? clearString(pos: 0, len: m_maxLength) : m_text;
4255
4256 int strIndex = 0;
4257 QString s = QString::fromLatin1(ba: "");
4258 int i = pos;
4259 while (i < m_maxLength) {
4260 if (strIndex < str.size()) {
4261 if (m_maskData[i].separator) {
4262 s += m_maskData[i].maskChar;
4263 if (str[strIndex] == m_maskData[i].maskChar)
4264 strIndex++;
4265 ++i;
4266 } else {
4267 if (isValidInput(key: str[strIndex], mask: m_maskData[i].maskChar)) {
4268 switch (m_maskData[i].caseMode) {
4269 case MaskInputData::Upper:
4270 s += str[strIndex].toUpper();
4271 break;
4272 case MaskInputData::Lower:
4273 s += str[strIndex].toLower();
4274 break;
4275 default:
4276 s += str[strIndex];
4277 }
4278 ++i;
4279 } else {
4280 // search for separator first
4281 int n = findInMask(pos: i, forward: true, findSeparator: true, searchChar: str[strIndex]);
4282 if (n != -1) {
4283 if (str.size() != 1 || i == 0 || (i > 0 && (!m_maskData[i-1].separator || m_maskData[i-1].maskChar != str[strIndex]))) {
4284 s += QStringView{fill}.mid(pos: i, n: n-i+1);
4285 i = n + 1; // update i to find + 1
4286 }
4287 } else {
4288 // search for valid m_blank if not
4289 n = findInMask(pos: i, forward: true, findSeparator: false, searchChar: str[strIndex]);
4290 if (n != -1) {
4291 s += QStringView{fill}.mid(pos: i, n: n-i);
4292 switch (m_maskData[n].caseMode) {
4293 case MaskInputData::Upper:
4294 s += str[strIndex].toUpper();
4295 break;
4296 case MaskInputData::Lower:
4297 s += str[strIndex].toLower();
4298 break;
4299 default:
4300 s += str[strIndex];
4301 }
4302 i = n + 1; // updates i to find + 1
4303 }
4304 }
4305 }
4306 ++strIndex;
4307 }
4308 } else
4309 break;
4310 }
4311
4312 return s;
4313}
4314
4315
4316
4317/*!
4318 \internal
4319
4320 Returns a "cleared" string with only separators and blank chars.
4321 Calling this when no inputMask is set is undefined.
4322*/
4323QString QQuickTextInputPrivate::clearString(uint pos, uint len) const
4324{
4325 if (pos >= (uint)m_maxLength)
4326 return QString();
4327
4328 QString s;
4329 int end = qMin(a: (uint)m_maxLength, b: pos + len);
4330 for (int i = pos; i < end; ++i)
4331 if (m_maskData[i].separator)
4332 s += m_maskData[i].maskChar;
4333 else
4334 s += m_blank;
4335
4336 return s;
4337}
4338
4339/*!
4340 \internal
4341
4342 Strips blank parts of the input in a QQuickTextInputPrivate when an inputMask is set,
4343 separators are still included. Typically "127.0__.0__.1__" becomes "127.0.0.1".
4344*/
4345QString QQuickTextInputPrivate::stripString(const QString &str) const
4346{
4347 if (!m_maskData)
4348 return str;
4349
4350 QString s;
4351 int end = qMin(a: m_maxLength, b: str.size());
4352 for (int i = 0; i < end; ++i) {
4353 if (m_maskData[i].separator)
4354 s += m_maskData[i].maskChar;
4355 else if (str[i] != m_blank)
4356 s += str[i];
4357 }
4358
4359 return s;
4360}
4361
4362/*!
4363 \internal
4364 searches forward/backward in m_maskData for either a separator or a m_blank
4365*/
4366int QQuickTextInputPrivate::findInMask(int pos, bool forward, bool findSeparator, QChar searchChar) const
4367{
4368 if (pos >= m_maxLength || pos < 0)
4369 return -1;
4370
4371 int end = forward ? m_maxLength : -1;
4372 int step = forward ? 1 : -1;
4373 int i = pos;
4374
4375 while (i != end) {
4376 if (findSeparator) {
4377 if (m_maskData[i].separator && m_maskData[i].maskChar == searchChar)
4378 return i;
4379 } else {
4380 if (!m_maskData[i].separator) {
4381 if (searchChar.isNull())
4382 return i;
4383 else if (isValidInput(key: searchChar, mask: m_maskData[i].maskChar))
4384 return i;
4385 }
4386 }
4387 i += step;
4388 }
4389 return -1;
4390}
4391
4392void QQuickTextInputPrivate::internalUndo(int until)
4393{
4394 if (!isUndoAvailable())
4395 return;
4396 cancelPasswordEchoTimer();
4397 internalDeselect();
4398 while (m_undoState && m_undoState > until) {
4399 Command& cmd = m_history[--m_undoState];
4400 switch (cmd.type) {
4401 case Insert:
4402 m_text.remove(i: cmd.pos, len: 1);
4403 m_cursor = cmd.pos;
4404 break;
4405 case SetSelection:
4406 m_selstart = cmd.selStart;
4407 m_selend = cmd.selEnd;
4408 m_cursor = cmd.pos;
4409 break;
4410 case Remove:
4411 case RemoveSelection:
4412 m_text.insert(i: cmd.pos, c: cmd.uc);
4413 m_cursor = cmd.pos + 1;
4414 break;
4415 case Delete:
4416 case DeleteSelection:
4417 m_text.insert(i: cmd.pos, c: cmd.uc);
4418 m_cursor = cmd.pos;
4419 break;
4420 case Separator:
4421 continue;
4422 }
4423 if (until < 0 && m_undoState) {
4424 Command& next = m_history[m_undoState-1];
4425 if (next.type != cmd.type
4426 && next.type < RemoveSelection
4427 && (cmd.type < RemoveSelection || next.type == Separator)) {
4428 break;
4429 }
4430 }
4431 }
4432 separate();
4433 m_textDirty = true;
4434}
4435
4436void QQuickTextInputPrivate::internalRedo()
4437{
4438 if (!isRedoAvailable())
4439 return;
4440 internalDeselect();
4441 while (m_undoState < m_history.size()) {
4442 Command& cmd = m_history[m_undoState++];
4443 switch (cmd.type) {
4444 case Insert:
4445 m_text.insert(i: cmd.pos, c: cmd.uc);
4446 m_cursor = cmd.pos + 1;
4447 break;
4448 case SetSelection:
4449 m_selstart = cmd.selStart;
4450 m_selend = cmd.selEnd;
4451 m_cursor = cmd.pos;
4452 break;
4453 case Remove:
4454 case Delete:
4455 case RemoveSelection:
4456 case DeleteSelection:
4457 m_text.remove(i: cmd.pos, len: 1);
4458 m_selstart = cmd.selStart;
4459 m_selend = cmd.selEnd;
4460 m_cursor = cmd.pos;
4461 break;
4462 case Separator:
4463 m_selstart = cmd.selStart;
4464 m_selend = cmd.selEnd;
4465 m_cursor = cmd.pos;
4466 break;
4467 }
4468 if (m_undoState < m_history.size()) {
4469 Command& next = m_history[m_undoState];
4470 if (next.type != cmd.type
4471 && cmd.type < RemoveSelection
4472 && next.type != Separator
4473 && (next.type < RemoveSelection || cmd.type == Separator)) {
4474 break;
4475 }
4476 }
4477 }
4478 m_textDirty = true;
4479}
4480
4481void QQuickTextInputPrivate::emitUndoRedoChanged()
4482{
4483 Q_Q(QQuickTextInput);
4484 const bool previousUndo = canUndo;
4485 const bool previousRedo = canRedo;
4486
4487 canUndo = isUndoAvailable();
4488 canRedo = isRedoAvailable();
4489
4490 if (previousUndo != canUndo)
4491 emit q->canUndoChanged();
4492 if (previousRedo != canRedo)
4493 emit q->canRedoChanged();
4494}
4495
4496/*!
4497 \internal
4498
4499 If the current cursor position differs from the last emitted cursor
4500 position, emits cursorPositionChanged().
4501*/
4502bool QQuickTextInputPrivate::emitCursorPositionChanged()
4503{
4504 Q_Q(QQuickTextInput);
4505 if (m_cursor != m_lastCursorPos) {
4506 m_lastCursorPos = m_cursor;
4507
4508 q->updateCursorRectangle();
4509 emit q->cursorPositionChanged();
4510
4511 if (!hasSelectedText()) {
4512 if (lastSelectionStart != m_cursor) {
4513 lastSelectionStart = m_cursor;
4514 emit q->selectionStartChanged();
4515 }
4516 if (lastSelectionEnd != m_cursor) {
4517 lastSelectionEnd = m_cursor;
4518 emit q->selectionEndChanged();
4519 }
4520 }
4521
4522#if QT_CONFIG(accessibility)
4523 if (QAccessible::isActive()) {
4524 if (QObject *acc = QQuickAccessibleAttached::findAccessible(object: q, role: QAccessible::EditableText)) {
4525 QAccessibleTextCursorEvent ev(acc, m_cursor);
4526 QAccessible::updateAccessibility(event: &ev);
4527 }
4528 }
4529#endif
4530
4531 return true;
4532 }
4533 return false;
4534}
4535
4536
4537void QQuickTextInputPrivate::setBlinkingCursorEnabled(bool enable)
4538{
4539 if (enable == m_blinkEnabled)
4540 return;
4541
4542 m_blinkEnabled = enable;
4543 updateCursorBlinking();
4544
4545 if (enable)
4546 connect(qApp->styleHints(), signal: &QStyleHints::cursorFlashTimeChanged, receiverPrivate: this, slot: &QQuickTextInputPrivate::updateCursorBlinking);
4547 else
4548 disconnect(qApp->styleHints(), signal: &QStyleHints::cursorFlashTimeChanged, receiverPrivate: this, slot: &QQuickTextInputPrivate::updateCursorBlinking);
4549}
4550
4551void QQuickTextInputPrivate::updateCursorBlinking()
4552{
4553 Q_Q(QQuickTextInput);
4554
4555 if (m_blinkTimer) {
4556 q->killTimer(id: m_blinkTimer);
4557 m_blinkTimer = 0;
4558 }
4559
4560 if (m_blinkEnabled && cursorVisible && !cursorItem && !m_readOnly) {
4561 int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
4562 if (flashTime >= 2)
4563 m_blinkTimer = q->startTimer(interval: flashTime / 2);
4564 }
4565
4566 m_blinkStatus = 1;
4567 updateType = UpdatePaintNode;
4568 q->polish();
4569 q->update();
4570}
4571
4572void QQuickTextInput::timerEvent(QTimerEvent *event)
4573{
4574 Q_D(QQuickTextInput);
4575 if (event->timerId() == d->m_blinkTimer) {
4576 d->m_blinkStatus = !d->m_blinkStatus;
4577 d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
4578 polish();
4579 update();
4580 } else if (event->timerId() == d->m_passwordEchoTimer.timerId()) {
4581 d->m_passwordEchoTimer.stop();
4582 d->updateDisplayText();
4583 updateCursorRectangle();
4584 }
4585}
4586
4587void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
4588{
4589 Q_Q(QQuickTextInput);
4590
4591 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
4592 if (hasAcceptableInput(str: m_text) == AcceptableInput || fixup()) {
4593
4594 QInputMethod *inputMethod = QGuiApplication::inputMethod();
4595 inputMethod->commit();
4596
4597 if (activeFocus) {
4598 // If we lost focus after hiding the virtual keyboard, we've already emitted
4599 // editingFinished from handleFocusEvent. Otherwise we emit it now.
4600 emit q->editingFinished();
4601 }
4602
4603 emit q->accepted();
4604 }
4605 event->ignore();
4606 return;
4607 }
4608
4609 if (m_blinkEnabled)
4610 updateCursorBlinking();
4611
4612 if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit
4613 && !m_passwordEchoEditing
4614 && !m_readOnly
4615 && !event->text().isEmpty()
4616 && !(event->modifiers() & Qt::ControlModifier)) {
4617 // Clear the edit and reset to normal echo mode while editing; the
4618 // echo mode switches back when the edit loses focus
4619 // ### resets current content. dubious code; you can
4620 // navigate with keys up, down, back, and select(?), but if you press
4621 // "left" or "right" it clears?
4622 updatePasswordEchoEditing(editing: true);
4623 clear();
4624 }
4625
4626 bool unknown = false;
4627#if QT_CONFIG(shortcut)
4628 bool visual = cursorMoveStyle() == Qt::VisualMoveStyle;
4629#endif
4630
4631 if (false) {
4632 }
4633#if QT_CONFIG(shortcut)
4634 else if (event == QKeySequence::Undo) {
4635 q->undo();
4636 }
4637 else if (event == QKeySequence::Redo) {
4638 q->redo();
4639 }
4640 else if (event == QKeySequence::SelectAll) {
4641 selectAll();
4642 }
4643#if QT_CONFIG(clipboard)
4644 else if (event == QKeySequence::Copy) {
4645 copy();
4646 }
4647 else if (event == QKeySequence::Paste) {
4648 if (!m_readOnly) {
4649 QClipboard::Mode mode = QClipboard::Clipboard;
4650 paste(clipboardMode: mode);
4651 }
4652 }
4653 else if (event == QKeySequence::Cut) {
4654 q->cut();
4655 }
4656 else if (event == QKeySequence::DeleteEndOfLine) {
4657 if (!m_readOnly)
4658 deleteEndOfLine();
4659 }
4660#endif // clipboard
4661 else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) {
4662 home(mark: 0);
4663 }
4664 else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) {
4665 end(mark: 0);
4666 }
4667 else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) {
4668 home(mark: 1);
4669 }
4670 else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) {
4671 end(mark: 1);
4672 }
4673 else if (event == QKeySequence::MoveToNextChar) {
4674 if (hasSelectedText()) {
4675 moveCursor(pos: selectionEnd(), mark: false);
4676 } else {
4677 cursorForward(mark: 0, steps: visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
4678 }
4679 }
4680 else if (event == QKeySequence::SelectNextChar) {
4681 cursorForward(mark: 1, steps: visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
4682 }
4683 else if (event == QKeySequence::MoveToPreviousChar) {
4684 if (hasSelectedText()) {
4685 moveCursor(pos: selectionStart(), mark: false);
4686 } else {
4687 cursorForward(mark: 0, steps: visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
4688 }
4689 }
4690 else if (event == QKeySequence::SelectPreviousChar) {
4691 cursorForward(mark: 1, steps: visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
4692 }
4693 else if (event == QKeySequence::MoveToNextWord) {
4694 if (m_echoMode == QQuickTextInput::Normal)
4695 layoutDirection() == Qt::LeftToRight ? cursorWordForward(mark: 0) : cursorWordBackward(mark: 0);
4696 else
4697 layoutDirection() == Qt::LeftToRight ? end(mark: 0) : home(mark: 0);
4698 }
4699 else if (event == QKeySequence::MoveToPreviousWord) {
4700 if (m_echoMode == QQuickTextInput::Normal)
4701 layoutDirection() == Qt::LeftToRight ? cursorWordBackward(mark: 0) : cursorWordForward(mark: 0);
4702 else if (!m_readOnly) {
4703 layoutDirection() == Qt::LeftToRight ? home(mark: 0) : end(mark: 0);
4704 }
4705 }
4706 else if (event == QKeySequence::SelectNextWord) {
4707 if (m_echoMode == QQuickTextInput::Normal)
4708 layoutDirection() == Qt::LeftToRight ? cursorWordForward(mark: 1) : cursorWordBackward(mark: 1);
4709 else
4710 layoutDirection() == Qt::LeftToRight ? end(mark: 1) : home(mark: 1);
4711 }
4712 else if (event == QKeySequence::SelectPreviousWord) {
4713 if (m_echoMode == QQuickTextInput::Normal)
4714 layoutDirection() == Qt::LeftToRight ? cursorWordBackward(mark: 1) : cursorWordForward(mark: 1);
4715 else
4716 layoutDirection() == Qt::LeftToRight ? home(mark: 1) : end(mark: 1);
4717 }
4718 else if (event == QKeySequence::Delete) {
4719 if (!m_readOnly)
4720 del();
4721 }
4722 else if (event == QKeySequence::DeleteEndOfWord) {
4723 if (!m_readOnly)
4724 deleteEndOfWord();
4725 }
4726 else if (event == QKeySequence::DeleteStartOfWord) {
4727 if (!m_readOnly)
4728 deleteStartOfWord();
4729 } else if (event == QKeySequence::DeleteCompleteLine) {
4730 if (!m_readOnly) {
4731 selectAll();
4732#if QT_CONFIG(clipboard)
4733 copy();
4734#endif
4735 del();
4736 }
4737 }
4738#endif // shortcut
4739 else {
4740 bool handled = false;
4741 if (event->modifiers() & Qt::ControlModifier) {
4742 switch (event->key()) {
4743 case Qt::Key_Backspace:
4744 if (!m_readOnly)
4745 deleteStartOfWord();
4746 break;
4747 default:
4748 if (!handled)
4749 unknown = true;
4750 }
4751 } else { // ### check for *no* modifier
4752 switch (event->key()) {
4753 case Qt::Key_Backspace:
4754 if (!m_readOnly) {
4755 backspace();
4756 }
4757 break;
4758 default:
4759 if (!handled)
4760 unknown = true;
4761 }
4762 }
4763 }
4764
4765 if (event->key() == Qt::Key_Direction_L || event->key() == Qt::Key_Direction_R) {
4766 setLayoutDirection((event->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
4767 unknown = false;
4768 }
4769
4770 if (unknown && !m_readOnly) {
4771 if (m_inputControl->isAcceptableInput(event)) {
4772 if (overwriteMode
4773 // no need to call del() if we have a selection, insert
4774 // does it already
4775 && !hasSelectedText()
4776 && !(m_cursor == q_func()->text().size())) {
4777 del();
4778 }
4779
4780 insert(newText: event->text());
4781 event->accept();
4782 return;
4783 }
4784 }
4785
4786 if (unknown)
4787 event->ignore();
4788 else
4789 event->accept();
4790}
4791
4792/*!
4793 \internal
4794
4795 Deletes the portion of the word before the current cursor position.
4796*/
4797
4798void QQuickTextInputPrivate::deleteStartOfWord()
4799{
4800 int priorState = m_undoState;
4801
4802 if (!separateSelection()) {
4803 Command cmd(SetSelection, m_cursor, u'\0', m_selstart, m_selend);
4804 separate();
4805 cursorWordBackward(mark: true);
4806 addCommand(cmd);
4807 }
4808
4809 removeSelectedText();
4810 finishChange(validateFromState: priorState);
4811}
4812
4813/*!
4814 \internal
4815
4816 Deletes the portion of the word after the current cursor position.
4817*/
4818
4819void QQuickTextInputPrivate::deleteEndOfWord()
4820{
4821 int priorState = m_undoState;
4822 Command cmd(SetSelection, m_cursor, u'\0', m_selstart, m_selend);
4823 separate();
4824 cursorWordForward(mark: true);
4825 // moveCursor (sometimes) calls separate() so we need to add the command after that so the
4826 // cursor position and selection are restored in the same undo operation as the remove.
4827 addCommand(cmd);
4828 removeSelectedText();
4829 finishChange(validateFromState: priorState);
4830}
4831
4832/*!
4833 \internal
4834
4835 Deletes all text from the cursor position to the end of the line.
4836*/
4837
4838void QQuickTextInputPrivate::deleteEndOfLine()
4839{
4840 int priorState = m_undoState;
4841 Command cmd(SetSelection, m_cursor, u'\0', m_selstart, m_selend);
4842 separate();
4843 setSelection(start: m_cursor, length: end());
4844 addCommand(cmd);
4845 removeSelectedText();
4846 finishChange(validateFromState: priorState);
4847}
4848
4849/*!
4850 \qmlmethod QtQuick::TextInput::ensureVisible(int position)
4851 \since 5.4
4852
4853 Scrolls the contents of the text input so that the specified character
4854 \a position is visible inside the boundaries of the text input.
4855
4856 \sa autoScroll
4857*/
4858void QQuickTextInput::ensureVisible(int position)
4859{
4860 Q_D(QQuickTextInput);
4861 d->ensureVisible(position);
4862 updateCursorRectangle(scroll: false);
4863}
4864
4865/*!
4866 \qmlmethod QtQuick::TextInput::clear()
4867 \since 5.7
4868
4869 Clears the contents of the text input
4870 and resets partial text input from an input method.
4871
4872 Use this method instead of setting the \l text property to an empty string.
4873
4874 \sa QInputMethod::reset()
4875*/
4876void QQuickTextInput::clear()
4877{
4878 Q_D(QQuickTextInput);
4879 d->cancelInput();
4880 d->clear();
4881}
4882
4883/*!
4884 \since 5.6
4885 \qmlproperty real QtQuick::TextInput::padding
4886 \qmlproperty real QtQuick::TextInput::topPadding
4887 \qmlproperty real QtQuick::TextInput::leftPadding
4888 \qmlproperty real QtQuick::TextInput::bottomPadding
4889 \qmlproperty real QtQuick::TextInput::rightPadding
4890
4891 These properties hold the padding around the content. This space is reserved
4892 in addition to the contentWidth and contentHeight.
4893
4894 The individual padding properties assume the value of the \c padding
4895 property unless they are set explicitly. For example, if \c padding is
4896 set to \c 4 and \c leftPadding to \c 8, \c 8 will be used as the left
4897 padding.
4898
4899 \note If an explicit width or height is given to a TextInput, care must be
4900 taken to ensure it is large enough to accommodate the relevant padding
4901 values. For example: if \c topPadding and \c bottomPadding are set to
4902 \c 10, but the height of the TextInput is only set to \c 20, the text will
4903 not have enough vertical space in which to be rendered, and will appear
4904 clipped.
4905*/
4906qreal QQuickTextInput::padding() const
4907{
4908 Q_D(const QQuickTextInput);
4909 return d->padding();
4910}
4911
4912void QQuickTextInput::setPadding(qreal padding)
4913{
4914 Q_D(QQuickTextInput);
4915 if (qFuzzyCompare(p1: d->padding(), p2: padding))
4916 return;
4917
4918 d->extra.value().padding = padding;
4919 d->updateLayout();
4920 updateCursorRectangle();
4921 emit paddingChanged();
4922 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
4923 emit topPaddingChanged();
4924 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
4925 emit leftPaddingChanged();
4926 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
4927 emit rightPaddingChanged();
4928 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
4929 emit bottomPaddingChanged();
4930}
4931
4932void QQuickTextInput::resetPadding()
4933{
4934 setPadding(0);
4935}
4936
4937qreal QQuickTextInput::topPadding() const
4938{
4939 Q_D(const QQuickTextInput);
4940 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
4941 return d->extra->topPadding;
4942 return d->padding();
4943}
4944
4945void QQuickTextInput::setTopPadding(qreal padding)
4946{
4947 Q_D(QQuickTextInput);
4948 d->setTopPadding(value: padding);
4949}
4950
4951void QQuickTextInput::resetTopPadding()
4952{
4953 Q_D(QQuickTextInput);
4954 d->setTopPadding(value: 0, reset: true);
4955}
4956
4957qreal QQuickTextInput::leftPadding() const
4958{
4959 Q_D(const QQuickTextInput);
4960 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
4961 return d->extra->leftPadding;
4962 return d->padding();
4963}
4964
4965void QQuickTextInput::setLeftPadding(qreal padding)
4966{
4967 Q_D(QQuickTextInput);
4968 d->setLeftPadding(value: padding);
4969}
4970
4971void QQuickTextInput::resetLeftPadding()
4972{
4973 Q_D(QQuickTextInput);
4974 d->setLeftPadding(value: 0, reset: true);
4975}
4976
4977qreal QQuickTextInput::rightPadding() const
4978{
4979 Q_D(const QQuickTextInput);
4980 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
4981 return d->extra->rightPadding;
4982 return d->padding();
4983}
4984
4985void QQuickTextInput::setRightPadding(qreal padding)
4986{
4987 Q_D(QQuickTextInput);
4988 d->setRightPadding(value: padding);
4989}
4990
4991void QQuickTextInput::resetRightPadding()
4992{
4993 Q_D(QQuickTextInput);
4994 d->setRightPadding(value: 0, reset: true);
4995}
4996
4997qreal QQuickTextInput::bottomPadding() const
4998{
4999 Q_D(const QQuickTextInput);
5000 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
5001 return d->extra->bottomPadding;
5002 return d->padding();
5003}
5004
5005void QQuickTextInput::setBottomPadding(qreal padding)
5006{
5007 Q_D(QQuickTextInput);
5008 d->setBottomPadding(value: padding);
5009}
5010
5011void QQuickTextInput::resetBottomPadding()
5012{
5013 Q_D(QQuickTextInput);
5014 d->setBottomPadding(value: 0, reset: true);
5015}
5016
5017#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
5018void QQuickTextInput::setOldSelectionDefault()
5019{
5020 Q_D(QQuickTextInput);
5021 d->selectByMouse = false;
5022 d->selectByTouchDrag = true;
5023 qCDebug(lcQuickTextInput, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
5024}
5025
5026// TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
5027QQuickPre64TextInput::QQuickPre64TextInput(QQuickItem *parent)
5028 : QQuickTextInput(parent)
5029{
5030 setOldSelectionDefault();
5031}
5032#endif
5033
5034QT_END_NAMESPACE
5035
5036#include "moc_qquicktextinput_p.cpp"
5037

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