summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp38
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"