Ground work for bindable properties in QObject

Add a private QBindableInterface and a public QBindable<T>
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 <ulf.hermann@qt.io>
This commit is contained in:
Lars Knoll 2020-08-21 13:09:21 +02:00
parent 638df6138e
commit 9b6df7deb3
2 changed files with 228 additions and 0 deletions

View File

@ -605,6 +605,122 @@ public:
return aliasedProperty() != nullptr; 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<typename Property, typename = void>
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<const Property *>(d)->value(); }, location); },
[](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
{ observer->setSource(static_cast<const Property *>(d)->bindingData()); }
};
};
template<typename Property>
class QBindableInterfaceForProperty<Property, std::void_t<decltype(std::declval<Property>().binding())>>
{
using T = typename Property::value_type;
public:
static constexpr QBindableInterface iface = {
[](const QUntypedPropertyData *d) -> QUntypedPropertyBinding
{ return static_cast<const Property *>(d)->binding(); },
[](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding
{ return static_cast<Property *>(d)->setBinding(static_cast<const QPropertyBinding<T> &>(binding)); },
[](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding
{ return Qt::makePropertyBinding([d]() -> T { return static_cast<const Property *>(d)->value(); }, location); },
[](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
{ observer->setSource(static_cast<const Property *>(d)->bindingData()); }
};
};
}
template<typename T>
class QBindable
{
protected:
QUntypedPropertyData *data;
const QtPrivate::QBindableInterface *iface;
public:
template<typename Property>
QBindable(Property *p)
: data(const_cast<std::remove_cv_t<Property> *>(p)),
iface(&QtPrivate::QBindableInterfaceForProperty<Property>::iface)
{}
QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION)
{
return static_cast<QPropertyBinding<T> &&>(iface->makeBinding(data, location));
}
template<typename Functor>
QPropertyChangeHandler<Functor> onValueChanged(Functor f)
{
QPropertyChangeHandler<Functor> handler(f);
iface->setObserver(data, &handler);
return handler;
}
template<typename Functor>
QPropertyChangeHandler<Functor> subscribe(Functor f)
{
f();
return onValueChanged(f);
}
QPropertyBinding<T> binding() const
{
if (!iface->getBinding)
return QPropertyBinding<T>();
return static_cast<QPropertyBinding<T> &&>(iface->getBinding(data));
}
QPropertyBinding<T> setBinding(const QPropertyBinding<T> &binding)
{
if (!iface->setBinding)
return QPropertyBinding<T>();
return static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding));
}
#ifndef Q_CLANG_QDOC
template <typename Functor>
QPropertyBinding<T> setBinding(Functor &&f,
const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION,
std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr)
{
return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location));
}
#else
template <typename Functor>
QPropertyBinding<T> setBinding(Functor f);
#endif
bool hasBinding() const
{
return !binding().isNull();
}
};
QT_END_NAMESPACE QT_END_NAMESPACE
#endif // QPROPERTY_H #endif // QPROPERTY_H

View File

@ -72,6 +72,9 @@ private slots:
void arrowAndStarOperator(); void arrowAndStarOperator();
void typeNoOperatorEqual(); void typeNoOperatorEqual();
void bindingValueReplacement(); void bindingValueReplacement();
void testNewStuff();
void qobjectObservers();
}; };
void tst_QProperty::functorBinding() void tst_QProperty::functorBinding()
@ -873,6 +876,115 @@ void tst_QProperty::bindingValueReplacement()
// QCOMPARE(test.iconText.value(), 42); // 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<int> bindableFoo() { return QBindable<int>(&fooData); }
QBindable<int> bindableBar() { return QBindable<int>(&barData); }
QBindable<int> bindableRead() { return QBindable<int>(&readData); }
public:
int fooChangedCount = 0;
int barChangedCount = 0;
int readChangedCount = 0;
QProperty<int> fooData;
QProperty<int> barData;
QProperty<int> 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<int>());
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); QTEST_MAIN(tst_QProperty);
#include "tst_qproperty.moc" #include "tst_qproperty.moc"