Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlbind.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
5#include "qqmlbind_p.h"
6
7#include <private/qqmlanybinding_p.h>
8#include <private/qqmlbinding_p.h>
9#include <private/qqmlcomponent_p.h>
10#include <private/qqmlmetatype_p.h>
11#include <private/qqmlnullablevalue_p.h>
12#include <private/qqmlproperty_p.h>
13#include <private/qqmlvmemetaobject_p.h>
14#include <private/qv4persistent_p.h>
15#include <private/qv4qmlcontext_p.h>
16#include <private/qv4resolvedtypereference_p.h>
17#include <private/qv4runtime_p.h>
18
19#include <QtQml/qqmlcontext.h>
20#include <QtQml/qqmlengine.h>
21#include <QtQml/qqmlinfo.h>
22#include <QtQml/qqmlproperty.h>
23#include <QtQml/qqmlpropertymap.h>
24
25#include <QtCore/private/qobject_p.h>
26
27#include <QtCore/qdebug.h>
28#include <QtCore/qfile.h>
29#include <QtCore/qloggingcategory.h>
30#include <QtCore/qpointer.h>
31#include <QtCore/qtimer.h>
32
34
35Q_STATIC_LOGGING_CATEGORY(lcQtQmlBindingRemoval, "qt.qml.binding.removal", QtWarningMsg)
36
37enum class QQmlBindEntryKind: quint8 {
38 V4Value,
39 Variant,
40 Binding,
41 None
42};
43
44/*!
45 * \internal
46 * QQmlBindEntryContent can store one of QV4::Value, QVariant, QQmlAnyBinding, or nothing,
47 * as denoted by QQmlBindEntryKind. It expects the calling code to know what is stored at
48 * any time. On each method invocation, the current kind has to be passed as last parameter
49 * and the new kind is returned.
50 */
53public:
56
74
92
94 {
95 switch (kind) {
99 break;
102 break;
104 break;
105 }
106 return QQmlBindEntryKind::None;
107 }
108
116
123
130
137
140 int none = 0;
141
142private:
143 void silentDestroy(QQmlBindEntryKind oldKind)
144 {
145 const QQmlBindEntryKind dead = destroy(oldKind);
146 Q_ASSERT(dead == QQmlBindEntryKind::None);
147 Q_UNUSED(dead);
148 }
149};
150
151/*!
152 * \internal
153 * QQmlBindEntry holds two QQmlBindEntryContent members, along with their kinds.
154 * The \l current content is the value or binding the Binding element installs on
155 * the target if enabled (that is, if \l{when}). The \l previous content is what
156 * the target holds before the Binding element installs its binding or value. It
157 * is restored if !\l{when}. The \l prop member holds the target property.
158 */
160{
161 QQmlBindEntry() = default;
163 {
164 currentKind = current.set(std::move(other.current), other.currentKind, currentKind);
165 previousKind = previous.set(std::move(other.previous), other.previousKind, previousKind);
166 }
167
169 : prop(other.prop)
170 {
171 currentKind = current.set(other.current, other.currentKind, currentKind);
172 previousKind = previous.set(other.previous, other.previousKind, previousKind);
173 }
174
176 {
177 currentKind = current.destroy(currentKind);
178 previousKind = previous.destroy(previousKind);
179 }
180
182 {
183 if (this == &other)
184 return *this;
185 prop = std::move(other.prop);
186 currentKind = current.set(std::move(other.current), other.currentKind, currentKind);
187 previousKind = previous.set(std::move(other.previous), other.previousKind, previousKind);
188 return *this;
189 }
190
192 {
193 if (this == &other)
194 return *this;
195 prop = other.prop;
196 currentKind = current.set(other.current, other.currentKind, currentKind);
197 previousKind = previous.set(other.previous, other.previousKind, previousKind);
198 return *this;
199 }
200
201
207
208 void validate(QQmlBind *q) const;
209 void clearPrev();
210 void setTarget(QQmlBind *q, const QQmlProperty &p);
211};
212
214{
215 // Only one entry is used for target/property/value
217
218 // The \l target object
220
221 // The \l property name
223};
224
226{
227 // There can be multiple entries when using the generalized grouped
228 // property mode.
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.
235};
236
238{
239public:
245
247 : when(true)
248 , componentComplete(true)
249 , delayed(false)
250 , pendingEval(false)
251 , restoreBinding(true)
252 , restoreValue(true)
253 , writingProperty(false)
254 {
255 }
256
258 {
259 switch (mode) {
260 case GeneralizedGroup:
261 generalizedGroupData.~GeneralizedGroupData();
262 return;
263 case ObjectPropertyValue:
264 objectPropertyValueData.~ObjectPropertyValueData();
265 return;
266 case Unknown:
267 return;
268 }
269 }
270
271 union {
272 int noData = 0;
275 };
276
278
279 // Whether the binding is enabled.
280 bool when: 1;
281
282 // Whether we have already parsed any generalized grouped properties
283 // we might need.
285
286 // Whether we should run in "delayed" mode and proxy all values before
287 // applying them to the target.
288 bool delayed:1;
289
290 // In delayed mode, when using the target/property mode, the \l value
291 // is the proxy. Then pendingEval denotes that a timer is active to
292 // apply the value. We should not start another timer then.
294
295 // Whether we should restore bindings on !when.
296 // TODO: Deprecate this and always do.
298
299 // Whether we should restore values on !when.
300 // TODO: Deprecate this and always do.
302
303 // writingProperty tracks whether we are updating the target property
304 // when using target/property/value. We use this information to warn about
305 // binding removal if we detect the target property to be updated while we
306 // are not writing it. This doesn't remove the Binding after all.
307 // For generalized grouped properties, we don't have to do this as writing
308 // the target property does remove the binding, just like it removes any
309 // other binding.
311
313 void validate(QQmlBind *binding) const;
315 QQmlBind *q, const QString &propertyPrefix, QQmlData::DeferredData *deferredData,
316 const QV4::CompiledData::Binding *binding,
317 QQmlComponentPrivate::ConstructionState *immediateState);
319 void onDelayedValueChanged(QString delayedName);
321 void buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState);
324 bool isCurrent(QQmlBindEntry *entry) const;
325};
326
327static void warnIgnoredProperties(QQmlBind *q)
328{
329 qmlWarning(q)
330 << "You should not set the 'object', 'property', or 'value' properties when using "
331 "generalized group properties. They are ignored.";
332}
333
334void QQmlBindEntry::validate(QQmlBind *q) const
335{
336 if (!prop.isWritable()) {
337 qmlWarning(q) << "Property '" << prop.name() << "' on "
338 << QQmlMetaType::prettyTypeName(prop.object()) << " is read-only.";
339 }
340}
341
343{
344 switch (mode) {
345 case GeneralizedGroup:
346 Q_UNREACHABLE_RETURN(nullptr);
347 case Unknown:
348 new (&objectPropertyValueData) ObjectPropertyValueData;
350 Q_FALLTHROUGH();
351 case ObjectPropertyValue:
352 return &objectPropertyValueData.entry;
353 }
354
355 return nullptr;
356}
357
358void QQmlBindPrivate::validate(QQmlBind *q) const
359{
360 if (!when)
361 return;
362
363 switch (mode) {
364 case ObjectPropertyValue:
365 if (!objectPropertyValueData.obj)
366 break;
367 if (objectPropertyValueData.entry.prop.isValid()) {
368 objectPropertyValueData.entry.validate(q);
369 } else {
370 qmlWarning(q) << "Property '" << objectPropertyValueData.propName
371 << "' does not exist on "
372 << QQmlMetaType::prettyTypeName(objectPropertyValueData.obj) << ".";
373 }
374 break;
375 case GeneralizedGroup:
376 for (const QQmlBindEntry &entry : generalizedGroupData.entries)
377 entry.validate(q);
378 break;
379 case Unknown:
380 break;
381 }
382}
383
384/*!
385 \qmltype Binding
386 \inqmlmodule QtQml
387 \ingroup qtquick-interceptors
388 \brief Enables the arbitrary creation of property bindings.
389
390 In QML, property bindings result in a dependency between the properties of
391 different objects.
392
393 \section1 Binding to an Inaccessible Property
394
395 Sometimes it is necessary to bind an object's property to
396 that of another object that isn't directly instantiated by QML, such as a
397 property of a class exported to QML by C++. You can use the Binding type
398 to establish this dependency; binding any value to any object's property.
399
400 For example, in a C++ application that maps an "app.enteredText" property
401 into QML, you can use Binding to update the enteredText property.
402
403 \qml
404 TextEdit { id: myTextField; text: "Please type here..." }
405 Binding { app.enteredText: myTextField.text }
406 \endqml
407
408 When \c{text} changes, the C++ property \c{enteredText} will update
409 automatically.
410
411 \section1 Conditional Bindings
412
413 In some cases you may want to modify the value of a property when a certain
414 condition is met but leave it unmodified otherwise. Often, it's not possible
415 to do this with direct bindings, as you have to supply values for all
416 possible branches.
417
418 For example, the code snippet below results in a warning whenever you
419 release the mouse. This is because the value of the binding is undefined
420 when the mouse isn't pressed.
421
422 \qml
423 // produces warning: "Unable to assign [undefined] to double value"
424 value: if (mouse.pressed) mouse.mouseX
425 \endqml
426
427 The Binding type can prevent this warning.
428
429 \qml
430 Binding on value {
431 when: mouse.pressed
432 value: mouse.mouseX
433 }
434 \endqml
435
436 The Binding type restores any previously set direct bindings on the
437 property.
438
439 \section1 Multiple targets in one Binding
440
441 You can specify multiple bindings to the same object in one Binding element:
442
443 \qml
444 Text {
445 id: t1
446 }
447
448 Binding {
449 t1 {
450 color: "#00FF00"
451 text: "green text"
452 }
453 }
454 \endqml
455
456 You can also specify several bindings with different target objects in a
457 single Binding element:
458
459 \qml
460 Text {
461 id: t1
462 }
463
464 Text {
465 id: t2
466 }
467
468 Binding {
469 t1.text: "Foo"
470 t2.text: "Bar"
471 }
472 \endqml
473
474 \sa {Qt Qml}
475*/
476QQmlBind::QQmlBind(QObject *parent)
477 : QObject(*(new QQmlBindPrivate), parent)
478{
479}
480
481QQmlBind::~QQmlBind()
482{
483 Q_D(QQmlBind);
484 // restore state when dynamic Binding is destroyed
485 if (!(d->when && d->componentComplete && restoreMode() != RestoreNone))
486 return;
487 // isDeletingChildren is supposed to happen later; we couldn't use declarativeData
488 // if isDeletingChildren were set
489 Q_ASSERT(!d->isDeletingChildren);
490 // We can't use qmlEngine (or QQmlData::get), as that checks for scheduled deletion
491 if (auto ddata = static_cast<QQmlData *>(d->declarativeData);
492 ddata && ddata->context && QQmlData::wasDeleted(ddata->context->engine()))
493 return; // whole engine is going away; don't bother resetting
494 d->when = false; // internal only change, no signal emission
495 eval();
496}
497
498/*!
499 \qmlproperty bool QtQml::Binding::when
500
501 This property holds when the binding is active.
502 This should be set to an expression that evaluates to true when you want the binding to be active.
503
504 \qml
505 Binding {
506 contactName.text: name
507 when: list.ListView.isCurrentItem
508 }
509 \endqml
510
511 By default, any binding or value that was set perviously is restored when the binding becomes
512 inactive. You can customize the restoration behavior using the \l restoreMode property.
513
514 \sa restoreMode
515*/
516bool QQmlBind::when() const
517{
518 Q_D(const QQmlBind);
519 return d->when;
520}
521
522void QQmlBind::setWhen(bool v)
523{
524 Q_D(QQmlBind);
525 if (d->when == v)
526 return;
527
528 d->when = v;
529 if (v && d->componentComplete)
530 d->validate(this);
531 eval();
532 emit whenChanged();
533}
534
535/*!
536 \qmlproperty QtObject QtQml::Binding::target
537
538 The object to be updated. You need to use this property if the binding target
539 does not have an \c id attribute (for example, when the target is a singleton).
540 Otherwise, the following two pieces of code are equivalent:
541
542 \qml
543 Binding { contactName.text: name }
544 \endqml
545
546 \qml
547 Binding {
548 target: contactName
549 property: "text"
550 value: name
551 }
552 \endqml
553
554 The former one is much more compact, but you cannot replace the target
555 object or property at run time. With the latter one you can.
556*/
557QObject *QQmlBind::object() const
558{
559 Q_D(const QQmlBind);
560 switch (d->mode) {
561 case QQmlBindPrivate::GeneralizedGroup:
562 case QQmlBindPrivate::Unknown:
563 return nullptr;
564 case QQmlBindPrivate::ObjectPropertyValue:
565 return d->objectPropertyValueData.obj;
566 }
567
568 Q_UNREACHABLE_RETURN(nullptr);
569}
570
571void QQmlBind::setObject(QObject *obj)
572{
573 Q_D(QQmlBind);
574 switch (d->mode) {
575 case QQmlBindPrivate::GeneralizedGroup:
576 if (obj != nullptr)
577 warnIgnoredProperties(this);
578 return;
579 case QQmlBindPrivate::ObjectPropertyValue:
580 if (d->objectPropertyValueData.obj == obj)
581 return;
582 break;
583 case QQmlBindPrivate::Unknown:
584 if (obj == nullptr)
585 return;
586 new (&d->objectPropertyValueData) ObjectPropertyValueData;
587 d->mode = QQmlBindPrivate::ObjectPropertyValue;
588 break;
589 }
590
591 if (d->when) {
592 /* if we switch the object at runtime, we need to restore the
593 previous binding on the old object before continuing */
594 d->when = false;
595 eval();
596 d->when = true;
597 }
598 /* if "when" and "target" depend on the same property, we might
599 end up here before we could have updated "when". So reevaluate
600 when manually here.
601 */
602 const QQmlProperty whenProp(this, QLatin1StringView("when"));
603 const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(whenProp);
604 if (auto abstractBinding = potentialWhenBinding.asAbstractBinding()) {
605 QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding);
606 if (binding->hasValidContext()) {
607 const auto boolType = QMetaType::fromType<bool>();
608 bool when;
609 binding->evaluate(&when, boolType);
610 if (when != d->when) {
611 d->when = when;
612 emit whenChanged();
613 }
614 }
615 }
616
617 switch (d->mode) {
618 case QQmlBindPrivate::GeneralizedGroup:
619 case QQmlBindPrivate::Unknown:
620 Q_UNREACHABLE();
621 return;
622 case QQmlBindPrivate::ObjectPropertyValue:
623 d->objectPropertyValueData.obj = obj;
624 if (d->componentComplete) {
625 setTarget(QQmlProperty(
626 d->objectPropertyValueData.obj, d->objectPropertyValueData.propName,
627 qmlContext(this)));
628 }
629 break;
630 }
631
632 if (d->componentComplete && d->when)
633 d->validate(this);
634
635 eval();
636 emit objectChanged();
637}
638
639/*!
640 \qmlproperty string QtQml::Binding::property
641
642 The property to be updated.
643
644 This can be a group property if the expression results in accessing a
645 property of a \l {QML Value Types}{value type}. For example:
646
647 \qml
648 Item {
649 id: item
650
651 property rect rectangle: Qt.rect(0, 0, 200, 200)
652 }
653
654 Binding {
655 target: item
656 property: "rectangle.x"
657 value: 100
658 }
659 \endqml
660
661 You only need to use this property if you can't supply the binding target
662 declaratively. The following snippet of code is equivalent to the above
663 binding, but more compact:
664
665 \qml
666 Binding { item.rectangle.x: 100 }
667 \endqml
668*/
669QString QQmlBind::property() const
670{
671 Q_D(const QQmlBind);
672 switch (d->mode) {
673 case QQmlBindPrivate::GeneralizedGroup:
674 case QQmlBindPrivate::Unknown:
675 return QString();
676 case QQmlBindPrivate::ObjectPropertyValue:
677 return d->objectPropertyValueData.propName;
678 }
679
680 Q_UNREACHABLE_RETURN(QString());
681}
682
683void QQmlBind::setProperty(const QString &p)
684{
685 Q_D(QQmlBind);
686 switch (d->mode) {
687 case QQmlBindPrivate::GeneralizedGroup:
688 if (!p.isEmpty())
689 warnIgnoredProperties(this);
690 return;
691 case QQmlBindPrivate::ObjectPropertyValue:
692 if (d->objectPropertyValueData.propName == p)
693 return;
694 break;
695 case QQmlBindPrivate::Unknown:
696 if (p.isEmpty())
697 return;
698 new (&d->objectPropertyValueData) ObjectPropertyValueData;
699 d->mode = QQmlBindPrivate::ObjectPropertyValue;
700 break;
701 }
702
703 if (!d->objectPropertyValueData.propName.isEmpty() && d->when) {
704 /* if we switch the property name at runtime, we need to restore the
705 previous binding on the old object before continuing */
706 d->when = false;
707 eval();
708 d->when = true;
709 }
710 d->objectPropertyValueData.propName = p;
711 if (d->componentComplete) {
712 setTarget(QQmlProperty(
713 d->objectPropertyValueData.obj, d->objectPropertyValueData.propName,
714 qmlContext(this)));
715 if (d->when)
716 d->validate(this);
717 }
718 eval();
719 emit propertyChanged();
720}
721
722/*!
723 \qmlproperty var QtQml::Binding::value
724
725 The value to be set on the target object and property. This can be a
726 constant (which isn't very useful), or a bound expression.
727
728 You only need to use this property if you can't supply the binding target
729 declaratively. Otherwise you can directly bind to the target.
730*/
731QVariant QQmlBind::value() const
732{
733 Q_D(const QQmlBind);
734 if (d->mode == QQmlBindPrivate::ObjectPropertyValue) {
735 Q_ASSERT(d->objectPropertyValueData.entry.currentKind == QQmlBindEntryKind::Variant);
736 QV4::ExecutionEngine *engine = d->objectPropertyValueData.entry.current.v4Value.engine();
737 return engine->toVariant(
738 *d->objectPropertyValueData.entry.current.v4Value.valueRef(), QMetaType());
739
740 }
741 return QVariant();
742}
743
744void QQmlBind::setValue(const QVariant &v)
745{
746 Q_D(QQmlBind);
747 switch (d->mode) {
748 case QQmlBindPrivate::GeneralizedGroup:
749 if (v.isValid())
750 warnIgnoredProperties(this);
751 return;
752 case QQmlBindPrivate::Unknown:
753 if (!v.isValid())
754 return;
755 new (&d->objectPropertyValueData) ObjectPropertyValueData;
756 d->mode = QQmlBindPrivate::ObjectPropertyValue;
757 Q_FALLTHROUGH();
758 case QQmlBindPrivate::ObjectPropertyValue: {
759 QQmlBindEntry *targetEntry = &d->objectPropertyValueData.entry;
760 QQmlEngine *engine = qmlEngine(this);
761 if (!engine) {
762 qWarning() << "QQmlBind must be created in a QML context";
763 return;
764 }
765 targetEntry->currentKind
766 = targetEntry->current.set(engine->handle(), v, targetEntry->currentKind);
767 prepareEval();
768 break;
769 }
770 }
771 emit valueChanged();
772}
773
774/*!
775 \qmlproperty bool QtQml::Binding::delayed
776 \since 5.8
777
778 This property holds whether the binding should be delayed.
779
780 A delayed binding will not immediately update the target, but rather wait
781 until the event queue has been cleared. This can be used as an optimization,
782 or to prevent intermediary values from being assigned.
783
784 \code
785 Binding {
786 contactName.text.value: givenName + " " + familyName
787 when: list.ListView.isCurrentItem
788 delayed: true
789 }
790 \endcode
791
792 \note Using the \l delayed property incurs a run time cost as the Binding
793 element has to create a proxy for the value, so that it can delay its
794 application to the actual target. When using the \l target and
795 \l property properties, this cost is lower because the \l value
796 property can be re-used as proxy. When using the form shown above,
797 Binding will allocate a separate object with a dynamic meta-object to
798 hold the proxy values.
799*/
800bool QQmlBind::delayed() const
801{
802 Q_D(const QQmlBind);
803 return d->delayed;
804}
805
806void QQmlBind::setDelayed(bool delayed)
807{
808 Q_D(QQmlBind);
809 if (d->delayed == delayed)
810 return;
811
812 d->delayed = delayed;
813 if (!d->componentComplete)
814 return;
815
816 if (d->mode == QQmlBindPrivate::GeneralizedGroup) {
817 d->generalizedGroupData.delayedValues.reset();
818
819 QVarLengthArray<QQmlBindEntry, 1> oldEntries = std::move(d->generalizedGroupData.entries);
820 d->generalizedGroupData.entries.clear();
821 d->buildBindEntries(this, nullptr);
822
823 for (qsizetype i = 0, end = oldEntries.size(); i < end; ++i) {
824 QQmlBindEntry &newEntry = d->generalizedGroupData.entries[i];
825 QQmlBindEntry &oldEntry = oldEntries[i];
826 newEntry.previousKind = newEntry.previous.set(
827 std::move(oldEntry.previous), oldEntry.previousKind, newEntry.previousKind);
828 if (d->delayed && oldEntry.currentKind == QQmlBindEntryKind::Binding)
829 QQmlAnyBinding::removeBindingFrom(oldEntry.prop);
830 }
831 }
832
833 if (!d->delayed)
834 eval();
835
836 emit delayedChanged();
837}
838
839/*!
840 \qmlproperty enumeration QtQml::Binding::restoreMode
841 \since 5.14
842
843 This property can be used to describe if and how the original value should
844 be restored when the binding is disabled.
845
846 The possible values are:
847
848 \value Binding.RestoreNone The original value is not restored at all
849 \value Binding.RestoreBinding The original value is restored if it was another binding.
850 In that case the old binding is in effect again.
851 \value Binding.RestoreValue The original value is restored if it was a plain
852 value rather than a binding.
853 \value Binding.RestoreBindingOrValue The original value is always restored.
854
855 The default value is \c Binding.RestoreBindingOrValue.
856
857 \note This property exists for backwards compatibility with earlier versions
858 of Qt. Don't use it in new code.
859*/
860QQmlBind::RestorationMode QQmlBind::restoreMode() const
861{
862 Q_D(const QQmlBind);
863 unsigned result = RestoreNone;
864 if (d->restoreValue)
865 result |= RestoreValue;
866 if (d->restoreBinding)
867 result |= RestoreBinding;
868 return RestorationMode(result);
869}
870
871void QQmlBind::setRestoreMode(RestorationMode newMode)
872{
873 Q_D(QQmlBind);
874 if (newMode != restoreMode()) {
875 d->restoreValue = (newMode & RestoreValue);
876 d->restoreBinding = (newMode & RestoreBinding);
877 emit restoreModeChanged();
878 }
879}
880
881void QQmlBind::setTarget(const QQmlProperty &p)
882{
883 Q_D(QQmlBind);
884 if (QQmlBindEntry *target = d->targetEntry()) {
885 target->setTarget(this, p);
886 return;
887 }
888 qmlWarning(this).nospace()
889 << "You should not use the 'on' syntax for Binding elements with generalized group "
890 "properties. It is ignored.";
891
892}
893
894void QQmlBindEntry::setTarget(QQmlBind *q, const QQmlProperty &p)
895{
896 if (Q_UNLIKELY(lcQtQmlBindingRemoval().isInfoEnabled())) {
897 if (QObject *oldObject = prop.object()) {
898 QMetaProperty metaProp = oldObject->metaObject()->property(prop.index());
899 if (metaProp.hasNotifySignal()) {
900 QByteArray signal('2' + metaProp.notifySignal().methodSignature());
901 QObject::disconnect(oldObject, signal.constData(),
902 q, SLOT(targetValueChanged()));
903 }
904 }
905 p.connectNotifySignal(q, SLOT(targetValueChanged()));
906 }
907
908 prop = p;
909}
910
911void QQmlBind::classBegin()
912{
913 Q_D(QQmlBind);
914 d->componentComplete = false;
915}
916
918 const QQmlProperty &prop, const QV4::CompiledData::Binding *binding,
919 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
920 const QQmlRefPointer<QQmlContextData> &contextData,
921 QObject *scopeObject)
922{
923 switch (binding->type()) {
924 case QV4::CompiledData::Binding::Type_Translation:
925 case QV4::CompiledData::Binding::Type_TranslationById:
926 return QQmlAnyBinding::createTranslationBinding(prop, compilationUnit, binding, scopeObject);
927 case QV4::CompiledData::Binding::Type_Script: {
928 const QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
929 if (id == QQmlBinding::Invalid) {
930 return QQmlAnyBinding::createFromCodeString(
931 prop, compilationUnit->bindingValueAsString(binding), scopeObject,
932 contextData, compilationUnit->finalUrlString(), binding->location.line());
933 }
934 QV4::Scope scope(contextData->engine()->handle());
935 QV4::Scoped<QV4::QmlContext> qmlCtxt(
936 scope, QV4::QmlContext::create(
937 scope.engine->rootContext(), contextData, scopeObject));
938 return QQmlAnyBinding::createFromFunction(
939 prop, compilationUnit->runtimeFunctions.at(id), scopeObject, contextData,
940 qmlCtxt);
941 }
942 default:
943 break;
944 }
945 return QQmlAnyBinding();
946}
947
948static void initCreator(
949 QQmlData::DeferredData *deferredData,
950 const QQmlRefPointer<QQmlContextData> &contextData,
951 QQmlComponentPrivate::ConstructionState *immediateState)
952{
953 if (!immediateState->hasCreator()) {
954 immediateState->setCompletePending(true);
955 immediateState->initCreator(
956 deferredData->context->parent(), deferredData->compilationUnit,
957 contextData, deferredData->inlineComponentName);
958 immediateState->creator()->beginPopulateDeferred(deferredData->context);
959 }
960}
961
962void QQmlBindPrivate::decodeBinding(
963 QQmlBind *q, const QString &propertyPrefix,
964 QQmlData::DeferredData *deferredData,
965 const QV4::CompiledData::Binding *binding,
966 QQmlComponentPrivate::ConstructionState *immediateState)
967{
968 const QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit
969 = deferredData->compilationUnit;
970 const QString propertySuffix = compilationUnit->stringAt(binding->propertyNameIndex);
971 const QString propertyName = propertyPrefix + propertySuffix;
972
973 switch (binding->type()) {
974 case QV4::CompiledData::Binding::Type_AttachedProperty:
975 if (propertyPrefix.isEmpty()) {
976 // Top-level attached properties cannot be generalized grouped properties.
977 // Treat them as regular properties.
978 // ... unless we're not supposed to handle regular properties. Then ignore them.
979 if (!immediateState)
980 return;
981
982 Q_ASSERT(compilationUnit->stringAt(compilationUnit->objectAt(binding->value.objectIndex)
983 ->inheritedTypeNameIndex).isEmpty());
984
985 const QV4::ResolvedTypeReference *typeReference
986 = compilationUnit->resolvedType(binding->propertyNameIndex);
987 Q_ASSERT(typeReference);
988 QQmlType attachedType = typeReference->type();
989 if (!attachedType.isValid()) {
990 if (QQmlTypeLoader *typeLoader = compilationUnit->engine->typeLoader()) {
991 const QQmlTypeNameCache::Result result
992 = deferredData->context->imports()->query(propertySuffix, typeLoader);
993 if (!result.isValid()) {
994 qmlWarning(q).nospace()
995 << "Unknown name " << propertySuffix << ". The binding is ignored.";
996 return;
997 }
998 attachedType = result.type;
999 }
1000 }
1001
1002 QQmlContext *context = qmlContext(q);
1003 QObject *attachedObject = qmlAttachedPropertiesObject(
1004 q, attachedType.attachedPropertiesFunction(QQmlTypeLoader::get(context->engine())));
1005 if (!attachedObject) {
1006 qmlWarning(q).nospace() <<"Could not create attached properties object '"
1007 << attachedType.typeName() << "'";
1008 return;
1009 }
1010
1011 initCreator(deferredData, QQmlContextData::get(context), immediateState);
1012 immediateState->creator()->populateDeferredInstance(
1013 q, deferredData->deferredIdx, binding->value.objectIndex, attachedObject,
1014 attachedObject, /*value type property*/ nullptr, binding);
1015 return;
1016 }
1017 Q_FALLTHROUGH();
1018 case QV4::CompiledData::Binding::Type_GroupProperty: {
1019 const QString pre = propertyName + u'.';
1020 const QV4::CompiledData::Object *subObj
1021 = compilationUnit->objectAt(binding->value.objectIndex);
1022 const QV4::CompiledData::Binding *subBinding = subObj->bindingTable();
1023 for (quint32 i = 0; i < subObj->nBindings; ++i, ++subBinding)
1024 decodeBinding(q, pre, deferredData, subBinding, immediateState);
1025 return;
1026 }
1027 default:
1028 break;
1029 }
1030
1031 QQmlBindEntry entry;
1032 QQmlContext *context = qmlContext(q);
1033 const QQmlRefPointer<QQmlContextData> contextData = QQmlContextData::get(context);
1034 entry.prop = QQmlPropertyPrivate::create(nullptr, propertyName, contextData,
1035 QQmlPropertyPrivate::InitFlag::AllowId);
1036 if (!entry.prop.isValid()) {
1037 // Try again in the context of this object. If that works, it's a regular property.
1038 // ... unless we're not supposed to handle regular properties. Then ignore it.
1039 if (!immediateState)
1040 return;
1041
1042 QQmlProperty property = QQmlPropertyPrivate::create(
1043 q, propertyName, contextData, QQmlPropertyPrivate::InitFlag::AllowSignal);
1044 if (property.isValid()) {
1045 initCreator(deferredData, contextData, immediateState);
1046 immediateState->creator()->populateDeferredBinding(
1047 property, deferredData->deferredIdx, binding);
1048 } else {
1049 qmlWarning(q).nospace() << "Unknown name " << propertyName
1050 << ". The binding is ignored.";
1051 }
1052 return;
1053 }
1054
1055 switch (mode) {
1056 case GeneralizedGroup:
1057 break;
1058 case ObjectPropertyValue:
1059 warnIgnoredProperties(q);
1060 objectPropertyValueData.~ObjectPropertyValueData();
1061 Q_FALLTHROUGH();
1062 case Unknown:
1063 new (&generalizedGroupData) GeneralizedGroupData;
1064 mode = GeneralizedGroup;
1065 break;
1066 }
1067
1068 const auto setVariant = [&entry](QV4::PersistentValue value) {
1069 entry.currentKind = entry.current.setVariant(value, entry.currentKind);
1070 };
1071
1072 const auto setBinding = [&entry](QQmlAnyBinding binding) {
1073 entry.currentKind = entry.current.set(binding, entry.currentKind);
1074 };
1075
1076 switch (binding->type()) {
1077 case QV4::CompiledData::Binding::Type_AttachedProperty:
1078 case QV4::CompiledData::Binding::Type_GroupProperty:
1079 Q_UNREACHABLE(); // Handled above
1080 break;
1081 case QV4::CompiledData::Binding::Type_Translation:
1082 case QV4::CompiledData::Binding::Type_TranslationById:
1083 case QV4::CompiledData::Binding::Type_Script:
1084 if (delayed) {
1085 if (!generalizedGroupData.delayedValues)
1086 createDelayedValues();
1087 const QString delayedName = QString::number(generalizedGroupData.entries.size());
1088 generalizedGroupData.delayedValues->insert(delayedName, QVariant());
1089 QQmlProperty bindingTarget
1090 = QQmlProperty(generalizedGroupData.delayedValues.get(), delayedName);
1091 Q_ASSERT(bindingTarget.isValid());
1092 QQmlAnyBinding anyBinding = createBinding(
1093 bindingTarget, binding, compilationUnit, contextData, q);
1094 anyBinding.installOn(bindingTarget);
1095 } else {
1096 setBinding(createBinding(entry.prop, binding, compilationUnit, contextData, q));
1097 }
1098 break;
1099 case QV4::CompiledData::Binding::Type_String:
1100 setVariant(QV4::PersistentValue(
1101 compilationUnit->engine,
1102 compilationUnit->runtimeStrings[binding->stringIndex]->asReturnedValue()));
1103 break;
1104 case QV4::CompiledData::Binding::Type_Number:
1105 setVariant(QV4::PersistentValue(
1106 compilationUnit->engine,
1107 compilationUnit->constants[binding->value.constantValueIndex].asReturnedValue()));
1108 break;
1109 case QV4::CompiledData::Binding::Type_Boolean:
1110 setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode(binding->value.b)));
1111 break;
1112 case QV4::CompiledData::Binding::Type_Null:
1113 setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode::null()));
1114 break;
1115 case QV4::CompiledData::Binding::Type_Object:
1116 case QV4::CompiledData::Binding::Type_Invalid:
1117 break;
1118 }
1119
1120 generalizedGroupData.entries.append(std::move(entry));
1121}
1122
1124{
1125 generalizedGroupData.delayedValues.reset(QQmlPropertyMap::create());
1126 QQmlPropertyMap *delayedValues = generalizedGroupData.delayedValues.get();
1127 QObject::connect(
1128 delayedValues, &QQmlPropertyMap::valueChanged,
1129 delayedValues, [this](QString delayedName, const QVariant &value) {
1130 Q_UNUSED(value);
1131 onDelayedValueChanged(std::move(delayedName));
1132 }
1133 );
1134}
1135
1136void QQmlBindPrivate::onDelayedValueChanged(QString delayedName)
1137{
1138 Q_ASSERT(delayed);
1139 Q_ASSERT(mode == GeneralizedGroup);
1140 QQmlPropertyMap *delayedValues = generalizedGroupData.delayedValues.get();
1141 Q_ASSERT(delayedValues);
1142 const QString pendingName = QStringLiteral("pending");
1143 QStringList pending = qvariant_cast<QStringList>((*delayedValues)[pendingName]);
1144 if (componentComplete && pending.size() == 0)
1145 QTimer::singleShot(0, delayedValues, [this]() { evalDelayed(); });
1146 else if (pending.contains(delayedName))
1147 return;
1148
1149 pending.append(std::move(delayedName));
1150 (*delayedValues)[pendingName].setValue(std::move(pending));
1151}
1152
1154{
1155 Q_ASSERT(mode == GeneralizedGroup);
1156 QQmlPropertyMap *delayedValues = generalizedGroupData.delayedValues.get();
1157 if (!when || !delayedValues)
1158 return;
1159
1160 const QString pendingName = QStringLiteral("pending");
1161 const QStringList pending = qvariant_cast<QStringList>((*delayedValues)[pendingName]);
1162 for (const QString &delayedName : pending) {
1163 bool ok;
1164 const int delayedIndex = delayedName.toInt(&ok);
1165 Q_ASSERT(ok);
1166 Q_ASSERT(delayedIndex >= 0 && delayedIndex < generalizedGroupData.entries.size());
1167 generalizedGroupData.entries[delayedIndex].prop.write((*delayedValues)[delayedName]);
1168 }
1169 (*delayedValues)[pendingName].setValue(QStringList());
1170}
1171
1172void QQmlBindPrivate::buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState)
1173{
1174 QQmlData *data = QQmlData::get(q);
1175 if (data && !data->deferredData.isEmpty()) {
1176 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine());
1177 for (QQmlData::DeferredData *deferredData : data->deferredData) {
1178 QMultiHash<int, const QV4::CompiledData::Binding *> *bindings = &deferredData->bindings;
1179 if (deferredState) {
1180 QQmlComponentPrivate::ConstructionState constructionState;
1181 for (auto it = bindings->cbegin(); it != bindings->cend(); ++it)
1182 decodeBinding(q, QString(), deferredData, *it, &constructionState);
1183
1184
1185 if (constructionState.hasCreator()) {
1186 ++ep->inProgressCreations;
1187 constructionState.creator()->finalizePopulateDeferred();
1188 constructionState.appendCreatorErrors();
1189 deferredState->push_back(std::move(constructionState));
1190 }
1191 } else {
1192 for (auto it = bindings->cbegin(); it != bindings->cend(); ++it)
1193 decodeBinding(q, QString(), deferredData, *it, nullptr);
1194 }
1195 }
1196
1197 if (deferredState) {
1198 data->releaseDeferredData();
1199 if (!deferredState->empty())
1200 QQmlComponentPrivate::completeDeferred(ep, deferredState);
1201 }
1202 }
1203}
1204
1205void QQmlBind::componentComplete()
1206{
1207 Q_D(QQmlBind);
1208 QQmlComponentPrivate::DeferredState deferredState;
1209 d->buildBindEntries(this, &deferredState);
1210 d->componentComplete = true;
1211 if (d->mode == QQmlBindPrivate::ObjectPropertyValue) {
1212 QQmlBindEntry *target = d->targetEntry();
1213 if (!target->prop.isValid()) {
1214 target->setTarget(this, QQmlProperty(
1215 d->objectPropertyValueData.obj,
1216 d->objectPropertyValueData.propName, qmlContext(this)));
1217 }
1218 }
1219 d->validate(this);
1220 if (d->mode == QQmlBindPrivate::GeneralizedGroup)
1221 d->evalDelayed();
1222 eval();
1223}
1224
1225void QQmlBind::prepareEval()
1226{
1227 Q_D(QQmlBind);
1228 if (d->delayed) {
1229 if (!d->pendingEval)
1230 QTimer::singleShot(0, this, &QQmlBind::eval);
1231 d->pendingEval = true;
1232 } else {
1233 eval();
1234 }
1235}
1236
1238{
1239 previousKind = previous.destroy(previousKind);
1240}
1241
1243{
1244 switch (entry->currentKind) {
1245 case QQmlBindEntryKind::V4Value: {
1246 auto propPriv = QQmlPropertyPrivate::get(entry->prop);
1247 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(propPriv->object);
1248 Q_ASSERT(vmemo);
1249 return QV4::RuntimeHelpers::strictEqual(
1250 // fromReturnedValue is OK here because strictEqual will not allocate
1251 QV4::Value::fromReturnedValue(vmemo->vmeProperty(propPriv->core.coreIndex())),
1252 *entry->current.v4Value.valueRef());
1253 }
1254 case QQmlBindEntryKind::Variant: {
1255 const QV4::PersistentValue &v4Value = entry->current.v4Value;
1256 return v4Value.engine()->toVariant(*v4Value.valueRef(), entry->prop.propertyMetaType())
1257 == entry->prop.read();
1258 }
1259 case QQmlBindEntryKind::Binding:
1260 return entry->current.binding == QQmlAnyBinding::ofProperty(entry->prop);
1261 case QQmlBindEntryKind::None:
1262 break;
1263 }
1264
1265 return false;
1266}
1267
1269{
1270 if (!entry->prop.isValid() || (entry->currentKind == QQmlBindEntryKind::None))
1271 return;
1272 if (!entry->prop.object())
1273 return; // if the target is already gone, we can't do anything
1274
1275 if (!when) {
1276 if (!isCurrent(entry)) {
1277 entry->clearPrev();
1278 return;
1279 }
1280
1281 //restore any previous binding
1282 switch (entry->previousKind) {
1283 case QQmlBindEntryKind::Binding:
1284 if (restoreBinding) {
1285 QQmlAnyBinding p = std::move(entry->previous.binding);
1286 entry->clearPrev(); // Do that before setBinding(), as setBinding() may recurse.
1287 p.installOn(entry->prop);
1288 }
1289 break;
1290 case QQmlBindEntryKind::V4Value:
1291 if (restoreValue) {
1292 QQmlAnyBinding::takeFrom(entry->prop); // we don't want to have a binding active
1293 auto propPriv = QQmlPropertyPrivate::get(entry->prop);
1294 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(propPriv->object);
1295 Q_ASSERT(vmemo);
1296 vmemo->setVMEProperty(propPriv->core.coreIndex(),
1297 *entry->previous.v4Value.valueRef());
1298 entry->clearPrev();
1299 }
1300 break;
1301 case QQmlBindEntryKind::Variant:
1302 if (restoreValue) {
1303 QQmlAnyBinding::takeFrom(entry->prop); // we don't want to have a binding active
1304 const QV4::PersistentValue &v4Value = entry->previous.v4Value;
1305 entry->prop.write(v4Value.engine()->toVariant(
1306 *v4Value.valueRef(), entry->prop.propertyMetaType()));
1307 entry->clearPrev();
1308 }
1309 break;
1310 case QQmlBindEntryKind::None:
1311 break;
1312 }
1313 return;
1314 }
1315
1316 //save any set binding for restoration
1317 if (entry->previousKind == QQmlBindEntryKind::None) {
1318 // try binding first; we need to use takeFrom to properly unlink the binding
1319 QQmlAnyBinding prevBind = QQmlAnyBinding::takeFrom(entry->prop);
1320 if (prevBind) {
1321 entry->previousKind = entry->previous.set(std::move(prevBind), entry->previousKind);
1322 } else {
1323 // nope, try a V4 value next
1324 auto propPriv = QQmlPropertyPrivate::get(entry->prop);
1325 auto propData = propPriv->core;
1326 if (!propPriv->valueTypeData.isValid() && propData.isVarProperty()) {
1327 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(propPriv->object);
1328 Q_ASSERT(vmemo);
1329 auto retVal = vmemo->vmeProperty(propData.coreIndex());
1330 entry->previousKind = entry->previous.set(
1331 QV4::PersistentValue(vmemo->engine, retVal), entry->previousKind);
1332 } else {
1333 // nope, use the meta object to get a QVariant
1334 entry->previousKind = entry->previous.set(
1335 propPriv->engine->handle(), entry->prop.read(), entry->previousKind);
1336 }
1337 }
1338 }
1339
1340 // NOTE: removeBinding has no effect on QProperty classes, but
1341 // we already used takeBinding to remove it
1342 QQmlPropertyPrivate::removeBinding(entry->prop, QQmlPropertyPrivate::OverrideSticky);
1343}
1344
1346{
1347 if (!entry->prop.isValid())
1348 return;
1349 switch (entry->currentKind) {
1350 case QQmlBindEntryKind::Variant: {
1351 const QV4::PersistentValue &v4Value = entry->current.v4Value;
1352 entry->prop.write(
1353 v4Value.engine()->toVariant(*v4Value.valueRef(), entry->prop.propertyMetaType()));
1354 break;
1355 }
1356 case QQmlBindEntryKind::Binding:
1357 Q_ASSERT(!delayed);
1358 entry->current.binding.installOn(entry->prop);
1359 break;
1360 case QQmlBindEntryKind::V4Value: {
1361 auto propPriv = QQmlPropertyPrivate::get(entry->prop);
1362 QQmlVMEMetaObject::get(propPriv->object)->setVMEProperty(
1363 propPriv->core.coreIndex(), *entry->current.v4Value.valueRef());
1364 break;
1365 }
1366 case QQmlBindEntryKind::None:
1367 break;
1368 }
1369}
1370
1371void QQmlBind::eval()
1372{
1373 Q_D(QQmlBind);
1374 d->pendingEval = false;
1375 if (!d->componentComplete)
1376 return;
1377
1378 switch (d->mode) {
1379 case QQmlBindPrivate::GeneralizedGroup:
1380 for (QQmlBindEntry &entry : d->generalizedGroupData.entries)
1381 d->preEvalEntry(&entry);
1382 break;
1383 case QQmlBindPrivate::ObjectPropertyValue:
1384 d->preEvalEntry(&d->objectPropertyValueData.entry);
1385 break;
1386 case QQmlBindPrivate::Unknown:
1387 break;
1388 }
1389
1390 if (!d->when)
1391 return;
1392
1393 d->writingProperty = true;
1394 switch (d->mode) {
1395 case QQmlBindPrivate::GeneralizedGroup:
1396 for (QQmlBindEntry &entry : d->generalizedGroupData.entries)
1397 d->postEvalEntry(&entry);
1398 break;
1399 case QQmlBindPrivate::ObjectPropertyValue:
1400 d->postEvalEntry(&d->objectPropertyValueData.entry);
1401 break;
1402 case QQmlBindPrivate::Unknown:
1403 break;
1404 }
1405 d->writingProperty = false;
1406}
1407
1408void QQmlBind::targetValueChanged()
1409{
1410 Q_D(QQmlBind);
1411 if (d->writingProperty)
1412 return;
1413
1414 if (!d->when)
1415 return;
1416
1417 QUrl url;
1418 quint16 line = 0;
1419
1420 const QQmlData *ddata = QQmlData::get(this, false);
1421 if (ddata && ddata->outerContext) {
1422 url = ddata->outerContext->url();
1423 line = ddata->lineNumber;
1424 }
1425
1426 qCInfo(lcQtQmlBindingRemoval,
1427 "The target property of the Binding element created at %s:%d was changed from "
1428 "elsewhere. This does not overwrite the binding. The target property will still be "
1429 "updated when the value of the Binding element changes.",
1430 qPrintable(url.toString()), line);
1431}
1432
1433QT_END_NAMESPACE
1434
1435#include "moc_qqmlbind_p.cpp"
void validate(QQmlBind *binding) const
Definition qqmlbind.cpp:358
GeneralizedGroupData generalizedGroupData
Definition qqmlbind.cpp:273
void postEvalEntry(QQmlBindEntry *entry)
QQmlBindEntry * targetEntry()
Definition qqmlbind.cpp:342
void onDelayedValueChanged(QString delayedName)
void decodeBinding(QQmlBind *q, const QString &propertyPrefix, QQmlData::DeferredData *deferredData, const QV4::CompiledData::Binding *binding, QQmlComponentPrivate::ConstructionState *immediateState)
Definition qqmlbind.cpp:962
bool isCurrent(QQmlBindEntry *entry) const
void preEvalEntry(QQmlBindEntry *entry)
void buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState)
void createDelayedValues()
ObjectPropertyValueData objectPropertyValueData
Definition qqmlbind.cpp:274
static void warnIgnoredProperties(QQmlBind *q)
Definition qqmlbind.cpp:327
static void initCreator(QQmlData::DeferredData *deferredData, const QQmlRefPointer< QQmlContextData > &contextData, QQmlComponentPrivate::ConstructionState *immediateState)
Definition qqmlbind.cpp:948
static QQmlAnyBinding createBinding(const QQmlProperty &prop, const QV4::CompiledData::Binding *binding, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, const QQmlRefPointer< QQmlContextData > &contextData, QObject *scopeObject)
Definition qqmlbind.cpp:917
std::unique_ptr< QQmlPropertyMap > delayedValues
Definition qqmlbind.cpp:234
QVarLengthArray< QQmlBindEntry, 1 > entries
Definition qqmlbind.cpp:229
QPointer< QObject > obj
Definition qqmlbind.cpp:219
QQmlBindEntryKind previousKind
Definition qqmlbind.cpp:206
QQmlBindEntry(const QQmlBindEntry &other)
Definition qqmlbind.cpp:168
QQmlBindEntry & operator=(QQmlBindEntry &&other) noexcept
Definition qqmlbind.cpp:181
QQmlProperty prop
Definition qqmlbind.cpp:204
void setTarget(QQmlBind *q, const QQmlProperty &p)
Definition qqmlbind.cpp:894
QQmlBindEntryKind currentKind
Definition qqmlbind.cpp:205
QQmlBindEntry & operator=(const QQmlBindEntry &other)
Definition qqmlbind.cpp:191
QQmlBindEntryContent current
Definition qqmlbind.cpp:202
QQmlBindEntryContent previous
Definition qqmlbind.cpp:203
void validate(QQmlBind *q) const
Definition qqmlbind.cpp:334
QQmlBindEntry(QQmlBindEntry &&other) noexcept
Definition qqmlbind.cpp:162
QQmlBindEntry()=default
QQmlAnyBinding binding
Definition qqmlbind.cpp:139