diff options
Diffstat (limited to 'src/corelib/kernel/qproperty.cpp')
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 2239 |
1 files changed, 1659 insertions, 580 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 72f74ac1e8..caa9fce787 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -1,235 +1,480 @@ -/**************************************************************************** -** -** 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" #include <qscopedvaluerollback.h> +#include <QScopeGuard> +#include <QtCore/qloggingcategory.h> +#include <QThread> +#include <QtCore/qmetaobject.h> + +#include "qobject_p.h" QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcQPropertyBinding, "qt.qproperty.binding"); + using namespace QtPrivate; +void QPropertyBindingPrivatePtr::destroyAndFreeMemory() +{ + QPropertyBindingPrivate::destroyAndFreeMemory(static_cast<QPropertyBindingPrivate *>(d)); +} + +void QPropertyBindingPrivatePtr::reset(QtPrivate::RefCounted *ptr) noexcept +{ + if (ptr != d) { + if (ptr) + ptr->addRef(); + auto *old = std::exchange(d, ptr); + if (old && !old->deref()) + QPropertyBindingPrivate::destroyAndFreeMemory(static_cast<QPropertyBindingPrivate *>(d)); + } +} + + void QPropertyBindingDataPointer::addObserver(QPropertyObserver *observer) { - if (auto *binding = bindingPtr()) { - observer->prev = &binding->firstObserver.ptr; - observer->next = binding->firstObserver.ptr; + if (auto *b = binding()) { + observer->prev = &b->firstObserver.ptr; + observer->next = b->firstObserver.ptr; if (observer->next) observer->next->prev = &observer->next; - binding->firstObserver.ptr = observer; + b->firstObserver.ptr = observer; } else { - auto firstObserver = reinterpret_cast<QPropertyObserver*>(ptr->d_ptr & ~QPropertyBindingData::FlagMask); - observer->prev = reinterpret_cast<QPropertyObserver**>(&ptr->d_ptr); + auto &d = ptr->d_ref(); + Q_ASSERT(!(d & QPropertyBindingData::BindingBit)); + auto firstObserver = reinterpret_cast<QPropertyObserver*>(d); + observer->prev = reinterpret_cast<QPropertyObserver**>(&d); observer->next = firstObserver; if (observer->next) observer->next->prev = &observer->next; + d = reinterpret_cast<quintptr>(observer); } - setFirstObserver(observer); } -QPropertyBindingPrivate::~QPropertyBindingPrivate() +/*! + \internal + + QPropertyDelayedNotifications is used to manage delayed notifications in grouped property updates. + It acts as a pool allocator for QPropertyProxyBindingData, and has methods to manage delayed + notifications. + + \sa beginPropertyUpdateGroup, endPropertyUpdateGroup +*/ +struct QPropertyDelayedNotifications { - if (firstObserver) - firstObserver.unlink(); -} + // we can't access the dynamic page size as we need a constant value + // use 4096 as a sensible default + static constexpr inline auto PageSize = 4096; + int ref = 0; + QPropertyDelayedNotifications *next = nullptr; // in case we have more than size dirty properties... + qsizetype used = 0; + // Size chosen to avoid allocating more than one page of memory, while still ensuring + // that we can store many delayed properties without doing further allocations + static constexpr qsizetype size = (PageSize - 3*sizeof(void *))/sizeof(QPropertyProxyBindingData); + QPropertyProxyBindingData delayedProperties[size]; + + /*! + \internal + This method is called when a property attempts to notify its observers while inside of a + property update group. Instead of actually notifying, it replaces \a bindingData's d_ptr + with a QPropertyProxyBindingData. + \a bindingData and \a propertyData are the binding data and property data of the property + whose notify call gets delayed. + \sa QPropertyBindingData::notifyObservers + */ + void addProperty(const QPropertyBindingData *bindingData, QUntypedPropertyData *propertyData) { + if (bindingData->isNotificationDelayed()) + return; + auto *data = this; + while (data->used == size) { + if (!data->next) + // add a new page + data->next = new QPropertyDelayedNotifications; + data = data->next; + } + auto *delayed = data->delayedProperties + data->used; + *delayed = QPropertyProxyBindingData { bindingData->d_ptr, bindingData, propertyData }; + ++data->used; + // preserve the binding bit for faster access + quintptr bindingBit = bindingData->d_ptr & QPropertyBindingData::BindingBit; + bindingData->d_ptr = reinterpret_cast<quintptr>(delayed) | QPropertyBindingData::DelayedNotificationBit | bindingBit; + Q_ASSERT(bindingData->d_ptr > 3); + if (!bindingBit) { + if (auto observer = reinterpret_cast<QPropertyObserver *>(delayed->d_ptr)) + observer->prev = reinterpret_cast<QPropertyObserver **>(&delayed->d_ptr); + } + } -void QPropertyBindingPrivate::unlinkAndDeref() + /*! + \internal + Called in Qt::endPropertyUpdateGroup. For the QPropertyProxyBindingData at position + \a index, it + \list + \li restores the original binding data that was modified in addProperty and + \li evaluates any bindings which depend on properties that were changed inside + the group. + \endlist + Change notifications are sent later with notify (following the logic of separating + binding updates and notifications used in non-deferred updates). + */ + void evaluateBindings(PendingBindingObserverList &bindingObservers, qsizetype index, QBindingStatus *status) { + auto *delayed = delayedProperties + index; + auto *bindingData = delayed->originalBindingData; + if (!bindingData) + return; + + bindingData->d_ptr = delayed->d_ptr; + Q_ASSERT(!(bindingData->d_ptr & QPropertyBindingData::DelayedNotificationBit)); + if (!bindingData->hasBinding()) { + if (auto observer = reinterpret_cast<QPropertyObserver *>(bindingData->d_ptr)) + observer->prev = reinterpret_cast<QPropertyObserver **>(&bindingData->d_ptr); + } + + QPropertyBindingDataPointer bindingDataPointer{bindingData}; + QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); + if (observer) + observer.evaluateBindings(bindingObservers, status); + } + + /*! + \internal + Called in Qt::endPropertyUpdateGroup. For the QPropertyProxyBindingData at position + \a i, it + \list + \li resets the proxy binding data and + \li sends any pending notifications. + \endlist + */ + void notify(qsizetype index) { + auto *delayed = delayedProperties + index; + 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; + + if (observer) + observer.notify(delayed->propertyData); + } +}; + +Q_CONSTINIT static thread_local QBindingStatus bindingStatus; + +/*! + \since 6.2 + + \relates QProperty + + Marks the beginning of a property update group. Inside this group, + changing a property does neither immediately update any dependent properties + nor does it trigger change notifications. + Those are instead deferred until the group is ended by a call to endPropertyUpdateGroup. + + Groups can be nested. In that case, the deferral ends only after the outermost group has been + ended. + + \note Change notifications are only send after all property values affected by the group have + been updated to their new values. This allows re-establishing a class invariant if multiple + properties need to be updated, preventing any external observer from noticing an inconsistent + state. + + \sa Qt::endPropertyUpdateGroup, QScopedPropertyUpdateGroup +*/ +void Qt::beginPropertyUpdateGroup() { - propertyDataPtr = nullptr; - if (!ref.deref()) - delete this; + QPropertyDelayedNotifications *& groupUpdateData = bindingStatus.groupUpdateData; + if (!groupUpdateData) + groupUpdateData = new QPropertyDelayedNotifications; + ++groupUpdateData->ref; } -void QPropertyBindingPrivate::markDirtyAndNotifyObservers() +/*! + \since 6.2 + \relates QProperty + + Ends a property update group. If the outermost group has been ended, and deferred + binding evaluations and notifications happen now. + + \warning Calling endPropertyUpdateGroup without a preceding call to beginPropertyUpdateGroup + results in undefined behavior. + + \sa Qt::beginPropertyUpdateGroup, QScopedPropertyUpdateGroup +*/ +void Qt::endPropertyUpdateGroup() { - if (dirty) + auto status = &bindingStatus; + QPropertyDelayedNotifications *& groupUpdateData = status->groupUpdateData; + auto *data = groupUpdateData; + Q_ASSERT(data->ref); + if (--data->ref) return; - dirty = true; - if (requiresEagerEvaluation()) { - // these are compat properties that we will need to evaluate eagerly - evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); + groupUpdateData = nullptr; + // ensures that bindings are kept alive until endPropertyUpdateGroup concludes + PendingBindingObserverList bindingObservers; + // update all delayed properties + auto start = data; + while (data) { + for (qsizetype i = 0; i < data->used; ++i) + data->evaluateBindings(bindingObservers, i, status); + data = data->next; + } + // 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 (qsizetype i = 0; i < data->used; ++i) + data->notify(i); + delete std::exchange(data, data->next); } - if (firstObserver) - firstObserver.notify(this, propertyDataPtr); - if (hasStaticObserver) - staticObserverCallback(propertyDataPtr); } -bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged(const QUntypedPropertyData *data) +/*! + \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 (!dirty) - return false; + if (firstObserver) + firstObserver.unlink(); + if (vtable->size) + vtable->destroy(reinterpret_cast<std::byte *>(this) + + QPropertyBindingPrivate::getSizeEnsuringAlignment()); +} - if (updating) { - error = QPropertyBindingError(QPropertyBindingError::BindingLoop); - return false; +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; +} - /* - * Evaluating the binding might lead to the binding being broken. This can - * cause ref to reach zero at the end of the function. However, the - * updateGuard's destructor will then still trigger, trying to set the - * updating bool to its old value - * To prevent this, we create a QPropertyBindingPrivatePtr which ensures - * that the object is still alive when updateGuard's dtor runs. - */ - QPropertyBindingPrivatePtr keepAlive {this}; - QScopedValueRollback<bool> updateGuard(updating, true); - - BindingEvaluationState evaluationFrame(this); +QPropertyObserverPointer QPropertyBindingPrivate::allocateDependencyObserver_slow() +{ + ++dependencyObserverCount; + if (!heapObservers) + heapObservers.reset(new std::vector<QPropertyObserver>()); + return {&heapObservers->emplace_back()}; +} - bool changed = false; +void QPropertyBindingPrivate::unlinkAndDeref() +{ + clearDependencyObservers(); + propertyDataPtr = nullptr; + if (!deref()) + destroyAndFreeMemory(this); +} - Q_ASSERT(propertyDataPtr == data); - QUntypedPropertyData *mutable_data = const_cast<QUntypedPropertyData *>(data); +bool QPropertyBindingPrivate::evaluateRecursive(PendingBindingObserverList &bindingObservers, QBindingStatus *status) +{ + if (!status) + status = &bindingStatus; + return evaluateRecursive_inline(bindingObservers, status); +} - if (hasBindingWrapper) { - changed = staticBindingWrapper(metaType, mutable_data, evaluationFunction); - } else { - changed = evaluationFunction(metaType, mutable_data); +void QPropertyBindingPrivate::notifyNonRecursive(const PendingBindingObserverList &bindingObservers) +{ + notifyNonRecursive(); + for (auto &&bindingObserver: bindingObservers) { + bindingObserver.binding()->notifyNonRecursive(); } +} - dirty = false; - return changed; +QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRecursive() +{ + if (!pendingNotify) + return Delayed; + pendingNotify = false; + Q_ASSERT(!updating); + updating = true; + if (firstObserver) { + firstObserver.noSelfDependencies(this); + firstObserver.notify(propertyDataPtr); + } + if (hasStaticObserver) + staticObserverCallback(propertyDataPtr); + updating = false; + return Sent; } +/*! + Constructs a null QUntypedPropertyBinding. + + \sa isNull() +*/ QUntypedPropertyBinding::QUntypedPropertyBinding() = default; -QUntypedPropertyBinding::QUntypedPropertyBinding(QMetaType metaType, QUntypedPropertyBinding::BindingEvaluationFunction function, +/*! + \fn template<typename Functor> + QUntypedPropertyBinding(QMetaType metaType, Functor &&f, const QPropertyBindingSourceLocation &location) + + \internal +*/ + +/*! + \internal + + Constructs QUntypedPropertyBinding. Assumes that \a metaType, \a function and \a vtable match. + Unless a specialization of \c BindingFunctionVTable is used, this function should never be called + directly. +*/ +QUntypedPropertyBinding::QUntypedPropertyBinding(QMetaType metaType, const BindingFunctionVTable *vtable, void *function, const QPropertyBindingSourceLocation &location) { - d = new QPropertyBindingPrivate(metaType, std::move(function), std::move(location)); + std::byte *mem = new std::byte[QPropertyBindingPrivate::getSizeEnsuringAlignment() + vtable->size](); + d = new(mem) QPropertyBindingPrivate(metaType, vtable, std::move(location)); + vtable->moveConstruct(mem + QPropertyBindingPrivate::getSizeEnsuringAlignment(), function); } +/*! + Move-constructs a QUntypedPropertyBinding from \a other. + + \a other is left in a null state. + \sa isNull() +*/ QUntypedPropertyBinding::QUntypedPropertyBinding(QUntypedPropertyBinding &&other) : d(std::move(other.d)) { } +/*! + Copy-constructs a QUntypedPropertyBinding from \a other. +*/ QUntypedPropertyBinding::QUntypedPropertyBinding(const QUntypedPropertyBinding &other) : d(other.d) { } +/*! + Copy-assigns \a other to this QUntypedPropertyBinding. +*/ QUntypedPropertyBinding &QUntypedPropertyBinding::operator=(const QUntypedPropertyBinding &other) { d = other.d; return *this; } +/*! + Move-assigns \a other to this QUntypedPropertyBinding. + + \a other is left in a null state. + \sa isNull +*/ QUntypedPropertyBinding &QUntypedPropertyBinding::operator=(QUntypedPropertyBinding &&other) { d = std::move(other.d); return *this; } +/*! + \internal +*/ QUntypedPropertyBinding::QUntypedPropertyBinding(QPropertyBindingPrivate *priv) : d(priv) { } +/*! + Destroys the QUntypedPropertyBinding. +*/ QUntypedPropertyBinding::~QUntypedPropertyBinding() { } +/*! + Returns \c true if the \c QUntypedPropertyBinding is null. + This is only true for default-constructed and moved-from instances. + + \sa isNull() +*/ bool QUntypedPropertyBinding::isNull() const { return !d; } +/*! + Returns the error state of the binding. + + \sa QPropertyBindingError +*/ QPropertyBindingError QUntypedPropertyBinding::error() const { if (!d) return QPropertyBindingError(); - return d->bindingError(); + return static_cast<QPropertyBindingPrivate *>(d.get())->bindingError(); } +/*! + Returns the meta-type of the binding. + If the QUntypedPropertyBinding is null, an invalid QMetaType is returned. +*/ QMetaType QUntypedPropertyBinding::valueMetaType() const { if (!d) return QMetaType(); - return d->valueMetaType(); -} - -QPropertyBindingData::QPropertyBindingData(QPropertyBindingData &&other, QUntypedPropertyData *propertyDataPtr) -{ - std::swap(d_ptr, other.d_ptr); - QPropertyBindingDataPointer d{this}; - d.setFirstObserver(nullptr); - if (auto binding = d.bindingPtr()) - binding->setProperty(propertyDataPtr); -} - -void QPropertyBindingData::moveAssign(QPropertyBindingData &&other, QUntypedPropertyData *propertyDataPtr) -{ - if (&other == this) - return; - - QPropertyBindingDataPointer d{this}; - auto observer = d.firstObserver(); - d.setFirstObserver(nullptr); - - if (auto binding = d.bindingPtr()) { - binding->unlinkAndDeref(); - d_ptr &= FlagMask; - } - - std::swap(d_ptr, other.d_ptr); - - if (auto binding = d.bindingPtr()) - binding->setProperty(propertyDataPtr); - - d.setFirstObserver(observer.ptr); - - // The caller will have to notify observers. + return static_cast<QPropertyBindingPrivate *>(d.get())->valueMetaType(); } QPropertyBindingData::~QPropertyBindingData() { QPropertyBindingDataPointer d{this}; + if (isNotificationDelayed()) + proxyData()->originalBindingData = nullptr; for (auto observer = d.firstObserver(); observer;) { auto next = observer.nextObserver(); observer.unlink(); observer = next; } - if (auto binding = d.bindingPtr()) + if (auto binding = d.binding()) binding->unlinkAndDeref(); } @@ -244,62 +489,66 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB QPropertyBindingDataPointer d{this}; QPropertyObserverPointer observer; - if (auto *existingBinding = d.bindingPtr()) { + auto &data = d_ref(); + if (auto *existingBinding = d.binding()) { if (existingBinding == newBinding.data()) - return QUntypedPropertyBinding(oldBinding.data()); + return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); + if (existingBinding->isUpdating()) { + existingBinding->setError({QPropertyBindingError::BindingLoop, QStringLiteral("Binding set during binding evaluation!")}); + return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); + } oldBinding = QPropertyBindingPrivatePtr(existingBinding); - observer = oldBinding->takeObservers(); - oldBinding->unlinkAndDeref(); - d_ptr &= FlagMask; + observer = static_cast<QPropertyBindingPrivate *>(oldBinding.data())->takeObservers(); + static_cast<QPropertyBindingPrivate *>(oldBinding.data())->unlinkAndDeref(); + data = 0; } else { observer = d.firstObserver(); } if (newBinding) { - newBinding.data()->ref.ref(); - d_ptr = (d_ptr & FlagMask) | reinterpret_cast<quintptr>(newBinding.data()); - d_ptr |= BindingBit; - newBinding->setDirty(true); - newBinding->setProperty(propertyDataPtr); + newBinding.data()->addRef(); + data = reinterpret_cast<quintptr>(newBinding.data()); + data |= BindingBit; + auto newBindingRaw = static_cast<QPropertyBindingPrivate *>(newBinding.data()); + newBindingRaw->setProperty(propertyDataPtr); if (observer) - newBinding->prependObserver(observer); - newBinding->setStaticObserver(staticObserverCallback, guardCallback); - if (newBinding->requiresEagerEvaluation()) - newBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); + newBindingRaw->prependObserver(observer); + newBindingRaw->setStaticObserver(staticObserverCallback, guardCallback); + + PendingBindingObserverList bindingObservers; + newBindingRaw->evaluateRecursive(bindingObservers); + newBindingRaw->notifyNonRecursive(bindingObservers); } else if (observer) { d.setObservers(observer.ptr); } else { - d_ptr &= ~QPropertyBindingData::BindingBit; + data = 0; } if (oldBinding) - oldBinding->detachFromProperty(); + static_cast<QPropertyBindingPrivate *>(oldBinding.data())->detachFromProperty(); - return QUntypedPropertyBinding(oldBinding.data()); + return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data())); } -QPropertyBindingPrivate *QPropertyBindingData::binding() const +QPropertyBindingData::QPropertyBindingData(QPropertyBindingData &&other) : d_ptr(std::exchange(other.d_ptr, 0)) { - QPropertyBindingDataPointer d{this}; - if (auto binding = d.bindingPtr()) - return binding; - return nullptr; + QPropertyBindingDataPointer::fixupAfterMove(this); } -static thread_local QBindingStatus bindingStatus; - -BindingEvaluationState::BindingEvaluationState(QPropertyBindingPrivate *binding) +BindingEvaluationState::BindingEvaluationState(QPropertyBindingPrivate *binding, QBindingStatus *status) : binding(binding) { + Q_ASSERT(status); + QBindingStatus *s = status; // store a pointer to the currentBindingEvaluationState to avoid a TLS lookup in // the destructor (as these come with a non zero cost) - currentState = &bindingStatus.currentlyEvaluatingBinding; + currentState = &s->currentlyEvaluatingBinding; previousState = *currentState; *currentState = this; binding->clearDependencyObservers(); } -CurrentCompatProperty::CurrentCompatProperty(QBindingStatus *status, QUntypedPropertyData *property) +CompatPropertySafePoint::CompatPropertySafePoint(QBindingStatus *status, QUntypedPropertyData *property) : property(property) { // store a pointer to the currentBindingEvaluationState to avoid a TLS lookup in @@ -307,6 +556,10 @@ CurrentCompatProperty::CurrentCompatProperty(QBindingStatus *status, QUntypedPro currentState = &status->currentCompatProperty; previousState = *currentState; *currentState = this; + + currentlyEvaluatingBindingList = &bindingStatus.currentlyEvaluatingBinding; + bindingState = *currentlyEvaluatingBindingList; + *currentlyEvaluatingBindingList = nullptr; } QPropertyBindingPrivate *QPropertyBindingPrivate::currentlyEvaluatingBinding() @@ -315,26 +568,26 @@ QPropertyBindingPrivate *QPropertyBindingPrivate::currentlyEvaluatingBinding() return currentState ? currentState->binding : nullptr; } -void QPropertyBindingData::evaluateIfDirty(const QUntypedPropertyData *property) const +// ### Unused, kept for BC with 6.0 +void QPropertyBindingData::evaluateIfDirty(const QUntypedPropertyData *) const { - QPropertyBindingDataPointer d{this}; - QPropertyBindingPrivate *binding = d.bindingPtr(); - if (!binding) - return; - binding->evaluateIfDirtyAndReturnTrueIfValueChanged(property); } -void QPropertyBindingData::removeBinding() +void QPropertyBindingData::removeBinding_helper() { QPropertyBindingDataPointer d{this}; - if (auto *existingBinding = d.bindingPtr()) { - auto observer = existingBinding->takeObservers(); - d_ptr &= ExtraBit; - if (observer) - d.setObservers(observer.ptr); - existingBinding->unlinkAndDeref(); + auto *existingBinding = d.binding(); + Q_ASSERT(existingBinding); + if (existingBinding->isSticky()) { + return; } + + auto observer = existingBinding->takeObservers(); + d_ref() = 0; + if (observer) + d.setObservers(observer.ptr); + existingBinding->unlinkAndDeref(); } void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding() const @@ -342,43 +595,98 @@ void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding() const auto currentState = bindingStatus.currentlyEvaluatingBinding; if (!currentState) return; + registerWithCurrentlyEvaluatingBinding_helper(currentState); +} + +void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding_helper(BindingEvaluationState *currentState) const +{ QPropertyBindingDataPointer d{this}; + if (currentState->alreadyCaptureProperties.contains(this)) + return; + else + currentState->alreadyCaptureProperties.push_back(this); + QPropertyObserverPointer dependencyObserver = currentState->binding->allocateDependencyObserver(); - dependencyObserver.setBindingToMarkDirty(currentState->binding); - dependencyObserver.observeProperty(d); + Q_ASSERT(QPropertyObserver::ObserverNotifiesBinding == 0); + dependencyObserver.setBindingToNotify_unsafe(currentState->binding); + d.addObserver(dependencyObserver.ptr); } void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr) const { + notifyObservers(propertyDataPtr, nullptr); +} + +void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr, QBindingStorage *storage) const +{ + if (isNotificationDelayed()) + return; QPropertyBindingDataPointer d{this}; - if (QPropertyObserverPointer observer = d.firstObserver()) - observer.notify(d.bindingPtr(), propertyDataPtr); + + PendingBindingObserverList bindingObservers; + if (QPropertyObserverPointer observer = d.firstObserver()) { + 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(); + } + } } -int QPropertyBindingDataPointer::observerCount() const +QPropertyBindingData::NotificationResult QPropertyBindingData::notifyObserver_helper +( + QUntypedPropertyData *propertyDataPtr, QBindingStorage *storage, + QPropertyObserverPointer observer, + PendingBindingObserverList &bindingObservers) const { - int count = 0; - for (auto observer = firstObserver(); observer; observer = observer.nextObserver()) - ++count; - return count; +#ifdef QT_HAS_FAST_CURRENT_THREAD_ID + QBindingStatus *status = storage ? storage->bindingStatus : nullptr; + if (!status || status->threadId != QThread::currentThreadId()) + status = &bindingStatus; +#else + Q_UNUSED(storage); + QBindingStatus *status = &bindingStatus; +#endif + if (QPropertyDelayedNotifications *delay = status->groupUpdateData) { + delay->addProperty(this, propertyDataPtr); + return Delayed; + } + + observer.evaluateBindings(bindingObservers, status); + return Evaluated; } + QPropertyObserver::QPropertyObserver(ChangeHandler changeHandler) { QPropertyObserverPointer d{this}; d.setChangeHandler(changeHandler); } -QPropertyObserver::QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr) +#if QT_DEPRECATED_SINCE(6, 6) +QPropertyObserver::QPropertyObserver(QUntypedPropertyData *data) { - QPropertyObserverPointer d{this}; - d.setAliasedProperty(aliasedPropertyPtr); + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + aliasData = data; + next.setTag(ObserverIsAlias); + QT_WARNING_POP } +#endif /*! \internal - */ +*/ void QPropertyObserver::setSource(const QPropertyBindingData &property) { QPropertyObserverPointer d{this}; @@ -386,36 +694,35 @@ void QPropertyObserver::setSource(const QPropertyBindingData &property) d.observeProperty(propPrivate); } - QPropertyObserver::~QPropertyObserver() { QPropertyObserverPointer d{this}; d.unlink(); } -QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) +QPropertyObserver::QPropertyObserver(QPropertyObserver &&other) noexcept { - std::swap(bindingToMarkDirty, other.bindingToMarkDirty); - std::swap(next, other.next); - std::swap(prev, other.prev); + binding = std::exchange(other.binding, {}); + next = std::exchange(other.next, {}); + prev = std::exchange(other.prev, {}); if (next) next->prev = &next; if (prev) prev.setPointer(this); } -QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) +QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) noexcept { if (this == &other) return *this; QPropertyObserverPointer d{this}; d.unlink(); - bindingToMarkDirty = nullptr; + binding = nullptr; - std::swap(bindingToMarkDirty, other.bindingToMarkDirty); - std::swap(next, other.next); - std::swap(prev, other.prev); + binding = std::exchange(other.binding, {}); + next = std::exchange(other.next, {}); + prev = std::exchange(other.prev, {}); if (next) next->prev = &next; if (prev) @@ -424,66 +731,86 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) return *this; } -void QPropertyObserverPointer::unlink() -{ - if (ptr->next.tag() & QPropertyObserver::ObserverNotifiesAlias) - ptr->aliasedPropertyData = nullptr; - 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 + */ + + +/*! + \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::setChangeHandler(QPropertyObserver::ChangeHandler changeHandler) { + Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder); ptr->changeHandler = changeHandler; ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler); } -void QPropertyObserverPointer::setAliasedProperty(QUntypedPropertyData *property) +/*! + \internal + The same as setBindingToNotify, but assumes that the tag is already correct. + */ +void QPropertyObserverPointer::setBindingToNotify_unsafe(QPropertyBindingPrivate *binding) { - ptr->aliasedPropertyData = property; - ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias); + Q_ASSERT(ptr->next.tag() == QPropertyObserver::ObserverNotifiesBinding); + ptr->binding = binding; } -void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding) -{ - ptr->bindingToMarkDirty = binding; - ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); -} +/*! + \class QPropertyObserverNodeProtector + \internal + QPropertyObserverNodeProtector is a RAII wrapper which takes care of the internal switching logic + for QPropertyObserverPointer::notify (described ibidem) +*/ -void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr) -{ - bool knownIfPropertyChanged = false; - bool propertyChanged = true; +/*! + \fn QPropertyObserverNodeProtector::notify(QUntypedPropertyData *propertyDataPtr) + \internal + \a propertyDataPtr is a pointer to the observed property's property data +*/ +#ifndef QT_NO_DEBUG +void QPropertyObserverPointer::noSelfDependencies(QPropertyBindingPrivate *binding) +{ auto observer = const_cast<QPropertyObserver*>(ptr); + // See also comment in notify() while (observer) { - auto * const next = observer->next.data(); - switch (observer->next.tag()) { - case QPropertyObserver::ObserverNotifiesChangeHandler: - if (!knownIfPropertyChanged && triggeringBinding) { - knownIfPropertyChanged = true; - - propertyChanged = triggeringBinding->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); + if (QPropertyObserver::ObserverTag(observer->next.tag()) == QPropertyObserver::ObserverNotifiesBinding) + if (observer->binding == binding) { + qCritical("Property depends on itself!"); + break; } - if (!propertyChanged) - return; - if (auto handlerToCall = std::exchange(observer->changeHandler, nullptr)) { - handlerToCall(observer, propertyDataPtr); - observer->changeHandler = handlerToCall; - } - break; - case QPropertyObserver::ObserverNotifiesBinding: - if (observer->bindingToMarkDirty) - observer->bindingToMarkDirty->markDirtyAndNotifyObservers(); - break; - case QPropertyObserver::ObserverNotifiesAlias: - break; + observer = observer->next.data(); + } + +} +#endif + +void QPropertyObserverPointer::evaluateBindings(PendingBindingObserverList &bindingObservers, QBindingStatus *status) +{ + Q_ASSERT(status); + auto observer = const_cast<QPropertyObserver*>(ptr); + // See also comment in notify() + while (observer) { + QPropertyObserver *next = observer->next.data(); + + if (QPropertyObserver::ObserverTag(observer->next.tag()) == QPropertyObserver::ObserverNotifiesBinding) { + auto bindingToEvaluate = observer->binding; + QPropertyObserverNodeProtector protector(observer); + 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(); } + observer = next; } } @@ -495,10 +822,61 @@ void QPropertyObserverPointer::observeProperty(QPropertyBindingDataPointer prope property.addObserver(ptr); } +/*! + \class QPropertyBindingError + \inmodule QtCore + \ingroup tools + \since 6.0 + + QPropertyBindingError is used by \l{The Property System}{the property + system} to report errors that occurred when a binding was evaluated. Use \l + type() to query which error occurred, and \l + description() to extract an error message which might contain + more details. + If there is no error, QPropertyBindingError has type + \c QPropertyBindingError::NoError and \c hasError() returns false. + + \code + extern QProperty<int> prop; + + QPropertyBindingError error = prop.binding().error(); + if (error.hasError()) + qDebug() << error.description(); + \endcode +*/ + +/*! + \enum QPropertyBindingError::Type + + This enum specifies which error occurred. + + \value NoError + No error occurred while evaluating the binding. + \value BindingLoop + Binding evaluation was stopped because a property depended on its own + value. + \value EvaluationError + Binding evaluation was stopped for any other reason than a binding loop. + For example, this value is used in the QML engine when an exception occurs + while a binding is evaluated. + \value UnknownError + A generic error type used when neither of the other values is suitable. + Calling \l description() might provide details. +*/ + +/*! + Default constructs QPropertyBindingError. + hasError() will return false, type will return \c NoError and + \l description() will return an empty string. +*/ QPropertyBindingError::QPropertyBindingError() { } +/*! + Constructs a QPropertyBindingError of type \a type with \a description as its + description. +*/ QPropertyBindingError::QPropertyBindingError(Type type, const QString &description) { if (type != NoError) { @@ -508,32 +886,54 @@ QPropertyBindingError::QPropertyBindingError(Type type, const QString &descripti } } +/*! + Copy-constructs QPropertyBindingError from \a other. +*/ QPropertyBindingError::QPropertyBindingError(const QPropertyBindingError &other) : d(other.d) { } +/*! + Copies \a other to this QPropertyBindingError. +*/ QPropertyBindingError &QPropertyBindingError::operator=(const QPropertyBindingError &other) { d = other.d; return *this; } +/*! + Move-constructs QPropertyBindingError from \a other. + \a other will be left in its default state. +*/ QPropertyBindingError::QPropertyBindingError(QPropertyBindingError &&other) : d(std::move(other.d)) { } +/*! + Move-assigns \a other to this QPropertyBindingError. + \a other will be left in its default state. +*/ QPropertyBindingError &QPropertyBindingError::operator=(QPropertyBindingError &&other) { d = std::move(other.d); return *this; } +/*! + Destroys the QPropertyBindingError. +*/ QPropertyBindingError::~QPropertyBindingError() { } +/*! + Returns the type of the QPropertyBindingError. + + \sa QPropertyBindingError::Type +*/ QPropertyBindingError::Type QPropertyBindingError::type() const { if (!d) @@ -541,6 +941,10 @@ QPropertyBindingError::Type QPropertyBindingError::type() const return d->type; } +/*! + Returns a descriptive error message for the QPropertyBindingError if + it has been set. +*/ QString QPropertyBindingError::description() const { if (!d) @@ -563,27 +967,27 @@ QString QPropertyBindingError::description() const used with care, as updates to the values will not get propagated to any bindings that depend on this property. - You should usually call value() and setValue() on QProperty<T> or QBindablePropertyData<T>, not use + You should usually call value() and setValue() on QProperty<T> or QObjectBindableProperty<T>, not use the low level mechanisms provided in this class. */ -/*! \fn QPropertyData<T>::parameter_type QPropertyData<T>::valueBypassingBindings() const +/*! \fn template <typename T> QPropertyData<T>::parameter_type QPropertyData<T>::valueBypassingBindings() const - \returns the data stored in this property. + Returns the data stored in this property. \note As this will bypass any binding evaluation it might return an outdated value if a binding is set on this property. Using this method will also not register the property access with any currently executing binding. */ -/*! \fn void QPropertyData<T>::setValueBypassingBindings(parameter_type v) +/*! \fn template <typename T> void QPropertyData<T>::setValueBypassingBindings(parameter_type v) Sets the data value stored in this property to \a v. \note Using this method will bypass any potential binding registered for this property. */ -/*! \fn void QPropertyData<T>::setValueBypassingBindings(rvalue_ref v) +/*! \fn template <typename T> void QPropertyData<T>::setValueBypassingBindings(rvalue_ref v) \overload Sets the data value stored in this property to \a v. @@ -591,190 +995,380 @@ QString QPropertyBindingError::description() const \note Using this method will bypass any potential binding registered for this property. */ - /*! - \class QProperty + \class QUntypedBindable \inmodule QtCore - \brief The QProperty class is a template class that enables automatic property bindings. + \brief QUntypedBindable is a uniform interface over bindable properties like \c QProperty\<T\> + and \c QObjectBindableProperty of any type \c T. \since 6.0 \ingroup tools - QProperty\<T\> is a generic container that holds an instance of T. You can assign - a value to it and you can read it via the value() function or the T conversion - operator. You can also tie the property to an expression that computes the value - dynamically, the binding expression. It is represented as a C++ lambda and - can be used to express relationships between different properties in your - application. + QUntypedBindable is a fully type-erased generic interface to wrap bindable properties. + You can use it to interact with properties without knowing their type nor caring what + kind of bindable property they are (e.g. QProperty or QObjectBindableProperty). + For most use cases, using QBindable\<T\> (which is generic over the property implementation + but has a fixed type) should be preferred. +*/ - 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: +/*! + \fn QUntypedBindable::QUntypedBindable() - \code - QProperty<QString> firstname("John"); - QProperty<QString> lastname("Smith"); - QProperty<int> age(41); + Default-constructs a QUntypedBindable. It is in an invalid state. + \sa isValid() +*/ - QProperty<QString> fullname; - fullname.setBinding([&]() { return firstname.value() + " " + lastname.value() + " age:" + QString::number(age.value()); }); +/*! + \fn template<typename Property> QUntypedBindable::QUntypedBindable(Property *property) - qDebug() << fullname.value(); // Prints "John Smith age: 41" + Constructs a QUntypedBindable from the property \a property. If Property is const, + the QUntypedBindable will be read only. If \a property is null, the QUntypedBindable + will be invalid. - firstname = "Emma"; // Marks binding expression as dirty + \sa isValid(), isReadOnly() +*/ - qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 41" +/*! + \fn bool QUntypedBindable::isValid() const - // Birthday is coming up - age.setValue(age.value() + 1); + Returns true if the QUntypedBindable is valid. Methods called on an invalid + QUntypedBindable generally have no effect, unless otherwise noted. +*/ - qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 42" - \endcode +/*! + \fn bool QUntypedBindable::isReadOnly() const + \since 6.1 - 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. + Returns true if the QUntypedBindable is read-only. +*/ - 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. +/*! + \fn bool QUntypedBindable::isBindable() const + \internal - 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. + Returns true if the underlying property's binding can be queried + with binding() and, if not read-only, changed with setBinding. + Only QObjectComputedProperty currently leads to this method returning + false. - \section1 Tracking properties + \sa isReadOnly() +*/ - 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. +/*! + \fn QUntypedPropertyBinding QUntypedBindable::makeBinding(const QPropertyBindingSourceLocation &location) const - 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. + Creates a binding returning the underlying properties' value, using a specified source \a location. */ /*! - \fn template <typename T> QProperty<T>::QProperty() + \fn void QUntypedBindable::observe(QPropertyObserver *observer) + \internal - Constructs a property with a default constructed instance of T. + Installs the observer on the underlying property. */ /*! - \fn template <typename T> explicit QProperty<T>::QProperty(const T &initialValue) + \fn template<typename Functor> QPropertyChangeHandler<Functor> QUntypedBindable::onValueChanged(Functor f) const - Constructs a property with the provided \a initialValue. + Installs \a f as a change handler. Whenever the underlying property changes, \a f will be called, as + long as the returned \c QPropertyChangeHandler and the property are kept alive. + On each value change, the handler is either called immediately, or deferred, depending on the context. + + \sa onValueChanged(), subscribe() */ /*! - \fn template <typename T> explicit QProperty<T>::QProperty(T &&initialValue) + \fn template<typename Functor> QPropertyChangeHandler<Functor> QUntypedBindable::subscribe(Functor f) const - Move-Constructs a property with the provided \a initialValue. + Behaves like a call to \a f followed by \c onValueChanged(f), + + \sa onValueChanged() */ /*! - \fn template <typename T> QProperty<T>::QProperty(QProperty<T> &&other) + \fn template<typename Functor> QPropertyNotifier QUntypedBindable::addNotifier(Functor f) - Move-constructs a QProperty instance, making it point at the same object that - \a other was pointing to. + Installs \a f as a change handler. Whenever the underlying property changes, \a f will be called, as + long as the returned \c QPropertyNotifier and the property are kept alive. + + 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() */ /*! - \fn template <typename T> QProperty<T> &QProperty<T>::operator=(QProperty &&other) + \fn QUntypedPropertyBinding QUntypedBindable::binding() const - Move-assigns \a other to this QProperty instance. + Returns the underlying property's binding if there is any, or a default + constructed QUntypedPropertyBinding otherwise. + + \sa hasBinding() */ /*! - \fn template <typename T> QProperty<T>::QProperty(const QPropertyBinding<T> &binding) + \fn QUntypedPropertyBinding QUntypedBindable::takeBinding() - 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. + Removes the currently set binding from the property and returns it. + Returns a default-constructed QUntypedPropertyBinding if no binding is set. + + \since 6.1 */ /*! - \fn template <typename T> template <typename Functor> QProperty<T>::QProperty(Functor &&f) + \fn bool QUntypedBindable::setBinding(const QUntypedPropertyBinding &binding) + + Sets the underlying property's binding to \a binding. This does not have any effect + if the QUntypedBindable is read-only, null or if \a binding's type does match the + underlying property's type. - 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. + \return \c true when the binding was successfully set. + + //! \sa QUntypedPropertyBinding::valueMetaType() */ /*! - \fn template <typename T> QProperty<T>::~QProperty() + \fn bool QUntypedBindable::hasBinding() const - Destroys the property. + Returns \c true if the underlying property has a binding. */ /*! - \fn template <typename T> T QProperty<T>::value() const + \fn QMetaType QUntypedBindable::metaType() const + \since 6.2 - Returns the value of the property. This may evaluate a binding expression that - is tied to this property, before returning the value. + Returns the metatype of the property from which the QUntypedBindable was created. + If the bindable is invalid, an invalid metatype will be returned. + + \sa isValid() + //! \sa QUntypedPropertyBinding::valueMetaType() */ /*! - \fn template <typename T> QProperty<T>::operator T() const + \class QBindable + \inmodule QtCore + \brief QBindable is a wrapper class around binding-enabled properties. It allows type-safe + operations while abstracting the differences between the various property classes away. + \inherits QUntypedBindable - Returns the value of the property. This may evaluate a binding expression that - is tied to this property, before returning the value. + \ingroup tools + + 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 + constructed from the QProperty. The returned QBindable allows users of the property to set + and query bindings of the property, without having to know the exact kind of binding-enabled + property used. + + \snippet code/src_corelib_kernel_qproperty.cpp 0 + \snippet code/src_corelib_kernel_qproperty.cpp 3 + + \sa QMetaProperty::isBindable, QProperty, QObjectBindableProperty, + QObjectComputedProperty, {Qt Bindable Properties} */ /*! - \fn template <typename T> void QProperty<T>::setValue(const T &newValue) + \fn template<typename T> QBindable<T>::QBindable(QObject *obj, const char *property) - Assigns \a newValue to this property and removes the property's associated - binding, if present. + 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> void QProperty<T>::setValue(T &&newValue) - \overload + \fn template<typename T> QBindable<T>::QBindable(QObject *obj, const QMetaProperty &property) - Assigns \a newValue to this property and removes the property's associated - binding, if present. + See \l QBindable::QBindable(QObject *obj, const char *property) */ /*! - \fn template <typename T> QProperty<T> &QProperty<T>::operator=(const T &newValue) + \fn template<typename T> QPropertyBinding<T> QBindable<T>::makeBinding(const QPropertyBindingSourceLocation &location) const - Assigns \a newValue to this property and returns a reference to this QProperty. + Constructs a binding evaluating to the underlying property's value, using a specified source + \a location. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QBindable<T>::binding() const + + Returns the currently set binding of the underlying property. If the property does not + have a binding, the returned \c QPropertyBinding<T> will be invalid. + + \sa setBinding, hasBinding + //! \sa QPropertyBinding::isValid() +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QBindable<T>::takeBinding() + + Removes the currently set binding of the underlying property and returns it. + If the property does not have a binding, the returned \c QPropertyBinding<T> will be invalid. + + \sa binding, setBinding, hasBinding + //! \sa QPropertyBinding::isValid() */ + /*! - \fn template <typename T> QProperty<T> &QProperty<T>::operator=(T &&newValue) + \fn template <typename T> void QBindable<T>::setBinding(const QPropertyBinding<T> &binding) + + Sets the underlying property's binding to \a binding. Does nothing if the QBindable is + read-only or invalid. + + \sa binding, isReadOnly(), isValid() + //! \sa QPropertyBinding::isValid() +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyBinding<T> QBindable<T>::setBinding(Functor f); \overload - Assigns \a newValue to this property and returns a reference to this QProperty. + Creates a \c QPropertyBinding<T> from \a f, and sets it as the underlying property's binding. */ /*! - \fn template <typename T> QProperty<T> &QProperty<T>::operator=(const QPropertyBinding<T> &newBinding) + \fn template <typename T> T QBindable<T>::value() const - 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. + Returns the underlying property's current value. If the QBindable is invalid, + a default constructed \c T is returned. + + \sa isValid() +*/ + +/*! + \fn template <typename T> void QBindable<T>::setValue(const T &value) + + Sets the underlying property's value to \a value. This removes any currenltly set + binding from it. This function has no effect if the QBindable is read-only or invalid. + + \sa isValid(), isReadOnly(), setBinding() +*/ + +/*! + \class QProperty + \inmodule QtCore + \brief The QProperty class is a template class that enables automatic property bindings. + \since 6.0 + + \ingroup tools + + QProperty\<T\> is one of the classes implementing \l {Qt Bindable Properties}. + It is a 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. + + \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. +*/ + +/*! + \fn template <typename T> QProperty<T>::QProperty() + + Constructs a property with a default constructed instance of T. +*/ + +/*! + \fn template <typename T> explicit QProperty<T>::QProperty(const T &initialValue) + + Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename T> explicit QProperty<T>::QProperty(T &&initialValue) + + Move-Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename T> QProperty<T>::QProperty(QProperty<T> &&other) + + Move-constructs a QProperty instance, making it point at the same object that + \a other was pointing to. +*/ + +/*! + \fn template <typename T> QProperty<T>::QProperty(const QPropertyBinding<T> &binding) + + 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 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() + + Destroys the property. +*/ + +/*! + \fn template <typename T> T QProperty<T>::value() const + + Returns the value of the property. This may evaluate a binding expression that + is tied to this property, before returning the value. +*/ + +/*! + \fn template <typename T> void QProperty<T>::setValue(rvalue_ref newValue) + \fn template <typename T> void QProperty<T>::setValue(parameter_type newValue) + + Assigns \a newValue to this property and removes the property's associated + binding, if present. +*/ + +/*! + \fn template <typename T> QProperty<T> &QProperty<T>::operator=(rvalue_ref newValue) + \fn template <typename T> QProperty<T> &QProperty<T>::operator=(parameter_type newValue) + + Assigns \a newValue to this property and returns a reference to this QProperty. */ /*! \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. */ /*! @@ -782,21 +1376,12 @@ 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. -*/ - -/*! - \fn template <typename T> QPropertyBinding<T> QProperty<T>::setBinding(QPropertyBinding<T> &&newBinding) - \overload + 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. - 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. + \sa {Formulating a Property Binding} */ /*! @@ -804,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. @@ -832,28 +1418,52 @@ QString QPropertyBindingError::description() const \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QProperty<T>::onValueChanged(Functor f) Registers the given functor \a f as a callback that shall be called whenever - the value of the property changes. + the 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, an 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. + 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, an 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. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyNotifier QProperty<T>::addNotifier(Functor f) + + 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 + \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. + + 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() */ /*! @@ -862,123 +1472,303 @@ QString QPropertyBindingError::description() const */ /*! - \class QBindablePropertyData + \class QObjectBindableProperty \inmodule QtCore - \brief The QBindablePropertyData 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 - QBindablePropertyData is a generic container that holds an - instance of T and behaves mostly like \l QProperty. The extra template - parameters are used to identify the surrounding class and a member function of - that class. The member function will be called whenever the value held by the - property changes. + QObjectBindableProperty is a generic container that holds an + 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 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 QBindablePropertyData to add binding support to code that uses Q_PROPERTY. - The getter and setter methods are easy to adapt for accessing a \l QBindablePropertyData - rather than the plain value. In order to invoke the change signal on property changes, use - QBindablePropertyData and pass the change signal as a callback. + 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}. - QBindablePropertyData is usually not used directly, instead an instance of it is created by - using the Q_BINDABLE_PROPERTY_DATA macro. + In order to invoke the change signal on property changes, use + QObjectBindableProperty and pass the change signal as a callback. - Use the Q_BINDABLE_PROPERTY macro in the class declaration to declare the property as bindable. + A simple example is given in the following. - \code - class MyClass : public QObject - { - \Q_OBJECT - Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged) - public: - int x() const { return xProp; } - void setX(int x) { xProp = x; } - // declare the property as bindable. The data needs to be stored in a QBindablePropertyData instance. - // The last argument of the macro tells moc how to access that instance. - Q_BINDABLE_PROPERTY(MyClass, x, x, xProp) - - signals: - void xChanged(); - - private: - // Declare the instance of the bindable property data. - Q_BINDABLE_PROPERTY_DATA(MyClass, int, xProp, &MyClass::xChanged) - }; - \endcode + \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. + + Use the Q_OBJECT_BINDABLE_PROPERTY macro in the class declaration to declare + the property as bindable. + + \snippet code/src_corelib_kernel_qproperty.cpp 0 + + If you need to directly initialize the property with some non-default value, + you can use the Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS macro. It accepts a + value for the initialization as one of its parameters. + + \snippet code/src_corelib_kernel_qproperty.cpp 1 + + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS does not support multiple arguments + directly. If your property requires multiple arguments for initialization, + please explicitly call the specific constructor. + + \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. + + 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} */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData() + \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. - Constructs a property with a default constructed instance of T. + \sa {Qt's Property System}, {Qt Bindable Properties} */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> explicit QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(const T &initialValue) + \macro Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(containingClass, type, name, initialvalue, signal) + \since 6.0 + \relates QObjectBindableProperty + \brief Declares a \l QObjectBindableProperty inside \a containingClass + of type \a type with name \a name which is initialized to \a initialvalue. + If the optional argument \a signal is given, this signal will be emitted when + the property is marked dirty. - Constructs a property with the provided \a initialValue. + \sa {Qt's Property System}, {Qt Bindable Properties} */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> explicit QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(T &&initialValue) + \class QObjectCompatProperty + \inmodule QtCore + \brief The QObjectCompatProperty class is a template class to help port old + properties to the bindable property system. + \since 6.0 + \ingroup tools + \internal - Move-Constructs a property with the provided \a initialValue. + QObjectCompatProperty is a generic container that holds an + instance of \c T and behaves mostly like QProperty, just like + QObjectBindableProperty. It's one of the Qt internal classes implementing + \l {Qt Bindable Properties}. Like QObjectBindableProperty, + QObjectCompatProperty stores its management data structure in the surrounding + QObject. The last template parameter specifies a method (of the owning + class) to be called when the property is changed through the binding. + This is usually a setter. + + As explained in \l {Qt Bindable Properties}, getters and setters for bindable + properties have to be almost trivial to be correct. However, in legacy code, + there is often complex logic in the setter. QObjectCompatProperty is a helper + to port these properties to the bindable property system. + + With QObjectCompatProperty, the same rules as described in + \l {Bindable Property Getters and Setters} hold for the getter. + For the setter, the rules are different. It remains that every possible code + path in the setter must write to the underlying QObjectCompatProperty, + otherwise calling the setter might not remove a pre-existing binding, as + it should. However, as QObjectCompatProperty will call the setter on every + change, the setter is allowed to contain code like updating class internals + or emitting signals. Every write to the QObjectCompatProperty has to + be analyzed carefully to comply with the rules given in + \l {Writing to a Bindable Property}. + + \section2 Properties with Virtual Setters + + Some of the pre-existing Qt classes (for example, \l QAbstractProxyModel) + have properties with virtual setters. Special care must be taken when + making such properties bindable. + + For the binding to work properly, the property must be correctly handled in + all reimplemented methods of each derived class. + + Unless the derived class has access to the underlying property object, the + base implementation \e must be called for the binding to work correctly. + + If the derived class can directly access the property instance, there is no + need to explicitly call the base implementation, but the property's value + \e must be correctly updated. + + Refer to \l {Bindable Properties with Virtual Setters and Getters} for more + details. + + In both cases the expected behavior \e must be documented in the property's + documentation, so that users can correctly override the setter. + + Properties for which these conditions cannot be met should not be made + bindable. + + \sa Q_OBJECT_COMPAT_PROPERTY, QObjectBindableProperty, {Qt's Property System}, {Qt Bindable + Properties} */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(Class *owner, const QPropertyBinding<T> &binding) + \macro Q_OBJECT_COMPAT_PROPERTY(containingClass, type, name, callback) + \since 6.0 + \relates QObjectCompatProperty + \internal + \brief Declares a \l QObjectCompatProperty inside \a containingClass + of type \a type with name \a name. The argument \a callback specifies + a setter function to be called when the property is changed through the 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. + \sa QObjectBindableProperty, {Qt's Property System}, {Qt Bindable Properties} */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(Class *owner, QPropertyBinding<T> &&binding) + \macro Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(containingClass, type, name, callback, value) + \since 6.0 + \relates QObjectCompatProperty + \internal + \brief Declares a \l QObjectCompatProperty inside of \a containingClass + of type \a type with name \a name. The argument \a callback specifies + a setter function to be called when the property is changed through the binding. + \a value specifies an initialization value. +*/ - 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. +/*! + \class QObjectComputedProperty + \inmodule QtCore + \brief The QObjectComputedProperty class is a template class to help port old + properties to the bindable property system. + \since 6.0 + \ingroup tools + + QObjectComputedProperty is a read-only property which is recomputed on each read. + It does not store the computed value. + It is one of the Qt internal classes implementing \l {Qt Bindable Properties}. + QObjectComputedProperty is usually not used directly, instead an instance of it is created by + using the Q_OBJECT_COMPUTED_PROPERTY macro. + + See the following example. + + \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 + should be trivial and only return the value of the QObjectComputedProperty object. + The callback given to the QObjectComputedProperty should usually be a private + method which is only called by the QObjectComputedProperty. + + No setter is required or allowed, as QObjectComputedProperty is read-only. + + To correctly participate in dependency handling, QObjectComputedProperty + has to know when its value, the result of the callback given to it, might + 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 \c notify + on the QObjectComputedProperty object. + This will inform dependent properties about the potential change. + + 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 \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, QProperty, QObjectBindableProperty, + {Qt's Property System}, {Qt Bindable Properties} */ +/*! + \macro Q_OBJECT_COMPUTED_PROPERTY(containingClass, type, name, callback) + \since 6.0 + \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. + + \sa QObjectBindableProperty, {Qt's Property System}, {Qt Bindable Properties} +*/ + +/*! + \fn template <typename Class, typename T, auto offset, auto Callback> QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty() + + Constructs a property with a default constructed instance of T. +*/ + +/*! + \fn template <typename Class, typename T, auto offset, auto Callback> explicit QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty(const T &initialValue) + + Constructs a property with the provided \a initialValue. +*/ + +/*! + \fn template <typename Class, typename T, auto offset, auto Callback> explicit QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty(T &&initialValue) + + Move-Constructs a property with the provided \a initialValue. +*/ + +/*! + \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 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> QBindablePropertyData<Class, T, offset, Callback>::QBindablePropertyData(Class *owner, Functor &&f) + \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 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. 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> QBindablePropertyData<Class, T, offset, Callback>::~QBindablePropertyData() + \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 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. - Destroys the property. */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> T QBindablePropertyData<Class, T, offset, Callback>::value() const + \fn template <typename Class, typename T, auto offset, auto Callback> QObjectBindableProperty<Class, T, offset, Callback>::~QObjectBindableProperty() - Returns the value of the property. This may evaluate a binding expression that - is tied to this property, before returning the value. + Destroys the property. */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QBindablePropertyData<Class, T, offset, Callback>::operator T() const + \fn template <typename Class, typename T, auto offset, auto Callback> T QObjectBindableProperty<Class, T, offset, Callback>::value() const Returns the value of the property. This may evaluate a binding expression that is tied to this property, before returning the value. */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> void QBindablePropertyData<Class, T, offset, Callback>::setValue(Class *owner, const T &newValue) + \fn template <typename Class, typename T, auto offset, auto Callback> void QObjectBindableProperty<Class, T, offset, Callback>::setValue(parameter_type newValue) + \fn template <typename Class, typename T, auto offset, auto Callback> void QObjectBindableProperty<Class, T, offset, Callback>::setValue(rvalue_ref newValue) Assigns \a newValue to this property and removes the property's associated binding, if present. If the property value changes as a result, calls the @@ -986,72 +1776,72 @@ QString QPropertyBindingError::description() const */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> void QBindablePropertyData<Class, T, offset, Callback>::setValue(Class *owner, T &&newValue) - \overload + \fn template <typename Class, typename T, auto offset, auto Callback> void QObjectBindableProperty<Class, T, offset, Callback>::notify() - Assigns \a newValue to this property and removes the property's associated - binding, if present. If the property value changes as a result, calls the - Callback function on \a owner. + 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. + + \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() */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, Callback>::setBinding(Class *owner, const QPropertyBinding<T> &newBinding) + \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 \a 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. */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, Callback>::setBinding(Class *owner, Functor f) + \fn template <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyBinding<T> QObjectBindableProperty<Class, T, offset, Callback>::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. When the property value - changes \a 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. -/*! - \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, Callback>::setBinding(Class *owner, QPropertyBinding<T> &&newBinding) - \overload + When the property value changes, the owner is notified via the Callback + function. - 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 \a owner is notified - via the Callback function. + \sa {Formulating a Property Binding} */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> bool QBindablePropertyData<Class, T, offset, Callback>::setBinding(Class *owner, const QUntypedPropertyBinding &newBinding) + \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> bool QObjectBindableProperty<Class, T, offset, Callback>::setBinding(const QUntypedPropertyBinding &newBinding) \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. When the property value - changes \a owner is notified via the Callback function. + 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. + + Returns \c true if the type of this property is the same as the type the + binding function returns; \c false otherwise. */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> bool QBindablePropertyData<Class, T, offset, Callback>::hasBinding() const + \fn template <typename Class, typename T, auto offset, auto Callback> bool QObjectBindableProperty<Class, T, offset, Callback>::hasBinding() const Returns true if the property is associated with a binding; false otherwise. */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, Callback>::binding() const + \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QObjectBindableProperty<Class, T, offset, Callback>::binding() const Returns the binding expression that is associated with this property. A default constructed QPropertyBinding<T> will be returned if no such @@ -1059,7 +1849,7 @@ QString QPropertyBindingError::description() const */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QBindablePropertyData<Class, T, offset, Callback>::takeBinding() + \fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QObjectBindableProperty<Class, T, offset, Callback>::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 @@ -1067,48 +1857,87 @@ QString QPropertyBindingError::description() const */ /*! - \fn template <typename Class, typename T, auto offset, auto Callback> template <typename Functor> QPropertyChangeHandler<T, Functor> QBindablePropertyData<Class, T, offset, Callback>::onValueChanged(Functor f) + \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. + 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, an 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> QBindablePropertyData<Class, T, offset, Callback>::subscribe(Functor f) + \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. + 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, an 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 T> QtPrivate::QPropertyBase &QBindablePropertyData<Class, T, offset, Callback>::propertyBase() const + \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. + + 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. + + 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() +*/ + +/*! + \fn template <typename T> QtPrivate::QPropertyBase &QObjectBindableProperty<Class, T, offset, Callback>::propertyBase() const \internal */ /*! \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. + + A handler instance can be transferred between C++ scopes using move semantics. +*/ + +/*! + \class QPropertyNotifier + \inmodule QtCore + \brief The QPropertyNotifier class controls the lifecycle of change callback installed on a QProperty. \ingroup tools - QPropertyChangeHandler\<PropertyType, Functor\> is created when registering a - callback on a QProperty to listen to changes to the property's value, using QProperty::onValueChanged - and QProperty::subscribe. As long as the change handler is alive, the callback remains installed. + 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. */ @@ -1116,7 +1945,10 @@ QString QPropertyBindingError::description() const /*! \class QPropertyAlias \inmodule QtCore - \brief The QPropertyAlias class is a safe alias for a QProperty with same template parameter. + \internal + + \brief The QPropertyAlias class is a safe alias for a QProperty with same + template parameter. \ingroup tools @@ -1131,10 +1963,10 @@ QString QPropertyBindingError::description() const QPropertyAlias<QString> nameAlias(name); QPropertyAlias<int> ageAlias(&age); - QPropertyAlias<QString> fullname; - fullname.setBinding([&]() { return nameAlias.value() + " age:" + QString::number(ageAlias.value()); }); + QProperty<QString> fullname; + fullname.setBinding([&]() { return nameAlias.value() + " age: " + QString::number(ageAlias.value()); }); - qDebug() << fullname.value(); // Prints "Smith age: 41" + qDebug() << fullname.value(); // Prints "John age: 41" *name = "Emma"; // Marks binding expression as dirty @@ -1187,14 +2019,6 @@ QString QPropertyBindingError::description() const */ /*! - \fn template <typename T> void QPropertyAlias<T>::setValue(T &&newValue) - \overload - - Assigns \a newValue to the aliased property and removes the property's - associated binding, if present. -*/ - -/*! \fn template <typename T> QPropertyAlias<T> &QPropertyAlias<T>::operator=(const T &newValue) Assigns \a newValue to the aliased property and returns a reference to this @@ -1202,48 +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>. -*/ - -/*! - \fn template <typename T> QPropertyBinding<T> QPropertyAlias<T>::setBinding(QPropertyBinding<T> &&newBinding) - \overload - - 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. Returns any previous binding associated with the property, or a default-constructed QPropertyBinding<T>. @@ -1254,27 +2044,29 @@ 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. */ /*! - \fn template <typename T> template <typename Functor> QPropertyBinding<T> setBinding(Functor f) + \fn template <typename T> template <typename Functor> QPropertyBinding<T> QPropertyAlias<T>::setBinding(Functor f) \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>. + + \sa {Formulating a Property Binding} */ /*! @@ -1304,11 +2096,12 @@ QString QPropertyBindingError::description() const \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QPropertyAlias<T>::onValueChanged(Functor f) Registers the given functor \a f as a callback that shall be called whenever - the value of the aliased property changes. + 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, an 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. @@ -1317,15 +2110,37 @@ 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. + 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, an 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 T> template <typename Functor> QPropertyNotifier QPropertyAlias<T>::addNotifier(Functor f) + + 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 + \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. + + 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() */ /*! @@ -1380,13 +2195,14 @@ struct QBindingStoragePrivate for (size_t i = 0; i < d->size; ++i, ++p) { if (p->data) { Pair *pp = pairs(newData); - size_t index = qHash(p->data); + Q_ASSERT(newData->size && (newData->size & (newData->size - 1)) == 0); // size is a power of two + size_t index = qHash(p->data) & (newData->size - 1); while (pp[index].data) { ++index; if (index == newData->size) index = 0; } - new (pp + index) Pair{p->data, QPropertyBindingData(std::move(p->bindingData), p->data)}; + new (pp + index) Pair{p->data, QPropertyBindingData(std::move(p->bindingData))}; } } // data has been moved, no need to call destructors on old Pairs @@ -1398,8 +2214,7 @@ struct QBindingStoragePrivate QPropertyBindingData *get(const QUntypedPropertyData *data) { - if (!d) - return nullptr; + Q_ASSERT(d); Q_ASSERT(d->size && (d->size & (d->size - 1)) == 0); // size is a power of two size_t index = qHash(data) & (d->size - 1); Pair *p = pairs(d); @@ -1412,10 +2227,13 @@ struct QBindingStoragePrivate } return nullptr; } - QPropertyBindingData *getAndCreate(QUntypedPropertyData *data) + QPropertyBindingData *get(QUntypedPropertyData *data, bool create) { - if (!d) + if (!d) { + if (!create) + return nullptr; reallocate(8); + } else if (d->used*2 >= d->size) reallocate(d->size*2); Q_ASSERT(d->size && (d->size & (d->size - 1)) == 0); // size is a power of two @@ -1428,6 +2246,8 @@ struct QBindingStoragePrivate if (index == d->size) index = 0; } + if (!create) + return nullptr; ++d->used; new (p + index) Pair{data, QPropertyBindingData()}; return &p[index].bindingData; @@ -1452,10 +2272,11 @@ struct QBindingStoragePrivate \internal QBindingStorage acts as a storage for property binding related data in QObject. - Any property in a QObject can be made bindable, by using the Q_BINDABLE_PROPERTY_DATA - macro to declare the data storage. Then implement a setter and getter for the property - and declare it as a Q_PROPERTY as usual. Finally make it bindable, but using - the Q_BINDABLE_PROPERTY macro after the declaration of the setter and getter. + Any property in a QObject can be made bindable by using the Q_OBJECT_BINDABLE_PROPERTY + macro to declare it. A setter and a getter for the property and a declaration using + Q_PROPERTY have to be made as usual. + Binding related data will automatically be stored within the QBindingStorage + inside the QObject. */ QBindingStorage::QBindingStorage() @@ -1469,32 +2290,290 @@ QBindingStorage::~QBindingStorage() QBindingStoragePrivate(d).destroy(); } -void QBindingStorage::maybeUpdateBindingAndRegister(const QUntypedPropertyData *data) const +void QBindingStorage::reinitAfterThreadMove() { + bindingStatus = &QT_PREPEND_NAMESPACE(bindingStatus); Q_ASSERT(bindingStatus); +} + +void QBindingStorage::clear() +{ + QBindingStoragePrivate(d).destroy(); + d = nullptr; + bindingStatus = nullptr; +} + +void QBindingStorage::registerDependency_helper(const QUntypedPropertyData *data) const +{ + Q_ASSERT(bindingStatus); + // Use ::bindingStatus to get the binding from TLS. This is required, so that reads from + // another thread do not register as dependencies + QtPrivate::BindingEvaluationState *currentBinding; +#ifdef QT_HAS_FAST_CURRENT_THREAD_ID + const bool threadMatches = (QThread::currentThreadId() == bindingStatus->threadId); + if (Q_LIKELY(threadMatches)) + currentBinding = bindingStatus->currentlyEvaluatingBinding; + else + currentBinding = QT_PREPEND_NAMESPACE(bindingStatus).currentlyEvaluatingBinding; +#else + currentBinding = QT_PREPEND_NAMESPACE(bindingStatus).currentlyEvaluatingBinding; +#endif QUntypedPropertyData *dd = const_cast<QUntypedPropertyData *>(data); - auto storage = bindingStatus->currentlyEvaluatingBinding ? - QBindingStoragePrivate(d).getAndCreate(dd) : - QBindingStoragePrivate(d).get(dd); + if (!currentBinding) + return; + auto storage = QBindingStoragePrivate(d).get(dd, true); if (!storage) return; - if (auto *binding = storage->binding()) - binding->evaluateIfDirtyAndReturnTrueIfValueChanged(const_cast<QUntypedPropertyData *>(data)); - storage->registerWithCurrentlyEvaluatingBinding(); + storage->registerWithCurrentlyEvaluatingBinding(currentBinding); } -QPropertyBindingData *QBindingStorage::bindingData(const QUntypedPropertyData *data) const + +QPropertyBindingData *QBindingStorage::bindingData_helper(const QUntypedPropertyData *data) const { return QBindingStoragePrivate(d).get(data); } -QPropertyBindingData *QBindingStorage::bindingData(QUntypedPropertyData *data, bool create) +const QBindingStatus *QBindingStorage::status(QtPrivate::QBindingStatusAccessToken) const +{ + return bindingStatus; +} + +QPropertyBindingData *QBindingStorage::bindingData_helper(QUntypedPropertyData *data, bool create) { - auto storage = create ? - QBindingStoragePrivate(d).getAndCreate(data) : - QBindingStoragePrivate(d).get(data); - return storage; + return QBindingStoragePrivate(d).get(data, create); } +namespace QtPrivate { + + +void initBindingStatusThreadId() +{ + bindingStatus.threadId = QThread::currentThreadId(); +} + +BindingEvaluationState *suspendCurrentBindingStatus() +{ + auto ret = bindingStatus.currentlyEvaluatingBinding; + bindingStatus.currentlyEvaluatingBinding = nullptr; + return ret; +} + +void restoreBindingStatus(BindingEvaluationState *status) +{ + bindingStatus.currentlyEvaluatingBinding = status; +} + +/*! + \internal + This function can be used to detect whether we are currently + evaluating a binding. This can e.g. be used to defer the allocation + of extra data for a QPropertyBindingStorage in a getter. + Note that this function accesses TLS storage, and is therefore soemwhat + costly to call. +*/ +bool isAnyBindingEvaluating() +{ + return bindingStatus.currentlyEvaluatingBinding != nullptr; +} + +bool isPropertyInBindingWrapper(const QUntypedPropertyData *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 { + +void printUnsuitableBindableWarning(QAnyStringView prefix, BindableWarnings::Reason reason) +{ + switch (reason) { + case QtPrivate::BindableWarnings::NonBindableInterface: + qCWarning(lcQPropertyBinding).noquote() << prefix.toString() + << "The QBindable does not allow interaction with the binding."; + break; + case QtPrivate::BindableWarnings::ReadOnlyInterface: + qCWarning(lcQPropertyBinding).noquote() << prefix.toString() + << "The QBindable is read-only."; + break; + default: + case QtPrivate::BindableWarnings::InvalidInterface: + qCWarning(lcQPropertyBinding).noquote() << prefix.toString() + << "The QBindable is invalid."; + break; + } +} + +void printMetaTypeMismatch(QMetaType actual, QMetaType expected) +{ + qCWarning(lcQPropertyBinding) << "setBinding: Could not set binding as the property expects it to be of type" + << actual.name() + << "but got" << expected.name() << "instead."; +} + +} // 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 |