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