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 } else if (!d->document->isEmpty()) {
1715 // Content was loaded into the document (e.g. via textDocument.source) while
1716 // we were not yet componentComplete: at that point text() could not recompute
1717 // and no binding/handler was connected to observe the change. Notify now.
1718 d->textCached = false;
1719 emit textChanged();
1720 }
1721
1722 if (d->dirty) {
1723 d->determineHorizontalAlignment();
1724 d->updateDefaultTextOption();
1725 updateSize();
1726 d->dirty = false;
1727 }
1728 if (d->cursorComponent && isCursorVisible())
1729 QQuickTextUtil::createCursor(d);
1730 polish();
1731
1732#if QT_CONFIG(accessibility)
1733 if (QAccessible::isActive())
1734 d->accessibilityActiveChanged(true);
1735#endif
1736}
1737
1738int QQuickTextEdit::resourcesLoading() const
1739{
1740 Q_D(const QQuickTextEdit);
1741 return d->pixmapsInProgress.size();
1742}
1743
1744/*!
1745 \qmlproperty bool QtQuick::TextEdit::selectByKeyboard
1746 \since 5.1
1747
1748 Defaults to true when the editor is editable, and false
1749 when read-only.
1750
1751 If true, the user can use the keyboard to select text
1752 even if the editor is read-only. If false, the user
1753 cannot use the keyboard to select text even if the
1754 editor is editable.
1755
1756 \sa readOnly
1757*/
1758bool QQuickTextEdit::selectByKeyboard() const
1759{
1760 Q_D(const QQuickTextEdit);
1761 if (d->selectByKeyboardSet)
1762 return d->selectByKeyboard;
1763 return !isReadOnly();
1764}
1765
1766void QQuickTextEdit::setSelectByKeyboard(bool on)
1767{
1768 Q_D(QQuickTextEdit);
1769 bool was = selectByKeyboard();
1770 if (!d->selectByKeyboardSet || was != on) {
1771 d->selectByKeyboardSet = true;
1772 d->selectByKeyboard = on;
1773 if (on)
1774 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByKeyboard);
1775 else
1776 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByKeyboard);
1777 emit selectByKeyboardChanged(on);
1778 }
1779}
1780
1781/*!
1782 \qmlproperty bool QtQuick::TextEdit::selectByMouse
1783
1784 Defaults to \c true since Qt 6.4.
1785
1786 If \c true, the user can use the mouse to select text in the usual way.
1787
1788 \note In versions prior to 6.4, the default was \c false; but if you
1789 enabled this property, you could also select text on a touchscreen by
1790 dragging your finger across it. This interfered with flicking when TextEdit
1791 was used inside a Flickable. However, Qt has supported text selection
1792 handles on mobile platforms, and on embedded platforms using Qt Virtual
1793 Keyboard, since version 5.7, via QInputMethod. Most users would be
1794 surprised if finger dragging selected text rather than flicking the parent
1795 Flickable. Therefore, selectByMouse now really means what it says: if
1796 \c true, you can select text by dragging \e only with a mouse, whereas
1797 the platform is expected to provide selection handles on touchscreens.
1798 If this change does not suit your application, you can set \c selectByMouse
1799 to \c false, or import an older API version (for example
1800 \c {import QtQuick 6.3}) to revert to the previous behavior. The option to
1801 revert behavior by changing the import version will be removed in a later
1802 version of Qt.
1803*/
1804bool QQuickTextEdit::selectByMouse() const
1805{
1806 Q_D(const QQuickTextEdit);
1807 return d->selectByMouse;
1808}
1809
1810void QQuickTextEdit::setSelectByMouse(bool on)
1811{
1812 Q_D(QQuickTextEdit);
1813 if (d->selectByMouse == on)
1814 return;
1815
1816 d->selectByMouse = on;
1817 setKeepMouseGrab(on);
1818 if (on)
1819 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByMouse);
1820 else
1821 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
1822
1823#if QT_CONFIG(cursor)
1824 d->updateMouseCursorShape();
1825#endif
1826 emit selectByMouseChanged(on);
1827}
1828
1829/*!
1830 \qmlproperty enumeration QtQuick::TextEdit::mouseSelectionMode
1831
1832 Specifies how text should be selected using a mouse.
1833
1834 \value TextEdit.SelectCharacters (default) The selection is updated with individual characters.
1835 \value TextEdit.SelectWords The selection is updated with whole words.
1836
1837 This property only applies when \l selectByMouse is true.
1838*/
1839QQuickTextEdit::SelectionMode QQuickTextEdit::mouseSelectionMode() const
1840{
1841 Q_D(const QQuickTextEdit);
1842 return d->mouseSelectionMode;
1843}
1844
1845void QQuickTextEdit::setMouseSelectionMode(SelectionMode mode)
1846{
1847 Q_D(QQuickTextEdit);
1848 if (d->mouseSelectionMode != mode) {
1849 d->mouseSelectionMode = mode;
1850 d->control->setWordSelectionEnabled(mode == SelectWords);
1851 emit mouseSelectionModeChanged(mode);
1852 }
1853}
1854
1855/*!
1856 \qmlproperty bool QtQuick::TextEdit::readOnly
1857
1858 Whether the user can interact with the TextEdit item. If this
1859 property is set to true the text cannot be edited by user interaction.
1860
1861 By default this property is false.
1862*/
1863void QQuickTextEdit::setReadOnly(bool r)
1864{
1865 Q_D(QQuickTextEdit);
1866 if (r == isReadOnly())
1867 return;
1868
1869#if QT_CONFIG(im)
1870 setFlag(QQuickItem::ItemAcceptsInputMethod, !r);
1871#endif
1872 Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse;
1873 if (d->selectByMouse)
1874 flags = flags | Qt::TextSelectableByMouse;
1875 if (d->selectByKeyboardSet && d->selectByKeyboard)
1876 flags = flags | Qt::TextSelectableByKeyboard;
1877 else if (!d->selectByKeyboardSet && !r)
1878 flags = flags | Qt::TextSelectableByKeyboard;
1879 if (!r)
1880 flags = flags | Qt::TextEditable;
1881 d->control->setTextInteractionFlags(flags);
1882 d->control->moveCursor(QTextCursor::End);
1883
1884#if QT_CONFIG(im)
1885 updateInputMethod(Qt::ImEnabled);
1886#endif
1887#if QT_CONFIG(cursor)
1888 d->updateMouseCursorShape();
1889#endif
1890 q_canPasteChanged();
1891 emit readOnlyChanged(r);
1892 if (!d->selectByKeyboardSet)
1893 emit selectByKeyboardChanged(!r);
1894 if (r) {
1895 setCursorVisible(false);
1896 } else if (hasActiveFocus()) {
1897 setCursorVisible(true);
1898 }
1899
1900#if QT_CONFIG(accessibility)
1901 if (QAccessible::isActive()) {
1902 if (QQuickAccessibleAttached *accessibleAttached = QQuickAccessibleAttached::attachedProperties(this))
1903 accessibleAttached->set_readOnly(r);
1904 }
1905#endif
1906}
1907
1908bool QQuickTextEdit::isReadOnly() const
1909{
1910 Q_D(const QQuickTextEdit);
1911 return !(d->control->textInteractionFlags() & Qt::TextEditable);
1912}
1913
1914/*!
1915 \qmlproperty rectangle QtQuick::TextEdit::cursorRectangle
1916
1917 The rectangle where the standard text cursor is rendered
1918 within the text edit. Read-only.
1919
1920 The position and height of a custom cursorDelegate are updated to follow the cursorRectangle
1921 automatically when it changes. The width of the delegate is unaffected by changes in the
1922 cursor rectangle.
1923*/
1924QRectF QQuickTextEdit::cursorRectangle() const
1925{
1926 Q_D(const QQuickTextEdit);
1927 return d->control->cursorRect().translated(d->xoff, d->yoff);
1928}
1929
1930bool QQuickTextEdit::event(QEvent *event)
1931{
1932 Q_D(QQuickTextEdit);
1933 bool state = QQuickImplicitSizeItem::event(event);
1934 if (event->type() == QEvent::ShortcutOverride && !event->isAccepted()) {
1935 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
1936 state = true;
1937 }
1938 return state;
1939}
1940
1941/*!
1942 \qmlproperty bool QtQuick::TextEdit::overwriteMode
1943 \since 5.8
1944 Whether text entered by the user will overwrite existing text.
1945
1946 As with many text editors, the text editor widget can be configured
1947 to insert or overwrite existing text with new text entered by the user.
1948
1949 If this property is \c true, existing text is overwritten, character-for-character
1950 by new text; otherwise, text is inserted at the cursor position, displacing
1951 existing text.
1952
1953 By default, this property is \c false (new text does not overwrite existing text).
1954*/
1955bool QQuickTextEdit::overwriteMode() const
1956{
1957 Q_D(const QQuickTextEdit);
1958 return d->control->overwriteMode();
1959}
1960
1961void QQuickTextEdit::setOverwriteMode(bool overwrite)
1962{
1963 Q_D(QQuickTextEdit);
1964 d->control->setOverwriteMode(overwrite);
1965}
1966
1967/*!
1968\overload
1969Handles the given key \a event.
1970*/
1971void QQuickTextEdit::keyPressEvent(QKeyEvent *event)
1972{
1973 Q_D(QQuickTextEdit);
1974 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
1975 if (!event->isAccepted())
1976 QQuickImplicitSizeItem::keyPressEvent(event);
1977}
1978
1979/*!
1980\overload
1981Handles the given key \a event.
1982*/
1983void QQuickTextEdit::keyReleaseEvent(QKeyEvent *event)
1984{
1985 Q_D(QQuickTextEdit);
1986 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
1987 if (!event->isAccepted())
1988 QQuickImplicitSizeItem::keyReleaseEvent(event);
1989}
1990
1991/*!
1992 \qmlmethod void QtQuick::TextEdit::deselect()
1993
1994 Removes active text selection.
1995*/
1996void QQuickTextEdit::deselect()
1997{
1998 Q_D(QQuickTextEdit);
1999 QTextCursor c = d->control->textCursor();
2000 c.clearSelection();
2001 d->control->setTextCursor(c);
2002}
2003
2004/*!
2005 \qmlmethod void QtQuick::TextEdit::selectAll()
2006
2007 Causes all text to be selected.
2008*/
2009void QQuickTextEdit::selectAll()
2010{
2011 Q_D(QQuickTextEdit);
2012 d->control->selectAll();
2013}
2014
2015/*!
2016 \qmlmethod void QtQuick::TextEdit::selectWord()
2017
2018 Causes the word closest to the current cursor position to be selected.
2019*/
2020void QQuickTextEdit::selectWord()
2021{
2022 Q_D(QQuickTextEdit);
2023 QTextCursor c = d->control->textCursor();
2024 c.select(QTextCursor::WordUnderCursor);
2025 d->control->setTextCursor(c);
2026}
2027
2028/*!
2029 \qmlmethod void QtQuick::TextEdit::select(int start, int end)
2030
2031 Causes the text from \a start to \a end to be selected.
2032
2033 If either start or end is out of range, the selection is not changed.
2034
2035 After calling this, selectionStart will become the lesser
2036 and selectionEnd will become the greater (regardless of the order passed
2037 to this method).
2038
2039 \sa selectionStart, selectionEnd
2040*/
2041void QQuickTextEdit::select(int start, int end)
2042{
2043 Q_D(QQuickTextEdit);
2044 if (start < 0 || end < 0 || start >= d->document->characterCount() || end >= d->document->characterCount())
2045 return;
2046 QTextCursor cursor = d->control->textCursor();
2047 cursor.setPosition(start, QTextCursor::MoveAnchor);
2048 cursor.setPosition(end, QTextCursor::KeepAnchor);
2049 d->control->setTextCursor(cursor);
2050
2051 // QTBUG-11100
2052 updateSelection();
2053#if QT_CONFIG(im)
2054 updateInputMethod();
2055#endif
2056}
2057
2058/*!
2059 \qmlmethod bool QtQuick::TextEdit::isRightToLeft(int start, int end)
2060
2061 Returns \c true if the natural reading direction of the editor text
2062 found between positions \a start and \a end is right to left.
2063*/
2064bool QQuickTextEdit::isRightToLeft(int start, int end)
2065{
2066 if (start > end) {
2067 qmlWarning(this) << "isRightToLeft(start, end) called with the end property being smaller than the start.";
2068 return false;
2069 } else {
2070 return getText(start, end).isRightToLeft();
2071 }
2072}
2073
2074#if QT_CONFIG(clipboard)
2075/*!
2076 \qmlmethod void QtQuick::TextEdit::cut()
2077
2078 Moves the currently selected text to the system clipboard.
2079*/
2080void QQuickTextEdit::cut()
2081{
2082 Q_D(QQuickTextEdit);
2083 d->control->cut();
2084}
2085
2086/*!
2087 \qmlmethod void QtQuick::TextEdit::copy()
2088
2089 Copies the currently selected text to the system clipboard.
2090*/
2091void QQuickTextEdit::copy()
2092{
2093 Q_D(QQuickTextEdit);
2094 d->control->copy();
2095}
2096
2097/*!
2098 \qmlmethod void QtQuick::TextEdit::paste()
2099
2100 Replaces the currently selected text by the contents of the system clipboard.
2101*/
2102void QQuickTextEdit::paste()
2103{
2104 Q_D(QQuickTextEdit);
2105 d->control->paste();
2106}
2107#endif // clipboard
2108
2109
2110/*!
2111 \qmlmethod void QtQuick::TextEdit::undo()
2112
2113 Undoes the last operation if undo is \l {canUndo}{available}. Deselects any
2114 current selection, and updates the selection start to the current cursor
2115 position.
2116*/
2117
2118void QQuickTextEdit::undo()
2119{
2120 Q_D(QQuickTextEdit);
2121 d->control->undo();
2122}
2123
2124/*!
2125 \qmlmethod void QtQuick::TextEdit::redo()
2126
2127 Redoes the last operation if redo is \l {canRedo}{available}.
2128*/
2129
2130void QQuickTextEdit::redo()
2131{
2132 Q_D(QQuickTextEdit);
2133 d->control->redo();
2134}
2135
2136/*!
2137\overload
2138Handles the given mouse \a event.
2139*/
2140void QQuickTextEdit::mousePressEvent(QMouseEvent *event)
2141{
2142 Q_D(QQuickTextEdit);
2143 const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event);
2144 setKeepMouseGrab(d->selectByMouse && isMouse);
2145 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2146 if (d->focusOnPress){
2147 bool hadActiveFocus = hasActiveFocus();
2148 forceActiveFocus(Qt::MouseFocusReason);
2149 // re-open input panel on press if already focused
2150#if QT_CONFIG(im)
2151 if (hasActiveFocus() && hadActiveFocus && !isReadOnly())
2152 qGuiApp->inputMethod()->show();
2153#else
2154 Q_UNUSED(hadActiveFocus);
2155#endif
2156 }
2157 if (!event->isAccepted())
2158 QQuickImplicitSizeItem::mousePressEvent(event);
2159}
2160
2161/*!
2162\overload
2163Handles the given mouse \a event.
2164*/
2165void QQuickTextEdit::mouseReleaseEvent(QMouseEvent *event)
2166{
2167 Q_D(QQuickTextEdit);
2168 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2169
2170 if (!event->isAccepted())
2171 QQuickImplicitSizeItem::mouseReleaseEvent(event);
2172}
2173
2174/*!
2175\overload
2176Handles the given mouse \a event.
2177*/
2178void QQuickTextEdit::mouseDoubleClickEvent(QMouseEvent *event)
2179{
2180 Q_D(QQuickTextEdit);
2181 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2182 if (!event->isAccepted())
2183 QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
2184}
2185
2186/*!
2187\overload
2188Handles the given mouse \a event.
2189*/
2190void QQuickTextEdit::mouseMoveEvent(QMouseEvent *event)
2191{
2192 Q_D(QQuickTextEdit);
2193 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2194 if (!event->isAccepted())
2195 QQuickImplicitSizeItem::mouseMoveEvent(event);
2196}
2197
2198#if QT_CONFIG(im)
2199/*!
2200\overload
2201Handles the given input method \a event.
2202*/
2203void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event)
2204{
2205 Q_D(QQuickTextEdit);
2206 const bool wasComposing = isInputMethodComposing();
2207 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
2208 setCursorVisible(d->control->cursorVisible());
2209 if (wasComposing != isInputMethodComposing())
2210 emit inputMethodComposingChanged();
2211}
2212
2213/*!
2214\overload
2215Returns the value of the given \a property and \a argument.
2216*/
2217QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
2218{
2219 Q_D(const QQuickTextEdit);
2220
2221 QVariant v;
2222 switch (property) {
2223 case Qt::ImEnabled:
2224 v = (bool)(flags() & ItemAcceptsInputMethod);
2225 break;
2226 case Qt::ImHints:
2227 v = (int)d->effectiveInputMethodHints();
2228 break;
2229 case Qt::ImInputItemClipRectangle:
2230 v = QQuickItem::inputMethodQuery(property);
2231 break;
2232 case Qt::ImReadOnly:
2233 v = isReadOnly();
2234 break;
2235 default:
2236 if (property == Qt::ImCursorPosition && !argument.isNull())
2237 argument = QVariant(argument.toPointF() - QPointF(d->xoff, d->yoff));
2238 v = d->control->inputMethodQuery(property, argument);
2239 if (property == Qt::ImCursorRectangle || property == Qt::ImAnchorRectangle)
2240 v = QVariant(v.toRectF().translated(d->xoff, d->yoff));
2241 break;
2242 }
2243 return v;
2244}
2245
2246/*!
2247\overload
2248Returns the value of the given \a property.
2249*/
2250QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const
2251{
2252 return inputMethodQuery(property, QVariant());
2253}
2254#endif // im
2255
2256void QQuickTextEdit::triggerPreprocess()
2257{
2258 Q_D(QQuickTextEdit);
2259 if (d->updateType == QQuickTextEditPrivate::UpdateNone)
2260 d->updateType = QQuickTextEditPrivate::UpdateOnlyPreprocess;
2261 polish();
2262 update();
2263}
2264
2265/*! \internal
2266 QTextDocument::loadResource() calls this to load inline images etc.
2267 But if it's a local file, don't do it: let QTextDocument::loadResource()
2268 load it in the default way. QQuickPixmap is for QtQuick-specific uses.
2269*/
2270QVariant QQuickTextEdit::loadResource(int type, const QUrl &source)
2271{
2272 Q_D(QQuickTextEdit);
2273 const QUrl url = d->document->baseUrl().resolved(source);
2274 if (url.isLocalFile()) {
2275 // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that)
2276 QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
2277 if (!fi.exists())
2278 qmlWarning(this) << "Cannot open: " << url.toString();
2279 // let QTextDocument::loadResource() handle local file loading
2280 return {};
2281 }
2282
2283 // If the image is in resources, load it here, because QTextDocument::loadResource() doesn't do that
2284 if (!url.scheme().compare("qrc"_L1, Qt::CaseInsensitive)) {
2285 // qmlWarning if the file doesn't exist
2286 QFile f(QQmlFile::urlToLocalFileOrQrc(url));
2287 if (f.open(QFile::ReadOnly)) {
2288 QByteArray buf = f.readAll();
2289 f.close();
2290 QImage image;
2291 image.loadFromData(buf);
2292 if (!image.isNull())
2293 return image;
2294 }
2295 // if we get here, loading failed
2296 qmlWarning(this) << "Cannot read resource: " << f.fileName();
2297 return {};
2298 }
2299
2300 // see if we already started a load job
2301 auto existingJobIter = std::find_if(
2302 d->pixmapsInProgress.cbegin(), d->pixmapsInProgress.cend(),
2303 [&url](const auto *job) { return job->url() == url; } );
2304 if (existingJobIter != d->pixmapsInProgress.cend()) {
2305 const QQuickPixmap *job = *existingJobIter;
2306 if (job->isError()) {
2307 qmlWarning(this) << job->error();
2308 d->pixmapsInProgress.erase(existingJobIter);
2309 delete job;
2310 return QImage();
2311 } else {
2312 qCDebug(lcTextEdit) << "already downloading" << url;
2313 // existing job: return a null variant if it's not done yet
2314 return job->isReady() ? job->image() : QVariant();
2315 }
2316 }
2317
2318 // not found: start a new load job
2319 qCDebug(lcTextEdit) << "loading" << source << "resolved" << url
2320 << "type" << static_cast<QTextDocument::ResourceType>(type);
2321 QQmlContext *context = qmlContext(this);
2322 Q_ASSERT(context);
2323 // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources
2324 QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{});
2325 p->connectFinished(this, SLOT(resourceRequestFinished()));
2326 d->pixmapsInProgress.append(p);
2327 // the new job is probably not done; return a null variant if the caller should poll again
2328 return p->isReady() ? p->image() : QVariant();
2329}
2330
2331/*! \internal
2332 Handle completion of a download that QQuickTextEdit::loadResource() started.
2333*/
2334void QQuickTextEdit::resourceRequestFinished()
2335{
2336 Q_D(QQuickTextEdit);
2337 for (auto it = d->pixmapsInProgress.cbegin(); it != d->pixmapsInProgress.cend(); ++it) {
2338 auto *job = *it;
2339 if (job->isError()) {
2340 // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, to return the placeholder
2341 qCDebug(lcTextEdit) << "failed to load (error)" << job->url();
2342 d->document->resource(QTextDocument::ImageResource, job->url());
2343 // that will call QQuickTextEdit::loadResource() which will delete the job;
2344 // so leave it in pixmapsInProgress for now, and stop this loop
2345 break;
2346 } else if (job->isReady()) {
2347 // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, and cache the result
2348 auto res = d->document->resource(QTextDocument::ImageResource, job->url());
2349 // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done.
2350 qCDebug(lcTextEdit) << (res.isValid() ? "done downloading" : "failed to load") << job->url() << job->rect();
2351 d->pixmapsInProgress.erase(it);
2352 delete job;
2353 break;
2354 }
2355 }
2356 if (d->pixmapsInProgress.isEmpty()) {
2357 invalidate();
2358 updateSize();
2359 q_invalidate();
2360 }
2361}
2362
2364using TextNodeIterator = QQuickTextEditPrivate::TextNodeIterator;
2365
2366static inline bool operator<(const TextNode &n1, const TextNode &n2)
2367{
2368 return n1.startPos() < n2.startPos();
2369}
2370
2371static inline void updateNodeTransform(QSGInternalTextNode *node, const QPointF &topLeft)
2372{
2373 QMatrix4x4 transformMatrix;
2374 transformMatrix.translate(topLeft.x(), topLeft.y());
2375 node->setMatrix(transformMatrix);
2376}
2377
2378/*!
2379 * \internal
2380 *
2381 * Invalidates font caches owned by the text objects owned by the element
2382 * to work around the fact that text objects cannot be used from multiple threads.
2383 */
2384void QQuickTextEdit::invalidateFontCaches()
2385{
2386 Q_D(QQuickTextEdit);
2387 if (d->document == nullptr)
2388 return;
2389
2390 QTextBlock block;
2391 for (block = d->document->firstBlock(); block.isValid(); block = block.next()) {
2392 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
2393 block.layout()->engine()->resetFontEngineCache();
2394 }
2395}
2396
2397QTextDocument *QQuickTextEdit::document() const
2398{
2399 Q_D(const QQuickTextEdit);
2400 return d->document;
2401}
2402
2403void QQuickTextEdit::setDocument(QTextDocument *doc)
2404{
2405 Q_D(QQuickTextEdit);
2406 // do not delete the owned document till after control has been updated
2407 std::unique_ptr<QTextDocument> cleanup(d->ownsDocument ? d->document : nullptr);
2408 d->document = doc;
2409 d->ownsDocument = false;
2410 d->control->setDocument(doc);
2411 q_textChanged();
2412}
2413
2414inline void resetEngine(QQuickTextNodeEngine *engine, const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor, qreal dpr)
2415{
2416 *engine = QQuickTextNodeEngine();
2417 engine->setTextColor(textColor);
2418 engine->setSelectedTextColor(selectedTextColor);
2419 engine->setSelectionColor(selectionColor);
2420 engine->setDevicePixelRatio(dpr);
2421}
2422
2423QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
2424{
2425 Q_UNUSED(updatePaintNodeData);
2426 Q_D(QQuickTextEdit);
2427
2428 if (d->updateType != QQuickTextEditPrivate::UpdatePaintNode
2429 && d->updateType != QQuickTextEditPrivate::UpdateAll
2430 && oldNode != nullptr) {
2431 // Update done in preprocess() in the nodes
2432 d->updateType = QQuickTextEditPrivate::UpdateNone;
2433 return oldNode;
2434 }
2435
2436 d->containsUnscalableGlyphs = false;
2437 if (!oldNode || d->updateType == QQuickTextEditPrivate::UpdateAll) {
2438 delete oldNode;
2439 oldNode = nullptr;
2440
2441 // If we had any QSGInternalTextNode node references, they were deleted along with the root node
2442 // But here we must delete the Node structures in textNodeMap
2443 d->textNodeMap.clear();
2444 }
2445
2446 d->updateType = QQuickTextEditPrivate::UpdateNone;
2447
2448 RootNode *rootNode = static_cast<RootNode *>(oldNode);
2449 TextNodeIterator nodeIterator = d->textNodeMap.begin();
2450 std::optional<int> firstPosAcrossAllNodes;
2451 if (nodeIterator != d->textNodeMap.end())
2452 firstPosAcrossAllNodes = nodeIterator->startPos();
2453
2454 while (nodeIterator != d->textNodeMap.end() && !nodeIterator->dirty())
2455 ++nodeIterator;
2456
2457 const auto dpr = d->effectiveDevicePixelRatio();
2458 QQuickTextNodeEngine engine;
2459 engine.setDevicePixelRatio(dpr);
2460 QQuickTextNodeEngine frameDecorationsEngine;
2461 frameDecorationsEngine.setDevicePixelRatio(dpr);
2462
2463 if (!oldNode || nodeIterator < d->textNodeMap.end() || d->textNodeMap.isEmpty()) {
2464
2465 if (!oldNode)
2466 rootNode = new RootNode;
2467
2468 int firstDirtyPos = 0;
2469 if (nodeIterator != d->textNodeMap.end()) {
2470 firstDirtyPos = nodeIterator->startPos();
2471 // ### this could be optimized if the first and last dirty nodes are not connected
2472 // as the intermediate text nodes would usually only need to be transformed differently.
2473 QSGInternalTextNode *firstCleanNode = nullptr;
2474 auto it = d->textNodeMap.constEnd();
2475 while (it != nodeIterator) {
2476 --it;
2477 if (it->dirty())
2478 break;
2479 firstCleanNode = it->textNode();
2480 }
2481 do {
2482 rootNode->removeChildNode(nodeIterator->textNode());
2483 delete nodeIterator->textNode();
2484 nodeIterator = d->textNodeMap.erase(nodeIterator);
2485 } while (nodeIterator != d->textNodeMap.constEnd() && nodeIterator->textNode() != firstCleanNode);
2486 }
2487
2488 // If there's a lot of text, insert only the range of blocks that can possibly be visible within the viewport.
2489 QRectF viewport;
2490 if (flags().testFlag(QQuickItem::ItemObservesViewport)) {
2491 viewport = clipRect();
2492 qCDebug(lcVP) << "text viewport" << viewport;
2493 }
2494
2495 // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
2496 rootNode->resetFrameDecorations(d->createTextNode());
2497 resetEngine(&frameDecorationsEngine, d->color, d->selectedTextColor, d->selectionColor, dpr);
2498
2499 QSGInternalTextNode *node = nullptr;
2500
2501 int currentNodeSize = 0;
2502 int nodeStart = firstDirtyPos;
2503 QPointF basePosition(d->xoff, d->yoff);
2504 QMatrix4x4 basePositionMatrix;
2505 basePositionMatrix.translate(basePosition.x(), basePosition.y());
2506 rootNode->setMatrix(basePositionMatrix);
2507
2508 QPointF nodeOffset;
2509 const TextNode firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator
2510 : TextNode();
2511
2512 QList<QTextFrame *> frames;
2513 frames.append(d->document->rootFrame());
2514
2515
2516 d->firstBlockInViewport = -1;
2517 d->firstBlockPastViewport = -1;
2518 int frameCount = -1;
2519 while (!frames.isEmpty()) {
2520 QTextFrame *textFrame = frames.takeFirst();
2521 ++frameCount;
2522 if (frameCount > 0)
2523 firstDirtyPos = 0;
2524 qCDebug(lcVP) << "frame" << frameCount << textFrame
2525 << "from" << positionToRectangle(textFrame->firstPosition()).topLeft()
2526 << "to" << positionToRectangle(textFrame->lastPosition()).bottomRight();
2527 frames.append(textFrame->childFrames());
2528 frameDecorationsEngine.addFrameDecorations(d->document, textFrame);
2529 resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor, dpr);
2530
2531 if (textFrame->firstPosition() > textFrame->lastPosition()
2532 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
2533 node = d->createTextNode();
2534 updateNodeTransform(node, d->document->documentLayout()->frameBoundingRect(textFrame).topLeft());
2535 const int pos = textFrame->firstPosition() - 1;
2536 auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(d->document->documentLayout());
2537 QTextCharFormat format = a->formatAccessor(pos);
2538 QTextBlock block = textFrame->firstCursorPosition().block();
2539 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2540 bool inView = true;
2541 if (!viewport.isNull() && block.layout()) {
2542 QRectF coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
2543 inView = coveredRegion.bottom() >= viewport.top() && coveredRegion.top() <= viewport.bottom();
2544 qCDebug(lcVP) << "non-flow frame" << coveredRegion << "in viewport?" << inView;
2545 }
2546 if (inView) {
2547 engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
2548 engine.addTextObject(block, QPointF(0, 0), format, QQuickTextNodeEngine::Unselected, d->document,
2549 pos, textFrame->frameFormat().position());
2550 }
2551 nodeStart = pos;
2552 } else {
2553 // Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
2554 QVarLengthArray<int, 8> frameBoundaries;
2555 frameBoundaries.reserve(frames.size());
2556 for (QTextFrame *frame : std::as_const(frames))
2557 frameBoundaries.append(frame->firstPosition());
2558 std::sort(frameBoundaries.begin(), frameBoundaries.end());
2559
2560 QTextFrame::iterator it = textFrame->begin();
2561 while (!it.atEnd()) {
2562 QTextBlock block = it.currentBlock();
2563 if (block.position() < firstDirtyPos) {
2564 ++it;
2565 continue;
2566 }
2567
2568 if (!engine.hasContents())
2569 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2570
2571 bool inView = true;
2572 if (!viewport.isNull()) {
2573 QRectF coveredRegion;
2574 if (block.layout()) {
2575 coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
2576 inView = coveredRegion.bottom() > viewport.top();
2577 }
2578 const bool potentiallyScrollingBackwards = firstPosAcrossAllNodes && *firstPosAcrossAllNodes == firstDirtyPos;
2579 if (d->firstBlockInViewport < 0 && inView && potentiallyScrollingBackwards) {
2580 // During backward scrolling, we need to iterate backwards from textNodeMap.begin() to fill the top of the viewport.
2581 if (coveredRegion.top() > viewport.top() + 1) {
2582 qCDebug(lcVP) << "checking backwards from block" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2583 while (it != textFrame->begin() && it.currentBlock().layout() &&
2584 it.currentBlock().layout()->boundingRect().top() + nodeOffset.y() > viewport.top()) {
2585 nodeOffset = d->document->documentLayout()->blockBoundingRect(it.currentBlock()).topLeft();
2586 --it;
2587 }
2588 if (!it.currentBlock().layout())
2589 ++it;
2590 if (Q_LIKELY(it.currentBlock().layout())) {
2591 block = it.currentBlock();
2592 coveredRegion = block.layout()->boundingRect().adjusted(nodeOffset.x(), nodeOffset.y(), nodeOffset.x(), nodeOffset.y());
2593 firstDirtyPos = it.currentBlock().position();
2594 } else {
2595 qCWarning(lcVP) << "failed to find a text block with layout during back-scrolling";
2596 }
2597 }
2598 qCDebug(lcVP) << "first block in viewport" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2599 if (block.layout())
2600 d->renderedRegion = coveredRegion;
2601 } else {
2602 if (nodeOffset.y() > viewport.bottom()) {
2603 inView = false;
2604 if (d->firstBlockInViewport >= 0 && d->firstBlockPastViewport < 0) {
2605 qCDebug(lcVP) << "first block past viewport" << viewport << block.blockNumber()
2606 << "@" << nodeOffset.y() << "total region rendered" << d->renderedRegion;
2607 d->firstBlockPastViewport = block.blockNumber();
2608 }
2609 break; // skip rest of blocks in this frame
2610 }
2611 if (inView && !block.text().isEmpty() && coveredRegion.isValid()) {
2612 d->renderedRegion = d->renderedRegion.united(coveredRegion);
2613 // In case we're going to visit more (nested) frames after this, ensure that we
2614 // don't omit any blocks that fit within the region that we claim as fully rendered.
2615 if (!frames.isEmpty())
2616 viewport = viewport.united(d->renderedRegion);
2617 }
2618 }
2619 if (inView && d->firstBlockInViewport < 0)
2620 d->firstBlockInViewport = block.blockNumber();
2621 }
2622
2623 bool createdNodeInView = false;
2624 if (inView) {
2625 if (!engine.hasContents()) {
2626 if (node) {
2627 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2628 || node->containsUnscalableGlyphs();
2629 if (!node->parent())
2630 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
2631 }
2632 node = d->createTextNode();
2633 createdNodeInView = true;
2634 updateNodeTransform(node, nodeOffset);
2635 nodeStart = block.position();
2636 }
2637 engine.addTextBlock(d->document, block, -nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
2638 currentNodeSize += block.length();
2639 }
2640
2641 if ((it.atEnd()) || block.next().position() >= firstCleanNode.startPos())
2642 break; // last node that needed replacing or last block of the frame
2643 const auto lowerBound =
2644 std::lower_bound(frameBoundaries.constBegin(),
2645 frameBoundaries.constEnd(), block.next().position());
2646 if (node && (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart)) {
2647 currentNodeSize = 0;
2648 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2649 || node->containsUnscalableGlyphs();
2650 if (!node->parent())
2651 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
2652 if (!createdNodeInView)
2653 node = d->createTextNode();
2654 resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor, dpr);
2655 nodeStart = block.next().position();
2656 }
2657 ++it;
2658 } // loop over blocks in frame
2659 }
2660 if (Q_LIKELY(node)) {
2661 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2662 || node->containsUnscalableGlyphs();
2663 if (Q_LIKELY(!node->parent()))
2664 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart);
2665 }
2666 }
2667 frameDecorationsEngine.addToSceneGraph(rootNode->frameDecorationsNode, nullptr, QQuickText::Normal, QColor());
2668 // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
2669 rootNode->prependChildNode(rootNode->frameDecorationsNode);
2670
2671 Q_ASSERT(nodeIterator == d->textNodeMap.end()
2672 || (nodeIterator->textNode() == firstCleanNode.textNode()
2673 && nodeIterator->startPos() == firstCleanNode.startPos()));
2674 // Update the position of the subsequent text blocks.
2675 if (firstCleanNode.textNode() != nullptr) {
2676 QPointF oldOffset = firstCleanNode.textNode()->matrix().map(QPointF(0,0));
2677 QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(
2678 d->document->findBlock(firstCleanNode.startPos())).topLeft();
2679 QPointF delta = currentOffset - oldOffset;
2680 while (nodeIterator != d->textNodeMap.end()) {
2681 QMatrix4x4 transformMatrix = nodeIterator->textNode()->matrix();
2682 transformMatrix.translate(delta.x(), delta.y());
2683 nodeIterator->textNode()->setMatrix(transformMatrix);
2684 ++nodeIterator;
2685 }
2686
2687 }
2688
2689 // Since we iterate over blocks from different text frames that are potentially not sorted
2690 // we need to ensure that our list of nodes is sorted again:
2691 std::sort(d->textNodeMap.begin(), d->textNodeMap.end());
2692 }
2693
2694 if (d->cursorComponent == nullptr) {
2695 QSGInternalRectangleNode* cursor = nullptr;
2696 if (!isReadOnly() && d->cursorVisible && d->control->cursorOn() && d->control->cursorVisible())
2697 cursor = d->sceneGraphContext()->createInternalRectangleNode(d->control->cursorRect(), d->color);
2698 rootNode->resetCursorNode(cursor);
2699 }
2700
2701 invalidateFontCaches();
2702
2703 return rootNode;
2704}
2705
2706void QQuickTextEdit::updatePolish()
2707{
2708 invalidateFontCaches();
2709}
2710
2711/*!
2712 \qmlproperty bool QtQuick::TextEdit::canPaste
2713
2714 Returns true if the TextEdit is writable and the content of the clipboard is
2715 suitable for pasting into the TextEdit.
2716*/
2717bool QQuickTextEdit::canPaste() const
2718{
2719 Q_D(const QQuickTextEdit);
2720 if (!d->canPasteValid) {
2721 const_cast<QQuickTextEditPrivate *>(d)->canPaste = d->control->canPaste();
2722 const_cast<QQuickTextEditPrivate *>(d)->canPasteValid = true;
2723 }
2724 return d->canPaste;
2725}
2726
2727/*!
2728 \qmlproperty bool QtQuick::TextEdit::canUndo
2729
2730 Returns true if the TextEdit is writable and there are previous operations
2731 that can be undone.
2732*/
2733
2734bool QQuickTextEdit::canUndo() const
2735{
2736 Q_D(const QQuickTextEdit);
2737 return d->document->isUndoAvailable();
2738}
2739
2740/*!
2741 \qmlproperty bool QtQuick::TextEdit::canRedo
2742
2743 Returns true if the TextEdit is writable and there are \l {undo}{undone}
2744 operations that can be redone.
2745*/
2746
2747bool QQuickTextEdit::canRedo() const
2748{
2749 Q_D(const QQuickTextEdit);
2750 return d->document->isRedoAvailable();
2751}
2752
2753/*!
2754 \qmlproperty bool QtQuick::TextEdit::inputMethodComposing
2755
2756
2757 This property holds whether the TextEdit has partial text input from an
2758 input method.
2759
2760 While it is composing an input method may rely on mouse or key events from
2761 the TextEdit to edit or commit the partial text. This property can be used
2762 to determine when to disable events handlers that may interfere with the
2763 correct operation of an input method.
2764*/
2765bool QQuickTextEdit::isInputMethodComposing() const
2766{
2767#if !QT_CONFIG(im)
2768 return false;
2769#else
2770 Q_D(const QQuickTextEdit);
2771 return d->control->hasImState();
2772#endif // im
2773}
2774
2775QQuickTextEditPrivate::ExtraData::ExtraData()
2776 : explicitTopPadding(false)
2777 , explicitLeftPadding(false)
2778 , explicitRightPadding(false)
2779 , explicitBottomPadding(false)
2780 , implicitResize(true)
2781{
2782}
2783
2784void QQuickTextEditPrivate::init()
2785{
2786 Q_Q(QQuickTextEdit);
2787
2788#if QT_CONFIG(clipboard)
2789 if (QGuiApplication::clipboard()->supportsSelection())
2790 q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
2791 else
2792#endif
2793 q->setAcceptedMouseButtons(Qt::LeftButton);
2794
2795#if QT_CONFIG(im)
2796 q->setFlag(QQuickItem::ItemAcceptsInputMethod);
2797#endif
2798 q->setFlag(QQuickItem::ItemHasContents);
2799
2800 q->setAcceptHoverEvents(true);
2801
2802 document = new QTextDocument(q);
2803 ownsDocument = true;
2804 auto *imageHandler = new QQuickTextImageHandler(document);
2805 document->documentLayout()->registerHandler(QTextFormat::ImageObject, imageHandler);
2806
2807 control = new QQuickTextControl(document, q);
2808 control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
2809 control->setAcceptRichText(false);
2810 control->setCursorIsFocusIndicator(true);
2811 q->setKeepMouseGrab(true);
2812
2813 qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
2814 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
2815 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2816 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2817 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
2818 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
2819 qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
2820 qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool)));
2821 qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
2822 qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged()));
2823#if QT_CONFIG(clipboard)
2824 qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
2825#endif
2826 qmlobject_connect(document, QTextDocument, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
2827 qmlobject_connect(document, QTextDocument, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
2828 QObject::connect(document, &QTextDocument::contentsChange, q, &QQuickTextEdit::q_contentsChange);
2829 QObject::connect(document->documentLayout(), &QAbstractTextDocumentLayout::updateBlock, q, &QQuickTextEdit::invalidateBlock);
2830 QObject::connect(control, &QQuickTextControl::linkHovered, q, &QQuickTextEdit::q_linkHovered);
2831 QObject::connect(control, &QQuickTextControl::markerHovered, q, &QQuickTextEdit::q_markerHovered);
2832 QObject::connect(control, &QQuickTextControl::hoveredToolTipChanged, q, &QQuickTextEdit::hoveredToolTipChanged);
2833
2834 document->setPageSize(QSizeF(0, 0));
2835 document->setDefaultFont(font);
2836 document->setDocumentMargin(textMargin);
2837 document->setUndoRedoEnabled(false); // flush undo buffer.
2838 document->setUndoRedoEnabled(true);
2839 updateDefaultTextOption();
2840 document->setModified(false); // we merely changed some defaults: no edits worth saving yet
2841 q->updateSize();
2842#if QT_CONFIG(cursor)
2843 updateMouseCursorShape();
2844#endif
2845 setSizePolicy(QLayoutPolicy::Expanding, QLayoutPolicy::Expanding);
2846}
2847
2848void QQuickTextEditPrivate::resetInputMethod()
2849{
2850 Q_Q(QQuickTextEdit);
2851 if (!q->isReadOnly() && q->hasActiveFocus() && qGuiApp)
2852 QGuiApplication::inputMethod()->reset();
2853}
2854
2855void QQuickTextEdit::q_textChanged()
2856{
2857 Q_D(QQuickTextEdit);
2858 d->textCached = false;
2859 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2860 d->contentDirection = d->textDirection(it.text());
2861 if (d->contentDirection != Qt::LayoutDirectionAuto)
2862 break;
2863 }
2864 d->determineHorizontalAlignment();
2865 d->updateDefaultTextOption();
2866 updateSize();
2867
2868 markDirtyNodesForRange(0, d->document->characterCount(), 0);
2869 if (isComponentComplete()) {
2870 polish();
2871 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2872 update();
2873 }
2874
2875 emit textChanged();
2876 if (d->control->isBeingEdited())
2877 emit textEdited();
2878}
2879
2880void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
2881{
2882 Q_D(QQuickTextEdit);
2883 if (start == end)
2884 return;
2885
2886 TextNode dummyNode(start);
2887
2888 const TextNodeIterator textNodeMapBegin = d->textNodeMap.begin();
2889 const TextNodeIterator textNodeMapEnd = d->textNodeMap.end();
2890
2891 TextNodeIterator it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, dummyNode);
2892 // qLowerBound gives us the first node past the start of the affected portion, rewind to the first node
2893 // that starts at the last position before the edit position. (there might be several because of images)
2894 if (it != textNodeMapBegin) {
2895 --it;
2896 TextNode otherDummy(it->startPos());
2897 it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, otherDummy);
2898 }
2899
2900 // mark the affected nodes as dirty
2901 while (it != textNodeMapEnd) {
2902 if (it->startPos() <= end)
2903 it->setDirty();
2904 else if (charDelta)
2905 it->moveStartPos(charDelta);
2906 else
2907 return;
2908 ++it;
2909 }
2910}
2911
2912void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
2913{
2914 Q_D(QQuickTextEdit);
2915
2916 const int editRange = pos + qMax(charsAdded, charsRemoved);
2917 const int delta = charsAdded - charsRemoved;
2918
2919 markDirtyNodesForRange(pos, editRange, delta);
2920
2921 if (isComponentComplete()) {
2922 polish();
2923 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2924 update();
2925 }
2926}
2927
2928void QQuickTextEdit::moveCursorDelegate()
2929{
2930 Q_D(QQuickTextEdit);
2931#if QT_CONFIG(im)
2932 updateInputMethod();
2933#endif
2934 emit cursorRectangleChanged();
2935 if (!d->cursorItem)
2936 return;
2937 QRectF cursorRect = cursorRectangle();
2938 d->cursorItem->setX(cursorRect.x());
2939 d->cursorItem->setY(cursorRect.y());
2940 d->cursorItem->setHeight(cursorRect.height());
2941}
2942
2943void QQuickTextEdit::updateSelection()
2944{
2945 Q_D(QQuickTextEdit);
2946
2947 // No need for node updates when we go from an empty selection to another empty selection
2948 if (d->control->textCursor().hasSelection() || d->hadSelection) {
2949 markDirtyNodesForRange(qMin(d->lastSelectionStart, d->control->textCursor().selectionStart()), qMax(d->control->textCursor().selectionEnd(), d->lastSelectionEnd), 0);
2950 if (isComponentComplete()) {
2951 polish();
2952 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2953 update();
2954 }
2955 }
2956
2957 d->hadSelection = d->control->textCursor().hasSelection();
2958
2959 if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
2960 d->lastSelectionStart = d->control->textCursor().selectionStart();
2961 emit selectionStartChanged();
2962 }
2963 if (d->lastSelectionEnd != d->control->textCursor().selectionEnd()) {
2964 d->lastSelectionEnd = d->control->textCursor().selectionEnd();
2965 emit selectionEndChanged();
2966 }
2967}
2968
2969QRectF QQuickTextEdit::boundingRect() const
2970{
2971 Q_D(const QQuickTextEdit);
2972 QRectF r(
2973 QQuickTextUtil::alignedX(d->contentSize.width(), width(), effectiveHAlign()),
2974 d->yoff,
2975 d->contentSize.width(),
2976 d->contentSize.height());
2977
2978 int cursorWidth = 1;
2979 if (d->cursorItem)
2980 cursorWidth = 0;
2981 else if (!d->document->isEmpty())
2982 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2983
2984 // Could include font max left/right bearings to either side of rectangle.
2985 r.setRight(r.right() + cursorWidth);
2986
2987 return r;
2988}
2989
2990QRectF QQuickTextEdit::clipRect() const
2991{
2992 Q_D(const QQuickTextEdit);
2993 QRectF r = QQuickImplicitSizeItem::clipRect();
2994 int cursorWidth = 1;
2995 if (d->cursorItem)
2996 cursorWidth = d->cursorItem->width();
2997 if (!d->document->isEmpty())
2998 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2999
3000 // Could include font max left/right bearings to either side of rectangle.
3001
3002 r.setRight(r.right() + cursorWidth);
3003 return r;
3004}
3005
3006qreal QQuickTextEditPrivate::getImplicitWidth() const
3007{
3008 Q_Q(const QQuickTextEdit);
3009 if (!requireImplicitWidth) {
3010 // We don't calculate implicitWidth unless it is required.
3011 // We need to force a size update now to ensure implicitWidth is calculated
3012 const_cast<QQuickTextEditPrivate*>(this)->requireImplicitWidth = true;
3013 const_cast<QQuickTextEdit*>(q)->updateSize();
3014 }
3015 return implicitWidth;
3016}
3017
3018//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't
3019// need to do all the calculations each time
3020void QQuickTextEdit::updateSize()
3021{
3022 Q_D(QQuickTextEdit);
3023 if (!isComponentComplete()) {
3024 d->dirty = true;
3025 return;
3026 }
3027
3028 // ### assumes that if the width is set, the text will fill to edges
3029 // ### (unless wrap is false, then clipping will occur)
3030 if (widthValid()) {
3031 if (!d->requireImplicitWidth) {
3032 emit implicitWidthChanged();
3033 // if the implicitWidth is used, then updateSize() has already been called (recursively)
3034 if (d->requireImplicitWidth)
3035 return;
3036 }
3037 if (d->requireImplicitWidth) {
3038 d->document->setTextWidth(-1);
3039 const qreal naturalWidth = d->document->idealWidth();
3040 const bool wasInLayout = d->inLayout;
3041 d->inLayout = true;
3042 if (d->isImplicitResizeEnabled())
3043 setImplicitWidth(naturalWidth + leftPadding() + rightPadding());
3044 d->inLayout = wasInLayout;
3045 if (d->inLayout) // probably the result of a binding loop, but by letting it
3046 return; // get this far we'll get a warning to that effect.
3047 }
3048 const qreal newTextWidth = width() - leftPadding() - rightPadding();
3049 if (d->document->textWidth() != newTextWidth)
3050 d->document->setTextWidth(newTextWidth);
3051 } else if (d->wrapMode == NoWrap) {
3052 // normally, if explicit width is not set, we should call setTextWidth(-1) here,
3053 // as we don't need to fit the text to any fixed width. But because of some bug
3054 // in QTextDocument it also breaks RTL text alignment, so we use "idealWidth" instead.
3055 const qreal newTextWidth = d->document->idealWidth();
3056 if (d->document->textWidth() != newTextWidth)
3057 d->document->setTextWidth(newTextWidth);
3058 } else {
3059 d->document->setTextWidth(-1);
3060 }
3061
3062 QFontMetricsF fm(d->font);
3063 const qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
3064 const qreal newWidth = d->document->idealWidth();
3065
3066 if (d->isImplicitResizeEnabled()) {
3067 // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
3068 if (!widthValid())
3069 setImplicitSize(newWidth + leftPadding() + rightPadding(), newHeight + topPadding() + bottomPadding());
3070 else
3071 setImplicitHeight(newHeight + topPadding() + bottomPadding());
3072 }
3073
3074 d->xoff = leftPadding() + qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width() - leftPadding() - rightPadding(), effectiveHAlign()));
3075 d->yoff = topPadding() + QQuickTextUtil::alignedY(d->document->size().height(), height() - topPadding() - bottomPadding(), d->vAlign);
3076
3077 qreal baseline = fm.ascent();
3078 QTextBlock firstBlock = d->document->firstBlock();
3079 if (firstBlock.isValid() && firstBlock.layout() != nullptr && firstBlock.lineCount() > 0) {
3080 QTextLine firstLine = firstBlock.layout()->lineAt(0);
3081 if (firstLine.isValid())
3082 baseline = firstLine.ascent();
3083 }
3084
3085 setBaselineOffset(baseline + d->yoff + d->textMargin);
3086
3087 QSizeF size(newWidth, newHeight);
3088 if (d->contentSize != size) {
3089 d->contentSize = size;
3090 // Note: inResize is a bitfield so QScopedValueRollback can't be used here
3091 const bool wasInResize = d->inResize;
3092 d->inResize = true;
3093 if (!wasInResize)
3094 emit contentSizeChanged();
3095 d->inResize = wasInResize;
3096 updateTotalLines();
3097 }
3098}
3099
3100void QQuickTextEdit::updateWholeDocument()
3101{
3102 Q_D(QQuickTextEdit);
3103 if (!d->textNodeMap.isEmpty()) {
3104 for (TextNode &node : d->textNodeMap)
3105 node.setDirty();
3106 }
3107
3108 if (isComponentComplete()) {
3109 polish();
3110 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3111 update();
3112 }
3113}
3114
3115void QQuickTextEdit::invalidateBlock(const QTextBlock &block)
3116{
3117 Q_D(QQuickTextEdit);
3118 markDirtyNodesForRange(block.position(), block.position() + block.length(), 0);
3119
3120 if (isComponentComplete()) {
3121 polish();
3122 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3123 update();
3124 }
3125}
3126
3127void QQuickTextEdit::updateCursor()
3128{
3129 Q_D(QQuickTextEdit);
3130 if (isComponentComplete() && isVisible()) {
3131 polish();
3132 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3133 update();
3134 }
3135}
3136
3137void QQuickTextEdit::q_linkHovered(const QString &link)
3138{
3139 Q_D(QQuickTextEdit);
3140 emit linkHovered(link);
3141#if QT_CONFIG(cursor)
3142 if (link.isEmpty()) {
3143 d->updateMouseCursorShape();
3144 } else if (cursor().shape() != Qt::PointingHandCursor) {
3145 setCursor(Qt::PointingHandCursor);
3146 }
3147#endif
3148}
3149
3150void QQuickTextEdit::q_markerHovered(bool hovered)
3151{
3152 Q_D(QQuickTextEdit);
3153#if QT_CONFIG(cursor)
3154 if (!hovered) {
3155 d->updateMouseCursorShape();
3156 } else if (cursor().shape() != Qt::PointingHandCursor) {
3157 setCursor(Qt::PointingHandCursor);
3158 }
3159#endif
3160}
3161
3162void QQuickTextEdit::q_updateAlignment()
3163{
3164 Q_D(QQuickTextEdit);
3165 if (d->determineHorizontalAlignment()) {
3166 d->updateDefaultTextOption();
3167 d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()));
3168 moveCursorDelegate();
3169 updateWholeDocument();
3170 }
3171}
3172
3173void QQuickTextEdit::updateTotalLines()
3174{
3175 Q_D(QQuickTextEdit);
3176
3177 int subLines = 0;
3178
3179 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
3180 QTextLayout *layout = it.layout();
3181 if (!layout)
3182 continue;
3183 subLines += layout->lineCount()-1;
3184 }
3185
3186 int newTotalLines = d->document->lineCount() + subLines;
3187 if (d->lineCount != newTotalLines) {
3188 d->lineCount = newTotalLines;
3189 emit lineCountChanged();
3190 }
3191}
3192
3193void QQuickTextEditPrivate::updateDefaultTextOption()
3194{
3195 Q_Q(QQuickTextEdit);
3196 QTextOption opt = document->defaultTextOption();
3197 const Qt::Alignment oldAlignment = opt.alignment();
3198 Qt::LayoutDirection oldTextDirection = opt.textDirection();
3199
3200 QQuickTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign();
3201 if (contentDirection == Qt::RightToLeft) {
3202 if (horizontalAlignment == QQuickTextEdit::AlignLeft)
3203 horizontalAlignment = QQuickTextEdit::AlignRight;
3204 else if (horizontalAlignment == QQuickTextEdit::AlignRight)
3205 horizontalAlignment = QQuickTextEdit::AlignLeft;
3206 }
3207 if (!hAlignImplicit)
3208 opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign));
3209 else
3210 opt.setAlignment(Qt::Alignment(vAlign));
3211
3212#if QT_CONFIG(im)
3213 if (contentDirection == Qt::LayoutDirectionAuto) {
3214 opt.setTextDirection(qGuiApp->inputMethod()->inputDirection());
3215 } else
3216#endif
3217 {
3218 opt.setTextDirection(contentDirection);
3219 }
3220
3221 QTextOption::WrapMode oldWrapMode = opt.wrapMode();
3222 opt.setWrapMode(QTextOption::WrapMode(wrapMode));
3223
3224 bool oldUseDesignMetrics = opt.useDesignMetrics();
3225 opt.setUseDesignMetrics(renderType != QQuickTextEdit::NativeRendering);
3226
3227 if (oldWrapMode != opt.wrapMode() || oldAlignment != opt.alignment()
3228 || oldTextDirection != opt.textDirection()
3229 || oldUseDesignMetrics != opt.useDesignMetrics()) {
3230 document->setDefaultTextOption(opt);
3231 }
3232}
3233
3234void QQuickTextEditPrivate::onDocumentStatusChanged()
3235{
3236 Q_ASSERT(quickDocument);
3237 switch (quickDocument->status()) {
3238 case QQuickTextDocument::Status::Loaded:
3239 case QQuickTextDocument::Status::Saved:
3240 switch (QQuickTextDocumentPrivate::get(quickDocument)->detectedFormat) {
3241 case Qt::RichText:
3242 richText = (format == QQuickTextEdit::RichText || format == QQuickTextEdit::AutoText);
3243 markdownText = false;
3244 break;
3245 case Qt::MarkdownText:
3246 richText = false;
3247 markdownText = (format == QQuickTextEdit::MarkdownText || format == QQuickTextEdit::AutoText);
3248 break;
3249 case Qt::PlainText:
3250 richText = false;
3251 markdownText = false;
3252 break;
3253 case Qt::AutoText: // format not detected
3254 break;
3255 }
3256 break;
3257 default:
3258 break;
3259 }
3260}
3261
3262void QQuickTextEdit::focusInEvent(QFocusEvent *event)
3263{
3264 Q_D(QQuickTextEdit);
3265 d->handleFocusEvent(event);
3266 QQuickImplicitSizeItem::focusInEvent(event);
3267}
3268
3269void QQuickTextEdit::focusOutEvent(QFocusEvent *event)
3270{
3271 Q_D(QQuickTextEdit);
3272 d->handleFocusEvent(event);
3273 QQuickImplicitSizeItem::focusOutEvent(event);
3274}
3275
3276#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
3277bool QQuickTextEditPrivate::handleContextMenuEvent(QContextMenuEvent *event)
3278#else
3279bool QQuickTextEdit::contextMenuEvent(QContextMenuEvent *event)
3280#endif
3281{
3282 Q_Q(QQuickTextEdit);
3283 QContextMenuEvent mapped(event->reason(),
3284 q->mapToScene(q->cursorRectangle().center()).toPoint(), event->globalPos(),
3285 event->modifiers());
3286 const bool eventProcessed = QQuickItemPrivate::handleContextMenuEvent(&mapped);
3287 event->setAccepted(mapped.isAccepted());
3288 return eventProcessed;
3289}
3290
3291void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
3292{
3293 Q_Q(QQuickTextEdit);
3294 bool focus = event->type() == QEvent::FocusIn;
3295 if (!q->isReadOnly())
3296 q->setCursorVisible(focus);
3297 control->processEvent(event, QPointF(-xoff, -yoff));
3298 if (focus) {
3299 q->q_updateAlignment();
3300#if QT_CONFIG(im)
3301 if (focusOnPress && !q->isReadOnly())
3302 qGuiApp->inputMethod()->show();
3303 q->connect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
3304 q, SLOT(q_updateAlignment()));
3305#endif
3306 } else {
3307#if QT_CONFIG(im)
3308 q->disconnect(QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
3309 q, SLOT(q_updateAlignment()));
3310#endif
3311 if (event->reason() != Qt::ActiveWindowFocusReason
3312 && event->reason() != Qt::PopupFocusReason
3313 && control->textCursor().hasSelection()
3314 && !persistentSelection)
3315 q->deselect();
3316
3317 emit q->editingFinished();
3318 }
3319}
3320
3321void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QSGInternalTextNode *node, TextNodeIterator &it, int startPos)
3322{
3323 engine->addToSceneGraph(node, nullptr, QQuickText::Normal, QColor());
3324 it = textNodeMap.insert(it, TextNode(startPos, node));
3325 ++it;
3326 root->appendChildNode(node);
3327 ++renderedBlockCount;
3328}
3329
3330QSGInternalTextNode *QQuickTextEditPrivate::createTextNode()
3331{
3332 Q_Q(QQuickTextEdit);
3333 QSGInternalTextNode* node = sceneGraphContext()->createInternalTextNode(sceneGraphRenderContext());
3334 node->setRenderType(QSGTextNode::RenderType(renderType));
3335 node->setFiltering(q->smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
3336 return node;
3337}
3338
3339void QQuickTextEdit::q_canPasteChanged()
3340{
3341 Q_D(QQuickTextEdit);
3342 bool old = d->canPaste;
3343 d->canPaste = d->control->canPaste();
3344 bool changed = old!=d->canPaste || !d->canPasteValid;
3345 d->canPasteValid = true;
3346 if (changed)
3347 emit canPasteChanged();
3348}
3349
3350/*!
3351 \qmlmethod string QtQuick::TextEdit::getText(int start, int end)
3352
3353 Returns the section of text that is between the \a start and \a end positions.
3354
3355 The returned text does not include any rich text formatting.
3356*/
3357
3358QString QQuickTextEdit::getText(int start, int end) const
3359{
3360 Q_D(const QQuickTextEdit);
3361 start = qBound(0, start, d->document->characterCount() - 1);
3362 end = qBound(0, end, d->document->characterCount() - 1);
3363 QTextCursor cursor(d->document);
3364 cursor.setPosition(start, QTextCursor::MoveAnchor);
3365 cursor.setPosition(end, QTextCursor::KeepAnchor);
3366#if QT_CONFIG(texthtmlparser)
3367 return d->richText || d->markdownText
3368 ? cursor.selectedText()
3369 : cursor.selection().toPlainText();
3370#else
3371 return cursor.selection().toPlainText();
3372#endif
3373}
3374
3375/*!
3376 \qmlmethod string QtQuick::TextEdit::getFormattedText(int start, int end)
3377
3378 Returns the section of text that is between the \a start and \a end positions.
3379
3380 The returned text will be formatted according the \l textFormat property.
3381*/
3382
3383QString QQuickTextEdit::getFormattedText(int start, int end) const
3384{
3385 Q_D(const QQuickTextEdit);
3386
3387 start = qBound(0, start, d->document->characterCount() - 1);
3388 end = qBound(0, end, d->document->characterCount() - 1);
3389
3390 QTextCursor cursor(d->document);
3391 cursor.setPosition(start, QTextCursor::MoveAnchor);
3392 cursor.setPosition(end, QTextCursor::KeepAnchor);
3393
3394 if (d->richText) {
3395#if QT_CONFIG(texthtmlparser)
3396 return cursor.selection().toHtml();
3397#else
3398 return cursor.selection().toPlainText();
3399#endif
3400 } else if (d->markdownText) {
3401#if QT_CONFIG(textmarkdownwriter)
3402 return cursor.selection().toMarkdown();
3403#else
3404 return cursor.selection().toPlainText();
3405#endif
3406 } else {
3407 return cursor.selection().toPlainText();
3408 }
3409}
3410
3411/*!
3412 \qmlmethod void QtQuick::TextEdit::insert(int position, string text)
3413
3414 Inserts \a text into the TextEdit at \a position.
3415*/
3416void QQuickTextEdit::insert(int position, const QString &text)
3417{
3418 Q_D(QQuickTextEdit);
3419 if (position < 0 || position >= d->document->characterCount())
3420 return;
3421 QTextCursor cursor(d->document);
3422 cursor.setPosition(position);
3423 d->richText = d->richText || (d->format == AutoText && Qt::mightBeRichText(text));
3424 if (d->richText) {
3425#if QT_CONFIG(texthtmlparser)
3426 cursor.insertHtml(text);
3427#else
3428 cursor.insertText(text);
3429#endif
3430 } else if (d->markdownText) {
3431#if QT_CONFIG(textmarkdownreader)
3432 cursor.insertMarkdown(text);
3433#else
3434 cursor.insertText(text);
3435#endif
3436 } else {
3437 cursor.insertText(text);
3438 }
3439 d->control->updateCursorRectangle(false);
3440}
3441
3442/*!
3443 \qmlmethod string QtQuick::TextEdit::remove(int start, int end)
3444
3445 Removes the section of text that is between the \a start and \a end positions from the TextEdit.
3446*/
3447
3448void QQuickTextEdit::remove(int start, int end)
3449{
3450 Q_D(QQuickTextEdit);
3451 start = qBound(0, start, d->document->characterCount() - 1);
3452 end = qBound(0, end, d->document->characterCount() - 1);
3453 QTextCursor cursor(d->document);
3454 cursor.setPosition(start, QTextCursor::MoveAnchor);
3455 cursor.setPosition(end, QTextCursor::KeepAnchor);
3456 cursor.removeSelectedText();
3457 d->control->updateCursorRectangle(false);
3458}
3459
3460/*!
3461 \qmlproperty TextDocument QtQuick::TextEdit::textDocument
3462 \since 5.1
3463
3464 Returns the QQuickTextDocument of this TextEdit.
3465 Since Qt 6.7, it has features for loading and saving files.
3466 It can also be used in C++ as a means of accessing the underlying QTextDocument
3467 instance, for example to install a \l QSyntaxHighlighter.
3468
3469 \sa QQuickTextDocument
3470*/
3471
3472QQuickTextDocument *QQuickTextEdit::textDocument()
3473{
3474 Q_D(QQuickTextEdit);
3475 if (!d->quickDocument) {
3476 d->quickDocument = new QQuickTextDocument(this);
3477 connect(d->quickDocument, &QQuickTextDocument::statusChanged, d->quickDocument,
3478 [d]() { d->onDocumentStatusChanged(); } );
3479 }
3480 return d->quickDocument;
3481}
3482
3483bool QQuickTextEditPrivate::isLinkHoveredConnected()
3484{
3485 Q_Q(QQuickTextEdit);
3486 IS_SIGNAL_CONNECTED(q, QQuickTextEdit, linkHovered, (const QString &));
3487}
3488
3489bool QQuickTextEditPrivate::isHoveredToolTipChangedConnected()
3490{
3491 Q_Q(QQuickTextEdit);
3492 IS_SIGNAL_CONNECTED(q, QQuickTextEdit, hoveredToolTipChanged, ());
3493}
3494
3495bool QQuickTextEditPrivate::isHoveredSignalConnected()
3496{
3497 return isLinkHoveredConnected() || isHoveredToolTipChangedConnected();
3498}
3499
3500#if QT_CONFIG(cursor)
3501void QQuickTextEditPrivate::updateMouseCursorShape()
3502{
3503 Q_Q(QQuickTextEdit);
3504 q->setCursor(q->isReadOnly() && !q->selectByMouse() ? Qt::ArrowCursor : Qt::IBeamCursor);
3505}
3506#endif
3507
3508/*!
3509 \qmlsignal QtQuick::TextEdit::linkHovered(string link)
3510 \since 5.2
3511
3512 This signal is emitted when the user hovers a link embedded in the text.
3513 The link must be in rich text or HTML format and the
3514 \a link string provides access to the particular link.
3515
3516 \sa hoveredLink, linkAt()
3517*/
3518
3519/*!
3520 \qmlsignal QtQuick::TextEdit::editingFinished()
3521 \since 5.6
3522
3523 This signal is emitted when the text edit loses focus.
3524*/
3525
3526/*!
3527 \qmlproperty string QtQuick::TextEdit::hoveredLink
3528 \since 5.2
3529
3530 This property contains the link string when the user hovers a link
3531 embedded in the text. The link must be in rich text or HTML format
3532 and the link string provides access to the particular link.
3533
3534 \sa linkHovered, linkAt()
3535*/
3536
3537/*!
3538 \qmlproperty string QtQuick::TextEdit::hoveredToolTip
3539 \since 6.13
3540
3541 This property contains the tool tip string of the text fragment that the
3542 user is hovering, if any; otherwise it is empty. It changes as the mouse
3543 moves between fragments, and becomes empty when the mouse leaves text that
3544 carries a tool tip, or leaves the item. The tool tip must be provided by
3545 rich text or HTML, or by adding character formats to a \l QTextDocument
3546 programmatically.
3547
3548 A typical use is to drive a \l ToolTip, which appears a short time after
3549 hovering in one place over the link or the image:
3550 \snippet qml/text/hoveredToolTip.qml textedit
3551
3552 Alternatively you could use a HoverHandler to make the ToolTip follow the
3553 cursor, but this is a little more expensive:
3554 \snippet qml/text/hoveredToolTip.qml textedit-follow
3555
3556 \sa hoveredLink
3557*/
3558
3559/*!
3560 \qmlsignal QtQuick::TextEdit::textEdited()
3561 \since 6.9
3562
3563 This signal is emitted whenever the text is edited. Unlike \l{TextEdit::text}{textChanged()},
3564 this signal is not emitted when the text is changed programmatically, for example,
3565 by changing the value of the \l text property or by calling \l clear().
3566*/
3567
3568QString QQuickTextEdit::hoveredLink() const
3569{
3570 Q_D(const QQuickTextEdit);
3571 if (const_cast<QQuickTextEditPrivate *>(d)->isLinkHoveredConnected()) {
3572 return d->control->hoveredLink();
3573 } else {
3574#if QT_CONFIG(cursor)
3575 if (QQuickWindow *wnd = window()) {
3576 QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
3577 return d->control->anchorAt(pos);
3578 }
3579#endif // cursor
3580 }
3581 return QString();
3582}
3583
3584QString QQuickTextEdit::hoveredToolTip() const
3585{
3586 Q_D(const QQuickTextEdit);
3587 if (const_cast<QQuickTextEditPrivate *>(d)->isHoveredToolTipChangedConnected()) {
3588 return d->control->hoveredToolTip();
3589 } else {
3590#if QT_CONFIG(cursor)
3591 if (QQuickWindow *wnd = window()) {
3592 const QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
3593 return d->control->charFormatAt(pos).toolTip();
3594 }
3595#endif // cursor
3596 }
3597 return QString();
3598}
3599
3600void QQuickTextEdit::hoverEnterEvent(QHoverEvent *event)
3601{
3602 Q_D(QQuickTextEdit);
3603 if (d->isHoveredSignalConnected())
3604 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
3605 event->ignore();
3606}
3607
3608void QQuickTextEdit::hoverMoveEvent(QHoverEvent *event)
3609{
3610 Q_D(QQuickTextEdit);
3611 if (d->isHoveredSignalConnected())
3612 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
3613 event->ignore();
3614}
3615
3616void QQuickTextEdit::hoverLeaveEvent(QHoverEvent *event)
3617{
3618 Q_D(QQuickTextEdit);
3619 if (d->isHoveredSignalConnected())
3620 d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
3621 event->ignore();
3622}
3623
3624/*!
3625 \qmlmethod void QtQuick::TextEdit::append(string text)
3626 \since 5.2
3627
3628 Appends a new paragraph with \a text to the end of the TextEdit.
3629
3630 In order to append without inserting a new paragraph,
3631 call \c myTextEdit.insert(myTextEdit.length, text) instead.
3632*/
3633void QQuickTextEdit::append(const QString &text)
3634{
3635 Q_D(QQuickTextEdit);
3636 QTextCursor cursor(d->document);
3637 cursor.beginEditBlock();
3638 cursor.movePosition(QTextCursor::End);
3639
3640 if (!d->document->isEmpty())
3641 cursor.insertBlock();
3642
3643 if (d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text))) {
3644#if QT_CONFIG(texthtmlparser)
3645 cursor.insertHtml(text);
3646#else
3647 cursor.insertText(text);
3648#endif
3649 } else if (d->format == MarkdownText) {
3650#if QT_CONFIG(textmarkdownreader)
3651 cursor.insertMarkdown(text);
3652#else
3653 cursor.insertText(text);
3654#endif
3655 } else {
3656 cursor.insertText(text);
3657 }
3658
3659 cursor.endEditBlock();
3660 d->control->updateCursorRectangle(false);
3661}
3662
3663/*!
3664 \qmlmethod string QtQuick::TextEdit::linkAt(real x, real y)
3665 \since 5.3
3666
3667 Returns the link string at point \a x, \a y in content coordinates,
3668 or an empty string if no link exists at that point.
3669
3670 \sa hoveredLink
3671*/
3672QString QQuickTextEdit::linkAt(qreal x, qreal y) const
3673{
3674 Q_D(const QQuickTextEdit);
3675 return d->control->anchorAt(QPointF(x + topPadding(), y + leftPadding()));
3676}
3677
3678/*!
3679 \since 5.6
3680 \qmlproperty real QtQuick::TextEdit::padding
3681 \qmlproperty real QtQuick::TextEdit::topPadding
3682 \qmlproperty real QtQuick::TextEdit::leftPadding
3683 \qmlproperty real QtQuick::TextEdit::bottomPadding
3684 \qmlproperty real QtQuick::TextEdit::rightPadding
3685
3686 These properties hold the padding around the content. This space is reserved
3687 in addition to the contentWidth and contentHeight.
3688*/
3689qreal QQuickTextEdit::padding() const
3690{
3691 Q_D(const QQuickTextEdit);
3692 return d->padding();
3693}
3694
3695void QQuickTextEdit::setPadding(qreal padding)
3696{
3697 Q_D(QQuickTextEdit);
3698 if (qFuzzyCompare(d->padding(), padding))
3699 return;
3700
3701 d->extra.value().padding = padding;
3702 updateSize();
3703 if (isComponentComplete()) {
3704 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3705 update();
3706 }
3707 emit paddingChanged();
3708 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3709 emit topPaddingChanged();
3710 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3711 emit leftPaddingChanged();
3712 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3713 emit rightPaddingChanged();
3714 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3715 emit bottomPaddingChanged();
3716}
3717
3718void QQuickTextEdit::resetPadding()
3719{
3720 setPadding(0);
3721}
3722
3723qreal QQuickTextEdit::topPadding() const
3724{
3725 Q_D(const QQuickTextEdit);
3726 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3727 return d->extra->topPadding;
3728 return d->padding();
3729}
3730
3731void QQuickTextEdit::setTopPadding(qreal padding)
3732{
3733 Q_D(QQuickTextEdit);
3734 d->setTopPadding(padding);
3735}
3736
3737void QQuickTextEdit::resetTopPadding()
3738{
3739 Q_D(QQuickTextEdit);
3740 d->setTopPadding(0, true);
3741}
3742
3743qreal QQuickTextEdit::leftPadding() const
3744{
3745 Q_D(const QQuickTextEdit);
3746 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3747 return d->extra->leftPadding;
3748 return d->padding();
3749}
3750
3751void QQuickTextEdit::setLeftPadding(qreal padding)
3752{
3753 Q_D(QQuickTextEdit);
3754 d->setLeftPadding(padding);
3755}
3756
3757void QQuickTextEdit::resetLeftPadding()
3758{
3759 Q_D(QQuickTextEdit);
3760 d->setLeftPadding(0, true);
3761}
3762
3763qreal QQuickTextEdit::rightPadding() const
3764{
3765 Q_D(const QQuickTextEdit);
3766 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3767 return d->extra->rightPadding;
3768 return d->padding();
3769}
3770
3771void QQuickTextEdit::setRightPadding(qreal padding)
3772{
3773 Q_D(QQuickTextEdit);
3774 d->setRightPadding(padding);
3775}
3776
3777void QQuickTextEdit::resetRightPadding()
3778{
3779 Q_D(QQuickTextEdit);
3780 d->setRightPadding(0, true);
3781}
3782
3783qreal QQuickTextEdit::bottomPadding() const
3784{
3785 Q_D(const QQuickTextEdit);
3786 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3787 return d->extra->bottomPadding;
3788 return d->padding();
3789}
3790
3791void QQuickTextEdit::setBottomPadding(qreal padding)
3792{
3793 Q_D(QQuickTextEdit);
3794 d->setBottomPadding(padding);
3795}
3796
3797void QQuickTextEdit::resetBottomPadding()
3798{
3799 Q_D(QQuickTextEdit);
3800 d->setBottomPadding(0, true);
3801}
3802
3803/*!
3804 \qmlproperty real QtQuick::TextEdit::tabStopDistance
3805 \since 5.10
3806
3807 The default distance, in device units, between tab stops.
3808
3809 \sa QTextOption::setTabStopDistance()
3810*/
3811int QQuickTextEdit::tabStopDistance() const
3812{
3813 Q_D(const QQuickTextEdit);
3814 return d->document->defaultTextOption().tabStopDistance();
3815}
3816
3817void QQuickTextEdit::setTabStopDistance(qreal distance)
3818{
3819 Q_D(QQuickTextEdit);
3820 QTextOption textOptions = d->document->defaultTextOption();
3821 if (textOptions.tabStopDistance() == distance)
3822 return;
3823
3824 textOptions.setTabStopDistance(distance);
3825 d->document->setDefaultTextOption(textOptions);
3826 emit tabStopDistanceChanged(distance);
3827}
3828
3829/*!
3830 \qmlmethod void QtQuick::TextEdit::clear()
3831 \since 5.7
3832
3833 Clears the contents of the text edit
3834 and resets partial text input from an input method.
3835
3836 Use this method instead of setting the \l text property to an empty string.
3837
3838 \sa QInputMethod::reset()
3839*/
3840void QQuickTextEdit::clear()
3841{
3842 Q_D(QQuickTextEdit);
3843 d->resetInputMethod();
3844 d->control->clear();
3845}
3846
3847#ifndef QT_NO_DEBUG_STREAM
3848QDebug operator<<(QDebug debug, const QQuickTextEditPrivate::Node &n)
3849{
3850 QDebugStateSaver saver(debug);
3851 debug.space();
3852 debug << "Node(startPos:" << n.m_startPos << "dirty:" << n.m_dirty << n.m_node << ')';
3853 return debug;
3854}
3855#endif
3856
3857#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
3858void QQuickTextEdit::setOldSelectionDefault()
3859{
3860 Q_D(QQuickTextEdit);
3861 d->selectByMouse = false;
3862 setKeepMouseGrab(false);
3863 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
3864 d->control->setTouchDragSelectionEnabled(true);
3865 qCDebug(lcTextEdit, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
3866}
3867
3868// TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
3869QQuickPre64TextEdit::QQuickPre64TextEdit(QQuickItem *parent)
3870 : QQuickTextEdit(parent)
3871{
3872 setOldSelectionDefault();
3873}
3874#endif
3875
3876QT_END_NAMESPACE
3877
3878#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