From 9f9049b486a47aef0c7e2e3852b20aa4ffdce748 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 10 Jul 2019 09:01:14 +0200 Subject: Initial import of the Qt C++ property binding system This implements the core value based property binding system with automatic dependency tracking. More features are to be added later, and the documentation will need further improvements as well. Change-Id: I77ec9163ba4dace6c4451f5933962ebe1b3b4b14 Reviewed-by: Qt CI Bot Reviewed-by: Fabian Kosmale --- src/corelib/.prev_CMakeLists.txt | 3 + src/corelib/CMakeLists.txt | 3 + src/corelib/kernel/kernel.pri | 10 +- src/corelib/kernel/qproperty.cpp | 671 ++++++++++++++++++++++++++++++++ src/corelib/kernel/qproperty.h | 451 +++++++++++++++++++++ src/corelib/kernel/qproperty_p.h | 118 ++++++ src/corelib/kernel/qpropertybinding.cpp | 141 +++++++ src/corelib/kernel/qpropertybinding_p.h | 98 +++++ src/corelib/kernel/qpropertyprivate.h | 249 ++++++++++++ 9 files changed, 1742 insertions(+), 2 deletions(-) create mode 100644 src/corelib/kernel/qproperty.cpp create mode 100644 src/corelib/kernel/qproperty.h create mode 100644 src/corelib/kernel/qproperty_p.h create mode 100644 src/corelib/kernel/qpropertybinding.cpp create mode 100644 src/corelib/kernel/qpropertybinding_p.h create mode 100644 src/corelib/kernel/qpropertyprivate.h (limited to 'src') 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 + +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(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(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(ptr->d_ptr & ~QPropertyBase::FlagMask); + return nullptr; +} + +QtPrivate::QPropertyTagPreservingPointerToPointer QPropertyBasePointer::firstObserverPtr() const +{ + if (auto *binding = bindingPtr()) + return const_cast(&binding->firstObserver.ptr); + return &ptr->d_ptr; +} + +QPropertyObserverPointer QPropertyBasePointer::firstObserver() const +{ + if (auto *binding = bindingPtr()) + return binding->firstObserver; + return {reinterpret_cast(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(&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(&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(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(&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\ 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 firstname("John"); + QProperty lastname("Smith"); + QProperty age(41); + + QProperty 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 QProperty::QProperty() + + Constructs a property with a default constructed instance of T. +*/ + +/*! + \fn template explicit QProperty::QProperty(const T &initialValue) + + Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template explicit QProperty::QProperty(T &&initialValue) + + Move-Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template QProperty::QProperty(QProperty &&other) + + Move-constructs a QProperty instance, making it point at the same object that + \a other was pointing to. +*/ + +/*! + \fn template QProperty &QProperty::operator=(QProperty &&other) + + Move-assigns \a other to this QProperty instance. +*/ + +/*! + \fn template QProperty::QProperty(const QPropertyBinding &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 template QProperty::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 QProperty::~QProperty() + + Destroys the property. +*/ + +/*! + \fn template T QProperty::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 QProperty::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 void QProperty::setValue(const T &newValue) + + Assigns \a newValue to this property and removes the property's associated + binding, if present. +*/ + +/*! + \fn template void QProperty::setValue(T &&newValue) + \overload + + Assigns \a newValue to this property and removes the property's associated + binding, if present. +*/ + +/*! + \fn template QProperty &QProperty::operator=(const T &newValue) + + Assigns \a newValue to this property and returns a reference to this QProperty. +*/ + +/*! + \fn template QProperty &QProperty::operator=(T &&newValue) + \overload + + Assigns \a newValue to this property and returns a reference to this QProperty. +*/ + +/*! + \fn template QProperty &QProperty::operator=(const QPropertyBinding &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 QPropertyBinding QProperty::setBinding(const QPropertyBinding &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 template QPropertyBinding QProperty::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 QPropertyBinding QProperty::setBinding(QPropertyBinding &&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 QPropertyBinding QProperty::binding() const + + Returns the binding expression that is associated with this property. A + default constructed QPropertyBinding will be returned if no such + association exists. +*/ + +/*! + \fn template QPropertyBinding QProperty::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 template QPropertyChangeHandler QProperty::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 template QPropertyChangeHandler QProperty::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\ 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 +#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() = 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 friend class QPropertyBinding; + QPropertyBindingPrivatePtr d; +}; + +template +class QPropertyBinding : public QUntypedPropertyBinding +{ + template + struct BindingAdaptor + { + Functor impl; + QUntypedPropertyBinding::BindingEvaluationResult operator()(int /*version*/, void *propertyStoragePtr) + { + std::variant result(impl()); + if (auto errorPtr = std::get_if(&result)) + return *errorPtr; + + if (auto valuePtr = std::get_if(&result)) { + auto storagePtr = reinterpret_cast*>(propertyStoragePtr); + return storagePtr->setValueAndReturnTrueIfChanged(std::move(*valuePtr)); + } + + return false; + } + }; + +public: + QPropertyBinding() = default; + + template + QPropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location) + : QUntypedPropertyBinding(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: + 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; + } + +#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 struct 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 +{ + 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); + + QtPrivate::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::QPropertyTagPreservingPointerToPointer prev; + + union { + QPropertyBindingPrivate *bindingToMarkDirty = nullptr; + void (*changeHandler)(QPropertyObserver*); + }; + + QPropertyObserver(const QPropertyObserver &) = delete; + QPropertyObserver &operator=(const QPropertyObserver &) = delete; + + friend struct QPropertyObserverPointer; +}; + +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 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 +#include + +#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 firstObserverPtr() const; + QPropertyObserverPointer firstObserver() const; + + int observerCount() const; + + template + static QPropertyBasePointer get(QProperty &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 *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 + +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 updateGuard(updating, true); + + BindingEvaluationState evaluationFrame(this); + bool changed = false; + auto result = evaluationFunction(/*version*/1, propertyDataPtr); + if (auto changedPtr = std::get_if(&result)) + changed = *changedPtr; + else if (auto errorPtr = std::get_if(&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 +#include +#include +#include +#include +#include + +#include "qproperty_p.h" + +QT_BEGIN_NAMESPACE + +struct QPropertyBindingPrivate : public QSharedData +{ + QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction; + + QPropertyObserverPointer firstObserver; + QVarLengthArray 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 +#include + +QT_BEGIN_NAMESPACE + +class QUntypedPropertyBinding; +struct QPropertyBindingPrivate; +using QPropertyBindingPrivatePtr = QExplicitlySharedDataPointer; +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 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 +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 +{ + 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 class QTaggedPointer; + +template +class QPropertyTagPreservingPointerToPointer +{ + quintptr *data = nullptr; + +public: + QPropertyTagPreservingPointerToPointer() = default; + QPropertyTagPreservingPointerToPointer(T **ptr) + : data(reinterpret_cast(ptr)) + {} + QPropertyTagPreservingPointerToPointer(quintptr *ptr) + : data(ptr) + {} + + QPropertyTagPreservingPointerToPointer &operator=(T **ptr) + { + data = reinterpret_cast(ptr); + return *this; + } + + QPropertyTagPreservingPointerToPointer &operator=(QTaggedPointer *ptr) + { + data = reinterpret_cast(ptr); + return *this; + } + + void clear() + { + data = nullptr; + } + + void set(T *ptr) + { + *data = (*data & Mask) | (reinterpret_cast(ptr) & ~Mask); + } + + T *get() const + { + return reinterpret_cast(*data & ~Mask); + } + + explicit operator bool() const { return data != nullptr; } +}; + +template +class QTaggedPointer +{ +public: + QTaggedPointer() = default; + QTaggedPointer(T *ptr) : tagAndPointer(reinterpret_cast(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(ptr) | tag; + return *this; + } + + T *data() const { return reinterpret_cast(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 -- cgit v1.2.3