diff options
Diffstat (limited to 'src/corelib/kernel/qproperty.h')
-rw-r--r-- | src/corelib/kernel/qproperty.h | 575 |
1 files changed, 436 insertions, 139 deletions
diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index d612486e2c..0373867a66 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -1,68 +1,69 @@ -/**************************************************************************** -** -** 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 <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 { @@ -89,6 +90,7 @@ public: void setValueBypassingBindings(rvalue_ref v) { val = std::move(v); } }; +// ### Qt 7: un-export struct Q_CORE_EXPORT QPropertyBindingSourceLocation { const char *fileName = nullptr; @@ -96,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(); @@ -183,10 +200,6 @@ public: : QUntypedPropertyBinding(QMetaType::fromType<PropertyType>(), &QtPrivate::bindingFunctionVTable<std::remove_reference_t<Functor>, PropertyType>, &f, location) {} - template<typename Property, typename = typename Property::InheritsQUntypedPropertyData> - QPropertyBinding(const Property &property) - : QUntypedPropertyBinding(property.bindingData().binding()) - {} // Internal explicit QPropertyBinding(const QUntypedPropertyBinding &binding) @@ -205,68 +218,81 @@ namespace Qt { struct QPropertyObserverPrivate; struct QPropertyObserverPointer; +class QPropertyObserver; -class Q_CORE_EXPORT 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 - ObserverNotifiesAlias, // used for QPropertyAlias - ActivelyExecuting // the observer is currently evaluated in QPropertyObserver::notifyObservers or its - // placeholder. We only can store 4 different values, therefore those two conflate + 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, typename = typename Property::InheritsQUntypedPropertyData> + template <typename Property, QtPrivate::IsUntypedPropertyData<Property> = true> void setSource(const Property &property) { setSource(property.bindingData()); } void setSource(const QtPrivate::QPropertyBindingData &property); protected: - using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *); 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 aliasedPropertyData; + return aliasData; } private: - 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 *bindingToMarkDirty = nullptr; - ChangeHandler changeHandler; - QUntypedPropertyData *aliasedPropertyData; - QPropertyObserver **nodeState; - }; - QPropertyObserver(const QPropertyObserver &) = delete; QPropertyObserver &operator=(const QPropertyObserver &) = delete; - friend struct QPropertyObserverPointer; - friend struct QPropertyBindingDataPointer; - friend class QPropertyBindingPrivate; - template<ObserverTag> - friend struct QPropertyObserverNodeProtector; }; template <typename Functor> -class [[nodiscard]] QPropertyChangeHandler : public QPropertyObserver +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); @@ -276,7 +302,8 @@ public: { } - template<typename Property, typename = typename Property::InheritsQUntypedPropertyData> + 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); @@ -288,6 +315,37 @@ public: } }; +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 : public QPropertyData<T> { @@ -313,7 +371,7 @@ public: explicit QProperty(const QPropertyBinding<T> &binding) : QProperty() { setBinding(binding); } -#ifndef Q_CLANG_QDOC +#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&>> * = nullptr) @@ -327,8 +385,6 @@ public: parameter_type value() const { - if (d.hasBinding()) - d.evaluateIfDirty(this); d.registerWithCurrentlyEvaluatingBinding(); return this->val; } @@ -387,9 +443,7 @@ public: QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) { - QPropertyBinding<T> oldBinding(d.setBinding(newBinding, this)); - notify(); - return oldBinding; + return QPropertyBinding<T>(d.setBinding(newBinding, this)); } bool setBinding(const QUntypedPropertyBinding &newBinding) @@ -400,7 +454,7 @@ public: 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, @@ -417,7 +471,7 @@ public: QPropertyBinding<T> binding() const { - return QPropertyBinding<T>(*this); + return QPropertyBinding<T>(QUntypedPropertyBinding(d.binding())); } QPropertyBinding<T> takeBinding() @@ -440,6 +494,13 @@ public: 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); + } + const QtPrivate::QPropertyBindingData &bindingData() const { return d; } private: void notify() @@ -480,6 +541,8 @@ struct QBindableInterface MakeBinding makeBinding; SetObserver setObserver; GetMetaType metaType; + + static constexpr quintptr MetaTypeAccessorFlag = 0x1; }; template<typename Property, typename = void> @@ -487,7 +550,7 @@ class QBindableInterfaceForProperty { using T = typename Property::value_type; public: - // interface for read-only properties. Those do not have a binding()/setBinding() method, but one can + // 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 @@ -504,6 +567,28 @@ public: }; template<typename Property> +class QBindableInterfaceForProperty<const Property, std::void_t<decltype(std::declval<Property>().binding())>> +{ + using T = typename Property::value_type; +public: + // 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>(); } + }; +}; + +template<typename Property> class QBindableInterfaceForProperty<Property, std::void_t<decltype(std::declval<Property>().binding())>> { using T = typename Property::value_type; @@ -527,8 +612,72 @@ public: } +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); +} + +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); + +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; @@ -536,6 +685,9 @@ protected: : data(d), iface(i) {} + 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); + public: constexpr QUntypedBindable() = default; template<typename Property> @@ -546,19 +698,41 @@ public: 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) + QUntypedPropertyBinding makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) const { return iface ? iface->makeBinding(data, location) : QUntypedPropertyBinding(); } - void observe(QPropertyObserver *observer) + + QUntypedPropertyBinding takeBinding() + { + 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; + } + + void observe(QPropertyObserver *observer) const { if (iface) iface->setObserver(data, observer); +#ifndef QT_NO_DEBUG + else + QtPrivate::BindableWarnings::printUnsuitableBindableWarning("observe:", + QtPrivate::BindableWarnings::InvalidInterface); +#endif } template<typename Functor> - QPropertyChangeHandler<Functor> onValueChanged(Functor f) + QPropertyChangeHandler<Functor> onValueChanged(Functor f) const { QPropertyChangeHandler<Functor> handler(f); observe(&handler); @@ -566,24 +740,47 @@ public: } template<typename Functor> - QPropertyChangeHandler<Functor> subscribe(Functor f) + QPropertyChangeHandler<Functor> subscribe(Functor f) const { f(); return onValueChanged(f); } + template<typename Functor> + QPropertyNotifier addNotifier(Functor f) + { + QPropertyNotifier handler(f); + observe(&handler); + return handler; + } + QUntypedPropertyBinding binding() const { - if (!iface->getBinding) + 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 (!iface->setBinding) + 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() != iface->metaType()) + } + if (!binding.isNull() && binding.valueMetaType() != metaType()) { +#ifndef QT_NO_DEBUG + QtPrivate::BindableWarnings::printMetaTypeMismatch(metaType(), binding.valueMetaType()); +#endif return false; + } iface->setBinding(data, binding); return true; } @@ -592,6 +789,21 @@ public: return !binding().isNull(); } + QMetaType metaType() const + { + 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; + } + }; template<typename T> @@ -606,13 +818,19 @@ public: using QUntypedBindable::QUntypedBindable; explicit QBindable(const QUntypedBindable &b) : QUntypedBindable(b) { - if (iface && iface->metaType() != QMetaType::fromType<T>()) { + if (iface && metaType() != QMetaType::fromType<T>()) { data = nullptr; iface = nullptr; } } - QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) + 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)); } @@ -620,13 +838,28 @@ public: { return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::binding()); } + + QPropertyBinding<T> takeBinding() + { + return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::takeBinding()); + } + using QUntypedBindable::setBinding; QPropertyBinding<T> setBinding(const QPropertyBinding<T> &binding) { - Q_ASSERT(!iface || binding.isNull() || binding.valueMetaType() == iface->metaType()); - return iface ? static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding)) : QPropertyBinding<T>(); + 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_CLANG_QDOC +#ifndef Q_QDOC template <typename Functor> QPropertyBinding<T> setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, @@ -651,18 +884,21 @@ public: void setValue(const T &value) { - if (iface) + if (iface && iface->setter) iface->setter(data, &value); } }; +#if QT_DEPRECATED_SINCE(6, 6) template<typename T> -class QPropertyAlias : public QPropertyObserver +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 { Q_DISABLE_COPY_MOVE(QPropertyAlias) const QtPrivate::QBindableInterface *iface = nullptr; public: + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QPropertyAlias(QProperty<T> *property) : QPropertyObserver(property), iface(&QtPrivate::QBindableInterfaceForProperty<QProperty<T>>::iface) @@ -671,7 +907,7 @@ public: iface->setObserver(aliasedProperty(), this); } - template<typename Property, typename = typename Property::InheritsQUntypedPropertyData> + template <typename Property, QtPrivate::IsUntypedPropertyData<Property> = true> QPropertyAlias(Property *property) : QPropertyObserver(property), iface(&QtPrivate::QBindableInterfaceForProperty<Property>::iface) @@ -728,7 +964,7 @@ public: return QBindable<T>(aliasedProperty(), iface).setBinding(newBinding); } -#ifndef Q_CLANG_QDOC +#ifndef Q_QDOC template <typename Functor> QPropertyBinding<T> setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, @@ -768,39 +1004,26 @@ public: return QBindable<T>(aliasedProperty(), iface).subscribe(f); } + template<typename Functor> + QPropertyNotifier addNotifier(Functor f) + { + return QBindable<T>(aliasedProperty(), iface).addNotifier(f); + } + bool isValid() const { return aliasedProperty() != nullptr; } + QT_WARNING_POP }; - -struct QBindingStatus; - -struct QBindingStorageData; -class Q_CORE_EXPORT QBindingStorage -{ - mutable QBindingStorageData *d = nullptr; - QBindingStatus *bindingStatus = nullptr; - - template<typename Class, typename T, auto Offset, auto Setter> - friend class QObjectCompatProperty; -public: - QBindingStorage(); - ~QBindingStorage(); - - bool isEmpty() { return !d; } - - void maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const; - QtPrivate::QPropertyBindingData *bindingData(const QUntypedPropertyData *data) const; - QtPrivate::QPropertyBindingData *bindingData(QUntypedPropertyData *data, bool create); -}; - +#endif // QT_DEPRECATED_SINCE(6, 6) 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); @@ -814,8 +1037,12 @@ class QObjectBindableProperty : public QPropertyData<T> static void signalCallBack(QUntypedPropertyData *o) { QObjectBindableProperty *that = static_cast<QObjectBindableProperty *>(o); - if constexpr (HasSignal) - (that->owner()->*Signal)(); + 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; @@ -829,7 +1056,7 @@ public: explicit QObjectBindableProperty(const QPropertyBinding<T> &binding) : QObjectBindableProperty() { setBinding(binding); } -#ifndef Q_CLANG_QDOC +#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) @@ -842,7 +1069,7 @@ public: parameter_type value() const { - qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this); + qGetBindingStorage(owner())->registerDependency(this); return this->val; } @@ -879,6 +1106,11 @@ public: notify(bd); } + void notify() { + auto *bd = qGetBindingStorage(owner())->bindingData(this); + notify(bd); + } + void setValue(rvalue_ref t) { auto *bd = qGetBindingStorage(owner())->bindingData(this); @@ -906,7 +1138,6 @@ public: { 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); } @@ -918,7 +1149,7 @@ public: 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, @@ -963,6 +1194,13 @@ public: 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); + } + const QtPrivate::QPropertyBindingData &bindingData() const { auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner())); @@ -972,19 +1210,63 @@ private: void notify(const QtPrivate::QPropertyBindingData *binding) { if (binding) - binding->notifyObservers(this); - if constexpr (HasSignal) - (owner()->*Signal)(); + binding->notifyObservers(this, qGetBindingStorage(owner())); + if constexpr (HasSignal) { + if constexpr (SignalTakesValue::value) + (owner()->*Signal)(this->valueBypassingBindings()); + else + (owner()->*Signal)(); + } } }; -#define Q_OBJECT_BINDABLE_PROPERTY(Class, Type, name, ...) \ +#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, __VA_ARGS__> name; + 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 @@ -1008,7 +1290,7 @@ public: parameter_type value() const { - qGetBindingStorage(owner())->maybeUpdateBindingAndRegister(this); + qGetBindingStorage(owner())->registerDependency(this); return (owner()->*Getter)(); } @@ -1048,13 +1330,26 @@ public: 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); } -private: + 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, ...) \ @@ -1065,6 +1360,8 @@ private: } \ QObjectComputedProperty<Class, Type, Class::_qt_property_##name##_offset, __VA_ARGS__> name; +#undef QT_SOURCE_LOCATION_NAMESPACE + QT_END_NAMESPACE #endif // QPROPERTY_H |