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