Skip to content

Commit

Permalink
Implement support for QProperty<T> with a static observer
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
tronical authored and Inkane committed May 28, 2020
1 parent 66d6943 commit 36f6922
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 4 deletions.
230 changes: 229 additions & 1 deletion src/corelib/kernel/qproperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -703,6 +707,230 @@ QPropertyBindingSourceLocation QPropertyBindingError::location() const
goes out of scope, the callback is unsubscribed.
*/

/*!
\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
Expand Down
Loading

0 comments on commit 36f6922

Please sign in to comment.