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