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