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