diff options
-rw-r--r-- | src/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qml/qml/ftw/qflagpointer_p.h | 20 | ||||
-rw-r--r-- | src/qml/qml/qqmlanybinding_p.h | 419 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertybinding.cpp | 14 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertybinding_p.h | 7 | ||||
-rw-r--r-- | tests/auto/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmlanybinding/CMakeLists.txt | 45 | ||||
-rw-r--r-- | tests/auto/qml/qqmlanybinding/data/newstylebinding.qml | 14 | ||||
-rw-r--r-- | tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml | 13 | ||||
-rw-r--r-- | tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp | 117 | ||||
-rw-r--r-- | tests/auto/qml/qqmlanybinding/withbindable.h | 48 |
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 + |