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