From 9b6df7deb3fff6179071ee59603250cb59c03222 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Fri, 21 Aug 2020 13:09:21 +0200 Subject: [PATCH] Ground work for bindable properties in QObject Add a private QBindableInterface and a public QBindable class, that will be the API interface for accessing bindings for properties in QObject. The QBindable class gives access to all aspects of the property related to bindings. This includes setting and retrieving bindings, installing observers and creating a direct binding on this property. Change-Id: Iaead54d2bd6947bd2cda5052142b2a47dd8bf7c4 Reviewed-by: Ulf Hermann --- src/corelib/kernel/qproperty.h | 116 ++++++++++++++++++ .../kernel/qproperty/tst_qproperty.cpp | 112 +++++++++++++++++ 2 files changed, 228 insertions(+) diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index 1f45caee49..85bdfdcd56 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -605,6 +605,122 @@ public: return aliasedProperty() != nullptr; } }; + +namespace QtPrivate +{ + +struct QBindableInterface +{ + using BindingGetter = QUntypedPropertyBinding (*)(const QUntypedPropertyData *d); + using BindingSetter = QUntypedPropertyBinding (*)(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding); + using MakeBinding = QUntypedPropertyBinding (*)(const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location); + using SetObserver = void (*)(const QUntypedPropertyData *d, QPropertyObserver *observer); + BindingGetter getBinding; + BindingSetter setBinding; + MakeBinding makeBinding; + SetObserver setObserver; +}; + +template +class QBindableInterfaceForProperty +{ + using T = typename Property::value_type; +public: + // interface for read-only properties. Those do not have a binding()/setBinding() method, but one can + // install observers on them. + static constexpr QBindableInterface iface = { + nullptr, + nullptr, + [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding + { return Qt::makePropertyBinding([d]() -> T { return static_cast(d)->value(); }, location); }, + [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void + { observer->setSource(static_cast(d)->bindingData()); } + }; +}; + +template +class QBindableInterfaceForProperty().binding())>> +{ + using T = typename Property::value_type; +public: + static constexpr QBindableInterface iface = { + [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding + { return static_cast(d)->binding(); }, + [](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding + { return static_cast(d)->setBinding(static_cast &>(binding)); }, + [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding + { return Qt::makePropertyBinding([d]() -> T { return static_cast(d)->value(); }, location); }, + [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void + { observer->setSource(static_cast(d)->bindingData()); } + }; +}; + +} + +template +class QBindable +{ +protected: + QUntypedPropertyData *data; + const QtPrivate::QBindableInterface *iface; + +public: + template + QBindable(Property *p) + : data(const_cast *>(p)), + iface(&QtPrivate::QBindableInterfaceForProperty::iface) + {} + + QPropertyBinding makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) + { + return static_cast &&>(iface->makeBinding(data, location)); + } + + template + QPropertyChangeHandler onValueChanged(Functor f) + { + QPropertyChangeHandler handler(f); + iface->setObserver(data, &handler); + return handler; + } + + template + QPropertyChangeHandler subscribe(Functor f) + { + f(); + return onValueChanged(f); + } + + QPropertyBinding binding() const + { + if (!iface->getBinding) + return QPropertyBinding(); + return static_cast &&>(iface->getBinding(data)); + } + QPropertyBinding setBinding(const QPropertyBinding &binding) + { + if (!iface->setBinding) + return QPropertyBinding(); + return static_cast &&>(iface->setBinding(data, binding)); + } +#ifndef Q_CLANG_QDOC + template + QPropertyBinding setBinding(Functor &&f, + const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, + std::enable_if_t> * = nullptr) + { + return setBinding(Qt::makePropertyBinding(std::forward(f), location)); + } +#else + template + QPropertyBinding setBinding(Functor f); +#endif + bool hasBinding() const + { + return !binding().isNull(); + } +}; + QT_END_NAMESPACE #endif // QPROPERTY_H diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 4f250992c9..340099a7e9 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -72,6 +72,9 @@ private slots: void arrowAndStarOperator(); void typeNoOperatorEqual(); void bindingValueReplacement(); + + void testNewStuff(); + void qobjectObservers(); }; void tst_QProperty::functorBinding() @@ -873,6 +876,115 @@ void tst_QProperty::bindingValueReplacement() // QCOMPARE(test.iconText.value(), 42); } +class MyQObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged) // Use Q_BINDABLE_PROPERTY and generate iface API + Q_PROPERTY(int bar READ bar WRITE setBar NOTIFY barChanged) + Q_PROPERTY(int read READ read NOTIFY readChanged) + +signals: + void fooChanged(); + void barChanged(); + void readChanged(); + +public slots: + void fooHasChanged() { fooChangedCount++; } + void barHasChanged() { barChangedCount++; } + void readHasChanged() { readChangedCount++; } + +public: + int foo() const { return fooData.value(); } + void setFoo(int i) { fooData.setValue(i); } + int bar() const { return barData.value(); } + void setBar(int i) { barData.setValue(i); } + int read() const { return readData.value(); } + + QBindable bindableFoo() { return QBindable(&fooData); } + QBindable bindableBar() { return QBindable(&barData); } + QBindable bindableRead() { return QBindable(&readData); } + +public: + int fooChangedCount = 0; + int barChangedCount = 0; + int readChangedCount = 0; + QProperty fooData; + QProperty barData; + QProperty readData; +}; + +void tst_QProperty::testNewStuff() +{ + MyQObject object; + QObject::connect(&object, &MyQObject::fooChanged, &object, &MyQObject::fooHasChanged); + QObject::connect(&object, &MyQObject::barChanged, &object, &MyQObject::barHasChanged); + QObject::connect(&object, &MyQObject::readChanged, &object, &MyQObject::readHasChanged); + +// QCOMPARE(object.fooChangedCount, 0); + object.setFoo(10); +// QCOMPARE(object.fooChangedCount, 1); + QCOMPARE(object.foo(), 10); + + auto f = [&object]() -> int { + return object.barData; + }; +// QCOMPARE(object.barChangedCount, 0); + object.setBar(42); +// QCOMPARE(object.barChangedCount, 1); +// QCOMPARE(object.fooChangedCount, 1); + object.fooData.setBinding(f); +// QCOMPARE(object.fooChangedCount, 2); + QCOMPARE(object.fooData.value(), 42); + object.setBar(666); +// QCOMPARE(object.fooChangedCount, 3); +// QCOMPARE(object.barChangedCount, 2); + QCOMPARE(object.fooData.value(), 666); +// QCOMPARE(object.fooChangedCount, 3); + + auto f2 = [&object]() -> int { + return object.barData / 2; + }; + + object.bindableFoo().setBinding(Qt::makePropertyBinding(f2)); + QVERIFY(object.bindableFoo().hasBinding()); + QCOMPARE(object.foo(), 333); + auto oldBinding = object.bindableFoo().setBinding(QPropertyBinding()); + QVERIFY(!object.bindableFoo().hasBinding()); + QVERIFY(!oldBinding.isNull()); + QCOMPARE(object.foo(), 333); + object.setBar(222); + QCOMPARE(object.foo(), 333); + object.bindableFoo().setBinding(oldBinding); + QCOMPARE(object.foo(), 111); + + auto b = object.bindableRead().makeBinding(); + object.bindableFoo().setBinding(b); + QCOMPARE(object.foo(), 0); + object.readData.setValue(10); + QCOMPARE(object.foo(), 10); +} + +void tst_QProperty::qobjectObservers() +{ + MyQObject object; + int onValueChangedCalled = 0; + { + auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() { ++onValueChangedCalled;}); + QCOMPARE(onValueChangedCalled, 0); + + object.setFoo(10); + QCOMPARE(onValueChangedCalled, 1); + + object.bindableFoo().setBinding(object.bindableBar().makeBinding()); + QCOMPARE(onValueChangedCalled, 2); + + object.setBar(42); + QCOMPARE(onValueChangedCalled, 3); + } + object.setBar(0); + QCOMPARE(onValueChangedCalled, 3); +} + QTEST_MAIN(tst_QProperty); #include "tst_qproperty.moc"