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"