1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmlbind_p.h" |
5 | |
6 | #include <private/qqmlanybinding_p.h> |
7 | #include <private/qqmlbinding_p.h> |
8 | #include <private/qqmlcomponent_p.h> |
9 | #include <private/qqmlmetatype_p.h> |
10 | #include <private/qqmlnullablevalue_p.h> |
11 | #include <private/qqmlproperty_p.h> |
12 | #include <private/qqmlvmemetaobject_p.h> |
13 | #include <private/qv4persistent_p.h> |
14 | #include <private/qv4qmlcontext_p.h> |
15 | #include <private/qv4resolvedtypereference_p.h> |
16 | |
17 | #include <QtQml/qqmlcontext.h> |
18 | #include <QtQml/qqmlengine.h> |
19 | #include <QtQml/qqmlinfo.h> |
20 | #include <QtQml/qqmlproperty.h> |
21 | #include <QtQml/qqmlpropertymap.h> |
22 | |
23 | #include <QtCore/private/qobject_p.h> |
24 | |
25 | #include <QtCore/qdebug.h> |
26 | #include <QtCore/qfile.h> |
27 | #include <QtCore/qloggingcategory.h> |
28 | #include <QtCore/qtimer.h> |
29 | |
30 | QT_BEGIN_NAMESPACE |
31 | |
32 | Q_DECLARE_LOGGING_CATEGORY(lcBindingRemoval) |
33 | |
34 | enum class QQmlBindEntryKind: quint8 { |
35 | V4Value, |
36 | Variant, |
37 | Binding, |
38 | None |
39 | }; |
40 | |
41 | /*! |
42 | * \internal |
43 | * QQmlBindEntryContent can store one of QV4::Value, QVariant, QQmlAnyBinding, or nothing, |
44 | * as denoted by QQmlBindEntryKind. It expects the calling code to know what is stored at |
45 | * any time. On each method invocation, the current kind has to be passed as last parameter |
46 | * and the new kind is returned. |
47 | */ |
48 | union QQmlBindEntryContent { |
49 | Q_DISABLE_COPY_MOVE(QQmlBindEntryContent) |
50 | public: |
51 | QQmlBindEntryContent() {} |
52 | ~QQmlBindEntryContent() {} |
53 | |
54 | [[nodiscard]] QQmlBindEntryKind set( |
55 | QQmlBindEntryContent &&other, QQmlBindEntryKind newKind, QQmlBindEntryKind oldKind) |
56 | { |
57 | silentDestroy(oldKind); |
58 | switch (newKind) { |
59 | case QQmlBindEntryKind::V4Value: |
60 | new (&v4Value) QV4::PersistentValue(std::move(other.v4Value)); |
61 | break; |
62 | case QQmlBindEntryKind::Variant: |
63 | new (&variant) QVariant(std::move(other.variant)); |
64 | break; |
65 | case QQmlBindEntryKind::Binding: |
66 | new (&binding) QQmlAnyBinding(std::move(other.binding)); |
67 | break; |
68 | case QQmlBindEntryKind::None: |
69 | break; |
70 | } |
71 | return newKind; |
72 | } |
73 | |
74 | [[nodiscard]] QQmlBindEntryKind set( |
75 | const QQmlBindEntryContent &other, QQmlBindEntryKind newKind, QQmlBindEntryKind oldKind) |
76 | { |
77 | silentDestroy(oldKind); |
78 | switch (newKind) { |
79 | case QQmlBindEntryKind::V4Value: |
80 | new (&v4Value) QV4::PersistentValue(other.v4Value); |
81 | break; |
82 | case QQmlBindEntryKind::Variant: |
83 | new (&variant) QVariant(other.variant); |
84 | break; |
85 | case QQmlBindEntryKind::Binding: |
86 | new (&binding) QQmlAnyBinding(other.binding); |
87 | break; |
88 | case QQmlBindEntryKind::None: |
89 | break; |
90 | } |
91 | return newKind; |
92 | } |
93 | |
94 | [[nodiscard]] QQmlBindEntryKind destroy(QQmlBindEntryKind kind) |
95 | { |
96 | switch (kind) { |
97 | case QQmlBindEntryKind::V4Value: |
98 | v4Value.~PersistentValue(); |
99 | break; |
100 | case QQmlBindEntryKind::Variant: |
101 | variant.~QVariant(); |
102 | break; |
103 | case QQmlBindEntryKind::Binding: |
104 | binding.~QQmlAnyBinding(); |
105 | break; |
106 | case QQmlBindEntryKind::None: |
107 | break; |
108 | } |
109 | return QQmlBindEntryKind::None; |
110 | } |
111 | |
112 | [[nodiscard]] QQmlBindEntryKind set(QVariant v, QQmlBindEntryKind oldKind) |
113 | { |
114 | silentDestroy(oldKind); |
115 | new (&variant) QVariant(std::move(v)); |
116 | return QQmlBindEntryKind::Variant; |
117 | } |
118 | |
119 | [[nodiscard]] QQmlBindEntryKind set(QV4::PersistentValue v, QQmlBindEntryKind oldKind) |
120 | { |
121 | silentDestroy(oldKind); |
122 | new (&v4Value) QV4::PersistentValue(std::move(v)); |
123 | return QQmlBindEntryKind::V4Value; |
124 | } |
125 | |
126 | [[nodiscard]] QQmlBindEntryKind set(QQmlAnyBinding v, QQmlBindEntryKind oldKind) |
127 | { |
128 | silentDestroy(oldKind); |
129 | new (&binding) QQmlAnyBinding(std::move(v)); |
130 | return QQmlBindEntryKind::Binding; |
131 | } |
132 | |
133 | QV4::PersistentValue v4Value; |
134 | QVariant variant; |
135 | QQmlAnyBinding binding; |
136 | |
137 | private: |
138 | void silentDestroy(QQmlBindEntryKind oldKind) |
139 | { |
140 | const QQmlBindEntryKind dead = destroy(kind: oldKind); |
141 | Q_ASSERT(dead == QQmlBindEntryKind::None); |
142 | Q_UNUSED(dead); |
143 | } |
144 | }; |
145 | |
146 | /*! |
147 | * \internal |
148 | * QQmlBindEntry holds two QQmlBindEntryContent members, along with their kinds. |
149 | * The \l current content is the value or binding the Binding element installs on |
150 | * the target if enabled (that is, if \l{when}). The \l previous content is what |
151 | * the target holds before the Binding element installs its binding or value. It |
152 | * is restored if !\l{when}. The \l prop member holds the target property. |
153 | */ |
154 | struct QQmlBindEntry |
155 | { |
156 | QQmlBindEntry() = default; |
157 | QQmlBindEntry(QQmlBindEntry &&other) noexcept : prop(std::move(other.prop)) |
158 | { |
159 | currentKind = current.set(other: std::move(other.current), newKind: other.currentKind, oldKind: currentKind); |
160 | previousKind = previous.set(other: std::move(other.previous), newKind: other.previousKind, oldKind: previousKind); |
161 | } |
162 | |
163 | QQmlBindEntry(const QQmlBindEntry &other) |
164 | : prop(other.prop) |
165 | { |
166 | currentKind = current.set(other: other.current, newKind: other.currentKind, oldKind: currentKind); |
167 | previousKind = previous.set(other: other.previous, newKind: other.previousKind, oldKind: previousKind); |
168 | } |
169 | |
170 | ~QQmlBindEntry() |
171 | { |
172 | currentKind = current.destroy(kind: currentKind); |
173 | previousKind = previous.destroy(kind: previousKind); |
174 | } |
175 | |
176 | QQmlBindEntry &operator=(QQmlBindEntry &&other) noexcept |
177 | { |
178 | if (this == &other) |
179 | return *this; |
180 | prop = std::move(other.prop); |
181 | currentKind = current.set(other: std::move(other.current), newKind: other.currentKind, oldKind: currentKind); |
182 | previousKind = previous.set(other: std::move(other.previous), newKind: other.previousKind, oldKind: previousKind); |
183 | return *this; |
184 | } |
185 | |
186 | QQmlBindEntry &operator=(const QQmlBindEntry &other) |
187 | { |
188 | if (this == &other) |
189 | return *this; |
190 | prop = other.prop; |
191 | currentKind = current.set(other: other.current, newKind: other.currentKind, oldKind: currentKind); |
192 | previousKind = previous.set(other: other.previous, newKind: other.previousKind, oldKind: previousKind); |
193 | return *this; |
194 | } |
195 | |
196 | |
197 | QQmlBindEntryContent current; |
198 | QQmlBindEntryContent previous; |
199 | QQmlProperty prop; |
200 | QQmlBindEntryKind currentKind = QQmlBindEntryKind::None; |
201 | QQmlBindEntryKind previousKind = QQmlBindEntryKind::None; |
202 | |
203 | void validate(QQmlBind *q) const; |
204 | void clearPrev(); |
205 | void setTarget(QQmlBind *q, const QQmlProperty &p); |
206 | }; |
207 | |
208 | class QQmlBindPrivate : public QObjectPrivate |
209 | { |
210 | public: |
211 | QQmlBindPrivate() |
212 | : when(true) |
213 | , componentComplete(true) |
214 | , delayed(false) |
215 | , pendingEval(false) |
216 | , restoreBinding(true) |
217 | , restoreValue(true) |
218 | , writingProperty(false) |
219 | , lastIsTarget(false) |
220 | { |
221 | } |
222 | ~QQmlBindPrivate() { } |
223 | |
224 | // There can be multiple entries when using the generalized grouped |
225 | // property syntax. One is used for target/property/value. |
226 | QVarLengthArray<QQmlBindEntry, 1> entries; |
227 | |
228 | // The target object if using the \l target property |
229 | QPointer<QObject> obj; |
230 | |
231 | // Any values we need to create a proxy for. This is necessary when |
232 | // using the \l delayed member on generalized grouped properties. See |
233 | // the note on \l delayed. |
234 | std::unique_ptr<QQmlPropertyMap> delayedValues; |
235 | |
236 | // The property name if using the \l property property. |
237 | QString propName; |
238 | |
239 | // Whether the binding is enabled. |
240 | bool when: 1; |
241 | |
242 | // Whether we have already parsed any generalized grouped properties |
243 | // we might need. |
244 | bool componentComplete:1; |
245 | |
246 | // Whether we should run in "delayed" mode and proxy all values before |
247 | // applying them to the target. |
248 | bool delayed:1; |
249 | |
250 | // In delayed mode, when using the target/property mode, the \l value |
251 | // is the proxy. Then pendingEval denotes that a timer is active to |
252 | // apply the value. We should not start another timer then. |
253 | bool pendingEval:1; |
254 | |
255 | // Whether we should restore bindings on !when. |
256 | // TODO: Deprecate this and always do. |
257 | bool restoreBinding:1; |
258 | |
259 | // Whether we should restore values on !when. |
260 | // TODO: Deprecate this and always do. |
261 | bool restoreValue:1; |
262 | |
263 | // writingProperty tracks whether we are updating the target property |
264 | // when using target/property/value. We use this information to warn about |
265 | // binding removal if we detect the target property to be updated while we |
266 | // are not writing it. This doesn't remove the Binding after all. |
267 | // For generalized grouped properties, we don't have to do this as writing |
268 | // the target property does remove the binding, just like it removes any |
269 | // other binding. |
270 | bool writingProperty:1; |
271 | |
272 | // Whether the last entry is the the target property referred to by the |
273 | // \l target object and the \l property property. This will generally be |
274 | // the case when using \l target and \l property. |
275 | bool lastIsTarget:1; |
276 | |
277 | QQmlBindEntry *targetEntry(); |
278 | void validate(QQmlBind *binding) const; |
279 | void decodeBinding( |
280 | QQmlBind *q, const QString &propertyPrefix, QQmlData::DeferredData *deferredData, |
281 | const QV4::CompiledData::Binding *binding, |
282 | QQmlComponentPrivate::ConstructionState *immediateState); |
283 | void createDelayedValues(); |
284 | void onDelayedValueChanged(QString delayedName); |
285 | void evalDelayed(); |
286 | void buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState); |
287 | }; |
288 | |
289 | void QQmlBindEntry::validate(QQmlBind *q) const |
290 | { |
291 | if (!prop.isWritable()) { |
292 | qmlWarning(me: q) << "Property '" << prop.name() << "' on " |
293 | << QQmlMetaType::prettyTypeName(object: prop.object()) << " is read-only." ; |
294 | } |
295 | } |
296 | |
297 | QQmlBindEntry *QQmlBindPrivate::targetEntry() |
298 | { |
299 | if (!lastIsTarget) { |
300 | entries.append(t: QQmlBindEntry()); |
301 | lastIsTarget = true; |
302 | } |
303 | return &entries.last(); |
304 | } |
305 | |
306 | void QQmlBindPrivate::validate(QQmlBind *q) const |
307 | { |
308 | if (!when) |
309 | return; |
310 | |
311 | qsizetype iterationEnd = entries.size(); |
312 | if (lastIsTarget) { |
313 | if (obj) { |
314 | Q_ASSERT(!entries.isEmpty()); |
315 | const QQmlBindEntry &last = entries.last(); |
316 | if (!last.prop.isValid()) { |
317 | qmlWarning(me: q) << "Property '" << propName << "' does not exist on " |
318 | << QQmlMetaType::prettyTypeName(object: obj) << "." ; |
319 | --iterationEnd; |
320 | } |
321 | } else { |
322 | --iterationEnd; |
323 | } |
324 | } |
325 | |
326 | for (qsizetype i = 0; i < iterationEnd; ++i) |
327 | entries[i].validate(q); |
328 | } |
329 | |
330 | /*! |
331 | \qmltype Binding |
332 | \instantiates QQmlBind |
333 | \inqmlmodule QtQml |
334 | \ingroup qtquick-interceptors |
335 | \brief Enables the arbitrary creation of property bindings. |
336 | |
337 | In QML, property bindings result in a dependency between the properties of |
338 | different objects. |
339 | |
340 | \section1 Binding to an Inaccessible Property |
341 | |
342 | Sometimes it is necessary to bind an object's property to |
343 | that of another object that isn't directly instantiated by QML, such as a |
344 | property of a class exported to QML by C++. You can use the Binding type |
345 | to establish this dependency; binding any value to any object's property. |
346 | |
347 | For example, in a C++ application that maps an "app.enteredText" property |
348 | into QML, you can use Binding to update the enteredText property. |
349 | |
350 | \qml |
351 | TextEdit { id: myTextField; text: "Please type here..." } |
352 | Binding { app.enteredText: myTextField.text } |
353 | \endqml |
354 | |
355 | When \c{text} changes, the C++ property \c{enteredText} will update |
356 | automatically. |
357 | |
358 | \section1 Conditional Bindings |
359 | |
360 | In some cases you may want to modify the value of a property when a certain |
361 | condition is met but leave it unmodified otherwise. Often, it's not possible |
362 | to do this with direct bindings, as you have to supply values for all |
363 | possible branches. |
364 | |
365 | For example, the code snippet below results in a warning whenever you |
366 | release the mouse. This is because the value of the binding is undefined |
367 | when the mouse isn't pressed. |
368 | |
369 | \qml |
370 | // produces warning: "Unable to assign [undefined] to double value" |
371 | value: if (mouse.pressed) mouse.mouseX |
372 | \endqml |
373 | |
374 | The Binding type can prevent this warning. |
375 | |
376 | \qml |
377 | Binding on value { |
378 | when: mouse.pressed |
379 | value: mouse.mouseX |
380 | } |
381 | \endqml |
382 | |
383 | The Binding type restores any previously set direct bindings on the |
384 | property. |
385 | |
386 | \sa {Qt QML} |
387 | */ |
388 | QQmlBind::QQmlBind(QObject *parent) |
389 | : QObject(*(new QQmlBindPrivate), parent) |
390 | { |
391 | } |
392 | |
393 | /*! |
394 | \qmlproperty bool QtQml::Binding::when |
395 | |
396 | This property holds when the binding is active. |
397 | This should be set to an expression that evaluates to true when you want the binding to be active. |
398 | |
399 | \qml |
400 | Binding { |
401 | contactName.text: name |
402 | when: list.ListView.isCurrentItem |
403 | } |
404 | \endqml |
405 | |
406 | By default, any binding or value that was set perviously is restored when the binding becomes |
407 | inactive. You can customize the restoration behavior using the \l restoreMode property. |
408 | |
409 | \sa restoreMode |
410 | */ |
411 | bool QQmlBind::when() const |
412 | { |
413 | Q_D(const QQmlBind); |
414 | return d->when; |
415 | } |
416 | |
417 | void QQmlBind::setWhen(bool v) |
418 | { |
419 | Q_D(QQmlBind); |
420 | if (d->when == v) |
421 | return; |
422 | |
423 | d->when = v; |
424 | if (v && d->componentComplete) |
425 | d->validate(q: this); |
426 | eval(); |
427 | } |
428 | |
429 | /*! |
430 | \qmlproperty QtObject QtQml::Binding::target |
431 | |
432 | The object to be updated. You only need to use this property if you can't |
433 | supply the binding target declaratively. The following two pieces of code |
434 | are equivalent. |
435 | |
436 | \qml |
437 | Binding { contactName.text: name } |
438 | \endqml |
439 | |
440 | \qml |
441 | Binding { |
442 | target: contactName |
443 | property: "text" |
444 | value: name |
445 | } |
446 | \endqml |
447 | |
448 | The former one is much more compact, but you cannot replace the target |
449 | object or property at run time. With the latter one you can. |
450 | */ |
451 | QObject *QQmlBind::object() |
452 | { |
453 | Q_D(const QQmlBind); |
454 | return d->obj; |
455 | } |
456 | |
457 | void QQmlBind::setObject(QObject *obj) |
458 | { |
459 | Q_D(QQmlBind); |
460 | if (d->obj && d->when) { |
461 | /* if we switch the object at runtime, we need to restore the |
462 | previous binding on the old object before continuing */ |
463 | d->when = false; |
464 | eval(); |
465 | d->when = true; |
466 | } |
467 | /* if "when" and "target" depend on the same property, we might |
468 | end up here before we could have updated "when". So reevaluate |
469 | when manually here. |
470 | */ |
471 | const QQmlProperty whenProp(this, QLatin1StringView("when" )); |
472 | const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(prop: whenProp); |
473 | if (auto abstractBinding = potentialWhenBinding.asAbstractBinding()) { |
474 | QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding); |
475 | if (binding->hasValidContext()) { |
476 | const auto boolType = QMetaType::fromType<bool>(); |
477 | bool when; |
478 | binding->evaluate(result: &when, type: boolType); |
479 | d->when = when; |
480 | } |
481 | } |
482 | d->obj = obj; |
483 | if (d->componentComplete) { |
484 | setTarget(QQmlProperty(d->obj, d->propName, qmlContext(this))); |
485 | if (d->when) |
486 | d->validate(q: this); |
487 | } |
488 | eval(); |
489 | } |
490 | |
491 | /*! |
492 | \qmlproperty string QtQml::Binding::property |
493 | |
494 | The property to be updated. |
495 | |
496 | This can be a group property if the expression results in accessing a |
497 | property of a \l {QML Value Types}{value type}. For example: |
498 | |
499 | \qml |
500 | Item { |
501 | id: item |
502 | |
503 | property rect rectangle: Qt.rect(0, 0, 200, 200) |
504 | } |
505 | |
506 | Binding { |
507 | target: item |
508 | property: "rectangle.x" |
509 | value: 100 |
510 | } |
511 | \endqml |
512 | |
513 | You only need to use this property if you can't supply the binding target |
514 | declaratively. The following snippet of code is equivalent to the above |
515 | binding, but more compact: |
516 | |
517 | \qml |
518 | Binding { item.rectangle.x: 100 } |
519 | \endqml |
520 | */ |
521 | QString QQmlBind::property() const |
522 | { |
523 | Q_D(const QQmlBind); |
524 | return d->propName; |
525 | } |
526 | |
527 | void QQmlBind::setProperty(const QString &p) |
528 | { |
529 | Q_D(QQmlBind); |
530 | if (!d->propName.isEmpty() && d->when) { |
531 | /* if we switch the property name at runtime, we need to restore the |
532 | previous binding on the old object before continuing */ |
533 | d->when = false; |
534 | eval(); |
535 | d->when = true; |
536 | } |
537 | d->propName = p; |
538 | if (d->componentComplete) { |
539 | setTarget(QQmlProperty(d->obj, d->propName, qmlContext(this))); |
540 | if (d->when) |
541 | d->validate(q: this); |
542 | } |
543 | eval(); |
544 | } |
545 | |
546 | /*! |
547 | \qmlproperty var QtQml::Binding::value |
548 | |
549 | The value to be set on the target object and property. This can be a |
550 | constant (which isn't very useful), or a bound expression. |
551 | |
552 | You only need to use this property if you can't supply the binding target |
553 | declaratively. Otherwise you can directly bind to the target. |
554 | */ |
555 | QVariant QQmlBind::value() const |
556 | { |
557 | Q_D(const QQmlBind); |
558 | if (!d->lastIsTarget) |
559 | return QVariant(); |
560 | Q_ASSERT(d->entries.last().currentKind == QQmlBindEntryKind::Variant); |
561 | return d->entries.last().current.variant; |
562 | } |
563 | |
564 | void QQmlBind::setValue(const QVariant &v) |
565 | { |
566 | Q_D(QQmlBind); |
567 | QQmlBindEntry *targetEntry = d->targetEntry(); |
568 | targetEntry->currentKind = targetEntry->current.set(v, oldKind: targetEntry->currentKind); |
569 | prepareEval(); |
570 | } |
571 | |
572 | /*! |
573 | \qmlproperty bool QtQml::Binding::delayed |
574 | \since 5.8 |
575 | |
576 | This property holds whether the binding should be delayed. |
577 | |
578 | A delayed binding will not immediately update the target, but rather wait |
579 | until the event queue has been cleared. This can be used as an optimization, |
580 | or to prevent intermediary values from being assigned. |
581 | |
582 | \code |
583 | Binding { |
584 | contactName.text.value: givenName + " " + familyName |
585 | when: list.ListView.isCurrentItem |
586 | delayed: true |
587 | } |
588 | \endcode |
589 | |
590 | \note Using the \l delayed property incurs a run time cost as the Binding |
591 | element has to create a proxy for the value, so that it can delay its |
592 | application to the actual target. When using the \l target and |
593 | \l property properties, this cost is lower because the \l value |
594 | property can be re-used as proxy. When using the form shown above, |
595 | Binding will allocate a separate object with a dynamic meta-object to |
596 | hold the proxy values. |
597 | */ |
598 | bool QQmlBind::delayed() const |
599 | { |
600 | Q_D(const QQmlBind); |
601 | return d->delayed; |
602 | } |
603 | |
604 | void QQmlBind::setDelayed(bool delayed) |
605 | { |
606 | Q_D(QQmlBind); |
607 | if (d->delayed == delayed) |
608 | return; |
609 | |
610 | d->delayed = delayed; |
611 | if (!d->componentComplete) |
612 | return; |
613 | |
614 | d->delayedValues.reset(); |
615 | |
616 | QVarLengthArray<QQmlBindEntry, 1> oldEntries = std::move(d->entries); |
617 | d->entries.clear(); |
618 | d->buildBindEntries(q: this, deferredState: nullptr); |
619 | |
620 | if (d->lastIsTarget) { |
621 | d->entries.append(t: std::move(oldEntries.last())); |
622 | oldEntries.pop_back(); |
623 | } |
624 | |
625 | for (qsizetype i = 0, end = oldEntries.size(); i < end; ++i) { |
626 | QQmlBindEntry &newEntry = d->entries[i]; |
627 | QQmlBindEntry &oldEntry = oldEntries[i]; |
628 | newEntry.previousKind = newEntry.previous.set( |
629 | other: std::move(oldEntry.previous), newKind: oldEntry.previousKind, oldKind: newEntry.previousKind); |
630 | if (d->delayed && oldEntry.currentKind == QQmlBindEntryKind::Binding) |
631 | QQmlAnyBinding::removeBindingFrom(prop&: oldEntry.prop); |
632 | } |
633 | |
634 | if (!d->delayed) |
635 | eval(); |
636 | } |
637 | |
638 | /*! |
639 | \qmlproperty enumeration QtQml::Binding::restoreMode |
640 | \since 5.14 |
641 | |
642 | This property can be used to describe if and how the original value should |
643 | be restored when the binding is disabled. |
644 | |
645 | The possible values are: |
646 | |
647 | \value Binding.RestoreNone The original value is not restored at all |
648 | \value Binding.RestoreBinding The original value is restored if it was another binding. |
649 | In that case the old binding is in effect again. |
650 | \value Binding.RestoreValue The original value is restored if it was a plain |
651 | value rather than a binding. |
652 | \value Binding.RestoreBindingOrValue The original value is always restored. |
653 | |
654 | The default value is \c Binding.RestoreBindingOrValue. |
655 | |
656 | \note This property exists for backwards compatibility with earlier versions |
657 | of Qt. Don't use it in new code. |
658 | */ |
659 | QQmlBind::RestorationMode QQmlBind::restoreMode() const |
660 | { |
661 | Q_D(const QQmlBind); |
662 | unsigned result = RestoreNone; |
663 | if (d->restoreValue) |
664 | result |= RestoreValue; |
665 | if (d->restoreBinding) |
666 | result |= RestoreBinding; |
667 | return RestorationMode(result); |
668 | } |
669 | |
670 | void QQmlBind::setRestoreMode(RestorationMode newMode) |
671 | { |
672 | Q_D(QQmlBind); |
673 | if (newMode != restoreMode()) { |
674 | d->restoreValue = (newMode & RestoreValue); |
675 | d->restoreBinding = (newMode & RestoreBinding); |
676 | emit restoreModeChanged(); |
677 | } |
678 | } |
679 | |
680 | void QQmlBind::setTarget(const QQmlProperty &p) |
681 | { |
682 | Q_D(QQmlBind); |
683 | d->targetEntry()->setTarget(q: this, p); |
684 | } |
685 | |
686 | void QQmlBindEntry::setTarget(QQmlBind *q, const QQmlProperty &p) |
687 | { |
688 | if (Q_UNLIKELY(lcBindingRemoval().isInfoEnabled())) { |
689 | if (QObject *oldObject = prop.object()) { |
690 | QMetaProperty metaProp = oldObject->metaObject()->property(index: prop.index()); |
691 | if (metaProp.hasNotifySignal()) { |
692 | QByteArray signal('2' + metaProp.notifySignal().methodSignature()); |
693 | QObject::disconnect(sender: oldObject, signal: signal.constData(), |
694 | receiver: q, SLOT(targetValueChanged())); |
695 | } |
696 | } |
697 | p.connectNotifySignal(dest: q, SLOT(targetValueChanged())); |
698 | } |
699 | |
700 | prop = p; |
701 | } |
702 | |
703 | void QQmlBind::classBegin() |
704 | { |
705 | Q_D(QQmlBind); |
706 | d->componentComplete = false; |
707 | } |
708 | |
709 | static QQmlAnyBinding createBinding( |
710 | const QQmlProperty &prop, const QV4::CompiledData::Binding *binding, |
711 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, |
712 | const QQmlRefPointer<QQmlContextData> &contextData, |
713 | QObject *scopeObject) |
714 | { |
715 | switch (binding->type()) { |
716 | case QV4::CompiledData::Binding::Type_Translation: |
717 | case QV4::CompiledData::Binding::Type_TranslationById: |
718 | return QQmlAnyBinding::createTranslationBinding(prop, compilationUnit, translationBinding: binding, scopeObject); |
719 | case QV4::CompiledData::Binding::Type_Script: { |
720 | const QQmlBinding::Identifier id = binding->value.compiledScriptIndex; |
721 | if (id == QQmlBinding::Invalid) { |
722 | return QQmlAnyBinding::createFromCodeString( |
723 | prop, code: compilationUnit->bindingValueAsString(binding), obj: scopeObject, |
724 | ctxt: contextData, url: compilationUnit->finalUrlString(), lineNumber: binding->location.line()); |
725 | } |
726 | QV4::Scope scope(contextData->engine()->handle()); |
727 | QV4::Scoped<QV4::QmlContext> qmlCtxt( |
728 | scope, QV4::QmlContext::create( |
729 | parent: scope.engine->rootContext(), context: contextData, scopeObject)); |
730 | return QQmlAnyBinding::createFromFunction( |
731 | prop, function: compilationUnit->runtimeFunctions.at(i: id), obj: scopeObject, ctxt: contextData, |
732 | scope: qmlCtxt); |
733 | } |
734 | default: |
735 | break; |
736 | } |
737 | return QQmlAnyBinding(); |
738 | } |
739 | |
740 | static void initCreator( |
741 | QQmlData::DeferredData *deferredData, |
742 | const QQmlRefPointer<QQmlContextData> &contextData, |
743 | QQmlComponentPrivate::ConstructionState *immediateState) |
744 | { |
745 | if (!immediateState->hasCreator()) { |
746 | immediateState->setCompletePending(true); |
747 | immediateState->initCreator( |
748 | parentContext: deferredData->context->parent(), compilationUnit: deferredData->compilationUnit, |
749 | creationContext: contextData); |
750 | immediateState->creator()->beginPopulateDeferred(context: deferredData->context); |
751 | } |
752 | } |
753 | |
754 | void QQmlBindPrivate::decodeBinding( |
755 | QQmlBind *q, const QString &propertyPrefix, |
756 | QQmlData::DeferredData *deferredData, |
757 | const QV4::CompiledData::Binding *binding, |
758 | QQmlComponentPrivate::ConstructionState *immediateState) |
759 | { |
760 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit |
761 | = deferredData->compilationUnit; |
762 | const QString propertySuffix = compilationUnit->stringAt(index: binding->propertyNameIndex); |
763 | const QString propertyName = propertyPrefix + propertySuffix; |
764 | |
765 | switch (binding->type()) { |
766 | case QV4::CompiledData::Binding::Type_AttachedProperty: |
767 | if (propertyPrefix.isEmpty()) { |
768 | // Top-level attached properties cannot be generalized grouped properties. |
769 | // Treat them as regular properties. |
770 | // ... unless we're not supposed to handle regular properties. Then ignore them. |
771 | if (!immediateState) |
772 | return; |
773 | |
774 | Q_ASSERT(compilationUnit->stringAt(compilationUnit->objectAt(binding->value.objectIndex) |
775 | ->inheritedTypeNameIndex).isEmpty()); |
776 | |
777 | const QV4::ResolvedTypeReference *typeReference |
778 | = compilationUnit->resolvedType(id: binding->propertyNameIndex); |
779 | Q_ASSERT(typeReference); |
780 | QQmlType attachedType = typeReference->type(); |
781 | if (!attachedType.isValid()) { |
782 | const QQmlTypeNameCache::Result result |
783 | = deferredData->context->imports()->query(key: propertySuffix); |
784 | if (!result.isValid()) { |
785 | qmlWarning(me: q).nospace() |
786 | << "Unknown name " << propertySuffix << ". The binding is ignored." ; |
787 | return; |
788 | } |
789 | attachedType = result.type; |
790 | } |
791 | |
792 | QQmlContext *context = qmlContext(q); |
793 | QObject *attachedObject = qmlAttachedPropertiesObject( |
794 | q, func: attachedType.attachedPropertiesFunction( |
795 | engine: QQmlEnginePrivate::get(e: context->engine()))); |
796 | if (!attachedObject) { |
797 | qmlWarning(me: q).nospace() <<"Could not create attached properties object '" |
798 | << attachedType.typeName() << "'" ; |
799 | return; |
800 | } |
801 | |
802 | initCreator(deferredData, contextData: QQmlContextData::get(context), immediateState); |
803 | immediateState->creator()->populateDeferredInstance( |
804 | outerObject: q, deferredIndex: deferredData->deferredIdx, index: binding->value.objectIndex, instance: attachedObject, |
805 | bindingTarget: attachedObject, /*value type property*/ valueTypeProperty: nullptr, binding); |
806 | return; |
807 | } |
808 | Q_FALLTHROUGH(); |
809 | case QV4::CompiledData::Binding::Type_GroupProperty: { |
810 | const QString pre = propertyName + u'.'; |
811 | const QV4::CompiledData::Object *subObj |
812 | = compilationUnit->objectAt(index: binding->value.objectIndex); |
813 | const QV4::CompiledData::Binding *subBinding = subObj->bindingTable(); |
814 | for (quint32 i = 0; i < subObj->nBindings; ++i, ++subBinding) |
815 | decodeBinding(q, propertyPrefix: pre, deferredData, binding: subBinding, immediateState); |
816 | return; |
817 | } |
818 | default: |
819 | break; |
820 | } |
821 | |
822 | QQmlBindEntry entry; |
823 | QQmlContext *context = qmlContext(q); |
824 | const QQmlRefPointer<QQmlContextData> contextData = QQmlContextData::get(context); |
825 | entry.prop = QQmlPropertyPrivate::create(target: nullptr, propertyName, context: contextData, |
826 | flags: QQmlPropertyPrivate::InitFlag::AllowId); |
827 | if (!entry.prop.isValid()) { |
828 | // Try again in the context of this object. If that works, it's a regular property. |
829 | // ... unless we're not supposed to handle regular properties. Then ignore it. |
830 | if (!immediateState) |
831 | return; |
832 | |
833 | QQmlProperty property = QQmlPropertyPrivate::create( |
834 | target: q, propertyName, context: contextData, flags: QQmlPropertyPrivate::InitFlag::AllowSignal); |
835 | if (property.isValid()) { |
836 | initCreator(deferredData, contextData, immediateState); |
837 | immediateState->creator()->populateDeferredBinding( |
838 | qmlProperty: property, deferredIndex: deferredData->deferredIdx, binding); |
839 | } else { |
840 | qmlWarning(me: q).nospace() << "Unknown name " << propertyName |
841 | << ". The binding is ignored." ; |
842 | } |
843 | return; |
844 | } |
845 | |
846 | const auto setVariant = [&entry](QVariant var) { |
847 | entry.currentKind = entry.current.set(v: std::move(var), oldKind: entry.currentKind); |
848 | }; |
849 | |
850 | const auto setBinding = [&entry](QQmlAnyBinding binding) { |
851 | entry.currentKind = entry.current.set(v: binding, oldKind: entry.currentKind); |
852 | }; |
853 | |
854 | switch (binding->type()) { |
855 | case QV4::CompiledData::Binding::Type_AttachedProperty: |
856 | case QV4::CompiledData::Binding::Type_GroupProperty: |
857 | Q_UNREACHABLE(); // Handled above |
858 | break; |
859 | case QV4::CompiledData::Binding::Type_Translation: |
860 | case QV4::CompiledData::Binding::Type_TranslationById: |
861 | case QV4::CompiledData::Binding::Type_Script: |
862 | if (delayed) { |
863 | if (!delayedValues) |
864 | createDelayedValues(); |
865 | const QString delayedName = QString::number(entries.size()); |
866 | delayedValues->insert(key: delayedName, value: QVariant()); |
867 | QQmlProperty bindingTarget = QQmlProperty(delayedValues.get(), delayedName); |
868 | Q_ASSERT(bindingTarget.isValid()); |
869 | QQmlAnyBinding anyBinding = createBinding( |
870 | prop: bindingTarget, binding, compilationUnit, contextData, scopeObject: q); |
871 | anyBinding.installOn(target: bindingTarget); |
872 | } else { |
873 | setBinding(createBinding(prop: entry.prop, binding, compilationUnit, contextData, scopeObject: q)); |
874 | } |
875 | break; |
876 | case QV4::CompiledData::Binding::Type_String: |
877 | setVariant(compilationUnit->bindingValueAsString(binding)); |
878 | break; |
879 | case QV4::CompiledData::Binding::Type_Number: |
880 | setVariant(compilationUnit->bindingValueAsNumber(binding)); |
881 | break; |
882 | case QV4::CompiledData::Binding::Type_Boolean: |
883 | setVariant(binding->valueAsBoolean()); |
884 | break; |
885 | case QV4::CompiledData::Binding::Type_Null: |
886 | setVariant(QVariant::fromValue(value: nullptr)); |
887 | break; |
888 | case QV4::CompiledData::Binding::Type_Object: |
889 | case QV4::CompiledData::Binding::Type_Invalid: |
890 | break; |
891 | } |
892 | |
893 | entries.append(t: std::move(entry)); |
894 | } |
895 | |
896 | void QQmlBindPrivate::createDelayedValues() |
897 | { |
898 | delayedValues = std::make_unique<QQmlPropertyMap>(); |
899 | QObject::connect( |
900 | sender: delayedValues.get(), signal: &QQmlPropertyMap::valueChanged, |
901 | context: delayedValues.get(), slot: [this](QString delayedName, const QVariant &value) { |
902 | Q_UNUSED(value); |
903 | onDelayedValueChanged(delayedName: std::move(delayedName)); |
904 | } |
905 | ); |
906 | } |
907 | |
908 | void QQmlBindPrivate::onDelayedValueChanged(QString delayedName) |
909 | { |
910 | Q_ASSERT(delayed); |
911 | Q_ASSERT(delayedValues); |
912 | const QString pendingName = QStringLiteral("pending" ); |
913 | QStringList pending = qvariant_cast<QStringList>(v: (*delayedValues)[pendingName]); |
914 | if (componentComplete && pending.size() == 0) |
915 | QTimer::singleShot(interval: 0, receiver: delayedValues.get(), slot: [this]() { evalDelayed(); }); |
916 | else if (pending.contains(str: delayedName)) |
917 | return; |
918 | |
919 | pending.append(t: std::move(delayedName)); |
920 | (*delayedValues)[pendingName].setValue(std::move(pending)); |
921 | } |
922 | |
923 | void QQmlBindPrivate::evalDelayed() |
924 | { |
925 | if (!when || !delayedValues) |
926 | return; |
927 | |
928 | const QString pendingName = QStringLiteral("pending" ); |
929 | const QStringList pending = qvariant_cast<QStringList>(v: (*delayedValues)[pendingName]); |
930 | for (const QString &delayedName : pending) { |
931 | bool ok; |
932 | const int delayedIndex = delayedName.toInt(ok: &ok); |
933 | Q_ASSERT(ok); |
934 | Q_ASSERT(delayedIndex >= 0 && delayedIndex < entries.size()); |
935 | entries[delayedIndex].prop.write((*delayedValues)[delayedName]); |
936 | } |
937 | (*delayedValues)[pendingName].setValue(QStringList()); |
938 | } |
939 | |
940 | void QQmlBindPrivate::buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState) |
941 | { |
942 | QQmlData *data = QQmlData::get(object: q); |
943 | if (data && !data->deferredData.isEmpty()) { |
944 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: data->context->engine()); |
945 | for (QQmlData::DeferredData *deferredData : data->deferredData) { |
946 | QMultiHash<int, const QV4::CompiledData::Binding *> *bindings = &deferredData->bindings; |
947 | if (deferredState) { |
948 | QQmlComponentPrivate::ConstructionState constructionState; |
949 | for (auto it = bindings->cbegin(); it != bindings->cend(); ++it) |
950 | decodeBinding(q, propertyPrefix: QString(), deferredData, binding: *it, immediateState: &constructionState); |
951 | |
952 | |
953 | if (constructionState.hasCreator()) { |
954 | ++ep->inProgressCreations; |
955 | constructionState.creator()->finalizePopulateDeferred(); |
956 | constructionState.appendCreatorErrors(); |
957 | deferredState->push_back(x: std::move(constructionState)); |
958 | } |
959 | } else { |
960 | for (auto it = bindings->cbegin(); it != bindings->cend(); ++it) |
961 | decodeBinding(q, propertyPrefix: QString(), deferredData, binding: *it, immediateState: nullptr); |
962 | } |
963 | } |
964 | |
965 | if (deferredState) { |
966 | data->releaseDeferredData(); |
967 | if (!deferredState->empty()) |
968 | QQmlComponentPrivate::completeDeferred(enginePriv: ep, deferredState); |
969 | } |
970 | } |
971 | } |
972 | |
973 | void QQmlBind::componentComplete() |
974 | { |
975 | Q_D(QQmlBind); |
976 | QQmlComponentPrivate::DeferredState deferredState; |
977 | d->buildBindEntries(q: this, deferredState: &deferredState); |
978 | d->componentComplete = true; |
979 | if (!d->propName.isEmpty() || d->obj) { |
980 | QQmlBindEntry *target = d->targetEntry(); |
981 | if (!target->prop.isValid()) |
982 | target->setTarget(q: this, p: QQmlProperty(d->obj, d->propName, qmlContext(this))); |
983 | } |
984 | d->validate(q: this); |
985 | d->evalDelayed(); |
986 | eval(); |
987 | } |
988 | |
989 | void QQmlBind::prepareEval() |
990 | { |
991 | Q_D(QQmlBind); |
992 | if (d->delayed) { |
993 | if (!d->pendingEval) |
994 | QTimer::singleShot(interval: 0, receiver: this, slot: &QQmlBind::eval); |
995 | d->pendingEval = true; |
996 | } else { |
997 | eval(); |
998 | } |
999 | } |
1000 | |
1001 | void QQmlBindEntry::clearPrev() |
1002 | { |
1003 | previousKind = previous.destroy(kind: previousKind); |
1004 | } |
1005 | |
1006 | void QQmlBind::eval() |
1007 | { |
1008 | Q_D(QQmlBind); |
1009 | d->pendingEval = false; |
1010 | if (!d->componentComplete) |
1011 | return; |
1012 | |
1013 | for (QQmlBindEntry &entry : d->entries) { |
1014 | if (!entry.prop.isValid() || (entry.currentKind == QQmlBindEntryKind::None)) |
1015 | continue; |
1016 | |
1017 | if (!d->when) { |
1018 | //restore any previous binding |
1019 | switch (entry.previousKind) { |
1020 | case QQmlBindEntryKind::Binding: |
1021 | if (d->restoreBinding) { |
1022 | QQmlAnyBinding p = std::move(entry.previous.binding); |
1023 | entry.clearPrev(); // Do that before setBinding(), as setBinding() may recurse. |
1024 | p.installOn(target: entry.prop); |
1025 | } |
1026 | break; |
1027 | case QQmlBindEntryKind::V4Value: |
1028 | if (d->restoreValue) { |
1029 | auto propPriv = QQmlPropertyPrivate::get(p: entry.prop); |
1030 | QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: propPriv->object); |
1031 | Q_ASSERT(vmemo); |
1032 | vmemo->setVMEProperty(index: propPriv->core.coreIndex(), |
1033 | v: *entry.previous.v4Value.valueRef()); |
1034 | entry.clearPrev(); |
1035 | } |
1036 | break; |
1037 | case QQmlBindEntryKind::Variant: |
1038 | if (d->restoreValue) { |
1039 | entry.prop.write(entry.previous.variant); |
1040 | entry.clearPrev(); |
1041 | } |
1042 | break; |
1043 | case QQmlBindEntryKind::None: |
1044 | break; |
1045 | } |
1046 | continue; |
1047 | } |
1048 | |
1049 | //save any set binding for restoration |
1050 | if (entry.previousKind == QQmlBindEntryKind::None) { |
1051 | // try binding first; we need to use takeFrom to properly unlink the binding |
1052 | QQmlAnyBinding prevBind = QQmlAnyBinding::takeFrom(prop: entry.prop); |
1053 | if (prevBind) { |
1054 | entry.previousKind = entry.previous.set(v: std::move(prevBind), oldKind: entry.previousKind); |
1055 | } else { |
1056 | // nope, try a V4 value next |
1057 | auto propPriv = QQmlPropertyPrivate::get(p: entry.prop); |
1058 | auto propData = propPriv->core; |
1059 | if (!propPriv->valueTypeData.isValid() && propData.isVarProperty()) { |
1060 | QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: propPriv->object); |
1061 | Q_ASSERT(vmemo); |
1062 | auto retVal = vmemo->vmeProperty(index: propData.coreIndex()); |
1063 | entry.previousKind = entry.previous.set( |
1064 | v: QV4::PersistentValue(vmemo->engine, retVal), oldKind: entry.previousKind); |
1065 | } else { |
1066 | // nope, use the meta object to get a QVariant |
1067 | entry.previousKind = entry.previous.set(v: entry.prop.read(), oldKind: entry.previousKind); |
1068 | } |
1069 | } |
1070 | } |
1071 | |
1072 | // NOTE: removeBinding has no effect on QProperty classes, but |
1073 | // we already used takeBinding to remove it |
1074 | QQmlPropertyPrivate::removeBinding(that: entry.prop); |
1075 | } |
1076 | |
1077 | if (!d->when) |
1078 | return; |
1079 | |
1080 | d->writingProperty = true; |
1081 | for (qsizetype i = 0, end = d->entries.size(); i != end; ++i) { |
1082 | QQmlBindEntry &entry = d->entries[i]; |
1083 | if (!entry.prop.isValid()) |
1084 | continue; |
1085 | switch (entry.currentKind) { |
1086 | case QQmlBindEntryKind::Variant: |
1087 | entry.prop.write(entry.current.variant); |
1088 | break; |
1089 | case QQmlBindEntryKind::Binding: |
1090 | Q_ASSERT(!d->delayed); |
1091 | entry.current.binding.installOn(target: entry.prop); |
1092 | break; |
1093 | case QQmlBindEntryKind::V4Value: { |
1094 | auto propPriv = QQmlPropertyPrivate::get(p: entry.prop); |
1095 | QQmlVMEMetaObject::get(obj: propPriv->object)->setVMEProperty( |
1096 | index: propPriv->core.coreIndex(), v: *entry.current.v4Value.valueRef()); |
1097 | break; |
1098 | } |
1099 | case QQmlBindEntryKind::None: |
1100 | break; |
1101 | } |
1102 | } |
1103 | d->writingProperty = false; |
1104 | } |
1105 | |
1106 | void QQmlBind::targetValueChanged() |
1107 | { |
1108 | Q_D(QQmlBind); |
1109 | if (d->writingProperty) |
1110 | return; |
1111 | |
1112 | if (!d->when) |
1113 | return; |
1114 | |
1115 | QUrl url; |
1116 | quint16 line = 0; |
1117 | |
1118 | const QQmlData *ddata = QQmlData::get(object: this, create: false); |
1119 | if (ddata && ddata->outerContext) { |
1120 | url = ddata->outerContext->url(); |
1121 | line = ddata->lineNumber; |
1122 | } |
1123 | |
1124 | qCInfo(lcBindingRemoval, |
1125 | "The target property of the Binding element created at %s:%d was changed from " |
1126 | "elsewhere. This does not overwrite the binding. The target property will still be " |
1127 | "updated when the value of the Binding element changes." , |
1128 | qPrintable(url.toString()), line); |
1129 | } |
1130 | |
1131 | QT_END_NAMESPACE |
1132 | |
1133 | #include "moc_qqmlbind_p.cpp" |
1134 | |