diff options
Diffstat (limited to 'src/corelib/kernel/qproperty.cpp')
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 1002 |
1 files changed, 570 insertions, 432 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index fa22c6f498..caa9fce787 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qproperty.h" #include "qproperty_p.h" @@ -44,6 +8,9 @@ #include <QScopeGuard> #include <QtCore/qloggingcategory.h> #include <QThread> +#include <QtCore/qmetaobject.h> + +#include "qobject_p.h" QT_BEGIN_NAMESPACE @@ -60,9 +27,9 @@ void QPropertyBindingPrivatePtr::reset(QtPrivate::RefCounted *ptr) noexcept { if (ptr != d) { if (ptr) - ptr->ref++; - auto *old = qExchange(d, ptr); - if (old && (--old->ref == 0)) + ptr->addRef(); + auto *old = std::exchange(d, ptr); + if (old && !old->deref()) QPropertyBindingPrivate::destroyAndFreeMemory(static_cast<QPropertyBindingPrivate *>(d)); } } @@ -154,7 +121,7 @@ struct QPropertyDelayedNotifications Change notifications are sent later with notify (following the logic of separating binding updates and notifications used in non-deferred updates). */ - void evaluateBindings(int index, QBindingStatus *status) { + void evaluateBindings(PendingBindingObserverList &bindingObservers, qsizetype index, QBindingStatus *status) { auto *delayed = delayedProperties + index; auto *bindingData = delayed->originalBindingData; if (!bindingData) @@ -170,7 +137,7 @@ struct QPropertyDelayedNotifications QPropertyBindingDataPointer bindingDataPointer{bindingData}; QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); if (observer) - observer.evaluateBindings(status); + observer.evaluateBindings(bindingObservers, status); } /*! @@ -182,28 +149,28 @@ struct QPropertyDelayedNotifications \li sends any pending notifications. \endlist */ - void notify(int index) { + void notify(qsizetype index) { auto *delayed = delayedProperties + index; - auto *bindingData = delayed->originalBindingData; - if (!bindingData) + if (delayed->d_ptr & QPropertyBindingData::BindingBit) + return; // already handled + if (!delayed->originalBindingData) return; - delayed->originalBindingData = nullptr; + + QPropertyObserverPointer observer { reinterpret_cast<QPropertyObserver *>(delayed->d_ptr & ~QPropertyBindingData::DelayedNotificationBit) }; delayed->d_ptr = 0; - QPropertyBindingDataPointer bindingDataPointer{bindingData}; - QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); if (observer) observer.notify(delayed->propertyData); } }; -static thread_local QBindingStatus bindingStatus; +Q_CONSTINIT static thread_local QBindingStatus bindingStatus; /*! \since 6.2 - \relates template<typename T> QProperty<T> + \relates QProperty Marks the beginning of a property update group. Inside this group, changing a property does neither immediately update any dependent properties @@ -218,7 +185,7 @@ static thread_local QBindingStatus bindingStatus; properties need to be updated, preventing any external observer from noticing an inconsistent state. - \sa Qt::endPropertyUpdateGroup + \sa Qt::endPropertyUpdateGroup, QScopedPropertyUpdateGroup */ void Qt::beginPropertyUpdateGroup() { @@ -230,7 +197,7 @@ void Qt::beginPropertyUpdateGroup() /*! \since 6.2 - \relates template<typename T> QProperty<T> + \relates QProperty Ends a property update group. If the outermost group has been ended, and deferred binding evaluations and notifications happen now. @@ -238,7 +205,7 @@ void Qt::beginPropertyUpdateGroup() \warning Calling endPropertyUpdateGroup without a preceding call to beginPropertyUpdateGroup results in undefined behavior. - \sa Qt::beginPropertyUpdateGroup + \sa Qt::beginPropertyUpdateGroup, QScopedPropertyUpdateGroup */ void Qt::endPropertyUpdateGroup() { @@ -249,24 +216,69 @@ void Qt::endPropertyUpdateGroup() if (--data->ref) return; groupUpdateData = nullptr; + // ensures that bindings are kept alive until endPropertyUpdateGroup concludes + PendingBindingObserverList bindingObservers; // update all delayed properties auto start = data; while (data) { - for (int i = 0; i < data->used; ++i) - data->evaluateBindings(i, status); + for (qsizetype i = 0; i < data->used; ++i) + data->evaluateBindings(bindingObservers, i, status); data = data->next; } - // notify all delayed properties + // notify all delayed notifications from binding evaluation + for (const QBindingObserverPtr &observer: bindingObservers) { + QPropertyBindingPrivate *binding = observer.binding(); + binding->notifyNonRecursive(); + } + // do the same for properties which only have observers data = start; while (data) { - for (int i = 0; i < data->used; ++i) + for (qsizetype i = 0; i < data->used; ++i) data->notify(i); - auto *next = data->next; - delete data; - data = next; + delete std::exchange(data, data->next); } } +/*! + \since 6.6 + \class QScopedPropertyUpdateGroup + \inmodule QtCore + \ingroup tools + \brief RAII class around Qt::beginPropertyUpdateGroup()/Qt::endPropertyUpdateGroup(). + + This class calls Qt::beginPropertyUpdateGroup() in its constructor and + Qt::endPropertyUpdateGroup() in its destructor, making sure the latter + function is reliably called even in the presence of early returns or thrown + exceptions. + + \note Qt::endPropertyUpdateGroup() may re-throw exceptions thrown by + binding evaluations. This means your application may crash + (\c{std::terminate()} called) if another exception is causing + QScopedPropertyUpdateGroup's destructor to be called during stack + unwinding. If you expect exceptions from binding evaluations, use manual + Qt::endPropertyUpdateGroup() calls and \c{try}/\c{catch} blocks. + + \sa QProperty +*/ + +/*! + \fn QScopedPropertyUpdateGroup::QScopedPropertyUpdateGroup() + + Calls Qt::beginPropertyUpdateGroup(). +*/ + +/*! + \fn QScopedPropertyUpdateGroup::~QScopedPropertyUpdateGroup() + + Calls Qt::endPropertyUpdateGroup(). +*/ + + +// check everything stored in QPropertyBindingPrivate's union is trivially destructible +// (though the compiler would also complain if that weren't the case) +static_assert(std::is_trivially_destructible_v<QPropertyBindingSourceLocation>); +static_assert(std::is_trivially_destructible_v<std::byte[sizeof(QPropertyBindingSourceLocation)]>); + QPropertyBindingPrivate::~QPropertyBindingPrivate() { if (firstObserver) @@ -276,25 +288,51 @@ QPropertyBindingPrivate::~QPropertyBindingPrivate() + QPropertyBindingPrivate::getSizeEnsuringAlignment()); } +void QPropertyBindingPrivate::clearDependencyObservers() { + for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) { + QPropertyObserverPointer p{&inlineDependencyObservers[i]}; + p.unlink_fast(); + } + if (heapObservers) + heapObservers->clear(); + dependencyObserverCount = 0; +} + +QPropertyObserverPointer QPropertyBindingPrivate::allocateDependencyObserver_slow() +{ + ++dependencyObserverCount; + if (!heapObservers) + heapObservers.reset(new std::vector<QPropertyObserver>()); + return {&heapObservers->emplace_back()}; +} + void QPropertyBindingPrivate::unlinkAndDeref() { clearDependencyObservers(); propertyDataPtr = nullptr; - if (--ref == 0) + if (!deref()) destroyAndFreeMemory(this); } -void QPropertyBindingPrivate::evaluateRecursive(QBindingStatus *status) +bool QPropertyBindingPrivate::evaluateRecursive(PendingBindingObserverList &bindingObservers, QBindingStatus *status) { if (!status) status = &bindingStatus; - return evaluateRecursive_inline(status); + return evaluateRecursive_inline(bindingObservers, status); +} + +void QPropertyBindingPrivate::notifyNonRecursive(const PendingBindingObserverList &bindingObservers) +{ + notifyNonRecursive(); + for (auto &&bindingObserver: bindingObservers) { + bindingObserver.binding()->notifyNonRecursive(); + } } -void QPropertyBindingPrivate::notifyRecursive() +QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRecursive() { if (!pendingNotify) - return; + return Delayed; pendingNotify = false; Q_ASSERT(!updating); updating = true; @@ -305,6 +343,7 @@ void QPropertyBindingPrivate::notifyRecursive() if (hasStaticObserver) staticObserverCallback(propertyDataPtr); updating = false; + return Sent; } /*! @@ -416,7 +455,7 @@ QPropertyBindingError QUntypedPropertyBinding::error() const /*! Returns the meta-type of the binding. - If the QUntypedProperyBinding is null, an invalid QMetaType is returned. + If the QUntypedPropertyBinding is null, an invalid QMetaType is returned. */ QMetaType QUntypedPropertyBinding::valueMetaType() const { @@ -428,6 +467,8 @@ QMetaType QUntypedPropertyBinding::valueMetaType() const QPropertyBindingData::~QPropertyBindingData() { QPropertyBindingDataPointer d{this}; + if (isNotificationDelayed()) + proxyData()->originalBindingData = nullptr; for (auto observer = d.firstObserver(); observer;) { auto next = observer.nextObserver(); observer.unlink(); @@ -474,8 +515,9 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB newBindingRaw->prependObserver(observer); newBindingRaw->setStaticObserver(staticObserverCallback, guardCallback); - newBindingRaw->evaluateRecursive(); - newBindingRaw->notifyRecursive(); + PendingBindingObserverList bindingObservers; + newBindingRaw->evaluateRecursive(bindingObservers); + newBindingRaw->notifyNonRecursive(bindingObservers); } else if (observer) { d.setObservers(observer.ptr); } else { @@ -561,6 +603,11 @@ void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding_helper(Binding { QPropertyBindingDataPointer d{this}; + if (currentState->alreadyCaptureProperties.contains(this)) + return; + else + currentState->alreadyCaptureProperties.push_back(this); + QPropertyObserverPointer dependencyObserver = currentState->binding->allocateDependencyObserver(); Q_ASSERT(QPropertyObserver::ObserverNotifiesBinding == 0); dependencyObserver.setBindingToNotify_unsafe(currentState->binding); @@ -578,37 +625,49 @@ void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr return; QPropertyBindingDataPointer d{this}; + PendingBindingObserverList bindingObservers; if (QPropertyObserverPointer observer = d.firstObserver()) { - auto status = storage ? storage->bindingStatus : nullptr; - QPropertyDelayedNotifications *delay; -#ifndef QT_HAS_FAST_CURRENT_THREAD_ID + if (notifyObserver_helper(propertyDataPtr, storage, observer, bindingObservers) == Evaluated) { + /* evaluateBindings() can trash the observers. We need to re-fetch here. + "this" might also no longer be valid in case we have a QObjectBindableProperty + and consequently d isn't either (this happens when binding evaluation has + caused the binding storage to resize. + If storage is nullptr, then there is no dynamically resizable storage, + and we cannot run into the issue. + */ + if (storage) + d = QPropertyBindingDataPointer {storage->bindingData(propertyDataPtr)}; + if (QPropertyObserverPointer observer = d.firstObserver()) + observer.notify(propertyDataPtr); + for (auto &&bindingObserver: bindingObservers) + bindingObserver.binding()->notifyNonRecursive(); + } + } +} + +QPropertyBindingData::NotificationResult QPropertyBindingData::notifyObserver_helper +( + QUntypedPropertyData *propertyDataPtr, QBindingStorage *storage, + QPropertyObserverPointer observer, + PendingBindingObserverList &bindingObservers) const +{ +#ifdef QT_HAS_FAST_CURRENT_THREAD_ID + QBindingStatus *status = storage ? storage->bindingStatus : nullptr; + if (!status || status->threadId != QThread::currentThreadId()) status = &bindingStatus; #else - if (!status || status->threadId != QThread::currentThreadId()) - status = &bindingStatus; + Q_UNUSED(storage); + QBindingStatus *status = &bindingStatus; #endif - delay = status->groupUpdateData; - if (delay) { - delay->addProperty(this, propertyDataPtr); - return; - } - observer.evaluateBindings(status); - } else { - return; + if (QPropertyDelayedNotifications *delay = status->groupUpdateData) { + delay->addProperty(this, propertyDataPtr); + return Delayed; } - // evaluateBindings() can trash the observers. We need to re-fetch here. - if (QPropertyObserverPointer observer = d.firstObserver()) - observer.notify(propertyDataPtr); + observer.evaluateBindings(bindingObservers, status); + return Evaluated; } -int QPropertyBindingDataPointer::observerCount() const -{ - int count = 0; - for (auto observer = firstObserver(); observer; observer = observer.nextObserver()) - ++count; - return count; -} QPropertyObserver::QPropertyObserver(ChangeHandler changeHandler) { @@ -616,11 +675,15 @@ QPropertyObserver::QPropertyObserver(ChangeHandler changeHandler) d.setChangeHandler(changeHandler); } +#if QT_DEPRECATED_SINCE(6, 6) QPropertyObserver::QPropertyObserver(QUntypedPropertyData *data) { + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED aliasData = data; next.setTag(ObserverIsAlias); + QT_WARNING_POP } +#endif /*! \internal */ @@ -668,37 +731,20 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexc return *this; } -#define UNLINK_COMMON \ - if (ptr->next) \ - ptr->next->prev = ptr->prev; \ - if (ptr->prev) \ - ptr->prev.setPointer(ptr->next.data()); \ - ptr->next = nullptr; \ - ptr->prev.clear(); - /*! + \fn QPropertyObserverPointer::unlink() \internal Unlinks */ -void QPropertyObserverPointer::unlink() -{ - UNLINK_COMMON - if (ptr->next.tag() == QPropertyObserver::ObserverIsAlias) - ptr->aliasData = nullptr; -} + /*! + \fn QPropertyObserverPointer::unlink_fast() \internal Like unlink, but does not handle ObserverIsAlias. Must only be called in places where we know that we are not dealing with such an observer. */ -void QPropertyObserverPointer::unlink_fast() -{ - Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsAlias); - UNLINK_COMMON -} -#undef UNLINK_COMMON void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler changeHandler) { @@ -707,16 +753,9 @@ void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler); } -void QPropertyObserverPointer::setBindingToNotify(QPropertyBindingPrivate *binding) -{ - Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder); - ptr->binding = binding; - ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); -} - /*! \internal - The same as as setBindingToNotify, but assumes that the tag is already correct. + The same as setBindingToNotify, but assumes that the tag is already correct. */ void QPropertyObserverPointer::setBindingToNotify_unsafe(QPropertyBindingPrivate *binding) { @@ -725,90 +764,17 @@ void QPropertyObserverPointer::setBindingToNotify_unsafe(QPropertyBindingPrivate } /*! + \class QPropertyObserverNodeProtector \internal QPropertyObserverNodeProtector is a RAII wrapper which takes care of the internal switching logic for QPropertyObserverPointer::notify (described ibidem) */ -struct [[nodiscard]] QPropertyObserverNodeProtector { - QPropertyObserverBase m_placeHolder; - QPropertyObserverNodeProtector(QPropertyObserver *observer) - { - // insert m_placeholder after observer into the linked list - QPropertyObserver *next = observer->next.data(); - m_placeHolder.next = next; - observer->next = static_cast<QPropertyObserver *>(&m_placeHolder); - if (next) - next->prev = &m_placeHolder.next; - m_placeHolder.prev = &observer->next; - m_placeHolder.next.setTag(QPropertyObserver::ObserverIsPlaceholder); - } - QPropertyObserver *next() const { return m_placeHolder.next.data(); } - - ~QPropertyObserverNodeProtector() { - QPropertyObserverPointer d{static_cast<QPropertyObserver *>(&m_placeHolder)}; - d.unlink_fast(); - } -}; - -/*! \internal +/*! + \fn QPropertyObserverNodeProtector::notify(QUntypedPropertyData *propertyDataPtr) + \internal \a propertyDataPtr is a pointer to the observed property's property data */ -void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr) -{ - auto observer = const_cast<QPropertyObserver*>(ptr); - /* - * The basic idea of the loop is as follows: We iterate over all observers in the linked list, - * and execute the functionality corresponding to their tag. - * However, complication arise due to the fact that the triggered operations might modify the list, - * which includes deletion and move of the current and next nodes. - * Therefore, we take a few safety precautions: - * 1. Before executing any action which might modify the list, we insert a placeholder node after the current node. - * As that one is stack allocated and owned by us, we can rest assured that it is - * still there after the action has executed, and placeHolder->next points to the actual next node in the list. - * Note that taking next at the beginning of the loop does not work, as the execuated action might either move - * or delete that node. - * 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to - * retrieve the next node. - * 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that - * checks whether we're already have the same change handler in our call stack. This can be done by checking whether - * the node after the current one is a placeholder node. - */ - while (observer) { - QPropertyObserver *next = observer->next.data(); - switch (QPropertyObserver::ObserverTag(observer->next.tag())) { - case QPropertyObserver::ObserverNotifiesChangeHandler: - { - auto handlerToCall = observer->changeHandler; - // prevent recursion - if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) { - observer = next->next.data(); - continue; - } - // handlerToCall might modify the list - QPropertyObserverNodeProtector protector(observer); - handlerToCall(observer, propertyDataPtr); - next = protector.next(); - break; - } - case QPropertyObserver::ObserverNotifiesBinding: - { - auto bindingToNotify = observer->binding; - QPropertyObserverNodeProtector protector(observer); - bindingToNotify->notifyRecursive(); - next = protector.next(); - break; - } - case QPropertyObserver::ObserverIsPlaceholder: - // recursion is already properly handled somewhere else - break; - case QPropertyObserver::ObserverIsAlias: - break; - default: Q_UNREACHABLE(); - } - observer = next; - } -} #ifndef QT_NO_DEBUG void QPropertyObserverPointer::noSelfDependencies(QPropertyBindingPrivate *binding) @@ -828,7 +794,7 @@ void QPropertyObserverPointer::noSelfDependencies(QPropertyBindingPrivate *bindi } #endif -void QPropertyObserverPointer::evaluateBindings(QBindingStatus *status) +void QPropertyObserverPointer::evaluateBindings(PendingBindingObserverList &bindingObservers, QBindingStatus *status) { Q_ASSERT(status); auto observer = const_cast<QPropertyObserver*>(ptr); @@ -839,7 +805,9 @@ void QPropertyObserverPointer::evaluateBindings(QBindingStatus *status) if (QPropertyObserver::ObserverTag(observer->next.tag()) == QPropertyObserver::ObserverNotifiesBinding) { auto bindingToEvaluate = observer->binding; QPropertyObserverNodeProtector protector(observer); - bindingToEvaluate->evaluateRecursive_inline(status); + QBindingObserverPtr bindingObserver(observer); // binding must not be gone after evaluateRecursive_inline + if (bindingToEvaluate->evaluateRecursive_inline(bindingObservers, status)) + bindingObservers.push_back(std::move(bindingObserver)); next = protector.next(); } @@ -1185,7 +1153,8 @@ QString QPropertyBindingError::description() const \ingroup tools - QBindable\<T\> helps to integrate Qt's traditional Q_PROPERTY with binding-enabled properties. + QBindable\<T\> helps to integrate Qt's traditional Q_PROPERTY with + \l {Qt Bindable Properties}{binding-enabled} properties. If a property is backed by a QProperty, QObjectBindableProperty or QObjectComputedProperty, you can add \c BINDABLE bindablePropertyName to the Q_PROPERTY declaration, where bindablePropertyName is a function returning an instance of QBindable @@ -1196,7 +1165,40 @@ QString QPropertyBindingError::description() const \snippet code/src_corelib_kernel_qproperty.cpp 0 \snippet code/src_corelib_kernel_qproperty.cpp 3 - \sa QMetaProperty::isBindable, QProperty, QObjectBindableProperty + \sa QMetaProperty::isBindable, QProperty, QObjectBindableProperty, + QObjectComputedProperty, {Qt Bindable Properties} +*/ + +/*! + \fn template<typename T> QBindable<T>::QBindable(QObject *obj, const char *property) + + Constructs a QBindable for the \l Q_PROPERTY \a property on \a obj. The property must + have a notify signal but does not need to have \c BINDABLE in its \c Q_PROPERTY + definition, so even binding unaware \c {Q_PROPERTY}s can be bound or used in binding + expressions. You must use \c QBindable::value() in binding expressions instead of the + normal property \c READ function (or \c MEMBER) to enable dependency tracking if the + property is not \c BINDABLE. When binding using a lambda, you may prefer to capture the + QBindable by value to avoid the cost of calling this constructor in the binding + expression. + This constructor should not be used to implement \c BINDABLE for a Q_PROPERTY, as the + resulting Q_PROPERTY will not support dependency tracking. To make a property that is + usable directly without reading through a QBindable use \l QProperty or + \l QObjectBindableProperty. + + \code + QProperty<QString> displayText; + QDateTimeEdit *dateTimeEdit = findDateTimeEdit(); + QBindable<QDateTime> dateTimeBindable(dateTimeEdit, "dateTime"); + displayText.setBinding([dateTimeBindable](){ return dateTimeBindable.value().toString(); }); + \endcode + + \sa QProperty, QObjectBindableProperty, {Qt Bindable Properties} +*/ + +/*! + \fn template<typename T> QBindable<T>::QBindable(QObject *obj, const QMetaProperty &property) + + See \l QBindable::QBindable(QObject *obj, const char *property) */ /*! @@ -1277,6 +1279,15 @@ QString QPropertyBindingError::description() const dynamically, the binding expression. It is represented as a C++ lambda and can be used to express relationships between different properties in your application. + + \note For QML, it's important to expose the \l QProperty in \l Q_PROPERTY + with the BINDABLE keyword. As a result, the QML engine uses + it as the bindable interface to set up the property binding. In turn, the + binding can then be interacted with C++ via the normal API: + QProperty<T>::onValueChanged, QProperty::takeBinding and QBindable::hasBinding + If the property is BINDABLE, the engine will use the change-tracking + inherent to the C++ property system for getting notified about changes, and it + won't rely on signals being emitted. */ /*! @@ -1307,20 +1318,20 @@ QString QPropertyBindingError::description() const /*! \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. + Constructs a property that is tied to the provided \a binding expression. + The property's value is set to the result of evaluating the new binding. + Whenever a dependency of the binding changes, the binding will be re-evaluated, + and the property's value gets updated accordingly. */ /*! \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. -*/ + Constructs a property that is tied to the provided binding expression \a f. + The property's value is set to the result of evaluating the new binding. + Whenever a dependency of the binding changes, the binding will be re-evaluated, + and the property's value gets updated accordingly. + */ /*! \fn template <typename T> QProperty<T>::~QProperty() @@ -1351,23 +1362,13 @@ QString QPropertyBindingError::description() const */ /*! - \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. + expression and returns the previously associated binding. The property's value + is set to the result of evaluating the new binding. Whenever a dependency of + the binding changes, the binding will be re-evaluated, and the property's + value gets updated accordingly. */ /*! @@ -1375,10 +1376,10 @@ QString QPropertyBindingError::description() const \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. + returns the previously associated binding. The property's value is set to the + result of evaluating the new binding. Whenever a dependency of the binding + changes, the binding will be re-evaluated, and the property's value gets + updated accordingly. \sa {Formulating a Property Binding} */ @@ -1388,9 +1389,10 @@ QString QPropertyBindingError::description() const \overload Associates the value of this property with the provided \a newBinding - 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. + expression. The property's value is set to the result of evaluating the new + binding. Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + Returns true if the type of this property is the same as the type the binding function returns; false otherwise. @@ -1419,27 +1421,29 @@ QString QPropertyBindingError::description() const the value of the property changes. On each value change, the handler is either called immediately, or deferred, depending on the context. - 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + 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. On each value change, the handler - is either called immediately, or deferred, depending on the context. + Subscribes the given functor \a f as a callback that is called immediately and + whenever the value of the property changes in the future. On each value + change, the handler is either called immediately, or deferred, depending on + the context. - The callback \a f is expected to be a type that can be copied and has a plain call - operator() without any parameters. This means that you can provide a C++ lambda expression, - a std::function or even a custom struct with a call operator. + The callback \a f is expected to be a type that can be copied and has a plain + call operator() without any parameters. This means that you can provide a C++ + lambda expression, a 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. + The returned property change handler object keeps track of the subscription. + When it goes out of scope, the callback is unsubscribed. */ /*! @@ -1448,15 +1452,16 @@ QString QPropertyBindingError::description() const Subscribes the given functor \a f as a callback that is 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + The returned property change handler object keeps track of the subscription. + When it goes out of scope, the callback is unsubscribed. - This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. - It can therefore more easily be stored, e.g. as a member in a class. + This method is in some cases easier to use than onValueChanged(), as the + returned object is not a template. It can therefore more easily be stored, + e.g. as a member in a class. \sa onValueChanged(), subscribe() */ @@ -1469,8 +1474,9 @@ QString QPropertyBindingError::description() const /*! \class QObjectBindableProperty \inmodule QtCore - \brief The QObjectBindableProperty class is a template class that enables automatic property bindings - for property data stored in QObject derived classes. + \brief The QObjectBindableProperty class is a template class that enables + automatic property bindings for property data stored in QObject derived + classes. \since 6.0 \ingroup tools @@ -1479,13 +1485,13 @@ QString QPropertyBindingError::description() const instance of T and behaves mostly like \l QProperty. It is one of the classes implementing \l {Qt Bindable Properties}. Unlike QProperty, it stores its management data structure in - the sourrounding QObject. + the surrounding QObject. The extra template parameters are used to identify the surrounding class and a member function of that class acting as a change handler. - You can use QObjectBindableProperty to add binding support to code that uses Q_PROPERTY. - The getter and setter methods must be adapted carefully according to the - rules described in \l {Bindable Property Getters and Setters}. + You can use QObjectBindableProperty to add binding support to code that uses + Q_PROPERTY. The getter and setter methods must be adapted carefully according + to the rules described in \l {Bindable Property Getters and Setters}. In order to invoke the change signal on property changes, use QObjectBindableProperty and pass the change signal as a callback. @@ -1494,8 +1500,8 @@ QString QPropertyBindingError::description() const \snippet code/src_corelib_kernel_qproperty.cpp 4 - QObjectBindableProperty is usually not used directly, instead an instance of it is created by - using the Q_OBJECT_BINDABLE_PROPERTY macro. + QObjectBindableProperty is usually not used directly, instead an instance of + it is created by using the Q_OBJECT_BINDABLE_PROPERTY macro. Use the Q_OBJECT_BINDABLE_PROPERTY macro in the class declaration to declare the property as bindable. @@ -1514,23 +1520,27 @@ QString QPropertyBindingError::description() const \snippet code/src_corelib_kernel_qproperty.cpp 2 - The change handler can optionally accept one argument, of the same type as the property, - in which case it is passed the new value of the property. Otherwise, it should take no - arguments. + The change handler can optionally accept one argument, of the same type as the + property, in which case it is passed the new value of the property. Otherwise, + it should take no arguments. If the property does not need a changed notification, you can leave out the "NOTIFY xChanged" in the Q_PROPERTY macro as well as the last argument of the Q_OBJECT_BINDABLE_PROPERTY and Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS macros. + + \sa Q_OBJECT_BINDABLE_PROPERTY, Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS, + QProperty, QObjectComputedProperty, {Qt's Property System}, {Qt Bindable + Properties} */ /*! \macro Q_OBJECT_BINDABLE_PROPERTY(containingClass, type, name, signal) \since 6.0 \relates QObjectBindableProperty - \brief Declares a \l QObjectBindableProperty inside \a containingClass - of type \a type with name \a name. If the optional argument \a signal is given, - this signal will be emitted when the property is marked dirty. + \brief Declares a \l QObjectBindableProperty inside \a containingClass of type + \a type with name \a name. If the optional argument \a signal is given, this + signal will be emitted when the property is marked dirty. \sa {Qt's Property System}, {Qt Bindable Properties} */ @@ -1640,7 +1650,6 @@ QString QPropertyBindingError::description() const properties to the bindable property system. \since 6.0 \ingroup tools - \internal QObjectComputedProperty is a read-only property which is recomputed on each read. It does not store the computed value. @@ -1650,42 +1659,7 @@ QString QPropertyBindingError::description() const See the following example. - \code - class Client{}; - - class MyClassPrivate : public QObjectPrivate - { - public: - QList<Client> clients; - bool hasClientsActualCalculation() const { return clients.size() > 0; } - Q_OBJECT_COMPUTED_PROPERTY(MyClassPrivate, bool, hasClientsData, - &MyClassPrivate::hasClientsActualCalculation) - }; - - class MyClass : public QObject - { - // add q-object macro here (confuses qdoc if we do it here) - Q_PROPERTY(bool hasClients READ hasClients STORED false BINDABLE bindableHasClients) - public: - QBindable<bool> bindableHasClients() - { - return QBindable<bool>(&d_func()->hasClientsData); - } - bool hasClients() const - { - return d_func()->hasClientsData.value(); - } - void addClient(const Client &c) - { - Q_D(MyClass); - d->clients.push_back(c); - // notify that the value could have changed - d->hasClientsData.markDirty(); - } - private: - Q_DECLARE_PRIVATE(MyClass) - }; - \endcode + \snippet code/src_corelib_kernel_qproperty.cpp 5 The rules for getters in \l {Bindable Property Getters and Setters} also apply for QObjectComputedProperty. Especially, the getter @@ -1700,27 +1674,26 @@ QString QPropertyBindingError::description() const have changed. Whenever a bindable property used in the callback changes, this happens automatically. If the result of the callback might change because of a change in a value which is not a bindable property, - it is the developer's responsibility to call markDirty + it is the developer's responsibility to call \c notify on the QObjectComputedProperty object. This will inform dependent properties about the potential change. - Note that calling markDirty might trigger change handlers in dependent + Note that calling \c notify might trigger change handlers in dependent properties, which might in turn use the object the QObjectComputedProperty - is a member of. So markDirty must not be called when in a transitional + is a member of. So \c notify must not be called when in a transitional or invalid state. QObjectComputedProperty is not suitable for use with a computation that depends on any input that might change without notice, such as the contents of a file. - \sa Q_OBJECT_COMPUTED_PROPERTY, QObjectBindableProperty, {Qt's Property System}, - {Qt Bindable Properties} + \sa Q_OBJECT_COMPUTED_PROPERTY, QProperty, QObjectBindableProperty, + {Qt's Property System}, {Qt Bindable Properties} */ /*! \macro Q_OBJECT_COMPUTED_PROPERTY(containingClass, type, name, callback) \since 6.0 - \relates QObjectCompatProperty - \internal + \relates QObjectComputedProperty \brief Declares a \l QObjectComputedProperty inside \a containingClass of type \a type with name \a name. The argument \a callback specifies a GETTER function to be called when the property is evaluated. @@ -1749,30 +1722,35 @@ QString QPropertyBindingError::description() const /*! \fn template <typename Class, typename T, auto offset, auto Callback> QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty(Class *owner, 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. When the property value changes \a - owner is notified via the Callback function. + Constructs a property that is tied to the provided \a binding expression. + The property's value is set to the result of evaluating the new binding. + Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + + When the property value changes, \a owner is notified via the Callback + function. */ /*! \fn template <typename Class, typename T, auto offset, auto Callback> QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty(Class *owner, 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. When the property value changes \a + Constructs a property that is tied to the provided \a binding expression. + The property's value is set to the result of evaluating the new binding. + Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + + When the property value changes, \a owner is notified via the Callback function. */ /*! \fn template <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty(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. + Constructs a property that is tied to the provided binding expression \a f. + The property's value is set to the result of evaluating the new binding. + Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + */ /*! @@ -1800,14 +1778,15 @@ QString QPropertyBindingError::description() const /*! \fn template <typename Class, typename T, auto offset, auto Callback> void QObjectBindableProperty<Class, T, offset, Callback>::notify() - Programmatically signals a change of the property. Any binding which depend on it will - be notified, and if the property has a signal, it will be emitted. + Programmatically signals a change of the property. Any binding which depend on + it will be notified, and if the property has a signal, it will be emitted. - This can be useful in combination with setValueBypassingBindings to defer signalling the change - until a class invariant has been restored. + This can be useful in combination with setValueBypassingBindings to defer + signalling the change until a class invariant has been restored. - \note If this property has a binding (i.e. hasBinding() returns true), that binding is not reevaluated when - notify() is called. Any binding depending on this property is still reevaluated as usual. + \note If this property has a binding (i.e. hasBinding() returns true), that + binding is not reevaluated when notify() is called. Any binding depending on + this property is still reevaluated as usual. \sa Qt::beginPropertyUpdateGroup(), setValueBypassingBindings() */ @@ -1816,11 +1795,12 @@ QString QPropertyBindingError::description() const \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QObjectBindableProperty<Class, T, offset, Callback>::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. When the property value changes, the owner is notified - via the Callback function. + expression and returns the previously associated binding. + The property's value is set to the result of evaluating the new binding. Whenever a dependency of + the binding changes, the binding will be re-evaluated, + and the property's value gets updated accordingly. + When the property value changes, the owner + is notified via the Callback function. */ /*! @@ -1828,11 +1808,13 @@ QString QPropertyBindingError::description() const \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. When the property value - changes, the owner is notified via the Callback function. + returns the previously associated binding. The property's value is set to the + result of evaluating the new binding by invoking the call operator \c{()} of \a + f. Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + + When the property value changes, the owner is notified via the Callback + function. \sa {Formulating a Property Binding} */ @@ -1842,12 +1824,13 @@ QString QPropertyBindingError::description() const \overload Associates the value of this property with the provided \a newBinding - 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. + expression. The property's value is set to the result of evaluating the new + binding. Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. - Returns \c true if the type of this property is the same as the type the binding - function returns; \c false otherwise. + + Returns \c true if the type of this property is the same as the type the + binding function returns; \c false otherwise. */ /*! @@ -1877,47 +1860,49 @@ QString QPropertyBindingError::description() const \fn template <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyChangeHandler<T, Functor> QObjectBindableProperty<Class, T, offset, Callback>::onValueChanged(Functor f) Registers the given functor \a f as a callback that shall be called whenever - the value of the property changes. On each value change, the handler - is either called immediately, or deferred, depending on the context. + the value of the property changes. On each value change, the handler is either + called immediately, or deferred, depending on the context. - 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + 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 Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyChangeHandler<T, Functor> QObjectBindableProperty<Class, T, offset, Callback>::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. On each value change, the handler - is either called immediately, or deferred, depending on the context. + Subscribes the given functor \a f as a callback that is called immediately and + whenever the value of the property changes in the future. On each value + change, the handler is either called immediately, or deferred, depending on + the context. - 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + The returned property change handler object keeps track of the subscription. + When it goes out of scope, the callback is unsubscribed. */ /*! \fn template <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyNotifier QObjectBindableProperty<Class, T, offset, Callback>::addNotifier(Functor f) - Subscribes the given functor \a f as a callback that is called whenever - the value of the property changes. + Subscribes the given functor \a f as a callback that is 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + The returned property change handler object keeps track of the subscription. + When it goes out of scope, the callback is unsubscribed. - This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. - It can therefore more easily be stored, e.g. as a member in a class. + This method is in some cases easier to use than onValueChanged(), as the + returned object is not a template. It can therefore more easily be stored, + e.g. as a member in a class. \sa onValueChanged(), subscribe() */ @@ -1930,13 +1915,15 @@ QString QPropertyBindingError::description() const /*! \class QPropertyChangeHandler \inmodule QtCore - \brief The QPropertyChangeHandler class controls the lifecycle of change callback installed on a QProperty. + \brief The QPropertyChangeHandler class controls the lifecycle of change + callback installed on a QProperty. \ingroup tools - QPropertyChangeHandler\<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. + QPropertyChangeHandler\<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. */ @@ -1948,9 +1935,9 @@ QString QPropertyBindingError::description() const \ingroup tools - QPropertyNotifier is created when registering a - callback on a QProperty to listen to changes to the property's value, using QProperty::addNotifier. - As long as the change handler is alive, the callback remains installed. + QPropertyNotifier is created when registering a callback on a QProperty to + listen to changes to the property's value, using QProperty::addNotifier. As + long as the change handler is alive, the callback remains installed. A handler instance can be transferred between C++ scopes using move semantics. */ @@ -1960,7 +1947,8 @@ QString QPropertyBindingError::description() const \inmodule QtCore \internal - \brief The QPropertyAlias class is a safe alias for a QProperty with same template parameter. + \brief The QPropertyAlias class is a safe alias for a QProperty with same + template parameter. \ingroup tools @@ -2038,33 +2026,14 @@ QString QPropertyBindingError::description() const */ /*! - \fn template <typename T> QPropertyAlias<T> &QPropertyAlias<T>::operator=(T &&newValue) - \overload - - Assigns \a newValue to the aliased property and returns a reference to this - QPropertyAlias. -*/ - -/*! - \fn template <typename T> QPropertyAlias<T> &QPropertyAlias<T>::operator=(const QPropertyBinding<T> &newBinding) - \overload - - Associates the value of the aliased property with the provided \a newBinding - expression and returns a reference to this alias. The first time the - property value is read, either from the property itself or from any alias, 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> QPropertyAlias<T>::setBinding(const QPropertyBinding<T> &newBinding) Associates the value of the aliased property with the provided \a newBinding expression and returns any previous binding the associated with the aliased - property. The first time the property value is read, either from the property - itself or from any alias, 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. + property.The property's value is set to the result of evaluating the new + binding. Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + Returns any previous binding associated with the property, or a default-constructed QPropertyBinding<T>. @@ -2075,10 +2044,10 @@ QString QPropertyBindingError::description() const \overload Associates the value of the aliased property with the provided \a newBinding - expression. The first time the property value is read, either from the - property itself or from any alias, 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. + expression. The property's value is set to the result of evaluating the new + binding. Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + Returns true if the type of this property is the same as the type the binding function returns; false otherwise. @@ -2089,10 +2058,10 @@ QString QPropertyBindingError::description() const \overload Associates the value of the aliased property with the provided functor \a f - expression. The first time the property value is read, either from the - property itself or from any alias, 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. + expression. The property's value is set to the result of evaluating the new + binding. Whenever a dependency of the binding changes, the binding will be + re-evaluated, and the property's value gets updated accordingly. + Returns any previous binding associated with the property, or a default-constructed QPropertyBinding<T>. @@ -2130,9 +2099,9 @@ QString QPropertyBindingError::description() const the value of the aliased property changes. On each value change, the handler is either called immediately, or deferred, depending on the context. - 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. @@ -2141,16 +2110,17 @@ QString QPropertyBindingError::description() const /*! \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QPropertyAlias<T>::subscribe(Functor f) - Subscribes the given functor \a f as a callback that is called immediately and whenever - the value of the aliased property changes in the future. On each value change, the handler - is either called immediately, or deferred, depending on the context. + Subscribes the given functor \a f as a callback that is called immediately and + whenever the value of the aliased property changes in the future. On each + value change, the handler is either called immediately, or deferred, depending + on the context. - 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + The returned property change handler object keeps track of the subscription. + When it goes out of scope, the callback is unsubscribed. */ /*! @@ -2159,15 +2129,16 @@ QString QPropertyBindingError::description() const Subscribes the given functor \a f as a callback that is called whenever the value of the aliased 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, a std::function - or even a custom struct with a call operator. + The callback \a f is expected to be a type that has a plain call operator + \c{()} without any parameters. This means that you can provide a C++ lambda + expression, a 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. + The returned property change handler object keeps track of the subscription. + When it goes out of scope, the callback is unsubscribed. - This method is in some cases easier to use than onValueChanged(), as the returned object is not a template. - It can therefore more easily be stored, e.g. as a member in a class. + This method is in some cases easier to use than onValueChanged(), as the + returned object is not a template. It can therefore more easily be stored, + e.g. as a member in a class. \sa onValueChanged(), subscribe() */ @@ -2319,16 +2290,17 @@ QBindingStorage::~QBindingStorage() QBindingStoragePrivate(d).destroy(); } -void QBindingStorage::clear() +void QBindingStorage::reinitAfterThreadMove() { - QBindingStoragePrivate(d).destroy(); - d = nullptr; + bindingStatus = &QT_PREPEND_NAMESPACE(bindingStatus); + Q_ASSERT(bindingStatus); } -// ### Unused, retained for BC with 6.0 -void QBindingStorage::maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const +void QBindingStorage::clear() { - registerDependency_helper(data); + QBindingStoragePrivate(d).destroy(); + d = nullptr; + bindingStatus = nullptr; } void QBindingStorage::registerDependency_helper(const QUntypedPropertyData *data) const @@ -2361,6 +2333,11 @@ QPropertyBindingData *QBindingStorage::bindingData_helper(const QUntypedProperty return QBindingStoragePrivate(d).get(data); } +const QBindingStatus *QBindingStorage::status(QtPrivate::QBindingStatusAccessToken) const +{ + return bindingStatus; +} + QPropertyBindingData *QBindingStorage::bindingData_helper(QUntypedPropertyData *data, bool create) { return QBindingStoragePrivate(d).get(data, create); @@ -2402,8 +2379,10 @@ bool isAnyBindingEvaluating() bool isPropertyInBindingWrapper(const QUntypedPropertyData *property) { - return bindingStatus.currentCompatProperty && - bindingStatus.currentCompatProperty->property == property; + // Accessing bindingStatus is expensive because it's thread-local. Do it only once. + if (const auto current = bindingStatus.currentCompatProperty) + return current->property == property; + return false; } namespace BindableWarnings { @@ -2436,6 +2415,165 @@ void printMetaTypeMismatch(QMetaType actual, QMetaType expected) } // namespace BindableWarnings end +/*! + \internal + Returns the binding statusof the current thread. + */ +QBindingStatus* getBindingStatus(QtPrivate::QBindingStatusAccessToken) { return &QT_PREPEND_NAMESPACE(bindingStatus); } + +namespace PropertyAdaptorSlotObjectHelpers { +void getter(const QUntypedPropertyData *d, void *value) +{ + auto adaptor = static_cast<const QtPrivate::QPropertyAdaptorSlotObject *>(d); + adaptor->bindingData().registerWithCurrentlyEvaluatingBinding(); + auto mt = adaptor->metaProperty().metaType(); + mt.destruct(value); + mt.construct(value, adaptor->metaProperty().read(adaptor->object()).data()); +} + +void setter(QUntypedPropertyData *d, const void *value) +{ + auto adaptor = static_cast<QtPrivate::QPropertyAdaptorSlotObject *>(d); + adaptor->bindingData().removeBinding(); + adaptor->metaProperty().write(adaptor->object(), + QVariant(adaptor->metaProperty().metaType(), value)); +} + +QUntypedPropertyBinding getBinding(const QUntypedPropertyData *d) +{ + auto adaptor = static_cast<const QtPrivate::QPropertyAdaptorSlotObject *>(d); + return QUntypedPropertyBinding(adaptor->bindingData().binding()); +} + +bool bindingWrapper(QMetaType type, QUntypedPropertyData *d, + QtPrivate::QPropertyBindingFunction binding, QUntypedPropertyData *temp, + void *value) +{ + auto adaptor = static_cast<const QtPrivate::QPropertyAdaptorSlotObject *>(d); + type.destruct(value); + type.construct(value, adaptor->metaProperty().read(adaptor->object()).data()); + if (binding.vtable->call(type, temp, binding.functor)) { + adaptor->metaProperty().write(adaptor->object(), QVariant(type, value)); + return true; + } + return false; +} + +QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding, + QPropertyBindingWrapper wrapper) +{ + auto adaptor = static_cast<QPropertyAdaptorSlotObject *>(d); + return adaptor->bindingData().setBinding(binding, d, nullptr, wrapper); +} + +void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer) +{ + observer->setSource(static_cast<const QPropertyAdaptorSlotObject *>(d)->bindingData()); +} +} + +QPropertyAdaptorSlotObject::QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty &p) + : QSlotObjectBase(&impl), obj(o), metaProperty_(p) +{ +} + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +void QPropertyAdaptorSlotObject::impl(int which, QSlotObjectBase *this_, QObject *r, void **a, + bool *ret) +#else +void QPropertyAdaptorSlotObject::impl(QSlotObjectBase *this_, QObject *r, void **a, int which, + bool *ret) +#endif +{ + auto self = static_cast<QPropertyAdaptorSlotObject *>(this_); + switch (which) { + case Destroy: + delete self; + break; + case Call: + if (!self->bindingData_.hasBinding()) + self->bindingData_.notifyObservers(self); + break; + case Compare: + case NumOperations: + Q_UNUSED(r); + Q_UNUSED(a); + Q_UNUSED(ret); + break; + } +} + } // namespace QtPrivate end +QUntypedBindable::QUntypedBindable(QObject *obj, const QMetaProperty &metaProperty, + const QtPrivate::QBindableInterface *i) + : iface(i) +{ + if (!obj) + return; + + if (!metaProperty.isValid()) { + qCWarning(lcQPropertyBinding) << "QUntypedBindable: Property is not valid"; + return; + } + + if (metaProperty.isBindable()) { + *this = metaProperty.bindable(obj); + return; + } + + if (!metaProperty.hasNotifySignal()) { + qCWarning(lcQPropertyBinding) + << "QUntypedBindable: Property" << metaProperty.name() << "has no notify signal"; + return; + } + + auto metatype = iface->metaType(); + if (metaProperty.metaType() != metatype) { + qCWarning(lcQPropertyBinding) << "QUntypedBindable: Property" << metaProperty.name() + << "of type" << metaProperty.metaType().name() + << "does not match requested type" << metatype.name(); + return; + } + + // Test for name pointer equality proves it's exactly the same property + if (obj->metaObject()->property(metaProperty.propertyIndex()).name() != metaProperty.name()) { + qCWarning(lcQPropertyBinding) << "QUntypedBindable: Property" << metaProperty.name() + << "does not belong to this object"; + return; + } + + // Get existing binding data if it exists + auto adaptor = QObjectPrivate::get(obj)->getPropertyAdaptorSlotObject(metaProperty); + + if (!adaptor) { + adaptor = new QPropertyAdaptorSlotObject(obj, metaProperty); + + auto c = QObjectPrivate::connect(obj, metaProperty.notifySignalIndex(), obj, adaptor, + Qt::DirectConnection); + Q_ASSERT(c); + } + + data = adaptor; +} + +QUntypedBindable::QUntypedBindable(QObject *obj, const char *property, + const QtPrivate::QBindableInterface *i) + : QUntypedBindable( + obj, + [=]() -> QMetaProperty { + if (!obj) + return {}; + auto propertyIndex = obj->metaObject()->indexOfProperty(property); + if (propertyIndex < 0) { + qCWarning(lcQPropertyBinding) + << "QUntypedBindable: No property named" << property; + return {}; + } + return obj->metaObject()->property(propertyIndex); + }(), + i) +{ +} + QT_END_NAMESPACE |