diff options
Diffstat (limited to 'src/corelib/kernel/qproperty.cpp')
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 173 |
1 files changed, 107 insertions, 66 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 2bace6a0e2..2ca42a7cb7 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -14,7 +14,7 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcQPropertyBinding, "qt.qproperty.binding"); +Q_STATIC_LOGGING_CATEGORY(lcQPropertyBinding, "qt.qproperty.binding"); using namespace QtPrivate; @@ -27,9 +27,9 @@ void QPropertyBindingPrivatePtr::reset(QtPrivate::RefCounted *ptr) noexcept { if (ptr != d) { if (ptr) - ptr->ref++; + ptr->addRef(); auto *old = std::exchange(d, ptr); - if (old && (--old->ref == 0)) + if (old && !old->deref()) QPropertyBindingPrivate::destroyAndFreeMemory(static_cast<QPropertyBindingPrivate *>(d)); } } @@ -121,12 +121,11 @@ struct QPropertyDelayedNotifications Change notifications are sent later with notify (following the logic of separating binding updates and notifications used in non-deferred updates). */ - [[nodiscard]] PendingBindingObserverList evaluateBindings(int index, QBindingStatus *status) { - PendingBindingObserverList bindingObservers; + void evaluateBindings(PendingBindingObserverList &bindingObservers, qsizetype index, QBindingStatus *status) { auto *delayed = delayedProperties + index; auto *bindingData = delayed->originalBindingData; if (!bindingData) - return bindingObservers; + return; bindingData->d_ptr = delayed->d_ptr; Q_ASSERT(!(bindingData->d_ptr & QPropertyBindingData::DelayedNotificationBit)); @@ -139,7 +138,6 @@ struct QPropertyDelayedNotifications QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); if (observer) observer.evaluateBindings(bindingObservers, status); - return bindingObservers; } /*! @@ -151,17 +149,17 @@ struct QPropertyDelayedNotifications \li sends any pending notifications. \endlist */ - void notify(int index) { + void notify(qsizetype index) { auto *delayed = delayedProperties + index; - auto *bindingData = delayed->originalBindingData; - if (!bindingData) + if (delayed->d_ptr & QPropertyBindingData::BindingBit) + return; // already handled + if (!delayed->originalBindingData) return; - delayed->originalBindingData = nullptr; + + QPropertyObserverPointer observer { reinterpret_cast<QPropertyObserver *>(delayed->d_ptr & ~QPropertyBindingData::DelayedNotificationBit) }; delayed->d_ptr = 0; - QPropertyBindingDataPointer bindingDataPointer{bindingData}; - QPropertyObserverPointer observer = bindingDataPointer.firstObserver(); if (observer) observer.notify(delayed->propertyData); } @@ -187,7 +185,7 @@ Q_CONSTINIT static thread_local QBindingStatus bindingStatus; properties need to be updated, preventing any external observer from noticing an inconsistent state. - \sa Qt::endPropertyUpdateGroup + \sa Qt::endPropertyUpdateGroup, QScopedPropertyUpdateGroup */ void Qt::beginPropertyUpdateGroup() { @@ -207,7 +205,7 @@ void Qt::beginPropertyUpdateGroup() \warning Calling endPropertyUpdateGroup without a preceding call to beginPropertyUpdateGroup results in undefined behavior. - \sa Qt::beginPropertyUpdateGroup + \sa Qt::beginPropertyUpdateGroup, QScopedPropertyUpdateGroup */ void Qt::endPropertyUpdateGroup() { @@ -218,27 +216,64 @@ void Qt::endPropertyUpdateGroup() if (--data->ref) return; groupUpdateData = nullptr; + // ensures that bindings are kept alive until endPropertyUpdateGroup concludes + PendingBindingObserverList bindingObservers; // update all delayed properties auto start = data; while (data) { - for (int i = 0; i < data->used; ++i) { - PendingBindingObserverList bindingObserves = data->evaluateBindings(i, status); - Q_UNUSED(bindingObserves); - // ### TODO: Use bindingObservers for notify - } + for (qsizetype i = 0; i < data->used; ++i) + data->evaluateBindings(bindingObservers, i, status); data = data->next; } - // notify all delayed properties + // notify all delayed notifications from binding evaluation + for (const QBindingObserverPtr &observer: bindingObservers) { + QPropertyBindingPrivate *binding = observer.binding(); + binding->notifyNonRecursive(); + } + // do the same for properties which only have observers data = start; while (data) { - for (int i = 0; i < data->used; ++i) + for (qsizetype i = 0; i < data->used; ++i) data->notify(i); - auto *next = data->next; - delete data; - data = next; + delete std::exchange(data, data->next); } } +/*! + \since 6.6 + \class QScopedPropertyUpdateGroup + \inmodule QtCore + \ingroup tools + \brief RAII class around Qt::beginPropertyUpdateGroup()/Qt::endPropertyUpdateGroup(). + + This class calls Qt::beginPropertyUpdateGroup() in its constructor and + Qt::endPropertyUpdateGroup() in its destructor, making sure the latter + function is reliably called even in the presence of early returns or thrown + exceptions. + + \note Qt::endPropertyUpdateGroup() may re-throw exceptions thrown by + binding evaluations. This means your application may crash + (\c{std::terminate()} called) if another exception is causing + QScopedPropertyUpdateGroup's destructor to be called during stack + unwinding. If you expect exceptions from binding evaluations, use manual + Qt::endPropertyUpdateGroup() calls and \c{try}/\c{catch} blocks. + + \sa QProperty +*/ + +/*! + \fn QScopedPropertyUpdateGroup::QScopedPropertyUpdateGroup() + + Calls Qt::beginPropertyUpdateGroup(). +*/ + +/*! + \fn QScopedPropertyUpdateGroup::~QScopedPropertyUpdateGroup() + + Calls Qt::endPropertyUpdateGroup(). +*/ + + // check everything stored in QPropertyBindingPrivate's union is trivially destructible // (though the compiler would also complain if that weren't the case) static_assert(std::is_trivially_destructible_v<QPropertyBindingSourceLocation>); @@ -275,7 +310,7 @@ void QPropertyBindingPrivate::unlinkAndDeref() { clearDependencyObservers(); propertyDataPtr = nullptr; - if (--ref == 0) + if (!deref()) destroyAndFreeMemory(this); } @@ -286,22 +321,6 @@ bool QPropertyBindingPrivate::evaluateRecursive(PendingBindingObserverList &bind return evaluateRecursive_inline(bindingObservers, status); } -void QPropertyBindingPrivate::notifyRecursive() -{ - if (!pendingNotify) - return; - pendingNotify = false; - Q_ASSERT(!updating); - updating = true; - if (firstObserver) { - firstObserver.noSelfDependencies(this); - firstObserver.notify(propertyDataPtr); - } - if (hasStaticObserver) - staticObserverCallback(propertyDataPtr); - updating = false; -} - void QPropertyBindingPrivate::notifyNonRecursive(const PendingBindingObserverList &bindingObservers) { notifyNonRecursive(); @@ -319,7 +338,7 @@ QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRec updating = true; if (firstObserver) { firstObserver.noSelfDependencies(this); - firstObserver.notifyOnlyChangeHandler(propertyDataPtr); + firstObserver.notify(propertyDataPtr); } if (hasStaticObserver) staticObserverCallback(propertyDataPtr); @@ -436,7 +455,7 @@ QPropertyBindingError QUntypedPropertyBinding::error() const /*! Returns the meta-type of the binding. - If the QUntypedProperyBinding is null, an invalid QMetaType is returned. + If the QUntypedPropertyBinding is null, an invalid QMetaType is returned. */ QMetaType QUntypedPropertyBinding::valueMetaType() const { @@ -448,6 +467,8 @@ QMetaType QUntypedPropertyBinding::valueMetaType() const QPropertyBindingData::~QPropertyBindingData() { QPropertyBindingDataPointer d{this}; + if (isNotificationDelayed()) + proxyData()->originalBindingData = nullptr; for (auto observer = d.firstObserver(); observer;) { auto next = observer.nextObserver(); observer.unlink(); @@ -582,6 +603,11 @@ void QPropertyBindingData::registerWithCurrentlyEvaluatingBinding_helper(Binding { QPropertyBindingDataPointer d{this}; + if (currentState->alreadyCaptureProperties.contains(this)) + return; + else + currentState->alreadyCaptureProperties.push_back(this); + QPropertyObserverPointer dependencyObserver = currentState->binding->allocateDependencyObserver(); Q_ASSERT(QPropertyObserver::ObserverNotifiesBinding == 0); dependencyObserver.setBindingToNotify_unsafe(currentState->binding); @@ -602,9 +628,17 @@ void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr PendingBindingObserverList bindingObservers; if (QPropertyObserverPointer observer = d.firstObserver()) { if (notifyObserver_helper(propertyDataPtr, storage, observer, bindingObservers) == Evaluated) { - // evaluateBindings() can trash the observers. We need to re-fetch here. + /* 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.notifyOnlyChangeHandler(propertyDataPtr); + observer.notify(propertyDataPtr); for (auto &&bindingObserver: bindingObservers) bindingObserver.binding()->notifyNonRecursive(); } @@ -641,11 +675,15 @@ QPropertyObserver::QPropertyObserver(ChangeHandler changeHandler) d.setChangeHandler(changeHandler); } +#if QT_DEPRECATED_SINCE(6, 6) QPropertyObserver::QPropertyObserver(QUntypedPropertyData *data) { + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED aliasData = data; next.setTag(ObserverIsAlias); + QT_WARNING_POP } +#endif /*! \internal */ @@ -715,16 +753,9 @@ void QPropertyObserverPointer::setChangeHandler(QPropertyObserver::ChangeHandler ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler); } -void QPropertyObserverPointer::setBindingToNotify(QPropertyBindingPrivate *binding) -{ - Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsPlaceholder); - ptr->binding = binding; - ptr->next.setTag(QPropertyObserver::ObserverNotifiesBinding); -} - /*! \internal - The same as as setBindingToNotify, but assumes that the tag is already correct. + The same as setBindingToNotify, but assumes that the tag is already correct. */ void QPropertyObserverPointer::setBindingToNotify_unsafe(QPropertyBindingPrivate *binding) { @@ -1154,13 +1185,20 @@ QString QPropertyBindingError::description() const 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 \c \l QBindable::QBindable(QObject *obj, const char *property) + See \l QBindable::QBindable(QObject *obj, const char *property) */ /*! @@ -1242,15 +1280,13 @@ QString QPropertyBindingError::description() const can be used to express relationships between different properties in your application. - \note In the case of QML it is important that \l QProperty needs to be exposed - 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 be then interacted with C++ via the normal API like: - + \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, then the engine will use the change-tracking - inherent to the C++ property system for getting notified about changes; and + 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. */ @@ -1638,13 +1674,13 @@ QString QPropertyBindingError::description() const have changed. Whenever a bindable property used in the callback changes, this happens automatically. If the result of the callback might change because of a change in a value which is not a bindable property, - it is the developer's responsibility to call markDirty + it is the developer's responsibility to call \c notify on the QObjectComputedProperty object. This will inform dependent properties about the potential change. - Note that calling markDirty might trigger change handlers in dependent + Note that calling \c notify might trigger change handlers in dependent properties, which might in turn use the object the QObjectComputedProperty - is a member of. So markDirty must not be called when in a transitional + is a member of. So \c notify must not be called when in a transitional or invalid state. QObjectComputedProperty is not suitable for use with a computation that depends @@ -2441,8 +2477,13 @@ QPropertyAdaptorSlotObject::QPropertyAdaptorSlotObject(QObject *o, const QMetaPr { } +#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) { |