QmlBind: support bindable properties

This patch ensures that the QML Binding element can also save and restore
C++ bindings. Should QQuickItem's x and y property be ported to the new
property system, we'd need new test cases to verify that "old-style"
bindings are still handled correctly. This task is however left for the
change porting the properties.

Task-number: QTBUG-90493
Change-Id: I506ffa1060ff32a7d722214e5ccd469bdaa61ff8
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Fabian Kosmale 2021-01-21 20:02:17 +01:00
parent b322a971f0
commit 0deb3961e6
5 changed files with 158 additions and 9 deletions

View File

@ -83,7 +83,10 @@ public:
QString propName;
QQmlNullableValue<QJSValue> value;
QQmlProperty prop;
QQmlAbstractBinding::Ptr prevBind;
union {
QQmlAbstractBinding::Ptr prevBind;
QUntypedPropertyBinding prevPropertyBinding;
};
QV4::PersistentValue v4Value;
QVariant prevValue;
bool prevIsVariant:1;
@ -96,6 +99,7 @@ public:
void validate(QObject *binding) const;
void clearPrev();
bool prevBindingSet() const;
};
void QQmlBindPrivate::validate(QObject *binding) const
@ -444,12 +448,23 @@ void QQmlBind::prepareEval()
void QQmlBindPrivate::clearPrev()
{
prevBind = nullptr;
if (prop.property().isBindable())
prevPropertyBinding = {};
else
prevBind = nullptr;
v4Value.clear();
prevValue.clear();
prevIsVariant = false;
}
bool QQmlBindPrivate::prevBindingSet() const
{
if (prop.property().isBindable())
return !prevPropertyBinding.isNull();
else
return prevBind;
}
void QQmlBind::eval()
{
Q_D(QQmlBind);
@ -460,11 +475,18 @@ void QQmlBind::eval()
if (d->when.isValid()) {
if (!d->when) {
//restore any previous binding
if (d->prevBind) {
if (d->prevBindingSet()) {
if (d->restoreBinding) {
QQmlAbstractBinding::Ptr p = d->prevBind;
d->clearPrev(); // Do that before setBinding(), as setBinding() may recurse.
QQmlPropertyPrivate::setBinding(p.data());
QMetaProperty metaProp = d->prop.property();
if (metaProp.isBindable()) {
auto prevBinding = d->prevPropertyBinding;
d->clearPrev(); // Do that before setBinding(), as setBinding() may recurse.
metaProp.bindable(d->prop.object()).setBinding(prevBinding);
} else {
QQmlAbstractBinding::Ptr p = d->prevBind;
d->clearPrev(); // Do that before setBinding(), as setBinding() may recurse.
QQmlPropertyPrivate::setBinding(p.data());
}
}
} else if (!d->v4Value.isEmpty()) {
if (d->restoreValue) {
@ -484,11 +506,18 @@ void QQmlBind::eval()
}
//save any set binding for restoration
if (!d->prevBind && d->v4Value.isEmpty() && !d->prevIsVariant) {
if (!d->prevBindingSet() && d->v4Value.isEmpty() && !d->prevIsVariant) {
// try binding first
d->prevBind = QQmlPropertyPrivate::binding(d->prop);
d->prop.property().isBindable();
QMetaProperty metaProp = d->prop.property();
if (metaProp.isBindable()) {
QUntypedBindable bindable = d->prop.property().bindable(d->prop.object());
d->prevPropertyBinding = bindable.takeBinding();
} else {
d->prevBind = QQmlPropertyPrivate::binding(d->prop);
}
if (!d->prevBind) { // nope, try a V4 value next
if (!d->prevBindingSet()) { // nope, try a V4 value next
auto propPriv = QQmlPropertyPrivate::get(d->prop);
auto propData = propPriv->core;
if (!propPriv->valueTypeData.isValid() && propData.isVarProperty()) {
@ -503,6 +532,8 @@ void QQmlBind::eval()
}
}
// NOTE: removeBinding has no effect on QProperty classes, but
// we already used takeBinding to remove it
QQmlPropertyPrivate::removeBinding(d->prop);
}

View File

@ -14,6 +14,7 @@ qt_internal_add_test(tst_qqmlbinding
SOURCES
../../shared/util.cpp ../../shared/util.h
tst_qqmlbinding.cpp
WithBindableProperties.h
INCLUDE_DIRECTORIES
../../shared
PUBLIC_LIBRARIES
@ -25,6 +26,14 @@ qt_internal_add_test(tst_qqmlbinding
TESTDATA ${test_data}
)
set_target_properties(tst_qqmlbinding PROPERTIES
QT_QML_MODULE_URI "test"
QT_QML_MODULE_VERSION 1.0
)
qt6_qml_type_registration(tst_qqmlbinding)
## Scopes:
#####################################################################

View File

@ -0,0 +1,55 @@
/****************************************************************************
**
** 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 WithBindableProperties_H
#define WithBindableProperties_H
#include <QObject>
#include <qqml.h>
class WithBindableProperties : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int a READ a WRITE setA BINDABLE bindableA)
Q_PROPERTY(int b READ b WRITE setB BINDABLE bindableB)
public:
QProperty<int> m_a;
QProperty<int> m_b;
int a() {return m_a;}
int b() {return m_b;}
void setA(int val) {m_a = val;}
void setB(int val) {m_b = val;}
QBindable<int> bindableA() {return QBindable<int>(&m_a); }
QBindable<int> bindableB() {return QBindable<int>(&m_b); }
};
#endif

View File

@ -0,0 +1,24 @@
import QtQuick 2.0
import test 1
Rectangle {
width: 400
height: 400
WithBindableProperties {
id: myItem
objectName: "myItem"
a: 100 - myItem.b
Binding on a {
when: myItem.b > 50
value: myItem.b
}
/*NumberAnimation on y {
loops: Animation.Infinite
to: 100
duration: 1000
}*/
}
}

View File

@ -31,6 +31,7 @@
#include <private/qqmlbind_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include "../../shared/util.h"
#include "WithBindableProperties.h"
class tst_qqmlbinding : public QQmlDataTest
{
@ -42,6 +43,7 @@ private slots:
void binding();
void whenAfterValue();
void restoreBinding();
void restoreBindingBindablePorperty();
void restoreBindingValue();
void restoreBindingVarValue();
void restoreBindingJSValue();
@ -134,6 +136,34 @@ void tst_qqmlbinding::restoreBinding()
QCOMPARE(myItem->x(), qreal(100-49));
}
void tst_qqmlbinding::restoreBindingBindablePorperty()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBinding5.qml"));
QScopedPointer<QQuickRectangle> rect { qobject_cast<QQuickRectangle*>(c.create()) };
QVERIFY2(rect, qPrintable(c.errorString()));
auto *myItem = rect->findChild<WithBindableProperties*>("myItem");
QVERIFY(myItem != nullptr);
myItem->setB(25);
QCOMPARE(myItem->a(), qreal(100-25));
myItem->setB(13);
QCOMPARE(myItem->a(), qreal(100-13));
//Binding takes effect
myItem->setB(51);
QCOMPARE(myItem->a(), qreal(51));
myItem->setB(88);
QCOMPARE(myItem->a(), qreal(88));
//original binding restored
myItem->setB(49);
QCOMPARE(myItem->a(), qreal(100-49));
}
void tst_qqmlbinding::restoreBindingValue()
{
QQmlEngine engine;