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