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>
This commit is contained in:
Fabian Kosmale 2021-02-26 10:22:33 +01:00
parent 776d1f0bf0
commit 50e984c072
11 changed files with 698 additions and 1 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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[]

View File

@ -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);
};

View File

@ -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)

View File

@ -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\\\"
)

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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"

View File

@ -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