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