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