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