summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qproperty.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/kernel/qproperty.cpp')
-rw-r--r--src/corelib/kernel/qproperty.cpp2460
1 files changed, 1879 insertions, 581 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp
index cbb2954a98..caa9fce787 100644
--- a/src/corelib/kernel/qproperty.cpp
+++ b/src/corelib/kernel/qproperty.cpp
@@ -1,427 +1,728 @@
-/****************************************************************************
-**
-** 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 QPropertyBasePointer::addObserver(QPropertyObserver *observer)
+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 & ~QPropertyBase::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);
+ }
+}
+
+/*!
+ \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
+{
+ // 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);
+ }
+ }
+
+ /*!
+ \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);
}
- setFirstObserver(observer);
+};
+
+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()
+{
+ QPropertyDelayedNotifications *& groupUpdateData = bindingStatus.groupUpdateData;
+ if (!groupUpdateData)
+ groupUpdateData = new QPropertyDelayedNotifications;
+ ++groupUpdateData->ref;
}
+/*!
+ \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()
+{
+ auto status = &bindingStatus;
+ QPropertyDelayedNotifications *& groupUpdateData = status->groupUpdateData;
+ auto *data = groupUpdateData;
+ Q_ASSERT(data->ref);
+ if (--data->ref)
+ return;
+ groupUpdateData = nullptr;
+ // ensures that bindings are kept alive until endPropertyUpdateGroup concludes
+ PendingBindingObserverList bindingObservers;
+ // update all delayed properties
+ auto start = data;
+ while (data) {
+ for (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);
+ }
+}
+
+/*!
+ \since 6.6
+ \class QScopedPropertyUpdateGroup
+ \inmodule QtCore
+ \ingroup tools
+ \brief RAII class around Qt::beginPropertyUpdateGroup()/Qt::endPropertyUpdateGroup().
+
+ This class calls Qt::beginPropertyUpdateGroup() in its constructor and
+ Qt::endPropertyUpdateGroup() in its destructor, making sure the latter
+ function is reliably called even in the presence of early returns or thrown
+ exceptions.
+
+ \note Qt::endPropertyUpdateGroup() may re-throw exceptions thrown by
+ binding evaluations. This means your application may crash
+ (\c{std::terminate()} called) if another exception is causing
+ QScopedPropertyUpdateGroup's destructor to be called during stack
+ unwinding. If you expect exceptions from binding evaluations, use manual
+ Qt::endPropertyUpdateGroup() calls and \c{try}/\c{catch} blocks.
+
+ \sa QProperty
+*/
+
+/*!
+ \fn QScopedPropertyUpdateGroup::QScopedPropertyUpdateGroup()
+
+ Calls Qt::beginPropertyUpdateGroup().
+*/
+
+/*!
+ \fn QScopedPropertyUpdateGroup::~QScopedPropertyUpdateGroup()
+
+ Calls Qt::endPropertyUpdateGroup().
+*/
+
+
+// check everything stored in QPropertyBindingPrivate's union is trivially destructible
+// (though the compiler would also complain if that weren't the case)
+static_assert(std::is_trivially_destructible_v<QPropertyBindingSourceLocation>);
+static_assert(std::is_trivially_destructible_v<std::byte[sizeof(QPropertyBindingSourceLocation)]>);
+
QPropertyBindingPrivate::~QPropertyBindingPrivate()
{
if (firstObserver)
firstObserver.unlink();
- if (!hasStaticObserver)
- inlineDependencyObservers.~ObserverArray(); // Explicit because of union.
+ if (vtable->size)
+ vtable->destroy(reinterpret_cast<std::byte *>(this)
+ + QPropertyBindingPrivate::getSizeEnsuringAlignment());
+}
+
+void QPropertyBindingPrivate::clearDependencyObservers() {
+ for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) {
+ QPropertyObserverPointer p{&inlineDependencyObservers[i]};
+ p.unlink_fast();
+ }
+ if (heapObservers)
+ heapObservers->clear();
+ dependencyObserverCount = 0;
+}
+
+QPropertyObserverPointer QPropertyBindingPrivate::allocateDependencyObserver_slow()
+{
+ ++dependencyObserverCount;
+ if (!heapObservers)
+ heapObservers.reset(new std::vector<QPropertyObserver>());
+ return {&heapObservers->emplace_back()};
}
void QPropertyBindingPrivate::unlinkAndDeref()
{
+ clearDependencyObservers();
propertyDataPtr = nullptr;
- if (!ref.deref())
- delete this;
+ if (!deref())
+ destroyAndFreeMemory(this);
}
-void QPropertyBindingPrivate::markDirtyAndNotifyObservers()
+bool QPropertyBindingPrivate::evaluateRecursive(PendingBindingObserverList &bindingObservers, QBindingStatus *status)
{
- if (dirty)
- return;
- dirty = true;
- if (firstObserver)
- firstObserver.notify(this, propertyDataPtr);
- if (hasStaticObserver) {
- if (isBool) {
- auto propertyPtr = reinterpret_cast<QPropertyBase *>(propertyDataPtr);
- bool oldValue = propertyPtr->extraBit();
- staticObserverCallback(staticObserver, &oldValue);
- } else {
- staticObserverCallback(staticObserver, propertyDataPtr);
- }
- }
+ if (!status)
+ status = &bindingStatus;
+ return evaluateRecursive_inline(bindingObservers, status);
}
-bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged()
+void QPropertyBindingPrivate::notifyNonRecursive(const PendingBindingObserverList &bindingObservers)
{
- if (!dirty)
- return false;
-
- if (updating) {
- error = QPropertyBindingError(QPropertyBindingError::BindingLoop);
- return false;
+ notifyNonRecursive();
+ for (auto &&bindingObserver: bindingObservers) {
+ bindingObserver.binding()->notifyNonRecursive();
}
+}
- /*
- * 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);
-
- bool changed = false;
-
- if (hasStaticObserver && staticGuardCallback) {
- if (isBool) {
- auto propertyPtr = reinterpret_cast<QPropertyBase *>(propertyDataPtr);
- bool newValue = propertyPtr->extraBit();
- changed = staticGuardCallback(metaType, &newValue, evaluationFunction, staticObserver);
- if (changed && !error.hasError())
- propertyPtr->setExtraBit(newValue);
- } else {
- changed = staticGuardCallback(metaType, propertyDataPtr, evaluationFunction, staticObserver);
- }
- } else {
- if (isBool) {
- auto propertyPtr = reinterpret_cast<QPropertyBase *>(propertyDataPtr);
- bool newValue = propertyPtr->extraBit();
- changed = evaluationFunction(metaType, &newValue);
- if (changed && !error.hasError())
- propertyPtr->setExtraBit(newValue);
- } else {
- changed = evaluationFunction(metaType, propertyDataPtr);
- }
+QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRecursive()
+{
+ if (!pendingNotify)
+ return Delayed;
+ pendingNotify = false;
+ Q_ASSERT(!updating);
+ updating = true;
+ if (firstObserver) {
+ firstObserver.noSelfDependencies(this);
+ firstObserver.notify(propertyDataPtr);
}
-
- dirty = false;
- return changed;
+ if (hasStaticObserver)
+ staticObserverCallback(propertyDataPtr);
+ updating = false;
+ return Sent;
}
+/*!
+ Constructs a null QUntypedPropertyBinding.
+
+ \sa isNull()
+*/
QUntypedPropertyBinding::QUntypedPropertyBinding() = default;
-QUntypedPropertyBinding::QUntypedPropertyBinding(const 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();
-}
-
-QPropertyBase::QPropertyBase(QPropertyBase &&other, void *propertyDataPtr)
-{
- std::swap(d_ptr, other.d_ptr);
- QPropertyBasePointer d{this};
- d.setFirstObserver(nullptr);
- if (auto binding = d.bindingPtr())
- binding->setProperty(propertyDataPtr);
+ return static_cast<QPropertyBindingPrivate *>(d.get())->valueMetaType();
}
-void QPropertyBase::moveAssign(QPropertyBase &&other, void *propertyDataPtr)
+QPropertyBindingData::~QPropertyBindingData()
{
- if (&other == this)
- return;
-
- QPropertyBasePointer 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.
-}
-
-QPropertyBase::~QPropertyBase()
-{
- QPropertyBasePointer d{this};
+ 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();
}
-QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding &binding,
- void *propertyDataPtr,
- void *staticObserver,
+QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyBinding &binding,
+ QUntypedPropertyData *propertyDataPtr,
QPropertyObserverCallback staticObserverCallback,
- QtPrivate::QPropertyGuardFunction guardCallback)
+ QtPrivate::QPropertyBindingWrapper guardCallback)
{
QPropertyBindingPrivatePtr oldBinding;
QPropertyBindingPrivatePtr newBinding = binding.d;
- QPropertyBasePointer d{this};
+ 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(staticObserver, staticObserverCallback, guardCallback);
+ 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 &= ~QPropertyBase::BindingBit;
+ data = 0;
}
- return QUntypedPropertyBinding(oldBinding.data());
+ if (oldBinding)
+ static_cast<QPropertyBindingPrivate *>(oldBinding.data())->detachFromProperty();
+
+ return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(oldBinding.data()));
}
-QPropertyBindingPrivate *QPropertyBase::binding()
+QPropertyBindingData::QPropertyBindingData(QPropertyBindingData &&other) : d_ptr(std::exchange(other.d_ptr, 0))
{
- QPropertyBasePointer d{this};
- if (auto binding = d.bindingPtr())
- return binding;
- return nullptr;
+ QPropertyBindingDataPointer::fixupAfterMove(this);
}
-static thread_local BindingEvaluationState *currentBindingEvaluationState = nullptr;
-
-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 = &currentBindingEvaluationState;
+ currentState = &s->currentlyEvaluatingBinding;
previousState = *currentState;
*currentState = this;
binding->clearDependencyObservers();
}
-BindingEvaluationState::~BindingEvaluationState()
+CompatPropertySafePoint::CompatPropertySafePoint(QBindingStatus *status, QUntypedPropertyData *property)
+ : property(property)
{
- *currentState = previousState;
+ // store a pointer to the currentBindingEvaluationState to avoid a TLS lookup in
+ // the destructor (as these come with a non zero cost)
+ currentState = &status->currentCompatProperty;
+ previousState = *currentState;
+ *currentState = this;
+
+ currentlyEvaluatingBindingList = &bindingStatus.currentlyEvaluatingBinding;
+ bindingState = *currentlyEvaluatingBindingList;
+ *currentlyEvaluatingBindingList = nullptr;
}
QPropertyBindingPrivate *QPropertyBindingPrivate::currentlyEvaluatingBinding()
{
- return currentBindingEvaluationState ? currentBindingEvaluationState->binding : nullptr;
+ auto currentState = bindingStatus.currentlyEvaluatingBinding ;
+ return currentState ? currentState->binding : nullptr;
}
-void QPropertyBase::evaluateIfDirty()
+// ### Unused, kept for BC with 6.0
+void QPropertyBindingData::evaluateIfDirty(const QUntypedPropertyData *) const
{
- QPropertyBasePointer d{this};
- QPropertyBindingPrivate *binding = d.bindingPtr();
- if (!binding)
- return;
- binding->evaluateIfDirtyAndReturnTrueIfValueChanged();
}
-void QPropertyBase::removeBinding()
+void QPropertyBindingData::removeBinding_helper()
{
- QPropertyBasePointer d{this};
+ 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 QPropertyBase::registerWithCurrentlyEvaluatingBinding() const
+void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding() const
{
- auto currentState = currentBindingEvaluationState;
+ auto currentState = bindingStatus.currentlyEvaluatingBinding;
if (!currentState)
return;
+ registerWithCurrentlyEvaluatingBinding_helper(currentState);
+}
+
- QPropertyBasePointer d{this};
+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 QPropertyBase::notifyObservers(void *propertyDataPtr)
+void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr) const
{
- QPropertyBasePointer d{this};
- if (QPropertyObserverPointer observer = d.firstObserver())
- observer.notify(d.bindingPtr(), propertyDataPtr);
+ notifyObservers(propertyDataPtr, nullptr);
}
-int QPropertyBasePointer::observerCount() const
+void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr, QBindingStorage *storage) const
{
- int count = 0;
- for (auto observer = firstObserver(); observer; observer = observer.nextObserver())
- ++count;
- return count;
+ if (isNotificationDelayed())
+ return;
+ QPropertyBindingDataPointer d{this};
+
+ 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();
+ }
+ }
}
-QPropertyObserver::QPropertyObserver(void (*callback)(QPropertyObserver *, void *))
+QPropertyBindingData::NotificationResult QPropertyBindingData::notifyObserver_helper
+(
+ QUntypedPropertyData *propertyDataPtr, QBindingStorage *storage,
+ QPropertyObserverPointer observer,
+ PendingBindingObserverList &bindingObservers) const
{
- QPropertyObserverPointer d{this};
- d.setChangeHandler(callback);
+#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(void *aliasedPropertyPtr)
+
+QPropertyObserver::QPropertyObserver(ChangeHandler changeHandler)
{
QPropertyObserverPointer d{this};
- d.setAliasedProperty(aliasedPropertyPtr);
+ d.setChangeHandler(changeHandler);
+}
+
+#if QT_DEPRECATED_SINCE(6, 6)
+QPropertyObserver::QPropertyObserver(QUntypedPropertyData *data)
+{
+ QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
+ aliasData = data;
+ next.setTag(ObserverIsAlias);
+ QT_WARNING_POP
}
+#endif
-void QPropertyObserver::setSource(QPropertyBase &property)
+/*! \internal
+*/
+void QPropertyObserver::setSource(const QPropertyBindingData &property)
{
QPropertyObserverPointer d{this};
- QPropertyBasePointer propPrivate{&property};
+ QPropertyBindingDataPointer propPrivate{&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)
@@ -430,81 +731,152 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other)
return *this;
}
-void QPropertyObserverPointer::unlink()
-{
- if (ptr->next.tag() & QPropertyObserver::ObserverNotifiesAlias)
- ptr->aliasedPropertyPtr = 0;
- if (ptr->next)
- ptr->next->prev = ptr->prev;
- if (ptr->prev)
- ptr->prev.setPointer(ptr->next.data());
- ptr->next = nullptr;
- ptr->prev.clear();
-}
+/*!
+ \fn QPropertyObserverPointer::unlink()
+ \internal
+ Unlinks
+ */
-void QPropertyObserverPointer::setChangeHandler(void (*changeHandler)(QPropertyObserver *, void *))
+
+/*!
+ \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(void *propertyPtr)
+/*!
+ \internal
+ The same as setBindingToNotify, but assumes that the tag is already correct.
+ */
+void QPropertyObserverPointer::setBindingToNotify_unsafe(QPropertyBindingPrivate *binding)
{
- ptr->aliasedPropertyPtr = quintptr(propertyPtr);
- 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, void *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();
+ 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;
}
}
-void QPropertyObserverPointer::observeProperty(QPropertyBasePointer property)
+void QPropertyObserverPointer::observeProperty(QPropertyBindingDataPointer property)
{
if (ptr->prev)
unlink();
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) {
@@ -514,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)
@@ -547,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)
@@ -555,187 +953,422 @@ QString QPropertyBindingError::description() const
}
/*!
- \class QProperty
+ \class QPropertyData
\inmodule QtCore
- \brief The QProperty class is a template class that enables automatic property bindings.
+ \brief The QPropertyData class is a helper class for properties with automatic property bindings.
+ \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.
+ QPropertyData\<T\> is a common base class for classes that can hold properties with automatic
+ data bindings. It mainly wraps the stored data, and offers low level access to that data.
- 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:
+ The low level access to the data provided by this class bypasses the binding mechanism, and should be
+ used with care, as updates to the values will not get propagated to any bindings that depend on this
+ property.
- \code
- QProperty<QString> firstname("John");
- QProperty<QString> lastname("Smith");
- QProperty<int> age(41);
+ You should usually call value() and setValue() on QProperty<T> or QObjectBindableProperty<T>, not use
+ the low level mechanisms provided in this class.
+*/
- QProperty<QString> fullname;
- fullname.setBinding([&]() { return firstname.value() + " " + lastname.value() + " age:" + QString::number(age.value()); });
+/*! \fn template <typename T> QPropertyData<T>::parameter_type QPropertyData<T>::valueBypassingBindings() const
- qDebug() << fullname.value(); // Prints "John Smith age: 41"
+ Returns the data stored in this property.
- firstname = "Emma"; // Marks binding expression as dirty
+ \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.
+*/
- qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 41"
+/*! \fn template <typename T> void QPropertyData<T>::setValueBypassingBindings(parameter_type v)
- // Birthday is coming up
- age.setValue(age.value() + 1);
+ Sets the data value stored in this property to \a v.
- qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma Smith age: 42"
- \endcode
+ \note Using this method will bypass any potential binding registered for this property.
+*/
- 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.
+/*! \fn template <typename T> void QPropertyData<T>::setValueBypassingBindings(rvalue_ref v)
+ \overload
- 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.
+ Sets the data value stored in this property to \a v.
- 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.
+ \note Using this method will bypass any potential binding registered for this property.
+*/
- \section1 Tracking properties
+/*!
+ \class QUntypedBindable
+ \inmodule QtCore
+ \brief QUntypedBindable is a uniform interface over bindable properties like \c QProperty\<T\>
+ and \c QObjectBindableProperty of any type \c T.
+ \since 6.0
- 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.
+ \ingroup tools
- 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.
+ 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.
*/
/*!
- \fn template <typename T> QProperty<T>::QProperty()
+ \fn QUntypedBindable::QUntypedBindable()
- Constructs a property with a default constructed instance of T.
+ Default-constructs a QUntypedBindable. It is in an invalid state.
+ \sa isValid()
*/
/*!
- \fn template <typename T> explicit QProperty<T>::QProperty(const T &initialValue)
+ \fn template<typename Property> QUntypedBindable::QUntypedBindable(Property *property)
- Constructs a property with the provided \a initialValue.
+ 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.
+
+ \sa isValid(), isReadOnly()
*/
/*!
- \fn template <typename T> explicit QProperty<T>::QProperty(T &&initialValue)
+ \fn bool QUntypedBindable::isValid() const
- Move-Constructs a property with the provided \a initialValue.
+ Returns true if the QUntypedBindable is valid. Methods called on an invalid
+ QUntypedBindable generally have no effect, unless otherwise noted.
*/
/*!
- \fn template <typename T> QProperty<T>::QProperty(QProperty<T> &&other)
+ \fn bool QUntypedBindable::isReadOnly() const
+ \since 6.1
- Move-constructs a QProperty instance, making it point at the same object that
- \a other was pointing to.
+ Returns true if the QUntypedBindable is read-only.
*/
/*!
- \fn template <typename T> QProperty<T> &QProperty<T>::operator=(QProperty &&other)
+ \fn bool QUntypedBindable::isBindable() const
+ \internal
+
+ 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.
- Move-assigns \a other to this QProperty instance.
+ \sa isReadOnly()
*/
/*!
- \fn template <typename T> QProperty<T>::QProperty(const QPropertyBinding<T> &binding)
+ \fn QUntypedPropertyBinding QUntypedBindable::makeBinding(const QPropertyBindingSourceLocation &location) const
- 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.
+ Creates a binding returning the underlying properties' value, using a specified source \a location.
*/
/*!
- \fn template <typename T> template <typename Functor> QProperty<T>::QProperty(Functor &&f)
+ \fn void QUntypedBindable::observe(QPropertyObserver *observer)
+ \internal
- 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.
+ Installs the observer on the underlying property.
*/
/*!
- \fn template <typename T> QProperty<T>::~QProperty()
+ \fn template<typename Functor> QPropertyChangeHandler<Functor> QUntypedBindable::onValueChanged(Functor f) const
- Destroys the property.
+ 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> T QProperty<T>::value() const
+ \fn template<typename Functor> QPropertyChangeHandler<Functor> QUntypedBindable::subscribe(Functor f) const
- Returns the value of the property. This may evaluate a binding expression that
- is tied to this property, before returning the value.
+ Behaves like a call to \a f followed by \c onValueChanged(f),
+
+ \sa onValueChanged()
*/
/*!
- \fn template <typename T> QProperty<T>::operator T() const
+ \fn template<typename Functor> QPropertyNotifier QUntypedBindable::addNotifier(Functor f)
- Returns the value of the property. This may evaluate a binding expression that
- is tied to this property, before returning the value.
+ 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> void QProperty<T>::setValue(const T &newValue)
+ \fn QUntypedPropertyBinding QUntypedBindable::binding() const
- Assigns \a newValue to this property and removes the property's associated
- binding, if present.
+ Returns the underlying property's binding if there is any, or a default
+ constructed QUntypedPropertyBinding otherwise.
+
+ \sa hasBinding()
*/
/*!
- \fn template <typename T> void QProperty<T>::setValue(T &&newValue)
- \overload
+ \fn QUntypedPropertyBinding QUntypedBindable::takeBinding()
- Assigns \a newValue to this property and removes the property's associated
- binding, if present.
+ 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> QProperty<T> &QProperty<T>::operator=(const T &newValue)
+ \fn bool QUntypedBindable::setBinding(const QUntypedPropertyBinding &binding)
- Assigns \a newValue to this property and returns a reference to this QProperty.
+ 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.
+
+ \return \c true when the binding was successfully set.
+
+ //! \sa QUntypedPropertyBinding::valueMetaType()
+*/
+
+/*!
+ \fn bool QUntypedBindable::hasBinding() const
+
+ Returns \c true if the underlying property has a binding.
+*/
+
+/*!
+ \fn QMetaType QUntypedBindable::metaType() const
+ \since 6.2
+
+ 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()
+*/
+
+/*!
+ \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
+
+ \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> QBindable<T>::QBindable(QObject *obj, const char *property)
+
+ Constructs a QBindable for the \l Q_PROPERTY \a property on \a obj. The property must
+ have a notify signal but does not need to have \c BINDABLE in its \c Q_PROPERTY
+ definition, so even binding unaware \c {Q_PROPERTY}s can be bound or used in binding
+ expressions. You must use \c QBindable::value() in binding expressions instead of the
+ normal property \c READ function (or \c MEMBER) to enable dependency tracking if the
+ property is not \c BINDABLE. When binding using a lambda, you may prefer to capture the
+ QBindable by value to avoid the cost of calling this constructor in the binding
+ expression.
+ This constructor should not be used to implement \c BINDABLE for a Q_PROPERTY, as the
+ resulting Q_PROPERTY will not support dependency tracking. To make a property that is
+ usable directly without reading through a QBindable use \l QProperty or
+ \l QObjectBindableProperty.
+
+ \code
+ QProperty<QString> displayText;
+ QDateTimeEdit *dateTimeEdit = findDateTimeEdit();
+ QBindable<QDateTime> dateTimeBindable(dateTimeEdit, "dateTime");
+ displayText.setBinding([dateTimeBindable](){ return dateTimeBindable.value().toString(); });
+ \endcode
+
+ \sa QProperty, QObjectBindableProperty, {Qt Bindable Properties}
+*/
+
+/*!
+ \fn template<typename T> QBindable<T>::QBindable(QObject *obj, const QMetaProperty &property)
+
+ See \l QBindable::QBindable(QObject *obj, const char *property)
+*/
+
+/*!
+ \fn template<typename T> QPropertyBinding<T> QBindable<T>::makeBinding(const QPropertyBindingSourceLocation &location) const
+
+ Constructs a binding evaluating to the underlying property's value, using a specified source
+ \a location.
*/
/*!
- \fn template <typename T> QProperty<T> &QProperty<T>::operator=(T &&newValue)
+ \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> 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.
*/
/*!
@@ -743,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.
-*/
+ 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.
-/*!
- \fn template <typename T> QPropertyBinding<T> QProperty<T>::setBinding(QPropertyBinding<T> &&newBinding)
- \overload
-
- Associates the value of this property with the provided \a newBinding
- expression and returns the previously associated binding. The first time the
- property value is read, the binding is evaluated. Whenever a dependency of the
- binding changes, the binding will be re-evaluated the next time the value of
- this property is read.
+ \sa {Formulating a Property Binding}
*/
/*!
@@ -765,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.
@@ -793,139 +1418,357 @@ 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 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.
+*/
+
+/*!
+ \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.
- 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.
+ 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.
- The returned property change handler object keeps track of the subscription. When it
- goes out of scope, the callback is unsubscribed.
+ \sa onValueChanged(), subscribe()
*/
/*!
- \class QNotifiedProperty
+ \fn template <typename T> QtPrivate::QPropertyBindingData &QProperty<T>::bindingData() const
+ \internal
+*/
+
+/*!
+ \class QObjectBindableProperty
\inmodule QtCore
- \brief The QNotifiedProperty class is a template class that enables automatic property bindings
- and invokes a callback function on the surrounding class when the value changes.
+ \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
- QNotifiedProperty\<T, Callback\> is a generic container that holds an
- instance of T and behaves mostly like \l QProperty. The extra template
- parameter is 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 QNotifiedProperty to port code that uses Q_PROPERTY. The getter
- and setter are trivial to adapt for accessing a \l QProperty rather than the
- plain value. In order to invoke the change signal on property changes, use
- QNotifiedProperty and pass the change signal as 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}.
- \code
- class MyClass : public QObject
- {
- \Q_OBJECT
- // Replacing: Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged)
- public:
- int x() const { return xProp; }
- void setX(int x) { xProp = x; }
-
- signals:
- void xChanged();
-
- private:
- // Now you can set bindings on xProp and use it in other bindings.
- QNotifiedProperty<int, &MyClass::xChanged> xProp;
- };
- \endcode
+ In order to invoke the change signal on property changes, use
+ QObjectBindableProperty and pass the change signal as a callback.
+
+ A simple example is given in the following.
+
+ \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 T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::QNotifiedProperty()
+ \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 T, typename Class, void(Class::*Callback)()> explicit QNotifiedProperty<T, Callback>::QNotifiedProperty(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 T, typename Class, void(Class::*Callback)()> explicit QNotifiedProperty<T, Callback>::QNotifiedProperty(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 T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::QNotifiedProperty(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 T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::QNotifiedProperty(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 T, typename Class, void(Class::*Callback)()> template <typename Functor> QNotifiedProperty<T, Callback>::QNotifiedProperty(Class *owner, Functor &&f)
+ \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 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> QObjectBindableProperty<Class, T, offset, Callback>::QObjectBindableProperty(Class *owner, 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 T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, Callback>::~QNotifiedProperty()
+ \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 T, typename Class, void(Class::*Callback)()> T QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> void QNotifiedProperty<T, 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
@@ -933,72 +1776,72 @@ QString QPropertyBindingError::description() const
*/
/*!
- \fn template <typename T, typename Class, void(Class::*Callback)()> void QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> template <typename Functor> QPropertyBinding<T> QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> bool QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> bool QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, 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
@@ -1006,7 +1849,7 @@ QString QPropertyBindingError::description() const
*/
/*!
- \fn template <typename T, typename Class, void(Class::*Callback)()> QPropertyBinding<T> QNotifiedProperty<T, 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
@@ -1014,43 +1857,87 @@ QString QPropertyBindingError::description() const
*/
/*!
- \fn template <typename T, typename Class, void(Class::*Callback)()> template <typename Functor> QPropertyChangeHandler<T, Functor> QNotifiedProperty<T, 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 T, typename Class, void(Class::*Callback)()> template <typename Functor> QPropertyChangeHandler<T, Functor> QNotifiedProperty<T, 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 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.
*/
@@ -1058,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
@@ -1073,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
@@ -1129,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
@@ -1144,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.
-
- Returns any previous binding associated with the property, or a
- default-constructed QPropertyBinding<T>.
-*/
+ 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.
-/*!
- \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>.
@@ -1196,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}
*/
/*!
@@ -1246,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.
@@ -1259,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()
*/
/*!
@@ -1278,4 +2151,429 @@ QString QPropertyBindingError::description() const
If the aliased property doesn't exist, all other method calls are ignored.
*/
+struct QBindingStorageData
+{
+ size_t size = 0;
+ size_t used = 0;
+ // Pair[] pairs;
+};
+
+struct QBindingStoragePrivate
+{
+ // This class basically implements a simple and fast hash map to store bindings for a QObject
+ // The reason that we're not using QHash is that QPropertyBindingData can not be copied, only
+ // moved. That doesn't work well together with an implicitly shared class.
+ struct Pair
+ {
+ QUntypedPropertyData *data;
+ QPropertyBindingData bindingData;
+ };
+ static_assert(alignof(Pair) == alignof(void *));
+ static_assert(alignof(size_t) == alignof(void *));
+
+ QBindingStorageData *&d;
+
+ static inline Pair *pairs(QBindingStorageData *dd)
+ {
+ Q_ASSERT(dd);
+ return reinterpret_cast<Pair *>(dd + 1);
+ }
+ void reallocate(size_t newSize)
+ {
+ Q_ASSERT(!d || newSize > d->size);
+ size_t allocSize = sizeof(QBindingStorageData) + newSize*sizeof(Pair);
+ void *nd = malloc(allocSize);
+ memset(nd, 0, allocSize);
+ QBindingStorageData *newData = new (nd) QBindingStorageData;
+ newData->size = newSize;
+ if (!d) {
+ d = newData;
+ return;
+ }
+ newData->used = d->used;
+ Pair *p = pairs(d);
+ for (size_t i = 0; i < d->size; ++i, ++p) {
+ if (p->data) {
+ Pair *pp = pairs(newData);
+ 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))};
+ }
+ }
+ // data has been moved, no need to call destructors on old Pairs
+ free(d);
+ d = newData;
+ }
+
+ QBindingStoragePrivate(QBindingStorageData *&_d) : d(_d) {}
+
+ QPropertyBindingData *get(const QUntypedPropertyData *data)
+ {
+ 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);
+ while (p[index].data) {
+ if (p[index].data == data)
+ return &p[index].bindingData;
+ ++index;
+ if (index == d->size)
+ index = 0;
+ }
+ return nullptr;
+ }
+ QPropertyBindingData *get(QUntypedPropertyData *data, bool create)
+ {
+ 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
+ size_t index = qHash(data) & (d->size - 1);
+ Pair *p = pairs(d);
+ while (p[index].data) {
+ if (p[index].data == data)
+ return &p[index].bindingData;
+ ++index;
+ if (index == d->size)
+ index = 0;
+ }
+ if (!create)
+ return nullptr;
+ ++d->used;
+ new (p + index) Pair{data, QPropertyBindingData()};
+ return &p[index].bindingData;
+ }
+
+ void destroy()
+ {
+ if (!d)
+ return;
+ Pair *p = pairs(d);
+ for (size_t i = 0; i < d->size; ++i) {
+ if (p->data)
+ p->~Pair();
+ ++p;
+ }
+ free(d);
+ }
+};
+
+/*!
+ \class QBindingStorage
+ \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_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()
+{
+ bindingStatus = &QT_PREPEND_NAMESPACE(bindingStatus);
+ Q_ASSERT(bindingStatus);
+}
+
+QBindingStorage::~QBindingStorage()
+{
+ QBindingStoragePrivate(d).destroy();
+}
+
+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);
+ if (!currentBinding)
+ return;
+ auto storage = QBindingStoragePrivate(d).get(dd, true);
+ if (!storage)
+ return;
+ storage->registerWithCurrentlyEvaluatingBinding(currentBinding);
+}
+
+
+QPropertyBindingData *QBindingStorage::bindingData_helper(const QUntypedPropertyData *data) const
+{
+ return QBindingStoragePrivate(d).get(data);
+}
+
+const QBindingStatus *QBindingStorage::status(QtPrivate::QBindingStatusAccessToken) const
+{
+ return bindingStatus;
+}
+
+QPropertyBindingData *QBindingStorage::bindingData_helper(QUntypedPropertyData *data, bool create)
+{
+ return QBindingStoragePrivate(d).get(data, create);
+}
+
+
+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