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