1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquicktextedit_p.h"
41#include "qquicktextedit_p_p.h"
42#include "qquicktextcontrol_p.h"
43#include "qquicktextdocument_p.h"
44#include "qquickevents_p_p.h"
45#include "qquickwindow.h"
46#include "qquicktextnode_p.h"
47#include "qquicktextnodeengine_p.h"
48
49#include <QtCore/qmath.h>
50#include <QtGui/qguiapplication.h>
51#include <QtGui/qevent.h>
52#include <QtGui/qpainter.h>
53#include <QtGui/qtextobject.h>
54#include <QtGui/qtexttable.h>
55#include <QtQml/qqmlinfo.h>
56#include <QtQuick/qsgsimplerectnode.h>
57
58#include <private/qqmlglobal_p.h>
59#include <private/qqmlproperty_p.h>
60#include <private/qtextengine_p.h>
61#include <private/qsgadaptationlayer_p.h>
62
63#include "qquicktextdocument.h"
64
65#include <algorithm>
66
67QT_BEGIN_NAMESPACE
68
69/*!
70 \qmltype TextEdit
71 \instantiates QQuickTextEdit
72 \inqmlmodule QtQuick
73 \ingroup qtquick-visual
74 \ingroup qtquick-input
75 \inherits Item
76 \brief Displays multiple lines of editable formatted text.
77
78 The TextEdit item displays a block of editable, formatted text.
79
80 It can display both plain and rich text. For example:
81
82 \qml
83TextEdit {
84 width: 240
85 text: "<b>Hello</b> <i>World!</i>"
86 font.family: "Helvetica"
87 font.pointSize: 20
88 color: "blue"
89 focus: true
90}
91 \endqml
92
93 \image declarative-textedit.gif
94
95 Setting \l {Item::focus}{focus} to \c true enables the TextEdit item to receive keyboard focus.
96
97 Note that the TextEdit does not implement scrolling, following the cursor, or other behaviors specific
98 to a look-and-feel. For example, to add flickable scrolling that follows the cursor:
99
100 \snippet qml/texteditor.qml 0
101
102 A particular look-and-feel might use smooth scrolling (eg. using SmoothedAnimation), might have a visible
103 scrollbar, or a scrollbar that fades in to show location, etc.
104
105 Clipboard support is provided by the cut(), copy(), and paste() functions, and the selection can
106 be handled in a traditional "mouse" mechanism by setting selectByMouse, or handled completely
107 from QML by manipulating selectionStart and selectionEnd, or using selectAll() or selectWord().
108
109 You can translate between cursor positions (characters from the start of the document) and pixel
110 points using positionAt() and positionToRectangle().
111
112 \sa Text, TextInput
113*/
114
115/*!
116 \qmlsignal QtQuick::TextEdit::linkActivated(string link)
117
118 This signal is emitted when the user clicks on a link embedded in the text.
119 The link must be in rich text or HTML format and the
120 \a link string provides access to the particular link.
121*/
122
123// This is a pretty arbitrary figure. The idea is that we don't want to break down the document
124// into text nodes corresponding to a text block each so that the glyph node grouping doesn't become pointless.
125static const int nodeBreakingSize = 300;
126
127namespace {
128 class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
129 {
130 public:
131 inline QTextCharFormat formatAccessor(int pos)
132 {
133 return format(pos);
134 }
135 };
136
137 class RootNode : public QSGTransformNode
138 {
139 public:
140 RootNode() : cursorNode(nullptr), frameDecorationsNode(nullptr)
141 { }
142
143 void resetFrameDecorations(QQuickTextNode* newNode)
144 {
145 if (frameDecorationsNode) {
146 removeChildNode(node: frameDecorationsNode);
147 delete frameDecorationsNode;
148 }
149 frameDecorationsNode = newNode;
150 newNode->setFlag(QSGNode::OwnedByParent);
151 }
152
153 void resetCursorNode(QSGInternalRectangleNode* newNode)
154 {
155 if (cursorNode)
156 removeChildNode(node: cursorNode);
157 delete cursorNode;
158 cursorNode = newNode;
159 if (cursorNode) {
160 appendChildNode(node: cursorNode);
161 cursorNode->setFlag(QSGNode::OwnedByParent);
162 }
163 }
164
165 QSGInternalRectangleNode *cursorNode;
166 QQuickTextNode* frameDecorationsNode;
167
168 };
169}
170
171QQuickTextEdit::QQuickTextEdit(QQuickItem *parent)
172: QQuickImplicitSizeItem(*(new QQuickTextEditPrivate), parent)
173{
174 Q_D(QQuickTextEdit);
175 d->init();
176}
177
178QQuickTextEdit::QQuickTextEdit(QQuickTextEditPrivate &dd, QQuickItem *parent)
179: QQuickImplicitSizeItem(dd, parent)
180{
181 Q_D(QQuickTextEdit);
182 d->init();
183}
184
185QString QQuickTextEdit::text() const
186{
187 Q_D(const QQuickTextEdit);
188 if (!d->textCached && isComponentComplete()) {
189 QQuickTextEditPrivate *d = const_cast<QQuickTextEditPrivate *>(d_func());
190#if QT_CONFIG(texthtmlparser)
191 if (d->richText)
192 d->text = d->control->toHtml();
193 else
194#endif
195#if QT_CONFIG(textmarkdownwriter)
196 if (d->markdownText)
197 d->text = d->control->toMarkdown();
198 else
199#endif
200 d->text = d->control->toPlainText();
201 d->textCached = true;
202 }
203 return d->text;
204}
205
206/*!
207 \qmlproperty string QtQuick::TextEdit::font.family
208
209 Sets the family name of the font.
210
211 The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
212 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
213 If the family isn't available a family will be set using the font matching algorithm.
214*/
215
216/*!
217 \qmlproperty string QtQuick::TextEdit::font.styleName
218 \since 5.6
219
220 Sets the style name of the font.
221
222 The style name is case insensitive. If set, the font will be matched against style name instead
223 of the font properties \l font.weight, \l font.bold and \l font.italic.
224*/
225
226
227/*!
228 \qmlproperty bool QtQuick::TextEdit::font.bold
229
230 Sets whether the font weight is bold.
231*/
232
233/*!
234 \qmlproperty enumeration QtQuick::TextEdit::font.weight
235
236 Sets the font's weight.
237
238 The weight can be one of:
239 \list
240 \li Font.Thin
241 \li Font.Light
242 \li Font.ExtraLight
243 \li Font.Normal - the default
244 \li Font.Medium
245 \li Font.DemiBold
246 \li Font.Bold
247 \li Font.ExtraBold
248 \li Font.Black
249 \endlist
250
251 \qml
252 TextEdit { text: "Hello"; font.weight: Font.DemiBold }
253 \endqml
254*/
255
256/*!
257 \qmlproperty bool QtQuick::TextEdit::font.italic
258
259 Sets whether the font has an italic style.
260*/
261
262/*!
263 \qmlproperty bool QtQuick::TextEdit::font.underline
264
265 Sets whether the text is underlined.
266*/
267
268/*!
269 \qmlproperty bool QtQuick::TextEdit::font.strikeout
270
271 Sets whether the font has a strikeout style.
272*/
273
274/*!
275 \qmlproperty real QtQuick::TextEdit::font.pointSize
276
277 Sets the font size in points. The point size must be greater than zero.
278*/
279
280/*!
281 \qmlproperty int QtQuick::TextEdit::font.pixelSize
282
283 Sets the font size in pixels.
284
285 Using this function makes the font device dependent. Use
286 \l{TextEdit::font.pointSize} to set the size of the font in a
287 device independent manner.
288*/
289
290/*!
291 \qmlproperty real QtQuick::TextEdit::font.letterSpacing
292
293 Sets the letter spacing for the font.
294
295 Letter spacing changes the default spacing between individual letters in the font.
296 A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
297*/
298
299/*!
300 \qmlproperty real QtQuick::TextEdit::font.wordSpacing
301
302 Sets the word spacing for the font.
303
304 Word spacing changes the default spacing between individual words.
305 A positive value increases the word spacing by a corresponding amount of pixels,
306 while a negative value decreases the inter-word spacing accordingly.
307*/
308
309/*!
310 \qmlproperty enumeration QtQuick::TextEdit::font.capitalization
311
312 Sets the capitalization for the text.
313
314 \list
315 \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
316 \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
317 \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
318 \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
319 \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
320 \endlist
321
322 \qml
323 TextEdit { text: "Hello"; font.capitalization: Font.AllLowercase }
324 \endqml
325*/
326
327/*!
328 \qmlproperty enumeration QtQuick::TextEdit::font.hintingPreference
329 \since 5.8
330
331 Sets the preferred hinting on the text. This is a hint to the underlying text rendering system
332 to use a certain level of hinting, and has varying support across platforms. See the table in
333 the documentation for QFont::HintingPreference for more details.
334
335 \note This property only has an effect when used together with render type TextEdit.NativeRendering.
336
337 \list
338 \value Font.PreferDefaultHinting - Use the default hinting level for the target platform.
339 \value Font.PreferNoHinting - If possible, render text without hinting the outlines
340 of the glyphs. The text layout will be typographically accurate, using the same metrics
341 as are used e.g. when printing.
342 \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
343 but align glyphs to the pixel grid in the vertical direction. The text will appear
344 crisper on displays where the density is too low to give an accurate rendering
345 of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
346 layout will be scalable to higher density devices (such as printers) without impacting
347 details such as line breaks.
348 \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
349 vertical directions. The text will be altered to optimize legibility on the target
350 device, but since the metrics will depend on the target size of the text, the positions
351 of glyphs, line breaks, and other typographical detail will not scale, meaning that a
352 text layout may look different on devices with different pixel densities.
353 \endlist
354
355 \qml
356 TextEdit { text: "Hello"; renderType: TextEdit.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
357 \endqml
358*/
359
360/*!
361 \qmlproperty bool QtQuick::TextEdit::font.kerning
362 \since 5.10
363
364 Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
365 improve performance when creating or changing the text, at the expense of some cosmetic
366 features. The default value is true.
367
368 \qml
369 TextEdit { text: "OATS FLAVOUR WAY"; kerning: font.false }
370 \endqml
371*/
372
373/*!
374 \qmlproperty bool QtQuick::TextEdit::font.preferShaping
375 \since 5.10
376
377 Sometimes, a font will apply complex rules to a set of characters in order to
378 display them correctly. In some writing systems, such as Brahmic scripts, this is
379 required in order for the text to be legible, but in e.g. Latin script, it is merely
380 a cosmetic feature. Setting the \c preferShaping property to false will disable all
381 such features when they are not required, which will improve performance in most cases.
382
383 The default value is true.
384
385 \qml
386 TextEdit { text: "Some text"; font.preferShaping: false }
387 \endqml
388*/
389
390/*!
391 \qmlproperty string QtQuick::TextEdit::text
392
393 The text to display. If the text format is AutoText the text edit will
394 automatically determine whether the text should be treated as
395 rich text. This determination is made using Qt::mightBeRichText().
396 However, detection of Markdown is not automatic.
397
398 The text-property is mostly suitable for setting the initial content and
399 handling modifications to relatively small text content. The append(),
400 insert() and remove() methods provide more fine-grained control and
401 remarkably better performance for modifying especially large rich text
402 content.
403
404 \sa clear(), textFormat
405*/
406void QQuickTextEdit::setText(const QString &text)
407{
408 Q_D(QQuickTextEdit);
409 if (QQuickTextEdit::text() == text)
410 return;
411
412 d->document->clearResources();
413 d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text));
414 d->markdownText = d->format == MarkdownText;
415 if (!isComponentComplete()) {
416 d->text = text;
417 } else if (d->richText) {
418#if QT_CONFIG(texthtmlparser)
419 d->control->setHtml(text);
420#else
421 d->control->setPlainText(text);
422#endif
423 } else if (d->markdownText) {
424 d->control->setMarkdownText(text);
425 } else {
426 d->control->setPlainText(text);
427 }
428}
429
430/*!
431 \qmlproperty string QtQuick::TextEdit::preeditText
432 \readonly
433 \since 5.7
434
435 This property contains partial text input from an input method.
436*/
437QString QQuickTextEdit::preeditText() const
438{
439 Q_D(const QQuickTextEdit);
440 return d->control->preeditText();
441}
442
443/*!
444 \qmlproperty enumeration QtQuick::TextEdit::textFormat
445
446 The way the \l text property should be displayed.
447
448 Supported text formats are:
449
450 \value TextEdit.PlainText (default) all styling tags are treated as plain text
451 \value TextEdit.AutoText detected via the Qt::mightBeRichText() heuristic
452 \value TextEdit.RichText \l {Supported HTML Subset} {a subset of HTML 4}
453 \value TextEdit.MarkdownText \l {https://commonmark.org/help/}{CommonMark} plus the
454 \l {https://guides.github.com/features/mastering-markdown/}{GitHub}
455 extensions for tables and task lists (since 5.14)
456
457 The default is \c TextEdit.PlainText. If the text format is set to
458 \c TextEdit.AutoText, the text edit will automatically determine whether
459 the text should be treated as rich text. This determination is made using
460 Qt::mightBeRichText(), which can detect the presence of an HTML tag on the
461 first line of text, but cannot distinguish Markdown from plain text.
462
463 \table
464 \row
465 \li
466 \snippet qml/text/textEditFormats.qml 0
467 \li \image declarative-textformat.png
468 \endtable
469
470 With \c TextEdit.MarkdownText, checkboxes that result from using the
471 \l {https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown}{GitHub checkbox extension}
472 are interactively checkable.
473
474 \note Interactively typing markup or markdown formatting is not supported.
475
476 \note With \c Text.MarkdownText, and with the supported subset of HTML,
477 some decorative elements are not rendered as they would be in a web browser:
478 \list
479 \li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
480 \li block quotes are indented, but there is no vertical line alongside the quote
481 \li horizontal rules are not rendered
482 \endlist
483*/
484QQuickTextEdit::TextFormat QQuickTextEdit::textFormat() const
485{
486 Q_D(const QQuickTextEdit);
487 return d->format;
488}
489
490void QQuickTextEdit::setTextFormat(TextFormat format)
491{
492 Q_D(QQuickTextEdit);
493 if (format == d->format)
494 return;
495
496 bool wasRich = d->richText;
497 d->richText = format == RichText || (format == AutoText && (wasRich || Qt::mightBeRichText(text())));
498 d->markdownText = format == MarkdownText;
499
500#if QT_CONFIG(texthtmlparser)
501 if (isComponentComplete()) {
502 if (wasRich && !d->richText) {
503 d->control->setPlainText(!d->textCached ? d->control->toHtml() : d->text);
504 updateSize();
505 } else if (!wasRich && d->richText) {
506 d->control->setHtml(!d->textCached ? d->control->toPlainText() : d->text);
507 updateSize();
508 }
509 }
510#endif
511
512 d->format = format;
513 d->control->setAcceptRichText(d->format != PlainText);
514 emit textFormatChanged(textFormat: d->format);
515}
516
517/*!
518 \qmlproperty enumeration QtQuick::TextEdit::renderType
519
520 Override the default rendering type for this component.
521
522 Supported render types are:
523 \list
524 \li Text.QtRendering
525 \li Text.NativeRendering
526 \endlist
527
528 Select Text.NativeRendering if you prefer text to look native on the target platform and do
529 not require advanced features such as transformation of the text. Using such features in
530 combination with the NativeRendering render type will lend poor and sometimes pixelated
531 results.
532
533 The default rendering type is determined by \l QQuickWindow::textRenderType().
534*/
535QQuickTextEdit::RenderType QQuickTextEdit::renderType() const
536{
537 Q_D(const QQuickTextEdit);
538 return d->renderType;
539}
540
541void QQuickTextEdit::setRenderType(QQuickTextEdit::RenderType renderType)
542{
543 Q_D(QQuickTextEdit);
544 if (d->renderType == renderType)
545 return;
546
547 d->renderType = renderType;
548 emit renderTypeChanged();
549 d->updateDefaultTextOption();
550
551 if (isComponentComplete())
552 updateSize();
553}
554
555QFont QQuickTextEdit::font() const
556{
557 Q_D(const QQuickTextEdit);
558 return d->sourceFont;
559}
560
561void QQuickTextEdit::setFont(const QFont &font)
562{
563 Q_D(QQuickTextEdit);
564 if (d->sourceFont == font)
565 return;
566
567 d->sourceFont = font;
568 QFont oldFont = d->font;
569 d->font = font;
570 if (d->font.pointSizeF() != -1) {
571 // 0.5pt resolution
572 qreal size = qRound(d: d->font.pointSizeF()*2.0);
573 d->font.setPointSizeF(size/2.0);
574 }
575
576 if (oldFont != d->font) {
577 d->document->setDefaultFont(d->font);
578 if (d->cursorItem) {
579 d->cursorItem->setHeight(QFontMetrics(d->font).height());
580 moveCursorDelegate();
581 }
582 updateSize();
583 updateWholeDocument();
584#if QT_CONFIG(im)
585 updateInputMethod(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle | Qt::ImFont);
586#endif
587 }
588 emit fontChanged(font: d->sourceFont);
589}
590
591/*!
592 \qmlproperty color QtQuick::TextEdit::color
593
594 The text color.
595
596 \qml
597 // green text using hexadecimal notation
598 TextEdit { color: "#00FF00" }
599 \endqml
600
601 \qml
602 // steelblue text using SVG color name
603 TextEdit { color: "steelblue" }
604 \endqml
605*/
606QColor QQuickTextEdit::color() const
607{
608 Q_D(const QQuickTextEdit);
609 return d->color;
610}
611
612void QQuickTextEdit::setColor(const QColor &color)
613{
614 Q_D(QQuickTextEdit);
615 if (d->color == color)
616 return;
617
618 d->color = color;
619 updateWholeDocument();
620 emit colorChanged(color: d->color);
621}
622
623/*!
624 \qmlproperty color QtQuick::TextEdit::selectionColor
625
626 The text highlight color, used behind selections.
627*/
628QColor QQuickTextEdit::selectionColor() const
629{
630 Q_D(const QQuickTextEdit);
631 return d->selectionColor;
632}
633
634void QQuickTextEdit::setSelectionColor(const QColor &color)
635{
636 Q_D(QQuickTextEdit);
637 if (d->selectionColor == color)
638 return;
639
640 d->selectionColor = color;
641 updateWholeDocument();
642 emit selectionColorChanged(color: d->selectionColor);
643}
644
645/*!
646 \qmlproperty color QtQuick::TextEdit::selectedTextColor
647
648 The selected text color, used in selections.
649*/
650QColor QQuickTextEdit::selectedTextColor() const
651{
652 Q_D(const QQuickTextEdit);
653 return d->selectedTextColor;
654}
655
656void QQuickTextEdit::setSelectedTextColor(const QColor &color)
657{
658 Q_D(QQuickTextEdit);
659 if (d->selectedTextColor == color)
660 return;
661
662 d->selectedTextColor = color;
663 updateWholeDocument();
664 emit selectedTextColorChanged(color: d->selectedTextColor);
665}
666
667/*!
668 \qmlproperty enumeration QtQuick::TextEdit::horizontalAlignment
669 \qmlproperty enumeration QtQuick::TextEdit::verticalAlignment
670 \qmlproperty enumeration QtQuick::TextEdit::effectiveHorizontalAlignment
671
672 Sets the horizontal and vertical alignment of the text within the TextEdit item's
673 width and height. By default, the text alignment follows the natural alignment
674 of the text, for example text that is read from left to right will be aligned to
675 the left.
676
677 Valid values for \c horizontalAlignment are:
678 \list
679 \li TextEdit.AlignLeft (default)
680 \li TextEdit.AlignRight
681 \li TextEdit.AlignHCenter
682 \li TextEdit.AlignJustify
683 \endlist
684
685 Valid values for \c verticalAlignment are:
686 \list
687 \li TextEdit.AlignTop (default)
688 \li TextEdit.AlignBottom
689 \li TextEdit.AlignVCenter
690 \endlist
691
692 When using the attached property LayoutMirroring::enabled to mirror application
693 layouts, the horizontal alignment of text will also be mirrored. However, the property
694 \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
695 of TextEdit, use the read-only property \c effectiveHorizontalAlignment.
696*/
697QQuickTextEdit::HAlignment QQuickTextEdit::hAlign() const
698{
699 Q_D(const QQuickTextEdit);
700 return d->hAlign;
701}
702
703void QQuickTextEdit::setHAlign(HAlignment align)
704{
705 Q_D(QQuickTextEdit);
706 bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
707 d->hAlignImplicit = false;
708 if (d->setHAlign(align, forceAlign) && isComponentComplete()) {
709 d->updateDefaultTextOption();
710 updateSize();
711 }
712}
713
714void QQuickTextEdit::resetHAlign()
715{
716 Q_D(QQuickTextEdit);
717 d->hAlignImplicit = true;
718 if (d->determineHorizontalAlignment() && isComponentComplete()) {
719 d->updateDefaultTextOption();
720 updateSize();
721 }
722}
723
724QQuickTextEdit::HAlignment QQuickTextEdit::effectiveHAlign() const
725{
726 Q_D(const QQuickTextEdit);
727 QQuickTextEdit::HAlignment effectiveAlignment = d->hAlign;
728 if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
729 switch (d->hAlign) {
730 case QQuickTextEdit::AlignLeft:
731 effectiveAlignment = QQuickTextEdit::AlignRight;
732 break;
733 case QQuickTextEdit::AlignRight:
734 effectiveAlignment = QQuickTextEdit::AlignLeft;
735 break;
736 default:
737 break;
738 }
739 }
740 return effectiveAlignment;
741}
742
743bool QQuickTextEditPrivate::setHAlign(QQuickTextEdit::HAlignment alignment, bool forceAlign)
744{
745 Q_Q(QQuickTextEdit);
746 if (hAlign != alignment || forceAlign) {
747 QQuickTextEdit::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
748 hAlign = alignment;
749 emit q->horizontalAlignmentChanged(alignment);
750 if (oldEffectiveHAlign != q->effectiveHAlign())
751 emit q->effectiveHorizontalAlignmentChanged();
752 return true;
753 }
754 return false;
755}
756
757
758Qt::LayoutDirection QQuickTextEditPrivate::textDirection(const QString &text) const
759{
760 const QChar *character = text.constData();
761 while (!character->isNull()) {
762 switch (character->direction()) {
763 case QChar::DirL:
764 return Qt::LeftToRight;
765 case QChar::DirR:
766 case QChar::DirAL:
767 case QChar::DirAN:
768 return Qt::RightToLeft;
769 default:
770 break;
771 }
772 character++;
773 }
774 return Qt::LayoutDirectionAuto;
775}
776
777bool QQuickTextEditPrivate::determineHorizontalAlignment()
778{
779 Q_Q(QQuickTextEdit);
780 if (hAlignImplicit && q->isComponentComplete()) {
781 Qt::LayoutDirection direction = contentDirection;
782#if QT_CONFIG(im)
783 if (direction == Qt::LayoutDirectionAuto) {
784 const QString preeditText = control->textCursor().block().layout()->preeditAreaText();
785 direction = textDirection(text: preeditText);
786 }
787 if (direction == Qt::LayoutDirectionAuto)
788 direction = qGuiApp->inputMethod()->inputDirection();
789#endif
790
791 return setHAlign(alignment: direction == Qt::RightToLeft ? QQuickTextEdit::AlignRight : QQuickTextEdit::AlignLeft);
792 }
793 return false;
794}
795
796void QQuickTextEditPrivate::mirrorChange()
797{
798 Q_Q(QQuickTextEdit);
799 if (q->isComponentComplete()) {
800 if (!hAlignImplicit && (hAlign == QQuickTextEdit::AlignRight || hAlign == QQuickTextEdit::AlignLeft)) {
801 updateDefaultTextOption();
802 q->updateSize();
803 emit q->effectiveHorizontalAlignmentChanged();
804 }
805 }
806}
807
808#if QT_CONFIG(im)
809Qt::InputMethodHints QQuickTextEditPrivate::effectiveInputMethodHints() const
810{
811 return inputMethodHints | Qt::ImhMultiLine;
812}
813#endif
814
815void QQuickTextEditPrivate::setTopPadding(qreal value, bool reset)
816{
817 Q_Q(QQuickTextEdit);
818 qreal oldPadding = q->topPadding();
819 if (!reset || extra.isAllocated()) {
820 extra.value().topPadding = value;
821 extra.value().explicitTopPadding = !reset;
822 }
823 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
824 q->updateSize();
825 q->updateWholeDocument();
826 emit q->topPaddingChanged();
827 }
828}
829
830void QQuickTextEditPrivate::setLeftPadding(qreal value, bool reset)
831{
832 Q_Q(QQuickTextEdit);
833 qreal oldPadding = q->leftPadding();
834 if (!reset || extra.isAllocated()) {
835 extra.value().leftPadding = value;
836 extra.value().explicitLeftPadding = !reset;
837 }
838 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
839 q->updateSize();
840 q->updateWholeDocument();
841 emit q->leftPaddingChanged();
842 }
843}
844
845void QQuickTextEditPrivate::setRightPadding(qreal value, bool reset)
846{
847 Q_Q(QQuickTextEdit);
848 qreal oldPadding = q->rightPadding();
849 if (!reset || extra.isAllocated()) {
850 extra.value().rightPadding = value;
851 extra.value().explicitRightPadding = !reset;
852 }
853 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
854 q->updateSize();
855 q->updateWholeDocument();
856 emit q->rightPaddingChanged();
857 }
858}
859
860void QQuickTextEditPrivate::setBottomPadding(qreal value, bool reset)
861{
862 Q_Q(QQuickTextEdit);
863 qreal oldPadding = q->bottomPadding();
864 if (!reset || extra.isAllocated()) {
865 extra.value().bottomPadding = value;
866 extra.value().explicitBottomPadding = !reset;
867 }
868 if ((!reset && !qFuzzyCompare(p1: oldPadding, p2: value)) || (reset && !qFuzzyCompare(p1: oldPadding, p2: padding()))) {
869 q->updateSize();
870 q->updateWholeDocument();
871 emit q->bottomPaddingChanged();
872 }
873}
874
875bool QQuickTextEditPrivate::isImplicitResizeEnabled() const
876{
877 return !extra.isAllocated() || extra->implicitResize;
878}
879
880void QQuickTextEditPrivate::setImplicitResizeEnabled(bool enabled)
881{
882 if (!enabled)
883 extra.value().implicitResize = false;
884 else if (extra.isAllocated())
885 extra->implicitResize = true;
886}
887
888QQuickTextEdit::VAlignment QQuickTextEdit::vAlign() const
889{
890 Q_D(const QQuickTextEdit);
891 return d->vAlign;
892}
893
894void QQuickTextEdit::setVAlign(QQuickTextEdit::VAlignment alignment)
895{
896 Q_D(QQuickTextEdit);
897 if (alignment == d->vAlign)
898 return;
899 d->vAlign = alignment;
900 d->updateDefaultTextOption();
901 updateSize();
902 moveCursorDelegate();
903 emit verticalAlignmentChanged(alignment: d->vAlign);
904}
905/*!
906 \qmlproperty enumeration QtQuick::TextEdit::wrapMode
907
908 Set this property to wrap the text to the TextEdit item's width.
909 The text will only wrap if an explicit width has been set.
910
911 \list
912 \li TextEdit.NoWrap - no wrapping will be performed. If the text contains insufficient newlines, then implicitWidth will exceed a set width.
913 \li TextEdit.WordWrap - wrapping is done on word boundaries only. If a word is too long, implicitWidth will exceed a set width.
914 \li TextEdit.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
915 \li TextEdit.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
916 \endlist
917
918 The default is TextEdit.NoWrap. If you set a width, consider using TextEdit.Wrap.
919*/
920QQuickTextEdit::WrapMode QQuickTextEdit::wrapMode() const
921{
922 Q_D(const QQuickTextEdit);
923 return d->wrapMode;
924}
925
926void QQuickTextEdit::setWrapMode(WrapMode mode)
927{
928 Q_D(QQuickTextEdit);
929 if (mode == d->wrapMode)
930 return;
931 d->wrapMode = mode;
932 d->updateDefaultTextOption();
933 updateSize();
934 emit wrapModeChanged();
935}
936
937/*!
938 \qmlproperty int QtQuick::TextEdit::lineCount
939
940 Returns the total number of lines in the TextEdit item.
941*/
942int QQuickTextEdit::lineCount() const
943{
944 Q_D(const QQuickTextEdit);
945 return d->lineCount;
946}
947
948/*!
949 \qmlproperty int QtQuick::TextEdit::length
950
951 Returns the total number of plain text characters in the TextEdit item.
952
953 As this number doesn't include any formatting markup it may not be the same as the
954 length of the string returned by the \l text property.
955
956 This property can be faster than querying the length the \l text property as it doesn't
957 require any copying or conversion of the TextEdit's internal string data.
958*/
959
960int QQuickTextEdit::length() const
961{
962 Q_D(const QQuickTextEdit);
963 // QTextDocument::characterCount() includes the terminating null character.
964 return qMax(a: 0, b: d->document->characterCount() - 1);
965}
966
967/*!
968 \qmlproperty real QtQuick::TextEdit::contentWidth
969
970 Returns the width of the text, including the width past the width
971 which is covered due to insufficient wrapping if \l wrapMode is set.
972*/
973qreal QQuickTextEdit::contentWidth() const
974{
975 Q_D(const QQuickTextEdit);
976 return d->contentSize.width();
977}
978
979/*!
980 \qmlproperty real QtQuick::TextEdit::contentHeight
981
982 Returns the height of the text, including the height past the height
983 that is covered if the text does not fit within the set height.
984*/
985qreal QQuickTextEdit::contentHeight() const
986{
987 Q_D(const QQuickTextEdit);
988 return d->contentSize.height();
989}
990
991/*!
992 \qmlproperty url QtQuick::TextEdit::baseUrl
993
994 This property specifies a base URL which is used to resolve relative URLs
995 within the text.
996
997 The default value is the url of the QML file instantiating the TextEdit item.
998*/
999
1000QUrl QQuickTextEdit::baseUrl() const
1001{
1002 Q_D(const QQuickTextEdit);
1003 if (d->baseUrl.isEmpty()) {
1004 if (QQmlContext *context = qmlContext(this))
1005 const_cast<QQuickTextEditPrivate *>(d)->baseUrl = context->baseUrl();
1006 }
1007 return d->baseUrl;
1008}
1009
1010void QQuickTextEdit::setBaseUrl(const QUrl &url)
1011{
1012 Q_D(QQuickTextEdit);
1013 if (baseUrl() != url) {
1014 d->baseUrl = url;
1015
1016 d->document->setBaseUrl(url);
1017 emit baseUrlChanged();
1018 }
1019}
1020
1021void QQuickTextEdit::resetBaseUrl()
1022{
1023 if (QQmlContext *context = qmlContext(this))
1024 setBaseUrl(context->baseUrl());
1025 else
1026 setBaseUrl(QUrl());
1027}
1028
1029/*!
1030 \qmlmethod rectangle QtQuick::TextEdit::positionToRectangle(position)
1031
1032 Returns the rectangle at the given \a position in the text. The x, y,
1033 and height properties correspond to the cursor that would describe
1034 that position.
1035*/
1036QRectF QQuickTextEdit::positionToRectangle(int pos) const
1037{
1038 Q_D(const QQuickTextEdit);
1039 QTextCursor c(d->document);
1040 c.setPosition(pos);
1041 return d->control->cursorRect(cursor: c).translated(dx: d->xoff, dy: d->yoff);
1042
1043}
1044
1045/*!
1046 \qmlmethod int QtQuick::TextEdit::positionAt(int x, int y)
1047
1048 Returns the text position closest to pixel position (\a x, \a y).
1049
1050 Position 0 is before the first character, position 1 is after the first character
1051 but before the second, and so on until position \l {text}.length, which is after all characters.
1052*/
1053int QQuickTextEdit::positionAt(qreal x, qreal y) const
1054{
1055 Q_D(const QQuickTextEdit);
1056 x -= d->xoff;
1057 y -= d->yoff;
1058
1059 int r = d->document->documentLayout()->hitTest(point: QPointF(x, y), accuracy: Qt::FuzzyHit);
1060#if QT_CONFIG(im)
1061 QTextCursor cursor = d->control->textCursor();
1062 if (r > cursor.position()) {
1063 // The cursor position includes positions within the preedit text, but only positions in the
1064 // same text block are offset so it is possible to get a position that is either part of the
1065 // preedit or the next text block.
1066 QTextLayout *layout = cursor.block().layout();
1067 const int preeditLength = layout
1068 ? layout->preeditAreaText().length()
1069 : 0;
1070 if (preeditLength > 0
1071 && d->document->documentLayout()->blockBoundingRect(block: cursor.block()).contains(ax: x, ay: y)) {
1072 r = r > cursor.position() + preeditLength
1073 ? r - preeditLength
1074 : cursor.position();
1075 }
1076 }
1077#endif
1078 return r;
1079}
1080
1081/*!
1082 \qmlmethod QtQuick::TextEdit::moveCursorSelection(int position, SelectionMode mode)
1083
1084 Moves the cursor to \a position and updates the selection according to the optional \a mode
1085 parameter. (To only move the cursor, set the \l cursorPosition property.)
1086
1087 When this method is called it additionally sets either the
1088 selectionStart or the selectionEnd (whichever was at the previous cursor position)
1089 to the specified position. This allows you to easily extend and contract the selected
1090 text range.
1091
1092 The selection mode specifies whether the selection is updated on a per character or a per word
1093 basis. If not specified the selection mode will default to \c {TextEdit.SelectCharacters}.
1094
1095 \list
1096 \li TextEdit.SelectCharacters - Sets either the selectionStart or selectionEnd (whichever was at
1097 the previous cursor position) to the specified position.
1098 \li TextEdit.SelectWords - Sets the selectionStart and selectionEnd to include all
1099 words between the specified position and the previous cursor position. Words partially in the
1100 range are included.
1101 \endlist
1102
1103 For example, take this sequence of calls:
1104
1105 \code
1106 cursorPosition = 5
1107 moveCursorSelection(9, TextEdit.SelectCharacters)
1108 moveCursorSelection(7, TextEdit.SelectCharacters)
1109 \endcode
1110
1111 This moves the cursor to position 5, extend the selection end from 5 to 9
1112 and then retract the selection end from 9 to 7, leaving the text from position 5 to 7
1113 selected (the 6th and 7th characters).
1114
1115 The same sequence with TextEdit.SelectWords will extend the selection start to a word boundary
1116 before or on position 5 and extend the selection end to a word boundary on or past position 9.
1117*/
1118void QQuickTextEdit::moveCursorSelection(int pos)
1119{
1120 //Note that this is the same as setCursorPosition but with the KeepAnchor flag set
1121 Q_D(QQuickTextEdit);
1122 QTextCursor cursor = d->control->textCursor();
1123 if (cursor.position() == pos)
1124 return;
1125 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1126 d->control->setTextCursor(cursor);
1127}
1128
1129void QQuickTextEdit::moveCursorSelection(int pos, SelectionMode mode)
1130{
1131 Q_D(QQuickTextEdit);
1132 QTextCursor cursor = d->control->textCursor();
1133 if (cursor.position() == pos)
1134 return;
1135 if (mode == SelectCharacters) {
1136 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1137 } else if (cursor.anchor() < pos || (cursor.anchor() == pos && cursor.position() < pos)) {
1138 if (cursor.anchor() > cursor.position()) {
1139 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1140 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1141 if (cursor.position() == cursor.anchor())
1142 cursor.movePosition(op: QTextCursor::PreviousWord, QTextCursor::MoveAnchor);
1143 else
1144 cursor.setPosition(pos: cursor.position(), mode: QTextCursor::MoveAnchor);
1145 } else {
1146 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1147 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
1148 }
1149
1150 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1151 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1152 if (cursor.position() != pos)
1153 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1154 } else if (cursor.anchor() > pos || (cursor.anchor() == pos && cursor.position() > pos)) {
1155 if (cursor.anchor() < cursor.position()) {
1156 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1157 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1158 } else {
1159 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1160 cursor.movePosition(op: QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1161 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1162 if (cursor.position() != cursor.anchor()) {
1163 cursor.setPosition(pos: cursor.anchor(), mode: QTextCursor::MoveAnchor);
1164 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
1165 }
1166 }
1167
1168 cursor.setPosition(pos, mode: QTextCursor::KeepAnchor);
1169 cursor.movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
1170 if (cursor.position() != pos) {
1171 cursor.movePosition(op: QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
1172 cursor.movePosition(op: QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
1173 }
1174 }
1175 d->control->setTextCursor(cursor);
1176}
1177
1178/*!
1179 \qmlproperty bool QtQuick::TextEdit::cursorVisible
1180 If true the text edit shows a cursor.
1181
1182 This property is set and unset when the text edit gets active focus, but it can also
1183 be set directly (useful, for example, if a KeyProxy might forward keys to it).
1184*/
1185bool QQuickTextEdit::isCursorVisible() const
1186{
1187 Q_D(const QQuickTextEdit);
1188 return d->cursorVisible;
1189}
1190
1191void QQuickTextEdit::setCursorVisible(bool on)
1192{
1193 Q_D(QQuickTextEdit);
1194 if (d->cursorVisible == on)
1195 return;
1196 d->cursorVisible = on;
1197 if (on && isComponentComplete())
1198 QQuickTextUtil::createCursor(d);
1199 if (!on && !d->persistentSelection)
1200 d->control->setCursorIsFocusIndicator(true);
1201 d->control->setCursorVisible(on);
1202 emit cursorVisibleChanged(isCursorVisible: d->cursorVisible);
1203}
1204
1205/*!
1206 \qmlproperty int QtQuick::TextEdit::cursorPosition
1207 The position of the cursor in the TextEdit.
1208*/
1209int QQuickTextEdit::cursorPosition() const
1210{
1211 Q_D(const QQuickTextEdit);
1212 return d->control->textCursor().position();
1213}
1214
1215void QQuickTextEdit::setCursorPosition(int pos)
1216{
1217 Q_D(QQuickTextEdit);
1218 if (pos < 0 || pos >= d->document->characterCount()) // characterCount includes the terminating null.
1219 return;
1220 QTextCursor cursor = d->control->textCursor();
1221 if (cursor.position() == pos && cursor.anchor() == pos)
1222 return;
1223 cursor.setPosition(pos);
1224 d->control->setTextCursor(cursor);
1225 d->control->updateCursorRectangle(force: true);
1226}
1227
1228/*!
1229 \qmlproperty Component QtQuick::TextEdit::cursorDelegate
1230 The delegate for the cursor in the TextEdit.
1231
1232 If you set a cursorDelegate for a TextEdit, this delegate will be used for
1233 drawing the cursor instead of the standard cursor. An instance of the
1234 delegate will be created and managed by the text edit when a cursor is
1235 needed, and the x and y properties of delegate instance will be set so as
1236 to be one pixel before the top left of the current character.
1237
1238 Note that the root item of the delegate component must be a QQuickItem or
1239 QQuickItem derived item.
1240*/
1241QQmlComponent* QQuickTextEdit::cursorDelegate() const
1242{
1243 Q_D(const QQuickTextEdit);
1244 return d->cursorComponent;
1245}
1246
1247void QQuickTextEdit::setCursorDelegate(QQmlComponent* c)
1248{
1249 Q_D(QQuickTextEdit);
1250 QQuickTextUtil::setCursorDelegate(d, delegate: c);
1251}
1252
1253void QQuickTextEdit::createCursor()
1254{
1255 Q_D(QQuickTextEdit);
1256 d->cursorPending = true;
1257 QQuickTextUtil::createCursor(d);
1258}
1259
1260/*!
1261 \qmlproperty int QtQuick::TextEdit::selectionStart
1262
1263 The cursor position before the first character in the current selection.
1264
1265 This property is read-only. To change the selection, use select(start,end),
1266 selectAll(), or selectWord().
1267
1268 \sa selectionEnd, cursorPosition, selectedText
1269*/
1270int QQuickTextEdit::selectionStart() const
1271{
1272 Q_D(const QQuickTextEdit);
1273 return d->control->textCursor().selectionStart();
1274}
1275
1276/*!
1277 \qmlproperty int QtQuick::TextEdit::selectionEnd
1278
1279 The cursor position after the last character in the current selection.
1280
1281 This property is read-only. To change the selection, use select(start,end),
1282 selectAll(), or selectWord().
1283
1284 \sa selectionStart, cursorPosition, selectedText
1285*/
1286int QQuickTextEdit::selectionEnd() const
1287{
1288 Q_D(const QQuickTextEdit);
1289 return d->control->textCursor().selectionEnd();
1290}
1291
1292/*!
1293 \qmlproperty string QtQuick::TextEdit::selectedText
1294
1295 This read-only property provides the text currently selected in the
1296 text edit.
1297
1298 It is equivalent to the following snippet, but is faster and easier
1299 to use.
1300 \code
1301 //myTextEdit is the id of the TextEdit
1302 myTextEdit.text.toString().substring(myTextEdit.selectionStart,
1303 myTextEdit.selectionEnd);
1304 \endcode
1305*/
1306QString QQuickTextEdit::selectedText() const
1307{
1308 Q_D(const QQuickTextEdit);
1309#if QT_CONFIG(texthtmlparser)
1310 return d->richText
1311 ? d->control->textCursor().selectedText()
1312 : d->control->textCursor().selection().toPlainText();
1313#else
1314 return d->control->textCursor().selection().toPlainText();
1315#endif
1316}
1317
1318/*!
1319 \qmlproperty bool QtQuick::TextEdit::activeFocusOnPress
1320
1321 Whether the TextEdit should gain active focus on a mouse press. By default this is
1322 set to true.
1323*/
1324bool QQuickTextEdit::focusOnPress() const
1325{
1326 Q_D(const QQuickTextEdit);
1327 return d->focusOnPress;
1328}
1329
1330void QQuickTextEdit::setFocusOnPress(bool on)
1331{
1332 Q_D(QQuickTextEdit);
1333 if (d->focusOnPress == on)
1334 return;
1335 d->focusOnPress = on;
1336 emit activeFocusOnPressChanged(activeFocusOnPressed: d->focusOnPress);
1337}
1338
1339/*!
1340 \qmlproperty bool QtQuick::TextEdit::persistentSelection
1341
1342 Whether the TextEdit should keep the selection visible when it loses active focus to another
1343 item in the scene. By default this is set to false.
1344*/
1345bool QQuickTextEdit::persistentSelection() const
1346{
1347 Q_D(const QQuickTextEdit);
1348 return d->persistentSelection;
1349}
1350
1351void QQuickTextEdit::setPersistentSelection(bool on)
1352{
1353 Q_D(QQuickTextEdit);
1354 if (d->persistentSelection == on)
1355 return;
1356 d->persistentSelection = on;
1357 emit persistentSelectionChanged(isPersistentSelection: d->persistentSelection);
1358}
1359
1360/*!
1361 \qmlproperty real QtQuick::TextEdit::textMargin
1362
1363 The margin, in pixels, around the text in the TextEdit.
1364*/
1365qreal QQuickTextEdit::textMargin() const
1366{
1367 Q_D(const QQuickTextEdit);
1368 return d->textMargin;
1369}
1370
1371void QQuickTextEdit::setTextMargin(qreal margin)
1372{
1373 Q_D(QQuickTextEdit);
1374 if (d->textMargin == margin)
1375 return;
1376 d->textMargin = margin;
1377 d->document->setDocumentMargin(d->textMargin);
1378 emit textMarginChanged(textMargin: d->textMargin);
1379}
1380
1381/*!
1382 \qmlproperty enumeration QtQuick::TextEdit::inputMethodHints
1383
1384 Provides hints to the input method about the expected content of the text edit and how it
1385 should operate.
1386
1387 The value is a bit-wise combination of flags or Qt.ImhNone if no hints are set.
1388
1389 Flags that alter behaviour are:
1390
1391 \list
1392 \li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords.
1393 \li Qt.ImhSensitiveData - Typed text should not be stored by the active input method
1394 in any persistent storage like predictive user dictionary.
1395 \li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case
1396 when a sentence ends.
1397 \li Qt.ImhPreferNumbers - Numbers are preferred (but not required).
1398 \li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required).
1399 \li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required).
1400 \li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing.
1401
1402 \li Qt.ImhDate - The text editor functions as a date field.
1403 \li Qt.ImhTime - The text editor functions as a time field.
1404 \endlist
1405
1406 Flags that restrict input (exclusive flags) are:
1407
1408 \list
1409 \li Qt.ImhDigitsOnly - Only digits are allowed.
1410 \li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign.
1411 \li Qt.ImhUppercaseOnly - Only upper case letter input is allowed.
1412 \li Qt.ImhLowercaseOnly - Only lower case letter input is allowed.
1413 \li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed.
1414 \li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed.
1415 \li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed.
1416 \endlist
1417
1418 Masks:
1419
1420 \list
1421 \li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used.
1422 \endlist
1423*/
1424
1425Qt::InputMethodHints QQuickTextEdit::inputMethodHints() const
1426{
1427#if !QT_CONFIG(im)
1428 return Qt::ImhNone;
1429#else
1430 Q_D(const QQuickTextEdit);
1431 return d->inputMethodHints;
1432#endif // im
1433}
1434
1435void QQuickTextEdit::setInputMethodHints(Qt::InputMethodHints hints)
1436{
1437#if !QT_CONFIG(im)
1438 Q_UNUSED(hints);
1439#else
1440 Q_D(QQuickTextEdit);
1441
1442 if (hints == d->inputMethodHints)
1443 return;
1444
1445 d->inputMethodHints = hints;
1446 updateInputMethod(queries: Qt::ImHints);
1447 emit inputMethodHintsChanged();
1448#endif // im
1449}
1450
1451void QQuickTextEdit::geometryChanged(const QRectF &newGeometry,
1452 const QRectF &oldGeometry)
1453{
1454 Q_D(QQuickTextEdit);
1455 if (!d->inLayout && ((newGeometry.width() != oldGeometry.width() && widthValid())
1456 || (newGeometry.height() != oldGeometry.height() && heightValid()))) {
1457 updateSize();
1458 updateWholeDocument();
1459 moveCursorDelegate();
1460 }
1461 QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
1462
1463}
1464
1465/*!
1466 Ensures any delayed caching or data loading the class
1467 needs to performed is complete.
1468*/
1469void QQuickTextEdit::componentComplete()
1470{
1471 Q_D(QQuickTextEdit);
1472 QQuickImplicitSizeItem::componentComplete();
1473
1474 d->document->setBaseUrl(baseUrl());
1475#if QT_CONFIG(texthtmlparser)
1476 if (d->richText)
1477 d->control->setHtml(d->text);
1478 else
1479#endif
1480 if (!d->text.isEmpty()) {
1481 if (d->markdownText)
1482 d->control->setMarkdownText(d->text);
1483 else
1484 d->control->setPlainText(d->text);
1485 }
1486
1487 if (d->dirty) {
1488 d->determineHorizontalAlignment();
1489 d->updateDefaultTextOption();
1490 updateSize();
1491 d->dirty = false;
1492 }
1493 if (d->cursorComponent && isCursorVisible())
1494 QQuickTextUtil::createCursor(d);
1495}
1496
1497/*!
1498 \qmlproperty bool QtQuick::TextEdit::selectByKeyboard
1499 \since 5.1
1500
1501 Defaults to true when the editor is editable, and false
1502 when read-only.
1503
1504 If true, the user can use the keyboard to select text
1505 even if the editor is read-only. If false, the user
1506 cannot use the keyboard to select text even if the
1507 editor is editable.
1508
1509 \sa readOnly
1510*/
1511bool QQuickTextEdit::selectByKeyboard() const
1512{
1513 Q_D(const QQuickTextEdit);
1514 if (d->selectByKeyboardSet)
1515 return d->selectByKeyboard;
1516 return !isReadOnly();
1517}
1518
1519void QQuickTextEdit::setSelectByKeyboard(bool on)
1520{
1521 Q_D(QQuickTextEdit);
1522 bool was = selectByKeyboard();
1523 if (!d->selectByKeyboardSet || was != on) {
1524 d->selectByKeyboardSet = true;
1525 d->selectByKeyboard = on;
1526 if (on)
1527 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByKeyboard);
1528 else
1529 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByKeyboard);
1530 emit selectByKeyboardChanged(selectByKeyboard: on);
1531 }
1532}
1533
1534/*!
1535 \qmlproperty bool QtQuick::TextEdit::selectByMouse
1536
1537 Defaults to false.
1538
1539 If true, the user can use the mouse to select text in some
1540 platform-specific way. Note that for some platforms this may
1541 not be an appropriate interaction; it may conflict with how
1542 the text needs to behave inside a Flickable, for example.
1543*/
1544bool QQuickTextEdit::selectByMouse() const
1545{
1546 Q_D(const QQuickTextEdit);
1547 return d->selectByMouse;
1548}
1549
1550void QQuickTextEdit::setSelectByMouse(bool on)
1551{
1552 Q_D(QQuickTextEdit);
1553 if (d->selectByMouse != on) {
1554 d->selectByMouse = on;
1555 setKeepMouseGrab(on);
1556 if (on)
1557 d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByMouse);
1558 else
1559 d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
1560 emit selectByMouseChanged(selectByMouse: on);
1561 }
1562}
1563
1564/*!
1565 \qmlproperty enumeration QtQuick::TextEdit::mouseSelectionMode
1566
1567 Specifies how text should be selected using a mouse.
1568
1569 \list
1570 \li TextEdit.SelectCharacters - The selection is updated with individual characters. (Default)
1571 \li TextEdit.SelectWords - The selection is updated with whole words.
1572 \endlist
1573
1574 This property only applies when \l selectByMouse is true.
1575*/
1576QQuickTextEdit::SelectionMode QQuickTextEdit::mouseSelectionMode() const
1577{
1578 Q_D(const QQuickTextEdit);
1579 return d->mouseSelectionMode;
1580}
1581
1582void QQuickTextEdit::setMouseSelectionMode(SelectionMode mode)
1583{
1584 Q_D(QQuickTextEdit);
1585 if (d->mouseSelectionMode != mode) {
1586 d->mouseSelectionMode = mode;
1587 d->control->setWordSelectionEnabled(mode == SelectWords);
1588 emit mouseSelectionModeChanged(mode);
1589 }
1590}
1591
1592/*!
1593 \qmlproperty bool QtQuick::TextEdit::readOnly
1594
1595 Whether the user can interact with the TextEdit item. If this
1596 property is set to true the text cannot be edited by user interaction.
1597
1598 By default this property is false.
1599*/
1600void QQuickTextEdit::setReadOnly(bool r)
1601{
1602 Q_D(QQuickTextEdit);
1603 if (r == isReadOnly())
1604 return;
1605
1606#if QT_CONFIG(im)
1607 setFlag(flag: QQuickItem::ItemAcceptsInputMethod, enabled: !r);
1608#endif
1609 Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse;
1610 if (d->selectByMouse)
1611 flags = flags | Qt::TextSelectableByMouse;
1612 if (d->selectByKeyboardSet && d->selectByKeyboard)
1613 flags = flags | Qt::TextSelectableByKeyboard;
1614 else if (!d->selectByKeyboardSet && !r)
1615 flags = flags | Qt::TextSelectableByKeyboard;
1616 if (!r)
1617 flags = flags | Qt::TextEditable;
1618 d->control->setTextInteractionFlags(flags);
1619 d->control->moveCursor(op: QTextCursor::End);
1620
1621#if QT_CONFIG(im)
1622 updateInputMethod(queries: Qt::ImEnabled);
1623#endif
1624 q_canPasteChanged();
1625 emit readOnlyChanged(isReadOnly: r);
1626 if (!d->selectByKeyboardSet)
1627 emit selectByKeyboardChanged(selectByKeyboard: !r);
1628 if (r) {
1629 setCursorVisible(false);
1630 } else if (hasActiveFocus()) {
1631 setCursorVisible(true);
1632 }
1633}
1634
1635bool QQuickTextEdit::isReadOnly() const
1636{
1637 Q_D(const QQuickTextEdit);
1638 return !(d->control->textInteractionFlags() & Qt::TextEditable);
1639}
1640
1641/*!
1642 \qmlproperty rectangle QtQuick::TextEdit::cursorRectangle
1643
1644 The rectangle where the standard text cursor is rendered
1645 within the text edit. Read-only.
1646
1647 The position and height of a custom cursorDelegate are updated to follow the cursorRectangle
1648 automatically when it changes. The width of the delegate is unaffected by changes in the
1649 cursor rectangle.
1650*/
1651QRectF QQuickTextEdit::cursorRectangle() const
1652{
1653 Q_D(const QQuickTextEdit);
1654 return d->control->cursorRect().translated(dx: d->xoff, dy: d->yoff);
1655}
1656
1657bool QQuickTextEdit::event(QEvent *event)
1658{
1659 Q_D(QQuickTextEdit);
1660 if (event->type() == QEvent::ShortcutOverride) {
1661 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1662 if (event->isAccepted())
1663 return true;
1664 }
1665 return QQuickImplicitSizeItem::event(event);
1666}
1667
1668/*!
1669 \qmlproperty bool QtQuick::TextEdit::overwriteMode
1670 \since 5.8
1671 Whether text entered by the user will overwrite existing text.
1672
1673 As with many text editors, the text editor widget can be configured
1674 to insert or overwrite existing text with new text entered by the user.
1675
1676 If this property is \c true, existing text is overwritten, character-for-character
1677 by new text; otherwise, text is inserted at the cursor position, displacing
1678 existing text.
1679
1680 By default, this property is \c false (new text does not overwrite existing text).
1681*/
1682bool QQuickTextEdit::overwriteMode() const
1683{
1684 Q_D(const QQuickTextEdit);
1685 return d->control->overwriteMode();
1686}
1687
1688void QQuickTextEdit::setOverwriteMode(bool overwrite)
1689{
1690 Q_D(QQuickTextEdit);
1691 d->control->setOverwriteMode(overwrite);
1692}
1693
1694/*!
1695\overload
1696Handles the given key \a event.
1697*/
1698void QQuickTextEdit::keyPressEvent(QKeyEvent *event)
1699{
1700 Q_D(QQuickTextEdit);
1701 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1702 if (!event->isAccepted())
1703 QQuickImplicitSizeItem::keyPressEvent(event);
1704}
1705
1706/*!
1707\overload
1708Handles the given key \a event.
1709*/
1710void QQuickTextEdit::keyReleaseEvent(QKeyEvent *event)
1711{
1712 Q_D(QQuickTextEdit);
1713 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1714 if (!event->isAccepted())
1715 QQuickImplicitSizeItem::keyReleaseEvent(event);
1716}
1717
1718/*!
1719 \qmlmethod QtQuick::TextEdit::deselect()
1720
1721 Removes active text selection.
1722*/
1723void QQuickTextEdit::deselect()
1724{
1725 Q_D(QQuickTextEdit);
1726 QTextCursor c = d->control->textCursor();
1727 c.clearSelection();
1728 d->control->setTextCursor(c);
1729}
1730
1731/*!
1732 \qmlmethod QtQuick::TextEdit::selectAll()
1733
1734 Causes all text to be selected.
1735*/
1736void QQuickTextEdit::selectAll()
1737{
1738 Q_D(QQuickTextEdit);
1739 d->control->selectAll();
1740}
1741
1742/*!
1743 \qmlmethod QtQuick::TextEdit::selectWord()
1744
1745 Causes the word closest to the current cursor position to be selected.
1746*/
1747void QQuickTextEdit::selectWord()
1748{
1749 Q_D(QQuickTextEdit);
1750 QTextCursor c = d->control->textCursor();
1751 c.select(selection: QTextCursor::WordUnderCursor);
1752 d->control->setTextCursor(c);
1753}
1754
1755/*!
1756 \qmlmethod QtQuick::TextEdit::select(int start, int end)
1757
1758 Causes the text from \a start to \a end to be selected.
1759
1760 If either start or end is out of range, the selection is not changed.
1761
1762 After calling this, selectionStart will become the lesser
1763 and selectionEnd will become the greater (regardless of the order passed
1764 to this method).
1765
1766 \sa selectionStart, selectionEnd
1767*/
1768void QQuickTextEdit::select(int start, int end)
1769{
1770 Q_D(QQuickTextEdit);
1771 if (start < 0 || end < 0 || start >= d->document->characterCount() || end >= d->document->characterCount())
1772 return;
1773 QTextCursor cursor = d->control->textCursor();
1774 cursor.beginEditBlock();
1775 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
1776 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
1777 cursor.endEditBlock();
1778 d->control->setTextCursor(cursor);
1779
1780 // QTBUG-11100
1781 updateSelection();
1782#if QT_CONFIG(im)
1783 updateInputMethod();
1784#endif
1785}
1786
1787/*!
1788 \qmlmethod QtQuick::TextEdit::isRightToLeft(int start, int end)
1789
1790 Returns true if the natural reading direction of the editor text
1791 found between positions \a start and \a end is right to left.
1792*/
1793bool QQuickTextEdit::isRightToLeft(int start, int end)
1794{
1795 if (start > end) {
1796 qmlWarning(me: this) << "isRightToLeft(start, end) called with the end property being smaller than the start.";
1797 return false;
1798 } else {
1799 return getText(start, end).isRightToLeft();
1800 }
1801}
1802
1803#if QT_CONFIG(clipboard)
1804/*!
1805 \qmlmethod QtQuick::TextEdit::cut()
1806
1807 Moves the currently selected text to the system clipboard.
1808*/
1809void QQuickTextEdit::cut()
1810{
1811 Q_D(QQuickTextEdit);
1812 d->control->cut();
1813}
1814
1815/*!
1816 \qmlmethod QtQuick::TextEdit::copy()
1817
1818 Copies the currently selected text to the system clipboard.
1819*/
1820void QQuickTextEdit::copy()
1821{
1822 Q_D(QQuickTextEdit);
1823 d->control->copy();
1824}
1825
1826/*!
1827 \qmlmethod QtQuick::TextEdit::paste()
1828
1829 Replaces the currently selected text by the contents of the system clipboard.
1830*/
1831void QQuickTextEdit::paste()
1832{
1833 Q_D(QQuickTextEdit);
1834 d->control->paste();
1835}
1836#endif // clipboard
1837
1838
1839/*!
1840 \qmlmethod QtQuick::TextEdit::undo()
1841
1842 Undoes the last operation if undo is \l {canUndo}{available}. Deselects any
1843 current selection, and updates the selection start to the current cursor
1844 position.
1845*/
1846
1847void QQuickTextEdit::undo()
1848{
1849 Q_D(QQuickTextEdit);
1850 d->control->undo();
1851}
1852
1853/*!
1854 \qmlmethod QtQuick::TextEdit::redo()
1855
1856 Redoes the last operation if redo is \l {canRedo}{available}.
1857*/
1858
1859void QQuickTextEdit::redo()
1860{
1861 Q_D(QQuickTextEdit);
1862 d->control->redo();
1863}
1864
1865/*!
1866\overload
1867Handles the given mouse \a event.
1868*/
1869void QQuickTextEdit::mousePressEvent(QMouseEvent *event)
1870{
1871 Q_D(QQuickTextEdit);
1872 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1873 if (d->focusOnPress){
1874 bool hadActiveFocus = hasActiveFocus();
1875 forceActiveFocus(reason: Qt::MouseFocusReason);
1876 // re-open input panel on press if already focused
1877#if QT_CONFIG(im)
1878 if (hasActiveFocus() && hadActiveFocus && !isReadOnly())
1879 qGuiApp->inputMethod()->show();
1880#else
1881 Q_UNUSED(hadActiveFocus);
1882#endif
1883 }
1884 if (!event->isAccepted())
1885 QQuickImplicitSizeItem::mousePressEvent(event);
1886}
1887
1888/*!
1889\overload
1890Handles the given mouse \a event.
1891*/
1892void QQuickTextEdit::mouseReleaseEvent(QMouseEvent *event)
1893{
1894 Q_D(QQuickTextEdit);
1895 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1896
1897 if (!event->isAccepted())
1898 QQuickImplicitSizeItem::mouseReleaseEvent(event);
1899}
1900
1901/*!
1902\overload
1903Handles the given mouse \a event.
1904*/
1905void QQuickTextEdit::mouseDoubleClickEvent(QMouseEvent *event)
1906{
1907 Q_D(QQuickTextEdit);
1908 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1909 if (!event->isAccepted())
1910 QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
1911}
1912
1913/*!
1914\overload
1915Handles the given mouse \a event.
1916*/
1917void QQuickTextEdit::mouseMoveEvent(QMouseEvent *event)
1918{
1919 Q_D(QQuickTextEdit);
1920 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1921 if (!event->isAccepted())
1922 QQuickImplicitSizeItem::mouseMoveEvent(event);
1923}
1924
1925#if QT_CONFIG(im)
1926/*!
1927\overload
1928Handles the given input method \a event.
1929*/
1930void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event)
1931{
1932 Q_D(QQuickTextEdit);
1933 const bool wasComposing = isInputMethodComposing();
1934 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
1935 setCursorVisible(d->control->cursorVisible());
1936 if (wasComposing != isInputMethodComposing())
1937 emit inputMethodComposingChanged();
1938}
1939
1940/*!
1941\overload
1942Returns the value of the given \a property and \a argument.
1943*/
1944QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
1945{
1946 Q_D(const QQuickTextEdit);
1947
1948 QVariant v;
1949 switch (property) {
1950 case Qt::ImEnabled:
1951 v = (bool)(flags() & ItemAcceptsInputMethod);
1952 break;
1953 case Qt::ImHints:
1954 v = (int)d->effectiveInputMethodHints();
1955 break;
1956 case Qt::ImInputItemClipRectangle:
1957 v = QQuickItem::inputMethodQuery(query: property);
1958 break;
1959 default:
1960 if (property == Qt::ImCursorPosition && !argument.isNull())
1961 argument = QVariant(argument.toPointF() - QPointF(d->xoff, d->yoff));
1962 v = d->control->inputMethodQuery(query: property, argument);
1963 if (property == Qt::ImCursorRectangle || property == Qt::ImAnchorRectangle)
1964 v = QVariant(v.toRectF().translated(dx: d->xoff, dy: d->yoff));
1965 break;
1966 }
1967 return v;
1968}
1969
1970/*!
1971\overload
1972Returns the value of the given \a property.
1973*/
1974QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const
1975{
1976 return inputMethodQuery(property, argument: QVariant());
1977}
1978#endif // im
1979
1980void QQuickTextEdit::triggerPreprocess()
1981{
1982 Q_D(QQuickTextEdit);
1983 if (d->updateType == QQuickTextEditPrivate::UpdateNone)
1984 d->updateType = QQuickTextEditPrivate::UpdateOnlyPreprocess;
1985 polish();
1986 update();
1987}
1988
1989typedef QQuickTextEditPrivate::Node TextNode;
1990using TextNodeIterator = QQuickTextEditPrivate::TextNodeIterator;
1991
1992static inline bool operator<(const TextNode &n1, const TextNode &n2)
1993{
1994 return n1.startPos() < n2.startPos();
1995}
1996
1997static inline void updateNodeTransform(QQuickTextNode* node, const QPointF &topLeft)
1998{
1999 QMatrix4x4 transformMatrix;
2000 transformMatrix.translate(x: topLeft.x(), y: topLeft.y());
2001 node->setMatrix(transformMatrix);
2002}
2003
2004/*!
2005 * \internal
2006 *
2007 * Invalidates font caches owned by the text objects owned by the element
2008 * to work around the fact that text objects cannot be used from multiple threads.
2009 */
2010void QQuickTextEdit::invalidateFontCaches()
2011{
2012 Q_D(QQuickTextEdit);
2013 if (d->document == nullptr)
2014 return;
2015
2016 QTextBlock block;
2017 for (block = d->document->firstBlock(); block.isValid(); block = block.next()) {
2018 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
2019 block.layout()->engine()->resetFontEngineCache();
2020 }
2021}
2022
2023inline void resetEngine(QQuickTextNodeEngine *engine, const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor)
2024{
2025 *engine = QQuickTextNodeEngine();
2026 engine->setTextColor(textColor);
2027 engine->setSelectedTextColor(selectedTextColor);
2028 engine->setSelectionColor(selectionColor);
2029}
2030
2031QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
2032{
2033 Q_UNUSED(updatePaintNodeData);
2034 Q_D(QQuickTextEdit);
2035
2036 if (d->updateType != QQuickTextEditPrivate::UpdatePaintNode && oldNode != nullptr) {
2037 // Update done in preprocess() in the nodes
2038 d->updateType = QQuickTextEditPrivate::UpdateNone;
2039 return oldNode;
2040 }
2041
2042 d->updateType = QQuickTextEditPrivate::UpdateNone;
2043
2044 if (!oldNode) {
2045 // If we had any QQuickTextNode node references, they were deleted along with the root node
2046 // But here we must delete the Node structures in textNodeMap
2047 d->textNodeMap.clear();
2048 }
2049
2050 RootNode *rootNode = static_cast<RootNode *>(oldNode);
2051 TextNodeIterator nodeIterator = d->textNodeMap.begin();
2052 while (nodeIterator != d->textNodeMap.end() && !nodeIterator->dirty())
2053 ++nodeIterator;
2054
2055 QQuickTextNodeEngine engine;
2056 QQuickTextNodeEngine frameDecorationsEngine;
2057
2058 if (!oldNode || nodeIterator < d->textNodeMap.end()) {
2059
2060 if (!oldNode)
2061 rootNode = new RootNode;
2062
2063 int firstDirtyPos = 0;
2064 if (nodeIterator != d->textNodeMap.end()) {
2065 firstDirtyPos = nodeIterator->startPos();
2066 // ### this could be optimized if the first and last dirty nodes are not connected
2067 // as the intermediate text nodes would usually only need to be transformed differently.
2068 QQuickTextNode *firstCleanNode = nullptr;
2069 auto it = d->textNodeMap.constEnd();
2070 while (it != nodeIterator) {
2071 --it;
2072 if (it->dirty())
2073 break;
2074 firstCleanNode = it->textNode();
2075 }
2076 do {
2077 rootNode->removeChildNode(node: nodeIterator->textNode());
2078 delete nodeIterator->textNode();
2079 nodeIterator = d->textNodeMap.erase(pos: nodeIterator);
2080 } while (nodeIterator != d->textNodeMap.constEnd() && nodeIterator->textNode() != firstCleanNode);
2081 }
2082
2083 // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
2084 rootNode->resetFrameDecorations(newNode: d->createTextNode());
2085 resetEngine(engine: &frameDecorationsEngine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2086
2087 QQuickTextNode *node = nullptr;
2088
2089 int currentNodeSize = 0;
2090 int nodeStart = firstDirtyPos;
2091 QPointF basePosition(d->xoff, d->yoff);
2092 QMatrix4x4 basePositionMatrix;
2093 basePositionMatrix.translate(x: basePosition.x(), y: basePosition.y());
2094 rootNode->setMatrix(basePositionMatrix);
2095
2096 QPointF nodeOffset;
2097 const TextNode firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator
2098 : TextNode();
2099
2100 QList<QTextFrame *> frames;
2101 frames.append(t: d->document->rootFrame());
2102
2103 while (!frames.isEmpty()) {
2104 QTextFrame *textFrame = frames.takeFirst();
2105 frames.append(t: textFrame->childFrames());
2106 frameDecorationsEngine.addFrameDecorations(document: d->document, frame: textFrame);
2107
2108 if (textFrame->lastPosition() < firstDirtyPos
2109 || textFrame->firstPosition() >= firstCleanNode.startPos())
2110 continue;
2111 node = d->createTextNode();
2112 resetEngine(engine: &engine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2113
2114 if (textFrame->firstPosition() > textFrame->lastPosition()
2115 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
2116 updateNodeTransform(node, topLeft: d->document->documentLayout()->frameBoundingRect(frame: textFrame).topLeft());
2117 const int pos = textFrame->firstPosition() - 1;
2118 ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(d->document->documentLayout());
2119 QTextCharFormat format = a->formatAccessor(pos);
2120 QTextBlock block = textFrame->firstCursorPosition().block();
2121 engine.setCurrentLine(block.layout()->lineForTextPosition(pos: pos - block.position()));
2122 engine.addTextObject(block, position: QPointF(0, 0), format, selectionState: QQuickTextNodeEngine::Unselected, textDocument: d->document,
2123 pos, layoutPosition: textFrame->frameFormat().position());
2124 nodeStart = pos;
2125 } else {
2126 // Having nodes spanning across frame boundaries will break the current bookkeeping mechanism. We need to prevent that.
2127 QList<int> frameBoundaries;
2128 frameBoundaries.reserve(size: frames.size());
2129 for (QTextFrame *frame : qAsConst(t&: frames))
2130 frameBoundaries.append(t: frame->firstPosition());
2131 std::sort(first: frameBoundaries.begin(), last: frameBoundaries.end());
2132
2133 QTextFrame::iterator it = textFrame->begin();
2134 while (!it.atEnd()) {
2135 QTextBlock block = it.currentBlock();
2136 ++it;
2137 if (block.position() < firstDirtyPos)
2138 continue;
2139
2140 if (!engine.hasContents()) {
2141 nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
2142 updateNodeTransform(node, topLeft: nodeOffset);
2143 nodeStart = block.position();
2144 }
2145
2146 engine.addTextBlock(d->document, block, position: -nodeOffset, textColor: d->color, anchorColor: QColor(), selectionStart: selectionStart(), selectionEnd: selectionEnd() - 1);
2147 currentNodeSize += block.length();
2148
2149 if ((it.atEnd()) || block.next().position() >= firstCleanNode.startPos())
2150 break; // last node that needed replacing or last block of the frame
2151
2152 QList<int>::const_iterator lowerBound = std::lower_bound(frameBoundaries.constBegin(), frameBoundaries.constEnd(), block.next().position());
2153 if (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart) {
2154 currentNodeSize = 0;
2155 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2156 node = d->createTextNode();
2157 resetEngine(engine: &engine, textColor: d->color, selectedTextColor: d->selectedTextColor, selectionColor: d->selectionColor);
2158 nodeStart = block.next().position();
2159 }
2160 }
2161 }
2162 d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, startPos: nodeStart);
2163 }
2164 frameDecorationsEngine.addToSceneGraph(parent: rootNode->frameDecorationsNode, style: QQuickText::Normal, styleColor: QColor());
2165 // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
2166 rootNode->prependChildNode(node: rootNode->frameDecorationsNode);
2167
2168 Q_ASSERT(nodeIterator == d->textNodeMap.end()
2169 || (nodeIterator->textNode() == firstCleanNode.textNode()
2170 && nodeIterator->startPos() == firstCleanNode.startPos()));
2171 // Update the position of the subsequent text blocks.
2172 if (firstCleanNode.textNode() != nullptr) {
2173 QPointF oldOffset = firstCleanNode.textNode()->matrix().map(point: QPointF(0,0));
2174 QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(
2175 block: d->document->findBlock(pos: firstCleanNode.startPos())).topLeft();
2176 QPointF delta = currentOffset - oldOffset;
2177 while (nodeIterator != d->textNodeMap.end()) {
2178 QMatrix4x4 transformMatrix = nodeIterator->textNode()->matrix();
2179 transformMatrix.translate(x: delta.x(), y: delta.y());
2180 nodeIterator->textNode()->setMatrix(transformMatrix);
2181 ++nodeIterator;
2182 }
2183
2184 }
2185
2186 // Since we iterate over blocks from different text frames that are potentially not sorted
2187 // we need to ensure that our list of nodes is sorted again:
2188 std::sort(first: d->textNodeMap.begin(), last: d->textNodeMap.end());
2189 }
2190
2191 if (d->cursorComponent == nullptr) {
2192 QSGInternalRectangleNode* cursor = nullptr;
2193 if (!isReadOnly() && d->cursorVisible && d->control->cursorOn())
2194 cursor = d->sceneGraphContext()->createInternalRectangleNode(rect: d->control->cursorRect(), c: d->color);
2195 rootNode->resetCursorNode(newNode: cursor);
2196 }
2197
2198 invalidateFontCaches();
2199
2200 return rootNode;
2201}
2202
2203void QQuickTextEdit::updatePolish()
2204{
2205 invalidateFontCaches();
2206}
2207
2208/*!
2209 \qmlproperty bool QtQuick::TextEdit::canPaste
2210
2211 Returns true if the TextEdit is writable and the content of the clipboard is
2212 suitable for pasting into the TextEdit.
2213*/
2214bool QQuickTextEdit::canPaste() const
2215{
2216 Q_D(const QQuickTextEdit);
2217 if (!d->canPasteValid) {
2218 const_cast<QQuickTextEditPrivate *>(d)->canPaste = d->control->canPaste();
2219 const_cast<QQuickTextEditPrivate *>(d)->canPasteValid = true;
2220 }
2221 return d->canPaste;
2222}
2223
2224/*!
2225 \qmlproperty bool QtQuick::TextEdit::canUndo
2226
2227 Returns true if the TextEdit is writable and there are previous operations
2228 that can be undone.
2229*/
2230
2231bool QQuickTextEdit::canUndo() const
2232{
2233 Q_D(const QQuickTextEdit);
2234 return d->document->isUndoAvailable();
2235}
2236
2237/*!
2238 \qmlproperty bool QtQuick::TextEdit::canRedo
2239
2240 Returns true if the TextEdit is writable and there are \l {undo}{undone}
2241 operations that can be redone.
2242*/
2243
2244bool QQuickTextEdit::canRedo() const
2245{
2246 Q_D(const QQuickTextEdit);
2247 return d->document->isRedoAvailable();
2248}
2249
2250/*!
2251 \qmlproperty bool QtQuick::TextEdit::inputMethodComposing
2252
2253
2254 This property holds whether the TextEdit has partial text input from an
2255 input method.
2256
2257 While it is composing an input method may rely on mouse or key events from
2258 the TextEdit to edit or commit the partial text. This property can be used
2259 to determine when to disable events handlers that may interfere with the
2260 correct operation of an input method.
2261*/
2262bool QQuickTextEdit::isInputMethodComposing() const
2263{
2264#if !QT_CONFIG(im)
2265 return false;
2266#else
2267 Q_D(const QQuickTextEdit);
2268 return d->control->hasImState();
2269#endif // im
2270}
2271
2272QQuickTextEditPrivate::ExtraData::ExtraData()
2273 : padding(0)
2274 , topPadding(0)
2275 , leftPadding(0)
2276 , rightPadding(0)
2277 , bottomPadding(0)
2278 , explicitTopPadding(false)
2279 , explicitLeftPadding(false)
2280 , explicitRightPadding(false)
2281 , explicitBottomPadding(false)
2282 , implicitResize(true)
2283{
2284}
2285
2286void QQuickTextEditPrivate::init()
2287{
2288 Q_Q(QQuickTextEdit);
2289
2290#if QT_CONFIG(clipboard)
2291 if (QGuiApplication::clipboard()->supportsSelection())
2292 q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton);
2293 else
2294#endif
2295 q->setAcceptedMouseButtons(Qt::LeftButton);
2296
2297#if QT_CONFIG(im)
2298 q->setFlag(flag: QQuickItem::ItemAcceptsInputMethod);
2299#endif
2300 q->setFlag(flag: QQuickItem::ItemHasContents);
2301
2302 q->setAcceptHoverEvents(true);
2303
2304 document = new QQuickTextDocumentWithImageResources(q);
2305
2306 control = new QQuickTextControl(document, q);
2307 control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable);
2308 control->setAcceptRichText(false);
2309 control->setCursorIsFocusIndicator(true);
2310
2311 qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
2312 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
2313 qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2314 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
2315 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
2316 qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
2317 qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
2318 qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool)));
2319 qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
2320 qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged()));
2321#if QT_CONFIG(clipboard)
2322 qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
2323#endif
2324 qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
2325 qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
2326 qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize()));
2327 QObject::connect(sender: document, signal: &QQuickTextDocumentWithImageResources::contentsChange, receiver: q, slot: &QQuickTextEdit::q_contentsChange);
2328 QObject::connect(sender: document->documentLayout(), signal: &QAbstractTextDocumentLayout::updateBlock, receiver: q, slot: &QQuickTextEdit::invalidateBlock);
2329 QObject::connect(sender: control, signal: &QQuickTextControl::linkHovered, receiver: q, slot: &QQuickTextEdit::q_linkHovered);
2330 QObject::connect(sender: control, signal: &QQuickTextControl::markerHovered, receiver: q, slot: &QQuickTextEdit::q_markerHovered);
2331
2332 document->setDefaultFont(font);
2333 document->setDocumentMargin(textMargin);
2334 document->setUndoRedoEnabled(false); // flush undo buffer.
2335 document->setUndoRedoEnabled(true);
2336 updateDefaultTextOption();
2337 q->updateSize();
2338#if QT_CONFIG(cursor)
2339 q->setCursor(Qt::IBeamCursor);
2340#endif
2341}
2342
2343void QQuickTextEditPrivate::resetInputMethod()
2344{
2345 Q_Q(QQuickTextEdit);
2346 if (!q->isReadOnly() && q->hasActiveFocus() && qGuiApp)
2347 QGuiApplication::inputMethod()->reset();
2348}
2349
2350void QQuickTextEdit::q_textChanged()
2351{
2352 Q_D(QQuickTextEdit);
2353 d->textCached = false;
2354 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2355 d->contentDirection = d->textDirection(text: it.text());
2356 if (d->contentDirection != Qt::LayoutDirectionAuto)
2357 break;
2358 }
2359 d->determineHorizontalAlignment();
2360 d->updateDefaultTextOption();
2361 updateSize();
2362
2363 markDirtyNodesForRange(start: 0, end: d->document->characterCount(), charDelta: 0);
2364 polish();
2365 if (isComponentComplete()) {
2366 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2367 update();
2368 }
2369
2370 emit textChanged();
2371}
2372
2373void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
2374{
2375 Q_D(QQuickTextEdit);
2376 if (start == end)
2377 return;
2378
2379 TextNode dummyNode(start);
2380
2381 const TextNodeIterator textNodeMapBegin = d->textNodeMap.begin();
2382 const TextNodeIterator textNodeMapEnd = d->textNodeMap.end();
2383
2384 TextNodeIterator it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, dummyNode);
2385 // qLowerBound gives us the first node past the start of the affected portion, rewind to the first node
2386 // that starts at the last position before the edit position. (there might be several because of images)
2387 if (it != textNodeMapBegin) {
2388 --it;
2389 TextNode otherDummy(it->startPos());
2390 it = std::lower_bound(textNodeMapBegin, textNodeMapEnd, otherDummy);
2391 }
2392
2393 // mark the affected nodes as dirty
2394 while (it != textNodeMapEnd) {
2395 if (it->startPos() <= end)
2396 it->setDirty();
2397 else if (charDelta)
2398 it->moveStartPos(delta: charDelta);
2399 else
2400 return;
2401 ++it;
2402 }
2403}
2404
2405void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
2406{
2407 Q_D(QQuickTextEdit);
2408
2409 const int editRange = pos + qMax(a: charsAdded, b: charsRemoved);
2410 const int delta = charsAdded - charsRemoved;
2411
2412 markDirtyNodesForRange(start: pos, end: editRange, charDelta: delta);
2413
2414 polish();
2415 if (isComponentComplete()) {
2416 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2417 update();
2418 }
2419}
2420
2421void QQuickTextEdit::moveCursorDelegate()
2422{
2423 Q_D(QQuickTextEdit);
2424#if QT_CONFIG(im)
2425 updateInputMethod();
2426#endif
2427 emit cursorRectangleChanged();
2428 if (!d->cursorItem)
2429 return;
2430 QRectF cursorRect = cursorRectangle();
2431 d->cursorItem->setX(cursorRect.x());
2432 d->cursorItem->setY(cursorRect.y());
2433 d->cursorItem->setHeight(cursorRect.height());
2434}
2435
2436void QQuickTextEdit::updateSelection()
2437{
2438 Q_D(QQuickTextEdit);
2439
2440 // No need for node updates when we go from an empty selection to another empty selection
2441 if (d->control->textCursor().hasSelection() || d->hadSelection) {
2442 markDirtyNodesForRange(start: qMin(a: d->lastSelectionStart, b: d->control->textCursor().selectionStart()), end: qMax(a: d->control->textCursor().selectionEnd(), b: d->lastSelectionEnd), charDelta: 0);
2443 polish();
2444 if (isComponentComplete()) {
2445 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2446 update();
2447 }
2448 }
2449
2450 d->hadSelection = d->control->textCursor().hasSelection();
2451
2452 if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
2453 d->lastSelectionStart = d->control->textCursor().selectionStart();
2454 emit selectionStartChanged();
2455 }
2456 if (d->lastSelectionEnd != d->control->textCursor().selectionEnd()) {
2457 d->lastSelectionEnd = d->control->textCursor().selectionEnd();
2458 emit selectionEndChanged();
2459 }
2460}
2461
2462QRectF QQuickTextEdit::boundingRect() const
2463{
2464 Q_D(const QQuickTextEdit);
2465 QRectF r(
2466 QQuickTextUtil::alignedX(textWidth: d->contentSize.width(), itemWidth: width(), alignment: effectiveHAlign()),
2467 d->yoff,
2468 d->contentSize.width(),
2469 d->contentSize.height());
2470
2471 int cursorWidth = 1;
2472 if (d->cursorItem)
2473 cursorWidth = 0;
2474 else if (!d->document->isEmpty())
2475 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2476
2477 // Could include font max left/right bearings to either side of rectangle.
2478 r.setRight(r.right() + cursorWidth);
2479
2480 return r;
2481}
2482
2483QRectF QQuickTextEdit::clipRect() const
2484{
2485 Q_D(const QQuickTextEdit);
2486 QRectF r = QQuickImplicitSizeItem::clipRect();
2487 int cursorWidth = 1;
2488 if (d->cursorItem)
2489 cursorWidth = d->cursorItem->width();
2490 if (!d->document->isEmpty())
2491 cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
2492
2493 // Could include font max left/right bearings to either side of rectangle.
2494
2495 r.setRight(r.right() + cursorWidth);
2496 return r;
2497}
2498
2499qreal QQuickTextEditPrivate::getImplicitWidth() const
2500{
2501 Q_Q(const QQuickTextEdit);
2502 if (!requireImplicitWidth) {
2503 // We don't calculate implicitWidth unless it is required.
2504 // We need to force a size update now to ensure implicitWidth is calculated
2505 const_cast<QQuickTextEditPrivate*>(this)->requireImplicitWidth = true;
2506 const_cast<QQuickTextEdit*>(q)->updateSize();
2507 }
2508 return implicitWidth;
2509}
2510
2511//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't
2512// need to do all the calculations each time
2513void QQuickTextEdit::updateSize()
2514{
2515 Q_D(QQuickTextEdit);
2516 if (!isComponentComplete()) {
2517 d->dirty = true;
2518 return;
2519 }
2520
2521 qreal naturalWidth = d->implicitWidth - leftPadding() - rightPadding();
2522
2523 qreal newWidth = d->document->idealWidth();
2524 // ### assumes that if the width is set, the text will fill to edges
2525 // ### (unless wrap is false, then clipping will occur)
2526 if (widthValid()) {
2527 if (!d->requireImplicitWidth) {
2528 emit implicitWidthChanged();
2529 // if the implicitWidth is used, then updateSize() has already been called (recursively)
2530 if (d->requireImplicitWidth)
2531 return;
2532 }
2533 if (d->requireImplicitWidth) {
2534 d->document->setTextWidth(-1);
2535 naturalWidth = d->document->idealWidth();
2536
2537 const bool wasInLayout = d->inLayout;
2538 d->inLayout = true;
2539 if (d->isImplicitResizeEnabled())
2540 setImplicitWidth(naturalWidth + leftPadding() + rightPadding());
2541 d->inLayout = wasInLayout;
2542 if (d->inLayout) // probably the result of a binding loop, but by letting it
2543 return; // get this far we'll get a warning to that effect.
2544 }
2545 const qreal newTextWidth = width() - leftPadding() - rightPadding();
2546 if (d->document->textWidth() != newTextWidth) {
2547 d->document->setTextWidth(newTextWidth);
2548 newWidth = d->document->idealWidth();
2549 }
2550 //### need to confirm cost of always setting these
2551 } else if (d->wrapMode == NoWrap && d->document->textWidth() != newWidth) {
2552 d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
2553 } else {
2554 d->document->setTextWidth(-1);
2555 }
2556
2557 QFontMetricsF fm(d->font);
2558 qreal newHeight = d->document->isEmpty() ? qCeil(v: fm.height()) : d->document->size().height();
2559
2560 if (d->isImplicitResizeEnabled()) {
2561 // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
2562 if (!widthValid())
2563 setImplicitSize(newWidth + leftPadding() + rightPadding(), newHeight + topPadding() + bottomPadding());
2564 else
2565 setImplicitHeight(newHeight + topPadding() + bottomPadding());
2566 }
2567
2568 d->xoff = leftPadding() + qMax(a: qreal(0), b: QQuickTextUtil::alignedX(textWidth: d->document->size().width(), itemWidth: width() - leftPadding() - rightPadding(), alignment: effectiveHAlign()));
2569 d->yoff = topPadding() + QQuickTextUtil::alignedY(textHeight: d->document->size().height(), itemHeight: height() - topPadding() - bottomPadding(), alignment: d->vAlign);
2570 setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
2571
2572 QSizeF size(newWidth, newHeight);
2573 if (d->contentSize != size) {
2574 d->contentSize = size;
2575 emit contentSizeChanged();
2576 updateTotalLines();
2577 }
2578}
2579
2580void QQuickTextEdit::updateWholeDocument()
2581{
2582 Q_D(QQuickTextEdit);
2583 if (!d->textNodeMap.isEmpty()) {
2584 for (TextNode &node : d->textNodeMap)
2585 node.setDirty();
2586 }
2587
2588 polish();
2589 if (isComponentComplete()) {
2590 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2591 update();
2592 }
2593}
2594
2595void QQuickTextEdit::invalidateBlock(const QTextBlock &block)
2596{
2597 Q_D(QQuickTextEdit);
2598 markDirtyNodesForRange(start: block.position(), end: block.position() + block.length(), charDelta: 0);
2599
2600 polish();
2601 if (isComponentComplete()) {
2602 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2603 update();
2604 }
2605}
2606
2607void QQuickTextEdit::updateCursor()
2608{
2609 Q_D(QQuickTextEdit);
2610 polish();
2611 if (isComponentComplete()) {
2612 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
2613 update();
2614 }
2615}
2616
2617void QQuickTextEdit::q_linkHovered(const QString &link)
2618{
2619 Q_D(QQuickTextEdit);
2620 emit linkHovered(link);
2621#if QT_CONFIG(cursor)
2622 if (link.isEmpty()) {
2623 setCursor(d->cursorToRestoreAfterHover);
2624 } else if (cursor().shape() != Qt::PointingHandCursor) {
2625 d->cursorToRestoreAfterHover = cursor().shape();
2626 setCursor(Qt::PointingHandCursor);
2627 }
2628#endif
2629}
2630
2631void QQuickTextEdit::q_markerHovered(bool hovered)
2632{
2633 Q_D(QQuickTextEdit);
2634#if QT_CONFIG(cursor)
2635 if (!hovered) {
2636 setCursor(d->cursorToRestoreAfterHover);
2637 } else if (cursor().shape() != Qt::PointingHandCursor) {
2638 d->cursorToRestoreAfterHover = cursor().shape();
2639 setCursor(Qt::PointingHandCursor);
2640 }
2641#endif
2642}
2643
2644void QQuickTextEdit::q_updateAlignment()
2645{
2646 Q_D(QQuickTextEdit);
2647 if (d->determineHorizontalAlignment()) {
2648 d->updateDefaultTextOption();
2649 d->xoff = qMax(a: qreal(0), b: QQuickTextUtil::alignedX(textWidth: d->document->size().width(), itemWidth: width(), alignment: effectiveHAlign()));
2650 moveCursorDelegate();
2651 }
2652}
2653
2654void QQuickTextEdit::updateTotalLines()
2655{
2656 Q_D(QQuickTextEdit);
2657
2658 int subLines = 0;
2659
2660 for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) {
2661 QTextLayout *layout = it.layout();
2662 if (!layout)
2663 continue;
2664 subLines += layout->lineCount()-1;
2665 }
2666
2667 int newTotalLines = d->document->lineCount() + subLines;
2668 if (d->lineCount != newTotalLines) {
2669 d->lineCount = newTotalLines;
2670 emit lineCountChanged();
2671 }
2672}
2673
2674void QQuickTextEditPrivate::updateDefaultTextOption()
2675{
2676 Q_Q(QQuickTextEdit);
2677 QTextOption opt = document->defaultTextOption();
2678 const Qt::Alignment oldAlignment = opt.alignment();
2679 Qt::LayoutDirection oldTextDirection = opt.textDirection();
2680
2681 QQuickTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign();
2682 if (contentDirection == Qt::RightToLeft) {
2683 if (horizontalAlignment == QQuickTextEdit::AlignLeft)
2684 horizontalAlignment = QQuickTextEdit::AlignRight;
2685 else if (horizontalAlignment == QQuickTextEdit::AlignRight)
2686 horizontalAlignment = QQuickTextEdit::AlignLeft;
2687 }
2688 if (!hAlignImplicit)
2689 opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign));
2690 else
2691 opt.setAlignment(Qt::Alignment(vAlign));
2692
2693#if QT_CONFIG(im)
2694 if (contentDirection == Qt::LayoutDirectionAuto) {
2695 opt.setTextDirection(qGuiApp->inputMethod()->inputDirection());
2696 } else
2697#endif
2698 {
2699 opt.setTextDirection(contentDirection);
2700 }
2701
2702 QTextOption::WrapMode oldWrapMode = opt.wrapMode();
2703 opt.setWrapMode(QTextOption::WrapMode(wrapMode));
2704
2705 bool oldUseDesignMetrics = opt.useDesignMetrics();
2706 opt.setUseDesignMetrics(renderType != QQuickTextEdit::NativeRendering);
2707
2708 if (oldWrapMode != opt.wrapMode() || oldAlignment != opt.alignment()
2709 || oldTextDirection != opt.textDirection()
2710 || oldUseDesignMetrics != opt.useDesignMetrics()) {
2711 document->setDefaultTextOption(opt);
2712 }
2713}
2714
2715void QQuickTextEdit::focusInEvent(QFocusEvent *event)
2716{
2717 Q_D(QQuickTextEdit);
2718 d->handleFocusEvent(event);
2719 QQuickImplicitSizeItem::focusInEvent(event);
2720}
2721
2722void QQuickTextEdit::focusOutEvent(QFocusEvent *event)
2723{
2724 Q_D(QQuickTextEdit);
2725 d->handleFocusEvent(event);
2726 QQuickImplicitSizeItem::focusOutEvent(event);
2727}
2728
2729void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event)
2730{
2731 Q_Q(QQuickTextEdit);
2732 bool focus = event->type() == QEvent::FocusIn;
2733 if (!q->isReadOnly())
2734 q->setCursorVisible(focus);
2735 control->processEvent(e: event, coordinateOffset: QPointF(-xoff, -yoff));
2736 if (focus) {
2737 q->q_updateAlignment();
2738#if QT_CONFIG(im)
2739 if (focusOnPress && !q->isReadOnly())
2740 qGuiApp->inputMethod()->show();
2741 q->connect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
2742 receiver: q, SLOT(q_updateAlignment()));
2743#endif
2744 } else {
2745#if QT_CONFIG(im)
2746 q->disconnect(sender: QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
2747 receiver: q, SLOT(q_updateAlignment()));
2748#endif
2749 if (event->reason() != Qt::ActiveWindowFocusReason
2750 && event->reason() != Qt::PopupFocusReason
2751 && control->textCursor().hasSelection()
2752 && !persistentSelection)
2753 q->deselect();
2754
2755 emit q->editingFinished();
2756 }
2757}
2758
2759void QQuickTextEditPrivate::addCurrentTextNodeToRoot(QQuickTextNodeEngine *engine, QSGTransformNode *root, QQuickTextNode *node, TextNodeIterator &it, int startPos)
2760{
2761 engine->addToSceneGraph(parent: node, style: QQuickText::Normal, styleColor: QColor());
2762 it = textNodeMap.insert(before: it, t: TextNode(startPos, node));
2763 ++it;
2764 root->appendChildNode(node);
2765}
2766
2767QQuickTextNode *QQuickTextEditPrivate::createTextNode()
2768{
2769 Q_Q(QQuickTextEdit);
2770 QQuickTextNode* node = new QQuickTextNode(q);
2771 node->setUseNativeRenderer(renderType == QQuickTextEdit::NativeRendering);
2772 return node;
2773}
2774
2775void QQuickTextEdit::q_canPasteChanged()
2776{
2777 Q_D(QQuickTextEdit);
2778 bool old = d->canPaste;
2779 d->canPaste = d->control->canPaste();
2780 bool changed = old!=d->canPaste || !d->canPasteValid;
2781 d->canPasteValid = true;
2782 if (changed)
2783 emit canPasteChanged();
2784}
2785
2786/*!
2787 \qmlmethod string QtQuick::TextEdit::getText(int start, int end)
2788
2789 Returns the section of text that is between the \a start and \a end positions.
2790
2791 The returned text does not include any rich text formatting.
2792*/
2793
2794QString QQuickTextEdit::getText(int start, int end) const
2795{
2796 Q_D(const QQuickTextEdit);
2797 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
2798 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
2799 QTextCursor cursor(d->document);
2800 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
2801 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
2802#if QT_CONFIG(texthtmlparser)
2803 return d->richText
2804 ? cursor.selectedText()
2805 : cursor.selection().toPlainText();
2806#else
2807 return cursor.selection().toPlainText();
2808#endif
2809}
2810
2811/*!
2812 \qmlmethod string QtQuick::TextEdit::getFormattedText(int start, int end)
2813
2814 Returns the section of text that is between the \a start and \a end positions.
2815
2816 The returned text will be formatted according the \l textFormat property.
2817*/
2818
2819QString QQuickTextEdit::getFormattedText(int start, int end) const
2820{
2821 Q_D(const QQuickTextEdit);
2822
2823 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
2824 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
2825
2826 QTextCursor cursor(d->document);
2827 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
2828 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
2829
2830 if (d->richText) {
2831#if QT_CONFIG(texthtmlparser)
2832 return cursor.selection().toHtml();
2833#else
2834 return cursor.selection().toPlainText();
2835#endif
2836 } else {
2837 return cursor.selection().toPlainText();
2838 }
2839}
2840
2841/*!
2842 \qmlmethod QtQuick::TextEdit::insert(int position, string text)
2843
2844 Inserts \a text into the TextEdit at \a position.
2845*/
2846void QQuickTextEdit::insert(int position, const QString &text)
2847{
2848 Q_D(QQuickTextEdit);
2849 if (position < 0 || position >= d->document->characterCount())
2850 return;
2851 QTextCursor cursor(d->document);
2852 cursor.setPosition(pos: position);
2853 d->richText = d->richText || (d->format == AutoText && Qt::mightBeRichText(text));
2854 if (d->richText) {
2855#if QT_CONFIG(texthtmlparser)
2856 cursor.insertHtml(html: text);
2857#else
2858 cursor.insertText(text);
2859#endif
2860 } else {
2861 cursor.insertText(text);
2862 }
2863 d->control->updateCursorRectangle(force: false);
2864}
2865
2866/*!
2867 \qmlmethod string QtQuick::TextEdit::remove(int start, int end)
2868
2869 Removes the section of text that is between the \a start and \a end positions from the TextEdit.
2870*/
2871
2872void QQuickTextEdit::remove(int start, int end)
2873{
2874 Q_D(QQuickTextEdit);
2875 start = qBound(min: 0, val: start, max: d->document->characterCount() - 1);
2876 end = qBound(min: 0, val: end, max: d->document->characterCount() - 1);
2877 QTextCursor cursor(d->document);
2878 cursor.setPosition(pos: start, mode: QTextCursor::MoveAnchor);
2879 cursor.setPosition(pos: end, mode: QTextCursor::KeepAnchor);
2880 cursor.removeSelectedText();
2881 d->control->updateCursorRectangle(force: false);
2882}
2883
2884/*!
2885 \qmlproperty TextDocument QtQuick::TextEdit::textDocument
2886 \since 5.1
2887
2888 Returns the QQuickTextDocument of this TextEdit.
2889 It can be used to implement syntax highlighting using
2890 \l QSyntaxHighlighter.
2891
2892 \sa QQuickTextDocument
2893*/
2894
2895QQuickTextDocument *QQuickTextEdit::textDocument()
2896{
2897 Q_D(QQuickTextEdit);
2898 if (!d->quickDocument)
2899 d->quickDocument = new QQuickTextDocument(this);
2900 return d->quickDocument;
2901}
2902
2903bool QQuickTextEditPrivate::isLinkHoveredConnected()
2904{
2905 Q_Q(QQuickTextEdit);
2906 IS_SIGNAL_CONNECTED(q, QQuickTextEdit, linkHovered, (const QString &));
2907}
2908
2909/*!
2910 \qmlsignal QtQuick::TextEdit::linkHovered(string link)
2911 \since 5.2
2912
2913 This signal is emitted when the user hovers a link embedded in the text.
2914 The link must be in rich text or HTML format and the
2915 \a link string provides access to the particular link.
2916
2917 \sa hoveredLink, linkAt()
2918*/
2919
2920/*!
2921 \qmlsignal QtQuick::TextEdit::editingFinished()
2922 \since 5.6
2923
2924 This signal is emitted when the text edit loses focus.
2925*/
2926
2927/*!
2928 \qmlproperty string QtQuick::TextEdit::hoveredLink
2929 \since 5.2
2930
2931 This property contains the link string when the user hovers a link
2932 embedded in the text. The link must be in rich text or HTML format
2933 and the link string provides access to the particular link.
2934
2935 \sa linkHovered, linkAt()
2936*/
2937
2938QString QQuickTextEdit::hoveredLink() const
2939{
2940 Q_D(const QQuickTextEdit);
2941 if (const_cast<QQuickTextEditPrivate *>(d)->isLinkHoveredConnected()) {
2942 return d->control->hoveredLink();
2943 } else {
2944#if QT_CONFIG(cursor)
2945 if (QQuickWindow *wnd = window()) {
2946 QPointF pos = QCursor::pos(screen: wnd->screen()) - wnd->position() - mapToScene(point: QPointF(0, 0));
2947 return d->control->anchorAt(pos);
2948 }
2949#endif // cursor
2950 }
2951 return QString();
2952}
2953
2954void QQuickTextEdit::hoverEnterEvent(QHoverEvent *event)
2955{
2956 Q_D(QQuickTextEdit);
2957 if (d->isLinkHoveredConnected())
2958 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
2959}
2960
2961void QQuickTextEdit::hoverMoveEvent(QHoverEvent *event)
2962{
2963 Q_D(QQuickTextEdit);
2964 if (d->isLinkHoveredConnected())
2965 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
2966}
2967
2968void QQuickTextEdit::hoverLeaveEvent(QHoverEvent *event)
2969{
2970 Q_D(QQuickTextEdit);
2971 if (d->isLinkHoveredConnected())
2972 d->control->processEvent(e: event, coordinateOffset: QPointF(-d->xoff, -d->yoff));
2973}
2974
2975/*!
2976 \qmlmethod void QtQuick::TextEdit::append(string text)
2977 \since 5.2
2978
2979 Appends a new paragraph with \a text to the end of the TextEdit.
2980
2981 In order to append without inserting a new paragraph,
2982 call \c myTextEdit.insert(myTextEdit.length, text) instead.
2983*/
2984void QQuickTextEdit::append(const QString &text)
2985{
2986 Q_D(QQuickTextEdit);
2987 QTextCursor cursor(d->document);
2988 cursor.beginEditBlock();
2989 cursor.movePosition(op: QTextCursor::End);
2990
2991 if (!d->document->isEmpty())
2992 cursor.insertBlock();
2993
2994#if QT_CONFIG(texthtmlparser)
2995 if (d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text))) {
2996 cursor.insertHtml(html: text);
2997 } else {
2998 cursor.insertText(text);
2999 }
3000#else
3001 cursor.insertText(text);
3002#endif // texthtmlparser
3003
3004 cursor.endEditBlock();
3005 d->control->updateCursorRectangle(force: false);
3006}
3007
3008/*!
3009 \qmlmethod QtQuick::TextEdit::linkAt(real x, real y)
3010 \since 5.3
3011
3012 Returns the link string at point \a x, \a y in content coordinates,
3013 or an empty string if no link exists at that point.
3014
3015 \sa hoveredLink
3016*/
3017QString QQuickTextEdit::linkAt(qreal x, qreal y) const
3018{
3019 Q_D(const QQuickTextEdit);
3020 return d->control->anchorAt(pos: QPointF(x + topPadding(), y + leftPadding()));
3021}
3022
3023/*!
3024 \since 5.6
3025 \qmlproperty real QtQuick::TextEdit::padding
3026 \qmlproperty real QtQuick::TextEdit::topPadding
3027 \qmlproperty real QtQuick::TextEdit::leftPadding
3028 \qmlproperty real QtQuick::TextEdit::bottomPadding
3029 \qmlproperty real QtQuick::TextEdit::rightPadding
3030
3031 These properties hold the padding around the content. This space is reserved
3032 in addition to the contentWidth and contentHeight.
3033*/
3034qreal QQuickTextEdit::padding() const
3035{
3036 Q_D(const QQuickTextEdit);
3037 return d->padding();
3038}
3039
3040void QQuickTextEdit::setPadding(qreal padding)
3041{
3042 Q_D(QQuickTextEdit);
3043 if (qFuzzyCompare(p1: d->padding(), p2: padding))
3044 return;
3045
3046 d->extra.value().padding = padding;
3047 updateSize();
3048 if (isComponentComplete()) {
3049 d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
3050 update();
3051 }
3052 emit paddingChanged();
3053 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3054 emit topPaddingChanged();
3055 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3056 emit leftPaddingChanged();
3057 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3058 emit rightPaddingChanged();
3059 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3060 emit bottomPaddingChanged();
3061}
3062
3063void QQuickTextEdit::resetPadding()
3064{
3065 setPadding(0);
3066}
3067
3068qreal QQuickTextEdit::topPadding() const
3069{
3070 Q_D(const QQuickTextEdit);
3071 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3072 return d->extra->topPadding;
3073 return d->padding();
3074}
3075
3076void QQuickTextEdit::setTopPadding(qreal padding)
3077{
3078 Q_D(QQuickTextEdit);
3079 d->setTopPadding(value: padding);
3080}
3081
3082void QQuickTextEdit::resetTopPadding()
3083{
3084 Q_D(QQuickTextEdit);
3085 d->setTopPadding(value: 0, reset: true);
3086}
3087
3088qreal QQuickTextEdit::leftPadding() const
3089{
3090 Q_D(const QQuickTextEdit);
3091 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3092 return d->extra->leftPadding;
3093 return d->padding();
3094}
3095
3096void QQuickTextEdit::setLeftPadding(qreal padding)
3097{
3098 Q_D(QQuickTextEdit);
3099 d->setLeftPadding(value: padding);
3100}
3101
3102void QQuickTextEdit::resetLeftPadding()
3103{
3104 Q_D(QQuickTextEdit);
3105 d->setLeftPadding(value: 0, reset: true);
3106}
3107
3108qreal QQuickTextEdit::rightPadding() const
3109{
3110 Q_D(const QQuickTextEdit);
3111 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3112 return d->extra->rightPadding;
3113 return d->padding();
3114}
3115
3116void QQuickTextEdit::setRightPadding(qreal padding)
3117{
3118 Q_D(QQuickTextEdit);
3119 d->setRightPadding(value: padding);
3120}
3121
3122void QQuickTextEdit::resetRightPadding()
3123{
3124 Q_D(QQuickTextEdit);
3125 d->setRightPadding(value: 0, reset: true);
3126}
3127
3128qreal QQuickTextEdit::bottomPadding() const
3129{
3130 Q_D(const QQuickTextEdit);
3131 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3132 return d->extra->bottomPadding;
3133 return d->padding();
3134}
3135
3136void QQuickTextEdit::setBottomPadding(qreal padding)
3137{
3138 Q_D(QQuickTextEdit);
3139 d->setBottomPadding(value: padding);
3140}
3141
3142void QQuickTextEdit::resetBottomPadding()
3143{
3144 Q_D(QQuickTextEdit);
3145 d->setBottomPadding(value: 0, reset: true);
3146}
3147
3148/*!
3149 \qmlproperty real QtQuick::TextEdit::tabStopDistance
3150 \since 5.10
3151
3152 The default distance, in device units, between tab stops.
3153
3154 \sa QTextOption::setTabStop()
3155*/
3156int QQuickTextEdit::tabStopDistance() const
3157{
3158 Q_D(const QQuickTextEdit);
3159 return d->document->defaultTextOption().tabStopDistance();
3160}
3161
3162void QQuickTextEdit::setTabStopDistance(qreal distance)
3163{
3164 Q_D(QQuickTextEdit);
3165 QTextOption textOptions = d->document->defaultTextOption();
3166 if (textOptions.tabStopDistance() == distance)
3167 return;
3168
3169 textOptions.setTabStopDistance(distance);
3170 d->document->setDefaultTextOption(textOptions);
3171 emit tabStopDistanceChanged(distance);
3172}
3173
3174/*!
3175 \qmlmethod QtQuick::TextEdit::clear()
3176 \since 5.7
3177
3178 Clears the contents of the text edit
3179 and resets partial text input from an input method.
3180
3181 Use this method instead of setting the \l text property to an empty string.
3182
3183 \sa QInputMethod::reset()
3184*/
3185void QQuickTextEdit::clear()
3186{
3187 Q_D(QQuickTextEdit);
3188 d->resetInputMethod();
3189 d->control->clear();
3190}
3191
3192QT_END_NAMESPACE
3193
3194#include "moc_qquicktextedit_p.cpp"
3195

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