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 moveCursorDelegate();
824 }
825}
826
827void QQuickTextEdit::resetHAlign()
828{
829 Q_D(QQuickTextEdit);
830 d->hAlignImplicit = true;
831 if (d->determineHorizontalAlignment() && isComponentComplete()) {
832 d->updateDefaultTextOption();
833 updateSize();
834 updateWholeDocument();
835 moveCursorDelegate();
836 }
837}
838
839QQuickTextEdit::HAlignment QQuickTextEdit::effectiveHAlign() const
840{
841 Q_D(const QQuickTextEdit);
842 QQuickTextEdit::HAlignment effectiveAlignment = d->hAlign;
843 if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
844 switch (d->hAlign) {
845 case QQuickTextEdit::AlignLeft:
846 effectiveAlignment = QQuickTextEdit::AlignRight;
847 break;
848 case QQuickTextEdit::AlignRight:
849 effectiveAlignment = QQuickTextEdit::AlignLeft;
850 break;
851 default:
852 break;
853 }
854 }
855 return effectiveAlignment;
856}
857
858bool QQuickTextEditPrivate::setHAlign(QQuickTextEdit::HAlignment align, bool forceAlign)
859{
860 Q_Q(QQuickTextEdit);
861 if (hAlign == align && !forceAlign)
862 return false;
863
864 const bool wasImplicit = hAlignImplicit;
865 const auto oldEffectiveHAlign = q->effectiveHAlign();
866
867 hAlignImplicit = !forceAlign;
868 if (hAlign != align) {
869 hAlign = align;
870 emit q->horizontalAlignmentChanged(align);
871 }
872
873 if (q->effectiveHAlign() != oldEffectiveHAlign) {
874 emit q->effectiveHorizontalAlignmentChanged();
875 return true;
876 }
877
878 if (forceAlign && wasImplicit) {
879 // QTBUG-120052 - when horizontal text alignment is set explicitly,
880 // we need notify any other controls that may depend on it, like QQuickPlaceholderText
881 emit q->effectiveHorizontalAlignmentChanged();
882 }
883 return false;
884}
885
886Qt::LayoutDirection QQuickTextEditPrivate::textDirection(const QString &text) const
887{
888 const QChar *character = text.constData();
889 while (!character->isNull()) {
890 switch (character->direction()) {
891 case QChar::DirL:
892 return Qt::LeftToRight;
893 case QChar::DirR:
894 case QChar::DirAL:
895 case QChar::DirAN:
896 return Qt::RightToLeft;
897 default:
898 break;
899 }
900 character++;
901 }
902 return Qt::LayoutDirectionAuto;
903}
904
905bool QQuickTextEditPrivate::determineHorizontalAlignment()
906{
907 Q_Q(QQuickTextEdit);
908 if (!hAlignImplicit || !q->isComponentComplete())
909 return false;
910
911 Qt::LayoutDirection direction = contentDirection;
912#if QT_CONFIG(im)
913 if (direction == Qt::LayoutDirectionAuto) {
914 QTextBlock block = control->textCursor().block();
915 if (!block.layout())
916 return false;
917 direction = textDirection(block.layout()->preeditAreaText());
918 }
919 if (direction == Qt::LayoutDirectionAuto)
920 direction = qGuiApp->inputMethod()->inputDirection();
921#endif
922
923 const auto implicitHAlign = direction == Qt::RightToLeft ?
924 QQuickTextEdit::AlignRight : QQuickTextEdit::AlignLeft;
925 return setHAlign(implicitHAlign);
926}
927
928void QQuickTextEditPrivate::mirrorChange()
929{
930 Q_Q(QQuickTextEdit);
931 if (q->isComponentComplete()) {
932 if (!hAlignImplicit && (hAlign == QQuickTextEdit::AlignRight || hAlign == QQuickTextEdit::AlignLeft)) {
933 updateDefaultTextOption();
934 q->updateSize();
935 q->updateWholeDocument();
936 emit q->effectiveHorizontalAlignmentChanged();
937 }
938 }
939}
940
941bool QQuickTextEditPrivate::transformChanged(QQuickItem *transformedItem)
942{
943 Q_Q(QQuickTextEdit);
944 qCDebug(lcVP) << q << "sees that" << transformedItem << "moved in VP" << q->clipRect();
945
946 // If there's a lot of text, and the TextEdit has been scrolled so that the viewport
947 // no longer completely covers the rendered region, we need QQuickTextEdit::updatePaintNode()
948 // to re-iterate blocks and populate a different range.
949 if (flags & QQuickItem::ItemObservesViewport) {
950 if (QQuickItem *viewport = q->viewportItem()) {
951 QRectF vp = q->mapRectFromItem(viewport, viewport->clipRect());
952 if (!(vp.top() > renderedRegion.top() && vp.bottom() < renderedRegion.bottom())) {
953 qCDebug(lcVP) << "viewport" << vp << "now goes beyond rendered region" << renderedRegion << "; updating";
954 q->updateWholeDocument();
955 }
956 const bool textCursorVisible = cursorVisible && q->cursorRectangle().intersects(vp);
957 if (cursorItem)
958 cursorItem->setVisible(textCursorVisible);
959 else
960 control->setCursorVisible(textCursorVisible);
961 }
962 }
963 return QQuickImplicitSizeItemPrivate::transformChanged(transformedItem);
964}
965
966#if QT_CONFIG(im)
967Qt::InputMethodHints QQuickTextEditPrivate::effectiveInputMethodHints() const
968{
969 return inputMethodHints | Qt::ImhMultiLine;
970}
971#endif
972
973#if QT_CONFIG(accessibility)
974void QQuickTextEditPrivate::accessibilityActiveChanged(bool active)
975{
976 if (!active)
977 return;
978
979 Q_Q(QQuickTextEdit);
980 if (QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(
981 qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true))) {
982 accessibleAttached->setRole(effectiveAccessibleRole());
983 accessibleAttached->set_readOnly(q->isReadOnly());
984 }
985}
986
987QAccessible::Role QQuickTextEditPrivate::accessibleRole() const
988{
989 return QAccessible::EditableText;
990}
991#endif
992
993void QQuickTextEditPrivate::setTopPadding(qreal value, bool reset)
994{
995 Q_Q(QQuickTextEdit);
996 qreal oldPadding = q->topPadding();
997 if (!reset || extra.isAllocated()) {
998 extra.value().topPadding = value;
999 extra.value().explicitTopPadding = !reset;
1000 }
1001 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
1002 q->updateSize();
1003 q->updateWholeDocument();
1004 emit q->topPaddingChanged();
1005 }
1006}
1007
1008void QQuickTextEditPrivate::setLeftPadding(qreal value, bool reset)
1009{
1010 Q_Q(QQuickTextEdit);
1011 qreal oldPadding = q->leftPadding();
1012 if (!reset || extra.isAllocated()) {
1013 extra.value().leftPadding = value;
1014 extra.value().explicitLeftPadding = !reset;
1015 }
1016 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
1017 q->updateSize();
1018 q->updateWholeDocument();
1019 emit q->leftPaddingChanged();
1020 }
1021}
1022
1023void QQuickTextEditPrivate::setRightPadding(qreal value, bool reset)
1024{
1025 Q_Q(QQuickTextEdit);
1026 qreal oldPadding = q->rightPadding();
1027 if (!reset || extra.isAllocated()) {
1028 extra.value().rightPadding = value;
1029 extra.value().explicitRightPadding = !reset;
1030 }
1031 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
1032 q->updateSize();
1033 q->updateWholeDocument();
1034 emit q->rightPaddingChanged();
1035 }
1036}
1037
1038void QQuickTextEditPrivate::setBottomPadding(qreal value, bool reset)
1039{
1040 Q_Q(QQuickTextEdit);
1041 qreal oldPadding = q->bottomPadding();
1042 if (!reset || extra.isAllocated()) {
1043 extra.value().bottomPadding = value;
1044 extra.value().explicitBottomPadding = !reset;
1045 }
1046 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
1047 q->updateSize();
1048 q->updateWholeDocument();
1049 emit q->bottomPaddingChanged();
1050 }
1051}
1052
1053bool QQuickTextEditPrivate::isImplicitResizeEnabled() const
1054{
1055 return !extra.isAllocated() || extra->implicitResize;
1056}
1057
1058void QQuickTextEditPrivate::setImplicitResizeEnabled(bool enabled)
1059{
1060 if (!enabled)
1061 extra.value().implicitResize = false;
1062 else if (extra.isAllocated())
1063 extra->implicitResize = true;
1064}
1065
1066QQuickTextEdit::VAlignment QQuickTextEdit::vAlign() const
1067{
1068 Q_D(const QQuickTextEdit);
1069 return d->vAlign;
1070}
1071
1072void QQuickTextEdit::setVAlign(QQuickTextEdit::VAlignment alignment)
1073{
1074 Q_D(QQuickTextEdit);
1075 if (alignment == d->vAlign)
1076 return;
1077 d->vAlign = alignment;
1078 d->updateDefaultTextOption();
1079 updateSize();
1080 updateWholeDocument();
1081 moveCursorDelegate();
1082 emit verticalAlignmentChanged(d->vAlign);
1083}
1084
1085/*!
1086 \qmlproperty enumeration QtQuick::TextEdit::wrapMode
1087
1088 Set this property to wrap the text to the TextEdit item's width.
1089 The text will only wrap if an explicit width has been set.
1090
1091 \value TextEdit.NoWrap
1092 (default) no wrapping will be performed. If the text contains insufficient newlines,
1093 \l {Item::}{implicitWidth} will exceed a set width.
1094 \value TextEdit.WordWrap
1095 wrapping is done on word boundaries only. If a word is too long,
1096 \l {Item::}{implicitWidth} will exceed a set width.
1097 \value TextEdit.WrapAnywhere
1098 wrapping is done at any point on a line, even if it occurs in the middle of a word.
1099 \value TextEdit.Wrap
1100 if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate
1101 point on the line, even in the middle of a word.
1102
1103 The default is \c TextEdit.NoWrap. If you set a width, consider using \c TextEdit.Wrap.
1104*/
1105QQuickTextEdit::WrapMode QQuickTextEdit::wrapMode() const
1106{
1107 Q_D(const QQuickTextEdit);
1108 return d->wrapMode;
1109}
1110
1111void QQuickTextEdit::setWrapMode(WrapMode mode)
1112{
1113 Q_D(QQuickTextEdit);
1114 if (mode == d->wrapMode)
1115 return;
1116 d->wrapMode = mode;
1117 d->updateDefaultTextOption();
1118 updateSize();
1119 emit wrapModeChanged();
1120}
1121
1122/*!
1123 \qmlproperty int QtQuick::TextEdit::lineCount
1124
1125 Returns the total number of lines in the TextEdit item.
1126*/
1127int QQuickTextEdit::lineCount() const
1128{
1129 Q_D(const QQuickTextEdit);
1130 return d->lineCount;
1131}
1132
1133/*!
1134 \qmlproperty int QtQuick::TextEdit::length
1135
1136 Returns the total number of plain text characters in the TextEdit item.
1137
1138 As this number doesn't include any formatting markup it may not be the same as the
1139 length of the string returned by the \l text property.
1140
1141 This property can be faster than querying the length the \l text property as it doesn't
1142 require any copying or conversion of the TextEdit's internal string data.
1143*/
1144
1145int QQuickTextEdit::length() const
1146{
1147 Q_D(const QQuickTextEdit);
1148 // QTextDocument::characterCount() includes the terminating null character.
1149 return qMax(0, d->document->characterCount() - 1);
1150}
1151
1152/*!
1153 \qmlproperty real QtQuick::TextEdit::contentWidth
1154
1155 Returns the width of the text, including the width past the width
1156 which is covered due to insufficient wrapping if \l wrapMode is set.
1157*/
1158qreal QQuickTextEdit::contentWidth() const
1159{
1160 Q_D(const QQuickTextEdit);
1161 return d->contentSize.width();
1162}
1163
1164/*!
1165 \qmlproperty real QtQuick::TextEdit::contentHeight
1166
1167 Returns the height of the text, including the height past the height
1168 that is covered if the text does not fit within the set height.
1169*/
1170qreal QQuickTextEdit::contentHeight() const
1171{
1172 Q_D(const QQuickTextEdit);
1173 return d->contentSize.height();
1174}
1175
1176/*!
1177 \qmlproperty url QtQuick::TextEdit::baseUrl
1178
1179 This property specifies a base URL which is used to resolve relative URLs
1180 within the text.
1181
1182 The default value is the url of the QML file instantiating the TextEdit item.
1183*/
1184
1185QUrl QQuickTextEdit::baseUrl() const
1186{
1187 Q_D(const QQuickTextEdit);
1188 if (d->baseUrl.isEmpty()) {
1189 if (QQmlContext *context = qmlContext(this))
1190 const_cast<QQuickTextEditPrivate *>(d)->baseUrl = context->baseUrl();
1191 }
1192 return d->baseUrl;
1193}
1194
1195void QQuickTextEdit::setBaseUrl(const QUrl &url)
1196{
1197 Q_D(QQuickTextEdit);
1198 if (baseUrl() != url) {
1199 d->baseUrl = url;
1200
1201 d->document->setBaseUrl(url);
1202 emit baseUrlChanged();
1203 }
1204}
1205
1206void QQuickTextEdit::resetBaseUrl()
1207{
1208 if (QQmlContext *context = qmlContext(this))
1209 setBaseUrl(context->baseUrl());
1210 else
1211 setBaseUrl(QUrl());
1212}
1213
1214/*!
1215 \qmlmethod rectangle QtQuick::TextEdit::positionToRectangle(position)
1216
1217 Returns the rectangle at the given \a position in the text. The x, y,
1218 and height properties correspond to the cursor that would describe
1219 that position.
1220*/
1221QRectF QQuickTextEdit::positionToRectangle(int pos) const
1222{
1223 Q_D(const QQuickTextEdit);
1224 QTextCursor c(d->document);
1225 c.setPosition(pos);
1226 return d->control->cursorRect(c).translated(d->xoff, d->yoff);
1227
1228}
1229
1230/*!
1231 \qmlmethod int QtQuick::TextEdit::positionAt(int x, int y)
1232
1233 Returns the text position closest to pixel position (\a x, \a y).
1234
1235 Position 0 is before the first character, position 1 is after the first character
1236 but before the second, and so on until position \l {text}.length, which is after all characters.
1237*/
1238int QQuickTextEdit::positionAt(qreal x, qreal y) const
1239{
1240 Q_D(const QQuickTextEdit);
1241 x -= d->xoff;
1242 y -= d->yoff;
1243
1244 int r = d->document->documentLayout()->hitTest(QPointF(x, y), Qt::FuzzyHit);
1245#if QT_CONFIG(im)
1246 QTextCursor cursor = d->control->textCursor();
1247 if (r > cursor.position()) {
1248 // The cursor position includes positions within the preedit text, but only positions in the
1249 // same text block are offset so it is possible to get a position that is either part of the
1250 // preedit or the next text block.
1251 QTextLayout *layout = cursor.block().layout();
1252 const int preeditLength = layout
1253 ? layout->preeditAreaText().size()
1254 : 0;
1255 if (preeditLength > 0
1256 && d->document->documentLayout()->blockBoundingRect(cursor.block()).contains(x, y)) {
1257 r = r > cursor.position() + preeditLength
1258 ? r - preeditLength
1259 : cursor.position();
1260 }
1261 }
1262#endif
1263 return r;
1264}
1265
1266/*!
1267 \qmlproperty QtQuick::TextSelection QtQuick::TextEdit::cursorSelection
1268 \since 6.7
1269 \preliminary
1270
1271 This property is an object that provides properties of the text that is
1272 currently selected, if any, alongside the text cursor.
1273
1274 \sa selectedText, selectionStart, selectionEnd
1275*/
1276QQuickTextSelection *QQuickTextEdit::cursorSelection() const
1277{
1278 Q_D(const QQuickTextEdit);
1279 if (!d->cursorSelection)
1280 d->cursorSelection = new QQuickTextSelection(const_cast<QQuickTextEdit *>(this));
1281 return d->cursorSelection;
1282}
1283
1284/*!
1285 \qmlmethod void QtQuick::TextEdit::moveCursorSelection(int position, SelectionMode mode)
1286
1287 Moves the cursor to \a position and updates the selection according to the optional \a mode
1288 parameter. (To only move the cursor, set the \l cursorPosition property.)
1289
1290 When this method is called it additionally sets either the
1291 selectionStart or the selectionEnd (whichever was at the previous cursor position)
1292 to the specified position. This allows you to easily extend and contract the selected
1293 text range.
1294
1295 The selection mode specifies whether the selection is updated on a per character or a per word
1296 basis. If not specified the selection mode will default to \c {TextEdit.SelectCharacters}.
1297
1298 \value TextEdit.SelectCharacters
1299 Sets either the selectionStart or selectionEnd (whichever was at the previous cursor position)
1300 to the specified position.
1301 \value TextEdit.SelectWords
1302 Sets the selectionStart and selectionEnd to include all words between the specified position
1303 and the previous cursor position. Words partially in the range are included.
1304
1305 For example, take this sequence of calls:
1306
1307 \code
1308 cursorPosition = 5
1309 moveCursorSelection(9, TextEdit.SelectCharacters)
1310 moveCursorSelection(7, TextEdit.SelectCharacters)
1311 \endcode
1312
1313 This moves the cursor to position 5, extend the selection end from 5 to 9
1314 and then retract the selection end from 9 to 7, leaving the text from position 5 to 7
1315 selected (the 6th and 7th characters).
1316
1317 The same sequence with TextEdit.SelectWords will extend the selection start to a word boundary
1318 before or on position 5 and extend the selection end to a word boundary on or past position 9.
1319*/
1320void QQuickTextEdit::moveCursorSelection(int pos)
1321{
1322 //Note that this is the same as setCursorPosition but with the KeepAnchor flag set
1323 Q_D(QQuickTextEdit);
1324 QTextCursor cursor = d->control->textCursor();
1325 if (cursor.position() == pos)
1326 return;
1327 cursor.setPosition(pos, QTextCursor::KeepAnchor);
1328 d->control->setTextCursor(cursor);
1329}
1330
1331void QQuickTextEdit::moveCursorSelection(int pos, SelectionMode mode)
1332{
1333 Q_D(QQuickTextEdit);
1334 QTextCursor cursor = d->control->textCursor();
1335 if (cursor.position() == pos)
1336 return;
1337 if (mode == SelectCharacters) {
1338 cursor.setPosition(pos, QTextCursor::KeepAnchor);
1339 } else if (cursor.anchor() < pos || (cursor.anchor() == pos && cursor.position() < pos)) {
1340 if (cursor.anchor() > cursor.position()) {
1341 cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor);
1342 cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1343 if (cursor.position() == cursor.anchor())
1344 cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor);
1345 else
1346 cursor.setPosition(cursor.position(), QTextCursor::MoveAnchor);
1347 } else {
1348 cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor);
1349 cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
1350 }
1351
1352 cursor.setPosition(pos, QTextCursor::KeepAnchor);
1353 cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1354 if (cursor.position() != pos)
1355 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1356 } else if (cursor.anchor() > pos || (cursor.anchor() == pos && cursor.position() > pos)) {
1357 if (cursor.anchor() < cursor.position()) {
1358 cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor);
1359 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1360 } else {
1361 cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor);
1362 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1363 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1364 if (cursor.position() != cursor.anchor()) {
1365 cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor);
1366 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1367 }
1368 }
1369
1370 cursor.setPosition(pos, QTextCursor::KeepAnchor);
1371 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1372 if (cursor.position() != pos) {
1373 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1374 cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1375 }
1376 }
1377 d->control->setTextCursor(cursor);
1378}
1379
1380/*!
1381 \qmlproperty bool QtQuick::TextEdit::cursorVisible
1382 If true the text edit shows a cursor.
1383
1384 This property is set and unset when the text edit gets active focus, but it can also
1385 be set directly (useful, for example, if a KeyProxy might forward keys to it).
1386*/
1387bool QQuickTextEdit::isCursorVisible() const
1388{
1389 Q_D(const QQuickTextEdit);
1390 return d->cursorVisible;
1391}
1392
1393void QQuickTextEdit::setCursorVisible(bool on)
1394{
1395 Q_D(QQuickTextEdit);
1396 if (d->cursorVisible == on)
1397 return;
1398 d->cursorVisible = on;
1399 if (on && isComponentComplete())
1400 QQuickTextUtil::createCursor(d);
1401 if (!on && !d->persistentSelection)
1402 d->control->setCursorIsFocusIndicator(true);
1403 d->control->setCursorVisible(on);
1404 emit cursorVisibleChanged(d->cursorVisible);
1405}
1406
1407/*!
1408 \qmlproperty int QtQuick::TextEdit::cursorPosition
1409 The position of the cursor in the TextEdit. The cursor is positioned between
1410 characters.
1411
1412 \note The \e characters in this case refer to the string of \l QChar objects,
1413 therefore 16-bit Unicode characters, and the position is considered an index
1414 into this string. This does not necessarily correspond to individual graphemes
1415 in the writing system, as a single grapheme may be represented by multiple
1416 Unicode characters, such as in the case of surrogate pairs, linguistic
1417 ligatures or diacritics.
1418*/
1419int QQuickTextEdit::cursorPosition() const
1420{
1421 Q_D(const QQuickTextEdit);
1422 return d->control->textCursor().position();
1423}
1424
1425void QQuickTextEdit::setCursorPosition(int pos)
1426{
1427 Q_D(QQuickTextEdit);
1428 if (pos < 0 || pos >= d->document->characterCount()) // characterCount includes the terminating null.
1429 return;
1430 QTextCursor cursor = d->control->textCursor();
1431 if (cursor.position() == pos && cursor.anchor() == pos)
1432 return;
1433 cursor.setPosition(pos);
1434 d->control->setTextCursor(cursor);
1435 d->control->updateCursorRectangle(true);
1436}
1437
1438/*!
1439 \qmlproperty Component QtQuick::TextEdit::cursorDelegate
1440 The delegate for the cursor in the TextEdit.
1441
1442 If you set a cursorDelegate for a TextEdit, this delegate will be used for
1443 drawing the cursor instead of the standard cursor. An instance of the
1444 delegate will be created and managed by the text edit when a cursor is
1445 needed, and the x and y properties of delegate instance will be set so as
1446 to be one pixel before the top left of the current character.
1447
1448 Note that the root item of the delegate component must be a QQuickItem or
1449 QQuickItem derived item.
1450*/
1451QQmlComponent* QQuickTextEdit::cursorDelegate() const
1452{
1453 Q_D(const QQuickTextEdit);
1454 return d->cursorComponent;
1455}
1456
1457void QQuickTextEdit::setCursorDelegate(QQmlComponent* c)
1458{
1459 Q_D(QQuickTextEdit);
1460 QQuickTextUtil::setCursorDelegate(d, c);
1461}
1462
1463void QQuickTextEdit::createCursor()
1464{
1465 Q_D(QQuickTextEdit);
1466 d->cursorPending = true;
1467 QQuickTextUtil::createCursor(d);
1468}
1469
1470/*!
1471 \qmlproperty int QtQuick::TextEdit::selectionStart
1472
1473 The cursor position before the first character in the current selection.
1474
1475 This property is read-only. To change the selection, use select(start,end),
1476 selectAll(), or selectWord().
1477
1478 \sa selectionEnd, cursorPosition, selectedText
1479*/
1480int QQuickTextEdit::selectionStart() const
1481{
1482 Q_D(const QQuickTextEdit);
1483 return d->control->textCursor().selectionStart();
1484}
1485
1486/*!
1487 \qmlproperty int QtQuick::TextEdit::selectionEnd
1488
1489 The cursor position after the last character in the current selection.
1490
1491 This property is read-only. To change the selection, use select(start,end),
1492 selectAll(), or selectWord().
1493
1494 \sa selectionStart, cursorPosition, selectedText
1495*/
1496int QQuickTextEdit::selectionEnd() const
1497{
1498 Q_D(const QQuickTextEdit);
1499 return d->control->textCursor().selectionEnd();
1500}
1501
1502/*!
1503 \qmlproperty string QtQuick::TextEdit::selectedText
1504
1505 This read-only property provides the text currently selected in the
1506 text edit.
1507
1508 It is equivalent to the following snippet, but is faster and easier
1509 to use.
1510 \code
1511 //myTextEdit is the id of the TextEdit
1512 myTextEdit.text.toString().substring(myTextEdit.selectionStart,
1513 myTextEdit.selectionEnd);
1514 \endcode
1515*/
1516QString QQuickTextEdit::selectedText() const
1517{
1518 Q_D(const QQuickTextEdit);
1519#if QT_CONFIG(texthtmlparser)
1520 return d->richText || d->markdownText
1521 ? d->control->textCursor().selectedText()
1522 : d->control->textCursor().selection().toPlainText();
1523#else
1524 return d->control->textCursor().selection().toPlainText();
1525#endif
1526}
1527
1528/*!
1529 \qmlproperty bool QtQuick::TextEdit::activeFocusOnPress
1530
1531 Whether the TextEdit should gain active focus on a mouse press. By default this is
1532 set to true.
1533*/
1534bool QQuickTextEdit::focusOnPress() const
1535{
1536 Q_D(const QQuickTextEdit);
1537 return d->focusOnPress;
1538}
1539
1540void QQuickTextEdit::setFocusOnPress(bool on)
1541{
1542 Q_D(QQuickTextEdit);
1543 if (d->focusOnPress == on)
1544 return;
1545 d->focusOnPress = on;
1546 emit activeFocusOnPressChanged(d->focusOnPress);
1547}
1548
1549/*!
1550 \qmlproperty bool QtQuick::TextEdit::persistentSelection
1551
1552 Whether the TextEdit should keep the selection visible when it loses active focus to another
1553 item in the scene. By default this is set to false.
1554*/
1555bool QQuickTextEdit::persistentSelection() const
1556{
1557 Q_D(const QQuickTextEdit);
1558 return d->persistentSelection;
1559}
1560
1561void QQuickTextEdit::setPersistentSelection(bool on)
1562{
1563 Q_D(QQuickTextEdit);
1564 if (d->persistentSelection == on)
1565 return;
1566 d->persistentSelection = on;
1567 emit persistentSelectionChanged(d->persistentSelection);
1568}
1569
1570/*!
1571 \qmlproperty real QtQuick::TextEdit::textMargin
1572
1573 The margin, in pixels, around the text in the TextEdit.
1574*/
1575qreal QQuickTextEdit::textMargin() const
1576{
1577 Q_D(const QQuickTextEdit);
1578 return d->textMargin;
1579}
1580
1581void QQuickTextEdit::setTextMargin(qreal margin)
1582{
1583 Q_D(QQuickTextEdit);
1584 if (d->textMargin == margin)
1585 return;
1586 d->textMargin = margin;
1587 d->document->setDocumentMargin(d->textMargin);
1588 emit textMarginChanged(d->textMargin);
1589}
1590
1591/*!
1592 \qmlproperty enumeration QtQuick::TextEdit::inputMethodHints
1593
1594 Provides hints to the input method about the expected content of the text edit and how it
1595 should operate.
1596
1597 The value is a bit-wise combination of flags or Qt.ImhNone if no hints are set.
1598
1599 Flags that alter behaviour are:
1600
1601 \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords.
1602 \value Qt.ImhSensitiveData Typed text should not be stored by the active input method
1603 in any persistent storage like predictive user dictionary.
1604 \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to
1605 upper case when a sentence ends.
1606 \value Qt.ImhPreferNumbers Numbers are preferred (but not required).
1607 \value Qt.ImhPreferUppercase Upper case letters are preferred (but not required).
1608 \value Qt.ImhPreferLowercase Lower case letters are preferred (but not required).
1609 \value Qt.ImhNoPredictiveText Do not use predictive text (i.e. dictionary lookup) while typing.
1610 \value Qt.ImhDate The text editor functions as a date field.
1611 \value Qt.ImhTime The text editor functions as a time field.
1612
1613 Flags that restrict input (exclusive flags) are:
1614
1615 \value Qt.ImhDigitsOnly Only digits are allowed.
1616 \value Qt.ImhFormattedNumbersOnly Only number input is allowed. This includes decimal point and minus sign.
1617 \value Qt.ImhUppercaseOnly Only upper case letter input is allowed.
1618 \value Qt.ImhLowercaseOnly Only lower case letter input is allowed.
1619 \value Qt.ImhDialableCharactersOnly Only characters suitable for phone dialing are allowed.
1620 \value Qt.ImhEmailCharactersOnly Only characters suitable for email addresses are allowed.
1621 \value Qt.ImhUrlCharactersOnly Only characters suitable for URLs are allowed.
1622
1623 Masks:
1624
1625 \value Qt.ImhExclusiveInputMask This mask yields nonzero if any of the exclusive flags are used.
1626*/
1627
1628Qt::InputMethodHints QQuickTextEdit::inputMethodHints() const
1629{
1630#if !QT_CONFIG(im)
1631 return Qt::ImhNone;
1632#else
1633 Q_D(const QQuickTextEdit);
1634 return d->inputMethodHints;
1635#endif // im
1636}
1637
1638void QQuickTextEdit::setInputMethodHints(Qt::InputMethodHints hints)
1639{
1640#if !QT_CONFIG(im)
1641 Q_UNUSED(hints);
1642#else
1643 Q_D(QQuickTextEdit);
1644
1645 if (hints == d->inputMethodHints)
1646 return;
1647
1648 d->inputMethodHints = hints;
1649 updateInputMethod(Qt::ImHints);
1650 emit inputMethodHintsChanged();
1651#endif // im
1652}
1653
1654void QQuickTextEdit::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1655{
1656 Q_D(QQuickTextEdit);
1657 if (!d->inLayout && ((newGeometry.width() != oldGeometry.width())
1658 || (newGeometry.height() != oldGeometry.height()))) {
1659 updateSize();
1660 updateWholeDocument();
1661 if (widthValid() || heightValid())
1662 moveCursorDelegate();
1663 }
1664 QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
1665}
1666
1667void QQuickTextEdit::itemChange(ItemChange change, const ItemChangeData &value)
1668{
1669 Q_D(QQuickTextEdit);
1670 Q_UNUSED(value);
1671 switch (change) {
1672 case ItemDevicePixelRatioHasChanged:
1673 if (d->containsUnscalableGlyphs) {
1674 // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
1675 // Text layout code respects the current device pixel ratio automatically, we only need
1676 // to rerun layout after the ratio changed.
1677 updateSize();
1678 updateWholeDocument();
1679 }
1680 break;
1681
1682 default:
1683 break;
1684 }
1685 QQuickImplicitSizeItem::itemChange(change, value);
1686}
1687
1688/*!
1689 Ensures any delayed caching or data loading the class
1690 needs to performed is complete.
1691*/
1692void QQuickTextEdit::componentComplete()
1693{
1694 Q_D(QQuickTextEdit);
1695 QQuickImplicitSizeItem::componentComplete();
1696
1697 const QUrl url = baseUrl();
1698 const QQmlContext *context = qmlContext(this);
1699 d->document->setBaseUrl(context ? context->resolvedUrl(url) : url);
1700 if (!d->text.isEmpty()) {
1701#if QT_CONFIG(texthtmlparser)
1702 if (d->richText)
1703 d->control->setHtml(d->text);
1704 else
1705#endif
1706#if QT_CONFIG(textmarkdownreader)
1707 if (d->markdownText)
1708 d->control->setMarkdownText(d->text);
1709 else
1710#endif
1711 d->control->setPlainText(d->text);
1712 }
1713
1714 if (d->dirty) {
1715 d->determineHorizontalAlignment();
1716 d->updateDefaultTextOption();
1717 updateSize();
1718 d->dirty = false;
1719 }
1720 if (d->cursorComponent && isCursorVisible())
1721 QQuickTextUtil::createCursor(d);
1722 polish();
1723
1724#if QT_CONFIG(accessibility)
1725 if (QAccessible::isActive())
1726 d->accessibilityActiveChanged(true);
1727#endif
1728}
1729
1730int QQuickTextEdit::resourcesLoading() const
1731{
1732 Q_D(const QQuickTextEdit);
1733 return d->pixmapsInProgress.size();
1734}
1735
1736/*!
1737 \qmlproperty bool QtQuick::TextEdit::selectByKeyboard
1738 \since 5.1
1739
1740 Defaults to true when the editor is editable, and false
1741 when read-only.
1742
1743 If true, the user can use the keyboard to select text
1744 even if the editor is read-only. If false, the user
1745 cannot use the keyboard to select text even if the
1746 editor is editable.
1747
1748 \sa readOnly
1749*/
1750bool QQuickTextEdit::selectByKeyboard() const
1751{
1752 Q_D(const QQuickTextEdit);
1753 if (d->selectByKeyboardSet)
1754 return d->selectByKeyboard;
1755 return !isReadOnly();
1756}
1757
1758void QQuickTextEdit::setSelectByKeyboard(bool on)
1759{
1760 Q_D(QQuickTextEdit);
1761 bool was = selectByKeyboard();
1762 if (!d->selectByKeyboardSet || was != on) {
1763 d->selectByKeyboardSet = true;
1764 d->selectByKeyboard = on;
1765 if (on)
1766 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByKeyboard);
1767 else
1768 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByKeyboard);
1769 emit selectByKeyboardChanged(on);
1770 }
1771}
1772
1773/*!
1774 \qmlproperty bool QtQuick::TextEdit::selectByMouse
1775
1776 Defaults to \c true since Qt 6.4.
1777
1778 If \c true, the user can use the mouse to select text in the usual way.
1779
1780 \note In versions prior to 6.4, the default was \c false; but if you
1781 enabled this property, you could also select text on a touchscreen by
1782 dragging your finger across it. This interfered with flicking when TextEdit
1783 was used inside a Flickable. However, Qt has supported text selection
1784 handles on mobile platforms, and on embedded platforms using Qt Virtual
1785 Keyboard, since version 5.7, via QInputMethod. Most users would be
1786 surprised if finger dragging selected text rather than flicking the parent
1787 Flickable. Therefore, selectByMouse now really means what it says: if
1788 \c true, you can select text by dragging \e only with a mouse, whereas
1789 the platform is expected to provide selection handles on touchscreens.
1790 If this change does not suit your application, you can set \c selectByMouse
1791 to \c false, or import an older API version (for example
1792 \c {import QtQuick 6.3}) to revert to the previous behavior. The option to
1793 revert behavior by changing the import version will be removed in a later
1794 version of Qt.
1795*/
1796bool QQuickTextEdit::selectByMouse() const
1797{
1798 Q_D(const QQuickTextEdit);
1799 return d->selectByMouse;
1800}
1801
1802void QQuickTextEdit::setSelectByMouse(bool on)
1803{
1804 Q_D(QQuickTextEdit);
1805 if (d->selectByMouse == on)
1806 return;
1807
1808 d->selectByMouse = on;
1809 setKeepMouseGrab(on);
1810 if (on)
1811 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByMouse);
1812 else
1813 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
1814
1815#if QT_CONFIG(cursor)
1816 d->updateMouseCursorShape();
1817#endif
1818 emit selectByMouseChanged(on);
1819}
1820
1821/*!
1822 \qmlproperty enumeration QtQuick::TextEdit::mouseSelectionMode
1823
1824 Specifies how text should be selected using a mouse.
1825
1826 \value TextEdit.SelectCharacters (default) The selection is updated with individual characters.
1827 \value TextEdit.SelectWords The selection is updated with whole words.
1828
1829 This property only applies when \l selectByMouse is true.
1830*/
1831QQuickTextEdit::SelectionMode QQuickTextEdit::mouseSelectionMode() const
1832{
1833 Q_D(const QQuickTextEdit);
1834 return d->mouseSelectionMode;
1835}
1836
1837void QQuickTextEdit::setMouseSelectionMode(SelectionMode mode)
1838{
1839 Q_D(QQuickTextEdit);
1840 if (d->mouseSelectionMode != mode) {
1841 d->mouseSelectionMode = mode;
1842 d->control->setWordSelectionEnabled(mode == SelectWords);
1843 emit mouseSelectionModeChanged(mode);
1844 }
1845}
1846
1847/*!
1848 \qmlproperty bool QtQuick::TextEdit::readOnly
1849
1850 Whether the user can interact with the TextEdit item. If this
1851 property is set to true the text cannot be edited by user interaction.
1852
1853 By default this property is false.
1854*/
1855void QQuickTextEdit::setReadOnly(bool r)
1856{
1857 Q_D(QQuickTextEdit);
1858 if (r == isReadOnly())
1859 return;
1860
1861#if QT_CONFIG(im)
1862 setFlag(QQuickItem::ItemAcceptsInputMethod, !r);
1863#endif
1864 Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse;
1865 if (d->selectByMouse)
1866 flags = flags | Qt::TextSelectableByMouse;
1867 if (d->selectByKeyboardSet && d->selectByKeyboard)
1868 flags = flags | Qt::TextSelectableByKeyboard;
1869 else if (!d->selectByKeyboardSet && !r)
1870 flags = flags | Qt::TextSelectableByKeyboard;
1871 if (!r)
1872 flags = flags | Qt::TextEditable;
1873 d->control->setTextInteractionFlags(flags);
1874 d->control->moveCursor(QTextCursor::End);
1875
1876#if QT_CONFIG(im)
1877 updateInputMethod(Qt::ImEnabled);
1878#endif
1879#if QT_CONFIG(cursor)
1880 d->updateMouseCursorShape();
1881#endif
1882 q_canPasteChanged();
1883 emit readOnlyChanged(r);
1884 if (!d->selectByKeyboardSet)
1885 emit selectByKeyboardChanged(!r);
1886 if (r) {
1887 setCursorVisible(false);
1888 } else if (hasActiveFocus()) {
1889 setCursorVisible(true);
1890 }
1891
1892#if QT_CONFIG(accessibility)
1893 if (QAccessible::isActive()) {
1894 if (QQuickAccessibleAttached *accessibleAttached = QQuickAccessibleAttached::attachedProperties(this))
1895 accessibleAttached->set_readOnly(r);
1896 }
1897#endif
1898}
1899
1900bool QQuickTextEdit::isReadOnly() const
1901{
1902 Q_D(const QQuickTextEdit);
1903 return !(d->control->textInteractionFlags() & Qt::TextEditable);
1904}
1905
1906/*!
1907 \qmlproperty rectangle QtQuick::TextEdit::cursorRectangle
1908
1909 The rectangle where the standard text cursor is rendered
1910 within the text edit. Read-only.
1911
1912 The position and height of a custom cursorDelegate are updated to follow the cursorRectangle
1913 automatically when it changes. The width of the delegate is unaffected by changes in the
1914 cursor rectangle.
1915*/
1916QRectF QQuickTextEdit::cursorRectangle() const
1917{
1918 Q_D(const QQuickTextEdit);
1919 return d->control->cursorRect().translated(d->xoff, d->yoff);
1920}
1921
1922bool QQuickTextEdit::event(QEvent *event)
1923{
1924 Q_D(QQuickTextEdit);
1925 bool state = QQuickImplicitSizeItem::event(event);
1926 if (event->type() == QEvent::ShortcutOverride && !event->isAccepted()) {
1927 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
1928 state = true;
1929 }
1930 return state;
1931}
1932
1933/*!
1934 \qmlproperty bool QtQuick::TextEdit::overwriteMode
1935 \since 5.8
1936 Whether text entered by the user will overwrite existing text.
1937
1938 As with many text editors, the text editor widget can be configured
1939 to insert or overwrite existing text with new text entered by the user.
1940
1941 If this property is \c true, existing text is overwritten, character-for-character
1942 by new text; otherwise, text is inserted at the cursor position, displacing
1943 existing text.
1944
1945 By default, this property is \c false (new text does not overwrite existing text).
1946*/
1947bool QQuickTextEdit::overwriteMode() const
1948{
1949 Q_D(const QQuickTextEdit);
1950 return d->control->overwriteMode();
1951}
1952
1953void QQuickTextEdit::setOverwriteMode(bool overwrite)
1954{
1955 Q_D(QQuickTextEdit);
1956 d->control->setOverwriteMode(overwrite);
1957}
1958
1959/*!
1960\overload
1961Handles the given key \a event.
1962*/
1963void QQuickTextEdit::keyPressEvent(QKeyEvent *event)
1964{
1965 Q_D(QQuickTextEdit);
1966 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
1967 if (!event->isAccepted())
1968 QQuickImplicitSizeItem::keyPressEvent(event);
1969}
1970
1971/*!
1972\overload
1973Handles the given key \a event.
1974*/
1975void QQuickTextEdit::keyReleaseEvent(QKeyEvent *event)
1976{
1977 Q_D(QQuickTextEdit);
1978 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
1979 if (!event->isAccepted())
1980 QQuickImplicitSizeItem::keyReleaseEvent(event);
1981}
1982
1983/*!
1984 \qmlmethod void QtQuick::TextEdit::deselect()
1985
1986 Removes active text selection.
1987*/
1988void QQuickTextEdit::deselect()
1989{
1990 Q_D(QQuickTextEdit);
1991 QTextCursor c = d->control->textCursor();
1992 c.clearSelection();
1993 d->control->setTextCursor(c);
1994}
1995
1996/*!
1997 \qmlmethod void QtQuick::TextEdit::selectAll()
1998
1999 Causes all text to be selected.
2000*/
2001void QQuickTextEdit::selectAll()
2002{
2003 Q_D(QQuickTextEdit);
2004 d->control->selectAll();
2005}
2006
2007/*!
2008 \qmlmethod void QtQuick::TextEdit::selectWord()
2009
2010 Causes the word closest to the current cursor position to be selected.
2011*/
2012void QQuickTextEdit::selectWord()
2013{
2014 Q_D(QQuickTextEdit);
2015 QTextCursor c = d->control->textCursor();
2016 c.select(QTextCursor::WordUnderCursor);
2017 d->control->setTextCursor(c);
2018}
2019
2020/*!
2021 \qmlmethod void QtQuick::TextEdit::select(int start, int end)
2022
2023 Causes the text from \a start to \a end to be selected.
2024
2025 If either start or end is out of range, the selection is not changed.
2026
2027 After calling this, selectionStart will become the lesser
2028 and selectionEnd will become the greater (regardless of the order passed
2029 to this method).
2030
2031 \sa selectionStart, selectionEnd
2032*/
2033void QQuickTextEdit::select(int start, int end)
2034{
2035 Q_D(QQuickTextEdit);
2036 if (start < 0 || end < 0 || start >= d->document->characterCount() || end >= d->document->characterCount())
2037 return;
2038 QTextCursor cursor = d->control->textCursor();
2039 cursor.beginEditBlock();
2040 cursor.setPosition(start, QTextCursor::MoveAnchor);
2041 cursor.setPosition(end, QTextCursor::KeepAnchor);
2042 cursor.endEditBlock();
2043 d->control->setTextCursor(cursor);
2044
2045 // QTBUG-11100
2046 updateSelection();
2047#if QT_CONFIG(im)
2048 updateInputMethod();
2049#endif
2050}
2051
2052/*!
2053 \qmlmethod bool QtQuick::TextEdit::isRightToLeft(int start, int end)
2054
2055 Returns \c true if the natural reading direction of the editor text
2056 found between positions \a start and \a end is right to left.
2057*/
2058bool QQuickTextEdit::isRightToLeft(int start, int end)
2059{
2060 if (start > end) {
2061 qmlWarning(this) << "isRightToLeft(start, end) called with the end property being smaller than the start.";
2062 return false;
2063 } else {
2064 return getText(start, end).isRightToLeft();
2065 }
2066}
2067
2068#if QT_CONFIG(clipboard)
2069/*!
2070 \qmlmethod void QtQuick::TextEdit::cut()
2071
2072 Moves the currently selected text to the system clipboard.
2073*/
2074void QQuickTextEdit::cut()
2075{
2076 Q_D(QQuickTextEdit);
2077 d->control->cut();
2078}
2079
2080/*!
2081 \qmlmethod void QtQuick::TextEdit::copy()
2082
2083 Copies the currently selected text to the system clipboard.
2084*/
2085void QQuickTextEdit::copy()
2086{
2087 Q_D(QQuickTextEdit);
2088 d->control->copy();
2089}
2090
2091/*!
2092 \qmlmethod void QtQuick::TextEdit::paste()
2093
2094 Replaces the currently selected text by the contents of the system clipboard.
2095*/
2096void QQuickTextEdit::paste()
2097{
2098 Q_D(QQuickTextEdit);
2099 d->control->paste();
2100}
2101#endif // clipboard
2102
2103
2104/*!
2105 \qmlmethod void QtQuick::TextEdit::undo()
2106
2107 Undoes the last operation if undo is \l {canUndo}{available}. Deselects any
2108 current selection, and updates the selection start to the current cursor
2109 position.
2110*/
2111
2112void QQuickTextEdit::undo()
2113{
2114 Q_D(QQuickTextEdit);
2115 d->control->undo();
2116}
2117
2118/*!
2119 \qmlmethod void QtQuick::TextEdit::redo()
2120
2121 Redoes the last operation if redo is \l {canRedo}{available}.
2122*/
2123
2124void QQuickTextEdit::redo()
2125{
2126 Q_D(QQuickTextEdit);
2127 d->control->redo();
2128}
2129
2130/*!
2131\overload
2132Handles the given mouse \a event.
2133*/
2134void QQuickTextEdit::mousePressEvent(QMouseEvent *event)
2135{
2136 Q_D(QQuickTextEdit);
2137 const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event);
2138 setKeepMouseGrab(d->selectByMouse && isMouse);
2139 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2140 if (d->focusOnPress){
2141 bool hadActiveFocus = hasActiveFocus();
2142 forceActiveFocus(Qt::MouseFocusReason);
2143 // re-open input panel on press if already focused
2144#if QT_CONFIG(im)
2145 if (hasActiveFocus() && hadActiveFocus && !isReadOnly())
2146 qGuiApp->inputMethod()->show();
2147#else
2148 Q_UNUSED(hadActiveFocus);
2149#endif
2150 }
2151 if (!event->isAccepted())
2152 QQuickImplicitSizeItem::mousePressEvent(event);
2153}
2154
2155/*!
2156\overload
2157Handles the given mouse \a event.
2158*/
2159void QQuickTextEdit::mouseReleaseEvent(QMouseEvent *event)
2160{
2161 Q_D(QQuickTextEdit);
2162 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2163
2164 if (!event->isAccepted())
2165 QQuickImplicitSizeItem::mouseReleaseEvent(event);
2166}
2167
2168/*!
2169\overload
2170Handles the given mouse \a event.
2171*/
2172void QQuickTextEdit::mouseDoubleClickEvent(QMouseEvent *event)
2173{
2174 Q_D(QQuickTextEdit);
2175 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2176 if (!event->isAccepted())
2177 QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
2178}
2179
2180/*!
2181\overload
2182Handles the given mouse \a event.
2183*/
2184void QQuickTextEdit::mouseMoveEvent(QMouseEvent *event)
2185{
2186 Q_D(QQuickTextEdit);
2187 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2188 if (!event->isAccepted())
2189 QQuickImplicitSizeItem::mouseMoveEvent(event);
2190}
2191
2192#if QT_CONFIG(im)
2193/*!
2194\overload
2195Handles the given input method \a event.
2196*/
2197void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event)
2198{
2199 Q_D(QQuickTextEdit);
2200 const bool wasComposing = isInputMethodComposing();
2201 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2202 setCursorVisible(d->control->cursorVisible());
2203 if (wasComposing != isInputMethodComposing())
2204 emit inputMethodComposingChanged();
2205}
2206
2207/*!
2208\overload
2209Returns the value of the given \a property and \a argument.
2210*/
2211QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
2212{
2213 Q_D(const QQuickTextEdit);
2214
2215 QVariant v;
2216 switch (property) {
2217 case Qt::ImEnabled:
2218 v = (bool)(flags() & ItemAcceptsInputMethod);
2219 break;
2220 case Qt::ImHints:
2221 v = (int)d->effectiveInputMethodHints();
2222 break;
2223 case Qt::ImInputItemClipRectangle:
2224 v = QQuickItem::inputMethodQuery(property);
2225 break;
2226 case Qt::ImReadOnly:
2227 v = isReadOnly();
2228 break;
2229 default:
2230 if (property == Qt::ImCursorPosition && !argument.isNull())
2231 argument = QVariant(argument.toPointF() - QPointF(d->xoff, d->yoff));
2232 v = d->control->inputMethodQuery(property, argument);
2233 if (property == Qt::ImCursorRectangle || property == Qt::ImAnchorRectangle)
2234 v = QVariant(v.toRectF().translated(d->xoff, d->yoff));
2235 break;
2236 }
2237 return v;
2238}
2239
2240/*!
2241\overload
2242Returns the value of the given \a property.
2243*/
2244QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const
2245{
2246 return inputMethodQuery(property, QVariant());
2247}
2248#endif // im
2249
2250void QQuickTextEdit::triggerPreprocess()
2251{
2252 Q_D(QQuickTextEdit);
2253 if (d->updateType == QQuickTextEditPrivate::UpdateNone)
2254 d->updateType = QQuickTextEditPrivate::UpdateOnlyPreprocess;
2255 polish();
2256 update();
2257}
2258
2259/*! \internal
2260 QTextDocument::loadResource() calls this to load inline images etc.
2261 But if it's a local file, don't do it: let QTextDocument::loadResource()
2262 load it in the default way. QQuickPixmap is for QtQuick-specific uses.
2263*/
2264QVariant QQuickTextEdit::loadResource(int type, const QUrl &source)
2265{
2266 Q_D(QQuickTextEdit);
2267 const QUrl url = d->document->baseUrl().resolved(source);
2268 if (url.isLocalFile()) {
2269 // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that)
2270 QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
2271 if (!fi.exists())
2272 qmlWarning(this) << "Cannot open: " << url.toString();
2273 // let QTextDocument::loadResource() handle local file loading
2274 return {};
2275 }
2276
2277 // If the image is in resources, load it here, because QTextDocument::loadResource() doesn't do that
2278 if (!url.scheme().compare("qrc"_L1, Qt::CaseInsensitive)) {
2279 // qmlWarning if the file doesn't exist
2280 QFile f(QQmlFile::urlToLocalFileOrQrc(url));
2281 if (f.open(QFile::ReadOnly)) {
2282 QByteArray buf = f.readAll();
2283 f.close();
2284 QImage image;
2285 image.loadFromData(buf);
2286 if (!image.isNull())
2287 return image;
2288 }
2289 // if we get here, loading failed
2290 qmlWarning(this) << "Cannot read resource: " << f.fileName();
2291 return {};
2292 }
2293
2294 // see if we already started a load job
2295 auto existingJobIter = std::find_if(
2296 d->pixmapsInProgress.cbegin(), d->pixmapsInProgress.cend(),
2297 [&url](const auto *job) { return job->url() == url; } );
2298 if (existingJobIter != d->pixmapsInProgress.cend()) {
2299 const QQuickPixmap *job = *existingJobIter;
2300 if (job->isError()) {
2301 qmlWarning(this) << job->error();
2302 d->pixmapsInProgress.erase(existingJobIter);
2303 delete job;
2304 return QImage();
2305 } else {
2306 qCDebug(lcTextEdit) << "already downloading" << url;
2307 // existing job: return a null variant if it's not done yet
2308 return job->isReady() ? job->image() : QVariant();
2309 }
2310 }
2311
2312 // not found: start a new load job
2313 qCDebug(lcTextEdit) << "loading" << source << "resolved" << url
2314 << "type" << static_cast<QTextDocument::ResourceType>(type);
2315 QQmlContext *context = qmlContext(this);
2316 Q_ASSERT(context);
2317 // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources
2318 QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{});
2319 p->connectFinished(this, SLOT(resourceRequestFinished()));
2320 d->pixmapsInProgress.append(p);
2321 // the new job is probably not done; return a null variant if the caller should poll again
2322 return p->isReady() ? p->image() : QVariant();
2323}
2324
2325/*! \internal
2326 Handle completion of a download that QQuickTextEdit::loadResource() started.
2327*/
2328void QQuickTextEdit::resourceRequestFinished()
2329{
2330 Q_D(QQuickTextEdit);
2331 for (auto it = d->pixmapsInProgress.cbegin(); it != d->pixmapsInProgress.cend(); ++it) {
2332 auto *job = *it;
2333 if (job->isError()) {
2334 // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, to return the placeholder
2335 qCDebug(lcTextEdit) << "failed to load (error)" << job->url();
2336 d->document->resource(QTextDocument::ImageResource, job->url());
2337 // that will call QQuickTextEdit::loadResource() which will delete the job;
2338 // so leave it in pixmapsInProgress for now, and stop this loop
2339 break;
2340 } else if (job->isReady()) {
2341 // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, and cache the result
2342 auto res = d->document->resource(QTextDocument::ImageResource, job->url());
2343 // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done.
2344 qCDebug(lcTextEdit) << (res.isValid() ? "done downloading" : "failed to load") << job->url() << job->rect();
2345 d->pixmapsInProgress.erase(it);
2346 delete job;
2347 break;
2348 }
2349 }
2350 if (d->pixmapsInProgress.isEmpty()) {
2351 invalidate();
2352 updateSize();
2353 q_invalidate();
2354 }
2355}
2356
2358using TextNodeIterator = QQuickTextEditPrivate::TextNodeIterator;
2359
2360static inline bool operator<(const TextNode &n1, const TextNode &n2)
2361{
2362 return n1.startPos() < n2.startPos();
2363}
2364
2365static inline void updateNodeTransform(QSGInternalTextNode *node, const QPointF &topLeft)
2366{
2367 QMatrix4x4 transformMatrix;
2368 transformMatrix.translate(topLeft.x(), topLeft.y());
2369 node->setMatrix(transformMatrix);
2370}
2371
2372/*!
2373 * \internal
2374 *
2375 * Invalidates font caches owned by the text objects owned by the element
2376 * to work around the fact that text objects cannot be used from multiple threads.
2377 */
2378void QQuickTextEdit::invalidateFontCaches()
2379{
2380 Q_D(QQuickTextEdit);
2381 if (d->document == nullptr)
2382 return;
2383
2384 QTextBlock block;
2385 for (block = d->document->firstBlock(); block.isValid(); block = block.next()) {
2386 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
2387 block.layout()->engine()->resetFontEngineCache();
2388 }
2389}
2390
2391QTextDocument *QQuickTextEdit::document() const
2392{
2393 Q_D(const QQuickTextEdit);
2394 return d->document;
2395}
2396
2397void QQuickTextEdit::setDocument(QTextDocument *doc)
2398{
2399 Q_D(QQuickTextEdit);
2400 // do not delete the owned document till after control has been updated
2401 std::unique_ptr<QTextDocument> cleanup(d->ownsDocument ? d->document : nullptr);
2402 d->document = doc;
2403 d->ownsDocument = false;
2404 d->control->setDocument(doc);
2405 q_textChanged();
2406}
2407
2408inline void resetEngine(QQuickTextNodeEngine *engine, const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor, qreal dpr)
2409{
2410 *engine = QQuickTextNodeEngine();
2411 engine->setTextColor(textColor);
2412 engine->setSelectedTextColor(selectedTextColor);
2413 engine->setSelectionColor(selectionColor);
2414 engine->setDevicePixelRatio(dpr);
2415}
2416
2417QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
2418{
2419 Q_UNUSED(updatePaintNodeData);
2420 Q_D(QQuickTextEdit);
2421
2422 if (d->updateType != QQuickTextEditPrivate::UpdatePaintNode
2423 && d->updateType != QQuickTextEditPrivate::UpdateAll
2424 && oldNode != nullptr) {
2425 // Update done in preprocess() in the nodes
2426 d->updateType = QQuickTextEditPrivate::UpdateNone;
2427 return oldNode;
2428 }
2429
2430 d->containsUnscalableGlyphs = false;
2431 if (!oldNode || d->updateType == QQuickTextEditPrivate::UpdateAll) {
2432 delete oldNode;
2433 oldNode = nullptr;
2434
2435 // If we had any QSGInternalTextNode node references, they were deleted along with the root node
2436 // But here we must delete the Node structures in textNodeMap
2437 d->textNodeMap.clear();
2438 }
2439
2440 d->updateType = QQuickTextEditPrivate::UpdateNone;
2441
2442 RootNode *rootNode = static_cast<RootNode *>(oldNode);
2443 TextNodeIterator nodeIterator = d->textNodeMap.begin();
2444 std::optional<int> firstPosAcrossAllNodes;
2445 if (nodeIterator != d->textNodeMap.end())
2446 firstPosAcrossAllNodes = nodeIterator->startPos();
2447
2448 while (nodeIterator != d->textNodeMap.end() && !nodeIterator->dirty())
2449 ++nodeIterator;
2450
2451 const auto dpr = d->effectiveDevicePixelRatio();
2452 QQuickTextNodeEngine engine;
2453 engine.setDevicePixelRatio(dpr);
2454 QQuickTextNodeEngine frameDecorationsEngine;
2455 frameDecorationsEngine.setDevicePixelRatio(dpr);
2456
2457 if (!oldNode || nodeIterator < d->textNodeMap.end() || d->textNodeMap.isEmpty()) {
2458
2459 if (!oldNode)
2460 rootNode = new RootNode;
2461
2462 int firstDirtyPos = 0;
2463 if (nodeIterator != d->textNodeMap.end()) {
2464 firstDirtyPos = nodeIterator->startPos();
2465 // ### this could be optimized if the first and last dirty nodes are not connected
2466 // as the intermediate text nodes would usually only need to be transformed differently.
2467 QSGInternalTextNode *firstCleanNode = nullptr;
2468 auto it = d->textNodeMap.constEnd();
2469 while (it != nodeIterator) {
2470 --it;
2471 if (it->dirty())
2472 break;
2473 firstCleanNode = it->textNode();
2474 }
2475 do {
2476 rootNode->removeChildNode(nodeIterator->textNode());
2477 delete nodeIterator->textNode();
2478 nodeIterator = d->textNodeMap.erase(nodeIterator);
2479 } while (nodeIterator != d->textNodeMap.constEnd() && nodeIterator->textNode() != firstCleanNode);
2480 }
2481
2482 // If there's a lot of text, insert only the range of blocks that can possibly be visible within the viewport.
2483 QRectF viewport;
2484 if (flags().testFlag(QQuickItem::ItemObservesViewport)) {
2485 viewport = clipRect();
2486 qCDebug(lcVP) << "text viewport" << viewport;
2487 }
2488
2489 // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
2490 rootNode->resetFrameDecorations(d->createTextNode());
2491 resetEngine(&frameDecorationsEngine, d->color, d->selectedTextColor, d->selectionColor, dpr);
2492
2493 QSGInternalTextNode *node = nullptr;
2494
2495 int currentNodeSize = 0;
2496 int nodeStart = firstDirtyPos;
2497 QPointF basePosition(d->xoff, d->yoff);
2498 QMatrix4x4 basePositionMatrix;
2499 basePositionMatrix.translate(basePosition.x(), basePosition.y());
2500 rootNode->setMatrix(basePositionMatrix);
2501
2502 QPointF nodeOffset;
2503 const TextNode firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator
2504 : TextNode();
2505
2506 QList<QTextFrame *> frames;
2507 frames.append(d->document->rootFrame());
2508
2509
2510 d->firstBlockInViewport = -1;
2511 d->firstBlockPastViewport = -1;
2512 int frameCount = -1;
2513 while (!frames.isEmpty()) {
2514 QTextFrame *textFrame = frames.takeFirst();
2515 ++frameCount;
2516 if (frameCount > 0)
2517 firstDirtyPos = 0;
2518 qCDebug(lcVP) << "frame" << frameCount << textFrame
2519 << "from" << positionToRectangle(textFrame->firstPosition()).topLeft()
2520 << "to" << positionToRectangle(textFrame->lastPosition()).bottomRight();
2521 frames.append(textFrame->childFrames());
2522 frameDecorationsEngine.addFrameDecorations(d->document, textFrame);
2523 resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor, dpr);
2524
2525 if (textFrame->firstPosition() > textFrame->lastPosition()
2526 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
2527 node = d->createTextNode();
2528 updateNodeTransform(node, d->document->documentLayout()->frameBoundingRect(textFrame).topLeft());
2529 const int pos = textFrame->firstPosition() - 1;
2530 auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(d->document->documentLayout());
2531 QTextCharFormat format = a->formatAccessor(pos);
2532 QTextBlock block = textFrame->firstCursorPosition().block();
2533 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2534 bool inView = true;
2535 if (!viewport.isNull() && block.layout()) {
2536 QRectF coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
2537 inView = coveredRegion.bottom() >= viewport.top() && coveredRegion.top() <= viewport.bottom();
2538 qCDebug(lcVP) << "non-flow frame" << coveredRegion << "in viewport?" << inView;
2539 }
2540 if (inView) {
2541 engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
2542 engine.addTextObject(block, QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
2543 pos, textFrame->frameFormat().position());
2544 }
2545 nodeStart = pos;
2546 } else {
2547 // Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
2548 QVarLengthArray<int, 8> frameBoundaries;
2549 frameBoundaries.reserve(frames.size());
2550 for (QTextFrame *frame : std::as_const(frames))
2551 frameBoundaries.append(frame->firstPosition());
2552 std::sort(frameBoundaries.begin(), frameBoundaries.end());
2553
2554 QTextFrame::iterator it = textFrame->begin();
2555 while (!it.atEnd()) {
2556 QTextBlock block = it.currentBlock();
2557 if (block.position() < firstDirtyPos) {
2558 ++it;
2559 continue;
2560 }
2561
2562 if (!engine.hasContents())
2563 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2564
2565 bool inView = true;
2566 if (!viewport.isNull()) {
2567 QRectF coveredRegion;
2568 if (block.layout()) {
2569 coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
2570 inView = coveredRegion.bottom() > viewport.top();
2571 }
2572 const bool potentiallyScrollingBackwards = firstPosAcrossAllNodes && *firstPosAcrossAllNodes == firstDirtyPos;
2573 if (d->firstBlockInViewport < 0 && inView && potentiallyScrollingBackwards) {
2574 // During backward scrolling, we need to iterate backwards from textNodeMap.begin() to fill the top of the viewport.
2575 if (coveredRegion.top() > viewport.top() + 1) {
2576 qCDebug(lcVP) << "checking backwards from block" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2577 while (it != textFrame->begin() && it.currentBlock().layout() &&
2578 it.currentBlock().layout()->boundingRect().top() + nodeOffset.y() > viewport.top()) {
2579 nodeOffset = d->document->documentLayout()->blockBoundingRect(it.currentBlock()).topLeft();
2580 --it;
2581 }
2582 if (!it.currentBlock().layout())
2583 ++it;
2584 if (Q_LIKELY(it.currentBlock().layout())) {
2585 block = it.currentBlock();
2586 coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
2587 firstDirtyPos = it.currentBlock().position();
2588 } else {
2589 qCWarning(lcVP) << "failed to find a text block with layout during back-scrolling";
2590 }
2591 }
2592 qCDebug(lcVP) << "first block in viewport" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2593 if (block.layout())
2594 d->renderedRegion = coveredRegion;
2595 } else {
2596 if (nodeOffset.y() > viewport.bottom()) {
2597 inView = false;
2598 if (d->firstBlockInViewport >= 0 && d->firstBlockPastViewport < 0) {
2599 qCDebug(lcVP) << "first block past viewport" << viewport << block.blockNumber()
2600 << "@" << nodeOffset.y() << "total region rendered" << d->renderedRegion;
2601 d->firstBlockPastViewport = block.blockNumber();
2602 }
2603 break; // skip rest of blocks in this frame
2604 }
2605 if (inView && !block.text().isEmpty() && coveredRegion.isValid()) {
2606 d->renderedRegion = d->renderedRegion.united(coveredRegion);
2607 // In case we're going to visit more (nested) frames after this, ensure that we
2608 // don't omit any blocks that fit within the region that we claim as fully rendered.
2609 if (!frames.isEmpty())
2610 viewport = viewport.united(d->renderedRegion);
2611 }
2612 }
2613 if (inView && d->firstBlockInViewport < 0)
2614 d->firstBlockInViewport = block.blockNumber();
2615 }
2616
2617 bool createdNodeInView = false;
2618 if (inView) {
2619 if (!engine.hasContents()) {
2620 if (node) {
2621 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2622 || node->containsUnscalableGlyphs();
2623 if (!node->parent())
2624 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
2625 }
2626 node = d->createTextNode();
2627 createdNodeInView = true;
2628 updateNodeTransform(node, nodeOffset);
2629 nodeStart = block.position();
2630 }
2631 engine.addTextBlock(d->document, block, -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
2632 currentNodeSize += block.length();
2633 }
2634
2635 if ((it.atEnd()) || block.next().position() >= firstCleanNode.startPos())
2636 break; // last node that needed replacing or last block of the frame
2637 const auto lowerBound =
2638 std::lower_bound(frameBoundaries.constBegin(),
2639 frameBoundaries.constEnd(), block.next().position());
2640 if (node && (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart)) {
2641 currentNodeSize = 0;
2642 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2643 || node->containsUnscalableGlyphs();
2644 if (!node->parent())
2645 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
2646 if (!createdNodeInView)
2647 node = d->createTextNode();
2648 resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor, dpr);
2649 nodeStart = block.next().position();
2650 }
2651 ++it;
2652 } // loop over blocks in frame
2653 }
2654 if (Q_LIKELY(node)) {
2655 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2656 || node->containsUnscalableGlyphs();
2657 if (Q_LIKELY(!node->parent()))
2658 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
2659 }
2660 }
2661 frameDecorationsEngine.addToSceneGraph(rootNode->frameDecorationsNode, QQuickText::Normal, QColor());
2662 // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
2663 rootNode->prependChildNode(rootNode->frameDecorationsNode);
2664
2665 Q_ASSERT(nodeIterator == d->textNodeMap.end()
2666 || (nodeIterator->textNode() == firstCleanNode.textNode()
2667 && nodeIterator->startPos() == firstCleanNode.startPos()));
2668 // Update the position of the subsequent text blocks.
2669 if (firstCleanNode.textNode() != nullptr) {
2670 QPointF oldOffset = firstCleanNode.textNode()->matrix().map(QPointF(0,0));
2671 QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(
2672 d->document->findBlock(firstCleanNode.startPos())).topLeft();
2673 QPointF delta = currentOffset - oldOffset;
2674 while (nodeIterator != d->textNodeMap.end()) {
2675 QMatrix4x4 transformMatrix = nodeIterator->textNode()->matrix();
2676 transformMatrix.translate(delta.x(), delta.y());
2677 nodeIterator->textNode()->setMatrix(transformMatrix);
2678 ++nodeIterator;
2679 }
2680
2681 }
2682
2683 // Since we iterate over blocks from different text frames that are potentially not sorted
2684 // we need to ensure that our list of nodes is sorted again:
2685 std::sort(d->textNodeMap.begin(), d->textNodeMap.end());
2686 }
2687
2688 if (d->cursorComponent == nullptr) {
2689 QSGInternalRectangleNode* cursor = nullptr;
2690 if (!isReadOnly() && d->cursorVisible && d->control->cursorOn() && d->control->cursorVisible())
2691 cursor = d->sceneGraphContext()->createInternalRectangleNode(d->control->cursorRect(), d->color);
2692 rootNode->resetCursorNode(cursor);
2693 }
2694
2695 invalidateFontCaches();
2696
2697 return rootNode;
2698}
2699
2700void QQuickTextEdit::updatePolish()
2701{
2702 invalidateFontCaches();
2703}
2704
2705/*!
2706 \qmlproperty bool QtQuick::TextEdit::canPaste
2707
2708 Returns true if the TextEdit is writable and the content of the clipboard is
2709 suitable for pasting into the TextEdit.
2710*/
2711bool QQuickTextEdit::canPaste() const
2712{
2713 Q_D(const QQuickTextEdit);
2714 if (!d->canPasteValid) {
2715 const_cast<QQuickTextEditPrivate *>(d)->canPaste = d->control->canPaste();
2716 const_cast<QQuickTextEditPrivate *>(d)->canPasteValid = true;
2717 }
2718 return d->canPaste;
2719}
2720
2721/*!
2722 \qmlproperty bool QtQuick::TextEdit::canUndo
2723
2724 Returns true if the TextEdit is writable and there are previous operations
2725 that can be undone.
2726*/
2727
2728bool QQuickTextEdit::canUndo() const
2729{
2730 Q_D(const QQuickTextEdit);
2731 return d->document->isUndoAvailable();
2732}
2733
2734/*!
2735 \qmlproperty bool QtQuick::TextEdit::canRedo
2736
2737 Returns true if the TextEdit is writable and there are \l {undo}{undone}
2738 operations that can be redone.
2739*/
2740
2741bool QQuickTextEdit::canRedo() const
2742{
2743 Q_D(const QQuickTextEdit);
2744 return d->document->isRedoAvailable();
2745}
2746
2747/*!
2748 \qmlproperty bool QtQuick::TextEdit::inputMethodComposing
2749
2750
2751 This property holds whether the TextEdit has partial text input from an
2752 input method.
2753
2754 While it is composing an input method may rely on mouse or key events from
2755 the TextEdit to edit or commit the partial text. This property can be used
2756 to determine when to disable events handlers that may interfere with the
2757 correct operation of an input method.
2758*/
2759bool QQuickTextEdit::isInputMethodComposing() const
2760{
2761#if !QT_CONFIG(im)
2762 return false;
2763#else
2764 Q_D(const QQuickTextEdit);
2765 return d->control->hasImState();
2766#endif // im
2767}
2768
2769QQuickTextEditPrivate::ExtraData::ExtraData()
2770 : explicitTopPadding(false)
2771 , explicitLeftPadding(false)
2772 , explicitRightPadding(false)
2773 , explicitBottomPadding(false)
2774 , implicitResize(true)
2775{
2776}
2777
2778void QQuickTextEditPrivate::init()
2779{
2780 Q_Q(QQuickTextEdit);
2781
2782#if QT_CONFIG(clipboard)
2783 if (QGuiApplication::clipboard()->supportsSelection())
2784 q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
2785 else
2786#endif
2787 q->setAcceptedMouseButtons(Qt::LeftButton);
2788
2789#if QT_CONFIG(im)
2790 q->setFlag(QQuickItem::ItemAcceptsInputMethod);
2791#endif
2792 q->setFlag(QQuickItem::ItemHasContents);
2793
2794 q->setAcceptHoverEvents(true);
2795
2796 document = new QTextDocument(q);
2797 ownsDocument = true;
2798 auto *imageHandler = new QQuickTextImageHandler(document);
2799 document->documentLayout()->registerHandler(QTextFormat::ImageObject, imageHandler);
2800
2801 control = new QQuickTextControl(document, q);
2802 control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
2803 control->setAcceptRichText(false);
2804 control->setCursorIsFocusIndicator(true);
2805 q->setKeepMouseGrab(true);
2806
2807 qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
2808 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
2809 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2810 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2811 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
2812 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
2813 qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
2814 qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool)));
2815 qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
2816 qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged()));
2817#if QT_CONFIG(clipboard)
2818 qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
2819#endif
2820 qmlobject_connect(document, QTextDocument, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
2821 qmlobject_connect(document, QTextDocument, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
2822 QObject::connect(document, &QTextDocument::contentsChange, q, &QQuickTextEdit::q_contentsChange);
2823 QObject::connect(document->documentLayout(), &QAbstractTextDocumentLayout::updateBlock, q, &QQuickTextEdit::invalidateBlock);
2824 QObject::connect(control, &QQuickTextControl::linkHovered, q, &QQuickTextEdit::q_linkHovered);
2825 QObject::connect(control, &QQuickTextControl::markerHovered, q, &QQuickTextEdit::q_markerHovered);
2826
2827 document->setPageSize(QSizeF(0, 0));
2828 document->setDefaultFont(font);
2829 document->setDocumentMargin(textMargin);
2830 document->setUndoRedoEnabled(false); // flush undo buffer.
2831 document->setUndoRedoEnabled(true);
2832 updateDefaultTextOption();
2833 document->setModified(false); // we merely changed some defaults: no edits worth saving yet
2834 q->updateSize();
2835#if QT_CONFIG(cursor)
2836 updateMouseCursorShape();
2837#endif
2838 setSizePolicy(QLayoutPolicy::Expanding, QLayoutPolicy::Expanding);
2839}
2840
2841void QQuickTextEditPrivate::resetInputMethod()
2842{
2843 Q_Q(QQuickTextEdit);
2844 if (!q->isReadOnly() && q->hasActiveFocus() && qGuiApp)
2845 QGuiApplication::inputMethod()->reset();
2846}
2847
2848void QQuickTextEdit::q_textChanged()
2849{
2850 Q_D(QQuickTextEdit);
2851 d->textCached = false;
2852 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2853 d->contentDirection = d->textDirection(it.text());
2854 if (d->contentDirection != Qt::LayoutDirectionAuto)
2855 break;
2856 }
2857 d->determineHorizontalAlignment();
2858 d->updateDefaultTextOption();
2859 updateSize();
2860
2861 markDirtyNodesForRange(0, d->document->characterCount(), 0);
2862 if (isComponentComplete()) {
2863 polish();
2864 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2865 update();
2866 }
2867
2868 emit textChanged();
2869 if (d->control->isBeingEdited())
2870 emit textEdited();
2871}
2872
2873void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
2874{
2875 Q_D(QQuickTextEdit);
2876 if (start == end)
2877 return;
2878
2879 TextNode dummyNode(start);
2880
2881 const TextNodeIterator textNodeMapBegin = d->textNodeMap.begin();
2882 const TextNodeIterator textNodeMapEnd = d->textNodeMap.end();
2883
2884 TextNodeIterator it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, dummyNode);
2885 // qLowerBound gives us the first node past the start of the affected portion, rewind to the first node
2886 // that starts at the last position before the edit position. (there might be several because of images)
2887 if (it != textNodeMapBegin) {
2888 --it;
2889 TextNode otherDummy(it->startPos());
2890 it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, otherDummy);
2891 }
2892
2893 // mark the affected nodes as dirty
2894 while (it != textNodeMapEnd) {
2895 if (it->startPos() <= end)
2896 it->setDirty();
2897 else if (charDelta)
2898 it->moveStartPos(charDelta);
2899 else
2900 return;
2901 ++it;
2902 }
2903}
2904
2905void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
2906{
2907 Q_D(QQuickTextEdit);
2908
2909 const int editRange = pos + qMax(charsAdded, charsRemoved);
2910 const int delta = charsAdded - charsRemoved;
2911
2912 markDirtyNodesForRange(pos, editRange, delta);
2913
2914 if (isComponentComplete()) {
2915 polish();
2916 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2917 update();
2918 }
2919}
2920
2921void QQuickTextEdit::moveCursorDelegate()
2922{
2923 Q_D(QQuickTextEdit);
2924#if QT_CONFIG(im)
2925 updateInputMethod();
2926#endif
2927 emit cursorRectangleChanged();
2928 if (!d->cursorItem)
2929 return;
2930 QRectF cursorRect = cursorRectangle();
2931 d->cursorItem->setX(cursorRect.x());
2932 d->cursorItem->setY(cursorRect.y());
2933 d->cursorItem->setHeight(cursorRect.height());
2934}
2935
2936void QQuickTextEdit::updateSelection()
2937{
2938 Q_D(QQuickTextEdit);
2939
2940 // No need for node updates when we go from an empty selection to another empty selection
2941 if (d->control->textCursor().hasSelection() || d->hadSelection) {
2942 markDirtyNodesForRange(qMin(d->lastSelectionStart, d->control->textCursor().selectionStart()), qMax(d->control->textCursor().selectionEnd(), d->lastSelectionEnd), 0);
2943 if (isComponentComplete()) {
2944 polish();
2945 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2946 update();
2947 }
2948 }
2949
2950 d->hadSelection = d->control->textCursor().hasSelection();
2951
2952 if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
2953 d->lastSelectionStart = d->control->textCursor().selectionStart();
2954 emit selectionStartChanged();
2955 }
2956 if (d->lastSelectionEnd != d->control->textCursor().selectionEnd()) {
2957 d->lastSelectionEnd = d->control->textCursor().selectionEnd();
2958 emit selectionEndChanged();
2959 }
2960}
2961
2962QRectF QQuickTextEdit::boundingRect() const
2963{
2964 Q_D(const QQuickTextEdit);
2965 QRectF r(
2966 QQuickTextUtil::alignedX(d->contentSize.width(), width(), effectiveHAlign()),
2967 d->yoff,
2968 d->contentSize.width(),
2969 d->contentSize.height());
2970
2971 int cursorWidth = 1;
2972 if (d->cursorItem)
2973 cursorWidth = 0;
2974 else if (!d->document->isEmpty())
2975 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2976
2977 // Could include font max left/right bearings to either side of rectangle.
2978 r.setRight(r.right() + cursorWidth);
2979
2980 return r;
2981}
2982
2983QRectF QQuickTextEdit::clipRect() const
2984{
2985 Q_D(const QQuickTextEdit);
2986 QRectF r = QQuickImplicitSizeItem::clipRect();
2987 int cursorWidth = 1;
2988 if (d->cursorItem)
2989 cursorWidth = d->cursorItem->width();
2990 if (!d->document->isEmpty())
2991 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2992
2993 // Could include font max left/right bearings to either side of rectangle.
2994
2995 r.setRight(r.right() + cursorWidth);
2996 return r;
2997}
2998
2999qreal QQuickTextEditPrivate::getImplicitWidth() const
3000{
3001 Q_Q(const QQuickTextEdit);
3002 if (!requireImplicitWidth) {
3003 // We don't calculate implicitWidth unless it is required.
3004 // We need to force a size update now to ensure implicitWidth is calculated
3005 const_cast<QQuickTextEditPrivate*>(this)->requireImplicitWidth = true;
3006 const_cast<QQuickTextEdit*>(q)->updateSize();
3007 }
3008 return implicitWidth;
3009}
3010
3011//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't
3012// need to do all the calculations each time
3013void QQuickTextEdit::updateSize()
3014{
3015 Q_D(QQuickTextEdit);
3016 if (!isComponentComplete()) {
3017 d->dirty = true;
3018 return;
3019 }
3020
3021 // ### assumes that if the width is set, the text will fill to edges
3022 // ### (unless wrap is false, then clipping will occur)
3023 if (widthValid()) {
3024 if (!d->requireImplicitWidth) {
3025 emit implicitWidthChanged();
3026 // if the implicitWidth is used, then updateSize() has already been called (recursively)
3027 if (d->requireImplicitWidth)
3028 return;
3029 }
3030 if (d->requireImplicitWidth) {
3031 d->document->setTextWidth(-1);
3032 const qreal naturalWidth = d->document->idealWidth();
3033 const bool wasInLayout = d->inLayout;
3034 d->inLayout = true;
3035 if (d->isImplicitResizeEnabled())
3036 setImplicitWidth(naturalWidth + leftPadding() + rightPadding());
3037 d->inLayout = wasInLayout;
3038 if (d->inLayout) // probably the result of a binding loop, but by letting it
3039 return; // get this far we'll get a warning to that effect.
3040 }
3041 const qreal newTextWidth = width() - leftPadding() - rightPadding();
3042 if (d->document->textWidth() != newTextWidth)
3043 d->document->setTextWidth(newTextWidth);
3044 } else if (d->wrapMode == NoWrap) {
3045 // normally, if explicit width is not set, we should call setTextWidth(-1) here,
3046 // as we don't need to fit the text to any fixed width. But because of some bug
3047 // in QTextDocument it also breaks RTL text alignment, so we use "idealWidth" instead.
3048 const qreal newTextWidth = d->document->idealWidth();
3049 if (d->document->textWidth() != newTextWidth)
3050 d->document->setTextWidth(newTextWidth);
3051 } else {
3052 d->document->setTextWidth(-1);
3053 }
3054
3055 QFontMetricsF fm(d->font);
3056 const qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
3057 const qreal newWidth = d->document->idealWidth();
3058
3059 if (d->isImplicitResizeEnabled()) {
3060 // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
3061 if (!widthValid())
3062 setImplicitSize(newWidth + leftPadding() + rightPadding(), newHeight + topPadding() + bottomPadding());
3063 else
3064 setImplicitHeight(newHeight + topPadding() + bottomPadding());
3065 }
3066
3067 d->xoff = leftPadding() + qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width() - leftPadding() - rightPadding(), effectiveHAlign()));
3068 d->yoff = topPadding() + QQuickTextUtil::alignedY(d->document->size().height(), height() - topPadding() - bottomPadding(), d->vAlign);
3069
3070 qreal baseline = fm.ascent();
3071 QTextBlock firstBlock = d->document->firstBlock();
3072 if (firstBlock.isValid() && firstBlock.layout() != nullptr && firstBlock.lineCount() > 0) {
3073 QTextLine firstLine = firstBlock.layout()->lineAt(0);
3074 if (firstLine.isValid())
3075 baseline = firstLine.ascent();
3076 }
3077
3078 setBaselineOffset(baseline + d->yoff + d->textMargin);
3079
3080 QSizeF size(newWidth, newHeight);
3081 if (d->contentSize != size) {
3082 d->contentSize = size;
3083 // Note: inResize is a bitfield so QScopedValueRollback can't be used here
3084 const bool wasInResize = d->inResize;
3085 d->inResize = true;
3086 if (!wasInResize)
3087 emit contentSizeChanged();
3088 d->inResize = wasInResize;
3089 updateTotalLines();
3090 }
3091}
3092
3093void QQuickTextEdit::updateWholeDocument()
3094{
3095 Q_D(QQuickTextEdit);
3096 if (!d->textNodeMap.isEmpty()) {
3097 for (TextNode &node : d->textNodeMap)
3098 node.setDirty();
3099 }
3100
3101 if (isComponentComplete()) {
3102 polish();
3103 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3104 update();
3105 }
3106}
3107
3108void QQuickTextEdit::invalidateBlock(const QTextBlock &block)
3109{
3110 Q_D(QQuickTextEdit);
3111 markDirtyNodesForRange(block.position(), block.position() + block.length(), 0);
3112
3113 if (isComponentComplete()) {
3114 polish();
3115 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3116 update();
3117 }
3118}
3119
3120void QQuickTextEdit::updateCursor()
3121{
3122 Q_D(QQuickTextEdit);
3123 if (isComponentComplete() && isVisible()) {
3124 polish();
3125 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3126 update();
3127 }
3128}
3129
3130void QQuickTextEdit::q_linkHovered(const QString &link)
3131{
3132 Q_D(QQuickTextEdit);
3133 emit linkHovered(link);
3134#if QT_CONFIG(cursor)
3135 if (link.isEmpty()) {
3136 d->updateMouseCursorShape();
3137 } else if (cursor().shape() != Qt::PointingHandCursor) {
3138 setCursor(Qt::PointingHandCursor);
3139 }
3140#endif
3141}
3142
3143void QQuickTextEdit::q_markerHovered(bool hovered)
3144{
3145 Q_D(QQuickTextEdit);
3146#if QT_CONFIG(cursor)
3147 if (!hovered) {
3148 d->updateMouseCursorShape();
3149 } else if (cursor().shape() != Qt::PointingHandCursor) {
3150 setCursor(Qt::PointingHandCursor);
3151 }
3152#endif
3153}
3154
3155void QQuickTextEdit::q_updateAlignment()
3156{
3157 Q_D(QQuickTextEdit);
3158 if (d->determineHorizontalAlignment()) {
3159 d->updateDefaultTextOption();
3160 d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()));
3161 moveCursorDelegate();
3162 updateWholeDocument();
3163 }
3164}
3165
3166void QQuickTextEdit::updateTotalLines()
3167{
3168 Q_D(QQuickTextEdit);
3169
3170 int subLines = 0;
3171
3172 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
3173 QTextLayout *layout = it.layout();
3174 if (!layout)
3175 continue;
3176 subLines += layout->lineCount()-1;
3177 }
3178
3179 int newTotalLines = d->document->lineCount() + subLines;
3180 if (d->lineCount != newTotalLines) {
3181 d->lineCount = newTotalLines;
3182 emit lineCountChanged();
3183 }
3184}
3185
3186void QQuickTextEditPrivate::updateDefaultTextOption()
3187{
3188 Q_Q(QQuickTextEdit);
3189 QTextOption opt = document->defaultTextOption();
3190 const Qt::Alignment oldAlignment = opt.alignment();
3191 Qt::LayoutDirection oldTextDirection = opt.textDirection();
3192
3193 QQuickTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign();
3194 if (contentDirection == Qt::RightToLeft) {
3195 if (horizontalAlignment == QQuickTextEdit::AlignLeft)
3196 horizontalAlignment = QQuickTextEdit::AlignRight;
3197 else if (horizontalAlignment == QQuickTextEdit::AlignRight)
3198 horizontalAlignment = QQuickTextEdit::AlignLeft;
3199 }
3200 if (!hAlignImplicit)
3201 opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign));
3202 else
3203 opt.setAlignment(Qt::Alignment(vAlign));
3204
3205#if QT_CONFIG(im)
3206 if (contentDirection == Qt::LayoutDirectionAuto) {
3207 opt.setTextDirection(qGuiApp->inputMethod()->inputDirection());
3208 } else
3209#endif
3210 {
3211 opt.setTextDirection(contentDirection);
3212 }
3213
3214 QTextOption::WrapMode oldWrapMode = opt.wrapMode();
3215 opt.setWrapMode(QTextOption::WrapMode(wrapMode));
3216
3217 bool oldUseDesignMetrics = opt.useDesignMetrics();
3218 opt.setUseDesignMetrics(renderType != QQuickTextEdit::NativeRendering);
3219
3220 if (oldWrapMode != opt.wrapMode() || oldAlignment != opt.alignment()
3221 || oldTextDirection != opt.textDirection()
3222 || oldUseDesignMetrics != opt.useDesignMetrics()) {
3223 document->setDefaultTextOption(opt);
3224 }
3225}
3226
3227void QQuickTextEditPrivate::onDocumentStatusChanged()
3228{
3229 Q_ASSERT(quickDocument);
3230 switch (quickDocument->status()) {
3231 case QQuickTextDocument::Status::Loaded:
3232 case QQuickTextDocument::Status::Saved:
3233 switch (QQuickTextDocumentPrivate::get(quickDocument)->detectedFormat) {
3234 case Qt::RichText:
3235 richText = (format == QQuickTextEdit::RichText || format == QQuickTextEdit::AutoText);
3236 markdownText = false;
3237 break;
3238 case Qt::MarkdownText:
3239 richText = false;
3240 markdownText = (format == QQuickTextEdit::MarkdownText || format == QQuickTextEdit::AutoText);
3241 break;
3242 case Qt::PlainText:
3243 richText = false;
3244 markdownText = false;
3245 break;
3246 case Qt::AutoText: // format not detected
3247 break;
3248 }
3249 break;
3250 default:
3251 break;
3252 }
3253}
3254
3255void QQuickTextEdit::focusInEvent(QFocusEvent *event)
3256{
3257 Q_D(QQuickTextEdit);
3258 d->handleFocusEvent(event);
3259 QQuickImplicitSizeItem::focusInEvent(event);
3260}
3261
3262void QQuickTextEdit::focusOutEvent(QFocusEvent *event)
3263{
3264 Q_D(QQuickTextEdit);
3265 d->handleFocusEvent(event);
3266 QQuickImplicitSizeItem::focusOutEvent(event);
3267}
3268
3269#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
3270bool QQuickTextEditPrivate::handleContextMenuEvent(QContextMenuEvent *event)
3271#else
3272bool QQuickTextEdit::contextMenuEvent(QContextMenuEvent *event)
3273#endif
3274{
3275 Q_Q(QQuickTextEdit);
3276 QContextMenuEvent mapped(event->reason(),
3277 q->mapToScene(q->cursorRectangle().center()).toPoint(), event->globalPos(),
3278 event->modifiers());
3279 const bool eventProcessed = QQuickItemPrivate::handleContextMenuEvent(&mapped);
3280 event->setAccepted(mapped.isAccepted());
3281 return eventProcessed;
3282}
3283
3284void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
3285{
3286 Q_Q(QQuickTextEdit);
3287 bool focus = event->type() == QEvent::FocusIn;
3288 if (!q->isReadOnly())
3289 q->setCursorVisible(focus);
3290 control->processEvent(event, QPointF(-xoff, -yoff));
3291 if (focus) {
3292 q->q_updateAlignment();
3293#if QT_CONFIG(im)
3294 if (focusOnPress && !q->isReadOnly())
3295 qGuiApp->inputMethod()->show();
3296 q->connect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
3297 q, SLOT(q_updateAlignment()));
3298#endif
3299 } else {
3300#if QT_CONFIG(im)
3301 q->disconnect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
3302 q, SLOT(q_updateAlignment()));
3303#endif
3304 if (event->reason() != Qt::ActiveWindowFocusReason
3305 && event->reason() != Qt::PopupFocusReason
3306 && control->textCursor().hasSelection()
3307 && !persistentSelection)
3308 q->deselect();
3309
3310 emit q->editingFinished();
3311 }
3312}
3313
3314void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QSGInternalTextNode *node, TextNodeIterator &it, int startPos)
3315{
3316 engine->addToSceneGraph(node, QQuickText::Normal, QColor());
3317 it = textNodeMap.insert(it, TextNode(startPos, node));
3318 ++it;
3319 root->appendChildNode(node);
3320 ++renderedBlockCount;
3321}
3322
3323QSGInternalTextNode *QQuickTextEditPrivate::createTextNode()
3324{
3325 Q_Q(QQuickTextEdit);
3326 QSGInternalTextNode* node = sceneGraphContext()->createInternalTextNode(sceneGraphRenderContext());
3327 node->setRenderType(QSGTextNode::RenderType(renderType));
3328 node->setFiltering(q->smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
3329 return node;
3330}
3331
3332void QQuickTextEdit::q_canPasteChanged()
3333{
3334 Q_D(QQuickTextEdit);
3335 bool old = d->canPaste;
3336 d->canPaste = d->control->canPaste();
3337 bool changed = old!=d->canPaste || !d->canPasteValid;
3338 d->canPasteValid = true;
3339 if (changed)
3340 emit canPasteChanged();
3341}
3342
3343/*!
3344 \qmlmethod string QtQuick::TextEdit::getText(int start, int end)
3345
3346 Returns the section of text that is between the \a start and \a end positions.
3347
3348 The returned text does not include any rich text formatting.
3349*/
3350
3351QString QQuickTextEdit::getText(int start, int end) const
3352{
3353 Q_D(const QQuickTextEdit);
3354 start = qBound(0, start, d->document->characterCount() - 1);
3355 end = qBound(0, end, d->document->characterCount() - 1);
3356 QTextCursor cursor(d->document);
3357 cursor.setPosition(start, QTextCursor::MoveAnchor);
3358 cursor.setPosition(end, QTextCursor::KeepAnchor);
3359#if QT_CONFIG(texthtmlparser)
3360 return d->richText || d->markdownText
3361 ? cursor.selectedText()
3362 : cursor.selection().toPlainText();
3363#else
3364 return cursor.selection().toPlainText();
3365#endif
3366}
3367
3368/*!
3369 \qmlmethod string QtQuick::TextEdit::getFormattedText(int start, int end)
3370
3371 Returns the section of text that is between the \a start and \a end positions.
3372
3373 The returned text will be formatted according the \l textFormat property.
3374*/
3375
3376QString QQuickTextEdit::getFormattedText(int start, int end) const
3377{
3378 Q_D(const QQuickTextEdit);
3379
3380 start = qBound(0, start, d->document->characterCount() - 1);
3381 end = qBound(0, end, d->document->characterCount() - 1);
3382
3383 QTextCursor cursor(d->document);
3384 cursor.setPosition(start, QTextCursor::MoveAnchor);
3385 cursor.setPosition(end, QTextCursor::KeepAnchor);
3386
3387 if (d->richText) {
3388#if QT_CONFIG(texthtmlparser)
3389 return cursor.selection().toHtml();
3390#else
3391 return cursor.selection().toPlainText();
3392#endif
3393 } else if (d->markdownText) {
3394#if QT_CONFIG(textmarkdownwriter)
3395 return cursor.selection().toMarkdown();
3396#else
3397 return cursor.selection().toPlainText();
3398#endif
3399 } else {
3400 return cursor.selection().toPlainText();
3401 }
3402}
3403
3404/*!
3405 \qmlmethod void QtQuick::TextEdit::insert(int position, string text)
3406
3407 Inserts \a text into the TextEdit at \a position.
3408*/
3409void QQuickTextEdit::insert(int position, const QString &text)
3410{
3411 Q_D(QQuickTextEdit);
3412 if (position < 0 || position >= d->document->characterCount())
3413 return;
3414 QTextCursor cursor(d->document);
3415 cursor.setPosition(position);
3416 d->richText = d->richText || (d->format == AutoText && Qt::mightBeRichText(text));
3417 if (d->richText) {
3418#if QT_CONFIG(texthtmlparser)
3419 cursor.insertHtml(text);
3420#else
3421 cursor.insertText(text);
3422#endif
3423 } else if (d->markdownText) {
3424#if QT_CONFIG(textmarkdownreader)
3425 cursor.insertMarkdown(text);
3426#else
3427 cursor.insertText(text);
3428#endif
3429 } else {
3430 cursor.insertText(text);
3431 }
3432 d->control->updateCursorRectangle(false);
3433}
3434
3435/*!
3436 \qmlmethod string QtQuick::TextEdit::remove(int start, int end)
3437
3438 Removes the section of text that is between the \a start and \a end positions from the TextEdit.
3439*/
3440
3441void QQuickTextEdit::remove(int start, int end)
3442{
3443 Q_D(QQuickTextEdit);
3444 start = qBound(0, start, d->document->characterCount() - 1);
3445 end = qBound(0, end, d->document->characterCount() - 1);
3446 QTextCursor cursor(d->document);
3447 cursor.setPosition(start, QTextCursor::MoveAnchor);
3448 cursor.setPosition(end, QTextCursor::KeepAnchor);
3449 cursor.removeSelectedText();
3450 d->control->updateCursorRectangle(false);
3451}
3452
3453/*!
3454 \qmlproperty TextDocument QtQuick::TextEdit::textDocument
3455 \since 5.1
3456
3457 Returns the QQuickTextDocument of this TextEdit.
3458 Since Qt 6.7, it has features for loading and saving files.
3459 It can also be used in C++ as a means of accessing the underlying QTextDocument
3460 instance, for example to install a \l QSyntaxHighlighter.
3461
3462 \sa QQuickTextDocument
3463*/
3464
3465QQuickTextDocument *QQuickTextEdit::textDocument()
3466{
3467 Q_D(QQuickTextEdit);
3468 if (!d->quickDocument) {
3469 d->quickDocument = new QQuickTextDocument(this);
3470 connect(d->quickDocument, &QQuickTextDocument::statusChanged, d->quickDocument,
3471 [d]() { d->onDocumentStatusChanged(); } );
3472 }
3473 return d->quickDocument;
3474}
3475
3476bool QQuickTextEditPrivate::isLinkHoveredConnected()
3477{
3478 Q_Q(QQuickTextEdit);
3479 IS_SIGNAL_CONNECTED(q, QQuickTextEdit, linkHovered, (const QString &));
3480}
3481
3482#if QT_CONFIG(cursor)
3483void QQuickTextEditPrivate::updateMouseCursorShape()
3484{
3485 Q_Q(QQuickTextEdit);
3486 q->setCursor(q->isReadOnly() && !q->selectByMouse() ? Qt::ArrowCursor : Qt::IBeamCursor);
3487}
3488#endif
3489
3490/*!
3491 \qmlsignal QtQuick::TextEdit::linkHovered(string link)
3492 \since 5.2
3493
3494 This signal is emitted when the user hovers a link embedded in the text.
3495 The link must be in rich text or HTML format and the
3496 \a link string provides access to the particular link.
3497
3498 \sa hoveredLink, linkAt()
3499*/
3500
3501/*!
3502 \qmlsignal QtQuick::TextEdit::editingFinished()
3503 \since 5.6
3504
3505 This signal is emitted when the text edit loses focus.
3506*/
3507
3508/*!
3509 \qmlproperty string QtQuick::TextEdit::hoveredLink
3510 \since 5.2
3511
3512 This property contains the link string when the user hovers a link
3513 embedded in the text. The link must be in rich text or HTML format
3514 and the link string provides access to the particular link.
3515
3516 \sa linkHovered, linkAt()
3517*/
3518
3519/*!
3520 \qmlsignal QtQuick::TextEdit::textEdited()
3521 \since 6.9
3522
3523 This signal is emitted whenever the text is edited. Unlike \l{TextEdit::text}{textChanged()},
3524 this signal is not emitted when the text is changed programmatically, for example,
3525 by changing the value of the \l text property or by calling \l clear().
3526*/
3527
3528QString QQuickTextEdit::hoveredLink() const
3529{
3530 Q_D(const QQuickTextEdit);
3531 if (const_cast<QQuickTextEditPrivate *>(d)->isLinkHoveredConnected()) {
3532 return d->control->hoveredLink();
3533 } else {
3534#if QT_CONFIG(cursor)
3535 if (QQuickWindow *wnd = window()) {
3536 QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
3537 return d->control->anchorAt(pos);
3538 }
3539#endif // cursor
3540 }
3541 return QString();
3542}
3543
3544void QQuickTextEdit::hoverEnterEvent(QHoverEvent *event)
3545{
3546 Q_D(QQuickTextEdit);
3547 if (d->isLinkHoveredConnected())
3548 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
3549 event->ignore();
3550}
3551
3552void QQuickTextEdit::hoverMoveEvent(QHoverEvent *event)
3553{
3554 Q_D(QQuickTextEdit);
3555 if (d->isLinkHoveredConnected())
3556 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
3557 event->ignore();
3558}
3559
3560void QQuickTextEdit::hoverLeaveEvent(QHoverEvent *event)
3561{
3562 Q_D(QQuickTextEdit);
3563 if (d->isLinkHoveredConnected())
3564 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
3565 event->ignore();
3566}
3567
3568/*!
3569 \qmlmethod void QtQuick::TextEdit::append(string text)
3570 \since 5.2
3571
3572 Appends a new paragraph with \a text to the end of the TextEdit.
3573
3574 In order to append without inserting a new paragraph,
3575 call \c myTextEdit.insert(myTextEdit.length, text) instead.
3576*/
3577void QQuickTextEdit::append(const QString &text)
3578{
3579 Q_D(QQuickTextEdit);
3580 QTextCursor cursor(d->document);
3581 cursor.beginEditBlock();
3582 cursor.movePosition(QTextCursor::End);
3583
3584 if (!d->document->isEmpty())
3585 cursor.insertBlock();
3586
3587 if (d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text))) {
3588#if QT_CONFIG(texthtmlparser)
3589 cursor.insertHtml(text);
3590#else
3591 cursor.insertText(text);
3592#endif
3593 } else if (d->format == MarkdownText) {
3594#if QT_CONFIG(textmarkdownreader)
3595 cursor.insertMarkdown(text);
3596#else
3597 cursor.insertText(text);
3598#endif
3599 } else {
3600 cursor.insertText(text);
3601 }
3602
3603 cursor.endEditBlock();
3604 d->control->updateCursorRectangle(false);
3605}
3606
3607/*!
3608 \qmlmethod string QtQuick::TextEdit::linkAt(real x, real y)
3609 \since 5.3
3610
3611 Returns the link string at point \a x, \a y in content coordinates,
3612 or an empty string if no link exists at that point.
3613
3614 \sa hoveredLink
3615*/
3616QString QQuickTextEdit::linkAt(qreal x, qreal y) const
3617{
3618 Q_D(const QQuickTextEdit);
3619 return d->control->anchorAt(QPointF(x + topPadding(), y + leftPadding()));
3620}
3621
3622/*!
3623 \since 5.6
3624 \qmlproperty real QtQuick::TextEdit::padding
3625 \qmlproperty real QtQuick::TextEdit::topPadding
3626 \qmlproperty real QtQuick::TextEdit::leftPadding
3627 \qmlproperty real QtQuick::TextEdit::bottomPadding
3628 \qmlproperty real QtQuick::TextEdit::rightPadding
3629
3630 These properties hold the padding around the content. This space is reserved
3631 in addition to the contentWidth and contentHeight.
3632*/
3633qreal QQuickTextEdit::padding() const
3634{
3635 Q_D(const QQuickTextEdit);
3636 return d->padding();
3637}
3638
3639void QQuickTextEdit::setPadding(qreal padding)
3640{
3641 Q_D(QQuickTextEdit);
3642 if (qFuzzyCompare(d->padding(), padding))
3643 return;
3644
3645 d->extra.value().padding = padding;
3646 updateSize();
3647 if (isComponentComplete()) {
3648 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3649 update();
3650 }
3651 emit paddingChanged();
3652 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3653 emit topPaddingChanged();
3654 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3655 emit leftPaddingChanged();
3656 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3657 emit rightPaddingChanged();
3658 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3659 emit bottomPaddingChanged();
3660}
3661
3662void QQuickTextEdit::resetPadding()
3663{
3664 setPadding(0);
3665}
3666
3667qreal QQuickTextEdit::topPadding() const
3668{
3669 Q_D(const QQuickTextEdit);
3670 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3671 return d->extra->topPadding;
3672 return d->padding();
3673}
3674
3675void QQuickTextEdit::setTopPadding(qreal padding)
3676{
3677 Q_D(QQuickTextEdit);
3678 d->setTopPadding(padding);
3679}
3680
3681void QQuickTextEdit::resetTopPadding()
3682{
3683 Q_D(QQuickTextEdit);
3684 d->setTopPadding(0, true);
3685}
3686
3687qreal QQuickTextEdit::leftPadding() const
3688{
3689 Q_D(const QQuickTextEdit);
3690 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3691 return d->extra->leftPadding;
3692 return d->padding();
3693}
3694
3695void QQuickTextEdit::setLeftPadding(qreal padding)
3696{
3697 Q_D(QQuickTextEdit);
3698 d->setLeftPadding(padding);
3699}
3700
3701void QQuickTextEdit::resetLeftPadding()
3702{
3703 Q_D(QQuickTextEdit);
3704 d->setLeftPadding(0, true);
3705}
3706
3707qreal QQuickTextEdit::rightPadding() const
3708{
3709 Q_D(const QQuickTextEdit);
3710 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3711 return d->extra->rightPadding;
3712 return d->padding();
3713}
3714
3715void QQuickTextEdit::setRightPadding(qreal padding)
3716{
3717 Q_D(QQuickTextEdit);
3718 d->setRightPadding(padding);
3719}
3720
3721void QQuickTextEdit::resetRightPadding()
3722{
3723 Q_D(QQuickTextEdit);
3724 d->setRightPadding(0, true);
3725}
3726
3727qreal QQuickTextEdit::bottomPadding() const
3728{
3729 Q_D(const QQuickTextEdit);
3730 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3731 return d->extra->bottomPadding;
3732 return d->padding();
3733}
3734
3735void QQuickTextEdit::setBottomPadding(qreal padding)
3736{
3737 Q_D(QQuickTextEdit);
3738 d->setBottomPadding(padding);
3739}
3740
3741void QQuickTextEdit::resetBottomPadding()
3742{
3743 Q_D(QQuickTextEdit);
3744 d->setBottomPadding(0, true);
3745}
3746
3747/*!
3748 \qmlproperty real QtQuick::TextEdit::tabStopDistance
3749 \since 5.10
3750
3751 The default distance, in device units, between tab stops.
3752
3753 \sa QTextOption::setTabStopDistance()
3754*/
3755int QQuickTextEdit::tabStopDistance() const
3756{
3757 Q_D(const QQuickTextEdit);
3758 return d->document->defaultTextOption().tabStopDistance();
3759}
3760
3761void QQuickTextEdit::setTabStopDistance(qreal distance)
3762{
3763 Q_D(QQuickTextEdit);
3764 QTextOption textOptions = d->document->defaultTextOption();
3765 if (textOptions.tabStopDistance() == distance)
3766 return;
3767
3768 textOptions.setTabStopDistance(distance);
3769 d->document->setDefaultTextOption(textOptions);
3770 emit tabStopDistanceChanged(distance);
3771}
3772
3773/*!
3774 \qmlmethod void QtQuick::TextEdit::clear()
3775 \since 5.7
3776
3777 Clears the contents of the text edit
3778 and resets partial text input from an input method.
3779
3780 Use this method instead of setting the \l text property to an empty string.
3781
3782 \sa QInputMethod::reset()
3783*/
3784void QQuickTextEdit::clear()
3785{
3786 Q_D(QQuickTextEdit);
3787 d->resetInputMethod();
3788 d->control->clear();
3789}
3790
3791#ifndef QT_NO_DEBUG_STREAM
3792QDebug operator<<(QDebug debug, const QQuickTextEditPrivate::Node &n)
3793{
3794 QDebugStateSaver saver(debug);
3795 debug.space();
3796 debug << "Node(startPos:" << n.m_startPos << "dirty:" << n.m_dirty << n.m_node << ')';
3797 return debug;
3798}
3799#endif
3800
3801#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
3802void QQuickTextEdit::setOldSelectionDefault()
3803{
3804 Q_D(QQuickTextEdit);
3805 d->selectByMouse = false;
3806 setKeepMouseGrab(false);
3807 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
3808 d->control->setTouchDragSelectionEnabled(true);
3809 qCDebug(lcTextEdit, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
3810}
3811
3812// TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
3813QQuickPre64TextEdit::QQuickPre64TextEdit(QQuickItem *parent)
3814 : QQuickTextEdit(parent)
3815{
3816 setOldSelectionDefault();
3817}
3818#endif
3819
3820QT_END_NAMESPACE
3821
3822#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