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
31QT_BEGIN_NAMESPACE
32
33Q_LOGGING_CATEGORY(lcQtQmlBindingRemoval, "qt.qml.binding.removal", QtWarningMsg)
34
35enum 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 */
49union QQmlBindEntryContent {
50 Q_DISABLE_COPY_MOVE(QQmlBindEntryContent)
51public:
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
138private:
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 */
155struct 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
209class QQmlBindPrivate : public QObjectPrivate
210{
211public:
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
290void 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
298QQmlBindEntry *QQmlBindPrivate::targetEntry()
299{
300 if (!lastIsTarget) {
301 entries.append(t: QQmlBindEntry());
302 lastIsTarget = true;
303 }
304 return &entries.last();
305}
306
307void 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*/
389QQmlBind::QQmlBind(QObject *parent)
390 : QObject(*(new QQmlBindPrivate), parent)
391{
392}
393
394QQmlBind::~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*/
429bool QQmlBind::when() const
430{
431 Q_D(const QQmlBind);
432 return d->when;
433}
434
435void 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*/
469QObject *QQmlBind::object()
470{
471 Q_D(const QQmlBind);
472 return d->obj;
473}
474
475void 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*/
539QString QQmlBind::property() const
540{
541 Q_D(const QQmlBind);
542 return d->propName;
543}
544
545void 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*/
573QVariant 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
582void 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*/
616bool QQmlBind::delayed() const
617{
618 Q_D(const QQmlBind);
619 return d->delayed;
620}
621
622void 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*/
677QQmlBind::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
688void 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
698void QQmlBind::setTarget(const QQmlProperty &p)
699{
700 Q_D(QQmlBind);
701 d->targetEntry()->setTarget(q: this, p);
702}
703
704void 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
721void QQmlBind::classBegin()
722{
723 Q_D(QQmlBind);
724 d->componentComplete = false;
725}
726
727static 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
758static 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
772void 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
916void 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
928void 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
943void 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
960void 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
993void 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
1009void 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
1021void QQmlBindEntry::clearPrev()
1022{
1023 previousKind = previous.destroy(kind: previousKind);
1024}
1025
1026void 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
1130void 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
1155QT_END_NAMESPACE
1156
1157#include "moc_qqmlbind_p.cpp"
1158

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmlmeta/types/qqmlbind.cpp