1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquicktextarea_p.h" |
5 | #include "qquicktextarea_p_p.h" |
6 | #include "qquickcontrol_p.h" |
7 | #include "qquickcontrol_p_p.h" |
8 | #include "qquickscrollview_p.h" |
9 | #include "qquickdeferredexecute_p_p.h" |
10 | |
11 | #include <QtQml/qqmlinfo.h> |
12 | #include <QtQuick/private/qquickitem_p.h> |
13 | #include <QtQuick/private/qquickclipnode_p.h> |
14 | #include <QtQuick/private/qquickflickable_p.h> |
15 | |
16 | #if QT_CONFIG(accessibility) |
17 | #include <QtQuick/private/qquickaccessibleattached_p.h> |
18 | #endif |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | using namespace Qt::StringLiterals; |
23 | |
24 | /*! |
25 | \qmltype TextArea |
26 | \inherits TextEdit |
27 | //! \instantiates QQuickTextArea |
28 | \inqmlmodule QtQuick.Controls |
29 | \since 5.7 |
30 | \ingroup qtquickcontrols-input |
31 | \brief Multi-line text input area. |
32 | |
33 | TextArea is a multi-line text editor. TextArea extends TextEdit with |
34 | a \l {placeholderText}{placeholder text} functionality, and adds decoration. |
35 | |
36 | \image qtquickcontrols-textarea.png |
37 | |
38 | \code |
39 | TextArea { |
40 | placeholderText: qsTr("Enter description") |
41 | } |
42 | \endcode |
43 | |
44 | TextArea is not scrollable by itself. Especially on screen-size constrained |
45 | platforms, it is often preferable to make entire application pages scrollable. |
46 | On such a scrollable page, a non-scrollable TextArea might behave better than |
47 | nested scrollable controls. Notice, however, that in such a scenario, the background |
48 | decoration of the TextArea scrolls together with the rest of the scrollable |
49 | content. |
50 | |
51 | \section2 Scrollable TextArea |
52 | |
53 | If you want to make a TextArea scrollable, for example, when it covers |
54 | an entire application page, it can be placed inside a \l ScrollView. |
55 | |
56 | \image qtquickcontrols-textarea-scrollable.png |
57 | |
58 | \snippet qtquickcontrols-textarea-scrollable.qml 1 |
59 | |
60 | A TextArea that is placed inside a \l ScrollView does the following: |
61 | |
62 | \list |
63 | \li Sets the content size automatically |
64 | \li Ensures that the background decoration stays in place |
65 | \li Clips the content |
66 | \endlist |
67 | |
68 | \section2 Tab Focus |
69 | |
70 | By default, pressing the tab key while TextArea has |
71 | \l {Item::activeFocus}{active focus} results in a tab character being input |
72 | into the control itself. To make tab pass active focus onto another item, |
73 | use the attached \l KeyNavigation properties: |
74 | |
75 | \code |
76 | TextField { |
77 | id: textField |
78 | } |
79 | |
80 | TextArea { |
81 | KeyNavigation.priority: KeyNavigation.BeforeItem |
82 | KeyNavigation.tab: textField |
83 | } |
84 | \endcode |
85 | |
86 | \sa TextField, {Customizing TextArea}, {Input Controls} |
87 | */ |
88 | |
89 | /*! |
90 | \qmlsignal QtQuick.Controls::TextArea::pressAndHold(MouseEvent event) |
91 | |
92 | This signal is emitted when there is a long press (the delay depends on the platform plugin). |
93 | The \a event parameter provides information about the press, including the x and y |
94 | coordinates of the press, and which button is pressed. |
95 | |
96 | \sa pressed, released |
97 | */ |
98 | |
99 | /*! |
100 | \qmlsignal QtQuick.Controls::TextArea::pressed(MouseEvent event) |
101 | \since QtQuick.Controls 2.1 (Qt 5.8) |
102 | |
103 | This signal is emitted when the text area is pressed by the user. |
104 | The \a event parameter provides information about the press, |
105 | including the x and y coordinates of the press, and which button is pressed. |
106 | |
107 | \sa released, pressAndHold |
108 | */ |
109 | |
110 | /*! |
111 | \qmlsignal QtQuick.Controls::TextArea::released(MouseEvent event) |
112 | \since QtQuick.Controls 2.1 (Qt 5.8) |
113 | |
114 | This signal is emitted when the text area is released by the user. |
115 | The \a event parameter provides information about the release, |
116 | including the x and y coordinates of the press, and which button |
117 | is pressed. |
118 | |
119 | \sa pressed, pressAndHold |
120 | */ |
121 | |
122 | QQuickTextAreaPrivate::QQuickTextAreaPrivate() |
123 | { |
124 | #if QT_CONFIG(accessibility) |
125 | QAccessible::installActivationObserver(this); |
126 | #endif |
127 | } |
128 | |
129 | QQuickTextAreaPrivate::~QQuickTextAreaPrivate() |
130 | { |
131 | #if QT_CONFIG(accessibility) |
132 | QAccessible::removeActivationObserver(this); |
133 | #endif |
134 | } |
135 | |
136 | void QQuickTextAreaPrivate::setTopInset(qreal value, bool reset) |
137 | { |
138 | Q_Q(QQuickTextArea); |
139 | const QMarginsF oldInset = getInset(); |
140 | extra.value().topInset = value; |
141 | extra.value().hasTopInset = !reset; |
142 | if (!qFuzzyCompare(p1: oldInset.top(), p2: value)) { |
143 | emit q->topInsetChanged(); |
144 | q->insetChange(newInset: getInset(), oldInset); |
145 | } |
146 | } |
147 | |
148 | void QQuickTextAreaPrivate::setLeftInset(qreal value, bool reset) |
149 | { |
150 | Q_Q(QQuickTextArea); |
151 | const QMarginsF oldInset = getInset(); |
152 | extra.value().leftInset = value; |
153 | extra.value().hasLeftInset = !reset; |
154 | if (!qFuzzyCompare(p1: oldInset.left(), p2: value)) { |
155 | emit q->leftInsetChanged(); |
156 | q->insetChange(newInset: getInset(), oldInset); |
157 | } |
158 | } |
159 | |
160 | void QQuickTextAreaPrivate::setRightInset(qreal value, bool reset) |
161 | { |
162 | Q_Q(QQuickTextArea); |
163 | const QMarginsF oldInset = getInset(); |
164 | extra.value().rightInset = value; |
165 | extra.value().hasRightInset = !reset; |
166 | if (!qFuzzyCompare(p1: oldInset.right(), p2: value)) { |
167 | emit q->rightInsetChanged(); |
168 | q->insetChange(newInset: getInset(), oldInset); |
169 | } |
170 | } |
171 | |
172 | void QQuickTextAreaPrivate::setBottomInset(qreal value, bool reset) |
173 | { |
174 | Q_Q(QQuickTextArea); |
175 | const QMarginsF oldInset = getInset(); |
176 | extra.value().bottomInset = value; |
177 | extra.value().hasBottomInset = !reset; |
178 | if (!qFuzzyCompare(p1: oldInset.bottom(), p2: value)) { |
179 | emit q->bottomInsetChanged(); |
180 | q->insetChange(newInset: getInset(), oldInset); |
181 | } |
182 | } |
183 | |
184 | void QQuickTextAreaPrivate::resizeBackground() |
185 | { |
186 | if (!background) |
187 | return; |
188 | |
189 | resizingBackground = true; |
190 | |
191 | // When using the attached property TextArea.flickable, we reparent the background out |
192 | // of TextArea and into the Flickable since we don't want the background to move while |
193 | // flicking. This means that the size of the background should also follow the size of |
194 | // the Flickable rather than the size of the TextArea. |
195 | const auto flickable = qobject_cast<QQuickFlickable *>(object: background->parentItem()); |
196 | |
197 | QQuickItemPrivate *p = QQuickItemPrivate::get(item: background); |
198 | if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(d: background->x())) |
199 | || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { |
200 | const qreal bgWidth = flickable ? flickable->width() : width; |
201 | background->setX(getLeftInset()); |
202 | background->setWidth(bgWidth - getLeftInset() - getRightInset()); |
203 | } |
204 | |
205 | if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(d: background->y())) |
206 | || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { |
207 | const qreal bgHeight = flickable ? flickable->height() : height; |
208 | background->setY(getTopInset()); |
209 | background->setHeight(bgHeight - getTopInset() - getBottomInset()); |
210 | } |
211 | |
212 | resizingBackground = false; |
213 | } |
214 | |
215 | /*! |
216 | \internal |
217 | |
218 | Determine which font is implicitly imposed on this control by its ancestors |
219 | and QGuiApplication::font, resolve this against its own font (attributes from |
220 | the implicit font are copied over). Then propagate this font to this |
221 | control's children. |
222 | */ |
223 | void QQuickTextAreaPrivate::resolveFont() |
224 | { |
225 | Q_Q(QQuickTextArea); |
226 | inheritFont(font: QQuickControlPrivate::parentFont(item: q)); |
227 | } |
228 | |
229 | void QQuickTextAreaPrivate::inheritFont(const QFont &font) |
230 | { |
231 | QFont parentFont = extra.isAllocated() ? extra->requestedFont.resolve(font) : font; |
232 | parentFont.setResolveMask(extra.isAllocated() ? extra->requestedFont.resolveMask() | font.resolveMask() : font.resolveMask()); |
233 | |
234 | const QFont defaultFont = QQuickTheme::font(scope: QQuickTheme::TextArea); |
235 | QFont resolvedFont = parentFont.resolve(defaultFont); |
236 | |
237 | setFont_helper(resolvedFont); |
238 | } |
239 | |
240 | /*! |
241 | \internal |
242 | |
243 | Assign \a font to this control, and propagate it to all children. |
244 | */ |
245 | void QQuickTextAreaPrivate::updateFont(const QFont &font) |
246 | { |
247 | Q_Q(QQuickTextArea); |
248 | QFont oldFont = sourceFont; |
249 | q->QQuickTextEdit::setFont(font); |
250 | |
251 | QQuickControlPrivate::updateFontRecur(item: q, font); |
252 | |
253 | if (oldFont != font) |
254 | emit q->fontChanged(); |
255 | } |
256 | |
257 | #if QT_CONFIG(quicktemplates2_hover) |
258 | void QQuickTextAreaPrivate::updateHoverEnabled(bool enabled, bool xplicit) |
259 | { |
260 | Q_Q(QQuickTextArea); |
261 | if (!xplicit && explicitHoverEnabled) |
262 | return; |
263 | |
264 | bool wasEnabled = q->isHoverEnabled(); |
265 | explicitHoverEnabled = xplicit; |
266 | if (wasEnabled != enabled) { |
267 | q->setAcceptHoverEvents(enabled); |
268 | QQuickControlPrivate::updateHoverEnabledRecur(item: q, enabled); |
269 | emit q->hoverEnabledChanged(); |
270 | } |
271 | } |
272 | #endif |
273 | |
274 | void QQuickTextAreaPrivate::attachFlickable(QQuickFlickable *item) |
275 | { |
276 | Q_Q(QQuickTextArea); |
277 | flickable = item; |
278 | q->setParentItem(flickable->contentItem()); |
279 | |
280 | if (background) |
281 | background->setParentItem(flickable); |
282 | |
283 | QObjectPrivate::connect(sender: q, signal: &QQuickTextArea::contentSizeChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::resizeFlickableContent); |
284 | QObjectPrivate::connect(sender: q, signal: &QQuickTextEdit::cursorRectangleChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::ensureCursorVisible); |
285 | |
286 | QObject::connect(sender: flickable, signal: &QQuickFlickable::contentXChanged, context: q, slot: &QQuickItem::update); |
287 | QObject::connect(sender: flickable, signal: &QQuickFlickable::contentYChanged, context: q, slot: &QQuickItem::update); |
288 | |
289 | QQuickItemPrivate::get(item: flickable)->updateOrAddGeometryChangeListener(listener: this, types: QQuickGeometryChange::Size); |
290 | QQuickItemPrivate::get(item: flickable)->addItemChangeListener(listener: this, types: QQuickItemPrivate::Destroyed); |
291 | QObjectPrivate::connect(sender: flickable, signal: &QQuickFlickable::contentWidthChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::resizeFlickableControl); |
292 | QObjectPrivate::connect(sender: flickable, signal: &QQuickFlickable::contentHeightChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::resizeFlickableControl); |
293 | |
294 | resizeFlickableControl(); |
295 | } |
296 | |
297 | void QQuickTextAreaPrivate::detachFlickable() |
298 | { |
299 | Q_Q(QQuickTextArea); |
300 | q->setParentItem(nullptr); |
301 | if (background && background->parentItem() == flickable) |
302 | background->setParentItem(q); |
303 | |
304 | QObjectPrivate::disconnect(sender: q, signal: &QQuickTextArea::contentSizeChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::resizeFlickableContent); |
305 | QObjectPrivate::disconnect(sender: q, signal: &QQuickTextEdit::cursorRectangleChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::ensureCursorVisible); |
306 | |
307 | QObject::disconnect(sender: flickable, signal: &QQuickFlickable::contentXChanged, receiver: q, slot: &QQuickItem::update); |
308 | QObject::disconnect(sender: flickable, signal: &QQuickFlickable::contentYChanged, receiver: q, slot: &QQuickItem::update); |
309 | |
310 | QQuickItemPrivate::get(item: flickable)->updateOrRemoveGeometryChangeListener(listener: this, types: QQuickGeometryChange::Nothing); |
311 | QQuickItemPrivate::get(item: flickable)->removeItemChangeListener(this, types: QQuickItemPrivate::Destroyed); |
312 | QObjectPrivate::disconnect(sender: flickable, signal: &QQuickFlickable::contentWidthChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::resizeFlickableControl); |
313 | QObjectPrivate::disconnect(sender: flickable, signal: &QQuickFlickable::contentHeightChanged, receiverPrivate: this, slot: &QQuickTextAreaPrivate::resizeFlickableControl); |
314 | |
315 | flickable = nullptr; |
316 | |
317 | resizeBackground(); |
318 | } |
319 | |
320 | void QQuickTextAreaPrivate::ensureCursorVisible() |
321 | { |
322 | Q_Q(QQuickTextArea); |
323 | if (!flickable) |
324 | return; |
325 | |
326 | const qreal cx = flickable->contentX(); |
327 | const qreal cy = flickable->contentY(); |
328 | const qreal w = flickable->width(); |
329 | const qreal h = flickable->height(); |
330 | |
331 | const qreal tp = q->topPadding(); |
332 | const qreal lp = q->leftPadding(); |
333 | const QRectF cr = q->cursorRectangle(); |
334 | |
335 | if (cr.left() <= cx + lp) { |
336 | flickable->setContentX(cr.left() - lp); |
337 | } else { |
338 | // calculate the rectangle of the next character and ensure that |
339 | // it's visible if it's on the same line with the cursor |
340 | const qreal rp = q->rightPadding(); |
341 | const QRectF nr = q->cursorPosition() < q->length() ? q->positionToRectangle(q->cursorPosition() + 1) : QRectF(); |
342 | if (qFuzzyCompare(p1: nr.y(), p2: cr.y()) && nr.right() >= cx + lp + w - rp) |
343 | flickable->setContentX(nr.right() - w + rp); |
344 | else if (cr.right() >= cx + lp + w - rp) |
345 | flickable->setContentX(cr.right() - w + rp); |
346 | } |
347 | |
348 | if (cr.top() <= cy + tp) { |
349 | flickable->setContentY(cr.top() - tp); |
350 | } else { |
351 | const qreal bp = q->bottomPadding(); |
352 | if (cr.bottom() >= cy + tp + h - bp && cr.bottom() <= flickable->contentHeight()) |
353 | flickable->setContentY(cr.bottom() - h + bp); |
354 | } |
355 | } |
356 | |
357 | void QQuickTextAreaPrivate::resizeFlickableControl() |
358 | { |
359 | Q_Q(QQuickTextArea); |
360 | if (!flickable) |
361 | return; |
362 | |
363 | const qreal w = wrapMode == QQuickTextArea::NoWrap ? qMax(a: flickable->width(), b: flickable->contentWidth()) : flickable->width(); |
364 | const qreal h = qMax(a: flickable->height(), b: flickable->contentHeight()); |
365 | q->setSize(QSizeF(w, h)); |
366 | |
367 | resizeBackground(); |
368 | } |
369 | |
370 | void QQuickTextAreaPrivate::resizeFlickableContent() |
371 | { |
372 | Q_Q(QQuickTextArea); |
373 | if (!flickable) |
374 | return; |
375 | |
376 | flickable->setContentWidth(q->implicitWidth()); |
377 | flickable->setContentHeight(q->implicitHeight()); |
378 | } |
379 | |
380 | void QQuickTextAreaPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) |
381 | { |
382 | Q_UNUSED(diff); |
383 | if (!resizingBackground && item == background) { |
384 | QQuickItemPrivate *p = QQuickItemPrivate::get(item); |
385 | // Only set hasBackgroundWidth/Height if it was a width/height change, |
386 | // otherwise we're prevented from setting a width/height in the future. |
387 | if (change.widthChange()) |
388 | extra.value().hasBackgroundWidth = p->widthValid(); |
389 | if (change.heightChange()) |
390 | extra.value().hasBackgroundHeight = p->heightValid(); |
391 | } |
392 | |
393 | if (flickable) |
394 | resizeFlickableControl(); |
395 | else |
396 | resizeBackground(); |
397 | } |
398 | |
399 | qreal QQuickTextAreaPrivate::getImplicitWidth() const |
400 | { |
401 | return QQuickItemPrivate::getImplicitWidth(); |
402 | } |
403 | |
404 | qreal QQuickTextAreaPrivate::getImplicitHeight() const |
405 | { |
406 | return QQuickItemPrivate::getImplicitHeight(); |
407 | } |
408 | |
409 | void QQuickTextAreaPrivate::implicitWidthChanged() |
410 | { |
411 | Q_Q(QQuickTextArea); |
412 | QQuickItemPrivate::implicitWidthChanged(); |
413 | emit q->implicitWidthChanged3(); |
414 | } |
415 | |
416 | void QQuickTextAreaPrivate::implicitHeightChanged() |
417 | { |
418 | Q_Q(QQuickTextArea); |
419 | QQuickItemPrivate::implicitHeightChanged(); |
420 | emit q->implicitHeightChanged3(); |
421 | } |
422 | |
423 | void QQuickTextAreaPrivate::readOnlyChanged(bool isReadOnly) |
424 | { |
425 | Q_UNUSED(isReadOnly); |
426 | #if QT_CONFIG(accessibility) |
427 | if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(object: q_func())) |
428 | accessibleAttached->set_readOnly(isReadOnly); |
429 | #endif |
430 | } |
431 | |
432 | #if QT_CONFIG(accessibility) |
433 | void QQuickTextAreaPrivate::accessibilityActiveChanged(bool active) |
434 | { |
435 | if (!active) |
436 | return; |
437 | |
438 | Q_Q(QQuickTextArea); |
439 | QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(object: qmlAttachedPropertiesObject<QQuickAccessibleAttached>(obj: q, create: true)); |
440 | Q_ASSERT(accessibleAttached); |
441 | accessibleAttached->setRole(effectiveAccessibleRole()); |
442 | accessibleAttached->set_readOnly(q->isReadOnly()); |
443 | accessibleAttached->setDescription(placeholder); |
444 | } |
445 | |
446 | QAccessible::Role QQuickTextAreaPrivate::accessibleRole() const |
447 | { |
448 | return QAccessible::EditableText; |
449 | } |
450 | #endif |
451 | |
452 | void QQuickTextAreaPrivate::cancelBackground() |
453 | { |
454 | Q_Q(QQuickTextArea); |
455 | quickCancelDeferred(object: q, property: backgroundName()); |
456 | } |
457 | |
458 | void QQuickTextAreaPrivate::executeBackground(bool complete) |
459 | { |
460 | Q_Q(QQuickTextArea); |
461 | if (background.wasExecuted()) |
462 | return; |
463 | |
464 | if (!background || complete) |
465 | quickBeginDeferred(object: q, property: backgroundName(), delegate&: background); |
466 | if (complete) |
467 | quickCompleteDeferred(object: q, property: backgroundName(), delegate&: background); |
468 | } |
469 | |
470 | void QQuickTextAreaPrivate::itemImplicitWidthChanged(QQuickItem *item) |
471 | { |
472 | Q_Q(QQuickTextArea); |
473 | if (item == background) |
474 | emit q->implicitBackgroundWidthChanged(); |
475 | } |
476 | |
477 | void QQuickTextAreaPrivate::itemImplicitHeightChanged(QQuickItem *item) |
478 | { |
479 | Q_Q(QQuickTextArea); |
480 | if (item == background) |
481 | emit q->implicitBackgroundHeightChanged(); |
482 | } |
483 | |
484 | void QQuickTextAreaPrivate::itemDestroyed(QQuickItem *item) |
485 | { |
486 | Q_Q(QQuickTextArea); |
487 | if (item == background) { |
488 | background = nullptr; |
489 | emit q->implicitBackgroundWidthChanged(); |
490 | emit q->implicitBackgroundHeightChanged(); |
491 | } else if (item == flickable) { |
492 | detachFlickable(); |
493 | } |
494 | } |
495 | |
496 | QPalette QQuickTextAreaPrivate::defaultPalette() const |
497 | { |
498 | return QQuickTheme::palette(scope: QQuickTheme::TextArea); |
499 | } |
500 | |
501 | QQuickTextArea::QQuickTextArea(QQuickItem *parent) |
502 | : QQuickTextEdit(*(new QQuickTextAreaPrivate), parent) |
503 | { |
504 | Q_D(QQuickTextArea); |
505 | setActiveFocusOnTab(true); |
506 | setAcceptedMouseButtons(Qt::AllButtons); |
507 | d->setImplicitResizeEnabled(false); |
508 | d->pressHandler.control = this; |
509 | |
510 | QObjectPrivate::connect(sender: this, signal: &QQuickTextEdit::readOnlyChanged, |
511 | receiverPrivate: d, slot: &QQuickTextAreaPrivate::readOnlyChanged); |
512 | #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) |
513 | if (qEnvironmentVariable(varName: "QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR" ) == u"old"_s ) |
514 | QQuickTextEdit::setOldSelectionDefault(); |
515 | #endif |
516 | } |
517 | |
518 | QQuickTextArea::~QQuickTextArea() |
519 | { |
520 | Q_D(QQuickTextArea); |
521 | if (d->flickable) |
522 | d->detachFlickable(); |
523 | QQuickControlPrivate::removeImplicitSizeListener(item: d->background, listener: d, changes: QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); |
524 | } |
525 | |
526 | QQuickTextAreaAttached *QQuickTextArea::qmlAttachedProperties(QObject *object) |
527 | { |
528 | return new QQuickTextAreaAttached(object); |
529 | } |
530 | |
531 | QFont QQuickTextArea::font() const |
532 | { |
533 | Q_D(const QQuickTextArea); |
534 | QFont font = QQuickTextEdit::font(); |
535 | // The resolve mask should inherit from the requestedFont |
536 | font.setResolveMask(d->extra.value().requestedFont.resolveMask()); |
537 | return font; |
538 | } |
539 | |
540 | void QQuickTextArea::setFont(const QFont &font) |
541 | { |
542 | Q_D(QQuickTextArea); |
543 | if (d->extra.value().requestedFont.resolveMask() == font.resolveMask() && d->extra.value().requestedFont == font) |
544 | return; |
545 | |
546 | d->extra.value().requestedFont = font; |
547 | d->resolveFont(); |
548 | } |
549 | |
550 | /*! |
551 | \qmlproperty Item QtQuick.Controls::TextArea::background |
552 | |
553 | This property holds the background item. |
554 | |
555 | \input qquickcontrol-background.qdocinc notes |
556 | |
557 | \sa {Customizing TextArea} |
558 | */ |
559 | QQuickItem *QQuickTextArea::background() const |
560 | { |
561 | QQuickTextAreaPrivate *d = const_cast<QQuickTextAreaPrivate *>(d_func()); |
562 | if (!d->background) |
563 | d->executeBackground(); |
564 | return d->background; |
565 | } |
566 | |
567 | void QQuickTextArea::setBackground(QQuickItem *background) |
568 | { |
569 | Q_D(QQuickTextArea); |
570 | if (d->background == background) |
571 | return; |
572 | |
573 | QQuickControlPrivate::warnIfCustomizationNotSupported(control: this, item: background, QStringLiteral("background" )); |
574 | |
575 | if (!d->background.isExecuting()) |
576 | d->cancelBackground(); |
577 | |
578 | const qreal oldImplicitBackgroundWidth = implicitBackgroundWidth(); |
579 | const qreal oldImplicitBackgroundHeight = implicitBackgroundHeight(); |
580 | |
581 | if (d->extra.isAllocated()) { |
582 | d->extra.value().hasBackgroundWidth = false; |
583 | d->extra.value().hasBackgroundHeight = false; |
584 | } |
585 | |
586 | QQuickControlPrivate::removeImplicitSizeListener(item: d->background, listener: d, changes: QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); |
587 | QQuickControlPrivate::hideOldItem(item: d->background); |
588 | d->background = background; |
589 | |
590 | if (background) { |
591 | QQuickItemPrivate *p = QQuickItemPrivate::get(item: background); |
592 | if (p->widthValid() || p->heightValid()) { |
593 | d->extra.value().hasBackgroundWidth = p->widthValid(); |
594 | d->extra.value().hasBackgroundHeight = p->heightValid(); |
595 | } |
596 | if (d->flickable) |
597 | background->setParentItem(d->flickable); |
598 | else |
599 | background->setParentItem(this); |
600 | if (qFuzzyIsNull(d: background->z())) |
601 | background->setZ(-1); |
602 | if (isComponentComplete()) |
603 | d->resizeBackground(); |
604 | QQuickControlPrivate::addImplicitSizeListener(item: background, listener: d, changes: QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); |
605 | } |
606 | |
607 | if (!qFuzzyCompare(p1: oldImplicitBackgroundWidth, p2: implicitBackgroundWidth())) |
608 | emit implicitBackgroundWidthChanged(); |
609 | if (!qFuzzyCompare(p1: oldImplicitBackgroundHeight, p2: implicitBackgroundHeight())) |
610 | emit implicitBackgroundHeightChanged(); |
611 | if (!d->background.isExecuting()) |
612 | emit backgroundChanged(); |
613 | } |
614 | |
615 | /*! |
616 | \qmlproperty string QtQuick.Controls::TextArea::placeholderText |
617 | |
618 | This property holds the short hint that is displayed in the text area before |
619 | the user enters a value. |
620 | */ |
621 | QString QQuickTextArea::placeholderText() const |
622 | { |
623 | Q_D(const QQuickTextArea); |
624 | return d->placeholder; |
625 | } |
626 | |
627 | void QQuickTextArea::setPlaceholderText(const QString &text) |
628 | { |
629 | Q_D(QQuickTextArea); |
630 | if (d->placeholder == text) |
631 | return; |
632 | |
633 | d->placeholder = text; |
634 | #if QT_CONFIG(accessibility) |
635 | if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(object: this)) |
636 | accessibleAttached->setDescription(text); |
637 | #endif |
638 | emit placeholderTextChanged(); |
639 | } |
640 | |
641 | /*! |
642 | \qmlproperty color QtQuick.Controls::TextArea::placeholderTextColor |
643 | \since QtQuick.Controls 2.5 (Qt 5.12) |
644 | |
645 | This property holds the color of placeholderText. |
646 | |
647 | \sa placeholderText |
648 | */ |
649 | QColor QQuickTextArea::placeholderTextColor() const |
650 | { |
651 | Q_D(const QQuickTextArea); |
652 | return d->placeholderColor; |
653 | } |
654 | |
655 | void QQuickTextArea::setPlaceholderTextColor(const QColor &color) |
656 | { |
657 | Q_D(QQuickTextArea); |
658 | if (d->placeholderColor == color) |
659 | return; |
660 | |
661 | d->placeholderColor = color; |
662 | emit placeholderTextColorChanged(); |
663 | } |
664 | |
665 | /*! |
666 | \qmlproperty enumeration QtQuick.Controls::TextArea::focusReason |
667 | |
668 | \include qquickcontrol-focusreason.qdocinc |
669 | */ |
670 | Qt::FocusReason QQuickTextArea::focusReason() const |
671 | { |
672 | Q_D(const QQuickTextArea); |
673 | return d->focusReason; |
674 | } |
675 | |
676 | void QQuickTextArea::setFocusReason(Qt::FocusReason reason) |
677 | { |
678 | Q_D(QQuickTextArea); |
679 | if (d->focusReason == reason) |
680 | return; |
681 | |
682 | d->focusReason = reason; |
683 | emit focusReasonChanged(); |
684 | } |
685 | |
686 | bool QQuickTextArea::contains(const QPointF &point) const |
687 | { |
688 | Q_D(const QQuickTextArea); |
689 | if (d->flickable && !d->flickable->contains(point: d->flickable->mapFromItem(item: this, point))) |
690 | return false; |
691 | return QQuickTextEdit::contains(point); |
692 | } |
693 | |
694 | /*! |
695 | \since QtQuick.Controls 2.1 (Qt 5.8) |
696 | \qmlproperty bool QtQuick.Controls::TextArea::hovered |
697 | \readonly |
698 | |
699 | This property holds whether the text area is hovered. |
700 | |
701 | \sa hoverEnabled |
702 | */ |
703 | bool QQuickTextArea::isHovered() const |
704 | { |
705 | #if QT_CONFIG(quicktemplates2_hover) |
706 | Q_D(const QQuickTextArea); |
707 | return d->hovered; |
708 | #else |
709 | return false; |
710 | #endif |
711 | } |
712 | |
713 | void QQuickTextArea::setHovered(bool hovered) |
714 | { |
715 | #if QT_CONFIG(quicktemplates2_hover) |
716 | Q_D(QQuickTextArea); |
717 | if (hovered == d->hovered) |
718 | return; |
719 | |
720 | d->hovered = hovered; |
721 | emit hoveredChanged(); |
722 | #else |
723 | Q_UNUSED(hovered); |
724 | #endif |
725 | } |
726 | |
727 | /*! |
728 | \since QtQuick.Controls 2.1 (Qt 5.8) |
729 | \qmlproperty bool QtQuick.Controls::TextArea::hoverEnabled |
730 | |
731 | This property determines whether the text area accepts hover events. The default value is \c true. |
732 | |
733 | \sa hovered |
734 | */ |
735 | bool QQuickTextArea::isHoverEnabled() const |
736 | { |
737 | #if QT_CONFIG(quicktemplates2_hover) |
738 | Q_D(const QQuickTextArea); |
739 | return d->hoverEnabled; |
740 | #else |
741 | return false; |
742 | #endif |
743 | } |
744 | |
745 | void QQuickTextArea::setHoverEnabled(bool enabled) |
746 | { |
747 | #if QT_CONFIG(quicktemplates2_hover) |
748 | Q_D(QQuickTextArea); |
749 | if (d->explicitHoverEnabled && enabled == d->hoverEnabled) |
750 | return; |
751 | |
752 | d->updateHoverEnabled(enabled, xplicit: true); // explicit=true |
753 | #else |
754 | Q_UNUSED(enabled); |
755 | #endif |
756 | } |
757 | |
758 | void QQuickTextArea::resetHoverEnabled() |
759 | { |
760 | #if QT_CONFIG(quicktemplates2_hover) |
761 | Q_D(QQuickTextArea); |
762 | if (!d->explicitHoverEnabled) |
763 | return; |
764 | |
765 | d->explicitHoverEnabled = false; |
766 | d->updateHoverEnabled(enabled: QQuickControlPrivate::calcHoverEnabled(item: d->parentItem), xplicit: false); // explicit=false |
767 | #endif |
768 | } |
769 | |
770 | /*! |
771 | \since QtQuick.Controls 2.5 (Qt 5.12) |
772 | \qmlproperty real QtQuick.Controls::TextArea::implicitBackgroundWidth |
773 | \readonly |
774 | |
775 | This property holds the implicit background width. |
776 | |
777 | The value is equal to \c {background ? background.implicitWidth : 0}. |
778 | |
779 | \sa implicitBackgroundHeight |
780 | */ |
781 | qreal QQuickTextArea::implicitBackgroundWidth() const |
782 | { |
783 | Q_D(const QQuickTextArea); |
784 | if (!d->background) |
785 | return 0; |
786 | return d->background->implicitWidth(); |
787 | } |
788 | |
789 | /*! |
790 | \since QtQuick.Controls 2.5 (Qt 5.12) |
791 | \qmlproperty real QtQuick.Controls::TextArea::implicitBackgroundHeight |
792 | \readonly |
793 | |
794 | This property holds the implicit background height. |
795 | |
796 | The value is equal to \c {background ? background.implicitHeight : 0}. |
797 | |
798 | \sa implicitBackgroundWidth |
799 | */ |
800 | qreal QQuickTextArea::implicitBackgroundHeight() const |
801 | { |
802 | Q_D(const QQuickTextArea); |
803 | if (!d->background) |
804 | return 0; |
805 | return d->background->implicitHeight(); |
806 | } |
807 | |
808 | /*! |
809 | \since QtQuick.Controls 2.5 (Qt 5.12) |
810 | \qmlproperty real QtQuick.Controls::TextArea::topInset |
811 | |
812 | This property holds the top inset for the background. |
813 | |
814 | \sa {Control Layout}, bottomInset |
815 | */ |
816 | qreal QQuickTextArea::topInset() const |
817 | { |
818 | Q_D(const QQuickTextArea); |
819 | return d->getTopInset(); |
820 | } |
821 | |
822 | void QQuickTextArea::setTopInset(qreal inset) |
823 | { |
824 | Q_D(QQuickTextArea); |
825 | d->setTopInset(value: inset); |
826 | } |
827 | |
828 | void QQuickTextArea::resetTopInset() |
829 | { |
830 | Q_D(QQuickTextArea); |
831 | d->setTopInset(value: 0, reset: true); |
832 | } |
833 | |
834 | /*! |
835 | \since QtQuick.Controls 2.5 (Qt 5.12) |
836 | \qmlproperty real QtQuick.Controls::TextArea::leftInset |
837 | |
838 | This property holds the left inset for the background. |
839 | |
840 | \sa {Control Layout}, rightInset |
841 | */ |
842 | qreal QQuickTextArea::leftInset() const |
843 | { |
844 | Q_D(const QQuickTextArea); |
845 | return d->getLeftInset(); |
846 | } |
847 | |
848 | void QQuickTextArea::setLeftInset(qreal inset) |
849 | { |
850 | Q_D(QQuickTextArea); |
851 | d->setLeftInset(value: inset); |
852 | } |
853 | |
854 | void QQuickTextArea::resetLeftInset() |
855 | { |
856 | Q_D(QQuickTextArea); |
857 | d->setLeftInset(value: 0, reset: true); |
858 | } |
859 | |
860 | /*! |
861 | \since QtQuick.Controls 2.5 (Qt 5.12) |
862 | \qmlproperty real QtQuick.Controls::TextArea::rightInset |
863 | |
864 | This property holds the right inset for the background. |
865 | |
866 | \sa {Control Layout}, leftInset |
867 | */ |
868 | qreal QQuickTextArea::rightInset() const |
869 | { |
870 | Q_D(const QQuickTextArea); |
871 | return d->getRightInset(); |
872 | } |
873 | |
874 | void QQuickTextArea::setRightInset(qreal inset) |
875 | { |
876 | Q_D(QQuickTextArea); |
877 | d->setRightInset(value: inset); |
878 | } |
879 | |
880 | void QQuickTextArea::resetRightInset() |
881 | { |
882 | Q_D(QQuickTextArea); |
883 | d->setRightInset(value: 0, reset: true); |
884 | } |
885 | |
886 | /*! |
887 | \since QtQuick.Controls 2.5 (Qt 5.12) |
888 | \qmlproperty real QtQuick.Controls::TextArea::bottomInset |
889 | |
890 | This property holds the bottom inset for the background. |
891 | |
892 | \sa {Control Layout}, topInset |
893 | */ |
894 | qreal QQuickTextArea::bottomInset() const |
895 | { |
896 | Q_D(const QQuickTextArea); |
897 | return d->getBottomInset(); |
898 | } |
899 | |
900 | void QQuickTextArea::setBottomInset(qreal inset) |
901 | { |
902 | Q_D(QQuickTextArea); |
903 | d->setBottomInset(value: inset); |
904 | } |
905 | |
906 | void QQuickTextArea::resetBottomInset() |
907 | { |
908 | Q_D(QQuickTextArea); |
909 | d->setBottomInset(value: 0, reset: true); |
910 | } |
911 | |
912 | void QQuickTextArea::classBegin() |
913 | { |
914 | Q_D(QQuickTextArea); |
915 | QQuickTextEdit::classBegin(); |
916 | d->resolveFont(); |
917 | } |
918 | |
919 | void QQuickTextArea::componentComplete() |
920 | { |
921 | Q_D(QQuickTextArea); |
922 | d->executeBackground(complete: true); |
923 | QQuickTextEdit::componentComplete(); |
924 | d->resizeBackground(); |
925 | #if QT_CONFIG(quicktemplates2_hover) |
926 | if (!d->explicitHoverEnabled) |
927 | setAcceptHoverEvents(QQuickControlPrivate::calcHoverEnabled(item: d->parentItem)); |
928 | #endif |
929 | #if QT_CONFIG(accessibility) |
930 | if (QAccessible::isActive()) |
931 | d->accessibilityActiveChanged(active: true); |
932 | #endif |
933 | } |
934 | |
935 | void QQuickTextArea::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
936 | { |
937 | Q_D(QQuickTextArea); |
938 | QQuickTextEdit::itemChange(change, value); |
939 | switch (change) { |
940 | case ItemEnabledHasChanged: |
941 | break; |
942 | case ItemSceneChange: |
943 | case ItemParentHasChanged: |
944 | if ((change == ItemParentHasChanged && value.item) || (change == ItemSceneChange && value.window)) { |
945 | d->resolveFont(); |
946 | #if QT_CONFIG(quicktemplates2_hover) |
947 | if (!d->explicitHoverEnabled) |
948 | d->updateHoverEnabled(enabled: QQuickControlPrivate::calcHoverEnabled(item: d->parentItem), xplicit: false); // explicit=false |
949 | #endif |
950 | if (change == ItemParentHasChanged) { |
951 | QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(object: value.item->parentItem()); |
952 | if (flickable) { |
953 | QQuickScrollView *scrollView = qobject_cast<QQuickScrollView *>(object: flickable->parentItem()); |
954 | if (scrollView) |
955 | d->attachFlickable(item: flickable); |
956 | } |
957 | } |
958 | } |
959 | break; |
960 | default: |
961 | break; |
962 | } |
963 | } |
964 | |
965 | void QQuickTextArea::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
966 | { |
967 | Q_D(QQuickTextArea); |
968 | QQuickTextEdit::geometryChange(newGeometry, oldGeometry); |
969 | d->resizeBackground(); |
970 | } |
971 | |
972 | void QQuickTextArea::insetChange(const QMarginsF &newInset, const QMarginsF &oldInset) |
973 | { |
974 | Q_D(QQuickTextArea); |
975 | Q_UNUSED(newInset); |
976 | Q_UNUSED(oldInset); |
977 | d->resizeBackground(); |
978 | } |
979 | |
980 | QSGNode *QQuickTextArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) |
981 | { |
982 | Q_D(QQuickTextArea); |
983 | QQuickDefaultClipNode *clipNode = static_cast<QQuickDefaultClipNode *>(oldNode); |
984 | if (!clipNode) |
985 | clipNode = new QQuickDefaultClipNode(QRectF()); |
986 | |
987 | QQuickItem *clipper = this; |
988 | if (d->flickable) |
989 | clipper = d->flickable; |
990 | |
991 | const QRectF cr = clipper->clipRect().adjusted( |
992 | xp1: leftPadding(), yp1: topPadding(), |
993 | xp2: (!d->cursorItem && effectiveHAlign() == HAlignment::AlignRight ? 1 : 0) - rightPadding(), |
994 | yp2: -bottomPadding()); |
995 | |
996 | clipNode->setRect(!d->flickable ? cr : cr.translated(dx: d->flickable->contentX(), dy: d->flickable->contentY())); |
997 | clipNode->update(); |
998 | |
999 | QSGNode *textNode = QQuickTextEdit::updatePaintNode(oldNode: clipNode->firstChild(), updatePaintNodeData: data); |
1000 | if (!textNode->parent()) |
1001 | clipNode->appendChildNode(node: textNode); |
1002 | |
1003 | if (d->cursorItem) { |
1004 | QQuickDefaultClipNode *cursorNode = QQuickItemPrivate::get(item: d->cursorItem)->clipNode(); |
1005 | if (cursorNode) |
1006 | cursorNode->setClipRect(d->cursorItem->mapRectFromItem(item: clipper, rect: cr)); |
1007 | } |
1008 | |
1009 | return clipNode; |
1010 | } |
1011 | |
1012 | void QQuickTextArea::focusInEvent(QFocusEvent *event) |
1013 | { |
1014 | QQuickTextEdit::focusInEvent(event); |
1015 | setFocusReason(event->reason()); |
1016 | } |
1017 | |
1018 | void QQuickTextArea::focusOutEvent(QFocusEvent *event) |
1019 | { |
1020 | QQuickTextEdit::focusOutEvent(event); |
1021 | setFocusReason(event->reason()); |
1022 | } |
1023 | |
1024 | #if QT_CONFIG(quicktemplates2_hover) |
1025 | void QQuickTextArea::hoverEnterEvent(QHoverEvent *event) |
1026 | { |
1027 | Q_D(QQuickTextArea); |
1028 | QQuickTextEdit::hoverEnterEvent(event); |
1029 | setHovered(d->hoverEnabled); |
1030 | event->ignore(); |
1031 | } |
1032 | |
1033 | void QQuickTextArea::hoverLeaveEvent(QHoverEvent *event) |
1034 | { |
1035 | QQuickTextEdit::hoverLeaveEvent(event); |
1036 | setHovered(false); |
1037 | event->ignore(); |
1038 | } |
1039 | #endif |
1040 | |
1041 | void QQuickTextArea::mousePressEvent(QMouseEvent *event) |
1042 | { |
1043 | Q_D(QQuickTextArea); |
1044 | d->pressHandler.mousePressEvent(event); |
1045 | if (d->pressHandler.isActive()) { |
1046 | if (d->pressHandler.delayedMousePressEvent) { |
1047 | QQuickTextEdit::mousePressEvent(event: d->pressHandler.delayedMousePressEvent.get()); |
1048 | d->pressHandler.clearDelayedMouseEvent(); |
1049 | } |
1050 | // Calling the base class implementation will result in QQuickTextControl's |
1051 | // press handler being called, which ignores events that aren't Qt::LeftButton. |
1052 | const bool wasAccepted = event->isAccepted(); |
1053 | QQuickTextEdit::mousePressEvent(event); |
1054 | if (wasAccepted) |
1055 | event->accept(); |
1056 | } |
1057 | } |
1058 | |
1059 | void QQuickTextArea::mouseMoveEvent(QMouseEvent *event) |
1060 | { |
1061 | Q_D(QQuickTextArea); |
1062 | d->pressHandler.mouseMoveEvent(event); |
1063 | if (d->pressHandler.isActive()) { |
1064 | if (d->pressHandler.delayedMousePressEvent) { |
1065 | QQuickTextEdit::mousePressEvent(event: d->pressHandler.delayedMousePressEvent.get()); |
1066 | d->pressHandler.clearDelayedMouseEvent(); |
1067 | } |
1068 | QQuickTextEdit::mouseMoveEvent(event); |
1069 | } |
1070 | } |
1071 | |
1072 | void QQuickTextArea::mouseReleaseEvent(QMouseEvent *event) |
1073 | { |
1074 | Q_D(QQuickTextArea); |
1075 | d->pressHandler.mouseReleaseEvent(event); |
1076 | if (d->pressHandler.isActive()) { |
1077 | if (d->pressHandler.delayedMousePressEvent) { |
1078 | QQuickTextEdit::mousePressEvent(event: d->pressHandler.delayedMousePressEvent.get()); |
1079 | d->pressHandler.clearDelayedMouseEvent(); |
1080 | } |
1081 | QQuickTextEdit::mouseReleaseEvent(event); |
1082 | } |
1083 | } |
1084 | |
1085 | void QQuickTextArea::mouseDoubleClickEvent(QMouseEvent *event) |
1086 | { |
1087 | Q_D(QQuickTextArea); |
1088 | if (d->pressHandler.delayedMousePressEvent) { |
1089 | QQuickTextEdit::mousePressEvent(event: d->pressHandler.delayedMousePressEvent.get()); |
1090 | d->pressHandler.clearDelayedMouseEvent(); |
1091 | } |
1092 | QQuickTextEdit::mouseDoubleClickEvent(event); |
1093 | } |
1094 | |
1095 | void QQuickTextArea::timerEvent(QTimerEvent *event) |
1096 | { |
1097 | Q_D(QQuickTextArea); |
1098 | if (event->timerId() == d->pressHandler.timer.timerId()) |
1099 | d->pressHandler.timerEvent(event); |
1100 | else |
1101 | QQuickTextEdit::timerEvent(event); |
1102 | } |
1103 | |
1104 | class QQuickTextAreaAttachedPrivate : public QObjectPrivate |
1105 | { |
1106 | public: |
1107 | QQuickTextArea *control = nullptr; |
1108 | }; |
1109 | |
1110 | QQuickTextAreaAttached::QQuickTextAreaAttached(QObject *parent) |
1111 | : QObject(*(new QQuickTextAreaAttachedPrivate), parent) |
1112 | { |
1113 | } |
1114 | |
1115 | /*! |
1116 | \qmlattachedproperty TextArea QtQuick.Controls::TextArea::flickable |
1117 | |
1118 | This property attaches a text area to a \l Flickable. |
1119 | |
1120 | \sa ScrollBar, ScrollIndicator, {Scrollable TextArea} |
1121 | */ |
1122 | QQuickTextArea *QQuickTextAreaAttached::flickable() const |
1123 | { |
1124 | Q_D(const QQuickTextAreaAttached); |
1125 | return d->control; |
1126 | } |
1127 | |
1128 | void QQuickTextAreaAttached::setFlickable(QQuickTextArea *control) |
1129 | { |
1130 | Q_D(QQuickTextAreaAttached); |
1131 | QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(object: parent()); |
1132 | if (!flickable) { |
1133 | qmlWarning(me: parent()) << "TextArea must be attached to a Flickable" ; |
1134 | return; |
1135 | } |
1136 | |
1137 | if (d->control == control) |
1138 | return; |
1139 | |
1140 | if (d->control) |
1141 | QQuickTextAreaPrivate::get(item: d->control)->detachFlickable(); |
1142 | |
1143 | d->control = control; |
1144 | |
1145 | if (control) |
1146 | QQuickTextAreaPrivate::get(item: control)->attachFlickable(item: flickable); |
1147 | |
1148 | emit flickableChanged(); |
1149 | } |
1150 | |
1151 | QT_END_NAMESPACE |
1152 | |
1153 | #include "moc_qquicktextarea_p.cpp" |
1154 | |