Add support for computed properties

Add a QObjectComputedProperty. This class doesn't store the data
itself, instead relies on a getter method to compute it's value.
As the property is read-only, one can not bind to it, but it can
be used in other property bindings.

Change-Id: I0f6bffdd9f80f1d0829826f93a47257f2b3127af
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Lars Knoll 2020-08-25 12:28:11 +02:00
parent 1e1b888092
commit 918c61f275
2 changed files with 90 additions and 0 deletions

View File

@ -929,6 +929,82 @@ private:
} \
QObjectBindableProperty<Class, Type, Class::_qt_property_##name##_offset, __VA_ARGS__> name;
template<typename Class, typename T, auto Offset, auto Getter>
class QObjectComputedProperty : public QUntypedPropertyData
{
Class *owner()
{
char *that = reinterpret_cast<char *>(this);
return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
}
const Class *owner() const
{
char *that = const_cast<char *>(reinterpret_cast<const char *>(this));
return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
}
public:
using value_type = T;
using parameter_type = T;
QObjectComputedProperty() = default;
parameter_type value() const {
qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this);
return (owner()->*Getter)();
}
std::conditional_t<QTypeTraits::is_dereferenceable_v<T>, parameter_type, void>
operator->() const
{
if constexpr (QTypeTraits::is_dereferenceable_v<T>)
return value();
else
return;
}
parameter_type operator*() const
{
return value();
}
operator parameter_type() const
{
return value();
}
constexpr bool hasBinding() const { return false; }
template<typename Functor>
QPropertyChangeHandler<Functor> onValueChanged(Functor f)
{
static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
return QPropertyChangeHandler<Functor>(*this, f);
}
template<typename Functor>
QPropertyChangeHandler<Functor> subscribe(Functor f)
{
static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
f();
return onValueChanged(f);
}
QtPrivate::QPropertyBindingData &bindingData() const
{
auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
return *storage->bindingData(const_cast<QObjectComputedProperty *>(this), true);
}
private:
};
#define Q_OBJECT_COMPUTED_PROPERTY(Class, Type, name, ...) \
static constexpr size_t _qt_property_##name##_offset() { \
QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
return offsetof(Class, name); \
QT_WARNING_POP \
} \
QObjectComputedProperty<Class, Type, Class::_qt_property_##name##_offset, __VA_ARGS__> name;
QT_END_NAMESPACE
#endif // QPROPERTY_H

View File

@ -968,6 +968,7 @@ class MyQObject : public QObject
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)
Q_PROPERTY(int computed READ computed STORED false)
signals:
void fooChanged();
@ -985,10 +986,12 @@ public:
int bar() const { return barData.value(); }
void setBar(int i) { barData.setValue(i); }
int read() const { return readData.value(); }
int computed() 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); }
QBindable<int> bindableComputed() { return QBindable<int>(&computedData); }
public:
int fooChangedCount = 0;
@ -998,6 +1001,7 @@ public:
Q_OBJECT_BINDABLE_PROPERTY(MyQObject, int, fooData, &MyQObject::fooChanged);
Q_OBJECT_BINDABLE_PROPERTY(MyQObject, int, barData, &MyQObject::barChanged);
Q_OBJECT_BINDABLE_PROPERTY(MyQObject, int, readData, &MyQObject::readChanged);
Q_OBJECT_COMPUTED_PROPERTY(MyQObject, int, computedData, &MyQObject::computed);
};
void tst_QProperty::testNewStuff()
@ -1049,6 +1053,16 @@ void tst_QProperty::testNewStuff()
QCOMPARE(object.foo(), 0);
object.readData.setValue(10);
QCOMPARE(object.foo(), 10);
QCOMPARE(object.computed(), 10);
object.readData.setValue(42);
QCOMPARE(object.computed(), 42);
object.bindableBar().setBinding(object.bindableComputed().makeBinding());
QCOMPARE(object.computed(), 42);
object.readData.setValue(111);
QCOMPARE(object.computed(), 111);
}
void tst_QProperty::qobjectObservers()