diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 237 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty.h | 187 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertyprivate.h | 9 |
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 |