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
4#include "qquicktextedit_p.h"
5#include "qquicktextedit_p_p.h"
6#include "qquicktextcontrol_p.h"
7#include "qquicktextdocument_p.h"
8#include "qquickwindow.h"
9#include "qsginternaltextnode_p.h"
10#include "qquicktextnodeengine_p.h"
11
12#include <QtCore/qmath.h>
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qevent.h>
15#include <QtGui/qpainter.h>
16#include <QtGui/qtextobject.h>
17#include <QtGui/qtexttable.h>
18#include <QtQml/qqmlinfo.h>
19#include <QtQuick/qsgsimplerectnode.h>
20
21#include <private/qqmlglobal_p.h>
22#include <private/qqmlproperty_p.h>
23#include <private/qtextengine_p.h>
24#include <private/qsgadaptationlayer_p.h>
25#include <QtQuick/private/qquickpixmapcache_p.h>
26
27#if QT_CONFIG(accessibility)
28#include <private/qquickaccessibleattached_p.h>
29#endif
30
31#include "qquicktextdocument.h"
32
33#include <algorithm>
34
35QT_BEGIN_NAMESPACE
36
37Q_DECLARE_LOGGING_CATEGORY(lcVP)
38Q_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.
69
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(node: 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(node: cursorNode);
130 delete cursorNode;
131 cursorNode = newNode;
132 if (cursorNode) {
133 appendChildNode(node: 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(c: 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(flag: QQuickItem::ItemObservesViewport, enabled: text.size() > QQuickTextEditPrivate::largeTextSizeThreshold);
443}
444
445void QQuickTextEdit::invalidate()
446{
447 QMetaObject::invokeMethod(object: this, function: &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(from: 0, length: 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(doc: 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(textFormat: 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: 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(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle | Qt::ImFont);
753#endif
754 }
755 emit fontChanged(font: 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(color: 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(color: 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(color: 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, forceAlign: 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(alignment: 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(text: 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(align: 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(item: viewport, rect: 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(r: 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 object: qmlAttachedPropertiesObject<QQuickAccessibleAttached>(obj: q, create: 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(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: 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(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: 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(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: 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(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: 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(alignment: 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(a: 0, b: 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(cursor: c).translated(dx: d->xoff, dy: 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(point: QPointF(x, y), accuracy: 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(block: cursor.block()).contains(ax: x, ay: 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, mode: 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, mode: QTextCursor::KeepAnchor);
1392 } else if (cursor.anchor() < pos || (cursor.anchor() == pos && cursor.position() < pos)) {
1393 if (cursor.anchor() > cursor.position()) {
1394 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1395 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1396 if (cursor.position() == cursor.anchor())
1397 cursor.movePosition(op: QTextCursor::PreviousWord, QTextCursor::MoveAnchor);
1398 else
1399 cursor.setPosition(pos: cursor.position(), mode: QTextCursor::MoveAnchor);
1400 } else {
1401 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1402 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
1403 }
1404
1405 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1406 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1407 if (cursor.position() != pos)
1408 cursor.movePosition(op: 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(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1412 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1413 } else {
1414 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1415 cursor.movePosition(op: QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1416 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1417 if (cursor.position() != cursor.anchor()) {
1418 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1419 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1420 }
1421 }
1422
1423 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1424 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1425 if (cursor.position() != pos) {
1426 cursor.movePosition(op: QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1427 cursor.movePosition(op: 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(isCursorVisible: 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(force: 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, delegate: 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(activeFocusOnPressed: 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(isPersistentSelection: 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(textMargin: 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(queries: 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(active: 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(selectByKeyboard: 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(selectByMouse: 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(flag: QQuickItem::ItemAcceptsInputMethod, enabled: !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(op: QTextCursor::End);
1928
1929#if QT_CONFIG(im)
1930 updateInputMethod(queries: Qt::ImEnabled);
1931#endif
1932#if QT_CONFIG(cursor)
1933 d->updateMouseCursorShape();
1934#endif
1935 q_canPasteChanged();
1936 emit readOnlyChanged(isReadOnly: r);
1937 if (!d->selectByKeyboardSet)
1938 emit selectByKeyboardChanged(selectByKeyboard: !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(obj: 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(dx: d->xoff, dy: d->yoff);
1973}
1974
1975bool QQuickTextEdit::event(QEvent *event)
1976{
1977 Q_D(QQuickTextEdit);
1978 if (event->type() == QEvent::ShortcutOverride) {
1979 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1980 if (event->isAccepted())
1981 return true;
1982 }
1983 return QQuickImplicitSizeItem::event(event);
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(e: event, coordinateOffset: 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(e: event, coordinateOffset: 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(selection: 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(pos: start, mode: QTextCursor::MoveAnchor);
2094 cursor.setPosition(pos: end, mode: 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(me: 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(ev: event);
2191 setKeepMouseGrab(d->selectByMouse && isMouse);
2192 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
2193 if (d->focusOnPress){
2194 bool hadActiveFocus = hasActiveFocus();
2195 forceActiveFocus(reason: 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(e: event, coordinateOffset: 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(e: event, coordinateOffset: 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(e: event, coordinateOffset: 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(e: event, coordinateOffset: 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(query: 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(query: property, argument);
2286 if (property == Qt::ImCursorRectangle || property == Qt::ImAnchorRectangle)
2287 v = QVariant(v.toRectF().translated(dx: d->xoff, dy: 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, argument: 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(relative: 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(me: 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(other: "qrc"_L1, cs: Qt::CaseInsensitive)) {
2332 // qmlWarning if the file doesn't exist
2333 QFile f(QQmlFile::urlToLocalFileOrQrc(url));
2334 if (f.open(flags: QFile::ReadOnly)) {
2335 QByteArray buf = f.readAll();
2336 f.close();
2337 QImage image;
2338 image.loadFromData(data: buf);
2339 if (!image.isNull())
2340 return image;
2341 }
2342 // if we get here, loading failed
2343 qmlWarning(me: 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 first: d->pixmapsInProgress.cbegin(), last: d->pixmapsInProgress.cend(),
2350 pred: [&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(me: this) << job->error();
2355 d->pixmapsInProgress.erase(pos: 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(t: 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(type: QTextDocument::ImageResource, name: 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(type: QTextDocument::ImageResource, name: 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(pos: it);
2399 delete job;
2400 break;
2401 }
2402 }
2403 if (d->pixmapsInProgress.isEmpty()) {
2404 invalidate();
2405 updateSize();
2406 q_invalidate();
2407 }
2408}
2409
2410typedef QQuickTextEditPrivate::Node TextNode;
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(x: topLeft.x(), y: 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)
2462{
2463 *engine = QQuickTextNodeEngine();
2464 engine->setTextColor(textColor);
2465 engine->setSelectedTextColor(selectedTextColor);
2466 engine->setSelectionColor(selectionColor);
2467}
2468
2469QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
2470{
2471 Q_UNUSED(updatePaintNodeData);
2472 Q_D(QQuickTextEdit);
2473
2474 if (d->updateType != QQuickTextEditPrivate::UpdatePaintNode
2475 && d->updateType != QQuickTextEditPrivate::UpdateAll
2476 && oldNode != nullptr) {
2477 // Update done in preprocess() in the nodes
2478 d->updateType = QQuickTextEditPrivate::UpdateNone;
2479 return oldNode;
2480 }
2481
2482 d->containsUnscalableGlyphs = false;
2483 if (!oldNode || d->updateType == QQuickTextEditPrivate::UpdateAll) {
2484 delete oldNode;
2485 oldNode = nullptr;
2486
2487 // If we had any QSGInternalTextNode node references, they were deleted along with the root node
2488 // But here we must delete the Node structures in textNodeMap
2489 d->textNodeMap.clear();
2490 }
2491
2492 d->updateType = QQuickTextEditPrivate::UpdateNone;
2493
2494 RootNode *rootNode = static_cast<RootNode *>(oldNode);
2495 TextNodeIterator nodeIterator = d->textNodeMap.begin();
2496 std::optional<int> firstPosAcrossAllNodes;
2497 if (nodeIterator != d->textNodeMap.end())
2498 firstPosAcrossAllNodes = nodeIterator->startPos();
2499
2500 while (nodeIterator != d->textNodeMap.end() && !nodeIterator->dirty())
2501 ++nodeIterator;
2502
2503 QQuickTextNodeEngine engine;
2504 QQuickTextNodeEngine frameDecorationsEngine;
2505
2506 if (!oldNode || nodeIterator < d->textNodeMap.end() || d->textNodeMap.isEmpty()) {
2507
2508 if (!oldNode)
2509 rootNode = new RootNode;
2510
2511 int firstDirtyPos = 0;
2512 if (nodeIterator != d->textNodeMap.end()) {
2513 firstDirtyPos = nodeIterator->startPos();
2514 // ### this could be optimized if the first and last dirty nodes are not connected
2515 // as the intermediate text nodes would usually only need to be transformed differently.
2516 QSGInternalTextNode *firstCleanNode = nullptr;
2517 auto it = d->textNodeMap.constEnd();
2518 while (it != nodeIterator) {
2519 --it;
2520 if (it->dirty())
2521 break;
2522 firstCleanNode = it->textNode();
2523 }
2524 do {
2525 rootNode->removeChildNode(node: nodeIterator->textNode());
2526 delete nodeIterator->textNode();
2527 nodeIterator = d->textNodeMap.erase(pos: nodeIterator);
2528 } while (nodeIterator != d->textNodeMap.constEnd() && nodeIterator->textNode() != firstCleanNode);
2529 }
2530
2531 // If there's a lot of text, insert only the range of blocks that can possibly be visible within the viewport.
2532 QRectF viewport;
2533 if (flags().testFlag(flag: QQuickItem::ItemObservesViewport)) {
2534 viewport = clipRect();
2535 qCDebug(lcVP) << "text viewport" << viewport;
2536 }
2537
2538 // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
2539 rootNode->resetFrameDecorations(newNode: d->createTextNode());
2540 resetEngine(engine: &frameDecorationsEngine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2541
2542 QSGInternalTextNode *node = nullptr;
2543
2544 int currentNodeSize = 0;
2545 int nodeStart = firstDirtyPos;
2546 QPointF basePosition(d->xoff, d->yoff);
2547 QMatrix4x4 basePositionMatrix;
2548 basePositionMatrix.translate(x: basePosition.x(), y: basePosition.y());
2549 rootNode->setMatrix(basePositionMatrix);
2550
2551 QPointF nodeOffset;
2552 const TextNode firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator
2553 : TextNode();
2554
2555 QList<QTextFrame *> frames;
2556 frames.append(t: d->document->rootFrame());
2557
2558
2559 d->firstBlockInViewport = -1;
2560 d->firstBlockPastViewport = -1;
2561 int frameCount = -1;
2562 while (!frames.isEmpty()) {
2563 QTextFrame *textFrame = frames.takeFirst();
2564 ++frameCount;
2565 if (frameCount > 0)
2566 firstDirtyPos = 0;
2567 qCDebug(lcVP) << "frame" << frameCount << textFrame
2568 << "from" << positionToRectangle(pos: textFrame->firstPosition()).topLeft()
2569 << "to" << positionToRectangle(pos: textFrame->lastPosition()).bottomRight();
2570 frames.append(l: textFrame->childFrames());
2571 frameDecorationsEngine.addFrameDecorations(document: d->document, frame: textFrame);
2572 resetEngine(engine: &engine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2573
2574 if (textFrame->firstPosition() > textFrame->lastPosition()
2575 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
2576 node = d->createTextNode();
2577 updateNodeTransform(node, topLeft: d->document->documentLayout()->frameBoundingRect(frame: textFrame).topLeft());
2578 const int pos = textFrame->firstPosition() - 1;
2579 auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(d->document->documentLayout());
2580 QTextCharFormat format = a->formatAccessor(pos);
2581 QTextBlock block = textFrame->firstCursorPosition().block();
2582 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2583 bool inView = true;
2584 if (!viewport.isNull() && block.layout()) {
2585 QRectF coveredRegion = block.layout()->boundingRect().adjusted(xp1: nodeOffset.x(), yp1: nodeOffset.y(), xp2: nodeOffset.x(), yp2: nodeOffset.y());
2586 inView = coveredRegion.bottom() >= viewport.top() && coveredRegion.top() <= viewport.bottom();
2587 qCDebug(lcVP) << "non-flow frame" << coveredRegion << "in viewport?" << inView;
2588 }
2589 if (inView) {
2590 engine.setCurrentLine(block.layout()->lineForTextPosition(pos: pos - block.position()));
2591 engine.addTextObject(block, position: QPointF(0, 0), format, selectionState: QQuickTextNodeEngine::Unselected, textDocument: d->document,
2592 pos, layoutPosition: textFrame->frameFormat().position());
2593 }
2594 nodeStart = pos;
2595 } else {
2596 // Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
2597 QList<int> frameBoundaries;
2598 frameBoundaries.reserve(size: frames.size());
2599 for (QTextFrame *frame : std::as_const(t&: frames))
2600 frameBoundaries.append(t: frame->firstPosition());
2601 std::sort(first: frameBoundaries.begin(), last: frameBoundaries.end());
2602
2603 QTextFrame::iterator it = textFrame->begin();
2604 while (!it.atEnd()) {
2605 QTextBlock block = it.currentBlock();
2606 if (block.position() < firstDirtyPos) {
2607 ++it;
2608 continue;
2609 }
2610
2611 if (!engine.hasContents())
2612 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2613
2614 bool inView = true;
2615 if (!viewport.isNull()) {
2616 QRectF coveredRegion;
2617 if (block.layout()) {
2618 coveredRegion = block.layout()->boundingRect().adjusted(xp1: nodeOffset.x(), yp1: nodeOffset.y(), xp2: nodeOffset.x(), yp2: nodeOffset.y());
2619 inView = coveredRegion.bottom() > viewport.top();
2620 }
2621 const bool potentiallyScrollingBackwards = firstPosAcrossAllNodes && *firstPosAcrossAllNodes == firstDirtyPos;
2622 if (d->firstBlockInViewport < 0 && inView && potentiallyScrollingBackwards) {
2623 // During backward scrolling, we need to iterate backwards from textNodeMap.begin() to fill the top of the viewport.
2624 if (coveredRegion.top() > viewport.top() + 1) {
2625 qCDebug(lcVP) << "checking backwards from block" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2626 while (it != textFrame->begin() && it.currentBlock().layout() &&
2627 it.currentBlock().layout()->boundingRect().top() + nodeOffset.y() > viewport.top()) {
2628 nodeOffset = d->document->documentLayout()->blockBoundingRect(block: it.currentBlock()).topLeft();
2629 --it;
2630 }
2631 if (!it.currentBlock().layout())
2632 ++it;
2633 if (Q_LIKELY(it.currentBlock().layout())) {
2634 block = it.currentBlock();
2635 coveredRegion = block.layout()->boundingRect().adjusted(xp1: nodeOffset.x(), yp1: nodeOffset.y(), xp2: nodeOffset.x(), yp2: nodeOffset.y());
2636 firstDirtyPos = it.currentBlock().position();
2637 } else {
2638 qCWarning(lcVP) << "failed to find a text block with layout during back-scrolling";
2639 }
2640 }
2641 qCDebug(lcVP) << "first block in viewport" << block.blockNumber() << "@" << nodeOffset.y() << coveredRegion;
2642 if (block.layout())
2643 d->renderedRegion = coveredRegion;
2644 } else {
2645 if (nodeOffset.y() > viewport.bottom()) {
2646 inView = false;
2647 if (d->firstBlockInViewport >= 0 && d->firstBlockPastViewport < 0) {
2648 qCDebug(lcVP) << "first block past viewport" << viewport << block.blockNumber()
2649 << "@" << nodeOffset.y() << "total region rendered" << d->renderedRegion;
2650 d->firstBlockPastViewport = block.blockNumber();
2651 }
2652 break; // skip rest of blocks in this frame
2653 }
2654 if (inView && !block.text().isEmpty() && coveredRegion.isValid()) {
2655 d->renderedRegion = d->renderedRegion.united(r: coveredRegion);
2656 // In case we're going to visit more (nested) frames after this, ensure that we
2657 // don't omit any blocks that fit within the region that we claim as fully rendered.
2658 if (!frames.isEmpty())
2659 viewport = viewport.united(r: d->renderedRegion);
2660 }
2661 }
2662 if (inView && d->firstBlockInViewport < 0)
2663 d->firstBlockInViewport = block.blockNumber();
2664 }
2665
2666 bool createdNodeInView = false;
2667 if (inView) {
2668 if (!engine.hasContents()) {
2669 if (node) {
2670 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2671 || node->containsUnscalableGlyphs();
2672 if (!node->parent())
2673 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2674 }
2675 node = d->createTextNode();
2676 createdNodeInView = true;
2677 updateNodeTransform(node, topLeft: nodeOffset);
2678 nodeStart = block.position();
2679 }
2680 engine.addTextBlock(d->document, block, position: -nodeOffset, textColor: d->color, anchorColor: QColor(), selectionStart: selectionStart(), selectionEnd: selectionEnd() - 1);
2681 currentNodeSize += block.length();
2682 }
2683
2684 if ((it.atEnd()) || block.next().position() >= firstCleanNode.startPos())
2685 break; // last node that needed replacing or last block of the frame
2686 QList<int>::const_iterator lowerBound = std::lower_bound(frameBoundaries.constBegin(), frameBoundaries.constEnd(), block.next().position());
2687 if (node && (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart)) {
2688 currentNodeSize = 0;
2689 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2690 || node->containsUnscalableGlyphs();
2691 if (!node->parent())
2692 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2693 if (!createdNodeInView)
2694 node = d->createTextNode();
2695 resetEngine(engine: &engine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2696 nodeStart = block.next().position();
2697 }
2698 ++it;
2699 } // loop over blocks in frame
2700 }
2701 if (Q_LIKELY(node)) {
2702 d->containsUnscalableGlyphs = d->containsUnscalableGlyphs
2703 || node->containsUnscalableGlyphs();
2704 if (Q_LIKELY(!node->parent()))
2705 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2706 }
2707 }
2708 frameDecorationsEngine.addToSceneGraph(parent: rootNode->frameDecorationsNode, style: QQuickText::Normal, styleColor: QColor());
2709 // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
2710 rootNode->prependChildNode(node: rootNode->frameDecorationsNode);
2711
2712 Q_ASSERT(nodeIterator == d->textNodeMap.end()
2713 || (nodeIterator->textNode() == firstCleanNode.textNode()
2714 && nodeIterator->startPos() == firstCleanNode.startPos()));
2715 // Update the position of the subsequent text blocks.
2716 if (firstCleanNode.textNode() != nullptr) {
2717 QPointF oldOffset = firstCleanNode.textNode()->matrix().map(point: QPointF(0,0));
2718 QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(
2719 block: d->document->findBlock(pos: firstCleanNode.startPos())).topLeft();
2720 QPointF delta = currentOffset - oldOffset;
2721 while (nodeIterator != d->textNodeMap.end()) {
2722 QMatrix4x4 transformMatrix = nodeIterator->textNode()->matrix();
2723 transformMatrix.translate(x: delta.x(), y: delta.y());
2724 nodeIterator->textNode()->setMatrix(transformMatrix);
2725 ++nodeIterator;
2726 }
2727
2728 }
2729
2730 // Since we iterate over blocks from different text frames that are potentially not sorted
2731 // we need to ensure that our list of nodes is sorted again:
2732 std::sort(first: d->textNodeMap.begin(), last: d->textNodeMap.end());
2733 }
2734
2735 if (d->cursorComponent == nullptr) {
2736 QSGInternalRectangleNode* cursor = nullptr;
2737 if (!isReadOnly() && d->cursorVisible && d->control->cursorOn() && d->control->cursorVisible())
2738 cursor = d->sceneGraphContext()->createInternalRectangleNode(rect: d->control->cursorRect(), c: d->color);
2739 rootNode->resetCursorNode(newNode: cursor);
2740 }
2741
2742 invalidateFontCaches();
2743
2744 return rootNode;
2745}
2746
2747void QQuickTextEdit::updatePolish()
2748{
2749 invalidateFontCaches();
2750}
2751
2752/*!
2753 \qmlproperty bool QtQuick::TextEdit::canPaste
2754
2755 Returns true if the TextEdit is writable and the content of the clipboard is
2756 suitable for pasting into the TextEdit.
2757*/
2758bool QQuickTextEdit::canPaste() const
2759{
2760 Q_D(const QQuickTextEdit);
2761 if (!d->canPasteValid) {
2762 const_cast<QQuickTextEditPrivate *>(d)->canPaste = d->control->canPaste();
2763 const_cast<QQuickTextEditPrivate *>(d)->canPasteValid = true;
2764 }
2765 return d->canPaste;
2766}
2767
2768/*!
2769 \qmlproperty bool QtQuick::TextEdit::canUndo
2770
2771 Returns true if the TextEdit is writable and there are previous operations
2772 that can be undone.
2773*/
2774
2775bool QQuickTextEdit::canUndo() const
2776{
2777 Q_D(const QQuickTextEdit);
2778 return d->document->isUndoAvailable();
2779}
2780
2781/*!
2782 \qmlproperty bool QtQuick::TextEdit::canRedo
2783
2784 Returns true if the TextEdit is writable and there are \l {undo}{undone}
2785 operations that can be redone.
2786*/
2787
2788bool QQuickTextEdit::canRedo() const
2789{
2790 Q_D(const QQuickTextEdit);
2791 return d->document->isRedoAvailable();
2792}
2793
2794/*!
2795 \qmlproperty bool QtQuick::TextEdit::inputMethodComposing
2796
2797
2798 This property holds whether the TextEdit has partial text input from an
2799 input method.
2800
2801 While it is composing an input method may rely on mouse or key events from
2802 the TextEdit to edit or commit the partial text. This property can be used
2803 to determine when to disable events handlers that may interfere with the
2804 correct operation of an input method.
2805*/
2806bool QQuickTextEdit::isInputMethodComposing() const
2807{
2808#if !QT_CONFIG(im)
2809 return false;
2810#else
2811 Q_D(const QQuickTextEdit);
2812 return d->control->hasImState();
2813#endif // im
2814}
2815
2816QQuickTextEditPrivate::ExtraData::ExtraData()
2817 : explicitTopPadding(false)
2818 , explicitLeftPadding(false)
2819 , explicitRightPadding(false)
2820 , explicitBottomPadding(false)
2821 , implicitResize(true)
2822{
2823}
2824
2825void QQuickTextEditPrivate::init()
2826{
2827 Q_Q(QQuickTextEdit);
2828
2829#if QT_CONFIG(clipboard)
2830 if (QGuiApplication::clipboard()->supportsSelection())
2831 q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
2832 else
2833#endif
2834 q->setAcceptedMouseButtons(Qt::LeftButton);
2835
2836#if QT_CONFIG(im)
2837 q->setFlag(flag: QQuickItem::ItemAcceptsInputMethod);
2838#endif
2839 q->setFlag(flag: QQuickItem::ItemHasContents);
2840
2841 q->setAcceptHoverEvents(true);
2842
2843 document = new QTextDocument(q);
2844 ownsDocument = true;
2845 auto *imageHandler = new QQuickTextImageHandler(document);
2846 document->documentLayout()->registerHandler(objectType: QTextFormat::ImageObject, component: imageHandler);
2847
2848 control = new QQuickTextControl(document, q);
2849 control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
2850 control->setAcceptRichText(false);
2851 control->setCursorIsFocusIndicator(true);
2852 q->setKeepMouseGrab(true);
2853
2854 qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
2855 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
2856 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2857 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2858 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
2859 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
2860 qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
2861 qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool)));
2862 qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
2863 qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged()));
2864#if QT_CONFIG(clipboard)
2865 qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
2866#endif
2867 qmlobject_connect(document, QTextDocument, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
2868 qmlobject_connect(document, QTextDocument, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
2869 QObject::connect(sender: document, signal: &QTextDocument::contentsChange, context: q, slot: &QQuickTextEdit::q_contentsChange);
2870 QObject::connect(sender: document->documentLayout(), signal: &QAbstractTextDocumentLayout::updateBlock, context: q, slot: &QQuickTextEdit::invalidateBlock);
2871 QObject::connect(sender: control, signal: &QQuickTextControl::linkHovered, context: q, slot: &QQuickTextEdit::q_linkHovered);
2872 QObject::connect(sender: control, signal: &QQuickTextControl::markerHovered, context: q, slot: &QQuickTextEdit::q_markerHovered);
2873
2874 document->setPageSize(QSizeF(0, 0));
2875 document->setDefaultFont(font);
2876 document->setDocumentMargin(textMargin);
2877 document->setUndoRedoEnabled(false); // flush undo buffer.
2878 document->setUndoRedoEnabled(true);
2879 updateDefaultTextOption();
2880 document->setModified(false); // we merely changed some defaults: no edits worth saving yet
2881 q->updateSize();
2882#if QT_CONFIG(cursor)
2883 updateMouseCursorShape();
2884#endif
2885 setSizePolicy(horizontalPolicy: QLayoutPolicy::Expanding, verticalPolicy: QLayoutPolicy::Expanding);
2886}
2887
2888void QQuickTextEditPrivate::resetInputMethod()
2889{
2890 Q_Q(QQuickTextEdit);
2891 if (!q->isReadOnly() && q->hasActiveFocus() && qGuiApp)
2892 QGuiApplication::inputMethod()->reset();
2893}
2894
2895void QQuickTextEdit::q_textChanged()
2896{
2897 Q_D(QQuickTextEdit);
2898 d->textCached = false;
2899 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2900 d->contentDirection = d->textDirection(text: it.text());
2901 if (d->contentDirection != Qt::LayoutDirectionAuto)
2902 break;
2903 }
2904 d->determineHorizontalAlignment();
2905 d->updateDefaultTextOption();
2906 updateSize();
2907
2908 markDirtyNodesForRange(start: 0, end: d->document->characterCount(), charDelta: 0);
2909 if (isComponentComplete()) {
2910 polish();
2911 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2912 update();
2913 }
2914
2915 emit textChanged();
2916}
2917
2918void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
2919{
2920 Q_D(QQuickTextEdit);
2921 if (start == end)
2922 return;
2923
2924 TextNode dummyNode(start);
2925
2926 const TextNodeIterator textNodeMapBegin = d->textNodeMap.begin();
2927 const TextNodeIterator textNodeMapEnd = d->textNodeMap.end();
2928
2929 TextNodeIterator it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, dummyNode);
2930 // qLowerBound gives us the first node past the start of the affected portion, rewind to the first node
2931 // that starts at the last position before the edit position. (there might be several because of images)
2932 if (it != textNodeMapBegin) {
2933 --it;
2934 TextNode otherDummy(it->startPos());
2935 it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, otherDummy);
2936 }
2937
2938 // mark the affected nodes as dirty
2939 while (it != textNodeMapEnd) {
2940 if (it->startPos() <= end)
2941 it->setDirty();
2942 else if (charDelta)
2943 it->moveStartPos(delta: charDelta);
2944 else
2945 return;
2946 ++it;
2947 }
2948}
2949
2950void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
2951{
2952 Q_D(QQuickTextEdit);
2953
2954 const int editRange = pos + qMax(a: charsAdded, b: charsRemoved);
2955 const int delta = charsAdded - charsRemoved;
2956
2957 markDirtyNodesForRange(start: pos, end: editRange, charDelta: delta);
2958
2959 if (isComponentComplete()) {
2960 polish();
2961 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2962 update();
2963 }
2964}
2965
2966void QQuickTextEdit::moveCursorDelegate()
2967{
2968 Q_D(QQuickTextEdit);
2969#if QT_CONFIG(im)
2970 updateInputMethod();
2971#endif
2972 emit cursorRectangleChanged();
2973 if (!d->cursorItem)
2974 return;
2975 QRectF cursorRect = cursorRectangle();
2976 d->cursorItem->setX(cursorRect.x());
2977 d->cursorItem->setY(cursorRect.y());
2978 d->cursorItem->setHeight(cursorRect.height());
2979}
2980
2981void QQuickTextEdit::updateSelection()
2982{
2983 Q_D(QQuickTextEdit);
2984
2985 // No need for node updates when we go from an empty selection to another empty selection
2986 if (d->control->textCursor().hasSelection() || d->hadSelection) {
2987 markDirtyNodesForRange(start: qMin(a: d->lastSelectionStart, b: d->control->textCursor().selectionStart()), end: qMax(a: d->control->textCursor().selectionEnd(), b: d->lastSelectionEnd), charDelta: 0);
2988 if (isComponentComplete()) {
2989 polish();
2990 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2991 update();
2992 }
2993 }
2994
2995 d->hadSelection = d->control->textCursor().hasSelection();
2996
2997 if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
2998 d->lastSelectionStart = d->control->textCursor().selectionStart();
2999 emit selectionStartChanged();
3000 }
3001 if (d->lastSelectionEnd != d->control->textCursor().selectionEnd()) {
3002 d->lastSelectionEnd = d->control->textCursor().selectionEnd();
3003 emit selectionEndChanged();
3004 }
3005}
3006
3007QRectF QQuickTextEdit::boundingRect() const
3008{
3009 Q_D(const QQuickTextEdit);
3010 QRectF r(
3011 QQuickTextUtil::alignedX(textWidth: d->contentSize.width(), itemWidth: width(), alignment: effectiveHAlign()),
3012 d->yoff,
3013 d->contentSize.width(),
3014 d->contentSize.height());
3015
3016 int cursorWidth = 1;
3017 if (d->cursorItem)
3018 cursorWidth = 0;
3019 else if (!d->document->isEmpty())
3020 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
3021
3022 // Could include font max left/right bearings to either side of rectangle.
3023 r.setRight(r.right() + cursorWidth);
3024
3025 return r;
3026}
3027
3028QRectF QQuickTextEdit::clipRect() const
3029{
3030 Q_D(const QQuickTextEdit);
3031 QRectF r = QQuickImplicitSizeItem::clipRect();
3032 int cursorWidth = 1;
3033 if (d->cursorItem)
3034 cursorWidth = d->cursorItem->width();
3035 if (!d->document->isEmpty())
3036 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
3037
3038 // Could include font max left/right bearings to either side of rectangle.
3039
3040 r.setRight(r.right() + cursorWidth);
3041 return r;
3042}
3043
3044qreal QQuickTextEditPrivate::getImplicitWidth() const
3045{
3046 Q_Q(const QQuickTextEdit);
3047 if (!requireImplicitWidth) {
3048 // We don't calculate implicitWidth unless it is required.
3049 // We need to force a size update now to ensure implicitWidth is calculated
3050 const_cast<QQuickTextEditPrivate*>(this)->requireImplicitWidth = true;
3051 const_cast<QQuickTextEdit*>(q)->updateSize();
3052 }
3053 return implicitWidth;
3054}
3055
3056//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't
3057// need to do all the calculations each time
3058void QQuickTextEdit::updateSize()
3059{
3060 Q_D(QQuickTextEdit);
3061 if (!isComponentComplete()) {
3062 d->dirty = true;
3063 return;
3064 }
3065
3066 // ### assumes that if the width is set, the text will fill to edges
3067 // ### (unless wrap is false, then clipping will occur)
3068 if (widthValid()) {
3069 if (!d->requireImplicitWidth) {
3070 emit implicitWidthChanged();
3071 // if the implicitWidth is used, then updateSize() has already been called (recursively)
3072 if (d->requireImplicitWidth)
3073 return;
3074 }
3075 if (d->requireImplicitWidth) {
3076 d->document->setTextWidth(-1);
3077 const qreal naturalWidth = d->document->idealWidth();
3078 const bool wasInLayout = d->inLayout;
3079 d->inLayout = true;
3080 if (d->isImplicitResizeEnabled())
3081 setImplicitWidth(naturalWidth + leftPadding() + rightPadding());
3082 d->inLayout = wasInLayout;
3083 if (d->inLayout) // probably the result of a binding loop, but by letting it
3084 return; // get this far we'll get a warning to that effect.
3085 }
3086 const qreal newTextWidth = width() - leftPadding() - rightPadding();
3087 if (d->document->textWidth() != newTextWidth)
3088 d->document->setTextWidth(newTextWidth);
3089 } else if (d->wrapMode == NoWrap) {
3090 // normally, if explicit width is not set, we should call setTextWidth(-1) here,
3091 // as we don't need to fit the text to any fixed width. But because of some bug
3092 // in QTextDocument it also breaks RTL text alignment, so we use "idealWidth" instead.
3093 const qreal newTextWidth = d->document->idealWidth();
3094 if (d->document->textWidth() != newTextWidth)
3095 d->document->setTextWidth(newTextWidth);
3096 } else {
3097 d->document->setTextWidth(-1);
3098 }
3099
3100 QFontMetricsF fm(d->font);
3101 const qreal newHeight = d->document->isEmpty() ? qCeil(v: fm.height()) : d->document->size().height();
3102 const qreal newWidth = d->document->idealWidth();
3103
3104 if (d->isImplicitResizeEnabled()) {
3105 // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
3106 if (!widthValid())
3107 setImplicitSize(newWidth + leftPadding() + rightPadding(), newHeight + topPadding() + bottomPadding());
3108 else
3109 setImplicitHeight(newHeight + topPadding() + bottomPadding());
3110 }
3111
3112 d->xoff = leftPadding() + qMax(a: qreal(0), b: QQuickTextUtil::alignedX(textWidth: d->document->size().width(), itemWidth: width() - leftPadding() - rightPadding(), alignment: effectiveHAlign()));
3113 d->yoff = topPadding() + QQuickTextUtil::alignedY(textHeight: d->document->size().height(), itemHeight: height() - topPadding() - bottomPadding(), alignment: d->vAlign);
3114
3115 qreal baseline = fm.ascent();
3116 QTextBlock firstBlock = d->document->firstBlock();
3117 if (firstBlock.isValid() && firstBlock.layout() != nullptr && firstBlock.lineCount() > 0) {
3118 QTextLine firstLine = firstBlock.layout()->lineAt(i: 0);
3119 if (firstLine.isValid())
3120 baseline = firstLine.ascent();
3121 }
3122
3123 setBaselineOffset(baseline + d->yoff + d->textMargin);
3124
3125 QSizeF size(newWidth, newHeight);
3126 if (d->contentSize != size) {
3127 d->contentSize = size;
3128 // Note: inResize is a bitfield so QScopedValueRollback can't be used here
3129 const bool wasInResize = d->inResize;
3130 d->inResize = true;
3131 if (!wasInResize)
3132 emit contentSizeChanged();
3133 d->inResize = wasInResize;
3134 updateTotalLines();
3135 }
3136}
3137
3138void QQuickTextEdit::updateWholeDocument()
3139{
3140 Q_D(QQuickTextEdit);
3141 if (!d->textNodeMap.isEmpty()) {
3142 for (TextNode &node : d->textNodeMap)
3143 node.setDirty();
3144 }
3145
3146 if (isComponentComplete()) {
3147 polish();
3148 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3149 update();
3150 }
3151}
3152
3153void QQuickTextEdit::invalidateBlock(const QTextBlock &block)
3154{
3155 Q_D(QQuickTextEdit);
3156 markDirtyNodesForRange(start: block.position(), end: block.position() + block.length(), charDelta: 0);
3157
3158 if (isComponentComplete()) {
3159 polish();
3160 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3161 update();
3162 }
3163}
3164
3165void QQuickTextEdit::updateCursor()
3166{
3167 Q_D(QQuickTextEdit);
3168 if (isComponentComplete() && isVisible()) {
3169 polish();
3170 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3171 update();
3172 }
3173}
3174
3175void QQuickTextEdit::q_linkHovered(const QString &link)
3176{
3177 Q_D(QQuickTextEdit);
3178 emit linkHovered(link);
3179#if QT_CONFIG(cursor)
3180 if (link.isEmpty()) {
3181 d->updateMouseCursorShape();
3182 } else if (cursor().shape() != Qt::PointingHandCursor) {
3183 setCursor(Qt::PointingHandCursor);
3184 }
3185#endif
3186}
3187
3188void QQuickTextEdit::q_markerHovered(bool hovered)
3189{
3190 Q_D(QQuickTextEdit);
3191#if QT_CONFIG(cursor)
3192 if (!hovered) {
3193 d->updateMouseCursorShape();
3194 } else if (cursor().shape() != Qt::PointingHandCursor) {
3195 setCursor(Qt::PointingHandCursor);
3196 }
3197#endif
3198}
3199
3200void QQuickTextEdit::q_updateAlignment()
3201{
3202 Q_D(QQuickTextEdit);
3203 if (d->determineHorizontalAlignment()) {
3204 d->updateDefaultTextOption();
3205 d->xoff = qMax(a: qreal(0), b: QQuickTextUtil::alignedX(textWidth: d->document->size().width(), itemWidth: width(), alignment: effectiveHAlign()));
3206 moveCursorDelegate();
3207 }
3208}
3209
3210void QQuickTextEdit::updateTotalLines()
3211{
3212 Q_D(QQuickTextEdit);
3213
3214 int subLines = 0;
3215
3216 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
3217 QTextLayout *layout = it.layout();
3218 if (!layout)
3219 continue;
3220 subLines += layout->lineCount()-1;
3221 }
3222
3223 int newTotalLines = d->document->lineCount() + subLines;
3224 if (d->lineCount != newTotalLines) {
3225 d->lineCount = newTotalLines;
3226 emit lineCountChanged();
3227 }
3228}
3229
3230void QQuickTextEditPrivate::updateDefaultTextOption()
3231{
3232 Q_Q(QQuickTextEdit);
3233 QTextOption opt = document->defaultTextOption();
3234 const Qt::Alignment oldAlignment = opt.alignment();
3235 Qt::LayoutDirection oldTextDirection = opt.textDirection();
3236
3237 QQuickTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign();
3238 if (contentDirection == Qt::RightToLeft) {
3239 if (horizontalAlignment == QQuickTextEdit::AlignLeft)
3240 horizontalAlignment = QQuickTextEdit::AlignRight;
3241 else if (horizontalAlignment == QQuickTextEdit::AlignRight)
3242 horizontalAlignment = QQuickTextEdit::AlignLeft;
3243 }
3244 if (!hAlignImplicit)
3245 opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign));
3246 else
3247 opt.setAlignment(Qt::Alignment(vAlign));
3248
3249#if QT_CONFIG(im)
3250 if (contentDirection == Qt::LayoutDirectionAuto) {
3251 opt.setTextDirection(qGuiApp->inputMethod()->inputDirection());
3252 } else
3253#endif
3254 {
3255 opt.setTextDirection(contentDirection);
3256 }
3257
3258 QTextOption::WrapMode oldWrapMode = opt.wrapMode();
3259 opt.setWrapMode(QTextOption::WrapMode(wrapMode));
3260
3261 bool oldUseDesignMetrics = opt.useDesignMetrics();
3262 opt.setUseDesignMetrics(renderType != QQuickTextEdit::NativeRendering);
3263
3264 if (oldWrapMode != opt.wrapMode() || oldAlignment != opt.alignment()
3265 || oldTextDirection != opt.textDirection()
3266 || oldUseDesignMetrics != opt.useDesignMetrics()) {
3267 document->setDefaultTextOption(opt);
3268 }
3269}
3270
3271void QQuickTextEditPrivate::onDocumentStatusChanged()
3272{
3273 Q_ASSERT(quickDocument);
3274 switch (quickDocument->status()) {
3275 case QQuickTextDocument::Status::Loaded:
3276 case QQuickTextDocument::Status::Saved:
3277 switch (QQuickTextDocumentPrivate::get(doc: quickDocument)->detectedFormat) {
3278 case Qt::RichText:
3279 richText = (format == QQuickTextEdit::RichText || format == QQuickTextEdit::AutoText);
3280 markdownText = false;
3281 break;
3282 case Qt::MarkdownText:
3283 richText = false;
3284 markdownText = (format == QQuickTextEdit::MarkdownText || format == QQuickTextEdit::AutoText);
3285 break;
3286 case Qt::PlainText:
3287 richText = false;
3288 markdownText = false;
3289 break;
3290 case Qt::AutoText: // format not detected
3291 break;
3292 }
3293 break;
3294 default:
3295 break;
3296 }
3297}
3298
3299void QQuickTextEdit::focusInEvent(QFocusEvent *event)
3300{
3301 Q_D(QQuickTextEdit);
3302 d->handleFocusEvent(event);
3303 QQuickImplicitSizeItem::focusInEvent(event);
3304}
3305
3306void QQuickTextEdit::focusOutEvent(QFocusEvent *event)
3307{
3308 Q_D(QQuickTextEdit);
3309 d->handleFocusEvent(event);
3310 QQuickImplicitSizeItem::focusOutEvent(event);
3311}
3312
3313void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
3314{
3315 Q_Q(QQuickTextEdit);
3316 bool focus = event->type() == QEvent::FocusIn;
3317 if (!q->isReadOnly())
3318 q->setCursorVisible(focus);
3319 control->processEvent(e: event, coordinateOffset: QPointF(-xoff, -yoff));
3320 if (focus) {
3321 q->q_updateAlignment();
3322#if QT_CONFIG(im)
3323 if (focusOnPress && !q->isReadOnly())
3324 qGuiApp->inputMethod()->show();
3325 q->connect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
3326 receiver: q, SLOT(q_updateAlignment()));
3327#endif
3328 } else {
3329#if QT_CONFIG(im)
3330 q->disconnect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
3331 receiver: q, SLOT(q_updateAlignment()));
3332#endif
3333 if (event->reason() != Qt::ActiveWindowFocusReason
3334 && event->reason() != Qt::PopupFocusReason
3335 && control->textCursor().hasSelection()
3336 && !persistentSelection)
3337 q->deselect();
3338
3339 emit q->editingFinished();
3340 }
3341}
3342
3343void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QSGInternalTextNode *node, TextNodeIterator &it, int startPos)
3344{
3345 engine->addToSceneGraph(parent: node, style: QQuickText::Normal, styleColor: QColor());
3346 it = textNodeMap.insert(before: it, t: TextNode(startPos, node));
3347 ++it;
3348 root->appendChildNode(node);
3349 ++renderedBlockCount;
3350}
3351
3352QSGInternalTextNode *QQuickTextEditPrivate::createTextNode()
3353{
3354 Q_Q(QQuickTextEdit);
3355 QSGInternalTextNode* node = sceneGraphContext()->createInternalTextNode(renderContext: sceneGraphRenderContext());
3356 node->setRenderType(QSGTextNode::RenderType(renderType));
3357 node->setFiltering(q->smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
3358 return node;
3359}
3360
3361void QQuickTextEdit::q_canPasteChanged()
3362{
3363 Q_D(QQuickTextEdit);
3364 bool old = d->canPaste;
3365 d->canPaste = d->control->canPaste();
3366 bool changed = old!=d->canPaste || !d->canPasteValid;
3367 d->canPasteValid = true;
3368 if (changed)
3369 emit canPasteChanged();
3370}
3371
3372/*!
3373 \qmlmethod string QtQuick::TextEdit::getText(int start, int end)
3374
3375 Returns the section of text that is between the \a start and \a end positions.
3376
3377 The returned text does not include any rich text formatting.
3378*/
3379
3380QString QQuickTextEdit::getText(int start, int end) const
3381{
3382 Q_D(const QQuickTextEdit);
3383 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
3384 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
3385 QTextCursor cursor(d->document);
3386 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
3387 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
3388#if QT_CONFIG(texthtmlparser)
3389 return d->richText || d->markdownText
3390 ? cursor.selectedText()
3391 : cursor.selection().toPlainText();
3392#else
3393 return cursor.selection().toPlainText();
3394#endif
3395}
3396
3397/*!
3398 \qmlmethod string QtQuick::TextEdit::getFormattedText(int start, int end)
3399
3400 Returns the section of text that is between the \a start and \a end positions.
3401
3402 The returned text will be formatted according the \l textFormat property.
3403*/
3404
3405QString QQuickTextEdit::getFormattedText(int start, int end) const
3406{
3407 Q_D(const QQuickTextEdit);
3408
3409 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
3410 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
3411
3412 QTextCursor cursor(d->document);
3413 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
3414 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
3415
3416 if (d->richText) {
3417#if QT_CONFIG(texthtmlparser)
3418 return cursor.selection().toHtml();
3419#else
3420 return cursor.selection().toPlainText();
3421#endif
3422 } else if (d->markdownText) {
3423#if QT_CONFIG(textmarkdownwriter)
3424 return cursor.selection().toMarkdown();
3425#else
3426 return cursor.selection().toPlainText();
3427#endif
3428 } else {
3429 return cursor.selection().toPlainText();
3430 }
3431}
3432
3433/*!
3434 \qmlmethod QtQuick::TextEdit::insert(int position, string text)
3435
3436 Inserts \a text into the TextEdit at \a position.
3437*/
3438void QQuickTextEdit::insert(int position, const QString &text)
3439{
3440 Q_D(QQuickTextEdit);
3441 if (position < 0 || position >= d->document->characterCount())
3442 return;
3443 QTextCursor cursor(d->document);
3444 cursor.setPosition(pos: position);
3445 d->richText = d->richText || (d->format == AutoText && Qt::mightBeRichText(text));
3446 if (d->richText) {
3447#if QT_CONFIG(texthtmlparser)
3448 cursor.insertHtml(html: text);
3449#else
3450 cursor.insertText(text);
3451#endif
3452 } else if (d->markdownText) {
3453#if QT_CONFIG(textmarkdownreader)
3454 cursor.insertMarkdown(markdown: text);
3455#else
3456 cursor.insertText(text);
3457#endif
3458 } else {
3459 cursor.insertText(text);
3460 }
3461 d->control->updateCursorRectangle(force: false);
3462}
3463
3464/*!
3465 \qmlmethod string QtQuick::TextEdit::remove(int start, int end)
3466
3467 Removes the section of text that is between the \a start and \a end positions from the TextEdit.
3468*/
3469
3470void QQuickTextEdit::remove(int start, int end)
3471{
3472 Q_D(QQuickTextEdit);
3473 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
3474 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
3475 QTextCursor cursor(d->document);
3476 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
3477 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
3478 cursor.removeSelectedText();
3479 d->control->updateCursorRectangle(force: false);
3480}
3481
3482/*!
3483 \qmlproperty TextDocument QtQuick::TextEdit::textDocument
3484 \since 5.1
3485
3486 Returns the QQuickTextDocument of this TextEdit.
3487 Since Qt 6.7, it has features for loading and saving files.
3488 It can also be used in C++ as a means of accessing the underlying QTextDocument
3489 instance, for example to install a \l QSyntaxHighlighter.
3490
3491 \sa QQuickTextDocument
3492*/
3493
3494QQuickTextDocument *QQuickTextEdit::textDocument()
3495{
3496 Q_D(QQuickTextEdit);
3497 if (!d->quickDocument) {
3498 d->quickDocument = new QQuickTextDocument(this);
3499 connect(sender: d->quickDocument, signal: &QQuickTextDocument::statusChanged, context: d->quickDocument,
3500 slot: [d]() { d->onDocumentStatusChanged(); } );
3501 }
3502 return d->quickDocument;
3503}
3504
3505bool QQuickTextEditPrivate::isLinkHoveredConnected()
3506{
3507 Q_Q(QQuickTextEdit);
3508 IS_SIGNAL_CONNECTED(q, QQuickTextEdit, linkHovered, (const QString &));
3509}
3510
3511#if QT_CONFIG(cursor)
3512void QQuickTextEditPrivate::updateMouseCursorShape()
3513{
3514 Q_Q(QQuickTextEdit);
3515 q->setCursor(q->isReadOnly() && !q->selectByMouse() ? Qt::ArrowCursor : Qt::IBeamCursor);
3516}
3517#endif
3518
3519/*!
3520 \qmlsignal QtQuick::TextEdit::linkHovered(string link)
3521 \since 5.2
3522
3523 This signal is emitted when the user hovers a link embedded in the text.
3524 The link must be in rich text or HTML format and the
3525 \a link string provides access to the particular link.
3526
3527 \sa hoveredLink, linkAt()
3528*/
3529
3530/*!
3531 \qmlsignal QtQuick::TextEdit::editingFinished()
3532 \since 5.6
3533
3534 This signal is emitted when the text edit loses focus.
3535*/
3536
3537/*!
3538 \qmlproperty string QtQuick::TextEdit::hoveredLink
3539 \since 5.2
3540
3541 This property contains the link string when the user hovers a link
3542 embedded in the text. The link must be in rich text or HTML format
3543 and the link string provides access to the particular link.
3544
3545 \sa linkHovered, linkAt()
3546*/
3547
3548QString QQuickTextEdit::hoveredLink() const
3549{
3550 Q_D(const QQuickTextEdit);
3551 if (const_cast<QQuickTextEditPrivate *>(d)->isLinkHoveredConnected()) {
3552 return d->control->hoveredLink();
3553 } else {
3554#if QT_CONFIG(cursor)
3555 if (QQuickWindow *wnd = window()) {
3556 QPointF pos = QCursor::pos(screen: wnd->screen()) - wnd->position() - mapToScene(point: QPointF(0, 0));
3557 return d->control->anchorAt(pos);
3558 }
3559#endif // cursor
3560 }
3561 return QString();
3562}
3563
3564void QQuickTextEdit::hoverEnterEvent(QHoverEvent *event)
3565{
3566 Q_D(QQuickTextEdit);
3567 if (d->isLinkHoveredConnected())
3568 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
3569 event->ignore();
3570}
3571
3572void QQuickTextEdit::hoverMoveEvent(QHoverEvent *event)
3573{
3574 Q_D(QQuickTextEdit);
3575 if (d->isLinkHoveredConnected())
3576 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
3577 event->ignore();
3578}
3579
3580void QQuickTextEdit::hoverLeaveEvent(QHoverEvent *event)
3581{
3582 Q_D(QQuickTextEdit);
3583 if (d->isLinkHoveredConnected())
3584 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
3585 event->ignore();
3586}
3587
3588/*!
3589 \qmlmethod void QtQuick::TextEdit::append(string text)
3590 \since 5.2
3591
3592 Appends a new paragraph with \a text to the end of the TextEdit.
3593
3594 In order to append without inserting a new paragraph,
3595 call \c myTextEdit.insert(myTextEdit.length, text) instead.
3596*/
3597void QQuickTextEdit::append(const QString &text)
3598{
3599 Q_D(QQuickTextEdit);
3600 QTextCursor cursor(d->document);
3601 cursor.beginEditBlock();
3602 cursor.movePosition(op: QTextCursor::End);
3603
3604 if (!d->document->isEmpty())
3605 cursor.insertBlock();
3606
3607 if (d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text))) {
3608#if QT_CONFIG(texthtmlparser)
3609 cursor.insertHtml(html: text);
3610#else
3611 cursor.insertText(text);
3612#endif
3613 } else if (d->format == MarkdownText) {
3614#if QT_CONFIG(textmarkdownreader)
3615 cursor.insertMarkdown(markdown: text);
3616#else
3617 cursor.insertText(text);
3618#endif
3619 } else {
3620 cursor.insertText(text);
3621 }
3622
3623 cursor.endEditBlock();
3624 d->control->updateCursorRectangle(force: false);
3625}
3626
3627/*!
3628 \qmlmethod QtQuick::TextEdit::linkAt(real x, real y)
3629 \since 5.3
3630
3631 Returns the link string at point \a x, \a y in content coordinates,
3632 or an empty string if no link exists at that point.
3633
3634 \sa hoveredLink
3635*/
3636QString QQuickTextEdit::linkAt(qreal x, qreal y) const
3637{
3638 Q_D(const QQuickTextEdit);
3639 return d->control->anchorAt(pos: QPointF(x + topPadding(), y + leftPadding()));
3640}
3641
3642/*!
3643 \since 5.6
3644 \qmlproperty real QtQuick::TextEdit::padding
3645 \qmlproperty real QtQuick::TextEdit::topPadding
3646 \qmlproperty real QtQuick::TextEdit::leftPadding
3647 \qmlproperty real QtQuick::TextEdit::bottomPadding
3648 \qmlproperty real QtQuick::TextEdit::rightPadding
3649
3650 These properties hold the padding around the content. This space is reserved
3651 in addition to the contentWidth and contentHeight.
3652*/
3653qreal QQuickTextEdit::padding() const
3654{
3655 Q_D(const QQuickTextEdit);
3656 return d->padding();
3657}
3658
3659void QQuickTextEdit::setPadding(qreal padding)
3660{
3661 Q_D(QQuickTextEdit);
3662 if (qFuzzyCompare(p1: d->padding(), p2: padding))
3663 return;
3664
3665 d->extra.value().padding = padding;
3666 updateSize();
3667 if (isComponentComplete()) {
3668 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3669 update();
3670 }
3671 emit paddingChanged();
3672 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3673 emit topPaddingChanged();
3674 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3675 emit leftPaddingChanged();
3676 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3677 emit rightPaddingChanged();
3678 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3679 emit bottomPaddingChanged();
3680}
3681
3682void QQuickTextEdit::resetPadding()
3683{
3684 setPadding(0);
3685}
3686
3687qreal QQuickTextEdit::topPadding() const
3688{
3689 Q_D(const QQuickTextEdit);
3690 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3691 return d->extra->topPadding;
3692 return d->padding();
3693}
3694
3695void QQuickTextEdit::setTopPadding(qreal padding)
3696{
3697 Q_D(QQuickTextEdit);
3698 d->setTopPadding(value: padding);
3699}
3700
3701void QQuickTextEdit::resetTopPadding()
3702{
3703 Q_D(QQuickTextEdit);
3704 d->setTopPadding(value: 0, reset: true);
3705}
3706
3707qreal QQuickTextEdit::leftPadding() const
3708{
3709 Q_D(const QQuickTextEdit);
3710 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3711 return d->extra->leftPadding;
3712 return d->padding();
3713}
3714
3715void QQuickTextEdit::setLeftPadding(qreal padding)
3716{
3717 Q_D(QQuickTextEdit);
3718 d->setLeftPadding(value: padding);
3719}
3720
3721void QQuickTextEdit::resetLeftPadding()
3722{
3723 Q_D(QQuickTextEdit);
3724 d->setLeftPadding(value: 0, reset: true);
3725}
3726
3727qreal QQuickTextEdit::rightPadding() const
3728{
3729 Q_D(const QQuickTextEdit);
3730 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3731 return d->extra->rightPadding;
3732 return d->padding();
3733}
3734
3735void QQuickTextEdit::setRightPadding(qreal padding)
3736{
3737 Q_D(QQuickTextEdit);
3738 d->setRightPadding(value: padding);
3739}
3740
3741void QQuickTextEdit::resetRightPadding()
3742{
3743 Q_D(QQuickTextEdit);
3744 d->setRightPadding(value: 0, reset: true);
3745}
3746
3747qreal QQuickTextEdit::bottomPadding() const
3748{
3749 Q_D(const QQuickTextEdit);
3750 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3751 return d->extra->bottomPadding;
3752 return d->padding();
3753}
3754
3755void QQuickTextEdit::setBottomPadding(qreal padding)
3756{
3757 Q_D(QQuickTextEdit);
3758 d->setBottomPadding(value: padding);
3759}
3760
3761void QQuickTextEdit::resetBottomPadding()
3762{
3763 Q_D(QQuickTextEdit);
3764 d->setBottomPadding(value: 0, reset: true);
3765}
3766
3767/*!
3768 \qmlproperty real QtQuick::TextEdit::tabStopDistance
3769 \since 5.10
3770
3771 The default distance, in device units, between tab stops.
3772
3773 \sa QTextOption::setTabStopDistance()
3774*/
3775int QQuickTextEdit::tabStopDistance() const
3776{
3777 Q_D(const QQuickTextEdit);
3778 return d->document->defaultTextOption().tabStopDistance();
3779}
3780
3781void QQuickTextEdit::setTabStopDistance(qreal distance)
3782{
3783 Q_D(QQuickTextEdit);
3784 QTextOption textOptions = d->document->defaultTextOption();
3785 if (textOptions.tabStopDistance() == distance)
3786 return;
3787
3788 textOptions.setTabStopDistance(distance);
3789 d->document->setDefaultTextOption(textOptions);
3790 emit tabStopDistanceChanged(distance);
3791}
3792
3793/*!
3794 \qmlmethod QtQuick::TextEdit::clear()
3795 \since 5.7
3796
3797 Clears the contents of the text edit
3798 and resets partial text input from an input method.
3799
3800 Use this method instead of setting the \l text property to an empty string.
3801
3802 \sa QInputMethod::reset()
3803*/
3804void QQuickTextEdit::clear()
3805{
3806 Q_D(QQuickTextEdit);
3807 d->resetInputMethod();
3808 d->control->clear();
3809}
3810
3811#ifndef QT_NO_DEBUG_STREAM
3812QDebug operator<<(QDebug debug, const QQuickTextEditPrivate::Node &n)
3813{
3814 QDebugStateSaver saver(debug);
3815 debug.space();
3816 debug << "Node(startPos:" << n.m_startPos << "dirty:" << n.m_dirty << n.m_node << ')';
3817 return debug;
3818}
3819#endif
3820
3821#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
3822void QQuickTextEdit::setOldSelectionDefault()
3823{
3824 Q_D(QQuickTextEdit);
3825 d->selectByMouse = false;
3826 setKeepMouseGrab(false);
3827 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
3828 d->control->setTouchDragSelectionEnabled(true);
3829 qCDebug(lcTextEdit, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
3830}
3831
3832// TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
3833QQuickPre64TextEdit::QQuickPre64TextEdit(QQuickItem *parent)
3834 : QQuickTextEdit(parent)
3835{
3836 setOldSelectionDefault();
3837}
3838#endif
3839
3840QT_END_NAMESPACE
3841
3842#include "moc_qquicktextedit_p.cpp"
3843

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/quick/items/qquicktextedit.cpp