/**************************************************************************** ** ** 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; 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: // Returns either a boolean to indicate value change or an error. using BindingEvaluationResult = std::variant; // returns true if value changed, false if the binding evaluation lead to the same value as the property // already has. 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; void setDirty(bool dirty = true); private: explicit QUntypedPropertyBinding(const QPropertyBindingPrivatePtr &priv); friend class QtPrivate::QPropertyBase; friend struct 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); if (*propertyPtr == *valuePtr) return false; *propertyPtr = std::move(*valuePtr); return true; } return false; } }; 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()) {} private: // Internal explicit QPropertyBinding(const QUntypedPropertyBinding &binding) : QUntypedPropertyBinding(binding) {} friend class QProperty; }; 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); #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) { if (d.setValueAndReturnTrueIfChanged(std::move(newValue))) notify(); d.priv.removeBinding(); } void setValue(const T &newValue) { if (d.setValueAndReturnTrueIfChanged(newValue)) notify(); d.priv.removeBinding(); } 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) { return setBinding(Qt::makePropertyBinding(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(); } Q_DISABLE_COPY(QProperty) friend struct QPropertyBasePointer; friend class QPropertyBinding; friend struct QPropertyObserver; // Mutable because querying for the value may require evalating the binding expression, calling // non-const functions on QPropertyBase. mutable QtPrivate::QPropertyValueStorage d; }; #ifndef Q_CLANG_QDOC template template QProperty::QProperty(Functor &&f, const QPropertyBindingSourceLocation &location, typename std::enable_if_t> *) : QProperty(QPropertyBinding(std::forward(f), location)) {} #endif namespace Qt { template QPropertyBinding makePropertyBinding(const QProperty &otherProperty, const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) { return Qt::makePropertyBinding([&otherProperty]() -> PropertyType { return otherProperty; }, location); } } struct QPropertyObserverPrivate; struct QPropertyObserverPointer; struct Q_CORE_EXPORT QPropertyObserver { // Internal enum ObserverTag { ObserverNotifiesBinding = 0x0, ObserverNotifiesChangeHandler = 0x1, }; Q_DECLARE_FLAGS(ObserverTags, ObserverTag) QPropertyObserver() = default; QPropertyObserver(QPropertyObserver &&other); QPropertyObserver &operator=(QPropertyObserver &&other); ~QPropertyObserver(); template void setSource(const QProperty &property) { setSource(property.d.priv); } protected: QPropertyObserver(void (*callback)(QPropertyObserver*)); 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*); }; QPropertyObserver(const QPropertyObserver &) = delete; QPropertyObserver &operator=(const QPropertyObserver &) = delete; friend struct QPropertyObserverPointer; friend struct QPropertyBasePointer; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QPropertyObserver::ObserverTags) template class QPropertyChangeHandler : public QPropertyObserver { Functor m_handler; public: QPropertyChangeHandler(Functor handler) : QPropertyObserver([](QPropertyObserver *self) { auto This = static_cast*>(self); This->m_handler(); }) , m_handler(handler) { } template QPropertyChangeHandler(const QProperty &property, Functor handler) : QPropertyObserver([](QPropertyObserver *self) { 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); } QT_END_NAMESPACE #endif // QPROPERTY_H