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 "qquicktextedit_p.h"
5#include "qquicktextedit_p_p.h"
6#include "qquicktextcontrol_p.h"
7#include "qquicktextdocument_p.h"
8#include "qquickevents_p_p.h"
9#include "qquickwindow.h"
10#include "qquicktextnode_p.h"
11#include "qquicktextnodeengine_p.h"
12
13#include <QtCore/qmath.h>
14#include <QtGui/qguiapplication.h>
15#include <QtGui/qevent.h>
16#include <QtGui/qpainter.h>
17#include <QtGui/qtextobject.h>
18#include <QtGui/qtexttable.h>
19#include <QtQml/qqmlinfo.h>
20#include <QtQuick/qsgsimplerectnode.h>
21
22#include <private/qqmlglobal_p.h>
23#include <private/qqmlproperty_p.h>
24#include <private/qtextengine_p.h>
25#include <private/qsgadaptationlayer_p.h>
26
27#include "qquicktextdocument.h"
28
29#include <algorithm>
30
31QT_BEGIN_NAMESPACE
32
33Q_DECLARE_LOGGING_CATEGORY(lcVP)
34Q_LOGGING_CATEGORY(lcTextEdit, "qt.quick.textedit")
35
36/*!
37 \qmltype TextEdit
38 \instantiates QQuickTextEdit
39 \inqmlmodule QtQuick
40 \ingroup qtquick-visual
41 \ingroup qtquick-input
42 \inherits Item
43 \brief Displays multiple lines of editable formatted text.
44
45 The TextEdit item displays a block of editable, formatted text.
46
47 It can display both plain and rich text. For example:
48
49 \qml
50TextEdit {
51 width: 240
52 text: "<b>Hello</b> <i>World!</i>"
53 font.family: "Helvetica"
54 font.pointSize: 20
55 color: "blue"
56 focus: true
57}
58 \endqml
59
60 \image declarative-textedit.gif
61
62 Setting \l {Item::focus}{focus} to \c true enables the TextEdit item to receive keyboard focus.
63
64 Note that the TextEdit does not implement scrolling, following the cursor, or other behaviors specific
65 to a look and feel. For example, to add flickable scrolling that follows the cursor:
66
67 \snippet qml/texteditor.qml 0
68
69 A particular look and feel might use smooth scrolling (eg. using SmoothedAnimation), might have a visible
70 scrollbar, or a scrollbar that fades in to show location, etc.
71
72 Clipboard support is provided by the cut(), copy(), and paste() functions.
73 Text can be selected by mouse in the usual way, unless \l selectByMouse is
74 set to \c false; and by keyboard with the \c {Shift+arrow} key
75 combinations, unless \l selectByKeyboard is set to \c false. To select text
76 programmatically, you can set the \l selectionStart and \l selectionEnd
77 properties, or use \l selectAll() or \l selectWord().
78
79 You can translate between cursor positions (characters from the start of the document) and pixel
80 points using positionAt() and positionToRectangle().
81
82 \sa Text, TextInput
83*/
84
85/*!
86 \qmlsignal QtQuick::TextEdit::linkActivated(string link)
87
88 This signal is emitted when the user clicks on a link embedded in the text.
89 The link must be in rich text or HTML format and the
90 \a link string provides access to the particular link.
91*/
92
93// This is a pretty arbitrary figure. The idea is that we don't want to break down the document
94// into text nodes corresponding to a text block each so that the glyph node grouping doesn't become pointless.
95static const int nodeBreakingSize = 300;
96
97#if !defined(QQUICKTEXT_LARGETEXT_THRESHOLD)
98 #define QQUICKTEXT_LARGETEXT_THRESHOLD 10000
99#endif
100// if QString::size() > largeTextSizeThreshold, we render more often, but only visible lines
101const int QQuickTextEditPrivate::largeTextSizeThreshold = QQUICKTEXT_LARGETEXT_THRESHOLD;
102
103namespace {
104 class RootNode : public QSGTransformNode
105 {
106 public:
107 RootNode() : cursorNode(nullptr), frameDecorationsNode(nullptr)
108 { }
109
110 void resetFrameDecorations(QQuickTextNode* newNode)
111 {
112 if (frameDecorationsNode) {
113 removeChildNode(node: frameDecorationsNode);
114 delete frameDecorationsNode;
115 }
116 frameDecorationsNode = newNode;
117 newNode->setFlag(QSGNode::OwnedByParent);
118 }
119
120 void resetCursorNode(QSGInternalRectangleNode* newNode)
121 {
122 if (cursorNode)
123 removeChildNode(node: cursorNode);
124 delete cursorNode;
125 cursorNode = newNode;
126 if (cursorNode) {
127 appendChildNode(node: cursorNode);
128 cursorNode->setFlag(QSGNode::OwnedByParent);
129 }
130 }
131
132 QSGInternalRectangleNode *cursorNode;
133 QQuickTextNode* frameDecorationsNode;
134
135 };
136}
137
138QQuickTextEdit::QQuickTextEdit(QQuickItem *parent)
139: QQuickImplicitSizeItem(*(new QQuickTextEditPrivate), parent)
140{
141 Q_D(QQuickTextEdit);
142 d->init();
143}
144
145QQuickTextEdit::QQuickTextEdit(QQuickTextEditPrivate &dd, QQuickItem *parent)
146: QQuickImplicitSizeItem(dd, parent)
147{
148 Q_D(QQuickTextEdit);
149 d->init();
150}
151
152QString QQuickTextEdit::text() const
153{
154 Q_D(const QQuickTextEdit);
155 if (!d->textCached && isComponentComplete()) {
156 QQuickTextEditPrivate *d = const_cast<QQuickTextEditPrivate *>(d_func());
157#if QT_CONFIG(texthtmlparser)
158 if (d->richText)
159 d->text = d->control->toHtml();
160 else
161#endif
162#if QT_CONFIG(textmarkdownwriter)
163 if (d->markdownText)
164 d->text = d->control->toMarkdown();
165 else
166#endif
167 d->text = d->control->toPlainText();
168 d->textCached = true;
169 }
170 return d->text;
171}
172
173/*!
174 \qmlproperty string QtQuick::TextEdit::font.family
175
176 Sets the family name of the font.
177
178 The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
179 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
180 If the family isn't available a family will be set using the font matching algorithm.
181*/
182
183/*!
184 \qmlproperty string QtQuick::TextEdit::font.styleName
185 \since 5.6
186
187 Sets the style name of the font.
188
189 The style name is case insensitive. If set, the font will be matched against style name instead
190 of the font properties \l font.weight, \l font.bold and \l font.italic.
191*/
192
193
194/*!
195 \qmlproperty bool QtQuick::TextEdit::font.bold
196
197 Sets whether the font weight is bold.
198*/
199
200/*!
201 \qmlproperty int QtQuick::TextEdit::font.weight
202
203 The requested weight of the font. The weight requested must be an integer
204 between 1 and 1000, or one of the predefined values:
205
206 \value Font.Thin 100
207 \value Font.ExtraLight 200
208 \value Font.Light 300
209 \value Font.Normal 400 (default)
210 \value Font.Medium 500
211 \value Font.DemiBold 600
212 \value Font.Bold 700
213 \value Font.ExtraBold 800
214 \value Font.Black 900
215
216 \qml
217 TextEdit { text: "Hello"; font.weight: Font.DemiBold }
218 \endqml
219*/
220
221/*!
222 \qmlproperty bool QtQuick::TextEdit::font.italic
223
224 Sets whether the font has an italic style.
225*/
226
227/*!
228 \qmlproperty bool QtQuick::TextEdit::font.underline
229
230 Sets whether the text is underlined.
231*/
232
233/*!
234 \qmlproperty bool QtQuick::TextEdit::font.strikeout
235
236 Sets whether the font has a strikeout style.
237*/
238
239/*!
240 \qmlproperty real QtQuick::TextEdit::font.pointSize
241
242 Sets the font size in points. The point size must be greater than zero.
243*/
244
245/*!
246 \qmlproperty int QtQuick::TextEdit::font.pixelSize
247
248 Sets the font size in pixels.
249
250 Using this function makes the font device dependent. Use
251 \l{TextEdit::font.pointSize} to set the size of the font in a
252 device independent manner.
253*/
254
255/*!
256 \qmlproperty real QtQuick::TextEdit::font.letterSpacing
257
258 Sets the letter spacing for the font.
259
260 Letter spacing changes the default spacing between individual letters in the font.
261 A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
262*/
263
264/*!
265 \qmlproperty real QtQuick::TextEdit::font.wordSpacing
266
267 Sets the word spacing for the font.
268
269 Word spacing changes the default spacing between individual words.
270 A positive value increases the word spacing by a corresponding amount of pixels,
271 while a negative value decreases the inter-word spacing accordingly.
272*/
273
274/*!
275 \qmlproperty enumeration QtQuick::TextEdit::font.capitalization
276
277 Sets the capitalization for the text.
278
279 \value Font.MixedCase no capitalization change is applied
280 \value Font.AllUppercase alters the text to be rendered in all uppercase type
281 \value Font.AllLowercase alters the text to be rendered in all lowercase type
282 \value Font.SmallCaps alters the text to be rendered in small-caps type
283 \value Font.Capitalize alters the text to be rendered with the first character of
284 each word as an uppercase character
285
286 \qml
287 TextEdit { text: "Hello"; font.capitalization: Font.AllLowercase }
288 \endqml
289*/
290
291/*!
292 \qmlproperty enumeration QtQuick::TextEdit::font.hintingPreference
293 \since 5.8
294
295 Sets the preferred hinting on the text. This is a hint to the underlying text rendering system
296 to use a certain level of hinting, and has varying support across platforms. See the table in
297 the documentation for QFont::HintingPreference for more details.
298
299 \note This property only has an effect when used together with render type TextEdit.NativeRendering.
300
301 \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
302 \value Font.PreferNoHinting If possible, render text without hinting the outlines
303 of the glyphs. The text layout will be typographically accurate, using the same metrics
304 as are used e.g. when printing.
305 \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
306 but align glyphs to the pixel grid in the vertical direction. The text will appear
307 crisper on displays where the density is too low to give an accurate rendering
308 of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
309 layout will be scalable to higher density devices (such as printers) without impacting
310 details such as line breaks.
311 \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
312 vertical directions. The text will be altered to optimize legibility on the target
313 device, but since the metrics will depend on the target size of the text, the positions
314 of glyphs, line breaks, and other typographical detail will not scale, meaning that a
315 text layout may look different on devices with different pixel densities.
316
317 \qml
318 TextEdit { text: "Hello"; renderType: TextEdit.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
319 \endqml
320*/
321
322/*!
323 \qmlproperty bool QtQuick::TextEdit::font.kerning
324 \since 5.10
325
326 Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
327 improve performance when creating or changing the text, at the expense of some cosmetic
328 features. The default value is true.
329
330 \qml
331 TextEdit { text: "OATS FLAVOUR WAY"; kerning: font.false }
332 \endqml
333*/
334
335/*!
336 \qmlproperty bool QtQuick::TextEdit::font.preferShaping
337 \since 5.10
338
339 Sometimes, a font will apply complex rules to a set of characters in order to
340 display them correctly. In some writing systems, such as Brahmic scripts, this is
341 required in order for the text to be legible, but in e.g. Latin script, it is merely
342 a cosmetic feature. Setting the \c preferShaping property to false will disable all
343 such features when they are not required, which will improve performance in most cases.
344
345 The default value is true.
346
347 \qml
348 TextEdit { text: "Some text"; font.preferShaping: false }
349 \endqml
350*/
351
352/*!
353 \qmlproperty object QtQuick::TextEdit::font.features
354 \since 6.6
355
356 \include qquicktext.cpp qml-font-features
357*/
358
359/*!
360 \qmlproperty string QtQuick::TextEdit::text
361
362 The text to display. If the text format is AutoText the text edit will
363 automatically determine whether the text should be treated as
364 rich text. This determination is made using Qt::mightBeRichText().
365 However, detection of Markdown is not automatic.
366
367 The text-property is mostly suitable for setting the initial content and
368 handling modifications to relatively small text content. The append(),
369 insert() and remove() methods provide more fine-grained control and
370 remarkably better performance for modifying especially large rich text
371 content.
372
373 Note that some keyboards use a predictive function. In this case,
374 the text being composed by the input method is not part of this property.
375 The part of the text related to the predictions is underlined and stored in
376 the \l preeditText property.
377
378 \sa clear(), preeditText, textFormat
379*/
380void QQuickTextEdit::setText(const QString &text)
381{
382 Q_D(QQuickTextEdit);
383 if (QQuickTextEdit::text() == text)
384 return;
385
386 d->document->clearResources();
387 d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text));
388 d->markdownText = d->format == MarkdownText;
389 if (!isComponentComplete()) {
390 d->text = text;
391 } else if (d->richText) {
392#if QT_CONFIG(texthtmlparser)
393 d->control->setHtml(text);
394#else
395 d->control->setPlainText(text);
396#endif
397 } else if (d->markdownText) {
398 d->control->setMarkdownText(text);
399 } else {
400 d->control->setPlainText(text);
401 }
402 setFlag(flag: QQuickItem::ItemObservesViewport, enabled: text.size() > QQuickTextEditPrivate::largeTextSizeThreshold);
403}
404
405void QQuickTextEdit::invalidate()
406{
407 QMetaObject::invokeMethod(object: this, function: &QQuickTextEdit::q_invalidate);
408}
409
410void QQuickTextEdit::q_invalidate()
411{
412 Q_D(QQuickTextEdit);
413 if (isComponentComplete()) {
414 if (d->document != nullptr)
415 d->document->markContentsDirty(from: 0, length: d->document->characterCount());
416 invalidateFontCaches();
417 d->updateType = QQuickTextEditPrivate::UpdateAll;
418 update();
419 }
420}
421
422/*!
423 \qmlproperty string QtQuick::TextEdit::preeditText
424 \readonly
425 \since 5.7
426
427 This property contains partial text input from an input method.
428
429 To turn off partial text that results from predictions, set the \c Qt.ImhNoPredictiveText
430 flag in inputMethodHints.
431
432 \sa inputMethodHints
433*/
434QString QQuickTextEdit::preeditText() const
435{
436 Q_D(const QQuickTextEdit);
437 return d->control->preeditText();
438}
439
440/*!
441 \qmlproperty enumeration QtQuick::TextEdit::textFormat
442
443 The way the \l text property should be displayed.
444
445 Supported text formats are:
446
447 \value TextEdit.PlainText (default) all styling tags are treated as plain text
448 \value TextEdit.AutoText detected via the Qt::mightBeRichText() heuristic
449 \value TextEdit.RichText \l {Supported HTML Subset} {a subset of HTML 4}
450 \value TextEdit.MarkdownText \l {https://commonmark.org/help/}{CommonMark} plus the
451 \l {https://guides.github.com/features/mastering-markdown/}{GitHub}
452 extensions for tables and task lists (since 5.14)
453
454 The default is \c TextEdit.PlainText. If the text format is set to
455 \c TextEdit.AutoText, the text edit will automatically determine whether
456 the text should be treated as rich text. This determination is made using
457 Qt::mightBeRichText(), which can detect the presence of an HTML tag on the
458 first line of text, but cannot distinguish Markdown from plain text.
459
460 \table
461 \row
462 \li
463 \snippet qml/text/textEditFormats.qml 0
464 \li \image declarative-textformat.png
465 \endtable
466
467 With \c TextEdit.MarkdownText, checkboxes that result from using the
468 \l {https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown}{GitHub checkbox extension}
469 are interactively checkable.
470
471 \note Interactively typing markup or markdown formatting is not supported.
472
473 \note With \c Text.MarkdownText, and with the supported subset of HTML,
474 some decorative elements are not rendered as they would be in a web browser:
475 \list
476 \li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
477 \li block quotes are indented, but there is no vertical line alongside the quote
478 \li horizontal rules are not rendered
479 \endlist
480*/
481QQuickTextEdit::TextFormat QQuickTextEdit::textFormat() const
482{
483 Q_D(const QQuickTextEdit);
484 return d->format;
485}
486
487void QQuickTextEdit::setTextFormat(TextFormat format)
488{
489 Q_D(QQuickTextEdit);
490 if (format == d->format)
491 return;
492
493 const bool wasRich = d->richText;
494 const bool wasMarkdown = d->markdownText;
495 d->richText = format == RichText || (format == AutoText && (wasRich || Qt::mightBeRichText(text())));
496 d->markdownText = format == MarkdownText;
497
498 if (isComponentComplete()) {
499#if QT_CONFIG(texthtmlparser)
500 if (wasRich && !d->richText && !d->markdownText) {
501 d->control->setPlainText(!d->textCached ? d->control->toHtml() : d->text);
502 updateSize();
503 } else if (!wasRich && d->richText) {
504 d->control->setHtml(!d->textCached ? d->control->toPlainText() : d->text);
505 updateSize();
506 }
507#endif
508#if QT_CONFIG(textmarkdownwriter) && QT_CONFIG(textmarkdownreader)
509 if (wasMarkdown && !d->markdownText && !d->richText) {
510 d->control->setPlainText(!d->textCached ? d->control->toMarkdown() : d->text);
511 updateSize();
512 } else if (!wasMarkdown && d->markdownText) {
513 d->control->setMarkdownText(!d->textCached ? d->control->toPlainText() : d->text);
514 updateSize();
515 }
516#endif
517 }
518
519 d->format = format;
520 d->control->setAcceptRichText(d->format != PlainText);
521 emit textFormatChanged(textFormat: d->format);
522}
523
524/*!
525 \qmlproperty enumeration QtQuick::TextEdit::renderType
526
527 Override the default rendering type for this component.
528
529 Supported render types are:
530
531 \value TextEdit.QtRendering Text is rendered using a scalable distance field for each glyph.
532 \value TextEdit.NativeRendering Text is rendered using a platform-specific technique.
533
534 Select \c TextEdit.NativeRendering if you prefer text to look native on the target platform and do
535 not require advanced features such as transformation of the text. Using such features in
536 combination with the NativeRendering render type will lend poor and sometimes pixelated
537 results.
538
539 The default rendering type is determined by \l QQuickWindow::textRenderType().
540*/
541QQuickTextEdit::RenderType QQuickTextEdit::renderType() const
542{
543 Q_D(const QQuickTextEdit);
544 return d->renderType;
545}
546
547void QQuickTextEdit::setRenderType(QQuickTextEdit::RenderType renderType)
548{
549 Q_D(QQuickTextEdit);
550 if (d->renderType == renderType)
551 return;
552
553 d->renderType = renderType;
554 emit renderTypeChanged();
555 d->updateDefaultTextOption();
556
557 if (isComponentComplete())
558 updateSize();
559}
560
561QFont QQuickTextEdit::font() const
562{
563 Q_D(const QQuickTextEdit);
564 return d->sourceFont;
565}
566
567void QQuickTextEdit::setFont(const QFont &font)
568{
569 Q_D(QQuickTextEdit);
570 if (d->sourceFont == font)
571 return;
572
573 d->sourceFont = font;
574 QFont oldFont = d->font;
575 d->font = font;
576 if (d->font.pointSizeF() != -1) {
577 // 0.5pt resolution
578 qreal size = qRound(d: d->font.pointSizeF()*2.0);
579 d->font.setPointSizeF(size/2.0);
580 }
581
582 if (oldFont != d->font) {
583 d->document->setDefaultFont(d->font);
584 if (d->cursorItem) {
585 d->cursorItem->setHeight(QFontMetrics(d->font).height());
586 moveCursorDelegate();
587 }
588 updateSize();
589 updateWholeDocument();
590#if QT_CONFIG(im)
591 updateInputMethod(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle | Qt::ImFont);
592#endif
593 }
594 emit fontChanged(font: d->sourceFont);
595}
596
597/*!
598 \qmlproperty color QtQuick::TextEdit::color
599
600 The text color.
601
602 \qml
603 // green text using hexadecimal notation
604 TextEdit { color: "#00FF00" }
605 \endqml
606
607 \qml
608 // steelblue text using SVG color name
609 TextEdit { color: "steelblue" }
610 \endqml
611*/
612QColor QQuickTextEdit::color() const
613{
614 Q_D(const QQuickTextEdit);
615 return d->color;
616}
617
618void QQuickTextEdit::setColor(const QColor &color)
619{
620 Q_D(QQuickTextEdit);
621 if (d->color == color)
622 return;
623
624 d->color = color;
625 updateWholeDocument();
626 emit colorChanged(color: d->color);
627}
628
629/*!
630 \qmlproperty color QtQuick::TextEdit::selectionColor
631
632 The text highlight color, used behind selections.
633*/
634QColor QQuickTextEdit::selectionColor() const
635{
636 Q_D(const QQuickTextEdit);
637 return d->selectionColor;
638}
639
640void QQuickTextEdit::setSelectionColor(const QColor &color)
641{
642 Q_D(QQuickTextEdit);
643 if (d->selectionColor == color)
644 return;
645
646 d->selectionColor = color;
647 updateWholeDocument();
648 emit selectionColorChanged(color: d->selectionColor);
649}
650
651/*!
652 \qmlproperty color QtQuick::TextEdit::selectedTextColor
653
654 The selected text color, used in selections.
655*/
656QColor QQuickTextEdit::selectedTextColor() const
657{
658 Q_D(const QQuickTextEdit);
659 return d->selectedTextColor;
660}
661
662void QQuickTextEdit::setSelectedTextColor(const QColor &color)
663{
664 Q_D(QQuickTextEdit);
665 if (d->selectedTextColor == color)
666 return;
667
668 d->selectedTextColor = color;
669 updateWholeDocument();
670 emit selectedTextColorChanged(color: d->selectedTextColor);
671}
672
673/*!
674 \qmlproperty enumeration QtQuick::TextEdit::horizontalAlignment
675 \qmlproperty enumeration QtQuick::TextEdit::verticalAlignment
676 \qmlproperty enumeration QtQuick::TextEdit::effectiveHorizontalAlignment
677
678 Sets the horizontal and vertical alignment of the text within the TextEdit item's
679 width and height. By default, the text alignment follows the natural alignment
680 of the text, for example text that is read from left to right will be aligned to
681 the left.
682
683 Valid values for \c horizontalAlignment are:
684
685 \value TextEdit.AlignLeft
686 left alignment with ragged edges on the right (default)
687 \value TextEdit.AlignRight
688 align each line to the right with ragged edges on the left
689 \value TextEdit.AlignHCenter
690 align each line to the center
691 \value TextEdit.AlignJustify
692 align each line to both right and left, spreading out words as necessary
693
694 Valid values for \c verticalAlignment are:
695
696 \value TextEdit.AlignTop start at the top of the item (default)
697 \value TextEdit.AlignBottom align the last line to the bottom and other lines above
698 \value TextEdit.AlignVCenter align the center vertically
699
700 When using the attached property LayoutMirroring::enabled to mirror application
701 layouts, the horizontal alignment of text will also be mirrored. However, the property
702 \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
703 of TextEdit, use the read-only property \c effectiveHorizontalAlignment.
704*/
705QQuickTextEdit::HAlignment QQuickTextEdit::hAlign() const
706{
707 Q_D(const QQuickTextEdit);
708 return d->hAlign;
709}
710
711void QQuickTextEdit::setHAlign(HAlignment align)
712{
713 Q_D(QQuickTextEdit);
714 bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
715 d->hAlignImplicit = false;
716 if (d->setHAlign(align, forceAlign) && isComponentComplete()) {
717 d->updateDefaultTextOption();
718 updateSize();
719 }
720}
721
722void QQuickTextEdit::resetHAlign()
723{
724 Q_D(QQuickTextEdit);
725 d->hAlignImplicit = true;
726 if (d->determineHorizontalAlignment() && isComponentComplete()) {
727 d->updateDefaultTextOption();
728 updateSize();
729 }
730}
731
732QQuickTextEdit::HAlignment QQuickTextEdit::effectiveHAlign() const
733{
734 Q_D(const QQuickTextEdit);
735 QQuickTextEdit::HAlignment effectiveAlignment = d->hAlign;
736 if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
737 switch (d->hAlign) {
738 case QQuickTextEdit::AlignLeft:
739 effectiveAlignment = QQuickTextEdit::AlignRight;
740 break;
741 case QQuickTextEdit::AlignRight:
742 effectiveAlignment = QQuickTextEdit::AlignLeft;
743 break;
744 default:
745 break;
746 }
747 }
748 return effectiveAlignment;
749}
750
751bool QQuickTextEditPrivate::setHAlign(QQuickTextEdit::HAlignment alignment, bool forceAlign)
752{
753 Q_Q(QQuickTextEdit);
754 if (hAlign != alignment || forceAlign) {
755 QQuickTextEdit::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
756 hAlign = alignment;
757 emit q->horizontalAlignmentChanged(alignment);
758 if (oldEffectiveHAlign != q->effectiveHAlign())
759 emit q->effectiveHorizontalAlignmentChanged();
760 return true;
761 }
762 return false;
763}
764
765
766Qt::LayoutDirection QQuickTextEditPrivate::textDirection(const QString &text) const
767{
768 const QChar *character = text.constData();
769 while (!character->isNull()) {
770 switch (character->direction()) {
771 case QChar::DirL:
772 return Qt::LeftToRight;
773 case QChar::DirR:
774 case QChar::DirAL:
775 case QChar::DirAN:
776 return Qt::RightToLeft;
777 default:
778 break;
779 }
780 character++;
781 }
782 return Qt::LayoutDirectionAuto;
783}
784
785bool QQuickTextEditPrivate::determineHorizontalAlignment()
786{
787 Q_Q(QQuickTextEdit);
788 if (hAlignImplicit && q->isComponentComplete()) {
789 Qt::LayoutDirection direction = contentDirection;
790#if QT_CONFIG(im)
791 if (direction == Qt::LayoutDirectionAuto) {
792 const QString preeditText = control->textCursor().block().layout()->preeditAreaText();
793 direction = textDirection(text: preeditText);
794 }
795 if (direction == Qt::LayoutDirectionAuto)
796 direction = qGuiApp->inputMethod()->inputDirection();
797#endif
798
799 return setHAlign(alignment: direction == Qt::RightToLeft ? QQuickTextEdit::AlignRight : QQuickTextEdit::AlignLeft);
800 }
801 return false;
802}
803
804void QQuickTextEditPrivate::mirrorChange()
805{
806 Q_Q(QQuickTextEdit);
807 if (q->isComponentComplete()) {
808 if (!hAlignImplicit && (hAlign == QQuickTextEdit::AlignRight || hAlign == QQuickTextEdit::AlignLeft)) {
809 updateDefaultTextOption();
810 q->updateSize();
811 emit q->effectiveHorizontalAlignmentChanged();
812 }
813 }
814}
815
816bool QQuickTextEditPrivate::transformChanged(QQuickItem *transformedItem)
817{
818 Q_Q(QQuickTextEdit);
819 qCDebug(lcVP) << q << "sees that" << transformedItem << "moved in VP" << q->clipRect();
820
821 // If there's a lot of text, and the TextEdit has been scrolled so that the viewport
822 // no longer completely covers the rendered region, we need QQuickTextEdit::updatePaintNode()
823 // to re-iterate blocks and populate a different range.
824 if (flags & QQuickItem::ItemObservesViewport) {
825 if (QQuickItem *viewport = q->viewportItem()) {
826 QRectF vp = q->mapRectFromItem(item: viewport, rect: viewport->clipRect());
827 if (!(vp.top() > renderedRegion.top() && vp.bottom() < renderedRegion.bottom())) {
828 qCDebug(lcVP) << "viewport" << vp << "now goes beyond rendered region" << renderedRegion << "; updating";
829 q->updateWholeDocument();
830 }
831 const bool textCursorVisible = cursorVisible && q->cursorRectangle().intersects(r: vp);
832 if (cursorItem)
833 cursorItem->setVisible(textCursorVisible);
834 else
835 control->setCursorVisible(textCursorVisible);
836 }
837 }
838 return QQuickImplicitSizeItemPrivate::transformChanged(transformedItem);
839}
840
841#if QT_CONFIG(im)
842Qt::InputMethodHints QQuickTextEditPrivate::effectiveInputMethodHints() const
843{
844 return inputMethodHints | Qt::ImhMultiLine;
845}
846#endif
847
848void QQuickTextEditPrivate::setTopPadding(qreal value, bool reset)
849{
850 Q_Q(QQuickTextEdit);
851 qreal oldPadding = q->topPadding();
852 if (!reset || extra.isAllocated()) {
853 extra.value().topPadding = value;
854 extra.value().explicitTopPadding = !reset;
855 }
856 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
857 q->updateSize();
858 q->updateWholeDocument();
859 emit q->topPaddingChanged();
860 }
861}
862
863void QQuickTextEditPrivate::setLeftPadding(qreal value, bool reset)
864{
865 Q_Q(QQuickTextEdit);
866 qreal oldPadding = q->leftPadding();
867 if (!reset || extra.isAllocated()) {
868 extra.value().leftPadding = value;
869 extra.value().explicitLeftPadding = !reset;
870 }
871 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
872 q->updateSize();
873 q->updateWholeDocument();
874 emit q->leftPaddingChanged();
875 }
876}
877
878void QQuickTextEditPrivate::setRightPadding(qreal value, bool reset)
879{
880 Q_Q(QQuickTextEdit);
881 qreal oldPadding = q->rightPadding();
882 if (!reset || extra.isAllocated()) {
883 extra.value().rightPadding = value;
884 extra.value().explicitRightPadding = !reset;
885 }
886 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
887 q->updateSize();
888 q->updateWholeDocument();
889 emit q->rightPaddingChanged();
890 }
891}
892
893void QQuickTextEditPrivate::setBottomPadding(qreal value, bool reset)
894{
895 Q_Q(QQuickTextEdit);
896 qreal oldPadding = q->bottomPadding();
897 if (!reset || extra.isAllocated()) {
898 extra.value().bottomPadding = value;
899 extra.value().explicitBottomPadding = !reset;
900 }
901 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
902 q->updateSize();
903 q->updateWholeDocument();
904 emit q->bottomPaddingChanged();
905 }
906}
907
908bool QQuickTextEditPrivate::isImplicitResizeEnabled() const
909{
910 return !extra.isAllocated() || extra->implicitResize;
911}
912
913void QQuickTextEditPrivate::setImplicitResizeEnabled(bool enabled)
914{
915 if (!enabled)
916 extra.value().implicitResize = false;
917 else if (extra.isAllocated())
918 extra->implicitResize = true;
919}
920
921QQuickTextEdit::VAlignment QQuickTextEdit::vAlign() const
922{
923 Q_D(const QQuickTextEdit);
924 return d->vAlign;
925}
926
927void QQuickTextEdit::setVAlign(QQuickTextEdit::VAlignment alignment)
928{
929 Q_D(QQuickTextEdit);
930 if (alignment == d->vAlign)
931 return;
932 d->vAlign = alignment;
933 d->updateDefaultTextOption();
934 updateSize();
935 moveCursorDelegate();
936 emit verticalAlignmentChanged(alignment: d->vAlign);
937}
938
939/*!
940 \qmlproperty enumeration QtQuick::TextEdit::wrapMode
941
942 Set this property to wrap the text to the TextEdit item's width.
943 The text will only wrap if an explicit width has been set.
944
945 \value TextEdit.NoWrap
946 (default) no wrapping will be performed. If the text contains insufficient newlines,
947 \l {Item::}{implicitWidth} will exceed a set width.
948 \value TextEdit.WordWrap
949 wrapping is done on word boundaries only. If a word is too long,
950 \l {Item::}{implicitWidth} will exceed a set width.
951 \value TextEdit.WrapAnywhere
952 wrapping is done at any point on a line, even if it occurs in the middle of a word.
953 \value TextEdit.Wrap
954 if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate
955 point on the line, even in the middle of a word.
956
957 The default is \c TextEdit.NoWrap. If you set a width, consider using \c TextEdit.Wrap.
958*/
959QQuickTextEdit::WrapMode QQuickTextEdit::wrapMode() const
960{
961 Q_D(const QQuickTextEdit);
962 return d->wrapMode;
963}
964
965void QQuickTextEdit::setWrapMode(WrapMode mode)
966{
967 Q_D(QQuickTextEdit);
968 if (mode == d->wrapMode)
969 return;
970 d->wrapMode = mode;
971 d->updateDefaultTextOption();
972 updateSize();
973 emit wrapModeChanged();
974}
975
976/*!
977 \qmlproperty int QtQuick::TextEdit::lineCount
978
979 Returns the total number of lines in the TextEdit item.
980*/
981int QQuickTextEdit::lineCount() const
982{
983 Q_D(const QQuickTextEdit);
984 return d->lineCount;
985}
986
987/*!
988 \qmlproperty int QtQuick::TextEdit::length
989
990 Returns the total number of plain text characters in the TextEdit item.
991
992 As this number doesn't include any formatting markup it may not be the same as the
993 length of the string returned by the \l text property.
994
995 This property can be faster than querying the length the \l text property as it doesn't
996 require any copying or conversion of the TextEdit's internal string data.
997*/
998
999int QQuickTextEdit::length() const
1000{
1001 Q_D(const QQuickTextEdit);
1002 // QTextDocument::characterCount() includes the terminating null character.
1003 return qMax(a: 0, b: d->document->characterCount() - 1);
1004}
1005
1006/*!
1007 \qmlproperty real QtQuick::TextEdit::contentWidth
1008
1009 Returns the width of the text, including the width past the width
1010 which is covered due to insufficient wrapping if \l wrapMode is set.
1011*/
1012qreal QQuickTextEdit::contentWidth() const
1013{
1014 Q_D(const QQuickTextEdit);
1015 return d->contentSize.width();
1016}
1017
1018/*!
1019 \qmlproperty real QtQuick::TextEdit::contentHeight
1020
1021 Returns the height of the text, including the height past the height
1022 that is covered if the text does not fit within the set height.
1023*/
1024qreal QQuickTextEdit::contentHeight() const
1025{
1026 Q_D(const QQuickTextEdit);
1027 return d->contentSize.height();
1028}
1029
1030/*!
1031 \qmlproperty url QtQuick::TextEdit::baseUrl
1032
1033 This property specifies a base URL which is used to resolve relative URLs
1034 within the text.
1035
1036 The default value is the url of the QML file instantiating the TextEdit item.
1037*/
1038
1039QUrl QQuickTextEdit::baseUrl() const
1040{
1041 Q_D(const QQuickTextEdit);
1042 if (d->baseUrl.isEmpty()) {
1043 if (QQmlContext *context = qmlContext(this))
1044 const_cast<QQuickTextEditPrivate *>(d)->baseUrl = context->baseUrl();
1045 }
1046 return d->baseUrl;
1047}
1048
1049void QQuickTextEdit::setBaseUrl(const QUrl &url)
1050{
1051 Q_D(QQuickTextEdit);
1052 if (baseUrl() != url) {
1053 d->baseUrl = url;
1054
1055 d->document->setBaseUrl(url);
1056 emit baseUrlChanged();
1057 }
1058}
1059
1060void QQuickTextEdit::resetBaseUrl()
1061{
1062 if (QQmlContext *context = qmlContext(this))
1063 setBaseUrl(context->baseUrl());
1064 else
1065 setBaseUrl(QUrl());
1066}
1067
1068/*!
1069 \qmlmethod rectangle QtQuick::TextEdit::positionToRectangle(position)
1070
1071 Returns the rectangle at the given \a position in the text. The x, y,
1072 and height properties correspond to the cursor that would describe
1073 that position.
1074*/
1075QRectF QQuickTextEdit::positionToRectangle(int pos) const
1076{
1077 Q_D(const QQuickTextEdit);
1078 QTextCursor c(d->document);
1079 c.setPosition(pos);
1080 return d->control->cursorRect(cursor: c).translated(dx: d->xoff, dy: d->yoff);
1081
1082}
1083
1084/*!
1085 \qmlmethod int QtQuick::TextEdit::positionAt(int x, int y)
1086
1087 Returns the text position closest to pixel position (\a x, \a y).
1088
1089 Position 0 is before the first character, position 1 is after the first character
1090 but before the second, and so on until position \l {text}.length, which is after all characters.
1091*/
1092int QQuickTextEdit::positionAt(qreal x, qreal y) const
1093{
1094 Q_D(const QQuickTextEdit);
1095 x -= d->xoff;
1096 y -= d->yoff;
1097
1098 int r = d->document->documentLayout()->hitTest(point: QPointF(x, y), accuracy: Qt::FuzzyHit);
1099#if QT_CONFIG(im)
1100 QTextCursor cursor = d->control->textCursor();
1101 if (r > cursor.position()) {
1102 // The cursor position includes positions within the preedit text, but only positions in the
1103 // same text block are offset so it is possible to get a position that is either part of the
1104 // preedit or the next text block.
1105 QTextLayout *layout = cursor.block().layout();
1106 const int preeditLength = layout
1107 ? layout->preeditAreaText().size()
1108 : 0;
1109 if (preeditLength > 0
1110 && d->document->documentLayout()->blockBoundingRect(block: cursor.block()).contains(ax: x, ay: y)) {
1111 r = r > cursor.position() + preeditLength
1112 ? r - preeditLength
1113 : cursor.position();
1114 }
1115 }
1116#endif
1117 return r;
1118}
1119
1120/*!
1121 \qmlmethod QtQuick::TextEdit::moveCursorSelection(int position, SelectionMode mode)
1122
1123 Moves the cursor to \a position and updates the selection according to the optional \a mode
1124 parameter. (To only move the cursor, set the \l cursorPosition property.)
1125
1126 When this method is called it additionally sets either the
1127 selectionStart or the selectionEnd (whichever was at the previous cursor position)
1128 to the specified position. This allows you to easily extend and contract the selected
1129 text range.
1130
1131 The selection mode specifies whether the selection is updated on a per character or a per word
1132 basis. If not specified the selection mode will default to \c {TextEdit.SelectCharacters}.
1133
1134 \value TextEdit.SelectCharacters
1135 Sets either the selectionStart or selectionEnd (whichever was at the previous cursor position)
1136 to the specified position.
1137 \value TextEdit.SelectWords
1138 Sets the selectionStart and selectionEnd to include all words between the specified position
1139 and the previous cursor position. Words partially in the range are included.
1140
1141 For example, take this sequence of calls:
1142
1143 \code
1144 cursorPosition = 5
1145 moveCursorSelection(9, TextEdit.SelectCharacters)
1146 moveCursorSelection(7, TextEdit.SelectCharacters)
1147 \endcode
1148
1149 This moves the cursor to position 5, extend the selection end from 5 to 9
1150 and then retract the selection end from 9 to 7, leaving the text from position 5 to 7
1151 selected (the 6th and 7th characters).
1152
1153 The same sequence with TextEdit.SelectWords will extend the selection start to a word boundary
1154 before or on position 5 and extend the selection end to a word boundary on or past position 9.
1155*/
1156void QQuickTextEdit::moveCursorSelection(int pos)
1157{
1158 //Note that this is the same as setCursorPosition but with the KeepAnchor flag set
1159 Q_D(QQuickTextEdit);
1160 QTextCursor cursor = d->control->textCursor();
1161 if (cursor.position() == pos)
1162 return;
1163 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1164 d->control->setTextCursor(cursor);
1165}
1166
1167void QQuickTextEdit::moveCursorSelection(int pos, SelectionMode mode)
1168{
1169 Q_D(QQuickTextEdit);
1170 QTextCursor cursor = d->control->textCursor();
1171 if (cursor.position() == pos)
1172 return;
1173 if (mode == SelectCharacters) {
1174 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1175 } else if (cursor.anchor() < pos || (cursor.anchor() == pos && cursor.position() < pos)) {
1176 if (cursor.anchor() > cursor.position()) {
1177 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1178 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1179 if (cursor.position() == cursor.anchor())
1180 cursor.movePosition(op: QTextCursor::PreviousWord, QTextCursor::MoveAnchor);
1181 else
1182 cursor.setPosition(pos: cursor.position(), mode: QTextCursor::MoveAnchor);
1183 } else {
1184 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1185 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
1186 }
1187
1188 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1189 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1190 if (cursor.position() != pos)
1191 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1192 } else if (cursor.anchor() > pos || (cursor.anchor() == pos && cursor.position() > pos)) {
1193 if (cursor.anchor() < cursor.position()) {
1194 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1195 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1196 } else {
1197 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1198 cursor.movePosition(op: QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1199 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1200 if (cursor.position() != cursor.anchor()) {
1201 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1202 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1203 }
1204 }
1205
1206 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1207 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1208 if (cursor.position() != pos) {
1209 cursor.movePosition(op: QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1210 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1211 }
1212 }
1213 d->control->setTextCursor(cursor);
1214}
1215
1216/*!
1217 \qmlproperty bool QtQuick::TextEdit::cursorVisible
1218 If true the text edit shows a cursor.
1219
1220 This property is set and unset when the text edit gets active focus, but it can also
1221 be set directly (useful, for example, if a KeyProxy might forward keys to it).
1222*/
1223bool QQuickTextEdit::isCursorVisible() const
1224{
1225 Q_D(const QQuickTextEdit);
1226 return d->cursorVisible;
1227}
1228
1229void QQuickTextEdit::setCursorVisible(bool on)
1230{
1231 Q_D(QQuickTextEdit);
1232 if (d->cursorVisible == on)
1233 return;
1234 d->cursorVisible = on;
1235 if (on && isComponentComplete())
1236 QQuickTextUtil::createCursor(d);
1237 if (!on && !d->persistentSelection)
1238 d->control->setCursorIsFocusIndicator(true);
1239 d->control->setCursorVisible(on);
1240 emit cursorVisibleChanged(isCursorVisible: d->cursorVisible);
1241}
1242
1243/*!
1244 \qmlproperty int QtQuick::TextEdit::cursorPosition
1245 The position of the cursor in the TextEdit. The cursor is positioned between
1246 characters.
1247
1248 \note The \e characters in this case refer to the string of \l QChar objects,
1249 therefore 16-bit Unicode characters, and the position is considered an index
1250 into this string. This does not necessarily correspond to individual graphemes
1251 in the writing system, as a single grapheme may be represented by multiple
1252 Unicode characters, such as in the case of surrogate pairs, linguistic
1253 ligatures or diacritics.
1254*/
1255int QQuickTextEdit::cursorPosition() const
1256{
1257 Q_D(const QQuickTextEdit);
1258 return d->control->textCursor().position();
1259}
1260
1261void QQuickTextEdit::setCursorPosition(int pos)
1262{
1263 Q_D(QQuickTextEdit);
1264 if (pos < 0 || pos >= d->document->characterCount()) // characterCount includes the terminating null.
1265 return;
1266 QTextCursor cursor = d->control->textCursor();
1267 if (cursor.position() == pos && cursor.anchor() == pos)
1268 return;
1269 cursor.setPosition(pos);
1270 d->control->setTextCursor(cursor);
1271 d->control->updateCursorRectangle(force: true);
1272}
1273
1274/*!
1275 \qmlproperty Component QtQuick::TextEdit::cursorDelegate
1276 The delegate for the cursor in the TextEdit.
1277
1278 If you set a cursorDelegate for a TextEdit, this delegate will be used for
1279 drawing the cursor instead of the standard cursor. An instance of the
1280 delegate will be created and managed by the text edit when a cursor is
1281 needed, and the x and y properties of delegate instance will be set so as
1282 to be one pixel before the top left of the current character.
1283
1284 Note that the root item of the delegate component must be a QQuickItem or
1285 QQuickItem derived item.
1286*/
1287QQmlComponent* QQuickTextEdit::cursorDelegate() const
1288{
1289 Q_D(const QQuickTextEdit);
1290 return d->cursorComponent;
1291}
1292
1293void QQuickTextEdit::setCursorDelegate(QQmlComponent* c)
1294{
1295 Q_D(QQuickTextEdit);
1296 QQuickTextUtil::setCursorDelegate(d, delegate: c);
1297}
1298
1299void QQuickTextEdit::createCursor()
1300{
1301 Q_D(QQuickTextEdit);
1302 d->cursorPending = true;
1303 QQuickTextUtil::createCursor(d);
1304}
1305
1306/*!
1307 \qmlproperty int QtQuick::TextEdit::selectionStart
1308
1309 The cursor position before the first character in the current selection.
1310
1311 This property is read-only. To change the selection, use select(start,end),
1312 selectAll(), or selectWord().
1313
1314 \sa selectionEnd, cursorPosition, selectedText
1315*/
1316int QQuickTextEdit::selectionStart() const
1317{
1318 Q_D(const QQuickTextEdit);
1319 return d->control->textCursor().selectionStart();
1320}
1321
1322/*!
1323 \qmlproperty int QtQuick::TextEdit::selectionEnd
1324
1325 The cursor position after the last character in the current selection.
1326
1327 This property is read-only. To change the selection, use select(start,end),
1328 selectAll(), or selectWord().
1329
1330 \sa selectionStart, cursorPosition, selectedText
1331*/
1332int QQuickTextEdit::selectionEnd() const
1333{
1334 Q_D(const QQuickTextEdit);
1335 return d->control->textCursor().selectionEnd();
1336}
1337
1338/*!
1339 \qmlproperty string QtQuick::TextEdit::selectedText
1340
1341 This read-only property provides the text currently selected in the
1342 text edit.
1343
1344 It is equivalent to the following snippet, but is faster and easier
1345 to use.
1346 \code
1347 //myTextEdit is the id of the TextEdit
1348 myTextEdit.text.toString().substring(myTextEdit.selectionStart,
1349 myTextEdit.selectionEnd);
1350 \endcode
1351*/
1352QString QQuickTextEdit::selectedText() const
1353{
1354 Q_D(const QQuickTextEdit);
1355#if QT_CONFIG(texthtmlparser)
1356 return d->richText || d->markdownText
1357 ? d->control->textCursor().selectedText()
1358 : d->control->textCursor().selection().toPlainText();
1359#else
1360 return d->control->textCursor().selection().toPlainText();
1361#endif
1362}
1363
1364/*!
1365 \qmlproperty bool QtQuick::TextEdit::activeFocusOnPress
1366
1367 Whether the TextEdit should gain active focus on a mouse press. By default this is
1368 set to true.
1369*/
1370bool QQuickTextEdit::focusOnPress() const
1371{
1372 Q_D(const QQuickTextEdit);
1373 return d->focusOnPress;
1374}
1375
1376void QQuickTextEdit::setFocusOnPress(bool on)
1377{
1378 Q_D(QQuickTextEdit);
1379 if (d->focusOnPress == on)
1380 return;
1381 d->focusOnPress = on;
1382 emit activeFocusOnPressChanged(activeFocusOnPressed: d->focusOnPress);
1383}
1384
1385/*!
1386 \qmlproperty bool QtQuick::TextEdit::persistentSelection
1387
1388 Whether the TextEdit should keep the selection visible when it loses active focus to another
1389 item in the scene. By default this is set to false.
1390*/
1391bool QQuickTextEdit::persistentSelection() const
1392{
1393 Q_D(const QQuickTextEdit);
1394 return d->persistentSelection;
1395}
1396
1397void QQuickTextEdit::setPersistentSelection(bool on)
1398{
1399 Q_D(QQuickTextEdit);
1400 if (d->persistentSelection == on)
1401 return;
1402 d->persistentSelection = on;
1403 emit persistentSelectionChanged(isPersistentSelection: d->persistentSelection);
1404}
1405
1406/*!
1407 \qmlproperty real QtQuick::TextEdit::textMargin
1408
1409 The margin, in pixels, around the text in the TextEdit.
1410*/
1411qreal QQuickTextEdit::textMargin() const
1412{
1413 Q_D(const QQuickTextEdit);
1414 return d->textMargin;
1415}
1416
1417void QQuickTextEdit::setTextMargin(qreal margin)
1418{
1419 Q_D(QQuickTextEdit);
1420 if (d->textMargin == margin)
1421 return;
1422 d->textMargin = margin;
1423 d->document->setDocumentMargin(d->textMargin);
1424 emit textMarginChanged(textMargin: d->textMargin);
1425}
1426
1427/*!
1428 \qmlproperty enumeration QtQuick::TextEdit::inputMethodHints
1429
1430 Provides hints to the input method about the expected content of the text edit and how it
1431 should operate.
1432
1433 The value is a bit-wise combination of flags or Qt.ImhNone if no hints are set.
1434
1435 Flags that alter behaviour are:
1436
1437 \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords.
1438 \value Qt.ImhSensitiveData Typed text should not be stored by the active input method
1439 in any persistent storage like predictive user dictionary.
1440 \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to
1441 upper case when a sentence ends.
1442 \value Qt.ImhPreferNumbers Numbers are preferred (but not required).
1443 \value Qt.ImhPreferUppercase Upper case letters are preferred (but not required).
1444 \value Qt.ImhPreferLowercase Lower case letters are preferred (but not required).
1445 \value Qt.ImhNoPredictiveText Do not use predictive text (i.e. dictionary lookup) while typing.
1446 \value Qt.ImhDate The text editor functions as a date field.
1447 \value Qt.ImhTime The text editor functions as a time field.
1448
1449 Flags that restrict input (exclusive flags) are:
1450
1451 \value Qt.ImhDigitsOnly Only digits are allowed.
1452 \value Qt.ImhFormattedNumbersOnly Only number input is allowed. This includes decimal point and minus sign.
1453 \value Qt.ImhUppercaseOnly Only upper case letter input is allowed.
1454 \value Qt.ImhLowercaseOnly Only lower case letter input is allowed.
1455 \value Qt.ImhDialableCharactersOnly Only characters suitable for phone dialing are allowed.
1456 \value Qt.ImhEmailCharactersOnly Only characters suitable for email addresses are allowed.
1457 \value Qt.ImhUrlCharactersOnly Only characters suitable for URLs are allowed.
1458
1459 Masks:
1460
1461 \value Qt.ImhExclusiveInputMask This mask yields nonzero if any of the exclusive flags are used.
1462*/
1463
1464Qt::InputMethodHints QQuickTextEdit::inputMethodHints() const
1465{
1466#if !QT_CONFIG(im)
1467 return Qt::ImhNone;
1468#else
1469 Q_D(const QQuickTextEdit);
1470 return d->inputMethodHints;
1471#endif // im
1472}
1473
1474void QQuickTextEdit::setInputMethodHints(Qt::InputMethodHints hints)
1475{
1476#if !QT_CONFIG(im)
1477 Q_UNUSED(hints);
1478#else
1479 Q_D(QQuickTextEdit);
1480
1481 if (hints == d->inputMethodHints)
1482 return;
1483
1484 d->inputMethodHints = hints;
1485 updateInputMethod(queries: Qt::ImHints);
1486 emit inputMethodHintsChanged();
1487#endif // im
1488}
1489
1490void QQuickTextEdit::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1491{
1492 Q_D(QQuickTextEdit);
1493 if (!d->inLayout && ((newGeometry.width() != oldGeometry.width() && widthValid())
1494 || (newGeometry.height() != oldGeometry.height() && heightValid()))) {
1495 updateSize();
1496 updateWholeDocument();
1497 moveCursorDelegate();
1498 }
1499 QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
1500
1501}
1502
1503/*!
1504 Ensures any delayed caching or data loading the class
1505 needs to performed is complete.
1506*/
1507void QQuickTextEdit::componentComplete()
1508{
1509 Q_D(QQuickTextEdit);
1510 QQuickImplicitSizeItem::componentComplete();
1511
1512 const QUrl url = baseUrl();
1513 const QQmlContext *context = qmlContext(this);
1514 d->document->setBaseUrl(context ? context->resolvedUrl(url) : url);
1515#if QT_CONFIG(texthtmlparser)
1516 if (d->richText)
1517 d->control->setHtml(d->text);
1518 else
1519#endif
1520#if QT_CONFIG(textmarkdownreader)
1521 if (d->markdownText)
1522 d->control->setMarkdownText(d->text);
1523 else
1524#endif
1525 if (!d->text.isEmpty()) {
1526 if (d->markdownText)
1527 d->control->setMarkdownText(d->text);
1528 else
1529 d->control->setPlainText(d->text);
1530 }
1531
1532 if (d->dirty) {
1533 d->determineHorizontalAlignment();
1534 d->updateDefaultTextOption();
1535 updateSize();
1536 d->dirty = false;
1537 }
1538 if (d->cursorComponent && isCursorVisible())
1539 QQuickTextUtil::createCursor(d);
1540 polish();
1541}
1542
1543/*!
1544 \qmlproperty bool QtQuick::TextEdit::selectByKeyboard
1545 \since 5.1
1546
1547 Defaults to true when the editor is editable, and false
1548 when read-only.
1549
1550 If true, the user can use the keyboard to select text
1551 even if the editor is read-only. If false, the user
1552 cannot use the keyboard to select text even if the
1553 editor is editable.
1554
1555 \sa readOnly
1556*/
1557bool QQuickTextEdit::selectByKeyboard() const
1558{
1559 Q_D(const QQuickTextEdit);
1560 if (d->selectByKeyboardSet)
1561 return d->selectByKeyboard;
1562 return !isReadOnly();
1563}
1564
1565void QQuickTextEdit::setSelectByKeyboard(bool on)
1566{
1567 Q_D(QQuickTextEdit);
1568 bool was = selectByKeyboard();
1569 if (!d->selectByKeyboardSet || was != on) {
1570 d->selectByKeyboardSet = true;
1571 d->selectByKeyboard = on;
1572 if (on)
1573 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByKeyboard);
1574 else
1575 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByKeyboard);
1576 emit selectByKeyboardChanged(selectByKeyboard: on);
1577 }
1578}
1579
1580/*!
1581 \qmlproperty bool QtQuick::TextEdit::selectByMouse
1582
1583 Defaults to \c true since Qt 6.4.
1584
1585 If \c true, the user can use the mouse to select text in the usual way.
1586
1587 \note In versions prior to 6.4, the default was \c false; but if you
1588 enabled this property, you could also select text on a touchscreen by
1589 dragging your finger across it. This interfered with flicking when TextEdit
1590 was used inside a Flickable. However, Qt has supported text selection
1591 handles on mobile platforms, and on embedded platforms using Qt Virtual
1592 Keyboard, since version 5.7, via QInputMethod. Most users would be
1593 surprised if finger dragging selected text rather than flicking the parent
1594 Flickable. Therefore, selectByMouse now really means what it says: if
1595 \c true, you can select text by dragging \e only with a mouse, whereas
1596 the platform is expected to provide selection handles on touchscreens.
1597 If this change does not suit your application, you can set \c selectByMouse
1598 to \c false, or import an older API version (for example
1599 \c {import QtQuick 6.3}) to revert to the previous behavior. The option to
1600 revert behavior by changing the import version will be removed in a later
1601 version of Qt.
1602*/
1603bool QQuickTextEdit::selectByMouse() const
1604{
1605 Q_D(const QQuickTextEdit);
1606 return d->selectByMouse;
1607}
1608
1609void QQuickTextEdit::setSelectByMouse(bool on)
1610{
1611 Q_D(QQuickTextEdit);
1612 if (d->selectByMouse == on)
1613 return;
1614
1615 d->selectByMouse = on;
1616 setKeepMouseGrab(on);
1617 if (on)
1618 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByMouse);
1619 else
1620 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
1621
1622#if QT_CONFIG(cursor)
1623 d->updateMouseCursorShape();
1624#endif
1625 emit selectByMouseChanged(selectByMouse: on);
1626}
1627
1628/*!
1629 \qmlproperty enumeration QtQuick::TextEdit::mouseSelectionMode
1630
1631 Specifies how text should be selected using a mouse.
1632
1633 \value TextEdit.SelectCharacters (default) The selection is updated with individual characters.
1634 \value TextEdit.SelectWords The selection is updated with whole words.
1635
1636 This property only applies when \l selectByMouse is true.
1637*/
1638QQuickTextEdit::SelectionMode QQuickTextEdit::mouseSelectionMode() const
1639{
1640 Q_D(const QQuickTextEdit);
1641 return d->mouseSelectionMode;
1642}
1643
1644void QQuickTextEdit::setMouseSelectionMode(SelectionMode mode)
1645{
1646 Q_D(QQuickTextEdit);
1647 if (d->mouseSelectionMode != mode) {
1648 d->mouseSelectionMode = mode;
1649 d->control->setWordSelectionEnabled(mode == SelectWords);
1650 emit mouseSelectionModeChanged(mode);
1651 }
1652}
1653
1654/*!
1655 \qmlproperty bool QtQuick::TextEdit::readOnly
1656
1657 Whether the user can interact with the TextEdit item. If this
1658 property is set to true the text cannot be edited by user interaction.
1659
1660 By default this property is false.
1661*/
1662void QQuickTextEdit::setReadOnly(bool r)
1663{
1664 Q_D(QQuickTextEdit);
1665 if (r == isReadOnly())
1666 return;
1667
1668#if QT_CONFIG(im)
1669 setFlag(flag: QQuickItem::ItemAcceptsInputMethod, enabled: !r);
1670#endif
1671 Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse;
1672 if (d->selectByMouse)
1673 flags = flags | Qt::TextSelectableByMouse;
1674 if (d->selectByKeyboardSet && d->selectByKeyboard)
1675 flags = flags | Qt::TextSelectableByKeyboard;
1676 else if (!d->selectByKeyboardSet && !r)
1677 flags = flags | Qt::TextSelectableByKeyboard;
1678 if (!r)
1679 flags = flags | Qt::TextEditable;
1680 d->control->setTextInteractionFlags(flags);
1681 d->control->moveCursor(op: QTextCursor::End);
1682
1683#if QT_CONFIG(im)
1684 updateInputMethod(queries: Qt::ImEnabled);
1685#endif
1686#if QT_CONFIG(cursor)
1687 d->updateMouseCursorShape();
1688#endif
1689 q_canPasteChanged();
1690 emit readOnlyChanged(isReadOnly: r);
1691 if (!d->selectByKeyboardSet)
1692 emit selectByKeyboardChanged(selectByKeyboard: !r);
1693 if (r) {
1694 setCursorVisible(false);
1695 } else if (hasActiveFocus()) {
1696 setCursorVisible(true);
1697 }
1698}
1699
1700bool QQuickTextEdit::isReadOnly() const
1701{
1702 Q_D(const QQuickTextEdit);
1703 return !(d->control->textInteractionFlags() & Qt::TextEditable);
1704}
1705
1706/*!
1707 \qmlproperty rectangle QtQuick::TextEdit::cursorRectangle
1708
1709 The rectangle where the standard text cursor is rendered
1710 within the text edit. Read-only.
1711
1712 The position and height of a custom cursorDelegate are updated to follow the cursorRectangle
1713 automatically when it changes. The width of the delegate is unaffected by changes in the
1714 cursor rectangle.
1715*/
1716QRectF QQuickTextEdit::cursorRectangle() const
1717{
1718 Q_D(const QQuickTextEdit);
1719 return d->control->cursorRect().translated(dx: d->xoff, dy: d->yoff);
1720}
1721
1722bool QQuickTextEdit::event(QEvent *event)
1723{
1724 Q_D(QQuickTextEdit);
1725 if (event->type() == QEvent::ShortcutOverride) {
1726 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1727 if (event->isAccepted())
1728 return true;
1729 }
1730 return QQuickImplicitSizeItem::event(event);
1731}
1732
1733/*!
1734 \qmlproperty bool QtQuick::TextEdit::overwriteMode
1735 \since 5.8
1736 Whether text entered by the user will overwrite existing text.
1737
1738 As with many text editors, the text editor widget can be configured
1739 to insert or overwrite existing text with new text entered by the user.
1740
1741 If this property is \c true, existing text is overwritten, character-for-character
1742 by new text; otherwise, text is inserted at the cursor position, displacing
1743 existing text.
1744
1745 By default, this property is \c false (new text does not overwrite existing text).
1746*/
1747bool QQuickTextEdit::overwriteMode() const
1748{
1749 Q_D(const QQuickTextEdit);
1750 return d->control->overwriteMode();
1751}
1752
1753void QQuickTextEdit::setOverwriteMode(bool overwrite)
1754{
1755 Q_D(QQuickTextEdit);
1756 d->control->setOverwriteMode(overwrite);
1757}
1758
1759/*!
1760\overload
1761Handles the given key \a event.
1762*/
1763void QQuickTextEdit::keyPressEvent(QKeyEvent *event)
1764{
1765 Q_D(QQuickTextEdit);
1766 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1767 if (!event->isAccepted())
1768 QQuickImplicitSizeItem::keyPressEvent(event);
1769}
1770
1771/*!
1772\overload
1773Handles the given key \a event.
1774*/
1775void QQuickTextEdit::keyReleaseEvent(QKeyEvent *event)
1776{
1777 Q_D(QQuickTextEdit);
1778 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1779 if (!event->isAccepted())
1780 QQuickImplicitSizeItem::keyReleaseEvent(event);
1781}
1782
1783/*!
1784 \qmlmethod QtQuick::TextEdit::deselect()
1785
1786 Removes active text selection.
1787*/
1788void QQuickTextEdit::deselect()
1789{
1790 Q_D(QQuickTextEdit);
1791 QTextCursor c = d->control->textCursor();
1792 c.clearSelection();
1793 d->control->setTextCursor(c);
1794}
1795
1796/*!
1797 \qmlmethod QtQuick::TextEdit::selectAll()
1798
1799 Causes all text to be selected.
1800*/
1801void QQuickTextEdit::selectAll()
1802{
1803 Q_D(QQuickTextEdit);
1804 d->control->selectAll();
1805}
1806
1807/*!
1808 \qmlmethod QtQuick::TextEdit::selectWord()
1809
1810 Causes the word closest to the current cursor position to be selected.
1811*/
1812void QQuickTextEdit::selectWord()
1813{
1814 Q_D(QQuickTextEdit);
1815 QTextCursor c = d->control->textCursor();
1816 c.select(selection: QTextCursor::WordUnderCursor);
1817 d->control->setTextCursor(c);
1818}
1819
1820/*!
1821 \qmlmethod QtQuick::TextEdit::select(int start, int end)
1822
1823 Causes the text from \a start to \a end to be selected.
1824
1825 If either start or end is out of range, the selection is not changed.
1826
1827 After calling this, selectionStart will become the lesser
1828 and selectionEnd will become the greater (regardless of the order passed
1829 to this method).
1830
1831 \sa selectionStart, selectionEnd
1832*/
1833void QQuickTextEdit::select(int start, int end)
1834{
1835 Q_D(QQuickTextEdit);
1836 if (start < 0 || end < 0 || start >= d->document->characterCount() || end >= d->document->characterCount())
1837 return;
1838 QTextCursor cursor = d->control->textCursor();
1839 cursor.beginEditBlock();
1840 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
1841 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
1842 cursor.endEditBlock();
1843 d->control->setTextCursor(cursor);
1844
1845 // QTBUG-11100
1846 updateSelection();
1847#if QT_CONFIG(im)
1848 updateInputMethod();
1849#endif
1850}
1851
1852/*!
1853 \qmlmethod QtQuick::TextEdit::isRightToLeft(int start, int end)
1854
1855 Returns true if the natural reading direction of the editor text
1856 found between positions \a start and \a end is right to left.
1857*/
1858bool QQuickTextEdit::isRightToLeft(int start, int end)
1859{
1860 if (start > end) {
1861 qmlWarning(me: this) << "isRightToLeft(start, end) called with the end property being smaller than the start.";
1862 return false;
1863 } else {
1864 return getText(start, end).isRightToLeft();
1865 }
1866}
1867
1868#if QT_CONFIG(clipboard)
1869/*!
1870 \qmlmethod QtQuick::TextEdit::cut()
1871
1872 Moves the currently selected text to the system clipboard.
1873*/
1874void QQuickTextEdit::cut()
1875{
1876 Q_D(QQuickTextEdit);
1877 d->control->cut();
1878}
1879
1880/*!
1881 \qmlmethod QtQuick::TextEdit::copy()
1882
1883 Copies the currently selected text to the system clipboard.
1884*/
1885void QQuickTextEdit::copy()
1886{
1887 Q_D(QQuickTextEdit);
1888 d->control->copy();
1889}
1890
1891/*!
1892 \qmlmethod QtQuick::TextEdit::paste()
1893
1894 Replaces the currently selected text by the contents of the system clipboard.
1895*/
1896void QQuickTextEdit::paste()
1897{
1898 Q_D(QQuickTextEdit);
1899 d->control->paste();
1900}
1901#endif // clipboard
1902
1903
1904/*!
1905 \qmlmethod QtQuick::TextEdit::undo()
1906
1907 Undoes the last operation if undo is \l {canUndo}{available}. Deselects any
1908 current selection, and updates the selection start to the current cursor
1909 position.
1910*/
1911
1912void QQuickTextEdit::undo()
1913{
1914 Q_D(QQuickTextEdit);
1915 d->control->undo();
1916}
1917
1918/*!
1919 \qmlmethod QtQuick::TextEdit::redo()
1920
1921 Redoes the last operation if redo is \l {canRedo}{available}.
1922*/
1923
1924void QQuickTextEdit::redo()
1925{
1926 Q_D(QQuickTextEdit);
1927 d->control->redo();
1928}
1929
1930/*!
1931\overload
1932Handles the given mouse \a event.
1933*/
1934void QQuickTextEdit::mousePressEvent(QMouseEvent *event)
1935{
1936 Q_D(QQuickTextEdit);
1937 const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(ev: event);
1938 setKeepMouseGrab(d->selectByMouse && isMouse);
1939 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1940 if (d->focusOnPress){
1941 bool hadActiveFocus = hasActiveFocus();
1942 forceActiveFocus(reason: Qt::MouseFocusReason);
1943 // re-open input panel on press if already focused
1944#if QT_CONFIG(im)
1945 if (hasActiveFocus() && hadActiveFocus && !isReadOnly())
1946 qGuiApp->inputMethod()->show();
1947#else
1948 Q_UNUSED(hadActiveFocus);
1949#endif
1950 }
1951 if (!event->isAccepted())
1952 QQuickImplicitSizeItem::mousePressEvent(event);
1953}
1954
1955/*!
1956\overload
1957Handles the given mouse \a event.
1958*/
1959void QQuickTextEdit::mouseReleaseEvent(QMouseEvent *event)
1960{
1961 Q_D(QQuickTextEdit);
1962 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1963
1964 if (!event->isAccepted())
1965 QQuickImplicitSizeItem::mouseReleaseEvent(event);
1966}
1967
1968/*!
1969\overload
1970Handles the given mouse \a event.
1971*/
1972void QQuickTextEdit::mouseDoubleClickEvent(QMouseEvent *event)
1973{
1974 Q_D(QQuickTextEdit);
1975 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1976 if (!event->isAccepted())
1977 QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
1978}
1979
1980/*!
1981\overload
1982Handles the given mouse \a event.
1983*/
1984void QQuickTextEdit::mouseMoveEvent(QMouseEvent *event)
1985{
1986 Q_D(QQuickTextEdit);
1987 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1988 if (!event->isAccepted())
1989 QQuickImplicitSizeItem::mouseMoveEvent(event);
1990}
1991
1992#if QT_CONFIG(im)
1993/*!
1994\overload
1995Handles the given input method \a event.
1996*/
1997void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event)
1998{
1999 Q_D(QQuickTextEdit);
2000 const bool wasComposing = isInputMethodComposing();
2001 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
2002 setCursorVisible(d->control->cursorVisible());
2003 if (wasComposing != isInputMethodComposing())
2004 emit inputMethodComposingChanged();
2005}
2006
2007/*!
2008\overload
2009Returns the value of the given \a property and \a argument.
2010*/
2011QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
2012{
2013 Q_D(const QQuickTextEdit);
2014
2015 QVariant v;
2016 switch (property) {
2017 case Qt::ImEnabled:
2018 v = (bool)(flags() & ItemAcceptsInputMethod);
2019 break;
2020 case Qt::ImHints:
2021 v = (int)d->effectiveInputMethodHints();
2022 break;
2023 case Qt::ImInputItemClipRectangle:
2024 v = QQuickItem::inputMethodQuery(query: property);
2025 break;
2026 case Qt::ImReadOnly:
2027 v = isReadOnly();
2028 break;
2029 default:
2030 if (property == Qt::ImCursorPosition && !argument.isNull())
2031 argument = QVariant(argument.toPointF() - QPointF(d->xoff, d->yoff));
2032 v = d->control->inputMethodQuery(query: property, argument);
2033 if (property == Qt::ImCursorRectangle || property == Qt::ImAnchorRectangle)
2034 v = QVariant(v.toRectF().translated(dx: d->xoff, dy: d->yoff));
2035 break;
2036 }
2037 return v;
2038}
2039
2040/*!
2041\overload
2042Returns the value of the given \a property.
2043*/
2044QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const
2045{
2046 return inputMethodQuery(property, argument: QVariant());
2047}
2048#endif // im
2049
2050void QQuickTextEdit::triggerPreprocess()
2051{
2052 Q_D(QQuickTextEdit);
2053 if (d->updateType == QQuickTextEditPrivate::UpdateNone)
2054 d->updateType = QQuickTextEditPrivate::UpdateOnlyPreprocess;
2055 polish();
2056 update();
2057}
2058
2059typedef QQuickTextEditPrivate::Node TextNode;
2060using TextNodeIterator = QQuickTextEditPrivate::TextNodeIterator;
2061
2062static inline bool operator<(const TextNode &n1, const TextNode &n2)
2063{
2064 return n1.startPos() < n2.startPos();
2065}
2066
2067static inline void updateNodeTransform(QQuickTextNode* node, const QPointF &topLeft)
2068{
2069 QMatrix4x4 transformMatrix;
2070 transformMatrix.translate(x: topLeft.x(), y: topLeft.y());
2071 node->setMatrix(transformMatrix);
2072}
2073
2074/*!
2075 * \internal
2076 *
2077 * Invalidates font caches owned by the text objects owned by the element
2078 * to work around the fact that text objects cannot be used from multiple threads.
2079 */
2080void QQuickTextEdit::invalidateFontCaches()
2081{
2082 Q_D(QQuickTextEdit);
2083 if (d->document == nullptr)
2084 return;
2085
2086 QTextBlock block;
2087 for (block = d->document->firstBlock(); block.isValid(); block = block.next()) {
2088 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
2089 block.layout()->engine()->resetFontEngineCache();
2090 }
2091}
2092
2093inline void resetEngine(QQuickTextNodeEngine *engine, const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor)
2094{
2095 *engine = QQuickTextNodeEngine();
2096 engine->setTextColor(textColor);
2097 engine->setSelectedTextColor(selectedTextColor);
2098 engine->setSelectionColor(selectionColor);
2099}
2100
2101QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
2102{
2103 Q_UNUSED(updatePaintNodeData);
2104 Q_D(QQuickTextEdit);
2105
2106 if (d->updateType != QQuickTextEditPrivate::UpdatePaintNode
2107 && d->updateType != QQuickTextEditPrivate::UpdateAll
2108 && oldNode != nullptr) {
2109 // Update done in preprocess() in the nodes
2110 d->updateType = QQuickTextEditPrivate::UpdateNone;
2111 return oldNode;
2112 }
2113
2114 if (!oldNode || d->updateType == QQuickTextEditPrivate::UpdateAll) {
2115 delete oldNode;
2116 oldNode = nullptr;
2117
2118 // If we had any QQuickTextNode node references, they were deleted along with the root node
2119 // But here we must delete the Node structures in textNodeMap
2120 d->textNodeMap.clear();
2121 }
2122
2123 d->updateType = QQuickTextEditPrivate::UpdateNone;
2124
2125 RootNode *rootNode = static_cast<RootNode *>(oldNode);
2126 TextNodeIterator nodeIterator = d->textNodeMap.begin();
2127 std::optional<int> firstPosAcrossAllNodes;
2128 if (nodeIterator != d->textNodeMap.end())
2129 firstPosAcrossAllNodes = nodeIterator->startPos();
2130
2131 while (nodeIterator != d->textNodeMap.end() && !nodeIterator->dirty())
2132 ++nodeIterator;
2133
2134 QQuickTextNodeEngine engine;
2135 QQuickTextNodeEngine frameDecorationsEngine;
2136
2137 if (!oldNode || nodeIterator < d->textNodeMap.end() || d->textNodeMap.isEmpty()) {
2138
2139 if (!oldNode)
2140 rootNode = new RootNode;
2141
2142 int firstDirtyPos = 0;
2143 if (nodeIterator != d->textNodeMap.end()) {
2144 firstDirtyPos = nodeIterator->startPos();
2145 // ### this could be optimized if the first and last dirty nodes are not connected
2146 // as the intermediate text nodes would usually only need to be transformed differently.
2147 QQuickTextNode *firstCleanNode = nullptr;
2148 auto it = d->textNodeMap.constEnd();
2149 while (it != nodeIterator) {
2150 --it;
2151 if (it->dirty())
2152 break;
2153 firstCleanNode = it->textNode();
2154 }
2155 do {
2156 rootNode->removeChildNode(node: nodeIterator->textNode());
2157 delete nodeIterator->textNode();
2158 nodeIterator = d->textNodeMap.erase(pos: nodeIterator);
2159 } while (nodeIterator != d->textNodeMap.constEnd() && nodeIterator->textNode() != firstCleanNode);
2160 }
2161
2162 // If there's a lot of text, insert only the range of blocks that can possibly be visible within the viewport.
2163 QRectF viewport;
2164 if (flags().testFlag(flag: QQuickItem::ItemObservesViewport)) {
2165 viewport = clipRect();
2166 qCDebug(lcVP) << "text viewport" << viewport;
2167 }
2168
2169 // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
2170 rootNode->resetFrameDecorations(newNode: d->createTextNode());
2171 resetEngine(engine: &frameDecorationsEngine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2172
2173 QQuickTextNode *node = nullptr;
2174
2175 int currentNodeSize = 0;
2176 int nodeStart = firstDirtyPos;
2177 QPointF basePosition(d->xoff, d->yoff);
2178 QMatrix4x4 basePositionMatrix;
2179 basePositionMatrix.translate(x: basePosition.x(), y: basePosition.y());
2180 rootNode->setMatrix(basePositionMatrix);
2181
2182 QPointF nodeOffset;
2183 const TextNode firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator
2184 : TextNode();
2185
2186 QList<QTextFrame *> frames;
2187 frames.append(t: d->document->rootFrame());
2188
2189
2190 d->firstBlockInViewport = -1;
2191 d->firstBlockPastViewport = -1;
2192 while (!frames.isEmpty()) {
2193 QTextFrame *textFrame = frames.takeFirst();
2194 frames.append(l: textFrame->childFrames());
2195 frameDecorationsEngine.addFrameDecorations(document: d->document, frame: textFrame);
2196
2197 if (textFrame->lastPosition() < firstDirtyPos
2198 || textFrame->firstPosition() >= firstCleanNode.startPos())
2199 continue;
2200 resetEngine(engine: &engine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2201
2202 if (textFrame->firstPosition() > textFrame->lastPosition()
2203 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
2204 node = d->createTextNode();
2205 updateNodeTransform(node, topLeft: d->document->documentLayout()->frameBoundingRect(frame: textFrame).topLeft());
2206 const int pos = textFrame->firstPosition() - 1;
2207 auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(d->document->documentLayout());
2208 QTextCharFormat format = a->formatAccessor(pos);
2209 QTextBlock block = textFrame->firstCursorPosition().block();
2210 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2211 bool inView = true;
2212 if (!viewport.isNull() && block.layout()) {
2213 QRectF coveredRegion = block.layout()->boundingRect().adjusted(xp1: nodeOffset.x(), yp1: nodeOffset.y(), xp2: nodeOffset.x(), yp2: nodeOffset.y());
2214 inView = coveredRegion.bottom() >= viewport.top() && coveredRegion.top() <= viewport.bottom();
2215 qCDebug(lcVP) << "non-flow frame" << coveredRegion << "in viewport?" << inView;
2216 }
2217 if (inView) {
2218 engine.setCurrentLine(block.layout()->lineForTextPosition(pos: pos - block.position()));
2219 engine.addTextObject(block, position: QPointF(0, 0), format, selectionState: QQuickTextNodeEngine::Unselected, textDocument: d->document,
2220 pos, layoutPosition: textFrame->frameFormat().position());
2221 }
2222 nodeStart = pos;
2223 } else {
2224 // Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
2225 QList<int> frameBoundaries;
2226 frameBoundaries.reserve(size: frames.size());
2227 for (QTextFrame *frame : std::as_const(t&: frames))
2228 frameBoundaries.append(t: frame->firstPosition());
2229 std::sort(first: frameBoundaries.begin(), last: frameBoundaries.end());
2230
2231 QTextFrame::iterator it = textFrame->begin();
2232 while (!it.atEnd()) {
2233 QTextBlock block = it.currentBlock();
2234 if (block.position() < firstDirtyPos) {
2235 ++it;
2236 continue;
2237 }
2238
2239 if (!engine.hasContents())
2240 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2241
2242 bool inView = true;
2243 if (!viewport.isNull()) {
2244 QRectF coveredRegion;
2245 if (block.layout()) {
2246 coveredRegion = block.layout()->boundingRect().adjusted(xp1: nodeOffset.x(), yp1: nodeOffset.y(), xp2: nodeOffset.x(), yp2: nodeOffset.y());
2247 inView = coveredRegion.bottom() > viewport.top();
2248 }
2249 const bool potentiallyScrollingBackwards = firstPosAcrossAllNodes && *firstPosAcrossAllNodes == firstDirtyPos;
2250 if (d->firstBlockInViewport < 0 && inView && potentiallyScrollingBackwards) {
2251 // During backward scrolling, we need to iterate backwards from textNodeMap.begin() to fill the top of the viewport.
2252 if (coveredRegion.top() > viewport.top() + 1) {
2253 qCDebug(lcVP) << "checking backwards from block" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2254 while (it != textFrame->begin() && it.currentBlock().layout() &&
2255 it.currentBlock().layout()->boundingRect().top() + nodeOffset.y() > viewport.top()) {
2256 nodeOffset = d->document->documentLayout()->blockBoundingRect(block: it.currentBlock()).topLeft();
2257 --it;
2258 }
2259 if (!it.currentBlock().layout())
2260 ++it;
2261 if (Q_LIKELY(it.currentBlock().layout())) {
2262 block = it.currentBlock();
2263 coveredRegion = block.layout()->boundingRect().adjusted(xp1: nodeOffset.x(), yp1: nodeOffset.y(), xp2: nodeOffset.x(), yp2: nodeOffset.y());
2264 firstDirtyPos = it.currentBlock().position();
2265 } else {
2266 qCWarning(lcVP) << "failed to find a text block with layout during back-scrolling";
2267 }
2268 }
2269 qCDebug(lcVP) << "first block in viewport" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2270 d->firstBlockInViewport = block.blockNumber();
2271 if (block.layout())
2272 d->renderedRegion = coveredRegion;
2273 } else {
2274 if (nodeOffset.y() > viewport.bottom()) {
2275 inView = false;
2276 if (d->firstBlockInViewport >= 0 && d->firstBlockPastViewport < 0) {
2277 qCDebug(lcVP) << "first block past viewport" << viewport << block.blockNumber()
2278 << "@" << nodeOffset.y() << "total region rendered" << d->renderedRegion;
2279 d->firstBlockPastViewport = block.blockNumber();
2280 }
2281 break; // skip rest of blocks in this frame
2282 }
2283 if (inView && !block.text().isEmpty() && coveredRegion.isValid())
2284 d->renderedRegion = d->renderedRegion.united(r: coveredRegion);
2285 }
2286 }
2287
2288 bool createdNodeInView = false;
2289 if (inView) {
2290 if (!engine.hasContents()) {
2291 if (node && !node->parent())
2292 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2293 node = d->createTextNode();
2294 createdNodeInView = true;
2295 updateNodeTransform(node, topLeft: nodeOffset);
2296 nodeStart = block.position();
2297 }
2298 engine.addTextBlock(d->document, block, position: -nodeOffset, textColor: d->color, anchorColor: QColor(), selectionStart: selectionStart(), selectionEnd: selectionEnd() - 1);
2299 currentNodeSize += block.length();
2300 }
2301
2302 if ((it.atEnd()) || block.next().position() >= firstCleanNode.startPos())
2303 break; // last node that needed replacing or last block of the frame
2304 QList<int>::const_iterator lowerBound = std::lower_bound(frameBoundaries.constBegin(), frameBoundaries.constEnd(), block.next().position());
2305 if (node && (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart)) {
2306 currentNodeSize = 0;
2307 if (!node->parent())
2308 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2309 if (!createdNodeInView)
2310 node = d->createTextNode();
2311 resetEngine(engine: &engine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2312 nodeStart = block.next().position();
2313 }
2314 ++it;
2315 } // loop over blocks in frame
2316 }
2317 if (Q_LIKELY(node && !node->parent()))
2318 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2319 }
2320 frameDecorationsEngine.addToSceneGraph(parent: rootNode->frameDecorationsNode, style: QQuickText::Normal, styleColor: QColor());
2321 // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
2322 rootNode->prependChildNode(node: rootNode->frameDecorationsNode);
2323
2324 Q_ASSERT(nodeIterator == d->textNodeMap.end()
2325 || (nodeIterator->textNode() == firstCleanNode.textNode()
2326 && nodeIterator->startPos() == firstCleanNode.startPos()));
2327 // Update the position of the subsequent text blocks.
2328 if (firstCleanNode.textNode() != nullptr) {
2329 QPointF oldOffset = firstCleanNode.textNode()->matrix().map(point: QPointF(0,0));
2330 QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(
2331 block: d->document->findBlock(pos: firstCleanNode.startPos())).topLeft();
2332 QPointF delta = currentOffset - oldOffset;
2333 while (nodeIterator != d->textNodeMap.end()) {
2334 QMatrix4x4 transformMatrix = nodeIterator->textNode()->matrix();
2335 transformMatrix.translate(x: delta.x(), y: delta.y());
2336 nodeIterator->textNode()->setMatrix(transformMatrix);
2337 ++nodeIterator;
2338 }
2339
2340 }
2341
2342 // Since we iterate over blocks from different text frames that are potentially not sorted
2343 // we need to ensure that our list of nodes is sorted again:
2344 std::sort(first: d->textNodeMap.begin(), last: d->textNodeMap.end());
2345 }
2346
2347 if (d->cursorComponent == nullptr) {
2348 QSGInternalRectangleNode* cursor = nullptr;
2349 if (!isReadOnly() && d->cursorVisible && d->control->cursorOn() && d->control->cursorVisible())
2350 cursor = d->sceneGraphContext()->createInternalRectangleNode(rect: d->control->cursorRect(), c: d->color);
2351 rootNode->resetCursorNode(newNode: cursor);
2352 }
2353
2354 invalidateFontCaches();
2355
2356 return rootNode;
2357}
2358
2359void QQuickTextEdit::updatePolish()
2360{
2361 invalidateFontCaches();
2362}
2363
2364/*!
2365 \qmlproperty bool QtQuick::TextEdit::canPaste
2366
2367 Returns true if the TextEdit is writable and the content of the clipboard is
2368 suitable for pasting into the TextEdit.
2369*/
2370bool QQuickTextEdit::canPaste() const
2371{
2372 Q_D(const QQuickTextEdit);
2373 if (!d->canPasteValid) {
2374 const_cast<QQuickTextEditPrivate *>(d)->canPaste = d->control->canPaste();
2375 const_cast<QQuickTextEditPrivate *>(d)->canPasteValid = true;
2376 }
2377 return d->canPaste;
2378}
2379
2380/*!
2381 \qmlproperty bool QtQuick::TextEdit::canUndo
2382
2383 Returns true if the TextEdit is writable and there are previous operations
2384 that can be undone.
2385*/
2386
2387bool QQuickTextEdit::canUndo() const
2388{
2389 Q_D(const QQuickTextEdit);
2390 return d->document->isUndoAvailable();
2391}
2392
2393/*!
2394 \qmlproperty bool QtQuick::TextEdit::canRedo
2395
2396 Returns true if the TextEdit is writable and there are \l {undo}{undone}
2397 operations that can be redone.
2398*/
2399
2400bool QQuickTextEdit::canRedo() const
2401{
2402 Q_D(const QQuickTextEdit);
2403 return d->document->isRedoAvailable();
2404}
2405
2406/*!
2407 \qmlproperty bool QtQuick::TextEdit::inputMethodComposing
2408
2409
2410 This property holds whether the TextEdit has partial text input from an
2411 input method.
2412
2413 While it is composing an input method may rely on mouse or key events from
2414 the TextEdit to edit or commit the partial text. This property can be used
2415 to determine when to disable events handlers that may interfere with the
2416 correct operation of an input method.
2417*/
2418bool QQuickTextEdit::isInputMethodComposing() const
2419{
2420#if !QT_CONFIG(im)
2421 return false;
2422#else
2423 Q_D(const QQuickTextEdit);
2424 return d->control->hasImState();
2425#endif // im
2426}
2427
2428QQuickTextEditPrivate::ExtraData::ExtraData()
2429 : padding(0)
2430 , topPadding(0)
2431 , leftPadding(0)
2432 , rightPadding(0)
2433 , bottomPadding(0)
2434 , explicitTopPadding(false)
2435 , explicitLeftPadding(false)
2436 , explicitRightPadding(false)
2437 , explicitBottomPadding(false)
2438 , implicitResize(true)
2439{
2440}
2441
2442void QQuickTextEditPrivate::init()
2443{
2444 Q_Q(QQuickTextEdit);
2445
2446#if QT_CONFIG(clipboard)
2447 if (QGuiApplication::clipboard()->supportsSelection())
2448 q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
2449 else
2450#endif
2451 q->setAcceptedMouseButtons(Qt::LeftButton);
2452
2453#if QT_CONFIG(im)
2454 q->setFlag(flag: QQuickItem::ItemAcceptsInputMethod);
2455#endif
2456 q->setFlag(flag: QQuickItem::ItemHasContents);
2457
2458 q->setAcceptHoverEvents(true);
2459
2460 document = new QQuickTextDocumentWithImageResources(q);
2461
2462 control = new QQuickTextControl(document, q);
2463 control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
2464 control->setAcceptRichText(false);
2465 control->setCursorIsFocusIndicator(true);
2466 q->setKeepMouseGrab(true);
2467
2468 qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
2469 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
2470 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2471 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2472 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
2473 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
2474 qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
2475 qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool)));
2476 qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
2477 qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged()));
2478#if QT_CONFIG(clipboard)
2479 qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
2480#endif
2481 qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
2482 qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
2483 qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize()));
2484 QObject::connect(sender: document, signal: &QQuickTextDocumentWithImageResources::contentsChange, context: q, slot: &QQuickTextEdit::q_contentsChange);
2485 QObject::connect(sender: document->documentLayout(), signal: &QAbstractTextDocumentLayout::updateBlock, context: q, slot: &QQuickTextEdit::invalidateBlock);
2486 QObject::connect(sender: control, signal: &QQuickTextControl::linkHovered, context: q, slot: &QQuickTextEdit::q_linkHovered);
2487 QObject::connect(sender: control, signal: &QQuickTextControl::markerHovered, context: q, slot: &QQuickTextEdit::q_markerHovered);
2488
2489 document->setDefaultFont(font);
2490 document->setDocumentMargin(textMargin);
2491 document->setUndoRedoEnabled(false); // flush undo buffer.
2492 document->setUndoRedoEnabled(true);
2493 updateDefaultTextOption();
2494 q->updateSize();
2495#if QT_CONFIG(cursor)
2496 updateMouseCursorShape();
2497#endif
2498}
2499
2500void QQuickTextEditPrivate::resetInputMethod()
2501{
2502 Q_Q(QQuickTextEdit);
2503 if (!q->isReadOnly() && q->hasActiveFocus() && qGuiApp)
2504 QGuiApplication::inputMethod()->reset();
2505}
2506
2507void QQuickTextEdit::q_textChanged()
2508{
2509 Q_D(QQuickTextEdit);
2510 d->textCached = false;
2511 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2512 d->contentDirection = d->textDirection(text: it.text());
2513 if (d->contentDirection != Qt::LayoutDirectionAuto)
2514 break;
2515 }
2516 d->determineHorizontalAlignment();
2517 d->updateDefaultTextOption();
2518 updateSize();
2519
2520 markDirtyNodesForRange(start: 0, end: d->document->characterCount(), charDelta: 0);
2521 if (isComponentComplete()) {
2522 polish();
2523 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2524 update();
2525 }
2526
2527 emit textChanged();
2528}
2529
2530void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
2531{
2532 Q_D(QQuickTextEdit);
2533 if (start == end)
2534 return;
2535
2536 TextNode dummyNode(start);
2537
2538 const TextNodeIterator textNodeMapBegin = d->textNodeMap.begin();
2539 const TextNodeIterator textNodeMapEnd = d->textNodeMap.end();
2540
2541 TextNodeIterator it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, dummyNode);
2542 // qLowerBound gives us the first node past the start of the affected portion, rewind to the first node
2543 // that starts at the last position before the edit position. (there might be several because of images)
2544 if (it != textNodeMapBegin) {
2545 --it;
2546 TextNode otherDummy(it->startPos());
2547 it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, otherDummy);
2548 }
2549
2550 // mark the affected nodes as dirty
2551 while (it != textNodeMapEnd) {
2552 if (it->startPos() <= end)
2553 it->setDirty();
2554 else if (charDelta)
2555 it->moveStartPos(delta: charDelta);
2556 else
2557 return;
2558 ++it;
2559 }
2560}
2561
2562void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
2563{
2564 Q_D(QQuickTextEdit);
2565
2566 const int editRange = pos + qMax(a: charsAdded, b: charsRemoved);
2567 const int delta = charsAdded - charsRemoved;
2568
2569 markDirtyNodesForRange(start: pos, end: editRange, charDelta: delta);
2570
2571 if (isComponentComplete()) {
2572 polish();
2573 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2574 update();
2575 }
2576}
2577
2578void QQuickTextEdit::moveCursorDelegate()
2579{
2580 Q_D(QQuickTextEdit);
2581#if QT_CONFIG(im)
2582 updateInputMethod();
2583#endif
2584 emit cursorRectangleChanged();
2585 if (!d->cursorItem)
2586 return;
2587 QRectF cursorRect = cursorRectangle();
2588 d->cursorItem->setX(cursorRect.x());
2589 d->cursorItem->setY(cursorRect.y());
2590 d->cursorItem->setHeight(cursorRect.height());
2591}
2592
2593void QQuickTextEdit::updateSelection()
2594{
2595 Q_D(QQuickTextEdit);
2596
2597 // No need for node updates when we go from an empty selection to another empty selection
2598 if (d->control->textCursor().hasSelection() || d->hadSelection) {
2599 markDirtyNodesForRange(start: qMin(a: d->lastSelectionStart, b: d->control->textCursor().selectionStart()), end: qMax(a: d->control->textCursor().selectionEnd(), b: d->lastSelectionEnd), charDelta: 0);
2600 if (isComponentComplete()) {
2601 polish();
2602 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2603 update();
2604 }
2605 }
2606
2607 d->hadSelection = d->control->textCursor().hasSelection();
2608
2609 if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
2610 d->lastSelectionStart = d->control->textCursor().selectionStart();
2611 emit selectionStartChanged();
2612 }
2613 if (d->lastSelectionEnd != d->control->textCursor().selectionEnd()) {
2614 d->lastSelectionEnd = d->control->textCursor().selectionEnd();
2615 emit selectionEndChanged();
2616 }
2617}
2618
2619QRectF QQuickTextEdit::boundingRect() const
2620{
2621 Q_D(const QQuickTextEdit);
2622 QRectF r(
2623 QQuickTextUtil::alignedX(textWidth: d->contentSize.width(), itemWidth: width(), alignment: effectiveHAlign()),
2624 d->yoff,
2625 d->contentSize.width(),
2626 d->contentSize.height());
2627
2628 int cursorWidth = 1;
2629 if (d->cursorItem)
2630 cursorWidth = 0;
2631 else if (!d->document->isEmpty())
2632 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2633
2634 // Could include font max left/right bearings to either side of rectangle.
2635 r.setRight(r.right() + cursorWidth);
2636
2637 return r;
2638}
2639
2640QRectF QQuickTextEdit::clipRect() const
2641{
2642 Q_D(const QQuickTextEdit);
2643 QRectF r = QQuickImplicitSizeItem::clipRect();
2644 int cursorWidth = 1;
2645 if (d->cursorItem)
2646 cursorWidth = d->cursorItem->width();
2647 if (!d->document->isEmpty())
2648 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2649
2650 // Could include font max left/right bearings to either side of rectangle.
2651
2652 r.setRight(r.right() + cursorWidth);
2653 return r;
2654}
2655
2656qreal QQuickTextEditPrivate::getImplicitWidth() const
2657{
2658 Q_Q(const QQuickTextEdit);
2659 if (!requireImplicitWidth) {
2660 // We don't calculate implicitWidth unless it is required.
2661 // We need to force a size update now to ensure implicitWidth is calculated
2662 const_cast<QQuickTextEditPrivate*>(this)->requireImplicitWidth = true;
2663 const_cast<QQuickTextEdit*>(q)->updateSize();
2664 }
2665 return implicitWidth;
2666}
2667
2668//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't
2669// need to do all the calculations each time
2670void QQuickTextEdit::updateSize()
2671{
2672 Q_D(QQuickTextEdit);
2673 if (!isComponentComplete()) {
2674 d->dirty = true;
2675 return;
2676 }
2677
2678 // ### assumes that if the width is set, the text will fill to edges
2679 // ### (unless wrap is false, then clipping will occur)
2680 if (widthValid()) {
2681 if (!d->requireImplicitWidth) {
2682 emit implicitWidthChanged();
2683 // if the implicitWidth is used, then updateSize() has already been called (recursively)
2684 if (d->requireImplicitWidth)
2685 return;
2686 }
2687 if (d->requireImplicitWidth) {
2688 d->document->setTextWidth(-1);
2689 const qreal naturalWidth = d->document->idealWidth();
2690 const bool wasInLayout = d->inLayout;
2691 d->inLayout = true;
2692 if (d->isImplicitResizeEnabled())
2693 setImplicitWidth(naturalWidth + leftPadding() + rightPadding());
2694 d->inLayout = wasInLayout;
2695 if (d->inLayout) // probably the result of a binding loop, but by letting it
2696 return; // get this far we'll get a warning to that effect.
2697 }
2698 const qreal newTextWidth = width() - leftPadding() - rightPadding();
2699 if (d->document->textWidth() != newTextWidth)
2700 d->document->setTextWidth(newTextWidth);
2701 } else if (d->wrapMode == NoWrap) {
2702 // normally, if explicit width is not set, we should call setTextWidth(-1) here,
2703 // as we don't need to fit the text to any fixed width. But because of some bug
2704 // in QTextDocument it also breaks RTL text alignment, so we use "idealWidth" instead.
2705 const qreal newTextWidth = d->document->idealWidth();
2706 if (d->document->textWidth() != newTextWidth)
2707 d->document->setTextWidth(newTextWidth);
2708 } else {
2709 d->document->setTextWidth(-1);
2710 }
2711
2712 QFontMetricsF fm(d->font);
2713 const qreal newHeight = d->document->isEmpty() ? qCeil(v: fm.height()) : d->document->size().height();
2714 const qreal newWidth = d->document->idealWidth();
2715
2716 if (d->isImplicitResizeEnabled()) {
2717 // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
2718 if (!widthValid())
2719 setImplicitSize(newWidth + leftPadding() + rightPadding(), newHeight + topPadding() + bottomPadding());
2720 else
2721 setImplicitHeight(newHeight + topPadding() + bottomPadding());
2722 }
2723
2724 d->xoff = leftPadding() + qMax(a: qreal(0), b: QQuickTextUtil::alignedX(textWidth: d->document->size().width(), itemWidth: width() - leftPadding() - rightPadding(), alignment: effectiveHAlign()));
2725 d->yoff = topPadding() + QQuickTextUtil::alignedY(textHeight: d->document->size().height(), itemHeight: height() - topPadding() - bottomPadding(), alignment: d->vAlign);
2726 setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
2727
2728 QSizeF size(newWidth, newHeight);
2729 if (d->contentSize != size) {
2730 d->contentSize = size;
2731 // Note: inResize is a bitfield so QScopedValueRollback can't be used here
2732 const bool wasInResize = d->inResize;
2733 d->inResize = true;
2734 if (!wasInResize)
2735 emit contentSizeChanged();
2736 d->inResize = wasInResize;
2737 updateTotalLines();
2738 }
2739}
2740
2741void QQuickTextEdit::updateWholeDocument()
2742{
2743 Q_D(QQuickTextEdit);
2744 if (!d->textNodeMap.isEmpty()) {
2745 for (TextNode &node : d->textNodeMap)
2746 node.setDirty();
2747 }
2748
2749 if (isComponentComplete()) {
2750 polish();
2751 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2752 update();
2753 }
2754}
2755
2756void QQuickTextEdit::invalidateBlock(const QTextBlock &block)
2757{
2758 Q_D(QQuickTextEdit);
2759 markDirtyNodesForRange(start: block.position(), end: block.position() + block.length(), charDelta: 0);
2760
2761 if (isComponentComplete()) {
2762 polish();
2763 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2764 update();
2765 }
2766}
2767
2768void QQuickTextEdit::updateCursor()
2769{
2770 Q_D(QQuickTextEdit);
2771 if (isComponentComplete() && isVisible()) {
2772 polish();
2773 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2774 update();
2775 }
2776}
2777
2778void QQuickTextEdit::q_linkHovered(const QString &link)
2779{
2780 Q_D(QQuickTextEdit);
2781 emit linkHovered(link);
2782#if QT_CONFIG(cursor)
2783 if (link.isEmpty()) {
2784 d->updateMouseCursorShape();
2785 } else if (cursor().shape() != Qt::PointingHandCursor) {
2786 setCursor(Qt::PointingHandCursor);
2787 }
2788#endif
2789}
2790
2791void QQuickTextEdit::q_markerHovered(bool hovered)
2792{
2793 Q_D(QQuickTextEdit);
2794#if QT_CONFIG(cursor)
2795 if (!hovered) {
2796 d->updateMouseCursorShape();
2797 } else if (cursor().shape() != Qt::PointingHandCursor) {
2798 setCursor(Qt::PointingHandCursor);
2799 }
2800#endif
2801}
2802
2803void QQuickTextEdit::q_updateAlignment()
2804{
2805 Q_D(QQuickTextEdit);
2806 if (d->determineHorizontalAlignment()) {
2807 d->updateDefaultTextOption();
2808 d->xoff = qMax(a: qreal(0), b: QQuickTextUtil::alignedX(textWidth: d->document->size().width(), itemWidth: width(), alignment: effectiveHAlign()));
2809 moveCursorDelegate();
2810 }
2811}
2812
2813void QQuickTextEdit::updateTotalLines()
2814{
2815 Q_D(QQuickTextEdit);
2816
2817 int subLines = 0;
2818
2819 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2820 QTextLayout *layout = it.layout();
2821 if (!layout)
2822 continue;
2823 subLines += layout->lineCount()-1;
2824 }
2825
2826 int newTotalLines = d->document->lineCount() + subLines;
2827 if (d->lineCount != newTotalLines) {
2828 d->lineCount = newTotalLines;
2829 emit lineCountChanged();
2830 }
2831}
2832
2833void QQuickTextEditPrivate::updateDefaultTextOption()
2834{
2835 Q_Q(QQuickTextEdit);
2836 QTextOption opt = document->defaultTextOption();
2837 const Qt::Alignment oldAlignment = opt.alignment();
2838 Qt::LayoutDirection oldTextDirection = opt.textDirection();
2839
2840 QQuickTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign();
2841 if (contentDirection == Qt::RightToLeft) {
2842 if (horizontalAlignment == QQuickTextEdit::AlignLeft)
2843 horizontalAlignment = QQuickTextEdit::AlignRight;
2844 else if (horizontalAlignment == QQuickTextEdit::AlignRight)
2845 horizontalAlignment = QQuickTextEdit::AlignLeft;
2846 }
2847 if (!hAlignImplicit)
2848 opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign));
2849 else
2850 opt.setAlignment(Qt::Alignment(vAlign));
2851
2852#if QT_CONFIG(im)
2853 if (contentDirection == Qt::LayoutDirectionAuto) {
2854 opt.setTextDirection(qGuiApp->inputMethod()->inputDirection());
2855 } else
2856#endif
2857 {
2858 opt.setTextDirection(contentDirection);
2859 }
2860
2861 QTextOption::WrapMode oldWrapMode = opt.wrapMode();
2862 opt.setWrapMode(QTextOption::WrapMode(wrapMode));
2863
2864 bool oldUseDesignMetrics = opt.useDesignMetrics();
2865 opt.setUseDesignMetrics(renderType != QQuickTextEdit::NativeRendering);
2866
2867 if (oldWrapMode != opt.wrapMode() || oldAlignment != opt.alignment()
2868 || oldTextDirection != opt.textDirection()
2869 || oldUseDesignMetrics != opt.useDesignMetrics()) {
2870 document->setDefaultTextOption(opt);
2871 }
2872}
2873
2874void QQuickTextEdit::focusInEvent(QFocusEvent *event)
2875{
2876 Q_D(QQuickTextEdit);
2877 d->handleFocusEvent(event);
2878 QQuickImplicitSizeItem::focusInEvent(event);
2879}
2880
2881void QQuickTextEdit::focusOutEvent(QFocusEvent *event)
2882{
2883 Q_D(QQuickTextEdit);
2884 d->handleFocusEvent(event);
2885 QQuickImplicitSizeItem::focusOutEvent(event);
2886}
2887
2888void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
2889{
2890 Q_Q(QQuickTextEdit);
2891 bool focus = event->type() == QEvent::FocusIn;
2892 if (!q->isReadOnly())
2893 q->setCursorVisible(focus);
2894 control->processEvent(e: event, coordinateOffset: QPointF(-xoff, -yoff));
2895 if (focus) {
2896 q->q_updateAlignment();
2897#if QT_CONFIG(im)
2898 if (focusOnPress && !q->isReadOnly())
2899 qGuiApp->inputMethod()->show();
2900 q->connect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
2901 receiver: q, SLOT(q_updateAlignment()));
2902#endif
2903 } else {
2904#if QT_CONFIG(im)
2905 q->disconnect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
2906 receiver: q, SLOT(q_updateAlignment()));
2907#endif
2908 if (event->reason() != Qt::ActiveWindowFocusReason
2909 && event->reason() != Qt::PopupFocusReason
2910 && control->textCursor().hasSelection()
2911 && !persistentSelection)
2912 q->deselect();
2913
2914 emit q->editingFinished();
2915 }
2916}
2917
2918void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QQuickTextNode *node, TextNodeIterator &it, int startPos)
2919{
2920 engine->addToSceneGraph(parent: node, style: QQuickText::Normal, styleColor: QColor());
2921 it = textNodeMap.insert(before: it, t: TextNode(startPos, node));
2922 ++it;
2923 root->appendChildNode(node);
2924}
2925
2926QQuickTextNode *QQuickTextEditPrivate::createTextNode()
2927{
2928 Q_Q(QQuickTextEdit);
2929 QQuickTextNode* node = new QQuickTextNode(q);
2930 node->setUseNativeRenderer(renderType == QQuickTextEdit::NativeRendering);
2931 return node;
2932}
2933
2934void QQuickTextEdit::q_canPasteChanged()
2935{
2936 Q_D(QQuickTextEdit);
2937 bool old = d->canPaste;
2938 d->canPaste = d->control->canPaste();
2939 bool changed = old!=d->canPaste || !d->canPasteValid;
2940 d->canPasteValid = true;
2941 if (changed)
2942 emit canPasteChanged();
2943}
2944
2945/*!
2946 \qmlmethod string QtQuick::TextEdit::getText(int start, int end)
2947
2948 Returns the section of text that is between the \a start and \a end positions.
2949
2950 The returned text does not include any rich text formatting.
2951*/
2952
2953QString QQuickTextEdit::getText(int start, int end) const
2954{
2955 Q_D(const QQuickTextEdit);
2956 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
2957 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
2958 QTextCursor cursor(d->document);
2959 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
2960 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
2961#if QT_CONFIG(texthtmlparser)
2962 return d->richText || d->markdownText
2963 ? cursor.selectedText()
2964 : cursor.selection().toPlainText();
2965#else
2966 return cursor.selection().toPlainText();
2967#endif
2968}
2969
2970/*!
2971 \qmlmethod string QtQuick::TextEdit::getFormattedText(int start, int end)
2972
2973 Returns the section of text that is between the \a start and \a end positions.
2974
2975 The returned text will be formatted according the \l textFormat property.
2976*/
2977
2978QString QQuickTextEdit::getFormattedText(int start, int end) const
2979{
2980 Q_D(const QQuickTextEdit);
2981
2982 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
2983 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
2984
2985 QTextCursor cursor(d->document);
2986 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
2987 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
2988
2989 if (d->richText) {
2990#if QT_CONFIG(texthtmlparser)
2991 return cursor.selection().toHtml();
2992#else
2993 return cursor.selection().toPlainText();
2994#endif
2995 } else if (d->markdownText) {
2996#if QT_CONFIG(textmarkdownwriter)
2997 return cursor.selection().toMarkdown();
2998#else
2999 return cursor.selection().toPlainText();
3000#endif
3001 } else {
3002 return cursor.selection().toPlainText();
3003 }
3004}
3005
3006/*!
3007 \qmlmethod QtQuick::TextEdit::insert(int position, string text)
3008
3009 Inserts \a text into the TextEdit at \a position.
3010*/
3011void QQuickTextEdit::insert(int position, const QString &text)
3012{
3013 Q_D(QQuickTextEdit);
3014 if (position < 0 || position >= d->document->characterCount())
3015 return;
3016 QTextCursor cursor(d->document);
3017 cursor.setPosition(pos: position);
3018 d->richText = d->richText || (d->format == AutoText && Qt::mightBeRichText(text));
3019 if (d->richText) {
3020#if QT_CONFIG(texthtmlparser)
3021 cursor.insertHtml(html: text);
3022#else
3023 cursor.insertText(text);
3024#endif
3025 } else if (d->markdownText) {
3026#if QT_CONFIG(textmarkdownreader)
3027 cursor.insertMarkdown(markdown: text);
3028#else
3029 cursor.insertText(text);
3030#endif
3031 } else {
3032 cursor.insertText(text);
3033 }
3034 d->control->updateCursorRectangle(force: false);
3035}
3036
3037/*!
3038 \qmlmethod string QtQuick::TextEdit::remove(int start, int end)
3039
3040 Removes the section of text that is between the \a start and \a end positions from the TextEdit.
3041*/
3042
3043void QQuickTextEdit::remove(int start, int end)
3044{
3045 Q_D(QQuickTextEdit);
3046 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
3047 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
3048 QTextCursor cursor(d->document);
3049 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
3050 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
3051 cursor.removeSelectedText();
3052 d->control->updateCursorRectangle(force: false);
3053}
3054
3055/*!
3056 \qmlproperty TextDocument QtQuick::TextEdit::textDocument
3057 \since 5.1
3058
3059 Returns the QQuickTextDocument of this TextEdit.
3060 It can be used to implement syntax highlighting using
3061 \l QSyntaxHighlighter.
3062
3063 \sa QQuickTextDocument
3064*/
3065
3066QQuickTextDocument *QQuickTextEdit::textDocument()
3067{
3068 Q_D(QQuickTextEdit);
3069 if (!d->quickDocument)
3070 d->quickDocument = new QQuickTextDocument(this);
3071 return d->quickDocument;
3072}
3073
3074bool QQuickTextEditPrivate::isLinkHoveredConnected()
3075{
3076 Q_Q(QQuickTextEdit);
3077 IS_SIGNAL_CONNECTED(q, QQuickTextEdit, linkHovered, (const QString &));
3078}
3079
3080#if QT_CONFIG(cursor)
3081void QQuickTextEditPrivate::updateMouseCursorShape()
3082{
3083 Q_Q(QQuickTextEdit);
3084 q->setCursor(q->isReadOnly() && !q->selectByMouse() ? Qt::ArrowCursor : Qt::IBeamCursor);
3085}
3086#endif
3087
3088/*!
3089 \qmlsignal QtQuick::TextEdit::linkHovered(string link)
3090 \since 5.2
3091
3092 This signal is emitted when the user hovers a link embedded in the text.
3093 The link must be in rich text or HTML format and the
3094 \a link string provides access to the particular link.
3095
3096 \sa hoveredLink, linkAt()
3097*/
3098
3099/*!
3100 \qmlsignal QtQuick::TextEdit::editingFinished()
3101 \since 5.6
3102
3103 This signal is emitted when the text edit loses focus.
3104*/
3105
3106/*!
3107 \qmlproperty string QtQuick::TextEdit::hoveredLink
3108 \since 5.2
3109
3110 This property contains the link string when the user hovers a link
3111 embedded in the text. The link must be in rich text or HTML format
3112 and the link string provides access to the particular link.
3113
3114 \sa linkHovered, linkAt()
3115*/
3116
3117QString QQuickTextEdit::hoveredLink() const
3118{
3119 Q_D(const QQuickTextEdit);
3120 if (const_cast<QQuickTextEditPrivate *>(d)->isLinkHoveredConnected()) {
3121 return d->control->hoveredLink();
3122 } else {
3123#if QT_CONFIG(cursor)
3124 if (QQuickWindow *wnd = window()) {
3125 QPointF pos = QCursor::pos(screen: wnd->screen()) - wnd->position() - mapToScene(point: QPointF(0, 0));
3126 return d->control->anchorAt(pos);
3127 }
3128#endif // cursor
3129 }
3130 return QString();
3131}
3132
3133void QQuickTextEdit::hoverEnterEvent(QHoverEvent *event)
3134{
3135 Q_D(QQuickTextEdit);
3136 if (d->isLinkHoveredConnected())
3137 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
3138 event->ignore();
3139}
3140
3141void QQuickTextEdit::hoverMoveEvent(QHoverEvent *event)
3142{
3143 Q_D(QQuickTextEdit);
3144 if (d->isLinkHoveredConnected())
3145 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
3146 event->ignore();
3147}
3148
3149void QQuickTextEdit::hoverLeaveEvent(QHoverEvent *event)
3150{
3151 Q_D(QQuickTextEdit);
3152 if (d->isLinkHoveredConnected())
3153 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
3154 event->ignore();
3155}
3156
3157/*!
3158 \qmlmethod void QtQuick::TextEdit::append(string text)
3159 \since 5.2
3160
3161 Appends a new paragraph with \a text to the end of the TextEdit.
3162
3163 In order to append without inserting a new paragraph,
3164 call \c myTextEdit.insert(myTextEdit.length, text) instead.
3165*/
3166void QQuickTextEdit::append(const QString &text)
3167{
3168 Q_D(QQuickTextEdit);
3169 QTextCursor cursor(d->document);
3170 cursor.beginEditBlock();
3171 cursor.movePosition(op: QTextCursor::End);
3172
3173 if (!d->document->isEmpty())
3174 cursor.insertBlock();
3175
3176 if (d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text))) {
3177#if QT_CONFIG(texthtmlparser)
3178 cursor.insertHtml(html: text);
3179#else
3180 cursor.insertText(text);
3181#endif
3182 } else if (d->format == MarkdownText) {
3183#if QT_CONFIG(textmarkdownreader)
3184 cursor.insertMarkdown(markdown: text);
3185#else
3186 cursor.insertText(text);
3187#endif
3188 } else {
3189 cursor.insertText(text);
3190 }
3191
3192 cursor.endEditBlock();
3193 d->control->updateCursorRectangle(force: false);
3194}
3195
3196/*!
3197 \qmlmethod QtQuick::TextEdit::linkAt(real x, real y)
3198 \since 5.3
3199
3200 Returns the link string at point \a x, \a y in content coordinates,
3201 or an empty string if no link exists at that point.
3202
3203 \sa hoveredLink
3204*/
3205QString QQuickTextEdit::linkAt(qreal x, qreal y) const
3206{
3207 Q_D(const QQuickTextEdit);
3208 return d->control->anchorAt(pos: QPointF(x + topPadding(), y + leftPadding()));
3209}
3210
3211/*!
3212 \since 5.6
3213 \qmlproperty real QtQuick::TextEdit::padding
3214 \qmlproperty real QtQuick::TextEdit::topPadding
3215 \qmlproperty real QtQuick::TextEdit::leftPadding
3216 \qmlproperty real QtQuick::TextEdit::bottomPadding
3217 \qmlproperty real QtQuick::TextEdit::rightPadding
3218
3219 These properties hold the padding around the content. This space is reserved
3220 in addition to the contentWidth and contentHeight.
3221*/
3222qreal QQuickTextEdit::padding() const
3223{
3224 Q_D(const QQuickTextEdit);
3225 return d->padding();
3226}
3227
3228void QQuickTextEdit::setPadding(qreal padding)
3229{
3230 Q_D(QQuickTextEdit);
3231 if (qFuzzyCompare(p1: d->padding(), p2: padding))
3232 return;
3233
3234 d->extra.value().padding = padding;
3235 updateSize();
3236 if (isComponentComplete()) {
3237 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3238 update();
3239 }
3240 emit paddingChanged();
3241 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3242 emit topPaddingChanged();
3243 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3244 emit leftPaddingChanged();
3245 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3246 emit rightPaddingChanged();
3247 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3248 emit bottomPaddingChanged();
3249}
3250
3251void QQuickTextEdit::resetPadding()
3252{
3253 setPadding(0);
3254}
3255
3256qreal QQuickTextEdit::topPadding() const
3257{
3258 Q_D(const QQuickTextEdit);
3259 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3260 return d->extra->topPadding;
3261 return d->padding();
3262}
3263
3264void QQuickTextEdit::setTopPadding(qreal padding)
3265{
3266 Q_D(QQuickTextEdit);
3267 d->setTopPadding(value: padding);
3268}
3269
3270void QQuickTextEdit::resetTopPadding()
3271{
3272 Q_D(QQuickTextEdit);
3273 d->setTopPadding(value: 0, reset: true);
3274}
3275
3276qreal QQuickTextEdit::leftPadding() const
3277{
3278 Q_D(const QQuickTextEdit);
3279 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3280 return d->extra->leftPadding;
3281 return d->padding();
3282}
3283
3284void QQuickTextEdit::setLeftPadding(qreal padding)
3285{
3286 Q_D(QQuickTextEdit);
3287 d->setLeftPadding(value: padding);
3288}
3289
3290void QQuickTextEdit::resetLeftPadding()
3291{
3292 Q_D(QQuickTextEdit);
3293 d->setLeftPadding(value: 0, reset: true);
3294}
3295
3296qreal QQuickTextEdit::rightPadding() const
3297{
3298 Q_D(const QQuickTextEdit);
3299 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3300 return d->extra->rightPadding;
3301 return d->padding();
3302}
3303
3304void QQuickTextEdit::setRightPadding(qreal padding)
3305{
3306 Q_D(QQuickTextEdit);
3307 d->setRightPadding(value: padding);
3308}
3309
3310void QQuickTextEdit::resetRightPadding()
3311{
3312 Q_D(QQuickTextEdit);
3313 d->setRightPadding(value: 0, reset: true);
3314}
3315
3316qreal QQuickTextEdit::bottomPadding() const
3317{
3318 Q_D(const QQuickTextEdit);
3319 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3320 return d->extra->bottomPadding;
3321 return d->padding();
3322}
3323
3324void QQuickTextEdit::setBottomPadding(qreal padding)
3325{
3326 Q_D(QQuickTextEdit);
3327 d->setBottomPadding(value: padding);
3328}
3329
3330void QQuickTextEdit::resetBottomPadding()
3331{
3332 Q_D(QQuickTextEdit);
3333 d->setBottomPadding(value: 0, reset: true);
3334}
3335
3336/*!
3337 \qmlproperty real QtQuick::TextEdit::tabStopDistance
3338 \since 5.10
3339
3340 The default distance, in device units, between tab stops.
3341
3342 \sa QTextOption::setTabStopDistance()
3343*/
3344int QQuickTextEdit::tabStopDistance() const
3345{
3346 Q_D(const QQuickTextEdit);
3347 return d->document->defaultTextOption().tabStopDistance();
3348}
3349
3350void QQuickTextEdit::setTabStopDistance(qreal distance)
3351{
3352 Q_D(QQuickTextEdit);
3353 QTextOption textOptions = d->document->defaultTextOption();
3354 if (textOptions.tabStopDistance() == distance)
3355 return;
3356
3357 textOptions.setTabStopDistance(distance);
3358 d->document->setDefaultTextOption(textOptions);
3359 emit tabStopDistanceChanged(distance);
3360}
3361
3362/*!
3363 \qmlmethod QtQuick::TextEdit::clear()
3364 \since 5.7
3365
3366 Clears the contents of the text edit
3367 and resets partial text input from an input method.
3368
3369 Use this method instead of setting the \l text property to an empty string.
3370
3371 \sa QInputMethod::reset()
3372*/
3373void QQuickTextEdit::clear()
3374{
3375 Q_D(QQuickTextEdit);
3376 d->resetInputMethod();
3377 d->control->clear();
3378}
3379
3380#ifndef QT_NO_DEBUG_STREAM
3381QDebug operator<<(QDebug debug, const QQuickTextEditPrivate::Node &n)
3382{
3383 QDebugStateSaver saver(debug);
3384 debug.space();
3385 debug << "Node(startPos:" << n.m_startPos << "dirty:" << n.m_dirty << n.m_node << ')';
3386 return debug;
3387}
3388#endif
3389
3390#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
3391void QQuickTextEdit::setOldSelectionDefault()
3392{
3393 Q_D(QQuickTextEdit);
3394 d->selectByMouse = false;
3395 setKeepMouseGrab(false);
3396 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
3397 d->control->setTouchDragSelectionEnabled(true);
3398 qCDebug(lcTextEdit, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
3399}
3400
3401// TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
3402QQuickPre64TextEdit::QQuickPre64TextEdit(QQuickItem *parent)
3403 : QQuickTextEdit(parent)
3404{
3405 setOldSelectionDefault();
3406}
3407#endif
3408
3409QT_END_NAMESPACE
3410
3411#include "moc_qquicktextedit_p.cpp"
3412
3413

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