diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/.prev_CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/corelib/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/corelib/kernel/kernel.pri | 10 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 671 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty.h | 451 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty_p.h | 118 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertybinding.cpp | 141 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertybinding_p.h | 98 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertyprivate.h | 249 |
9 files changed, 1742 insertions, 2 deletions
diff --git a/src/corelib/.prev_CMakeLists.txt b/src/corelib/.prev_CMakeLists.txt index d132dbaffc..b9b9e8eb92 100644 --- a/src/corelib/.prev_CMakeLists.txt +++ b/src/corelib/.prev_CMakeLists.txt @@ -102,6 +102,9 @@ qt_add_module(Core kernel/qobjectdefs.h kernel/qobjectdefs_impl.h kernel/qpointer.cpp kernel/qpointer.h + kernel/qproperty.cpp kernel/qproperty.h kernel/qproperty_p.h + kernel/qpropertybinding.cpp kernel/qpropertybinding_p.h + kernel/qpropertyprivate.h kernel/qsharedmemory.cpp kernel/qsharedmemory.h kernel/qsharedmemory_p.h kernel/qsignalmapper.cpp kernel/qsignalmapper.h kernel/qsocketnotifier.cpp kernel/qsocketnotifier.h diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index e60ef18e17..4f53ab02f0 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -118,6 +118,9 @@ qt_add_module(Core kernel/qobjectdefs.h kernel/qobjectdefs_impl.h kernel/qpointer.cpp kernel/qpointer.h + kernel/qproperty.cpp kernel/qproperty.h kernel/qproperty_p.h + kernel/qpropertybinding.cpp kernel/qpropertybinding_p.h + kernel/qpropertyprivate.h kernel/qsharedmemory.cpp kernel/qsharedmemory.h kernel/qsharedmemory_p.h kernel/qsignalmapper.cpp kernel/qsignalmapper.h kernel/qsocketnotifier.cpp kernel/qsocketnotifier.h diff --git a/src/corelib/kernel/kernel.pri b/src/corelib/kernel/kernel.pri index bd3cabc01a..1b4f6d4923 100644 --- a/src/corelib/kernel/kernel.pri +++ b/src/corelib/kernel/kernel.pri @@ -43,7 +43,11 @@ HEADERS += \ kernel/qsystemerror_p.h \ kernel/qmetatype_p.h \ kernel/qmetatypeswitcher_p.h \ - kernel/qtestsupport_core.h + kernel/qtestsupport_core.h \ + kernel/qproperty.h \ + kernel/qpropertyprivate.h \ + kernel/qproperty_p.h \ + kernel/qpropertybinding_p.h SOURCES += \ kernel/qabstracteventdispatcher.cpp \ @@ -71,7 +75,9 @@ SOURCES += \ kernel/qpointer.cpp \ kernel/qmath.cpp \ kernel/qsystemerror.cpp \ - kernel/qtestsupport_core.cpp + kernel/qtestsupport_core.cpp \ + kernel/qproperty.cpp \ + kernel/qpropertybinding.cpp win32 { SOURCES += \ diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp new file mode 100644 index 0000000000..75110fa031 --- /dev/null +++ b/src/corelib/kernel/qproperty.cpp @@ -0,0 +1,671 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qproperty.h" +#include "qproperty_p.h" +#include "qpropertybinding_p.h" + +#include <qscopedvaluerollback.h> + +QT_BEGIN_NAMESPACE + +using namespace QtPrivate; + +QPropertyBase::QPropertyBase(QPropertyBase &&other, void *propertyDataPtr) +{ + std::swap(d_ptr, other.d_ptr); + QPropertyBasePointer d{this}; + d.firstObserverPtr().set(nullptr); + if (auto binding = d.bindingPtr()) + binding->propertyDataPtr = propertyDataPtr; +} + +void QPropertyBase::moveAssign(QPropertyBase &&other, void *propertyDataPtr) +{ + if (&other == this) + return; + + QPropertyBasePointer d{this}; + auto observer = d.firstObserver(); + d.firstObserverPtr().set(nullptr); + + if (auto binding = d.bindingPtr()) { + binding->unlinkAndDeref(); + d_ptr &= FlagMask; + } + + std::swap(d_ptr, other.d_ptr); + + if (auto binding = d.bindingPtr()) + binding->propertyDataPtr = propertyDataPtr; + + d.firstObserverPtr().set(const_cast<QPropertyObserver*>(observer.ptr)); + + // The caller will have to notify observers. +} + +QPropertyBase::~QPropertyBase() +{ + QPropertyBasePointer d{this}; + if (auto observer = d.firstObserver()) + observer.unlink(); + if (auto binding = d.bindingPtr()) + binding->unlinkAndDeref(); +} + +QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding &binding, void *propertyDataPtr) +{ + QPropertyBindingPrivatePtr oldBinding; + QPropertyBindingPrivatePtr newBinding = binding.d; + + QPropertyBasePointer d{this}; + + auto observer = d.firstObserver(); + if (observer) + observer.unlink(); + + if (auto *existingBinding = d.bindingPtr()) { + if (existingBinding == newBinding.data()) + return QUntypedPropertyBinding(oldBinding); + oldBinding = QPropertyBindingPrivatePtr(existingBinding); + oldBinding->unlinkAndDeref(); + d_ptr &= FlagMask; + } + if (newBinding) { + newBinding.data()->ref.ref(); + d_ptr = (d_ptr & FlagMask) | reinterpret_cast<quintptr>(newBinding.data()); + d_ptr |= BindingBit; + newBinding->dirty = true; + newBinding->propertyDataPtr = propertyDataPtr; + if (observer) + observer.prependToBinding(newBinding.data()); + } else { + d_ptr &= ~BindingBit; + } + + return QUntypedPropertyBinding(oldBinding); +} + +QPropertyBindingPrivatePtr QPropertyBase::binding() +{ + QPropertyBasePointer d{this}; + if (auto binding = d.bindingPtr()) + return QPropertyBindingPrivatePtr(binding); + return QPropertyBindingPrivatePtr(); +} + +QPropertyBindingPrivate *QPropertyBasePointer::bindingPtr() const +{ + if (ptr->d_ptr & QPropertyBase::BindingBit) + return reinterpret_cast<QPropertyBindingPrivate*>(ptr->d_ptr & ~QPropertyBase::FlagMask); + return nullptr; +} + +QtPrivate::QPropertyTagPreservingPointerToPointer<QPropertyObserver> QPropertyBasePointer::firstObserverPtr() const +{ + if (auto *binding = bindingPtr()) + return const_cast<QPropertyObserver**>(&binding->firstObserver.ptr); + return &ptr->d_ptr; +} + +QPropertyObserverPointer QPropertyBasePointer::firstObserver() const +{ + if (auto *binding = bindingPtr()) + return binding->firstObserver; + return {reinterpret_cast<QPropertyObserver*>(ptr->d_ptr & ~QPropertyBase::FlagMask)}; +} + +static thread_local BindingEvaluationState *currentBindingEvaluationState = nullptr; + +BindingEvaluationState::BindingEvaluationState(QPropertyBindingPrivate *binding) + : binding(binding) + , dependencyObservers(&binding->dependencyObservers) +{ + previousState = currentBindingEvaluationState; + currentBindingEvaluationState = this; + dependencyObservers->clear(); +} + +BindingEvaluationState::~BindingEvaluationState() +{ + currentBindingEvaluationState = previousState; +} + +void QPropertyBase::evaluateIfDirty() +{ + QPropertyBasePointer d{this}; + QPropertyBindingPrivate *binding = d.bindingPtr(); + if (!binding) + return; + binding->evaluateIfDirtyAndReturnTrueIfValueChanged(); +} + +void QPropertyBase::removeBinding() +{ + QPropertyBasePointer d{this}; + + auto observer = d.firstObserver(); + if (observer) + observer.unlink(); + + if (auto *existingBinding = d.bindingPtr()) { + existingBinding->unlinkAndDeref(); + d_ptr &= FlagMask; + } + d_ptr &= ~BindingBit; + + if (observer) + observer.observeProperty(d); +} + +void QPropertyBase::registerWithCurrentlyEvaluatingBinding() const +{ + auto currentState = currentBindingEvaluationState; + if (!currentState) + return; + + QPropertyBasePointer d{this}; + + currentState->dependencyObservers->append(QPropertyObserver()); + QPropertyObserverPointer dependencyObserver{&(*currentState->dependencyObservers)[currentState->dependencyObservers->size() - 1]}; + dependencyObserver.setBindingToMarkDirty(currentState->binding); + dependencyObserver.observeProperty(d); +} + +void QPropertyBase::notifyObservers() +{ + QPropertyBasePointer d{this}; + if (QPropertyObserverPointer observer = d.firstObserver()) + observer.notify(d.bindingPtr()); +} + +int QPropertyBasePointer::observerCount() const +{ + int count = 0; + for (auto observer = firstObserver(); observer; observer = observer.nextObserver()) + ++count; + return count; +} + +QPropertyObserver::QPropertyObserver(void (*callback)(QPropertyObserver*)) +{ + QPropertyObserverPointer d{this}; + d.setChangeHandler(callback); +} + +void QPropertyObserver::setSource(QPropertyBase &property) +{ + QPropertyObserverPointer d{this}; + QPropertyBasePointer propPrivate{&property}; + d.observeProperty(propPrivate); +} + + +QPropertyObserver::~QPropertyObserver() +{ + QPropertyObserverPointer d{this}; + d.unlink(); +} + +QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) +{ + std::swap(bindingToMarkDirty, other.bindingToMarkDirty); + std::swap(next, other.next); + std::swap(prev, other.prev); + if (next) + next->prev = reinterpret_cast<quintptr*>(&next); + if (prev) + prev.set(this); +} + +QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) +{ + if (this == &other) + return *this; + + QPropertyObserverPointer d{this}; + d.unlink(); + bindingToMarkDirty = nullptr; + + std::swap(bindingToMarkDirty, other.bindingToMarkDirty); + std::swap(next, other.next); + std::swap(prev, other.prev); + if (next) + next->prev = reinterpret_cast<quintptr*>(&next); + if (prev) + prev.set(this); + + return *this; +} + +void QPropertyObserverPointer::unlink() +{ + if (ptr->next) + ptr->next->prev = ptr->prev; + if (ptr->prev) + ptr->prev.set(ptr->next.data()); + ptr->next = nullptr; + ptr->prev.clear(); +} + +void QPropertyObserverPointer::setChangeHandler(void (*changeHandler)(QPropertyObserver *)) +{ + ptr->changeHandler = changeHandler; + ptr->next.setFlag(true); +} + +void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding) +{ + ptr->bindingToMarkDirty = binding; + ptr->next.setFlag(false); +} + +void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding) +{ + bool knownIfPropertyChanged = false; + bool propertyChanged =true; + + auto observer = const_cast<QPropertyObserver*>(ptr); + while (observer) { + auto * const next = observer->next.data(); + if (observer->next.flag()) { + if (!knownIfPropertyChanged && triggeringBinding) { + knownIfPropertyChanged = true; + + propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(); + } + if (!propertyChanged) + return; + + if (auto handlerToCall = std::exchange(observer->changeHandler, nullptr)) { + handlerToCall(observer); + observer->changeHandler = handlerToCall; + } + } else { + if (observer->bindingToMarkDirty) + observer->bindingToMarkDirty->markDirtyAndNotifyObservers(); + } + observer = next; + } +} + +void QPropertyObserverPointer::observeProperty(QPropertyBasePointer property) +{ + unlink(); + auto firstObserverPtr = property.firstObserverPtr(); + ptr->prev = firstObserverPtr; + ptr->next = firstObserverPtr.get(); + if (ptr->next) + ptr->next->prev = &ptr->next; + firstObserverPtr.set(ptr); +} + +void QPropertyObserverPointer::prependToBinding(QPropertyBindingPrivate *binding) +{ + ptr->prev = const_cast<QPropertyObserver **>(&binding->firstObserver.ptr); + binding->firstObserver = *this; +} + +QPropertyBindingError::QPropertyBindingError(Type type) +{ + if (type != NoError) { + d = new QPropertyBindingErrorPrivate; + d->type = type; + } +} + +QPropertyBindingError::QPropertyBindingError(const QPropertyBindingError &other) + : d(other.d) +{ +} + +QPropertyBindingError &QPropertyBindingError::operator=(const QPropertyBindingError &other) +{ + d = other.d; + return *this; +} + +QPropertyBindingError::QPropertyBindingError(QPropertyBindingError &&other) + : d(std::move(other.d)) +{ +} + +QPropertyBindingError &QPropertyBindingError::operator=(QPropertyBindingError &&other) +{ + d = std::move(other.d); + return *this; +} + +QPropertyBindingError::~QPropertyBindingError() +{ +} + +QPropertyBindingError::Type QPropertyBindingError::type() const +{ + if (!d) + return QPropertyBindingError::NoError; + return d->type; +} + +void QPropertyBindingError::setDescription(const QString &description) +{ + if (!d) + d = new QPropertyBindingErrorPrivate; + d->description = description; +} + +QString QPropertyBindingError::description() const +{ + if (!d) + return QString(); + return d->description; +} + +QPropertyBindingSourceLocation QPropertyBindingError::location() const +{ + if (!d) + return QPropertyBindingSourceLocation(); + return d->location; +} + +/*! + \class QProperty + \inmodule QtCore + \brief The QProperty class is a template class that enables automatic property bindings. + + \ingroup tools + + QProperty\<T\> is a generic container that holds an instance of T. You can assign + a value to it and you can read it via the value() function or the T conversion + operator. You can also tie the property to an expression that computes the value + dynamically, the binding expression. It is represented as a C++ lambda and + can be used to express relationships between different properties in your + application. + + The binding expression computes the value by reading other QProperty values. + Behind the scenes this dependency is tracked. Whenever a change in any property's + dependency is detected, the binding expression is re-evaluated and the new + result is applied to the property. This happens lazily, by marking the binding + as dirty and evaluating it only when the property's value is requested. For example: + + \code + QProperty<QString> firstname("John"); + QProperty<QString> lastname("Smith"); + QProperty<int> age(41); + + QProperty<QString> fullname; + fullname.setBinding([&]() { return firstname.value() + " " + lastname.value() + " age:" + QString::number(age.value()); }); + + qDebug() << fullname.value(); // Prints "John Smith age: 41" + + firstname = "Emma"; // Marks binding expression as dirty + + qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 41" + + // Birthday is coming up + age.setValue(age.value() + 1); + + qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 42" + \endcode + + When a new value is assigned to the \c firstname property, the binding + expression for \c fullname is marked as dirty. So when the last \c qDebug() statement + tries to read the name value of the \c fullname property, the expression is + evaluated again, \c firstname() will be called again and return the new value. + + Since bindings are C++ lambda expressions, they may do anything that's possible + in C++. This includes calling other functions. If those functions access values + held by QProperty, they automatically become dependencies to the binding. + + Binding expressions may use properties of any type, so in the above example the age + is an integer and folded into the string value using conversion to integer, but + the dependency is fully tracked. + + \section1 Tracking properties + + Sometimes the relationships between properties cannot be expressed using + bindings. Instead you may need to run custom code whenever the value of a property + changes and instead of assigning the value to another property, pass it to + other parts of your application. For example writing data into a network socket + or printing debug output. QProperty provides two mechanisms for tracking. + + You can register for a callback function to be called whenever the value of + a property changes, by using onValueChanged(). If you want the callback to also + be called for the current value of the property, register your callback using + subscribe() instead. +*/ + +/*! + \fn template <typename T> QProperty<T>::QProperty() + + Constructs a property with a default constructed instance of T. +*/ + +/*! + \fn template <typename T> explicit QProperty<T>::QProperty(const T &initialValue) + + Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename T> explicit QProperty<T>::QProperty(T &&initialValue) + + Move-Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename T> QProperty<T>::QProperty(QProperty<T> &&other) + + Move-constructs a QProperty instance, making it point at the same object that + \a other was pointing to. +*/ + +/*! + \fn template <typename T> QProperty<T> &QProperty<T>::operator=(QProperty &&other) + + Move-assigns \a other to this QProperty instance. +*/ + +/*! + \fn template <typename T> QProperty<T>::QProperty(const QPropertyBinding<T> &binding) + + Constructs a property that is tied to the provided \a binding expression. The + first time the property value is read, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. +*/ + +/*! + \fn template <typename T> template <typename Functor> QProperty<T>::QProperty(Functor &&f) + + Constructs a property that is tied to the provided binding expression \a f. The + first time the property value is read, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. +*/ + +/*! + \fn template <typename T> QProperty<T>::~QProperty() + + Destroys the property. +*/ + +/*! + \fn template <typename T> T QProperty<T>::value() const + + Returns the value of the property. This may evaluate a binding expression that + is tied to this property, before returning the value. +*/ + +/*! + \fn template <typename T> QProperty<T>::operator T() const + + Returns the value of the property. This may evaluate a binding expression that + is tied to this property, before returning the value. +*/ + +/*! + \fn template <typename T> void QProperty<T>::setValue(const T &newValue) + + Assigns \a newValue to this property and removes the property's associated + binding, if present. +*/ + +/*! + \fn template <typename T> void QProperty<T>::setValue(T &&newValue) + \overload + + Assigns \a newValue to this property and removes the property's associated + binding, if present. +*/ + +/*! + \fn template <typename T> QProperty<T> &QProperty<T>::operator=(const T &newValue) + + Assigns \a newValue to this property and returns a reference to this QProperty. +*/ + +/*! + \fn template <typename T> QProperty<T> &QProperty<T>::operator=(T &&newValue) + \overload + + Assigns \a newValue to this property and returns a reference to this QProperty. +*/ + +/*! + \fn template <typename T> QProperty<T> &QProperty<T>::operator=(const QPropertyBinding<T> &newBinding) + + Associates the value of this property with the provided \a newBinding + expression and returns a reference to this property. The first time the + property value is read, the binding is evaluated. Whenever a dependency of the + binding changes, the binding will be re-evaluated the next time the value of + this property is read. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QProperty<T>::setBinding(const QPropertyBinding<T> &newBinding) + + Associates the value of this property with the provided \a newBinding + expression and returns the previously associated binding. The first time the + property value is read, the binding is evaluated. Whenever a dependency of the + binding changes, the binding will be re-evaluated the next time the value of + this property is read. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyBinding<T> QProperty<T>::setBinding(Functor f) + \overload + + Associates the value of this property with the provided functor \a f and + returns the previously associated binding. The first time the property value + is read, the binding is evaluated by invoking the call operator () of \a f. + Whenever a dependency of the binding changes, the binding will be re-evaluated + the next time the value of this property is read. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QProperty<T>::setBinding(QPropertyBinding<T> &&newBinding) + \overload + + Associates the value of this property with the provided \a newBinding + expression and returns the previously associated binding. The first time the + property value is read, the binding is evaluated. Whenever a dependency of the + binding changes, the binding will be re-evaluated the next time the value of + this property is read. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QProperty<T>::binding() const + + Returns the binding expression that is associated with this property. A + default constructed QPropertyBinding<T> will be returned if no such + association exists. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QProperty<T>::takeBinding() + + Disassociates the binding expression from this property and returns it. After + calling this function, the value of the property will only change if you + assign a new value to it, or when a new binding is set. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QProperty<T>::onValueChanged(Functor f) + + Registers the given functor \a f as a callback that shall be called whenever + the value of the property changes. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, an std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the registration. When it + goes out of scope, the callback is de-registered. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QProperty<T>::subscribe(Functor f) + + Subscribes the given functor \a f as a callback that is called immediately and whenever + the value of the property changes in the future. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, an std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the subscription. When it + goes out of scope, the callback is unsubscribed. +*/ + +/*! + \class QPropertyChangeHandler + \inmodule QtCore + \brief The QPropertyChangeHandler class controls the lifecycle of change callback installed on a QProperty. + + \ingroup tools + + QPropertyChangeHandler\<PropertyType, Functor\> is created when registering a + callback on a QProperty to listen to changes to the property's value, using QProperty::onValueChanged + and QProperty::subscribe. As long as the change handler is alive, the callback remains installed. + + A handler instance can be transferred between C++ scopes using move semantics. +*/ + +QT_END_NAMESPACE diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h new file mode 100644 index 0000000000..45acfadd50 --- /dev/null +++ b/src/corelib/kernel/qproperty.h @@ -0,0 +1,451 @@ +/**************************************************************************** +** +** 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 <QtCore/qglobal.h> +#include <QtCore/QSharedDataPointer> +#include <QtCore/QString> +#include <functional> +#include <type_traits> +#include <variant> + +#include <QtCore/qpropertyprivate.h> + +#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_CLANG_QDOC) +#include <experimental/source_location> +#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) +#include <experimental/source_location> +#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 <typename Functor> class QPropertyChangeHandler; + +template <typename T> 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<QPropertyBindingErrorPrivate> d; +}; + +class Q_CORE_EXPORT QUntypedPropertyBinding +{ +public: + // Returns either a boolean to indicate value change or an error. + using BindingEvaluationResult = std::variant<bool, QPropertyBindingError>; + // returns true if value changed, false if the binding evaluation lead to the same value as the property + // already has. + using BindingEvaluationFunction = std::function<BindingEvaluationResult(int version, void *propertyStoragePtr)>; + + QUntypedPropertyBinding() = default; + QUntypedPropertyBinding(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; + +private: + explicit QUntypedPropertyBinding(const QPropertyBindingPrivatePtr &priv); + friend class QtPrivate::QPropertyBase; + friend struct QPropertyBindingPrivate; + template <typename> friend class QPropertyBinding; + QPropertyBindingPrivatePtr d; +}; + +template <typename PropertyType> +class QPropertyBinding : public QUntypedPropertyBinding +{ + template <typename Functor> + struct BindingAdaptor + { + Functor impl; + QUntypedPropertyBinding::BindingEvaluationResult operator()(int /*version*/, void *propertyStoragePtr) + { + std::variant<PropertyType, QPropertyBindingError> result(impl()); + if (auto errorPtr = std::get_if<QPropertyBindingError>(&result)) + return *errorPtr; + + if (auto valuePtr = std::get_if<PropertyType>(&result)) { + auto storagePtr = reinterpret_cast<QtPrivate::QPropertyValueStorage<PropertyType>*>(propertyStoragePtr); + return storagePtr->setValueAndReturnTrueIfChanged(std::move(*valuePtr)); + } + + return false; + } + }; + +public: + QPropertyBinding() = default; + + template<typename Functor> + QPropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location) + : QUntypedPropertyBinding(BindingAdaptor<Functor>{std::forward<Functor>(f)}, location) + {} + + QPropertyBinding(const QProperty<PropertyType> &property) + : QUntypedPropertyBinding(property.d.priv.binding()) + {} + +private: + // Internal + explicit QPropertyBinding(const QUntypedPropertyBinding &binding) + : QUntypedPropertyBinding(binding) + {} + friend class QProperty<PropertyType>; +}; + +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) + { + 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>) + } +} + +struct QPropertyBasePointer; + +template <typename T> +class QProperty +{ +public: + 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) + : QProperty() + { operator=(binding); } + QProperty(QPropertyBinding<T> &&binding) + : QProperty() + { operator=(std::move(binding)); } +#ifndef Q_CLANG_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); +#else + template <typename Functor> + 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<T> &operator=(T &&newValue) + { + setValue(std::move(newValue)); + return *this; + } + + QProperty<T> &operator=(const T &newValue) + { + setValue(newValue); + return *this; + } + + QProperty<T> &operator=(const QPropertyBinding<T> &newBinding) + { + setBinding(newBinding); + return *this; + } + + QProperty<T> &operator=(QPropertyBinding<T> &&newBinding) + { + setBinding(std::move(newBinding)); + 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; + } + +#ifndef Q_CLANG_QDOC + template <typename Functor> + QPropertyBinding<T> setBinding(Functor f, + const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) + { + return setBinding(Qt::makePropertyBinding(f, location)); + } +#else + template <typename Functor> + QPropertyBinding<T> setBinding(Functor f); +#endif + + bool hasBinding() const { return d.priv.hasBinding(); } + + QPropertyBinding<T> binding() const + { + return QPropertyBinding<T>(*this); + } + + QPropertyBinding<T> takeBinding() + { + return QPropertyBinding<T>(d.priv.setBinding(QUntypedPropertyBinding(), &d)); + } + + template<typename Functor> + QPropertyChangeHandler<Functor> onValueChanged(Functor f); + template<typename Functor> + QPropertyChangeHandler<Functor> subscribe(Functor f); + +private: + void notify() + { + d.priv.notifyObservers(); + } + + Q_DISABLE_COPY(QProperty) + + friend struct QPropertyBasePointer; + friend struct QPropertyBinding<T>; + friend struct QPropertyObserver; + // Mutable because querying for the value may require evalating the binding expression, calling + // non-const functions on QPropertyBase. + mutable QtPrivate::QPropertyValueStorage<T> d; +}; + +#ifndef Q_CLANG_QDOC +template <typename PropertyType> +template <typename Functor> +QProperty<PropertyType>::QProperty(Functor &&f, const QPropertyBindingSourceLocation &location, + typename std::enable_if_t<std::is_invocable_r_v<PropertyType, Functor&>> *) + : QProperty(QPropertyBinding<PropertyType>(std::forward<Functor>(f), location)) +{} +#endif + +namespace Qt { + template <typename PropertyType> + QPropertyBinding<PropertyType> makePropertyBinding(const QProperty<PropertyType> &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 +{ + QPropertyObserver() = default; + QPropertyObserver(QPropertyObserver &&other); + QPropertyObserver &operator=(QPropertyObserver &&other); + ~QPropertyObserver(); + + template <typename PropertyType> + void setSource(const QProperty<PropertyType> &property) + { setSource(property.d.priv); } + +protected: + QPropertyObserver(void (*callback)(QPropertyObserver*)); + +private: + void setSource(QtPrivate::QPropertyBase &property); + + QtPrivate::QTaggedPointer<QPropertyObserver> next; + // prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the + // first node. + QtPrivate::QPropertyTagPreservingPointerToPointer<QPropertyObserver> prev; + + union { + QPropertyBindingPrivate *bindingToMarkDirty = nullptr; + void (*changeHandler)(QPropertyObserver*); + }; + + QPropertyObserver(const QPropertyObserver &) = delete; + QPropertyObserver &operator=(const QPropertyObserver &) = delete; + + friend struct QPropertyObserverPointer; +}; + +template <typename Functor> +class QPropertyChangeHandler : public QPropertyObserver +{ + Functor m_handler; +public: + QPropertyChangeHandler(Functor handler) + : QPropertyObserver([](QPropertyObserver *self) { + auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); + This->m_handler(); + }) + , m_handler(handler) + { + } + + template <typename PropertyType> + QPropertyChangeHandler(const QProperty<PropertyType> &property, Functor handler) + : QPropertyObserver([](QPropertyObserver *self) { + auto This = static_cast<QPropertyChangeHandler<Functor>*>(self); + This->m_handler(); + }) + , m_handler(handler) + { + setSource(property); + } +}; + +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); +} + +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); +} + +QT_END_NAMESPACE + +#endif // QPROPERTY_H diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h new file mode 100644 index 0000000000..1b96e09d58 --- /dev/null +++ b/src/corelib/kernel/qproperty_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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_P_H +#define QPROPERTY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <qglobal.h> +#include <qvarlengtharray.h> + +#include "qproperty.h" + +QT_BEGIN_NAMESPACE + +// This is a helper "namespace" +struct Q_AUTOTEST_EXPORT QPropertyBasePointer +{ + const QtPrivate::QPropertyBase *ptr = nullptr; + + QPropertyBindingPrivate *bindingPtr() const; + + QtPrivate::QPropertyTagPreservingPointerToPointer<QPropertyObserver> firstObserverPtr() const; + QPropertyObserverPointer firstObserver() const; + + int observerCount() const; + + template <typename T> + static QPropertyBasePointer get(QProperty<T> &property) + { + return QPropertyBasePointer{&property.d.priv}; + } +}; + +// This is a helper "namespace" +struct QPropertyObserverPointer +{ + QPropertyObserver *ptr = nullptr; + + void unlink(); + + void setBindingToMarkDirty(QPropertyBindingPrivate *binding); + void setChangeHandler(void (*changeHandler)(QPropertyObserver*)); + + void notify(QPropertyBindingPrivate *triggeringBinding); + void observeProperty(QPropertyBasePointer property); + void prependToBinding(QPropertyBindingPrivate *binding); + + explicit operator bool() const { return ptr != nullptr; } + + QPropertyObserverPointer nextObserver() const { return {ptr->next.data()}; } +}; + +class QPropertyBindingErrorPrivate : public QSharedData +{ +public: + QPropertyBindingError::Type type = QPropertyBindingError::NoError; + QString description; + QPropertyBindingSourceLocation location; +}; + +struct BindingEvaluationState +{ + BindingEvaluationState(QPropertyBindingPrivate *binding); + ~BindingEvaluationState(); + QPropertyBindingPrivate *binding; + QVarLengthArray<QPropertyObserver, 4> *dependencyObservers = nullptr; + BindingEvaluationState *previousState = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QPROPERTY_P_H diff --git a/src/corelib/kernel/qpropertybinding.cpp b/src/corelib/kernel/qpropertybinding.cpp new file mode 100644 index 0000000000..23f7075998 --- /dev/null +++ b/src/corelib/kernel/qpropertybinding.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qpropertybinding_p.h" +#include "qproperty_p.h" + +#include <QScopedValueRollback> + +QT_BEGIN_NAMESPACE + +using namespace QtPrivate; + +QPropertyBindingPrivate::~QPropertyBindingPrivate() +{ + if (firstObserver) + firstObserver.unlink(); +} + +void QPropertyBindingPrivate::unlinkAndDeref() +{ + propertyDataPtr = nullptr; + if (!ref.deref()) + delete this; +} + +void QPropertyBindingPrivate::markDirtyAndNotifyObservers() +{ + dirty = true; + if (firstObserver) + firstObserver.notify(this); +} + +bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged() +{ + if (!dirty) + return false; + + if (updating) { + error = QPropertyBindingError(QPropertyBindingError::BindingLoop); + return false; + } + + QScopedValueRollback<bool> updateGuard(updating, true); + + BindingEvaluationState evaluationFrame(this); + bool changed = false; + auto result = evaluationFunction(/*version*/1, propertyDataPtr); + if (auto changedPtr = std::get_if<bool>(&result)) + changed = *changedPtr; + else if (auto errorPtr = std::get_if<QPropertyBindingError>(&result)) + error = std::move(*errorPtr); + dirty = false; + return changed; +} + +QUntypedPropertyBinding::QUntypedPropertyBinding(QUntypedPropertyBinding::BindingEvaluationFunction function, + const QPropertyBindingSourceLocation &location) +{ + d = new QPropertyBindingPrivate(std::move(function), std::move(location)); +} + +QUntypedPropertyBinding::QUntypedPropertyBinding(QUntypedPropertyBinding &&other) + : d(std::move(other.d)) +{ +} + +QUntypedPropertyBinding::QUntypedPropertyBinding(const QUntypedPropertyBinding &other) + : d(other.d) +{ +} + +QUntypedPropertyBinding &QUntypedPropertyBinding::operator=(const QUntypedPropertyBinding &other) +{ + d = other.d; + return *this; +} + +QUntypedPropertyBinding &QUntypedPropertyBinding::operator=(QUntypedPropertyBinding &&other) +{ + d = std::move(other.d); + return *this; +} + +QUntypedPropertyBinding::QUntypedPropertyBinding(const QPropertyBindingPrivatePtr &priv) + : d(priv) +{ +} + +QUntypedPropertyBinding::~QUntypedPropertyBinding() +{ +} + +bool QUntypedPropertyBinding::isNull() const +{ + return !d; +} + +QPropertyBindingError QUntypedPropertyBinding::error() const +{ + if (!d) + return QPropertyBindingError(); + return d->error; +} + +QT_END_NAMESPACE diff --git a/src/corelib/kernel/qpropertybinding_p.h b/src/corelib/kernel/qpropertybinding_p.h new file mode 100644 index 0000000000..12913b3088 --- /dev/null +++ b/src/corelib/kernel/qpropertybinding_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 QPROPERTYBINDING_P_H +#define QPROPERTYBINDING_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtCore/qshareddata.h> +#include <QtCore/qvarlengtharray.h> +#include <memory> +#include <vector> +#include <functional> + +#include "qproperty_p.h" + +QT_BEGIN_NAMESPACE + +struct QPropertyBindingPrivate : public QSharedData +{ + QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction; + + QPropertyObserverPointer firstObserver; + QVarLengthArray<QPropertyObserver, 4> dependencyObservers; + + void *propertyDataPtr = nullptr; + + QPropertyBindingSourceLocation location; + QPropertyBindingError error; + + bool dirty = false; + bool updating = false; + + QPropertyBindingPrivate(QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction, + const QPropertyBindingSourceLocation &location) + : evaluationFunction(std::move(evaluationFunction)) + , location(location) + {} + virtual ~QPropertyBindingPrivate(); + + void unlinkAndDeref(); + + void markDirtyAndNotifyObservers(); + bool evaluateIfDirtyAndReturnTrueIfValueChanged(); + + static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding) + { return binding.d.data(); } +}; + +QT_END_NAMESPACE + +#endif // QPROPERTYBINDING_P_H diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h new file mode 100644 index 0000000000..6d4a729845 --- /dev/null +++ b/src/corelib/kernel/qpropertyprivate.h @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** 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 QPROPERTYPRIVATE_H +#define QPROPERTYPRIVATE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtCore/QExplicitlySharedDataPointer> + +QT_BEGIN_NAMESPACE + +class QUntypedPropertyBinding; +struct QPropertyBindingPrivate; +using QPropertyBindingPrivatePtr = QExplicitlySharedDataPointer<QPropertyBindingPrivate>; +struct QPropertyBasePointer; + +namespace QtPrivate { + +class Q_CORE_EXPORT QPropertyBase +{ + // Mutable because the address of the observer of the currently evaluating binding is stored here, for + // notification later when the value changes. + mutable quintptr d_ptr = 0; + friend struct QT_PREPEND_NAMESPACE(QPropertyBasePointer); +public: + QPropertyBase() = default; + Q_DISABLE_COPY(QPropertyBase) + QPropertyBase(QPropertyBase &&other) = delete; + QPropertyBase(QPropertyBase &&other, void *propertyDataPtr); + QPropertyBase &operator=(QPropertyBase &&other) = delete; + ~QPropertyBase(); + + void moveAssign(QPropertyBase &&other, void *propertyDataPtr); + + bool hasBinding() const { return d_ptr & BindingBit; } + + QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding, void *propertyDataPtr); + QPropertyBindingPrivatePtr binding(); + + void evaluateIfDirty(); + void removeBinding(); + + void registerWithCurrentlyEvaluatingBinding() const; + void notifyObservers(); + + void setExtraBit(bool b) + { + if (b) + d_ptr |= ExtraBit; + else + d_ptr &= ~ExtraBit; + } + + bool extraBit() const { return d_ptr & ExtraBit; } + + static const quintptr ExtraBit = 0x1; // Used for QProperty<bool> specialization + static const quintptr BindingBit = 0x2; // Is d_ptr pointing to a binding (1) or list of notifiers (0)? + static const quintptr FlagMask = BindingBit | ExtraBit; +}; + +template <typename T> +struct QPropertyValueStorage +{ +private: + T value; +public: + QPropertyBase priv; + + QPropertyValueStorage() : value() {} + Q_DISABLE_COPY(QPropertyValueStorage) + explicit QPropertyValueStorage(const T &initialValue) : value(initialValue) {} + QPropertyValueStorage &operator=(const T &newValue) { value = newValue; return *this; } + explicit QPropertyValueStorage(T &&initialValue) : value(std::move(initialValue)) {} + QPropertyValueStorage &operator=(T &&newValue) { value = std::move(newValue); return *this; } + QPropertyValueStorage(QPropertyValueStorage &&other) : value(std::move(other.value)), priv(std::move(other.priv), this) {} + QPropertyValueStorage &operator=(QPropertyValueStorage &&other) { value = std::move(other.value); priv.moveAssign(std::move(other.priv), &value); return *this; } + + T getValue() const { return value; } + bool setValueAndReturnTrueIfChanged(T &&v) + { + if (v == value) + return false; + value = std::move(v); + return true; + } + bool setValueAndReturnTrueIfChanged(const T &v) + { + if (v == value) + return false; + value = v; + return true; + } +}; + +template<> +struct QPropertyValueStorage<bool> +{ + QPropertyBase priv; + + QPropertyValueStorage() = default; + Q_DISABLE_COPY(QPropertyValueStorage) + explicit QPropertyValueStorage(bool initialValue) { priv.setExtraBit(initialValue); } + QPropertyValueStorage &operator=(bool newValue) { priv.setExtraBit(newValue); return *this; } + QPropertyValueStorage(QPropertyValueStorage &&other) : priv(std::move(other.priv), this) {} + QPropertyValueStorage &operator=(QPropertyValueStorage &&other) { priv.moveAssign(std::move(other.priv), this); return *this; } + + bool getValue() const { return priv.extraBit(); } + bool setValueAndReturnTrueIfChanged(bool v) + { + if (v == priv.extraBit()) + return false; + priv.setExtraBit(v); + return true; + } +}; + +template <typename T> class QTaggedPointer; + +template <typename T, quintptr Mask = 0x3> +class QPropertyTagPreservingPointerToPointer +{ + quintptr *data = nullptr; + +public: + QPropertyTagPreservingPointerToPointer() = default; + QPropertyTagPreservingPointerToPointer(T **ptr) + : data(reinterpret_cast<quintptr*>(ptr)) + {} + QPropertyTagPreservingPointerToPointer(quintptr *ptr) + : data(ptr) + {} + + QPropertyTagPreservingPointerToPointer<T> &operator=(T **ptr) + { + data = reinterpret_cast<quintptr*>(ptr); + return *this; + } + + QPropertyTagPreservingPointerToPointer<T> &operator=(QTaggedPointer<T> *ptr) + { + data = reinterpret_cast<quintptr*>(ptr); + return *this; + } + + void clear() + { + data = nullptr; + } + + void set(T *ptr) + { + *data = (*data & Mask) | (reinterpret_cast<quintptr>(ptr) & ~Mask); + } + + T *get() const + { + return reinterpret_cast<T*>(*data & ~Mask); + } + + explicit operator bool() const { return data != nullptr; } +}; + +template <typename T> +class QTaggedPointer +{ +public: + QTaggedPointer() = default; + QTaggedPointer(T *ptr) : tagAndPointer(reinterpret_cast<quintptr>(ptr)) {} + + void setFlag(bool b) + { + if (b) + tagAndPointer |= Flag1Mask; + else + tagAndPointer &= ~Flag1Mask; + } + + bool flag() const { return tagAndPointer & Flag1Mask; } + + QTaggedPointer &operator=(T *ptr) + { + quintptr tag = tagAndPointer & TagMask; + tagAndPointer = reinterpret_cast<quintptr>(ptr) | tag; + return *this; + } + + T *data() const { return reinterpret_cast<T*>(tagAndPointer & ~TagMask); } + + explicit operator bool() const { return tagAndPointer & ~TagMask; } + T *operator->() const { return data(); } + +private: + quintptr tagAndPointer = 0; + static const quintptr Flag1Mask = 0x1; + static const quintptr TagMask = 0x3; +}; + +} // namespace QtPrivate + +QT_END_NAMESPACE + +#endif // QPROPERTYPRIVATE_H |