aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2021-02-26 10:22:33 +0100
committerFabian Kosmale <fabian.kosmale@qt.io>2021-03-04 12:53:46 +0100
commit50e984c07229b753d6558610e7fa7c7df733fe50 (patch)
tree3c802a88a36ea081988c454cebb59fa5648ac417
parent776d1f0bf02c5dc2a9dac4c5bd906f4421abf2f4 (diff)
Introduce QQmlAnyBinding as an abstraction over bindings
QQmlAnyBinding is meant to abstract over the differences between classical, QQmlAbstractBinding derived bindings and the newer QPropertyBindingPrivate based ones. It can be used to store, receive, remove, create and set bindings of both types. In addition, this patches adds the functionality to create a QQmlPropertyBinding from a code string, which was so far only possible with QQmlBinding; and adds a few methods to QBiPointer to ease the implementation of QQmlAnyBinding. Task-number: QTBUG-91000 Change-Id: I7076d6fb426f315f32c1b054c5c3ba56312bed29 Reviewed-by: Andrei Golubev <andrei.golubev@qt.io> Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/qml/CMakeLists.txt1
-rw-r--r--src/qml/qml/ftw/qflagpointer_p.h20
-rw-r--r--src/qml/qml/qqmlanybinding_p.h419
-rw-r--r--src/qml/qml/qqmlpropertybinding.cpp14
-rw-r--r--src/qml/qml/qqmlpropertybinding_p.h7
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qqmlanybinding/CMakeLists.txt45
-rw-r--r--tests/auto/qml/qqmlanybinding/data/newstylebinding.qml14
-rw-r--r--tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml13
-rw-r--r--tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp117
-rw-r--r--tests/auto/qml/qqmlanybinding/withbindable.h48
11 files changed, 698 insertions, 1 deletions
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt
index a96ddc2ab7..0d87869d00 100644
--- a/src/qml/CMakeLists.txt
+++ b/src/qml/CMakeLists.txt
@@ -269,6 +269,7 @@ qt_internal_add_module(Qml
qml/qqmlprivate.h
qml/qqmlproperty.cpp qml/qqmlproperty.h qml/qqmlproperty_p.h
qml/qqmlpropertybinding.cpp qml/qqmlpropertybinding_p.h
+ qml/qqmlanybinding_p.h
qml/qqmlpropertycache.cpp qml/qqmlpropertycache_p.h
qml/qqmlpropertycachecreator.cpp qml/qqmlpropertycachecreator_p.h
qml/qqmlpropertycachemethodarguments_p.h
diff --git a/src/qml/qml/ftw/qflagpointer_p.h b/src/qml/qml/ftw/qflagpointer_p.h
index 5cdf973352..c8824f3866 100644
--- a/src/qml/qml/ftw/qflagpointer_p.h
+++ b/src/qml/qml/ftw/qflagpointer_p.h
@@ -87,6 +87,26 @@ public:
inline QBiPointer<T, T2> &operator=(T *);
inline QBiPointer<T, T2> &operator=(T2 *);
+ friend inline bool operator==(QBiPointer<T, T2> ptr1, QBiPointer<T, T2> ptr2)
+ {
+ if (ptr1.isNull() && ptr2.isNull())
+ return true;
+ if (ptr1.isT1() && ptr2.isT1())
+ return ptr1.asT1() == ptr2.asT1();
+ if (ptr1.isT2() && ptr2.isT2())
+ return ptr1.asT2() == ptr2.asT2();
+ return false;
+ }
+ friend inline bool operator!=(QBiPointer<T, T2> ptr1, QBiPointer<T, T2> ptr2)
+ {
+ return !(ptr1 == ptr2);
+ }
+
+ friend inline void swap(QBiPointer<T, T2> ptr1, QBiPointer<T, T2> ptr2)
+ {
+ qSwap(ptr1.ptr_value, ptr2.ptr_value);
+ }
+
inline T *asT1() const;
inline T2 *asT2() const;
diff --git a/src/qml/qml/qqmlanybinding_p.h b/src/qml/qml/qqmlanybinding_p.h
new file mode 100644
index 0000000000..38c1023c79
--- /dev/null
+++ b/src/qml/qml/qqmlanybinding_p.h
@@ -0,0 +1,419 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLANYBINDINGPTR_P_H
+#define QQMLANYBINDINGPTR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qqmlproperty.h>
+#include <private/qqmlpropertybinding_p.h>
+#include <private/qqmlbinding_p.h>
+
+// Fully inline so that subsequent prop.isBindable check might get ellided.
+
+/*!
+ \internal
+ \brief QQmlAnyBinding is an abstraction over the various bindings in QML
+
+ QQmlAnyBinding can store both classical bindings (derived from QQmlAbstractBinding)
+ as well as new-style bindings (derived from QPropertyBindingPrivate). For both, it keeps
+ a strong reference to them, and knows how to delete them in case the reference count
+ becomes zero. In that sense it can be thought of as a union of QUntypedPropertyBinding
+ and QQmlAbstractBinding::Ptr.
+
+ It also offers methods to create bindings (from QV4::Function, from translation bindings
+ and from code strings). Moreover, it allows the retrieval, the removal and the
+ installation of bindings on a QQmlProperty.
+
+ Note that the class intentionally does not allow construction from QUntypedProperty and
+ QQmlAbstractBinding::Ptr. This is meant to catch code which doesn't handle bindable properties
+ yet when porting existing code.
+ */
+class QQmlAnyBinding {
+public:
+
+ QQmlAnyBinding() = default;
+ QQmlAnyBinding(std::nullptr_t) : d(static_cast<QQmlAbstractBinding *>(nullptr)) {}
+
+
+ /*!
+ \internal
+ Returns the binding of the property \a prop as a QQmlAnyBinding.
+ The binding continues to be active and set on the property.
+ If there was no binding set, the returned QQmlAnyBinding is null.
+ */
+ static QQmlAnyBinding ofProperty(const QQmlProperty &prop) {
+ QQmlAnyBinding binding;
+ if (prop.isBindable()) {
+ QUntypedBindable bindable = prop.property().bindable(prop.object());
+ binding = bindable.binding();
+ } else {
+ binding = QQmlPropertyPrivate::binding(prop);
+ }
+ return binding;
+ }
+
+ /*!
+ Removes the binding from the property \a prop, and returns it as a
+ QQmlAnyBinding if there was any. Otherwise returns a null
+ QQmlAnyBinding.
+ */
+ static QQmlAnyBinding takeFrom(const QQmlProperty &prop)
+ {
+ QQmlAnyBinding binding;
+ if (prop.isBindable()) {
+ QUntypedBindable bindable = prop.property().bindable(prop.object());
+ binding = bindable.takeBinding();
+ } else {
+ auto qmlBinding = QQmlPropertyPrivate::binding(prop);
+ if (qmlBinding) {
+ qmlBinding->setEnabled(false, QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor);
+ qmlBinding->removeFromObject();
+ binding = qmlBinding;
+ }
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Creates a binding for property \a prop from \a function.
+ \a obj is the scope object which shall be used for the function and \a scope its QML scope.
+ The binding is not installed on the property (but if a QQmlBinding is created, it has its
+ target set to \a prop).
+ */
+ static QQmlAnyBinding createFromFunction(const QQmlProperty &prop, QV4::Function *function,
+ QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
+ QV4::ExecutionContext *scope)
+ {
+ QQmlAnyBinding binding;
+ auto propPriv = QQmlPropertyPrivate::get(prop);
+ if (prop.isBindable()) {
+ auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1);
+ binding = QQmlPropertyBinding::create(&propPriv->core,
+ function, obj, ctxt,
+ scope, prop.object(), index);
+ } else {
+ auto qmlBinding = QQmlBinding::create(&propPriv->core, function, obj, ctxt, scope);
+ qmlBinding->setTarget(prop);
+ binding = qmlBinding;
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Removes the binding from \a prop if there is any.
+ */
+ static void removeBindingFrom(QQmlProperty &prop)
+ {
+ if (prop.isBindable())
+ prop.property().bindable(prop.object()).takeBinding();
+ else
+ QQmlPropertyPrivate::removeBinding(prop);
+ }
+
+ /*!
+ \internal
+ Creates a binding for property \a prop from \a function.
+ \a obj is the scope object which shall be used for the function and \a scope its QML scope.
+ The binding is not installed on the property (but if a QQmlBinding is created, it has its
+ target set to \a prop).
+ */
+ static QQmlAnyBinding createFromCodeString(const QQmlProperty &prop, const QString& code, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber) {
+ QQmlAnyBinding binding;
+ auto propPriv = QQmlPropertyPrivate::get(prop);
+ if (prop.isBindable()) {
+ auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1);
+ binding = QQmlPropertyBinding::createFromCodeString(&propPriv->core,
+ code, obj, ctxt,
+ url, lineNumber,
+ prop.object(), index);
+ } else {
+ auto qmlBinding = QQmlBinding::create(&propPriv->core, code, obj, ctxt, url, lineNumber);
+ qmlBinding->setTarget(prop);
+ binding = qmlBinding;
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Creates a translattion binding for \a prop from \a compilationUnit and \a transationBinding.
+ \a obj is the context object, \a context the qml context.
+ */
+ static QQmlAnyBinding createTranslationBinding(const QQmlProperty &prop, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *translationBinding, QObject *scopeObject=nullptr, QQmlRefPointer<QQmlContextData> context={})
+ {
+ QQmlAnyBinding binding;
+ auto propPriv = QQmlPropertyPrivate::get(prop);
+ if (prop.isBindable()) {
+ binding = QQmlTranslationPropertyBinding::create(&propPriv->core, compilationUnit, translationBinding);
+ } else {
+ auto qmlBinding = QQmlBinding::createTranslationBinding(compilationUnit, translationBinding, scopeObject, context);
+ binding = qmlBinding;
+ qmlBinding->setTarget(prop);
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Installs the binding referenced by this QQmlAnyBinding on the target.
+ If \a mode is set to RespectInterceptors, interceptors are honored, otherwise
+ writes and binding installation bypass them (the default).
+ Preconditions:
+ - The binding is non-null.
+ - If the binding is QQmlAbstractBinding derived, the target is non-bindable.
+ - If the binding is a QUntypedPropertyBinding, then the target is bindable.
+ */
+ enum InterceptorMode : bool {
+ IgnoreInterceptors,
+ RespectInterceptors
+ };
+
+ void installOn(const QQmlProperty &target, InterceptorMode mode = IgnoreInterceptors)
+ {
+ Q_ASSERT(!d.isNull());
+ if (isAbstractPropertyBinding()) {
+ auto abstractBinding = asAbstractBinding();
+ Q_ASSERT(abstractBinding->targetObject() == target.object() || QQmlPropertyPrivate::get(target)->core.isAlias());
+ Q_ASSERT(!target.isBindable());
+ if (mode == IgnoreInterceptors)
+ QQmlPropertyPrivate::setBinding(abstractBinding, QQmlPropertyPrivate::None, QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor);
+ else
+ QQmlPropertyPrivate::setBinding(abstractBinding);
+ } else {
+ Q_ASSERT(target.isBindable());
+ // TODO: QMetaProperty::bindable needs a mode to work on the dynamic metaobject
+ QUntypedBindable bindable = target.property().bindable(target.object());
+ bindable.setBinding(asUntypedPropertyBinding());
+ }
+ }
+
+ /*!
+ Stores a null binding. For purpose of classification, the null bindings is
+ treated as a QQmlAbstractPropertyBindings.
+ */
+ QQmlAnyBinding& operator=(std::nullptr_t )
+ {
+ clear();
+ return *this;
+ }
+
+ operator bool() const{
+ return !d.isNull();
+ }
+
+ /*!
+ \internal
+ Returns true if a binding derived from QQmlAbstractPropertyBinding is stored.
+ The binding migh still be null.
+ */
+ bool isAbstractPropertyBinding() const
+ { return d.isT1(); }
+
+ /*!
+ \internal
+ Returns true if a binding derived from QPropertyBindingPrivate is stored.
+ The binding might still be null.
+ */
+ bool isUntypedPropertyBinding() const
+ { return d.isT2(); }
+
+ /*!
+ \internal
+ Returns the stored QPropertyBindingPrivate as a QUntypedPropertyBinding.
+ If no such binding is currently stored, a null QUntypedPropertyBinding is returned.
+ */
+ QUntypedPropertyBinding asUntypedPropertyBinding() const
+ {
+ if (d.isT1() || d.isNull())
+ return {};
+ auto priv = d.asT2();
+ return QUntypedPropertyBinding {priv};
+ }
+
+ /*!
+ \internal
+ Returns the stored QQmlAbstractBinding.
+ If no such binding is currently stored, a null pointer is returned.
+ */
+ QQmlAbstractBinding *asAbstractBinding() const
+ {
+ if (d.isT2() || d.isNull())
+ return nullptr;
+ return d.asT1();
+ }
+
+ /*!
+ \internal
+ Stores \a binding and keeps a reference to it.
+ */
+ QQmlAnyBinding& operator=(QQmlAbstractBinding *binding)
+ {
+ clear();
+ if (binding) {
+ d = binding;
+ binding->ref.ref();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ Stores the binding stored in \a binding and keeps a reference to it.
+ */
+ QQmlAnyBinding& operator=(const QQmlAbstractBinding::Ptr &binding)
+ {
+ clear();
+ if (binding) {
+ d = binding.data();
+ binding->ref.ref();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ Stores \a binding's binding, taking ownership from \a binding.
+ */
+ QQmlAnyBinding& operator=(QQmlAbstractBinding::Ptr &&binding)
+ {
+ clear();
+ if (binding) {
+ d = binding.take();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ Stores the binding stored in \a untypedBinding and keeps a reference to it.
+ */
+ QQmlAnyBinding& operator=(const QUntypedPropertyBinding &untypedBinding)
+ {
+ clear();
+ auto binding = QPropertyBindingPrivate::get(untypedBinding);
+ if (binding) {
+ d = binding;
+ binding->addRef();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ \overload
+ Stores the binding stored in \a untypedBinding, taking ownership from it.
+ */
+ QQmlAnyBinding& operator=(const QUntypedPropertyBinding &&untypedBinding)
+ {
+ clear();
+ auto binding = QPropertyBindingPrivate::get(untypedBinding);
+ QPropertyBindingPrivatePtr ptr(binding);
+ if (binding) {
+ d = static_cast<QPropertyBindingPrivate *>(ptr.take());
+ }
+ return *this;
+ }
+
+ QQmlAnyBinding(const QQmlAnyBinding &other)
+ {
+ *this = other;
+ }
+
+ friend void swap(const QQmlAnyBinding &a, const QQmlAnyBinding &b)
+ {
+ qSwap(a.d, b.d);
+ }
+
+ QQmlAnyBinding& operator=(const QQmlAnyBinding &other)
+ {
+ clear();
+ if (auto abstractBinding = other.asAbstractBinding())
+ *this = abstractBinding;
+ else if (auto untypedBinding = other.asUntypedPropertyBinding(); !untypedBinding.isNull())
+ *this = untypedBinding;
+ return *this;
+ }
+
+ friend inline bool operator==(const QQmlAnyBinding &p1, const QQmlAnyBinding &p2)
+ {
+ return p1.d == p2.d;
+ }
+
+ friend inline bool operator!=(const QQmlAnyBinding &p1, const QQmlAnyBinding &p2)
+ {
+ return p1.d != p2.d;
+ }
+
+ ~QQmlAnyBinding() {
+ clear();
+ }
+private:
+ void clear() {
+ if (d.isNull())
+ return;
+ if (d.isT1()) {
+ QQmlAbstractBinding *qqmlptr = d.asT1();
+ if (!qqmlptr->ref.deref())
+ delete qqmlptr;
+ } else if (d.isT2()) {
+ QPropertyBindingPrivate *priv = d.asT2();
+ priv->ref--;
+ if (!priv->ref)
+ QPropertyBindingPrivate::destroyAndFreeMemory(priv);
+ }
+ d = static_cast<QQmlAbstractBinding *>(nullptr);
+ }
+ QBiPointer<QQmlAbstractBinding, QPropertyBindingPrivate> d = static_cast<QQmlAbstractBinding *>(nullptr);
+};
+
+
+#endif // QQMLANYBINDINGPTR_P_H
diff --git a/src/qml/qml/qqmlpropertybinding.cpp b/src/qml/qml/qqmlpropertybinding.cpp
index 6757618b86..e600748be7 100644
--- a/src/qml/qml/qqmlpropertybinding.cpp
+++ b/src/qml/qml/qqmlpropertybinding.cpp
@@ -97,6 +97,20 @@ QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd,
return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
}
+QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex)
+{
+ auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
+ auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, true);
+ auto js = new(buffer + sizeof(QQmlPropertyBinding) + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
+ Q_ASSERT(binding->jsExpression() == js);
+ Q_ASSERT(js->asBinding() == binding);
+ Q_UNUSED(js);
+ binding->jsExpression()->setNotifyOnValueChanged(true);
+ binding->jsExpression()->setContext(ctxt);
+ binding->jsExpression()->createQmlBinding(ctxt, obj, str, url, lineNumber);
+ return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
+}
+
QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
{
auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
diff --git a/src/qml/qml/qqmlpropertybinding_p.h b/src/qml/qml/qqmlpropertybinding_p.h
index 4dcd5c1879..7c7fac866a 100644
--- a/src/qml/qml/qqmlpropertybinding_p.h
+++ b/src/qml/qml/qqmlpropertybinding_p.h
@@ -101,6 +101,11 @@ public:
QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
QV4::ExecutionContext *scope, QObject *target,
QQmlPropertyIndex targetIndex);
+ static QUntypedPropertyBinding createFromCodeString(const QQmlPropertyData *property,
+ const QString &str, QObject *obj,
+ const QQmlRefPointer<QQmlContextData> &ctxt,
+ const QString &url, quint16 lineNumber,
+ QObject *target, QQmlPropertyIndex targetIndex);
static QUntypedPropertyBinding createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function,
QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
@@ -157,7 +162,7 @@ inline constexpr BindingFunctionVTable bindingFunctionVTable<QQmlPropertyBinding
class QQmlTranslationPropertyBinding
{
public:
- static QUntypedPropertyBinding create(const QQmlPropertyData *pd,
+ static QUntypedPropertyBinding Q_QML_PRIVATE_EXPORT create(const QQmlPropertyData *pd,
const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
const QV4::CompiledData::Binding *binding);
};
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt
index ae57b63a1d..58520d92a0 100644
--- a/tests/auto/qml/CMakeLists.txt
+++ b/tests/auto/qml/CMakeLists.txt
@@ -55,6 +55,7 @@ if(QT_FEATURE_private_tests)
add_subdirectory(qmlcachegen)
add_subdirectory(animation)
add_subdirectory(qqmlecmascript)
+ add_subdirectory(qqmlanybinding)
add_subdirectory(qqmlcontext)
add_subdirectory(qqmlexpression)
add_subdirectory(qqmlglobal)
diff --git a/tests/auto/qml/qqmlanybinding/CMakeLists.txt b/tests/auto/qml/qqmlanybinding/CMakeLists.txt
new file mode 100644
index 0000000000..83c9bd7994
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/CMakeLists.txt
@@ -0,0 +1,45 @@
+#####################################################################
+## tst_qqmlanybinding Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qqmlanybinding
+ SOURCES
+ ../../shared/util.cpp ../../shared/util.h
+ withbindable.h
+ tst_qqmlanybinding.cpp
+ INCLUDE_DIRECTORIES
+ ../../shared
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Network
+ Qt::QmlPrivate
+ TESTDATA ${test_data}
+)
+
+set_target_properties(tst_qqmlanybinding PROPERTIES
+ QT_QML_MODULE_VERSION 1.0
+ QT_QML_MODULE_URI bindable
+)
+
+qt6_qml_type_registration(tst_qqmlanybinding)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qqmlanybinding CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\":/data\\\"
+)
+
+qt_internal_extend_target(tst_qqmlanybinding CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+)
diff --git a/tests/auto/qml/qqmlanybinding/data/newstylebinding.qml b/tests/auto/qml/qqmlanybinding/data/newstylebinding.qml
new file mode 100644
index 0000000000..0db793952e
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/newstylebinding.qml
@@ -0,0 +1,14 @@
+import QtQml
+import bindable 1.0
+
+WithBinding {
+ id: root
+ property int trigger: 1
+ prop: trigger
+ property alias a2: obj1.a1
+
+ property QtObject obj: QtObject {
+ id: obj1
+ property alias a1: root.prop
+ }
+}
diff --git a/tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml b/tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml
new file mode 100644
index 0000000000..bcbadbc919
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml
@@ -0,0 +1,13 @@
+import QtQml
+
+QtObject {
+ id: root
+ property int trigger: 1
+ property int prop: trigger
+ property alias a2: obj1.a1
+
+ property QtObject obj: QtObject {
+ id: obj1
+ property alias a1: root.prop
+ }
+}
diff --git a/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
new file mode 100644
index 0000000000..d99b988c52
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "../../shared/util.h"
+#include <QtQml/QQmlEngine>
+#include <QtQml/QQmlComponent>
+#include <QtCore/QScopedPointer>
+#include <QtQml/private/qqmlanybinding_p.h>
+#include "withbindable.h"
+
+class tst_qqmlanybinding : public QQmlDataTest
+{
+ Q_OBJECT
+
+private slots:
+ void basicActions_data();
+ void basicActions();
+};
+
+void tst_qqmlanybinding::basicActions_data()
+{
+ QTest::addColumn<QString>("fileName");
+ QTest::addRow("old style") << QStringLiteral("oldstylebinding.qml");
+ QTest::addRow("new style") << QStringLiteral("newstylebinding.qml");
+}
+
+static int getRefCount(const QQmlAnyBinding &binding)
+{
+ if (binding.isAbstractPropertyBinding()) {
+ return binding.asAbstractBinding()->ref;
+ } else {
+ // this temporarily adds a refcount because we construc a new untypedpropertybinding
+ // thus -1
+ return QPropertyBindingPrivate::get(binding.asUntypedPropertyBinding())->ref - 1;
+ }
+}
+
+void tst_qqmlanybinding::basicActions()
+{
+ QQmlEngine engine;
+ QFETCH(QString, fileName);
+ QQmlComponent comp(&engine, testFileUrl(fileName));
+ QScopedPointer<QObject> root(comp.create());
+ QVERIFY2(root, qPrintable(comp.errorString()));
+
+ QQmlProperty prop(root.get(), "prop");
+ QQmlProperty trigger(root.get(), "trigger");
+
+ // sanity check
+ QCOMPARE(prop.read().toInt(), 1);
+
+ // We can take the binding from a property
+ auto binding = QQmlAnyBinding::ofProperty(prop);
+ if (fileName == u"oldstylebinding.qml") {
+ // and it is a QQmlAbstractBinding,
+ QVERIFY(binding.isAbstractPropertyBinding());
+ } else {
+ // and it is a new style binding,
+ QVERIFY(binding.isUntypedPropertyBinding());
+ }
+ // which is not null.
+ QVERIFY(binding);
+ // Getting the binding did not remove it.
+ trigger.write(42);
+ QCOMPARE(prop.read().toInt(), 42);
+ // remove does that,
+ QQmlAnyBinding::removeBindingFrom(prop);
+ QVERIFY(!QQmlAnyBinding::ofProperty(prop));
+ // but does not change the value.
+ QCOMPARE(prop.read().toInt(), 42);
+ // As there is no binding anymore, there won't be any change if trigger changes.
+ trigger.write(2);
+ QCOMPARE(prop.read().toInt(), 42);
+ // The binding we took is still valid (and we are currently the sole owner).
+ QVERIFY(getRefCount(binding) == 1);
+ // We can reinstall the binding
+ binding.installOn(prop);
+ // This changes the value to reflect that trigger has changed
+ QCOMPARE(prop.read().toInt(), 2);
+ // The binding behaves normally.
+ trigger.write(3);
+ QCOMPARE(prop.read().toInt(), 3);
+ // "binding" still has a reference to the binding
+ QVERIFY(getRefCount(binding) == 2);
+
+ // aliases are resolved correctly
+ QQmlProperty a2(root.get(), "a2");
+ QCOMPARE(QQmlAnyBinding::ofProperty(a2), binding);
+}
+
+QTEST_MAIN(tst_qqmlanybinding)
+
+#include "tst_qqmlanybinding.moc"
diff --git a/tests/auto/qml/qqmlanybinding/withbindable.h b/tests/auto/qml/qqmlanybinding/withbindable.h
new file mode 100644
index 0000000000..4488c0c350
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/withbindable.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef WITH_BINDABLE_H
+#define WITH_BINDABLE_H
+
+#include <QObject>
+#include <qqml.h>
+
+class WithBinding : public QObject {
+ Q_OBJECT
+ QML_ELEMENT
+
+ Q_PROPERTY(int prop READ prop WRITE setProp BINDABLE bindableProp)
+public:
+ int prop() {return m_prop;}
+ void setProp(int i) {m_prop = i;}
+ QBindable<int> bindableProp() {return &m_prop;}
+private:
+ QProperty<int> m_prop;
+};
+
+#endif
+