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