summaryrefslogtreecommitdiffstats
path: root/src/corelib
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib')
-rw-r--r--src/corelib/kernel/qproperty.cpp237
-rw-r--r--src/corelib/kernel/qproperty.h187
-rw-r--r--src/corelib/kernel/qpropertyprivate.h9
3 files changed, 433 insertions, 0 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp
index 3c1d682b7c..52bf9f33e6 100644
--- a/src/corelib/kernel/qproperty.cpp
+++ b/src/corelib/kernel/qproperty.cpp
@@ -846,6 +846,243 @@ QString QPropertyBindingError::description() const
\internal
*/
+/*!
+ \class QBindablePropertyData
+ \inmodule QtCore
+ \brief The QBindablePropertyData class is a template class that enables automatic property bindings
+ for property data stored in QObject derived classes.
+ \since 6.0
+
+ \ingroup tools
+
+ QBindablePropertyData is a generic container that holds an
+ instance of T and behaves mostly like \l QProperty. The extra template
+ parameters are 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 QBindablePropertyData to add binding support to code that uses Q_PROPERTY.
+ The getter and setter methods are easy to adapt for accessing a \l QBindablePropertyData
+ rather than the plain value. In order to invoke the change signal on property changes, use
+ QBindablePropertyData and pass the change signal as a callback.
+
+ QBindablePropertyData is usually not used directly, instead an instance of it is created by
+ using the Q_BINDABLE_PROPERTY_DATA macro.
+
+ Use the Q_BINDABLE_PROPERTY macro in the class declaration to declare the property as bindable.
+
+ \code
+ class MyClass : public QObject
+ {
+ \Q_OBJECT
+ Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged)
+ public:
+ int x() const { return xProp; }
+ void setX(int x) { xProp = x; }
+ // declare the property as bindable. The data needs to be stored in a QBindablePropertyData instance.
+ // The last argument of the macro tells moc how to access that instance.
+ Q_BINDABLE_PROPERTY(MyClass, x, x, xProp)
+
+ signals:
+ void xChanged();
+
+ private:
+ // Declare the instance of the bindable property data.
+ Q_BINDABLE_PROPERTY_DATA(MyClass, int, xProp, &MyClass::xChanged)
+ };
+ \endcode
+*/
+
+/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData()
+
+ Constructs a property with a default constructed instance of T.
+*/
+
+/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> explicit QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(const T &initialValue)
+
+ Constructs a property with the provided \a initialValue.
+*/
+
+/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> explicit QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(T &&initialValue)
+
+ Move-Constructs a property with the provided \a initialValue.
+*/
+
+/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(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 Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(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 Class, typename T, auto offset, auto Callback> template <typename Functor> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(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 Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::~QBindablePropertyData()
+
+ Destroys the property.
+*/
+
+/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> T QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> void QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> void QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> QPropertyBinding<T> bool QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> bool QBindablePropertyData<Class, T, offset, Callback>::hasBinding() const
+
+ Returns true if the property is associated with a binding; false otherwise.
+*/
+
+
+/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyChangeHandler<T, Functor> QBindablePropertyData<Class, T, offset, 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 Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyChangeHandler<T, Functor> QBindablePropertyData<Class, T, offset, 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.
+*/
+
+/*!
+ \fn template <typename T> QtPrivate::QPropertyBase &QBindablePropertyData<Class, T, offset, Callback>::propertyBase() const
+ \internal
+*/
/*!
\class QPropertyChangeHandler
diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h
index 4013b8c92b..14319f0382 100644
--- a/src/corelib/kernel/qproperty.h
+++ b/src/corelib/kernel/qproperty.h
@@ -742,6 +742,193 @@ public:
QtPrivate::QPropertyBindingData *bindingData(QUntypedPropertyData *data, bool create);
};
+
+template<typename Class, typename T, auto Offset, auto Signal = nullptr>
+class QObjectBindableProperty : public QPropertyData<T>
+{
+ using ThisType = QObjectBindableProperty<Class, T, Offset, Signal>;
+ static bool constexpr HasSignal = !std::is_same_v<decltype(Signal), std::nullptr_t>;
+ 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));
+ }
+ static void signalCallBack(QUntypedPropertyData *o)
+ {
+ QObjectBindableProperty *that = static_cast<QObjectBindableProperty *>(o);
+ if constexpr (HasSignal)
+ (that->owner()->*Signal)();
+ }
+public:
+ using value_type = typename QPropertyData<T>::value_type;
+ using parameter_type = typename QPropertyData<T>::parameter_type;
+ using rvalue_ref = typename QPropertyData<T>::rvalue_ref;
+ using arrow_operator_result = typename QPropertyData<T>::arrow_operator_result;
+
+ QObjectBindableProperty() = default;
+ explicit QObjectBindableProperty(const T &initialValue) : QPropertyData<T>(initialValue) {}
+ explicit QObjectBindableProperty(T &&initialValue) : QPropertyData<T>(std::move(initialValue)) {}
+ explicit QObjectBindableProperty(const QPropertyBinding<T> &binding)
+ : QObjectBindableProperty()
+ { setBinding(binding); }
+#ifndef Q_CLANG_QDOC
+ template <typename Functor>
+ explicit QObjectBindableProperty(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION,
+ typename std::enable_if_t<std::is_invocable_r_v<T, Functor&>> * = 0)
+ : QObjectBindableProperty(QPropertyBinding<T>(std::forward<Functor>(f), location))
+ {}
+#else
+ template <typename Functor>
+ explicit QProperty(Functor &&f);
+#endif
+
+ parameter_type value() const {
+ qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this);
+ return this->val;
+ }
+
+ arrow_operator_result operator->() const
+ {
+ if constexpr (QTypeTraits::is_dereferenceable_v<T>) {
+ return value();
+ } else if constexpr (std::is_pointer_v<T>) {
+ value();
+ return this->val;
+ } else {
+ return;
+ }
+ }
+
+ parameter_type operator*() const
+ {
+ return value();
+ }
+
+ operator parameter_type() const
+ {
+ return value();
+ }
+
+ void setValue(parameter_type t) {
+ auto *bd = qGetBindingStorage(owner())->bindingData(this);
+ if (bd)
+ bd->removeBinding();
+ if (this->val == t)
+ return;
+ this->val = t;
+ notify(bd);
+ }
+
+ void setValue(rvalue_ref t) {
+ auto *bd = qGetBindingStorage(owner())->bindingData(this);
+ if (bd)
+ bd->removeBinding();
+ if (this->val == t)
+ return;
+ this->val = std::move(t);
+ notify(bd);
+ }
+
+ QObjectBindableProperty &operator=(rvalue_ref newValue)
+ {
+ setValue(std::move(newValue));
+ return *this;
+ }
+
+ QObjectBindableProperty &operator=(parameter_type newValue)
+ {
+ setValue(newValue);
+ return *this;
+ }
+
+ QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding)
+ {
+ QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true);
+ QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, HasSignal ? &signalCallBack : nullptr));
+ notify(bd);
+ return static_cast<QPropertyBinding<T> &>(oldBinding);
+ }
+
+ bool setBinding(const QUntypedPropertyBinding &newBinding)
+ {
+ if (!newBinding.isNull() && newBinding.valueMetaType().id() != qMetaTypeId<T>())
+ return false;
+ setBinding(static_cast<const QPropertyBinding<T> &>(newBinding));
+ return true;
+ }
+
+#ifndef Q_CLANG_QDOC
+ template <typename Functor>
+ QPropertyBinding<T> setBinding(Functor &&f,
+ const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION,
+ std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr)
+ {
+ return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location));
+ }
+#else
+ template <typename Functor>
+ QPropertyBinding<T> setBinding(Functor f);
+#endif
+
+ bool hasBinding() const {
+ auto *bd = qGetBindingStorage(owner())->bindingData(this);
+ return bd && bd->binding() != nullptr;
+ }
+
+ QPropertyBinding<T> binding() const
+ {
+ auto *bd = qGetBindingStorage(owner())->bindingData(this);
+ return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr));
+ }
+
+ QPropertyBinding<T> takeBinding()
+ {
+ return setBinding(QPropertyBinding<T>());
+ }
+
+ 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);
+ }
+
+ const QtPrivate::QPropertyBindingData &bindingData() const
+ {
+ auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
+ return *storage->bindingData(const_cast<ThisType *>(this), true);
+ }
+private:
+ void notify(const QtPrivate::QPropertyBindingData *binding)
+ {
+ if (binding)
+ binding->notifyObservers(this);
+ if constexpr (HasSignal)
+ (owner()->*Signal)();
+ }
+};
+
+#define Q_OBJECT_BINDABLE_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 \
+ } \
+ QObjectBindableProperty<Class, Type, Class::_qt_property_##name##_offset, __VA_ARGS__> name;
+
QT_END_NAMESPACE
#endif // QPROPERTY_H
diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h
index adc347db84..6f0e563a28 100644
--- a/src/corelib/kernel/qpropertyprivate.h
+++ b/src/corelib/kernel/qpropertyprivate.h
@@ -178,6 +178,15 @@ namespace detail {
template<typename T, typename C>
struct ExtractClassFromFunctionPointer<T C::*> { using Class = C; };
+
+ constexpr size_t getOffset(size_t o)
+ {
+ return o;
+ }
+ constexpr size_t getOffset(size_t (*offsetFn)())
+ {
+ return offsetFn();
+ }
}
// type erased guard functions, casts its arguments to the correct types