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::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 |
Definitions
- lcTextEdit
- nodeBreakingSize
- largeTextSizeThreshold
- RootNode
- RootNode
- resetFrameDecorations
- resetCursorNode
- QQuickTextEdit
- ~QQuickTextEdit
- QQuickTextEdit
- text
- setText
- invalidate
- q_invalidate
- preeditText
- textFormat
- setTextFormat
- renderType
- setRenderType
- font
- setFont
- color
- setColor
- selectionColor
- setSelectionColor
- selectedTextColor
- setSelectedTextColor
- hAlign
- setHAlign
- resetHAlign
- effectiveHAlign
- setHAlign
- textDirection
- determineHorizontalAlignment
- mirrorChange
- transformChanged
- effectiveInputMethodHints
- accessibilityActiveChanged
- accessibleRole
- setTopPadding
- setLeftPadding
- setRightPadding
- setBottomPadding
- isImplicitResizeEnabled
- setImplicitResizeEnabled
- vAlign
- setVAlign
- wrapMode
- setWrapMode
- lineCount
- length
- contentWidth
- contentHeight
- baseUrl
- setBaseUrl
- resetBaseUrl
- positionToRectangle
- positionAt
- cursorSelection
- moveCursorSelection
- moveCursorSelection
- isCursorVisible
- setCursorVisible
- cursorPosition
- setCursorPosition
- cursorDelegate
- setCursorDelegate
- createCursor
- selectionStart
- selectionEnd
- selectedText
- focusOnPress
- setFocusOnPress
- persistentSelection
- setPersistentSelection
- textMargin
- setTextMargin
- inputMethodHints
- setInputMethodHints
- geometryChange
- itemChange
- componentComplete
- resourcesLoading
- selectByKeyboard
- setSelectByKeyboard
- selectByMouse
- setSelectByMouse
- mouseSelectionMode
- setMouseSelectionMode
- setReadOnly
- isReadOnly
- cursorRectangle
- event
- overwriteMode
- setOverwriteMode
- keyPressEvent
- keyReleaseEvent
- deselect
- selectAll
- selectWord
- select
- isRightToLeft
- cut
- copy
- paste
- undo
- redo
- mousePressEvent
- mouseReleaseEvent
- mouseDoubleClickEvent
- mouseMoveEvent
- inputMethodEvent
- inputMethodQuery
- inputMethodQuery
- triggerPreprocess
- loadResource
- resourceRequestFinished
- operator<
- updateNodeTransform
- invalidateFontCaches
- document
- setDocument
- resetEngine
- updatePaintNode
- updatePolish
- canPaste
- canUndo
- canRedo
- isInputMethodComposing
- ExtraData
- init
- resetInputMethod
- q_textChanged
- markDirtyNodesForRange
- q_contentsChange
- moveCursorDelegate
- updateSelection
- boundingRect
- clipRect
- getImplicitWidth
- updateSize
- updateWholeDocument
- invalidateBlock
- updateCursor
- q_linkHovered
- q_markerHovered
- q_updateAlignment
- updateTotalLines
- updateDefaultTextOption
- onDocumentStatusChanged
- focusInEvent
- focusOutEvent
- handleFocusEvent
- addCurrentTextNodeToRoot
- createTextNode
- q_canPasteChanged
- getText
- getFormattedText
- insert
- remove
- textDocument
- isLinkHoveredConnected
- updateMouseCursorShape
- hoveredLink
- hoverEnterEvent
- hoverMoveEvent
- hoverLeaveEvent
- append
- linkAt
- padding
- setPadding
- resetPadding
- topPadding
- setTopPadding
- resetTopPadding
- leftPadding
- setLeftPadding
- resetLeftPadding
- rightPadding
- setRightPadding
- resetRightPadding
- bottomPadding
- setBottomPadding
- resetBottomPadding
- tabStopDistance
- setTabStopDistance
- clear
- operator<<
- setOldSelectionDefault
Start learning QML with our Intro Training
Find out more