diff options
Diffstat (limited to 'src/corelib/kernel/qproperty.h')
-rw-r--r-- | src/corelib/kernel/qproperty.h | 1512 |
1 files changed, 1002 insertions, 510 deletions
diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index de08a71349..0373867a66 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -1,69 +1,96 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QPROPERTY_H #define QPROPERTY_H #include <QtCore/qglobal.h> -#include <QtCore/QSharedDataPointer> -#include <QtCore/QString> -#include <QtCore/qmetatype.h> -#include <functional> +#include <QtCore/qshareddata.h> +#include <QtCore/qstring.h> +#include <QtCore/qbindingstorage.h> + #include <type_traits> -#include <variant> #include <QtCore/qpropertyprivate.h> -#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_CLANG_QDOC) -#include <experimental/source_location> +#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_QDOC) +#include <source_location> +#if defined(__cpp_lib_source_location) +#define QT_SOURCE_LOCATION_NAMESPACE std #define QT_PROPERTY_COLLECT_BINDING_LOCATION -#define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation(std::source_location::current()) -#elif __has_include(<experimental/source_location>) && __cplusplus >= 201703L && !defined(Q_CLANG_QDOC) +#if defined(Q_CC_MSVC) +/* MSVC runs into an issue with constexpr with source location (error C7595) + so use the factory function as a workaround */ +# define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation::fromStdSourceLocation(std::source_location::current()) +#else +/* some versions of gcc in turn run into + expression ‘std::source_location::current()’ is not a constant expression + so don't use the workaround there */ +# define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation(std::source_location::current()) +#endif +#endif +#endif + +#if __has_include(<experimental/source_location>) && !defined(Q_QDOC) #include <experimental/source_location> +#if !defined(QT_PROPERTY_COLLECT_BINDING_LOCATION) +#if defined(__cpp_lib_experimental_source_location) +#define QT_SOURCE_LOCATION_NAMESPACE std::experimental #define QT_PROPERTY_COLLECT_BINDING_LOCATION #define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation(std::experimental::source_location::current()) -#else +#endif // defined(__cpp_lib_experimental_source_location) +#endif +#endif + +#if !defined(QT_PROPERTY_COLLECT_BINDING_LOCATION) #define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation() #endif QT_BEGIN_NAMESPACE +namespace Qt { +Q_CORE_EXPORT void beginPropertyUpdateGroup(); +Q_CORE_EXPORT void endPropertyUpdateGroup(); +} + +class QScopedPropertyUpdateGroup +{ + Q_DISABLE_COPY_MOVE(QScopedPropertyUpdateGroup) +public: + Q_NODISCARD_CTOR + QScopedPropertyUpdateGroup() + { Qt::beginPropertyUpdateGroup(); } + ~QScopedPropertyUpdateGroup() noexcept(false) + { Qt::endPropertyUpdateGroup(); } +}; + +template <typename T> +class QPropertyData : public QUntypedPropertyData +{ +protected: + mutable T val = T(); +private: + class DisableRValueRefs {}; +protected: + static constexpr bool UseReferences = !(std::is_arithmetic_v<T> || std::is_enum_v<T> || std::is_pointer_v<T>); +public: + using value_type = T; + using parameter_type = std::conditional_t<UseReferences, const T &, T>; + using rvalue_ref = typename std::conditional_t<UseReferences, T &&, DisableRValueRefs>; + using arrow_operator_result = std::conditional_t<std::is_pointer_v<T>, const T &, + std::conditional_t<QTypeTraits::is_dereferenceable_v<T>, const T &, void>>; + + QPropertyData() = default; + QPropertyData(parameter_type t) : val(t) {} + QPropertyData(rvalue_ref t) : val(std::move(t)) {} + ~QPropertyData() = default; + + parameter_type valueBypassingBindings() const { return val; } + void setValueBypassingBindings(parameter_type v) { val = v; } + void setValueBypassingBindings(rvalue_ref v) { val = std::move(v); } +}; + +// ### Qt 7: un-export struct Q_CORE_EXPORT QPropertyBindingSourceLocation { const char *fileName = nullptr; @@ -71,8 +98,23 @@ struct Q_CORE_EXPORT QPropertyBindingSourceLocation quint32 line = 0; quint32 column = 0; QPropertyBindingSourceLocation() = default; -#ifdef QT_PROPERTY_COLLECT_BINDING_LOCATION - QPropertyBindingSourceLocation(const std::experimental::source_location &cppLocation) +#ifdef __cpp_lib_source_location + constexpr QPropertyBindingSourceLocation(const std::source_location &cppLocation) + { + fileName = cppLocation.file_name(); + functionName = cppLocation.function_name(); + line = cppLocation.line(); + column = cppLocation.column(); + } + QT_POST_CXX17_API_IN_EXPORTED_CLASS + static consteval QPropertyBindingSourceLocation + fromStdSourceLocation(const std::source_location &cppLocation) + { + return cppLocation; + } +#endif +#ifdef __cpp_lib_experimental_source_location + constexpr QPropertyBindingSourceLocation(const std::experimental::source_location &cppLocation) { fileName = cppLocation.file_name(); functionName = cppLocation.function_name(); @@ -83,10 +125,6 @@ struct Q_CORE_EXPORT QPropertyBindingSourceLocation }; template <typename Functor> class QPropertyChangeHandler; - -template <typename T> class QProperty; -template <typename T, auto callbackMember, auto guardCallback> class QNotifiedProperty; - class QPropertyBindingErrorPrivate; class Q_CORE_EXPORT QPropertyBindingError @@ -99,17 +137,18 @@ public: UnknownError }; - QPropertyBindingError(Type type = NoError); + QPropertyBindingError(); + QPropertyBindingError(Type type, const QString &description = QString()); + QPropertyBindingError(const QPropertyBindingError &other); QPropertyBindingError &operator=(const QPropertyBindingError &other); QPropertyBindingError(QPropertyBindingError &&other); QPropertyBindingError &operator=(QPropertyBindingError &&other); ~QPropertyBindingError(); + bool hasError() const { return d.get() != nullptr; } Type type() const; - void setDescription(const QString &description); QString description() const; - QPropertyBindingSourceLocation location() const; private: QSharedDataPointer<QPropertyBindingErrorPrivate> d; @@ -118,12 +157,17 @@ private: class Q_CORE_EXPORT QUntypedPropertyBinding { public: - using BindingEvaluationResult = QPropertyBindingError; // writes binding result into dataPtr - using BindingEvaluationFunction = std::function<BindingEvaluationResult(const QMetaType &metaType, void *dataPtr)>; + using BindingFunctionVTable = QtPrivate::BindingFunctionVTable; QUntypedPropertyBinding(); - QUntypedPropertyBinding(const QMetaType &metaType, BindingEvaluationFunction function, const QPropertyBindingSourceLocation &location); + QUntypedPropertyBinding(QMetaType metaType, const BindingFunctionVTable *vtable, void *function, const QPropertyBindingSourceLocation &location); + + template<typename Functor> + QUntypedPropertyBinding(QMetaType metaType, Functor &&f, const QPropertyBindingSourceLocation &location) + : QUntypedPropertyBinding(metaType, &QtPrivate::bindingFunctionVTable<std::remove_reference_t<Functor>>, &f, location) + {} + QUntypedPropertyBinding(QUntypedPropertyBinding &&other); QUntypedPropertyBinding(const QUntypedPropertyBinding &other); QUntypedPropertyBinding &operator=(const QUntypedPropertyBinding &other); @@ -138,7 +182,7 @@ public: explicit QUntypedPropertyBinding(QPropertyBindingPrivate *priv); private: - friend class QtPrivate::QPropertyBase; + friend class QtPrivate::QPropertyBindingData; friend class QPropertyBindingPrivate; template <typename> friend class QPropertyBinding; QPropertyBindingPrivatePtr d; @@ -147,42 +191,15 @@ private: template <typename PropertyType> class QPropertyBinding : public QUntypedPropertyBinding { - template <typename Functor> - struct BindingAdaptor - { - Functor impl; - QUntypedPropertyBinding::BindingEvaluationResult operator()(const QMetaType &/*metaType*/, void *dataPtr) - { - std::variant<PropertyType, QPropertyBindingError> result(impl()); - if (auto errorPtr = std::get_if<QPropertyBindingError>(&result)) - return *errorPtr; - - if (auto valuePtr = std::get_if<PropertyType>(&result)) { - PropertyType *propertyPtr = reinterpret_cast<PropertyType *>(dataPtr); - *propertyPtr = std::move(*valuePtr); - return {}; - } - - return {}; - } - }; public: QPropertyBinding() = default; template<typename Functor> QPropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location) - : QUntypedPropertyBinding(QMetaType::fromType<PropertyType>(), BindingAdaptor<Functor>{std::forward<Functor>(f)}, location) - {} - - QPropertyBinding(const QProperty<PropertyType> &property) - : QUntypedPropertyBinding(property.d.priv.binding()) + : QUntypedPropertyBinding(QMetaType::fromType<PropertyType>(), &QtPrivate::bindingFunctionVTable<std::remove_reference_t<Functor>, PropertyType>, &f, location) {} - template<auto notifier, auto guard> - QPropertyBinding(const QNotifiedProperty<PropertyType, notifier, guard> &property) - : QUntypedPropertyBinding(property.d.priv.binding()) - {} // Internal explicit QPropertyBinding(const QUntypedPropertyBinding &binding) @@ -190,51 +207,174 @@ public: {} }; -namespace QtPrivate { - template<typename... Ts> - constexpr auto is_variant_v = false; - template<typename... Ts> - constexpr auto is_variant_v<std::variant<Ts...>> = true; -} - namespace Qt { template <typename Functor> auto makePropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, - std::enable_if_t<std::is_invocable_v<Functor>> * = 0) + std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr) { - if constexpr (QtPrivate::is_variant_v<std::invoke_result_t<Functor>>) { - return QPropertyBinding<std::variant_alternative_t<0, std::invoke_result_t<Functor>>>(std::forward<Functor>(f), location); - } else { - return QPropertyBinding<std::invoke_result_t<Functor>>(std::forward<Functor>(f), location); - } - // Work around bogus warning - Q_UNUSED(QtPrivate::is_variant_v<bool>) + return QPropertyBinding<std::invoke_result_t<Functor>>(std::forward<Functor>(f), location); } } -struct QPropertyBasePointer; +struct QPropertyObserverPrivate; +struct QPropertyObserverPointer; +class QPropertyObserver; + +class QPropertyObserverBase +{ +public: + // Internal + enum ObserverTag { + ObserverNotifiesBinding, // observer was installed to notify bindings that obsverved property changed + ObserverNotifiesChangeHandler, // observer is a change handler, which runs on every change + ObserverIsPlaceholder, // the observer before this one is currently evaluated in QPropertyObserver::notifyObservers. +#if QT_DEPRECATED_SINCE(6, 6) + ObserverIsAlias QT_DEPRECATED_VERSION_X_6_6("Use QProperty and add a binding to the target.") +#endif + }; +protected: + using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *); + +private: + friend struct QPropertyDelayedNotifications; + friend struct QPropertyObserverNodeProtector; + friend class QPropertyObserver; + friend struct QPropertyObserverPointer; + friend struct QPropertyBindingDataPointer; + friend class QPropertyBindingPrivate; + + QTaggedPointer<QPropertyObserver, ObserverTag> next; + // prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the + // first node. + QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev; + + union { + QPropertyBindingPrivate *binding = nullptr; + ChangeHandler changeHandler; + QUntypedPropertyData *aliasData; + }; +}; + +class Q_CORE_EXPORT QPropertyObserver : public QPropertyObserverBase +{ +public: + constexpr QPropertyObserver() = default; + QPropertyObserver(QPropertyObserver &&other) noexcept; + QPropertyObserver &operator=(QPropertyObserver &&other) noexcept; + ~QPropertyObserver(); + + template <typename Property, QtPrivate::IsUntypedPropertyData<Property> = true> + void setSource(const Property &property) + { setSource(property.bindingData()); } + void setSource(const QtPrivate::QPropertyBindingData &property); + +protected: + QPropertyObserver(ChangeHandler changeHandler); +#if QT_DEPRECATED_SINCE(6, 6) + QT_DEPRECATED_VERSION_X_6_6("This constructor was only meant for internal use. Use QProperty and add a binding to the target.") + QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr); +#endif + + QUntypedPropertyData *aliasedProperty() const + { + return aliasData; + } + +private: + + QPropertyObserver(const QPropertyObserver &) = delete; + QPropertyObserver &operator=(const QPropertyObserver &) = delete; + +}; + +template <typename Functor> +class QPropertyChangeHandler : public QPropertyObserver +{ + Functor m_handler; +public: + Q_NODISCARD_CTOR + QPropertyChangeHandler(Functor handler) + : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); + This->m_handler(); + }) + , m_handler(handler) + { + } + + template <typename Property, QtPrivate::IsUntypedPropertyData<Property> = true> + Q_NODISCARD_CTOR + QPropertyChangeHandler(const Property &property, Functor handler) + : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); + This->m_handler(); + }) + , m_handler(handler) + { + setSource(property); + } +}; + +class QPropertyNotifier : public QPropertyObserver +{ + std::function<void()> m_handler; +public: + Q_NODISCARD_CTOR + QPropertyNotifier() = default; + template<typename Functor> + Q_NODISCARD_CTOR + QPropertyNotifier(Functor handler) + : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast<QPropertyNotifier *>(self); + This->m_handler(); + }) + , m_handler(handler) + { + } + + template <typename Functor, typename Property, + QtPrivate::IsUntypedPropertyData<Property> = true> + Q_NODISCARD_CTOR + QPropertyNotifier(const Property &property, Functor handler) + : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast<QPropertyNotifier *>(self); + This->m_handler(); + }) + , m_handler(handler) + { + setSource(property); + } +}; template <typename T> -class QProperty +class QProperty : public QPropertyData<T> { + QtPrivate::QPropertyBindingData d; + bool is_equal(const T &v) + { + if constexpr (QTypeTraits::has_operator_equal_v<T>) { + if (v == this->val) + return true; + } + return false; + } + public: - using value_type = T; + 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; QProperty() = default; - explicit QProperty(const T &initialValue) : d(initialValue) {} - explicit QProperty(T &&initialValue) : d(std::move(initialValue)) {} - QProperty(QProperty &&other) : d(std::move(other.d)) { notify(); } - QProperty &operator=(QProperty &&other) { d = std::move(other.d); notify(); return *this; } - QProperty(const QPropertyBinding<T> &binding) + explicit QProperty(parameter_type initialValue) : QPropertyData<T>(initialValue) {} + explicit QProperty(rvalue_ref initialValue) : QPropertyData<T>(std::move(initialValue)) {} + explicit QProperty(const QPropertyBinding<T> &binding) : QProperty() - { operator=(binding); } - QProperty(QPropertyBinding<T> &&binding) - : QProperty() - { operator=(std::move(binding)); } -#ifndef Q_CLANG_QDOC + { setBinding(binding); } +#ifndef Q_QDOC template <typename Functor> explicit QProperty(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, - typename std::enable_if_t<std::is_invocable_r_v<T, Functor&>> * = 0) + typename std::enable_if_t<std::is_invocable_r_v<T, Functor&>> * = nullptr) : QProperty(QPropertyBinding<T>(std::forward<Functor>(f), location)) {} #else @@ -243,82 +383,78 @@ public: #endif ~QProperty() = default; - T value() const + parameter_type value() const { - if (d.priv.hasBinding()) - d.priv.evaluateIfDirty(); - d.priv.registerWithCurrentlyEvaluatingBinding(); - return d.getValue(); + d.registerWithCurrentlyEvaluatingBinding(); + return this->val; } - operator T() const + arrow_operator_result operator->() const { - return value(); + if constexpr (QTypeTraits::is_dereferenceable_v<T>) { + return value(); + } else if constexpr (std::is_pointer_v<T>) { + value(); + return this->val; + } else { + return; + } } - void setValue(T &&newValue) + parameter_type operator*() const { - d.priv.removeBinding(); - if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) - notify(); + return value(); } - void setValue(const T &newValue) + operator parameter_type() const { - d.priv.removeBinding(); - if (d.setValueAndReturnTrueIfChanged(newValue)) - notify(); + return value(); } - QProperty<T> &operator=(T &&newValue) + void setValue(rvalue_ref newValue) { - setValue(std::move(newValue)); - return *this; + d.removeBinding(); + if (is_equal(newValue)) + return; + this->val = std::move(newValue); + notify(); } - QProperty<T> &operator=(const T &newValue) + void setValue(parameter_type newValue) { - setValue(newValue); - return *this; + d.removeBinding(); + if (is_equal(newValue)) + return; + this->val = newValue; + notify(); } - QProperty<T> &operator=(const QPropertyBinding<T> &newBinding) + QProperty<T> &operator=(rvalue_ref newValue) { - setBinding(newBinding); + setValue(std::move(newValue)); return *this; } - QProperty<T> &operator=(QPropertyBinding<T> &&newBinding) + QProperty<T> &operator=(parameter_type newValue) { - setBinding(std::move(newBinding)); + setValue(newValue); return *this; } QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) { - QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d)); - notify(); - return oldBinding; - } - - QPropertyBinding<T> setBinding(QPropertyBinding<T> &&newBinding) - { - QPropertyBinding<T> b(std::move(newBinding)); - QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d)); - notify(); - return oldBinding; + return QPropertyBinding<T>(d.setBinding(newBinding, this)); } bool setBinding(const QUntypedPropertyBinding &newBinding) { - if (newBinding.valueMetaType().id() != qMetaTypeId<T>()) + if (!newBinding.isNull() && newBinding.valueMetaType().id() != qMetaTypeId<T>()) return false; - d.priv.setBinding(newBinding, &d); - notify(); + setBinding(static_cast<const QPropertyBinding<T> &>(newBinding)); return true; } -#ifndef Q_CLANG_QDOC +#ifndef Q_QDOC template <typename Functor> QPropertyBinding<T> setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, @@ -331,37 +467,48 @@ public: QPropertyBinding<T> setBinding(Functor f); #endif - bool hasBinding() const { return d.priv.hasBinding(); } + bool hasBinding() const { return d.hasBinding(); } QPropertyBinding<T> binding() const { - return QPropertyBinding<T>(*this); + return QPropertyBinding<T>(QUntypedPropertyBinding(d.binding())); } QPropertyBinding<T> takeBinding() { - return QPropertyBinding<T>(d.priv.setBinding(QUntypedPropertyBinding(), &d)); + return QPropertyBinding<T>(d.setBinding(QUntypedPropertyBinding(), this)); } template<typename Functor> - QPropertyChangeHandler<Functor> onValueChanged(Functor f); + 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); + } + template<typename Functor> - QPropertyChangeHandler<Functor> subscribe(Functor f); + QPropertyNotifier addNotifier(Functor f) + { + static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, f); + } + const QtPrivate::QPropertyBindingData &bindingData() const { return d; } private: void notify() { - d.priv.notifyObservers(&d); + d.notifyObservers(this); } - Q_DISABLE_COPY(QProperty) - - friend struct QPropertyBasePointer; - 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; + Q_DISABLE_COPY_MOVE(QProperty) }; namespace Qt { @@ -374,448 +521,635 @@ namespace Qt { } } -namespace detail { - template <typename F> - struct ExtractClassFromFunctionPointer; - template<typename T, typename C> - struct ExtractClassFromFunctionPointer<T C::*> { using Class = C; }; -} +namespace QtPrivate +{ -template <typename T, auto Callback, auto ValueGuard=nullptr> -class QNotifiedProperty +struct QBindableInterface { + using Getter = void (*)(const QUntypedPropertyData *d, void *value); + using Setter = void (*)(QUntypedPropertyData *d, const void *value); + using BindingGetter = QUntypedPropertyBinding (*)(const QUntypedPropertyData *d); + using BindingSetter = QUntypedPropertyBinding (*)(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding); + using MakeBinding = QUntypedPropertyBinding (*)(const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location); + using SetObserver = void (*)(const QUntypedPropertyData *d, QPropertyObserver *observer); + using GetMetaType = QMetaType (*)(); + Getter getter; + Setter setter; + BindingGetter getBinding; + BindingSetter setBinding; + MakeBinding makeBinding; + SetObserver setObserver; + GetMetaType metaType; + + static constexpr quintptr MetaTypeAccessorFlag = 0x1; +}; + +template<typename Property, typename = void> +class QBindableInterfaceForProperty +{ + using T = typename Property::value_type; public: - using value_type = T; - using Class = typename detail::ExtractClassFromFunctionPointer<decltype(Callback)>::Class; -private: - static bool constexpr ValueGuardModifiesArgument = std::is_invocable_r_v<bool, decltype(ValueGuard), Class, T&>; - static bool constexpr CallbackAcceptsOldValue = std::is_invocable_v<decltype(Callback), Class, T>; - static bool constexpr HasValueGuard = !std::is_same_v<decltype(ValueGuard), std::nullptr_t>; + // interface for computed properties. Those do not have a binding()/setBinding() method, but one can + // install observers on them. + static constexpr QBindableInterface iface = { + [](const QUntypedPropertyData *d, void *value) -> void + { *static_cast<T*>(value) = static_cast<const Property *>(d)->value(); }, + nullptr, + nullptr, + nullptr, + [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding + { return Qt::makePropertyBinding([d]() -> T { return static_cast<const Property *>(d)->value(); }, location); }, + [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void + { observer->setSource(static_cast<const Property *>(d)->bindingData()); }, + []() { return QMetaType::fromType<T>(); } + }; +}; + +template<typename Property> +class QBindableInterfaceForProperty<const Property, std::void_t<decltype(std::declval<Property>().binding())>> +{ + using T = typename Property::value_type; public: - static_assert(CallbackAcceptsOldValue || std::is_invocable_v<decltype(Callback), Class>); - static_assert( - std::is_invocable_r_v<bool, decltype(ValueGuard), Class, T> || - ValueGuardModifiesArgument || - !HasValueGuard, - "Guard has wrong signature"); -private: - // type erased guard functions, casts its arguments to the correct types - static constexpr bool (*GuardTEHelper)(void *, void*) = [](void *o, void *newValue){ - if constexpr (HasValueGuard) { // Guard->* is invalid if Guard == nullptr - return (reinterpret_cast<Class *>(o)->*(ValueGuard))(*static_cast<T *>(newValue)); - } else { - Q_UNUSED(o); // some compilers complain about unused variables - Q_UNUSED(newValue); - return true; - } + // A bindable created from a const property results in a read-only interface, too. + static constexpr QBindableInterface iface = { + + [](const QUntypedPropertyData *d, void *value) -> void + { *static_cast<T*>(value) = static_cast<const Property *>(d)->value(); }, + /*setter=*/nullptr, + [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding + { return static_cast<const Property *>(d)->binding(); }, + /*setBinding=*/nullptr, + [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding + { return Qt::makePropertyBinding([d]() -> T { return static_cast<const Property *>(d)->value(); }, location); }, + [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void + { observer->setSource(static_cast<const Property *>(d)->bindingData()); }, + []() { return QMetaType::fromType<T>(); } }; - static constexpr bool(*GuardTE)(void *, void*) = HasValueGuard ? GuardTEHelper : nullptr; +}; + +template<typename Property> +class QBindableInterfaceForProperty<Property, std::void_t<decltype(std::declval<Property>().binding())>> +{ + using T = typename Property::value_type; public: + static constexpr QBindableInterface iface = { + [](const QUntypedPropertyData *d, void *value) -> void + { *static_cast<T*>(value) = static_cast<const Property *>(d)->value(); }, + [](QUntypedPropertyData *d, const void *value) -> void + { static_cast<Property *>(d)->setValue(*static_cast<const T*>(value)); }, + [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding + { return static_cast<const Property *>(d)->binding(); }, + [](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding + { return static_cast<Property *>(d)->setBinding(static_cast<const QPropertyBinding<T> &>(binding)); }, + [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding + { return Qt::makePropertyBinding([d]() -> T { return static_cast<const Property *>(d)->value(); }, location); }, + [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void + { observer->setSource(static_cast<const Property *>(d)->bindingData()); }, + []() { return QMetaType::fromType<T>(); } + }; +}; - QNotifiedProperty() = default; +} - explicit QNotifiedProperty(const T &initialValue) : d(initialValue) {} - explicit QNotifiedProperty(T &&initialValue) : d(std::move(initialValue)) {} +namespace QtPrivate { +// used in Q(Untyped)Bindable to print warnings about various binding errors +namespace BindableWarnings { +enum Reason { InvalidInterface, NonBindableInterface, ReadOnlyInterface }; +Q_CORE_EXPORT void printUnsuitableBindableWarning(QAnyStringView prefix, Reason reason); +Q_CORE_EXPORT void printMetaTypeMismatch(QMetaType actual, QMetaType expected); +} - QNotifiedProperty(Class *owner, const QPropertyBinding<T> &binding) - : QNotifiedProperty() - { setBinding(owner, binding); } - QNotifiedProperty(Class *owner, QPropertyBinding<T> &&binding) - : QNotifiedProperty() - { setBinding(owner, std::move(binding)); } +namespace PropertyAdaptorSlotObjectHelpers { +Q_CORE_EXPORT void getter(const QUntypedPropertyData *d, void *value); +Q_CORE_EXPORT void setter(QUntypedPropertyData *d, const void *value); +Q_CORE_EXPORT QUntypedPropertyBinding getBinding(const QUntypedPropertyData *d); +Q_CORE_EXPORT bool bindingWrapper(QMetaType type, QUntypedPropertyData *d, + QtPrivate::QPropertyBindingFunction binding, + QUntypedPropertyData *temp, void *value); +Q_CORE_EXPORT QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, + const QUntypedPropertyBinding &binding, + QPropertyBindingWrapper wrapper); +Q_CORE_EXPORT void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer); -#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)) +template<typename T> +bool bindingWrapper(QMetaType type, QUntypedPropertyData *d, + QtPrivate::QPropertyBindingFunction binding) +{ + struct Data : QPropertyData<T> + { + void *data() { return &this->val; } + } temp; + return bindingWrapper(type, d, binding, &temp, temp.data()); +} + +template<typename T> +QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) +{ + return setBinding(d, binding, &bindingWrapper<T>); +} + +template<typename T> +QUntypedPropertyBinding makeBinding(const QUntypedPropertyData *d, + const QPropertyBindingSourceLocation &location) +{ + return Qt::makePropertyBinding( + [d]() -> T { + T r; + getter(d, &r); + return r; + }, + location); +} + +template<class T> +inline constexpr QBindableInterface iface = { + &getter, + &setter, + &getBinding, + &setBinding<T>, + &makeBinding<T>, + &setObserver, + &QMetaType::fromType<T>, +}; +} +} + +class QUntypedBindable +{ + friend struct QUntypedBindablePrivate; // allows access to internal data +protected: + QUntypedPropertyData *data = nullptr; + const QtPrivate::QBindableInterface *iface = nullptr; + constexpr QUntypedBindable(QUntypedPropertyData *d, const QtPrivate::QBindableInterface *i) + : data(d), iface(i) {} -#else - template <typename Functor> - explicit QProperty(Class *owner, Functor &&f); -#endif - ~QNotifiedProperty() = default; + Q_CORE_EXPORT explicit QUntypedBindable(QObject* obj, const QMetaProperty &property, const QtPrivate::QBindableInterface *i); + Q_CORE_EXPORT explicit QUntypedBindable(QObject* obj, const char* property, const QtPrivate::QBindableInterface *i); - T value() const +public: + constexpr QUntypedBindable() = default; + template<typename Property> + QUntypedBindable(Property *p) + : data(const_cast<std::remove_cv_t<Property> *>(p)), + iface(&QtPrivate::QBindableInterfaceForProperty<Property>::iface) + { Q_ASSERT(data && iface); } + + bool isValid() const { return data != nullptr; } + bool isBindable() const { return iface && iface->getBinding; } + bool isReadOnly() const { return !(iface && iface->setBinding && iface->setObserver); } + + QUntypedPropertyBinding makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) const { - if (d.priv.hasBinding()) - d.priv.evaluateIfDirty(); - d.priv.registerWithCurrentlyEvaluatingBinding(); - return d.getValue(); + return iface ? iface->makeBinding(data, location) : QUntypedPropertyBinding(); } - operator T() const + QUntypedPropertyBinding takeBinding() { - return value(); + if (!iface) + return QUntypedPropertyBinding {}; + // We do not have a dedicated takeBinding function pointer in the interface + // therefore we synthesize takeBinding by retrieving the binding with binding + // and calling setBinding with a default constructed QUntypedPropertyBinding + // afterwards. + if (!(iface->getBinding && iface->setBinding)) + return QUntypedPropertyBinding {}; + QUntypedPropertyBinding binding = iface->getBinding(data); + iface->setBinding(data, QUntypedPropertyBinding{}); + return binding; } - template<typename S> - auto setValue(Class *owner, S &&newValue) -> std::enable_if_t<!ValueGuardModifiesArgument && std::is_same_v<S, T>, void> + void observe(QPropertyObserver *observer) const { - if constexpr (HasValueGuard) { - if (!(owner->*ValueGuard)(newValue)) - return; - } - if constexpr (CallbackAcceptsOldValue) { - T oldValue = value(); // TODO: kind of pointless if there was no change - if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) - notify(owner, &oldValue); - } else { - if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) - notify(owner); - } - d.priv.removeBinding(); + if (iface) + iface->setObserver(data, observer); +#ifndef QT_NO_DEBUG + else + QtPrivate::BindableWarnings::printUnsuitableBindableWarning("observe:", + QtPrivate::BindableWarnings::InvalidInterface); +#endif } - void setValue(Class *owner, std::conditional_t<ValueGuardModifiesArgument, T, const T &> newValue) + template<typename Functor> + QPropertyChangeHandler<Functor> onValueChanged(Functor f) const { - if constexpr (HasValueGuard) { - if (!(owner->*ValueGuard)(newValue)) - return; - } - if constexpr (CallbackAcceptsOldValue) { - // When newValue is T, we move it, if it's const T& it stays const T& and won't get moved - T oldValue = value(); - if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) - notify(owner, &oldValue); - } else { - if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) - notify(owner); - } - d.priv.removeBinding(); + QPropertyChangeHandler<Functor> handler(f); + observe(&handler); + return handler; } - QPropertyBinding<T> setBinding(Class *owner, const QPropertyBinding<T> &newBinding) + template<typename Functor> + QPropertyChangeHandler<Functor> subscribe(Functor f) const { - if constexpr (CallbackAcceptsOldValue) { - T oldValue = value(); - QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o, void *oldVal) { - (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldVal)); - }, GuardTE)); - notify(owner, &oldValue); - return oldBinding; - } else { - QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o, void *) { - (reinterpret_cast<Class *>(o)->*Callback)(); - }, GuardTE)); - notify(owner); - return oldBinding; - } + f(); + return onValueChanged(f); } - QPropertyBinding<T> setBinding(Class *owner, QPropertyBinding<T> &&newBinding) + template<typename Functor> + QPropertyNotifier addNotifier(Functor f) { - QPropertyBinding<T> b(std::move(newBinding)); - if constexpr (CallbackAcceptsOldValue) { - T oldValue = value(); - QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d, owner, [](void *o, void *oldVal) { - (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldVal)); - }, GuardTE)); - notify(owner, &oldValue); - return oldBinding; - } else { - QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d, owner, [](void *o, void *) { - (reinterpret_cast<Class *>(o)->*Callback)(); - }, GuardTE)); - notify(owner); - return oldBinding; - } + QPropertyNotifier handler(f); + observe(&handler); + return handler; } - bool setBinding(Class *owner, const QUntypedPropertyBinding &newBinding) + QUntypedPropertyBinding binding() const + { + if (!isBindable()) { +#ifndef QT_NO_DEBUG + QtPrivate::BindableWarnings::printUnsuitableBindableWarning("binding: ", + QtPrivate::BindableWarnings::NonBindableInterface); +#endif + return QUntypedPropertyBinding(); + } + return iface->getBinding(data); + } + bool setBinding(const QUntypedPropertyBinding &binding) { - if (newBinding.valueMetaType().id() != qMetaTypeId<T>()) + if (isReadOnly()) { +#ifndef QT_NO_DEBUG + const auto errorType = iface ? QtPrivate::BindableWarnings::ReadOnlyInterface : + QtPrivate::BindableWarnings::InvalidInterface; + QtPrivate::BindableWarnings::printUnsuitableBindableWarning("setBinding: Could not set binding via bindable interface.", errorType); +#endif + return false; + } + if (!binding.isNull() && binding.valueMetaType() != metaType()) { +#ifndef QT_NO_DEBUG + QtPrivate::BindableWarnings::printMetaTypeMismatch(metaType(), binding.valueMetaType()); +#endif return false; - if constexpr (CallbackAcceptsOldValue) { - T oldValue = value(); - d.priv.setBinding(newBinding, &d, owner, [](void *o, void *oldVal) { - (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldVal)); - }, GuardTE); - notify(owner, &oldValue); - } else { - d.priv.setBinding(newBinding, &d, owner, [](void *o, void *) { - (reinterpret_cast<Class *>(o)->*Callback)(); - }, GuardTE); - notify(owner); } + iface->setBinding(data, binding); return true; } + bool hasBinding() const + { + return !binding().isNull(); + } -#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) + QMetaType metaType() const { - return setBinding(owner, Qt::makePropertyBinding(std::forward<Functor>(f), location)); + if (!(iface && data)) + return QMetaType(); + if (iface->metaType) + return iface->metaType(); + // ### Qt 7: Change the metatype function to take data as its argument + // special casing for QML's proxy bindable: allow multiplexing in the getter + // function to retrieve the metatype from data + Q_ASSERT(iface->getter); + QMetaType result; + iface->getter(data, reinterpret_cast<void *>(quintptr(&result) | QtPrivate::QBindableInterface::MetaTypeAccessorFlag)); + return result; } -#else - template <typename Functor> - QPropertyBinding<T> setBinding(Class *owner, Functor f); -#endif - bool hasBinding() const { return d.priv.hasBinding(); } +}; + +template<typename T> +class QBindable : public QUntypedBindable +{ + template<typename U> + friend class QPropertyAlias; + constexpr QBindable(QUntypedPropertyData *d, const QtPrivate::QBindableInterface *i) + : QUntypedBindable(d, i) + {} +public: + using QUntypedBindable::QUntypedBindable; + explicit QBindable(const QUntypedBindable &b) : QUntypedBindable(b) + { + if (iface && metaType() != QMetaType::fromType<T>()) { + data = nullptr; + iface = nullptr; + } + } + + explicit QBindable(QObject *obj, const QMetaProperty &property) + : QUntypedBindable(obj, property, &QtPrivate::PropertyAdaptorSlotObjectHelpers::iface<T>) {} + + explicit QBindable(QObject *obj, const char *property) + : QUntypedBindable(obj, property, &QtPrivate::PropertyAdaptorSlotObjectHelpers::iface<T>) {} + QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) const + { + return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::makeBinding(location)); + } QPropertyBinding<T> binding() const { - return QPropertyBinding<T>(*this); + return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::binding()); } QPropertyBinding<T> takeBinding() { - return QPropertyBinding<T>(d.priv.setBinding(QUntypedPropertyBinding(), &d)); + return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::takeBinding()); } - template<typename Functor> - QPropertyChangeHandler<Functor> onValueChanged(Functor f); - template<typename Functor> - QPropertyChangeHandler<Functor> subscribe(Functor f); + using QUntypedBindable::setBinding; + QPropertyBinding<T> setBinding(const QPropertyBinding<T> &binding) + { + Q_ASSERT(!iface || binding.isNull() || binding.valueMetaType() == metaType()); + + if (iface && iface->setBinding) + return static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding)); +#ifndef QT_NO_DEBUG + if (!iface) + QtPrivate::BindableWarnings::printUnsuitableBindableWarning("setBinding", QtPrivate::BindableWarnings::InvalidInterface); + else + QtPrivate::BindableWarnings::printUnsuitableBindableWarning("setBinding: Could not set binding via bindable interface.", QtPrivate::BindableWarnings::ReadOnlyInterface); +#endif + return QPropertyBinding<T>(); + } +#ifndef Q_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 -private: - void notify(Class *owner, T *oldValue=nullptr) + T value() const { - d.priv.notifyObservers(&d); - if constexpr (std::is_invocable_v<decltype(Callback), Class>) { - Q_UNUSED(oldValue); - (owner->*Callback)(); - } else { - (owner->*Callback)(*oldValue); + if (iface) { + T result; + iface->getter(data, &result); + return result; } + return T{}; } - 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; + void setValue(const T &value) + { + if (iface && iface->setter) + iface->setter(data, &value); + } }; -struct QPropertyObserverPrivate; -struct QPropertyObserverPointer; - -class Q_CORE_EXPORT QPropertyObserver +#if QT_DEPRECATED_SINCE(6, 6) +template<typename T> +class QT_DEPRECATED_VERSION_X_6_6("Class was only meant for internal use, use a QProperty and add a binding to the target") +QPropertyAlias : public QPropertyObserver { -public: - // Internal - enum ObserverTag { - ObserverNotifiesBinding, - ObserverNotifiesChangeHandler, - ObserverNotifiesAlias, - }; + Q_DISABLE_COPY_MOVE(QPropertyAlias) + const QtPrivate::QBindableInterface *iface = nullptr; - QPropertyObserver(); - QPropertyObserver(QPropertyObserver &&other); - QPropertyObserver &operator=(QPropertyObserver &&other); - ~QPropertyObserver(); +public: + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + QPropertyAlias(QProperty<T> *property) + : QPropertyObserver(property), + iface(&QtPrivate::QBindableInterfaceForProperty<QProperty<T>>::iface) + { + if (iface) + iface->setObserver(aliasedProperty(), this); + } - template <typename PropertyType> - void setSource(const QProperty<PropertyType> &property) - { setSource(property.d.priv); } + template <typename Property, QtPrivate::IsUntypedPropertyData<Property> = true> + QPropertyAlias(Property *property) + : QPropertyObserver(property), + iface(&QtPrivate::QBindableInterfaceForProperty<Property>::iface) + { + if (iface) + iface->setObserver(aliasedProperty(), this); + } - template <typename PropertyType, auto notifier, auto guard> - void setSource(const QNotifiedProperty<PropertyType, notifier, guard> &property) - { setSource(property.d.priv); } + QPropertyAlias(QPropertyAlias<T> *alias) + : QPropertyObserver(alias->aliasedProperty()), + iface(alias->iface) + { + if (iface) + iface->setObserver(aliasedProperty(), this); + } -protected: - QPropertyObserver(void (*callback)(QPropertyObserver*, void *)); - QPropertyObserver(void *aliasedPropertyPtr); + QPropertyAlias(const QBindable<T> &property) + : QPropertyObserver(property.data), + iface(property.iface) + { + if (iface) + iface->setObserver(aliasedProperty(), this); + } - template<typename PropertyType> - QProperty<PropertyType> *aliasedProperty() const + T value() const { - return reinterpret_cast<QProperty<PropertyType> *>(aliasedPropertyPtr); + T t = T(); + if (auto *p = aliasedProperty()) + iface->getter(p, &t); + return t; } -private: - void setSource(QtPrivate::QPropertyBase &property); + operator T() const { return value(); } - QTaggedPointer<QPropertyObserver, ObserverTag> next; - // prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the - // first node. - QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev; + void setValue(const T &newValue) + { + if (auto *p = aliasedProperty()) + iface->setter(p, &newValue); + } - union { - QPropertyBindingPrivate *bindingToMarkDirty = nullptr; - void (*changeHandler)(QPropertyObserver*, void *); - quintptr aliasedPropertyPtr; - }; + QPropertyAlias<T> &operator=(const T &newValue) + { + setValue(newValue); + return *this; + } - QPropertyObserver(const QPropertyObserver &) = delete; - QPropertyObserver &operator=(const QPropertyObserver &) = delete; + QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) + { + return QBindable<T>(aliasedProperty(), iface).setBinding(newBinding); + } - friend struct QPropertyObserverPointer; - friend struct QPropertyBasePointer; - friend class QPropertyBindingPrivate; -}; + bool setBinding(const QUntypedPropertyBinding &newBinding) + { + return QBindable<T>(aliasedProperty(), iface).setBinding(newBinding); + } -template <typename Functor> -class QPropertyChangeHandler : public QPropertyObserver -{ - Functor m_handler; -public: - QPropertyChangeHandler(Functor handler) - : QPropertyObserver([](QPropertyObserver *self, void *) { - auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); - This->m_handler(); - }) - , m_handler(handler) +#ifndef Q_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 - template <typename PropertyType> - QPropertyChangeHandler(const QProperty<PropertyType> &property, Functor handler) - : QPropertyObserver([](QPropertyObserver *self, void *) { - auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); - This->m_handler(); - }) - , m_handler(handler) + bool hasBinding() const { - setSource(property); + return QBindable<T>(aliasedProperty(), iface).hasBinding(); } - template <typename PropertyType, auto Callback, auto Guard> - QPropertyChangeHandler(const QNotifiedProperty<PropertyType, Callback, Guard> &property, Functor handler) - : QPropertyObserver([](QPropertyObserver *self, void *) { - auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); - This->m_handler(); - }) - , m_handler(handler) + QPropertyBinding<T> binding() const { - setSource(property); + return QBindable<T>(aliasedProperty(), iface).binding(); } -}; -template <typename T> -template<typename Functor> -QPropertyChangeHandler<Functor> QProperty<T>::onValueChanged(Functor f) -{ -#if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) - static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); -#endif - return QPropertyChangeHandler<Functor>(*this, f); -} + QPropertyBinding<T> takeBinding() + { + return QBindable<T>(aliasedProperty(), iface).takeBinding(); + } -template <typename T> -template<typename Functor> -QPropertyChangeHandler<Functor> QProperty<T>::subscribe(Functor f) -{ -#if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) - static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); -#endif - f(); - return onValueChanged(f); -} + template<typename Functor> + QPropertyChangeHandler<Functor> onValueChanged(Functor f) + { + return QBindable<T>(aliasedProperty(), iface).onValueChanged(f); + } -template <typename T, auto Callback, auto ValueGuard> -template<typename Functor> -QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback, ValueGuard>::onValueChanged(Functor f) -{ -#if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) - static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); -#endif - return QPropertyChangeHandler<Functor>(*this, f); -} + template<typename Functor> + QPropertyChangeHandler<Functor> subscribe(Functor f) + { + return QBindable<T>(aliasedProperty(), iface).subscribe(f); + } -template <typename T, auto Callback, auto ValueGuard> -template<typename Functor> -QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback, ValueGuard>::subscribe(Functor f) -{ -#if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) - static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); -#endif - f(); - return onValueChanged(f); -} + template<typename Functor> + QPropertyNotifier addNotifier(Functor f) + { + return QBindable<T>(aliasedProperty(), iface).addNotifier(f); + } -template<typename T> -class QPropertyAlias : public QPropertyObserver -{ - Q_DISABLE_COPY_MOVE(QPropertyAlias) -public: - QPropertyAlias(QProperty<T> *property) - : QPropertyObserver(property) + bool isValid() const { - if (property) - setSource(*property); + return aliasedProperty() != nullptr; } + QT_WARNING_POP +}; +#endif // QT_DEPRECATED_SINCE(6, 6) - QPropertyAlias(QPropertyAlias<T> *alias) - : QPropertyAlias(alias->aliasedProperty<T>()) +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>; + using SignalTakesValue = std::is_invocable<decltype(Signal), Class, 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) { + if constexpr (SignalTakesValue::value) + (that->owner()->*Signal)(that->valueBypassingBindings()); + else + (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_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&>> * = nullptr) + : QObjectBindableProperty(QPropertyBinding<T>(std::forward<Functor>(f), location)) {} +#else + template <typename Functor> + explicit QObjectBindableProperty(Functor &&f); +#endif - T value() const + parameter_type value() const { - if (auto *p = aliasedProperty<T>()) - return p->value(); - return T(); + qGetBindingStorage(owner())->registerDependency(this); + return this->val; } - operator T() const { return value(); } - - void setValue(T &&newValue) + arrow_operator_result operator->() const { - if (auto *p = aliasedProperty<T>()) - p->setValue(std::move(newValue)); + if constexpr (QTypeTraits::is_dereferenceable_v<T>) { + return value(); + } else if constexpr (std::is_pointer_v<T>) { + value(); + return this->val; + } else { + return; + } } - void setValue(const T &newValue) + parameter_type operator*() const { - if (auto *p = aliasedProperty<T>()) - p->setValue(newValue); + return value(); } - QPropertyAlias<T> &operator=(T &&newValue) + operator parameter_type() const { - if (auto *p = aliasedProperty<T>()) - *p = std::move(newValue); - return *this; + return value(); } - QPropertyAlias<T> &operator=(const T &newValue) + void setValue(parameter_type t) { - if (auto *p = aliasedProperty<T>()) - *p = newValue; - return *this; + auto *bd = qGetBindingStorage(owner())->bindingData(this); + if (bd) + bd->removeBinding(); + if (this->val == t) + return; + this->val = t; + notify(bd); + } + + void notify() { + auto *bd = qGetBindingStorage(owner())->bindingData(this); + notify(bd); } - QPropertyAlias<T> &operator=(const QPropertyBinding<T> &newBinding) + void setValue(rvalue_ref t) { - setBinding(newBinding); - return *this; + auto *bd = qGetBindingStorage(owner())->bindingData(this); + if (bd) + bd->removeBinding(); + if (this->val == t) + return; + this->val = std::move(t); + notify(bd); } - QPropertyAlias<T> &operator=(QPropertyBinding<T> &&newBinding) + QObjectBindableProperty &operator=(rvalue_ref newValue) { - setBinding(std::move(newBinding)); + setValue(std::move(newValue)); return *this; } - QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) + QObjectBindableProperty &operator=(parameter_type newValue) { - if (auto *p = aliasedProperty<T>()) - return p->setBinding(newBinding); - return QPropertyBinding<T>(); + setValue(newValue); + return *this; } - QPropertyBinding<T> setBinding(QPropertyBinding<T> &&newBinding) + QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) { - if (auto *p = aliasedProperty<T>()) - return p->setBinding(std::move(newBinding)); - return QPropertyBinding<T>(); + QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true); + QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, HasSignal ? &signalCallBack : nullptr)); + return static_cast<QPropertyBinding<T> &>(oldBinding); } bool setBinding(const QUntypedPropertyBinding &newBinding) { - if (auto *p = aliasedProperty<T>()) - return p->setBinding(newBinding); - return false; + if (!newBinding.isNull() && newBinding.valueMetaType().id() != qMetaTypeId<T>()) + return false; + setBinding(static_cast<const QPropertyBinding<T> &>(newBinding)); + return true; } -#ifndef Q_CLANG_QDOC +#ifndef Q_QDOC template <typename Functor> QPropertyBinding<T> setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, @@ -830,46 +1164,204 @@ public: bool hasBinding() const { - if (auto *p = aliasedProperty<T>()) - return p->hasBinding(); - return false; + auto *bd = qGetBindingStorage(owner())->bindingData(this); + return bd && bd->binding() != nullptr; } QPropertyBinding<T> binding() const { - if (auto *p = aliasedProperty<T>()) - return p->binding(); - return QPropertyBinding<T>(); + auto *bd = qGetBindingStorage(owner())->bindingData(this); + return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr)); } QPropertyBinding<T> takeBinding() { - if (auto *p = aliasedProperty<T>()) - return p->takeBinding(); - return QPropertyBinding<T>(); + return setBinding(QPropertyBinding<T>()); } template<typename Functor> QPropertyChangeHandler<Functor> onValueChanged(Functor f) { - if (auto *p = aliasedProperty<T>()) - return p->onValueChanged(f); - return QPropertyChangeHandler<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) { - if (auto *p = aliasedProperty<T>()) - return p->subscribe(f); - return QPropertyChangeHandler<Functor>(f); + static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); + f(); + return onValueChanged(f); } - bool isValid() const + template<typename Functor> + QPropertyNotifier addNotifier(Functor f) { - return aliasedProperty<T>() != nullptr; + static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, 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, qGetBindingStorage(owner())); + if constexpr (HasSignal) { + if constexpr (SignalTakesValue::value) + (owner()->*Signal)(this->valueBypassingBindings()); + else + (owner()->*Signal)(); + } } }; + +#define QT_OBJECT_BINDABLE_PROPERTY_3(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, nullptr> name; + +#define QT_OBJECT_BINDABLE_PROPERTY_4(Class, Type, name, Signal) \ + 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, Signal> name; + +#define Q_OBJECT_BINDABLE_PROPERTY(...) \ + QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \ + QT_OVERLOADED_MACRO(QT_OBJECT_BINDABLE_PROPERTY, __VA_ARGS__) \ + QT_WARNING_POP + +#define QT_OBJECT_BINDABLE_PROPERTY_WITH_ARGS_4(Class, Type, name, value) \ + 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, nullptr> name = \ + QObjectBindableProperty<Class, Type, Class::_qt_property_##name##_offset, nullptr>( \ + value); + +#define QT_OBJECT_BINDABLE_PROPERTY_WITH_ARGS_5(Class, Type, name, value, Signal) \ + 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, Signal> name = \ + QObjectBindableProperty<Class, Type, Class::_qt_property_##name##_offset, Signal>( \ + value); + +#define Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(...) \ + QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \ + QT_OVERLOADED_MACRO(QT_OBJECT_BINDABLE_PROPERTY_WITH_ARGS, __VA_ARGS__) \ + QT_WARNING_POP + +template<typename Class, typename T, auto Offset, auto Getter> +class QObjectComputedProperty : public QUntypedPropertyData +{ + 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)); + } + +public: + using value_type = T; + using parameter_type = T; + + QObjectComputedProperty() = default; + + parameter_type value() const + { + qGetBindingStorage(owner())->registerDependency(this); + return (owner()->*Getter)(); + } + + std::conditional_t<QTypeTraits::is_dereferenceable_v<T>, parameter_type, void> + operator->() const + { + if constexpr (QTypeTraits::is_dereferenceable_v<T>) + return value(); + else + return; + } + + parameter_type operator*() const + { + return value(); + } + + operator parameter_type() const + { + return value(); + } + + constexpr bool hasBinding() const { return false; } + + 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); + } + + template<typename Functor> + QPropertyNotifier addNotifier(Functor f) + { + static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters"); + return QPropertyNotifier(*this, f); + } + + QtPrivate::QPropertyBindingData &bindingData() const + { + auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); + return *storage->bindingData(const_cast<QObjectComputedProperty *>(this), true); + } + + void notify() { + // computed property can't store a binding, so there's nothing to mark + auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); + auto bd = storage->bindingData(const_cast<QObjectComputedProperty *>(this), false); + if (bd) + bd->notifyObservers(this, qGetBindingStorage(owner())); + } +}; + +#define Q_OBJECT_COMPUTED_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 \ + } \ + QObjectComputedProperty<Class, Type, Class::_qt_property_##name##_offset, __VA_ARGS__> name; + +#undef QT_SOURCE_LOCATION_NAMESPACE + QT_END_NAMESPACE #endif // QPROPERTY_H |