Revision control

Copy as Markdown

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qqmlbind_p.h"
#include <private/qqmlanybinding_p.h>
#include <private/qqmlbinding_p.h>
#include <private/qqmlcomponent_p.h>
#include <private/qqmlmetatype_p.h>
#include <private/qqmlnullablevalue_p.h>
#include <private/qqmlproperty_p.h>
#include <private/qqmlvmemetaobject_p.h>
#include <private/qv4persistent_p.h>
#include <private/qv4qmlcontext_p.h>
#include <private/qv4resolvedtypereference_p.h>
#include <private/qv4runtime_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlinfo.h>
#include <QtQml/qqmlproperty.h>
#include <QtQml/qqmlpropertymap.h>
#include <QtQml/private/qqmllist_p.h>
#include <QtQml/private/qqmllistwrapper_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QtCore/private/qobject_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfile.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qpointer.h>
#include <QtCore/qtimer.h>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcQtQmlBindingRemoval, "qt.qml.binding.removal", QtWarningMsg)
enum class QQmlBindEntryKind: quint8 {
V4Value,
Variant,
Binding,
None
};
/*!
* \internal
* QQmlBindEntryContent can store one of QV4::Value, QVariant, QQmlAnyBinding, or nothing,
* as denoted by QQmlBindEntryKind. It expects the calling code to know what is stored at
* any time. On each method invocation, the current kind has to be passed as last parameter
* and the new kind is returned.
*/
union QQmlBindEntryContent {
Q_DISABLE_COPY_MOVE(QQmlBindEntryContent)
public:
QQmlBindEntryContent() {}
~QQmlBindEntryContent() {}
[[nodiscard]] QQmlBindEntryKind set(
QQmlBindEntryContent &&other, QQmlBindEntryKind newKind, QQmlBindEntryKind oldKind)
{
silentDestroy(oldKind);
switch (newKind) {
case QQmlBindEntryKind::V4Value:
case QQmlBindEntryKind::Variant:
new (&v4Value) QV4::PersistentValue(std::move(other.v4Value));
break;
case QQmlBindEntryKind::Binding:
new (&binding) QQmlAnyBinding(std::move(other.binding));
break;
case QQmlBindEntryKind::None:
break;
}
return newKind;
}
[[nodiscard]] QQmlBindEntryKind set(
const QQmlBindEntryContent &other, QQmlBindEntryKind newKind, QQmlBindEntryKind oldKind)
{
silentDestroy(oldKind);
switch (newKind) {
case QQmlBindEntryKind::V4Value:
case QQmlBindEntryKind::Variant:
new (&v4Value) QV4::PersistentValue(other.v4Value);
break;
case QQmlBindEntryKind::Binding:
new (&binding) QQmlAnyBinding(other.binding);
break;
case QQmlBindEntryKind::None:
break;
}
return newKind;
}
[[nodiscard]] QQmlBindEntryKind destroy(QQmlBindEntryKind kind)
{
switch (kind) {
case QQmlBindEntryKind::V4Value:
case QQmlBindEntryKind::Variant:
v4Value.~PersistentValue();
break;
case QQmlBindEntryKind::Binding:
binding.~QQmlAnyBinding();
break;
case QQmlBindEntryKind::None:
break;
}
return QQmlBindEntryKind::None;
}
[[nodiscard]] QQmlBindEntryKind set(
QV4::ExecutionEngine *engine, const QVariant &v, QQmlBindEntryKind oldKind)
{
silentDestroy(oldKind);
new (&v4Value) QV4::PersistentValue(engine, engine->fromVariant(v));
return QQmlBindEntryKind::Variant;
}
[[nodiscard]] QQmlBindEntryKind set(QV4::PersistentValue v, QQmlBindEntryKind oldKind)
{
silentDestroy(oldKind);
new (&v4Value) QV4::PersistentValue(std::move(v));
return QQmlBindEntryKind::V4Value;
}
[[nodiscard]] QQmlBindEntryKind setVariant(QV4::PersistentValue v, QQmlBindEntryKind oldKind)
{
silentDestroy(oldKind);
new (&v4Value) QV4::PersistentValue(std::move(v));
return QQmlBindEntryKind::Variant;
}
[[nodiscard]] QQmlBindEntryKind set(QQmlAnyBinding v, QQmlBindEntryKind oldKind)
{
silentDestroy(oldKind);
new (&binding) QQmlAnyBinding(std::move(v));
return QQmlBindEntryKind::Binding;
}
QV4::PersistentValue v4Value;
QQmlAnyBinding binding;
int none = 0;
private:
void silentDestroy(QQmlBindEntryKind oldKind)
{
const QQmlBindEntryKind dead = destroy(oldKind);
Q_ASSERT(dead == QQmlBindEntryKind::None);
Q_UNUSED(dead);
}
};
/*!
* \internal
* QQmlBindEntry holds two QQmlBindEntryContent members, along with their kinds.
* The \l current content is the value or binding the Binding element installs on
* the target if enabled (that is, if \l{when}). The \l previous content is what
* the target holds before the Binding element installs its binding or value. It
* is restored if !\l{when}. The \l prop member holds the target property.
*/
struct QQmlBindEntry
{
QQmlBindEntry() = default;
QQmlBindEntry(QQmlBindEntry &&other) noexcept : prop(std::move(other.prop))
{
currentKind = current.set(std::move(other.current), other.currentKind, currentKind);
previousKind = previous.set(std::move(other.previous), other.previousKind, previousKind);
}
QQmlBindEntry(const QQmlBindEntry &other)
: prop(other.prop)
{
currentKind = current.set(other.current, other.currentKind, currentKind);
previousKind = previous.set(other.previous, other.previousKind, previousKind);
}
~QQmlBindEntry()
{
currentKind = current.destroy(currentKind);
previousKind = previous.destroy(previousKind);
}
QQmlBindEntry &operator=(QQmlBindEntry &&other) noexcept
{
if (this == &other)
return *this;
prop = std::move(other.prop);
currentKind = current.set(std::move(other.current), other.currentKind, currentKind);
previousKind = previous.set(std::move(other.previous), other.previousKind, previousKind);
return *this;
}
QQmlBindEntry &operator=(const QQmlBindEntry &other)
{
if (this == &other)
return *this;
prop = other.prop;
currentKind = current.set(other.current, other.currentKind, currentKind);
previousKind = previous.set(other.previous, other.previousKind, previousKind);
return *this;
}
QQmlBindEntryContent current;
QQmlBindEntryContent previous;
QQmlProperty prop;
QQmlBindEntryKind currentKind = QQmlBindEntryKind::None;
QQmlBindEntryKind previousKind = QQmlBindEntryKind::None;
void validate(QQmlBind *q) const;
void clearPrev();
void setTarget(QQmlBind *q, const QQmlProperty &p);
};
struct ObjectPropertyValueData
{
// Only one entry is used for target/property/value
QQmlBindEntry entry;
// The \l target object
QPointer<QObject> obj;
// The \l property name
QString propName;
};
struct GeneralizedGroupData
{
// There can be multiple entries when using the generalized grouped
// property mode.
QVarLengthArray<QQmlBindEntry, 1> entries;
// Any values we need to create a proxy for. This is necessary when
// using the \l delayed member on generalized grouped properties. See
// the note on \l delayed.
std::unique_ptr<QQmlPropertyMap> delayedValues;
};
class QQmlBindPrivate : public QObjectPrivate
{
public:
enum Mode : quint8 {
GeneralizedGroup,
ObjectPropertyValue,
Unknown,
};
QQmlBindPrivate()
: when(true)
, componentComplete(true)
, delayed(false)
, pendingEval(false)
, restoreBinding(true)
, restoreValue(true)
, writingProperty(false)
{
}
~QQmlBindPrivate()
{
switch (mode) {
case GeneralizedGroup:
generalizedGroupData.~GeneralizedGroupData();
return;
case ObjectPropertyValue:
objectPropertyValueData.~ObjectPropertyValueData();
return;
case Unknown:
return;
}
}
union {
int noData = 0;
GeneralizedGroupData generalizedGroupData;
ObjectPropertyValueData objectPropertyValueData;
};
Mode mode = Unknown;
// Whether the binding is enabled.
bool when: 1;
// Whether we have already parsed any generalized grouped properties
// we might need.
bool componentComplete:1;
// Whether we should run in "delayed" mode and proxy all values before
// applying them to the target.
bool delayed:1;
// In delayed mode, when using the target/property mode, the \l value
// is the proxy. Then pendingEval denotes that a timer is active to
// apply the value. We should not start another timer then.
bool pendingEval:1;
// Whether we should restore bindings on !when.
// TODO: Deprecate this and always do.
bool restoreBinding:1;
// Whether we should restore values on !when.
// TODO: Deprecate this and always do.
bool restoreValue:1;
// writingProperty tracks whether we are updating the target property
// when using target/property/value. We use this information to warn about
// binding removal if we detect the target property to be updated while we
// are not writing it. This doesn't remove the Binding after all.
// For generalized grouped properties, we don't have to do this as writing
// the target property does remove the binding, just like it removes any
// other binding.
bool writingProperty:1;
QQmlBindEntry *targetEntry();
void validate(QQmlBind *binding) const;
void decodeBinding(
QQmlBind *q, const QString &propertyPrefix, QQmlData::DeferredData *deferredData,
int objectIndex, const QV4::CompiledData::Binding *binding,
QQmlComponentPrivate::ConstructionState *immediateState);
void createDelayedValues();
void onDelayedValueChanged(QString delayedName);
void evalDelayed();
void buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState);
void preEvalEntry(QQmlBindEntry *entry);
void postEvalEntry(QQmlBindEntry *entry);
bool isCurrent(QQmlBindEntry *entry) const;
};
static void warnIgnoredProperties(QQmlBind *q)
{
qmlWarning(q)
<< "You should not set the 'object', 'property', or 'value' properties when using "
"generalized grouped properties. They are ignored.";
}
void QQmlBindEntry::validate(QQmlBind *q) const
{
if (!prop.isWritable()) {
qmlWarning(q) << "Property '" << prop.name() << "' on "
<< QQmlMetaType::prettyTypeName(prop.object()) << " is read-only.";
}
}
QQmlBindEntry *QQmlBindPrivate::targetEntry()
{
switch (mode) {
case GeneralizedGroup:
Q_UNREACHABLE_RETURN(nullptr);
case Unknown:
new (&objectPropertyValueData) ObjectPropertyValueData;
mode = ObjectPropertyValue;
Q_FALLTHROUGH();
case ObjectPropertyValue:
return &objectPropertyValueData.entry;
}
return nullptr;
}
void QQmlBindPrivate::validate(QQmlBind *q) const
{
if (!when)
return;
switch (mode) {
case ObjectPropertyValue:
if (!objectPropertyValueData.obj)
break;
if (objectPropertyValueData.entry.prop.isValid()) {
objectPropertyValueData.entry.validate(q);
} else {
qmlWarning(q) << "Property '" << objectPropertyValueData.propName
<< "' does not exist on "
<< QQmlMetaType::prettyTypeName(objectPropertyValueData.obj) << ".";
}
break;
case GeneralizedGroup:
for (const QQmlBindEntry &entry : generalizedGroupData.entries)
entry.validate(q);
break;
case Unknown:
break;
}
}
/*!
\qmltype Binding
\inqmlmodule QtQml
\ingroup qtquick-interceptors
\brief Enables the arbitrary creation of property bindings.
In QML, property bindings result in a dependency between the properties of
different objects.
\section1 Binding to an Inaccessible Property
Sometimes it is necessary to bind an object's property to
that of another object that isn't directly instantiated by QML, such as a
property of a class exported to QML by C++. You can use the Binding type
to establish this dependency; binding any value to any object's property.
For example, in a C++ application that maps an "app.enteredText" property
into QML, you can use Binding to update the enteredText property.
\qml
TextEdit { id: myTextField; text: "Please type here..." }
Binding { app.enteredText: myTextField.text }
\endqml
When \c{text} changes, the C++ property \c{enteredText} will update
automatically.
\section1 Conditional Bindings
In some cases you may want to modify the value of a property when a certain
condition is met but leave it unmodified otherwise. Often, it's not possible
to do this with direct bindings, as you have to supply values for all
possible branches.
For example, the code snippet below results in a warning whenever you
release the mouse. This is because the value of the binding is undefined
when the mouse isn't pressed.
\qml
// produces warning: "Unable to assign [undefined] to double value"
targetProperty: if (mouse.pressed) mouse.mouseX
\endqml
The Binding type can prevent this warning.
\qml
Binding on targetProperty {
when: mouse.pressed
value: mouse.mouseX
}
\endqml
The Binding type restores any previously set direct bindings on the
property.
However, the binding to \c{Binding}'s \l{value} property is still evaluated
unconditionally, even when \c{when} is \c{false}.
This might cause warnings when the Binding is for example used to guard
against a value being \c{null}:
\qml
Binding on targetProperty {
when: root.object !== null
// produces warning: "TypeError: Cannot read property 'someProperty' of null"
value: root.object.someProperty
}
\endqml
There are to ways to avoid this issue:
\list
\li If the target has an \c id, you can use the syntax described in
\l{Multiple targets in one Binding}, which also causes the
binding expression to only run when the \c{Binding} is enabled:
\qml
Binding {
when: root.object !== null
root.targetProperty: root.object.someProperty
}
\endqml
\li You can repeat the check in the binding and return early:
\qml
Binding on targetProperty {
when: root.object !== null
value: {
if (root.object === null)
return
return root.object.someProperty
}
}
\endqml
\endlist
\section1 Multiple targets in one Binding
You can specify multiple bindings to the same object in one Binding element:
\qml
Text {
id: t1
}
Binding {
t1 {
color: "#00FF00"
text: "green text"
}
}
\endqml
You can also specify several bindings with different target objects in a
single Binding element:
\qml
Text {
id: t1
}
Text {
id: t2
}
Binding {
t1.text: "Foo"
t2.text: "Bar"
}
\endqml
\sa {Qt Qml}
*/
QQmlBind::QQmlBind(QObject *parent)
: QObject(*(new QQmlBindPrivate), parent)
{
}
QQmlBind::~QQmlBind()
{
Q_D(QQmlBind);
// restore state when dynamic Binding is destroyed
if (!(d->when && d->componentComplete && restoreMode() != RestoreNone))
return;
// isDeletingChildren is supposed to happen later; we couldn't use declarativeData
// if isDeletingChildren were set
Q_ASSERT(!d->isDeletingChildren);
// We can't use qmlEngine (or QQmlData::get), as that checks for scheduled deletion
if (auto ddata = static_cast<QQmlData *>(d->declarativeData);
ddata && ddata->context && QQmlData::wasDeleted(ddata->context->engine()))
return; // whole engine is going away; don't bother resetting
d->when = false; // internal only change, no signal emission
eval();
}
/*!
\qmlproperty bool QtQml::Binding::when
This property holds when the binding is active.
This should be set to an expression that evaluates to true when you want the binding to be active.
\qml
Binding {
contactName.text: name
when: list.ListView.isCurrentItem
}
\endqml
By default, any binding or value that was set perviously is restored when the binding becomes
inactive. You can customize the restoration behavior using the \l restoreMode property.
\sa restoreMode
*/
bool QQmlBind::when() const
{
Q_D(const QQmlBind);
return d->when;
}
void QQmlBind::setWhen(bool v)
{
Q_D(QQmlBind);
if (d->when == v)
return;
d->when = v;
if (v && d->componentComplete)
d->validate(this);
eval();
emit whenChanged();
}
/*!
\qmlproperty QtObject QtQml::Binding::target
The object to be updated. You need to use this property if the binding target
does not have an \c id attribute (for example, when the target is a singleton).
Otherwise, the following two pieces of code are equivalent:
\qml
Binding { contactName.text: name }
\endqml
\qml
Binding {
target: contactName
property: "text"
value: name
}
\endqml
The former one is much more compact, but you cannot replace the target
object or property at run time. With the latter one you can.
*/
QObject *QQmlBind::object() const
{
Q_D(const QQmlBind);
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
case QQmlBindPrivate::Unknown:
return nullptr;
case QQmlBindPrivate::ObjectPropertyValue:
return d->objectPropertyValueData.obj;
}
Q_UNREACHABLE_RETURN(nullptr);
}
void QQmlBind::setObject(QObject *obj)
{
Q_D(QQmlBind);
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
if (obj != nullptr)
warnIgnoredProperties(this);
return;
case QQmlBindPrivate::ObjectPropertyValue:
if (d->objectPropertyValueData.obj == obj)
return;
break;
case QQmlBindPrivate::Unknown:
if (obj == nullptr)
return;
new (&d->objectPropertyValueData) ObjectPropertyValueData;
d->mode = QQmlBindPrivate::ObjectPropertyValue;
break;
}
if (d->when) {
/* if we switch the object at runtime, we need to restore the
previous binding on the old object before continuing */
d->when = false;
eval();
d->when = true;
}
/* if "when" and "target" depend on the same property, we might
end up here before we could have updated "when". So reevaluate
when manually here.
*/
const QQmlProperty whenProp(this, QLatin1StringView("when"));
const auto potentialWhenBinding = QQmlAnyBinding::ofProperty(whenProp);
if (auto abstractBinding = potentialWhenBinding.asAbstractBinding()) {
QQmlBinding *binding = static_cast<QQmlBinding *>(abstractBinding);
if (binding->hasValidContext()) {
const auto boolType = QMetaType::fromType<bool>();
bool when;
binding->evaluate(&when, boolType);
if (when != d->when) {
d->when = when;
emit whenChanged();
}
}
}
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
case QQmlBindPrivate::Unknown:
Q_UNREACHABLE();
return;
case QQmlBindPrivate::ObjectPropertyValue:
d->objectPropertyValueData.obj = obj;
if (d->componentComplete) {
setTarget(QQmlProperty(
d->objectPropertyValueData.obj, d->objectPropertyValueData.propName,
qmlContext(this)));
}
break;
}
if (d->componentComplete && d->when)
d->validate(this);
eval();
emit objectChanged();
}
/*!
\qmlproperty string QtQml::Binding::property
The property to be updated.
This can be a grouped property if the expression results in accessing a
property of a \l {QML Value Types}{value type}. For example:
\qml
Item {
id: item
property rect rectangle: Qt.rect(0, 0, 200, 200)
}
Binding {
target: item
property: "rectangle.x"
value: 100
}
\endqml
You only need to use this property if you can't supply the binding target
declaratively. The following snippet of code is equivalent to the above
binding, but more compact:
\qml
Binding { item.rectangle.x: 100 }
\endqml
*/
QString QQmlBind::property() const
{
Q_D(const QQmlBind);
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
case QQmlBindPrivate::Unknown:
return QString();
case QQmlBindPrivate::ObjectPropertyValue:
return d->objectPropertyValueData.propName;
}
Q_UNREACHABLE_RETURN(QString());
}
void QQmlBind::setProperty(const QString &p)
{
Q_D(QQmlBind);
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
if (!p.isEmpty())
warnIgnoredProperties(this);
return;
case QQmlBindPrivate::ObjectPropertyValue:
if (d->objectPropertyValueData.propName == p)
return;
break;
case QQmlBindPrivate::Unknown:
if (p.isEmpty())
return;
new (&d->objectPropertyValueData) ObjectPropertyValueData;
d->mode = QQmlBindPrivate::ObjectPropertyValue;
break;
}
if (!d->objectPropertyValueData.propName.isEmpty() && d->when) {
/* if we switch the property name at runtime, we need to restore the
previous binding on the old object before continuing */
d->when = false;
eval();
d->when = true;
}
d->objectPropertyValueData.propName = p;
if (d->componentComplete) {
setTarget(QQmlProperty(
d->objectPropertyValueData.obj, d->objectPropertyValueData.propName,
qmlContext(this)));
if (d->when)
d->validate(this);
}
eval();
emit propertyChanged();
}
/*!
\qmlproperty var QtQml::Binding::value
The value to be set on the target object and property. This can be a
constant (which isn't very useful), or a bound expression.
You only need to use this property if you can't supply the binding target
declaratively. Otherwise you can directly bind to the target.
*/
QVariant QQmlBind::value() const
{
Q_D(const QQmlBind);
if (d->mode == QQmlBindPrivate::ObjectPropertyValue) {
Q_ASSERT(d->objectPropertyValueData.entry.currentKind == QQmlBindEntryKind::Variant);
QV4::ExecutionEngine *engine = d->objectPropertyValueData.entry.current.v4Value.engine();
return engine->toVariant(
*d->objectPropertyValueData.entry.current.v4Value.valueRef(), QMetaType());
}
return QVariant();
}
void QQmlBind::setValue(const QVariant &v)
{
Q_D(QQmlBind);
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
if (v.isValid())
warnIgnoredProperties(this);
return;
case QQmlBindPrivate::Unknown:
if (!v.isValid())
return;
new (&d->objectPropertyValueData) ObjectPropertyValueData;
d->mode = QQmlBindPrivate::ObjectPropertyValue;
Q_FALLTHROUGH();
case QQmlBindPrivate::ObjectPropertyValue: {
QQmlBindEntry *targetEntry = &d->objectPropertyValueData.entry;
QQmlEngine *engine = qmlEngine(this);
if (!engine) {
qWarning() << "QQmlBind must be created in a QML context";
return;
}
targetEntry->currentKind
= targetEntry->current.set(engine->handle(), v, targetEntry->currentKind);
prepareEval();
break;
}
}
emit valueChanged();
}
/*!
\qmlproperty bool QtQml::Binding::delayed
\since 5.8
This property holds whether the binding should be delayed.
A delayed binding will not immediately update the target, but rather wait
until the event queue has been cleared. This can be used as an optimization,
or to prevent intermediary values from being assigned.
\code
Binding {
contactName.text.value: givenName + " " + familyName
when: list.ListView.isCurrentItem
delayed: true
}
\endcode
\note Using the \l delayed property incurs a run time cost as the Binding
element has to create a proxy for the value, so that it can delay its
application to the actual target. When using the \l target and
\l property properties, this cost is lower because the \l value
property can be re-used as proxy. When using the form shown above,
Binding will allocate a separate object with a dynamic meta-object to
hold the proxy values.
*/
bool QQmlBind::delayed() const
{
Q_D(const QQmlBind);
return d->delayed;
}
void QQmlBind::setDelayed(bool delayed)
{
Q_D(QQmlBind);
if (d->delayed == delayed)
return;
d->delayed = delayed;
if (!d->componentComplete)
return;
if (d->mode == QQmlBindPrivate::GeneralizedGroup) {
d->generalizedGroupData.delayedValues.reset();
QVarLengthArray<QQmlBindEntry, 1> oldEntries = std::move(d->generalizedGroupData.entries);
d->generalizedGroupData.entries.clear();
d->buildBindEntries(this, nullptr);
for (qsizetype i = 0, end = oldEntries.size(); i < end; ++i) {
QQmlBindEntry &newEntry = d->generalizedGroupData.entries[i];
QQmlBindEntry &oldEntry = oldEntries[i];
newEntry.previousKind = newEntry.previous.set(
std::move(oldEntry.previous), oldEntry.previousKind, newEntry.previousKind);
if (d->delayed && oldEntry.currentKind == QQmlBindEntryKind::Binding)
QQmlAnyBinding::removeBindingFrom(oldEntry.prop);
}
}
if (!d->delayed)
eval();
emit delayedChanged();
}
/*!
\qmlproperty enumeration QtQml::Binding::restoreMode
\since 5.14
This property can be used to describe if and how the original value should
be restored when the binding is disabled.
The possible values are:
\value Binding.RestoreNone The original value is not restored at all
\value Binding.RestoreBinding The original value is restored if it was another binding.
In that case the old binding is in effect again.
\value Binding.RestoreValue The original value is restored if it was a plain
value rather than a binding.
\value Binding.RestoreBindingOrValue The original value is always restored.
The default value is \c Binding.RestoreBindingOrValue.
\note This property exists for backwards compatibility with earlier versions
of Qt. Don't use it in new code.
*/
QQmlBind::RestorationMode QQmlBind::restoreMode() const
{
Q_D(const QQmlBind);
unsigned result = RestoreNone;
if (d->restoreValue)
result |= RestoreValue;
if (d->restoreBinding)
result |= RestoreBinding;
return RestorationMode(result);
}
void QQmlBind::setRestoreMode(RestorationMode newMode)
{
Q_D(QQmlBind);
if (newMode != restoreMode()) {
d->restoreValue = (newMode & RestoreValue);
d->restoreBinding = (newMode & RestoreBinding);
emit restoreModeChanged();
}
}
void QQmlBind::setTarget(const QQmlProperty &p)
{
Q_D(QQmlBind);
if (QQmlBindEntry *target = d->targetEntry()) {
target->setTarget(this, p);
return;
}
qmlWarning(this).nospace()
<< "You should not use the 'on' syntax for Binding elements with generalized group "
"properties. It is ignored.";
}
void QQmlBindEntry::setTarget(QQmlBind *q, const QQmlProperty &p)
{
if (Q_UNLIKELY(lcQtQmlBindingRemoval().isInfoEnabled())) {
if (QObject *oldObject = prop.object()) {
QMetaProperty metaProp = oldObject->metaObject()->property(prop.index());
if (metaProp.hasNotifySignal()) {
QByteArray signal('2' + metaProp.notifySignal().methodSignature());
QObject::disconnect(oldObject, signal.constData(),
q, SLOT(targetValueChanged()));
}
}
p.connectNotifySignal(q, SLOT(targetValueChanged()));
}
prop = p;
}
void QQmlBind::classBegin()
{
Q_D(QQmlBind);
d->componentComplete = false;
}
static QQmlAnyBinding createBinding(
const QQmlProperty &prop, const QV4::CompiledData::Binding *binding,
const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
const QQmlRefPointer<QQmlContextData> &contextData,
QObject *scopeObject)
{
switch (binding->type()) {
case QV4::CompiledData::Binding::Type_Translation:
case QV4::CompiledData::Binding::Type_TranslationById:
return QQmlAnyBinding::createTranslationBinding(prop, compilationUnit, binding, scopeObject);
case QV4::CompiledData::Binding::Type_Script: {
const QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
if (id == QQmlBinding::Invalid) {
return QQmlAnyBinding::createFromCodeString(
prop, compilationUnit->bindingValueAsString(binding), scopeObject,
contextData, compilationUnit->finalUrlString(), binding->location.line());
}
QV4::Scope scope(contextData->engine()->handle());
QV4::Scoped<QV4::QmlContext> qmlCtxt(
scope, QV4::QmlContext::create(
scope.engine->rootContext(), contextData, scopeObject));
return QQmlAnyBinding::createFromFunction(
prop, compilationUnit->runtimeFunctions.at(id), scopeObject, contextData,
qmlCtxt);
}
default:
break;
}
return QQmlAnyBinding();
}
static void initCreator(
QQmlData::DeferredData *deferredData,
const QQmlRefPointer<QQmlContextData> &contextData,
QQmlComponentPrivate::ConstructionState *immediateState)
{
if (!immediateState->hasCreator()) {
immediateState->setCompletePending(true);
immediateState->initCreator(
deferredData->context->parent(), deferredData->compilationUnit,
contextData, deferredData->inlineComponentName);
immediateState->creator()->beginPopulateDeferred(deferredData->context);
}
}
void QQmlBindPrivate::decodeBinding(
QQmlBind *q, const QString &propertyPrefix,
QQmlData::DeferredData *deferredData, int objectIndex,
const QV4::CompiledData::Binding *binding,
QQmlComponentPrivate::ConstructionState *immediateState)
{
const QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit
= deferredData->compilationUnit;
QString propertySuffix = compilationUnit->stringAt(binding->propertyNameIndex);
const int bindingIndex = int(binding - compilationUnit->objectAt(objectIndex)->bindingTable());
if (compilationUnit->bindingIsSignalHandlerExpression(objectIndex, bindingIndex)
|| compilationUnit->bindingIsSignalHandlerObject(objectIndex, bindingIndex)) {
propertySuffix = *QQmlSignalNames::anyHandlerNameToSignalName(propertySuffix);
} else if (compilationUnit->bindingIsPropertyObserver(objectIndex, bindingIndex)) {
propertySuffix = *QQmlSignalNames::changedHandlerNameToPropertyName(propertySuffix);
}
const QString propertyName = propertyPrefix + propertySuffix;
switch (binding->type()) {
case QV4::CompiledData::Binding::Type_AttachedProperty:
if (propertyPrefix.isEmpty()) {
// Top-level attached properties cannot be generalized grouped properties.
// Treat them as regular properties.
// ... unless we're not supposed to handle regular properties. Then ignore them.
if (!immediateState)
return;
Q_ASSERT(compilationUnit->stringAt(compilationUnit->objectAt(binding->value.objectIndex)
->inheritedTypeNameIndex).isEmpty());
const QV4::ResolvedTypeReference *typeReference
= compilationUnit->resolvedType(binding->propertyNameIndex);
Q_ASSERT(typeReference);
QQmlType attachedType = typeReference->type();
if (!attachedType.isValid()) {
if (QQmlTypeLoader *typeLoader = compilationUnit->engine->typeLoader()) {
const QQmlTypeNameCache::Result result
= deferredData->context->imports()->query(propertySuffix, typeLoader);
if (!result.isValid()) {
qmlWarning(q).nospace()
<< "Unknown name " << propertySuffix << ". The binding is ignored.";
return;
}
attachedType = result.type;
}
}
QQmlContext *context = qmlContext(q);
QObject *attachedObject = qmlAttachedPropertiesObject(
q, attachedType.attachedPropertiesFunction(QQmlTypeLoader::get(context->engine())));
if (!attachedObject) {
qmlWarning(q).nospace() <<"Could not create attached properties object '"
<< attachedType.typeName() << "'";
return;
}
initCreator(deferredData, QQmlContextData::get(context), immediateState);
immediateState->creator()->populateDeferredInstance(
q, deferredData->deferredIdx, binding->value.objectIndex, attachedObject,
attachedObject, /*value type property*/ nullptr, binding);
return;
}
Q_FALLTHROUGH();
case QV4::CompiledData::Binding::Type_GroupedProperty: {
const QString pre = propertyName + u'.';
const QV4::CompiledData::Object *subObj
= compilationUnit->objectAt(binding->value.objectIndex);
const QV4::CompiledData::Binding *subBinding = subObj->bindingTable();
for (quint32 i = 0; i < subObj->nBindings; ++i, ++subBinding)
decodeBinding(q, pre, deferredData, binding->value.objectIndex, subBinding,
immediateState);
return;
}
default:
break;
}
QQmlBindEntry entry;
QQmlContext *context = qmlContext(q);
const QQmlRefPointer<QQmlContextData> contextData = QQmlContextData::get(context);
entry.prop = QQmlPropertyPrivate::create(nullptr, propertyName, contextData,
QQmlPropertyPrivate::InitFlag::AllowId);
if (!entry.prop.isValid()) {
// Try again in the context of this object. If that works, it's a regular property.
// ... unless we're not supposed to handle regular properties. Then ignore it.
if (!immediateState)
return;
QQmlProperty property = QQmlPropertyPrivate::create(
q, propertyName, contextData, QQmlPropertyPrivate::InitFlag::AllowSignal);
if (property.isValid()) {
initCreator(deferredData, contextData, immediateState);
immediateState->creator()->populateDeferredBinding(
property, deferredData->deferredIdx, binding);
} else {
qmlWarning(q).nospace() << "Unknown name " << propertyName
<< ". The binding is ignored.";
}
return;
}
switch (mode) {
case GeneralizedGroup:
break;
case ObjectPropertyValue:
warnIgnoredProperties(q);
objectPropertyValueData.~ObjectPropertyValueData();
Q_FALLTHROUGH();
case Unknown:
new (&generalizedGroupData) GeneralizedGroupData;
mode = GeneralizedGroup;
break;
}
const auto setVariant = [&entry](QV4::PersistentValue value) {
entry.currentKind = entry.current.setVariant(value, entry.currentKind);
};
const auto setBinding = [&entry](QQmlAnyBinding binding) {
entry.currentKind = entry.current.set(binding, entry.currentKind);
};
switch (binding->type()) {
case QV4::CompiledData::Binding::Type_AttachedProperty:
case QV4::CompiledData::Binding::Type_GroupedProperty:
Q_UNREACHABLE(); // Handled above
break;
case QV4::CompiledData::Binding::Type_Translation:
case QV4::CompiledData::Binding::Type_TranslationById:
case QV4::CompiledData::Binding::Type_Script:
if (delayed) {
if (!generalizedGroupData.delayedValues)
createDelayedValues();
const QString delayedName = QString::number(generalizedGroupData.entries.size());
generalizedGroupData.delayedValues->insert(delayedName, QVariant());
QQmlProperty bindingTarget
= QQmlProperty(generalizedGroupData.delayedValues.get(), delayedName);
Q_ASSERT(bindingTarget.isValid());
QQmlAnyBinding anyBinding = createBinding(
bindingTarget, binding, compilationUnit, contextData, q);
anyBinding.installOn(bindingTarget);
} else {
setBinding(createBinding(entry.prop, binding, compilationUnit, contextData, q));
}
break;
case QV4::CompiledData::Binding::Type_String:
setVariant(QV4::PersistentValue(
compilationUnit->engine,
compilationUnit->runtimeStrings[binding->stringIndex]->asReturnedValue()));
break;
case QV4::CompiledData::Binding::Type_Number:
setVariant(QV4::PersistentValue(
compilationUnit->engine,
QV4::Encode(compilationUnit->bindingValueAsNumber(binding))));
break;
case QV4::CompiledData::Binding::Type_Boolean:
setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode(binding->value.b)));
break;
case QV4::CompiledData::Binding::Type_Null:
setVariant(QV4::PersistentValue(compilationUnit->engine, QV4::Encode::null()));
break;
case QV4::CompiledData::Binding::Type_Object:
{
// skip creation/binding logic if we are not actually dealing with a deferred property
if (!immediateState)
return;
auto v4 = compilationUnit->engine;
// we are handling someId.someProp: SomeObject {}
// Via entry.prop, we get the QQmlProperty for someProp, notably including its object (not Binding)
const QQmlProperty &property = entry.prop;
QObject *bindingTargetObject = property.object();
// via bindign.value.objectIndex, we get the object id of SomeObject
int objectToBeCreated = binding->value.objectIndex;
// we want to create that object in the context of the bindingTargetObject
auto context = qmlContext(bindingTargetObject);
auto contextData = QQmlContextData::get(context);
initCreator(deferredData, contextData, immediateState);
QObject *newObject = immediateState->creator()->createObjectInContext(objectToBeCreated, bindingTargetObject, contextData);
setVariant(QV4::PersistentValue(v4, QV4::QObjectWrapper::wrap(v4, newObject)));
break;
}
case QV4::CompiledData::Binding::Type_Invalid:
break;
}
generalizedGroupData.entries.append(std::move(entry));
}
void QQmlBindPrivate::createDelayedValues()
{
generalizedGroupData.delayedValues.reset(QQmlPropertyMap::create());
QQmlPropertyMap *delayedValues = generalizedGroupData.delayedValues.get();
QObject::connect(
delayedValues, &QQmlPropertyMap::valueChanged,
delayedValues, [this](QString delayedName, const QVariant &value) {
Q_UNUSED(value);
onDelayedValueChanged(std::move(delayedName));
}
);
}
void QQmlBindPrivate::onDelayedValueChanged(QString delayedName)
{
Q_ASSERT(delayed);
Q_ASSERT(mode == GeneralizedGroup);
QQmlPropertyMap *delayedValues = generalizedGroupData.delayedValues.get();
Q_ASSERT(delayedValues);
const QString pendingName = QStringLiteral("pending");
QStringList pending = qvariant_cast<QStringList>((*delayedValues)[pendingName]);
if (componentComplete && pending.size() == 0)
QTimer::singleShot(0, delayedValues, [this]() { evalDelayed(); });
else if (pending.contains(delayedName))
return;
pending.append(std::move(delayedName));
(*delayedValues)[pendingName].setValue(std::move(pending));
}
void QQmlBindPrivate::evalDelayed()
{
Q_ASSERT(mode == GeneralizedGroup);
QQmlPropertyMap *delayedValues = generalizedGroupData.delayedValues.get();
if (!when || !delayedValues)
return;
const QString pendingName = QStringLiteral("pending");
const QStringList pending = qvariant_cast<QStringList>((*delayedValues)[pendingName]);
for (const QString &delayedName : pending) {
bool ok;
const int delayedIndex = delayedName.toInt(&ok);
Q_ASSERT(ok);
Q_ASSERT(delayedIndex >= 0 && delayedIndex < generalizedGroupData.entries.size());
generalizedGroupData.entries[delayedIndex].prop.write((*delayedValues)[delayedName]);
}
(*delayedValues)[pendingName].setValue(QStringList());
}
void QQmlBindPrivate::buildBindEntries(QQmlBind *q, QQmlComponentPrivate::DeferredState *deferredState)
{
QQmlData *data = QQmlData::get(q);
if (data && !data->deferredData.isEmpty()) {
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine());
for (QQmlData::DeferredData *deferredData : data->deferredData) {
QMultiHash<int, const QV4::CompiledData::Binding *> *bindings = &deferredData->bindings;
if (deferredState) {
QQmlComponentPrivate::ConstructionState constructionState;
for (auto it = bindings->cbegin(); it != bindings->cend(); ++it)
decodeBinding(q, QString(), deferredData, deferredData->deferredIdx, *it,
&constructionState);
if (constructionState.hasCreator()) {
++ep->inProgressCreations;
constructionState.creator()->finalizePopulateDeferred();
constructionState.appendCreatorErrors();
deferredState->push_back(std::move(constructionState));
}
} else {
for (auto it = bindings->cbegin(); it != bindings->cend(); ++it)
decodeBinding(q, QString(), deferredData, deferredData->deferredIdx, *it,
nullptr);
}
}
if (deferredState) {
data->releaseDeferredData();
if (!deferredState->empty())
QQmlComponentPrivate::completeDeferred(ep, deferredState);
}
}
}
void QQmlBind::componentComplete()
{
Q_D(QQmlBind);
QQmlComponentPrivate::DeferredState deferredState;
d->buildBindEntries(this, &deferredState);
d->componentComplete = true;
if (d->mode == QQmlBindPrivate::ObjectPropertyValue) {
QQmlBindEntry *target = d->targetEntry();
if (!target->prop.isValid()) {
target->setTarget(this, QQmlProperty(
d->objectPropertyValueData.obj,
d->objectPropertyValueData.propName, qmlContext(this)));
}
}
d->validate(this);
if (d->mode == QQmlBindPrivate::GeneralizedGroup)
d->evalDelayed();
eval();
}
void QQmlBind::prepareEval()
{
Q_D(QQmlBind);
if (d->delayed) {
if (!d->pendingEval)
QTimer::singleShot(0, this, &QQmlBind::eval);
d->pendingEval = true;
} else {
eval();
}
}
void QQmlBindEntry::clearPrev()
{
previousKind = previous.destroy(previousKind);
}
bool QQmlBindPrivate::isCurrent(QQmlBindEntry *entry) const
{
switch (entry->currentKind) {
case QQmlBindEntryKind::V4Value: {
auto propPriv = QQmlPropertyPrivate::get(entry->prop);
QQmlVMEMetaObject *vmemo =
QQmlVMEMetaObject::get(propPriv->object, propPriv->engine->handle());
Q_ASSERT(vmemo);
return QV4::RuntimeHelpers::strictEqual(
// fromReturnedValue is OK here because strictEqual will not allocate
QV4::Value::fromReturnedValue(vmemo->vmeProperty(propPriv->core.coreIndex())),
*entry->current.v4Value.valueRef());
}
case QQmlBindEntryKind::Variant: {
const QV4::PersistentValue &v4Value = entry->current.v4Value;
QMetaType propMetaType = entry->prop.propertyMetaType();
/*
* We currently really want to use toVariant here, and not metaTypeFromJS.
* metaTypeFromJS can't do all conversions, and would leave us with an
* empty QVariant if it fails; whereas toVariant tries very hard to give
* us a QVariant representation, even though its type might not match the
* property's meta-type. In case of an actual mismatch, we use
* convertToWriteTargetType to mirror the conversions that writing to a
* property would have done; with the exception of list properties, where
* we directly go through a specialized code path using
* QQmlPropertyPrivate::convertToQQmlListProperty
*/
QVariant valueAsVariant = v4Value.engine()->toVariant(*v4Value.valueRef(),
propMetaType);
if (propMetaType.flags() & QMetaType::IsQmlList) {
// our expected value is not necessarily of the correct type, so we copy the steps done by
// QQmlPropertyPrivate::write to bring them into the correct format
QList<QObject *> expectedObjectList;
QQmlListProperty<QObject> expectedAsListProp(nullptr, &expectedObjectList);
QQmlPropertyPrivate::convertToQQmlListProperty(&expectedAsListProp, propMetaType, valueAsVariant);
QVariant actualValue = entry->prop.read();
// reading a QQmlListProperty property yields a QQmlListReference
QQmlListReference* listReference = get_if<QQmlListReference>(&actualValue);
Q_ASSERT(listReference);
auto actualObjectList = QQmlListReferencePrivate::get(listReference)->property.toList<QList<QObject *>>();
return std::equal(expectedObjectList.constBegin(), expectedObjectList.constEnd(),
actualObjectList.constBegin(), actualObjectList.constEnd());
} else if (QMetaType varMetaType = valueAsVariant.metaType(); varMetaType != propMetaType) {
QVariant converted = QQmlPropertyPrivate::convertToWriteTargetType(
valueAsVariant, propMetaType);
if (converted.isValid())
valueAsVariant = std::move(converted);
}
return valueAsVariant
== entry->prop.read();
}
case QQmlBindEntryKind::Binding:
return entry->current.binding == QQmlAnyBinding::ofProperty(entry->prop);
case QQmlBindEntryKind::None:
break;
}
return false;
}
void QQmlBindPrivate::preEvalEntry(QQmlBindEntry *entry)
{
if (!entry->prop.isValid() || (entry->currentKind == QQmlBindEntryKind::None))
return;
if (!entry->prop.object())
return; // if the target is already gone, we can't do anything
if (!when) {
if (!isCurrent(entry)) {
entry->clearPrev();
return;
}
//restore any previous binding
switch (entry->previousKind) {
case QQmlBindEntryKind::Binding:
if (restoreBinding) {
QQmlAnyBinding p = std::move(entry->previous.binding);
entry->clearPrev(); // Do that before setBinding(), as setBinding() may recurse.
p.installOn(entry->prop);
}
break;
case QQmlBindEntryKind::V4Value:
if (restoreValue) {
QQmlAnyBinding::takeFrom(entry->prop); // we don't want to have a binding active
auto propPriv = QQmlPropertyPrivate::get(entry->prop);
QQmlVMEMetaObject *vmemo =
QQmlVMEMetaObject::get(propPriv->object, propPriv->engine->handle());
Q_ASSERT(vmemo);
vmemo->setVMEProperty(propPriv->core.coreIndex(),
*entry->previous.v4Value.valueRef());
entry->clearPrev();
}
break;
case QQmlBindEntryKind::Variant:
if (restoreValue) {
QQmlAnyBinding::takeFrom(entry->prop); // we don't want to have a binding active
const QV4::PersistentValue &v4Value = entry->previous.v4Value;
entry->prop.write(v4Value.engine()->toVariant(
*v4Value.valueRef(), entry->prop.propertyMetaType()));
entry->clearPrev();
}
break;
case QQmlBindEntryKind::None:
break;
}
return;
}
//save any set binding for restoration
if (entry->previousKind == QQmlBindEntryKind::None) {
// try binding first; we need to use takeFrom to properly unlink the binding
QQmlAnyBinding prevBind = QQmlAnyBinding::takeFrom(entry->prop);
if (prevBind) {
entry->previousKind = entry->previous.set(std::move(prevBind), entry->previousKind);
} else {
// nope, try a V4 value next
auto propPriv = QQmlPropertyPrivate::get(entry->prop);
auto propData = propPriv->core;
if (!propPriv->valueTypeData.isValid() && propData.isVarProperty()) {
QQmlVMEMetaObject *vmemo =
QQmlVMEMetaObject::get(propPriv->object, propPriv->engine->handle());
Q_ASSERT(vmemo);
auto retVal = vmemo->vmeProperty(propData.coreIndex());
entry->previousKind = entry->previous.set(
QV4::PersistentValue(vmemo->engine(), retVal), entry->previousKind);
} else if (entry->prop.propertyMetaType().flags() & QMetaType::IsQmlList) {
using namespace QV4;
ExecutionEngine *v4 = qmlEngine(q_func())->handle();
entry->previousKind = entry->previous.setVariant(
QV4::PersistentValue(v4, QV4::QmlListWrapper::createOwned(v4, entry->prop)),
entry->previousKind);
} else {
// nope, use the meta object to get a QVariant
entry->previousKind = entry->previous.set(
propPriv->engine->handle(), entry->prop.read(), entry->previousKind);
}
}
}
// NOTE: removeBinding has no effect on QProperty classes, but
// we already used takeBinding to remove it
QQmlPropertyPrivate::removeBinding(entry->prop, QQmlPropertyPrivate::OverrideSticky);
}
void QQmlBindPrivate::postEvalEntry(QQmlBindEntry *entry)
{
if (!entry->prop.isValid())
return;
switch (entry->currentKind) {
case QQmlBindEntryKind::Variant: {
const QV4::PersistentValue &v4Value = entry->current.v4Value;
entry->prop.write(
v4Value.engine()->toVariant(*v4Value.valueRef(), entry->prop.propertyMetaType()));
break;
}
case QQmlBindEntryKind::Binding:
Q_ASSERT(!delayed);
entry->current.binding.installOn(entry->prop);
break;
case QQmlBindEntryKind::V4Value: {
auto propPriv = QQmlPropertyPrivate::get(entry->prop);
QQmlVMEMetaObject::get(propPriv->object, propPriv->engine->handle())->setVMEProperty(
propPriv->core.coreIndex(), *entry->current.v4Value.valueRef());
break;
}
case QQmlBindEntryKind::None:
break;
}
}
void QQmlBind::eval()
{
Q_D(QQmlBind);
d->pendingEval = false;
if (!d->componentComplete)
return;
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
for (QQmlBindEntry &entry : d->generalizedGroupData.entries)
d->preEvalEntry(&entry);
break;
case QQmlBindPrivate::ObjectPropertyValue:
d->preEvalEntry(&d->objectPropertyValueData.entry);
break;
case QQmlBindPrivate::Unknown:
break;
}
if (!d->when)
return;
d->writingProperty = true;
switch (d->mode) {
case QQmlBindPrivate::GeneralizedGroup:
for (QQmlBindEntry &entry : d->generalizedGroupData.entries)
d->postEvalEntry(&entry);
break;
case QQmlBindPrivate::ObjectPropertyValue:
d->postEvalEntry(&d->objectPropertyValueData.entry);
break;
case QQmlBindPrivate::Unknown:
break;
}
d->writingProperty = false;
}
void QQmlBind::targetValueChanged()
{
Q_D(QQmlBind);
if (d->writingProperty)
return;
if (!d->when)
return;
QUrl url;
quint16 line = 0;
const QQmlData *ddata = QQmlData::get(this, false);
if (ddata && ddata->outerContext) {
url = ddata->outerContext->url();
line = ddata->lineNumber;
}
qCInfo(lcQtQmlBindingRemoval,
"The target property of the Binding element created at %s:%d was changed from "
"elsewhere. This does not overwrite the binding. The target property will still be "
"updated when the value of the Binding element changes.",
qPrintable(url.toString()), line);
}
QT_END_NAMESPACE
#include "moc_qqmlbind_p.cpp"