summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@qt.io>2020-04-29 12:41:36 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2020-05-28 21:08:51 +0000
commit36f69229253480116de72a7ef46c3bdde1e805e5 (patch)
treeea94df7ec60092acabcf749cab64d009fd3a5fcc /src
parent66d6943478c3ae76f996c2cd2fdf04fee4fcb767 (diff)
Implement support for QProperty<T> with a static observer
A common pattern in Qt Quick will be QProperty members that are connected to a callback that needs to perform something when the value changes, for example emitting a compatibility signal or marking scene graph node data dirty. To make such a pattern more efficient, a new QNotifiedProperty type is introduced that offers the same API as QProperty<T>, with two changes: (1) The template instantiation not only takes the property type as parameter but also a callback pointer-to-member. (2) Since that member itself cannot be called without an instance and to avoid storing an instance pointer permanently, the API for setBinding and setValue are adjusted to also take the instance pointer. For the former it gets stored in the binding, for the latter it is used to invoke the callback after setting the new value. Change-Id: I85cc1d1d1c0472164c4ae87808cfdc0d0b1475e1 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/kernel/qproperty.cpp230
-rw-r--r--src/corelib/kernel/qproperty.h146
-rw-r--r--src/corelib/kernel/qpropertybinding.cpp2
-rw-r--r--src/corelib/kernel/qpropertybinding_p.h4
-rw-r--r--src/corelib/kernel/qpropertyprivate.h4
5 files changed, 382 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();