/**************************************************************************** ** ** 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$ ** ****************************************************************************/ #ifndef QPROPERTY_H #define QPROPERTY_H #include #include #include #include #include #include #include #include #if __has_include() && __cplusplus >= 202002L && !defined(Q_CLANG_QDOC) #include #define QT_PROPERTY_COLLECT_BINDING_LOCATION #define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation(std::source_location::current()) #elif __has_include() && __cplusplus >= 201703L && !defined(Q_CLANG_QDOC) #include #define QT_PROPERTY_COLLECT_BINDING_LOCATION #define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation(std::experimental::source_location::current()) #else #define QT_PROPERTY_DEFAULT_BINDING_LOCATION QPropertyBindingSourceLocation() #endif QT_BEGIN_NAMESPACE struct Q_CORE_EXPORT QPropertyBindingSourceLocation { const char *fileName = nullptr; const char *functionName = nullptr; quint32 line = 0; quint32 column = 0; QPropertyBindingSourceLocation() = default; #ifdef QT_PROPERTY_COLLECT_BINDING_LOCATION QPropertyBindingSourceLocation(const std::experimental::source_location &cppLocation) { fileName = cppLocation.file_name(); functionName = cppLocation.function_name(); line = cppLocation.line(); column = cppLocation.column(); } #endif }; template class QPropertyChangeHandler; template class QProperty; template class QNotifiedProperty; class QPropertyBindingErrorPrivate; class Q_CORE_EXPORT QPropertyBindingError { public: enum Type { NoError, BindingLoop, EvaluationError, UnknownError }; QPropertyBindingError(Type type = NoError); QPropertyBindingError(const QPropertyBindingError &other); QPropertyBindingError &operator=(const QPropertyBindingError &other); QPropertyBindingError(QPropertyBindingError &&other); QPropertyBindingError &operator=(QPropertyBindingError &&other); ~QPropertyBindingError(); Type type() const; void setDescription(const QString &description); QString description() const; QPropertyBindingSourceLocation location() const; private: QSharedDataPointer d; }; class Q_CORE_EXPORT QUntypedPropertyBinding { public: using BindingEvaluationResult = QPropertyBindingError; // writes binding result into dataPtr using BindingEvaluationFunction = std::function; QUntypedPropertyBinding(); QUntypedPropertyBinding(const QMetaType &metaType, BindingEvaluationFunction function, const QPropertyBindingSourceLocation &location); QUntypedPropertyBinding(QUntypedPropertyBinding &&other); QUntypedPropertyBinding(const QUntypedPropertyBinding &other); QUntypedPropertyBinding &operator=(const QUntypedPropertyBinding &other); QUntypedPropertyBinding &operator=(QUntypedPropertyBinding &&other); ~QUntypedPropertyBinding(); bool isNull() const; QPropertyBindingError error() const; QMetaType valueMetaType() const; explicit QUntypedPropertyBinding(QPropertyBindingPrivate *priv); private: friend class QtPrivate::QPropertyBase; friend class QPropertyBindingPrivate; template friend class QPropertyBinding; QPropertyBindingPrivatePtr d; }; template class QPropertyBinding : public QUntypedPropertyBinding { template struct BindingAdaptor { Functor impl; QUntypedPropertyBinding::BindingEvaluationResult operator()(const QMetaType &/*metaType*/, void *dataPtr) { std::variant result(impl()); if (auto errorPtr = std::get_if(&result)) return *errorPtr; if (auto valuePtr = std::get_if(&result)) { PropertyType *propertyPtr = reinterpret_cast(dataPtr); *propertyPtr = std::move(*valuePtr); return {}; } return {}; } }; public: QPropertyBinding() = default; template QPropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location) : QUntypedPropertyBinding(QMetaType::fromType(), BindingAdaptor{std::forward(f)}, location) {} QPropertyBinding(const QProperty &property) : QUntypedPropertyBinding(property.d.priv.binding()) {} template QPropertyBinding(const QNotifiedProperty &property) : QUntypedPropertyBinding(property.d.priv.binding()) {} // Internal explicit QPropertyBinding(const QUntypedPropertyBinding &binding) : QUntypedPropertyBinding(binding) {} }; namespace QtPrivate { template constexpr auto is_variant_v = false; template constexpr auto is_variant_v> = true; } namespace Qt { template auto makePropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, std::enable_if_t> * = 0) { if constexpr (QtPrivate::is_variant_v>) { return QPropertyBinding>>(std::forward(f), location); } else { return QPropertyBinding>(std::forward(f), location); } // Work around bogus warning Q_UNUSED(QtPrivate::is_variant_v) } } struct QPropertyBasePointer; template class QProperty { public: using value_type = T; 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 &binding) : QProperty() { operator=(binding); } QProperty(QPropertyBinding &&binding) : QProperty() { operator=(std::move(binding)); } #ifndef Q_CLANG_QDOC template explicit QProperty(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, typename std::enable_if_t> * = 0) : QProperty(QPropertyBinding(std::forward(f), location)) {} #else template explicit QProperty(Functor &&f); #endif ~QProperty() = default; T value() const { if (d.priv.hasBinding()) d.priv.evaluateIfDirty(); d.priv.registerWithCurrentlyEvaluatingBinding(); return d.getValue(); } operator T() const { return value(); } void setValue(T &&newValue) { d.priv.removeBinding(); if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) notify(); } void setValue(const T &newValue) { d.priv.removeBinding(); if (d.setValueAndReturnTrueIfChanged(newValue)) notify(); } QProperty &operator=(T &&newValue) { setValue(std::move(newValue)); return *this; } QProperty &operator=(const T &newValue) { setValue(newValue); return *this; } QProperty &operator=(const QPropertyBinding &newBinding) { setBinding(newBinding); return *this; } QProperty &operator=(QPropertyBinding &&newBinding) { setBinding(std::move(newBinding)); return *this; } QPropertyBinding setBinding(const QPropertyBinding &newBinding) { QPropertyBinding oldBinding(d.priv.setBinding(newBinding, &d)); notify(); return oldBinding; } QPropertyBinding setBinding(QPropertyBinding &&newBinding) { QPropertyBinding b(std::move(newBinding)); QPropertyBinding oldBinding(d.priv.setBinding(b, &d)); notify(); return oldBinding; } bool setBinding(const QUntypedPropertyBinding &newBinding) { if (newBinding.valueMetaType().id() != qMetaTypeId()) return false; d.priv.setBinding(newBinding, &d); notify(); return true; } #ifndef Q_CLANG_QDOC template QPropertyBinding setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, std::enable_if_t> * = nullptr) { return setBinding(Qt::makePropertyBinding(std::forward(f), location)); } #else template QPropertyBinding setBinding(Functor f); #endif bool hasBinding() const { return d.priv.hasBinding(); } QPropertyBinding binding() const { return QPropertyBinding(*this); } QPropertyBinding takeBinding() { return QPropertyBinding(d.priv.setBinding(QUntypedPropertyBinding(), &d)); } template QPropertyChangeHandler onValueChanged(Functor f); template QPropertyChangeHandler subscribe(Functor f); private: void notify() { d.priv.notifyObservers(&d); } Q_DISABLE_COPY(QProperty) friend struct QPropertyBasePointer; friend class QPropertyBinding; friend class QPropertyObserver; // Mutable because querying for the value may require evalating the binding expression, calling // non-const functions on QPropertyBase. mutable QtPrivate::QPropertyValueStorage d; }; namespace Qt { template QPropertyBinding makePropertyBinding(const QProperty &otherProperty, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) { return Qt::makePropertyBinding([&otherProperty]() -> PropertyType { return otherProperty; }, location); } } template class QNotifiedProperty { public: using value_type = T; QNotifiedProperty() = default; explicit QNotifiedProperty(const T &initialValue) : d(initialValue) {} explicit QNotifiedProperty(T &&initialValue) : d(std::move(initialValue)) {} QNotifiedProperty(Class *owner, const QPropertyBinding &binding) : QNotifiedProperty() { setBinding(owner, binding); } QNotifiedProperty(Class *owner, QPropertyBinding &&binding) : QNotifiedProperty() { setBinding(owner, std::move(binding)); } #ifndef Q_CLANG_QDOC template explicit QNotifiedProperty(Class *owner, Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, typename std::enable_if_t> * = 0) : QNotifiedProperty(QPropertyBinding(owner, std::forward(f), location)) {} #else template explicit QProperty(Class *owner, Functor &&f); #endif ~QNotifiedProperty() = default; T value() const { if (d.priv.hasBinding()) d.priv.evaluateIfDirty(); d.priv.registerWithCurrentlyEvaluatingBinding(); return d.getValue(); } operator T() const { return value(); } void setValue(Class *owner, T &&newValue) { if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) notify(owner); d.priv.removeBinding(); } void setValue(Class *owner, const T &newValue) { if (d.setValueAndReturnTrueIfChanged(newValue)) notify(owner); d.priv.removeBinding(); } QPropertyBinding setBinding(Class *owner, const QPropertyBinding &newBinding) { QPropertyBinding oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o) { (reinterpret_cast(o)->*Callback)(); })); notify(owner); return oldBinding; } QPropertyBinding setBinding(Class *owner, QPropertyBinding &&newBinding) { QPropertyBinding b(std::move(newBinding)); QPropertyBinding oldBinding(d.priv.setBinding(b, &d, owner, [](void *o) { (reinterpret_cast(o)->*Callback)(); })); notify(owner); return oldBinding; } bool setBinding(Class *owner, const QUntypedPropertyBinding &newBinding) { if (newBinding.valueMetaType().id() != qMetaTypeId()) return false; d.priv.setBinding(newBinding, &d, owner, [](void *o) { (reinterpret_cast(o)->*Callback)(); }); notify(owner); return true; } #ifndef Q_CLANG_QDOC template QPropertyBinding setBinding(Class *owner, Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, std::enable_if_t> * = nullptr) { return setBinding(owner, Qt::makePropertyBinding(std::forward(f), location)); } #else template QPropertyBinding setBinding(Class *owner, Functor f); #endif bool hasBinding() const { return d.priv.hasBinding(); } QPropertyBinding binding() const { return QPropertyBinding(*this); } QPropertyBinding takeBinding() { return QPropertyBinding(d.priv.setBinding(QUntypedPropertyBinding(), &d)); } template QPropertyChangeHandler onValueChanged(Functor f); template QPropertyChangeHandler subscribe(Functor f); private: void notify(Class *owner) { d.priv.notifyObservers(&d); (owner->*Callback)(); } Q_DISABLE_COPY_MOVE(QNotifiedProperty) friend class QPropertyBinding; friend class QPropertyObserver; // Mutable because querying for the value may require evalating the binding expression, calling // non-const functions on QPropertyBase. mutable QtPrivate::QPropertyValueStorage d; }; struct QPropertyObserverPrivate; struct QPropertyObserverPointer; class Q_CORE_EXPORT QPropertyObserver { public: // Internal enum ObserverTag { ObserverNotifiesBinding, ObserverNotifiesChangeHandler, ObserverNotifiesAlias, }; QPropertyObserver(); QPropertyObserver(QPropertyObserver &&other); QPropertyObserver &operator=(QPropertyObserver &&other); ~QPropertyObserver(); template void setSource(const QProperty &property) { setSource(property.d.priv); } template void setSource(const QNotifiedProperty &property) { setSource(property.d.priv); } protected: QPropertyObserver(void (*callback)(QPropertyObserver*, void *)); QPropertyObserver(void *aliasedPropertyPtr); template QProperty *aliasedProperty() const { return reinterpret_cast *>(aliasedPropertyPtr); } private: void setSource(QtPrivate::QPropertyBase &property); QTaggedPointer 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 prev; union { QPropertyBindingPrivate *bindingToMarkDirty = nullptr; void (*changeHandler)(QPropertyObserver*, void *); quintptr aliasedPropertyPtr; }; QPropertyObserver(const QPropertyObserver &) = delete; QPropertyObserver &operator=(const QPropertyObserver &) = delete; friend struct QPropertyObserverPointer; friend struct QPropertyBasePointer; friend class QPropertyBindingPrivate; }; template class QPropertyChangeHandler : public QPropertyObserver { Functor m_handler; public: QPropertyChangeHandler(Functor handler) : QPropertyObserver([](QPropertyObserver *self, void *) { auto This = static_cast*>(self); This->m_handler(); }) , m_handler(handler) { } template QPropertyChangeHandler(const QProperty &property, Functor handler) : QPropertyObserver([](QPropertyObserver *self, void *) { auto This = static_cast*>(self); This->m_handler(); }) , m_handler(handler) { setSource(property); } template QPropertyChangeHandler(const QNotifiedProperty &property, Functor handler) : QPropertyObserver([](QPropertyObserver *self, void *) { auto This = static_cast*>(self); This->m_handler(); }) , m_handler(handler) { setSource(property); } }; template template QPropertyChangeHandler QProperty::onValueChanged(Functor f) { #if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); #endif return QPropertyChangeHandler(*this, f); } template template QPropertyChangeHandler QProperty::subscribe(Functor f) { #if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); #endif f(); return onValueChanged(f); } template template QPropertyChangeHandler QNotifiedProperty::onValueChanged(Functor f) { #if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); #endif return QPropertyChangeHandler(*this, f); } template template QPropertyChangeHandler QNotifiedProperty::subscribe(Functor f) { #if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L) static_assert(std::is_invocable_v, "Functor callback must be callable without any parameters"); #endif f(); return onValueChanged(f); } template struct QPropertyMemberChangeHandler; template struct QPropertyMemberChangeHandler : public QPropertyObserver { QPropertyMemberChangeHandler(Class *obj) : QPropertyObserver(notify) { setSource(obj->*PropertyMember); } static void notify(QPropertyObserver *, void *propertyDataPtr) { // memberOffset is the offset of the QProperty<> member within the class. We get the absolute address // of that member and subtracting the relative offset gives us the address of the class instance. const size_t memberOffset = reinterpret_cast(&(static_cast(nullptr)->*PropertyMember)); Class *obj = reinterpret_cast(reinterpret_cast(propertyDataPtr) - memberOffset); (obj->*Callback)(); } }; template class QPropertyAlias : public QPropertyObserver { Q_DISABLE_COPY_MOVE(QPropertyAlias) public: QPropertyAlias(QProperty *property) : QPropertyObserver(property) { if (property) setSource(*property); } QPropertyAlias(QPropertyAlias *alias) : QPropertyAlias(alias->aliasedProperty()) {} T value() const { if (auto *p = aliasedProperty()) return p->value(); return T(); } operator T() const { return value(); } void setValue(T &&newValue) { if (auto *p = aliasedProperty()) p->setValue(std::move(newValue)); } void setValue(const T &newValue) { if (auto *p = aliasedProperty()) p->setValue(newValue); } QPropertyAlias &operator=(T &&newValue) { if (auto *p = aliasedProperty()) *p = std::move(newValue); return *this; } QPropertyAlias &operator=(const T &newValue) { if (auto *p = aliasedProperty()) *p = newValue; return *this; } QPropertyAlias &operator=(const QPropertyBinding &newBinding) { setBinding(newBinding); return *this; } QPropertyAlias &operator=(QPropertyBinding &&newBinding) { setBinding(std::move(newBinding)); return *this; } QPropertyBinding setBinding(const QPropertyBinding &newBinding) { if (auto *p = aliasedProperty()) return p->setBinding(newBinding); return QPropertyBinding(); } QPropertyBinding setBinding(QPropertyBinding &&newBinding) { if (auto *p = aliasedProperty()) return p->setBinding(std::move(newBinding)); return QPropertyBinding(); } bool setBinding(const QUntypedPropertyBinding &newBinding) { if (auto *p = aliasedProperty()) return p->setBinding(newBinding); return false; } #ifndef Q_CLANG_QDOC template QPropertyBinding setBinding(Functor &&f, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, std::enable_if_t> * = nullptr) { return setBinding(Qt::makePropertyBinding(std::forward(f), location)); } #else template QPropertyBinding setBinding(Functor f); #endif bool hasBinding() const { if (auto *p = aliasedProperty()) return p->hasBinding(); return false; } QPropertyBinding binding() const { if (auto *p = aliasedProperty()) return p->binding(); return QPropertyBinding(); } QPropertyBinding takeBinding() { if (auto *p = aliasedProperty()) return p->takeBinding(); return QPropertyBinding(); } template QPropertyChangeHandler onValueChanged(Functor f) { if (auto *p = aliasedProperty()) return p->onValueChanged(f); return QPropertyChangeHandler(f); } template QPropertyChangeHandler subscribe(Functor f) { if (auto *p = aliasedProperty()) return p->subscribe(f); return QPropertyChangeHandler(f); } bool isValid() const { return aliasedProperty() != nullptr; } }; QT_END_NAMESPACE #endif // QPROPERTY_H