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
30QT_BEGIN_NAMESPACE
31
32Q_DECLARE_LOGGING_CATEGORY(lcBindingRemoval)
33
34enum 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 */
48union QQmlBindEntryContent {
49 Q_DISABLE_COPY_MOVE(QQmlBindEntryContent)
50public:
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
137private:
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 */
154struct 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
208class QQmlBindPrivate : public QObjectPrivate
209{
210public:
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
289void 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
297QQmlBindEntry *QQmlBindPrivate::targetEntry()
298{
299 if (!lastIsTarget) {
300 entries.append(t: QQmlBindEntry());
301 lastIsTarget = true;
302 }
303 return &entries.last();
304}
305
306void 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*/
388QQmlBind::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*/
411bool QQmlBind::when() const
412{
413 Q_D(const QQmlBind);
414 return d->when;
415}
416
417void 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*/
451QObject *QQmlBind::object()
452{
453 Q_D(const QQmlBind);
454 return d->obj;
455}
456
457void 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*/
521QString QQmlBind::property() const
522{
523 Q_D(const QQmlBind);
524 return d->propName;
525}
526
527void 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*/
555QVariant 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
564void 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*/
598bool QQmlBind::delayed() const
599{
600 Q_D(const QQmlBind);
601 return d->delayed;
602}
603
604void 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*/
659QQmlBind::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
670void 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
680void QQmlBind::setTarget(const QQmlProperty &p)
681{
682 Q_D(QQmlBind);
683 d->targetEntry()->setTarget(q: this, p);
684}
685
686void 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
703void QQmlBind::classBegin()
704{
705 Q_D(QQmlBind);
706 d->componentComplete = false;
707}
708
709static 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
740static 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
754void 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
896void 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
908void 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
923void 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
940void 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
973void 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
989void 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
1001void QQmlBindEntry::clearPrev()
1002{
1003 previousKind = previous.destroy(kind: previousKind);
1004}
1005
1006void 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
1106void 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
1131QT_END_NAMESPACE
1132
1133#include "moc_qqmlbind_p.cpp"
1134

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