diff options
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 230 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty.h | 146 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertybinding.cpp | 2 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertybinding_p.h | 4 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertyprivate.h | 4 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp | 38 |
6 files changed, 420 insertions, 4 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index c3e7711f94..a1b1d7b127 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -92,7 +92,10 @@ QPropertyBase::~QPropertyBase() binding->unlinkAndDeref(); } -QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding &binding, void *propertyDataPtr) +QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding &binding, + void *propertyDataPtr, + void *staticObserver, + void (*staticObserverCallback)(void*)) { QPropertyBindingPrivatePtr oldBinding; QPropertyBindingPrivatePtr newBinding = binding.d; @@ -119,6 +122,7 @@ QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding newBinding->setProperty(propertyDataPtr); if (observer) newBinding->prependObserver(observer); + newBinding->setStaticObserver(staticObserver, staticObserverCallback); } else if (observer) { d.setObservers(observer.ptr); } else { @@ -704,6 +708,230 @@ QPropertyBindingSourceLocation QPropertyBindingError::location() const */ /*! + \class QNotifiedProperty + \inmodule QtCore + \brief The QNotifiedProperty class is a template class that enables automatic property bindings + and invokes a callback function on the surrounding class when the value changes. + + \ingroup tools + + QNotifiedProperty\<T, Callback\> is a generic container that holds an + instance of T and behaves mostly like \l QProperty. The extra template + parameter is used to identify the surrounding class and a member function of + that class. The member function will be called whenever the value held by the + property changes. + + You can use QNotifiedProperty to port code that uses Q_PROPERTY. The getter + and setter are trivial to adapt for accessing a \l QProperty rather than the + plain value. In order to invoke the change signal on property changes, use + QNotifiedProperty and pass the change signal as callback. + + \code + class MyClass : public QObject + { + \Q_OBJECT + // Replacing: Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged) + public: + int x() const { return xProp; } + void setX(int x) { xProp = x; } + + signals: + void xChanged(); + + private: + // Now you can set bindings on xProp and use it in other bindings. + QNotifiedProperty<int, &MyClass::xChanged> xProp; + }; + \endcode +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::QNotifiedProperty() + + Constructs a property with a default constructed instance of T. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> explicit QNotifiedProperty<T, Callback>::QNotifiedProperty(const T &initialValue) + + Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> explicit QNotifiedProperty<T, Callback>::QNotifiedProperty(T &&initialValue) + + Move-Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::QNotifiedProperty(Class *owner, const QPropertyBinding<T> &binding) + + Constructs a property that is tied to the provided \a binding expression. The + first time the property value is read, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. When the property value changes \a + owner is notified via the Callback function. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::QNotifiedProperty(Class *owner, QPropertyBinding<T> &&binding) + + Constructs a property that is tied to the provided \a binding expression. The + first time the property value is read, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. When the property value changes \a + owner is notified via the Callback function. +*/ + + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> template <typename Functor> QNotifiedProperty<T, Callback>::QNotifiedProperty(Class *owner, Functor &&f) + + Constructs a property that is tied to the provided binding expression \a f. The + first time the property value is read, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. When the property value changes \a + owner is notified via the Callback function. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::~QNotifiedProperty() + + Destroys the property. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> T QNotifiedProperty<T, Callback>::value() const + + Returns the value of the property. This may evaluate a binding expression that + is tied to this property, before returning the value. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::operator T() const + + Returns the value of the property. This may evaluate a binding expression that + is tied to this property, before returning the value. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> void QNotifiedProperty<T, Callback>::setValue(Class *owner, const T &newValue) + + Assigns \a newValue to this property and removes the property's associated + binding, if present. If the property value changes as a result, calls the + Callback function on \a owner. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> void QNotifiedProperty<T, Callback>::setValue(Class *owner, T &&newValue) + \overload + + Assigns \a newValue to this property and removes the property's associated + binding, if present. If the property value changes as a result, calls the + Callback function on \a owner. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, Callback>::setBinding(Class *owner, const QPropertyBinding<T> &newBinding) + + Associates the value of this property with the provided \a newBinding + expression and returns the previously associated binding. The first time the + property value is read, the binding is evaluated. Whenever a dependency of the + binding changes, the binding will be re-evaluated the next time the value of + this property is read. When the property value changes \a owner is notified + via the Callback function. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> template <typename Functor> QPropertyBinding<T> QNotifiedProperty<T, Callback>::setBinding(Class *owner, Functor f) + \overload + + Associates the value of this property with the provided functor \a f and + returns the previously associated binding. The first time the property value + is read, the binding is evaluated by invoking the call operator () of \a f. + Whenever a dependency of the binding changes, the binding will be re-evaluated + the next time the value of this property is read. When the property value + changes \a owner is notified via the Callback function. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, Callback>::setBinding(Class *owner, QPropertyBinding<T> &&newBinding) + \overload + + Associates the value of this property with the provided \a newBinding + expression and returns the previously associated binding. The first time the + property value is read, the binding is evaluated. Whenever a dependency of the + binding changes, the binding will be re-evaluated the next time the value of + this property is read. When the property value changes \a owner is notified + via the Callback function. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> bool QNotifiedProperty<T, Callback>::setBinding(Class *owner, const QUntypedPropertyBinding &newBinding) + \overload + + Associates the value of this property with the provided \a newBinding + expression. The first time the property value is read, the binding is evaluated. + Whenever a dependency of the binding changes, the binding will be re-evaluated + the next time the value of this property is read. When the property value + changes \a owner is notified via the Callback function. + + Returns true if the type of this property is the same as the type the binding + function returns; false otherwise. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> bool QNotifiedProperty<T, Callback>::hasBinding() const + + Returns true if the property is associated with a binding; false otherwise. +*/ + + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, Callback>::binding() const + + Returns the binding expression that is associated with this property. A + default constructed QPropertyBinding<T> will be returned if no such + association exists. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, Callback>::takeBinding() + + Disassociates the binding expression from this property and returns it. After + calling this function, the value of the property will only change if you + assign a new value to it, or when a new binding is set. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> template <typename Functor> QPropertyChangeHandler<T, Functor> QNotifiedProperty<T, Callback>::onValueChanged(Functor f) + + Registers the given functor \a f as a callback that shall be called whenever + the value of the property changes. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, an std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the registration. When it + goes out of scope, the callback is de-registered. +*/ + +/*! + \fn template <typename T, typename Class, void(Class::*Callback)()> template <typename Functor> QPropertyChangeHandler<T, Functor> QNotifiedProperty<T, Callback>::subscribe(Functor f) + + Subscribes the given functor \a f as a callback that is called immediately and whenever + the value of the property changes in the future. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, an std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the subscription. When it + goes out of scope, the callback is unsubscribed. +*/ + +/*! \class QPropertyChangeHandler \inmodule QtCore \brief The QPropertyChangeHandler class controls the lifecycle of change callback installed on a QProperty. diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index 76c41f356c..e2a6172d84 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -85,6 +85,7 @@ struct Q_CORE_EXPORT QPropertyBindingSourceLocation template <typename Functor> class QPropertyChangeHandler; template <typename T> class QProperty; +template <typename T, auto callbackMember> class QNotifiedProperty; class QPropertyBindingErrorPrivate; @@ -178,12 +179,15 @@ public: : QUntypedPropertyBinding(property.d.priv.binding()) {} -private: + template<auto notifier> + QPropertyBinding(const QNotifiedProperty<PropertyType, notifier> &property) + : QUntypedPropertyBinding(property.d.priv.binding()) + {} + // Internal explicit QPropertyBinding(const QUntypedPropertyBinding &binding) : QUntypedPropertyBinding(binding) {} - friend class QProperty<PropertyType>; }; namespace QtPrivate { @@ -370,6 +374,140 @@ namespace Qt { } } +template <typename T, typename Class, void(Class::*Callback)()> +class QNotifiedProperty<T, Callback> +{ +public: + using value_type = T; + + QNotifiedProperty() = default; + + explicit QNotifiedProperty(const T &initialValue) : d(initialValue) {} + explicit QNotifiedProperty(T &&initialValue) : d(std::move(initialValue)) {} + + QNotifiedProperty(Class *owner, const QPropertyBinding<T> &binding) + : QNotifiedProperty() + { setBinding(owner, binding); } + QNotifiedProperty(Class *owner, QPropertyBinding<T> &&binding) + : QNotifiedProperty() + { setBinding(owner, std::move(binding)); } + +#ifndef Q_CLANG_QDOC + template <typename Functor> + explicit QNotifiedProperty(Class *owner, Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, + typename std::enable_if_t<std::is_invocable_r_v<T, Functor&>> * = 0) + : QNotifiedProperty(QPropertyBinding<T>(owner, std::forward<Functor>(f), location)) + {} +#else + template <typename Functor> + explicit QProperty(Class *owner, Functor &&f); +#endif + + ~QNotifiedProperty() = default; + + T value() const + { + if (d.priv.hasBinding()) + d.priv.evaluateIfDirty(); + d.priv.registerWithCurrentlyEvaluatingBinding(); + return d.getValue(); + } + + operator T() const + { + return value(); + } + + void setValue(Class *owner, T &&newValue) + { + if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) + notify(owner); + d.priv.removeBinding(); + } + + void setValue(Class *owner, const T &newValue) + { + if (d.setValueAndReturnTrueIfChanged(newValue)) + notify(owner); + d.priv.removeBinding(); + } + + QPropertyBinding<T> setBinding(Class *owner, const QPropertyBinding<T> &newBinding) + { + QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o) { + (reinterpret_cast<Class *>(o)->*Callback)(); + })); + notify(owner); + return oldBinding; + } + + QPropertyBinding<T> setBinding(Class *owner, QPropertyBinding<T> &&newBinding) + { + QPropertyBinding<T> b(std::move(newBinding)); + QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d, owner, [](void *o) { + (reinterpret_cast<Class *>(o)->*Callback)(); + })); + notify(owner); + return oldBinding; + } + + bool setBinding(Class *owner, const QUntypedPropertyBinding &newBinding) + { + if (newBinding.valueMetaType().id() != qMetaTypeId<T>()) + return false; + d.priv.setBinding(newBinding, &d, owner, [](void *o) { + (reinterpret_cast<Class *>(o)->*Callback)(); + }); + notify(owner); + return true; + } + +#ifndef Q_CLANG_QDOC + template <typename Functor> + QPropertyBinding<T> setBinding(Class *owner, Functor &&f, + const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, + std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr) + { + return setBinding(owner, Qt::makePropertyBinding(std::forward<Functor>(f), location)); + } +#else + template <typename Functor> + QPropertyBinding<T> setBinding(Class *owner, Functor f); +#endif + + bool hasBinding() const { return d.priv.hasBinding(); } + + QPropertyBinding<T> binding() const + { + return QPropertyBinding<T>(*this); + } + + QPropertyBinding<T> takeBinding() + { + return QPropertyBinding<T>(d.priv.setBinding(QUntypedPropertyBinding(), &d)); + } + + template<typename Functor> + QPropertyChangeHandler<Functor> onValueChanged(Functor f); + template<typename Functor> + QPropertyChangeHandler<Functor> subscribe(Functor f); + +private: + void notify(Class *owner) + { + d.priv.notifyObservers(&d); + (owner->*Callback)(); + } + + Q_DISABLE_COPY_MOVE(QNotifiedProperty) + + friend class QPropertyBinding<T>; + friend class QPropertyObserver; + // Mutable because querying for the value may require evalating the binding expression, calling + // non-const functions on QPropertyBase. + mutable QtPrivate::QPropertyValueStorage<T> d; +}; + struct QPropertyObserverPrivate; struct QPropertyObserverPointer; @@ -392,6 +530,10 @@ public: void setSource(const QProperty<PropertyType> &property) { setSource(property.d.priv); } + template <typename PropertyType, auto notifier> + void setSource(const QNotifiedProperty<PropertyType, notifier> &property) + { setSource(property.d.priv); } + protected: QPropertyObserver(void (*callback)(QPropertyObserver*, void *)); QPropertyObserver(void *aliasedPropertyPtr); diff --git a/src/corelib/kernel/qpropertybinding.cpp b/src/corelib/kernel/qpropertybinding.cpp index 29b1a4a69a..55f2fe913d 100644 --- a/src/corelib/kernel/qpropertybinding.cpp +++ b/src/corelib/kernel/qpropertybinding.cpp @@ -63,6 +63,8 @@ void QPropertyBindingPrivate::unlinkAndDeref() void QPropertyBindingPrivate::markDirtyAndNotifyObservers() { dirty = true; + if (staticObserver) + staticObserverCallback(staticObserver); if (firstObserver) firstObserver.notify(this, propertyDataPtr); } diff --git a/src/corelib/kernel/qpropertybinding_p.h b/src/corelib/kernel/qpropertybinding_p.h index 7c4166592b..a2c733abd9 100644 --- a/src/corelib/kernel/qpropertybinding_p.h +++ b/src/corelib/kernel/qpropertybinding_p.h @@ -71,6 +71,9 @@ private: QPropertyObserverPointer firstObserver; std::array<QPropertyObserver, 4> inlineDependencyObservers; QScopedPointer<std::vector<QPropertyObserver>> heapObservers; + // ### merge with inline dependency observer storage + void *staticObserver = nullptr; + void (*staticObserverCallback)(void*) = nullptr; void *propertyDataPtr = nullptr; @@ -96,6 +99,7 @@ public: void setDirty(bool d) { dirty = d; } void setProperty(void *propertyPtr) { propertyDataPtr = propertyPtr; } + void setStaticObserver(void *observer, void (*callback)(void*)) { staticObserver = observer; staticObserverCallback = callback; } void prependObserver(QPropertyObserverPointer observer) { observer.ptr->prev = const_cast<QPropertyObserver **>(&firstObserver.ptr); firstObserver = observer; diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h index 4b0b09d9db..7a2c6a5b77 100644 --- a/src/corelib/kernel/qpropertyprivate.h +++ b/src/corelib/kernel/qpropertyprivate.h @@ -82,7 +82,9 @@ public: bool hasBinding() const { return d_ptr & BindingBit; } - QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding, void *propertyDataPtr); + QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding, + void *propertyDataPtr, void *staticObserver = nullptr, + void (*staticObserverCallback)(void*) = nullptr); QPropertyBindingPrivate *binding(); void evaluateIfDirty(); diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 15adcdffd6..394438ab88 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -71,6 +71,7 @@ private slots: void setBindingFunctor(); void multipleObservers(); void propertyAlias(); + void notifiedProperty(); }; void tst_QProperty::functorBinding() @@ -769,6 +770,43 @@ void tst_QProperty::propertyAlias() QCOMPARE(value2, 22); } +struct ClassWithNotifiedProperty +{ + QVector<int> recordedValues; + + void callback() { recordedValues << property.value(); } + + QNotifiedProperty<int, &ClassWithNotifiedProperty::callback> property; +}; + +void tst_QProperty::notifiedProperty() +{ + ClassWithNotifiedProperty instance; + QVERIFY(instance.recordedValues.isEmpty()); + + instance.property.setValue(&instance, 42); + QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.at(0), 42); + instance.recordedValues.clear(); + + instance.property.setValue(&instance, 42); + QVERIFY(instance.recordedValues.isEmpty()); + + QProperty<int> injectedValue(100); + instance.property.setBinding(&instance, [&injectedValue]() { return injectedValue.value(); }); + + QCOMPARE(instance.property.value(), 100); + QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.at(0), 100); + instance.recordedValues.clear(); + + injectedValue = 200; + QCOMPARE(instance.property.value(), 200); + QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.at(0), 200); + instance.recordedValues.clear(); +} + QTEST_MAIN(tst_QProperty); #include "tst_qproperty.moc" |