1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QPROPERTY_P_H
5#define QPROPERTY_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists for the convenience
12// of a number of Qt sources files. This header file may change from
13// version to version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <private/qglobal_p.h>
19#include <qproperty.h>
20
21#include <qmetaobject.h>
22#include <qscopedpointer.h>
23#include <qscopedvaluerollback.h>
24#include <qvariant.h>
25#include <vector>
26#include <QtCore/QVarLengthArray>
27
28QT_BEGIN_NAMESPACE
29
30namespace QtPrivate {
31 Q_CORE_EXPORT bool isAnyBindingEvaluating();
32 struct QBindingStatusAccessToken {};
33}
34
35
36/*!
37 \internal
38 Similar to \c QPropertyBindingPrivatePtr, but stores a
39 \c QPropertyObserver * linking to the QPropertyBindingPrivate*
40 instead of the QPropertyBindingPrivate* itself
41 */
42struct QBindingObserverPtr
43{
44private:
45 QPropertyObserver *d = nullptr;
46public:
47 QBindingObserverPtr() = default;
48 Q_DISABLE_COPY(QBindingObserverPtr);
49 void swap(QBindingObserverPtr &other) noexcept
50 { qt_ptr_swap(lhs&: d, rhs&: other.d); }
51 QBindingObserverPtr(QBindingObserverPtr &&other) noexcept : d(std::exchange(obj&: other.d, new_val: nullptr)) {}
52 QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QBindingObserverPtr);
53
54
55 inline QBindingObserverPtr(QPropertyObserver *observer) noexcept;
56 inline ~QBindingObserverPtr();
57 inline QPropertyBindingPrivate *binding() const noexcept;
58 inline QPropertyObserver *operator ->();
59};
60
61using PendingBindingObserverList = QVarLengthArray<QBindingObserverPtr>;
62
63// Keep all classes related to QProperty in one compilation unit. Performance of this code is crucial and
64// we need to allow the compiler to inline where it makes sense.
65
66// This is a helper "namespace"
67struct QPropertyBindingDataPointer
68{
69 const QtPrivate::QPropertyBindingData *ptr = nullptr;
70
71 QPropertyBindingPrivate *binding() const
72 {
73 return ptr->binding();
74 }
75
76 void setObservers(QPropertyObserver *observer)
77 {
78 auto &d = ptr->d_ref();
79 observer->prev = reinterpret_cast<QPropertyObserver**>(&d);
80 d = reinterpret_cast<quintptr>(observer);
81 }
82 static void fixupAfterMove(QtPrivate::QPropertyBindingData *ptr);
83 void Q_ALWAYS_INLINE addObserver(QPropertyObserver *observer);
84 inline void setFirstObserver(QPropertyObserver *observer);
85 inline QPropertyObserverPointer firstObserver() const;
86 static QPropertyProxyBindingData *proxyData(QtPrivate::QPropertyBindingData *ptr);
87
88 inline int observerCount() const;
89
90 template <typename T>
91 static QPropertyBindingDataPointer get(QProperty<T> &property)
92 {
93 return QPropertyBindingDataPointer{&property.bindingData()};
94 }
95};
96
97struct QPropertyObserverNodeProtector
98{
99 Q_DISABLE_COPY_MOVE(QPropertyObserverNodeProtector)
100
101 QPropertyObserverBase m_placeHolder;
102 Q_NODISCARD_CTOR
103 QPropertyObserverNodeProtector(QPropertyObserver *observer)
104 {
105 // insert m_placeholder after observer into the linked list
106 QPropertyObserver *next = observer->next.data();
107 m_placeHolder.next = next;
108 observer->next = static_cast<QPropertyObserver *>(&m_placeHolder);
109 if (next)
110 next->prev = &m_placeHolder.next;
111 m_placeHolder.prev = &observer->next;
112 m_placeHolder.next.setTag(QPropertyObserver::ObserverIsPlaceholder);
113 }
114
115 QPropertyObserver *next() const { return m_placeHolder.next.data(); }
116
117 ~QPropertyObserverNodeProtector();
118};
119
120// This is a helper "namespace"
121struct QPropertyObserverPointer
122{
123 QPropertyObserver *ptr = nullptr;
124
125 void unlink()
126 {
127 unlink_common();
128#if QT_DEPRECATED_SINCE(6, 6)
129 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
130 if (ptr->next.tag() == QPropertyObserver::ObserverIsAlias)
131 ptr->aliasData = nullptr;
132 QT_WARNING_POP
133#endif
134 }
135
136 void unlink_fast()
137 {
138#if QT_DEPRECATED_SINCE(6, 6)
139 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
140 Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsAlias);
141 QT_WARNING_POP
142#endif
143 unlink_common();
144 }
145
146 void setBindingToNotify(QPropertyBindingPrivate *binding);
147 void setBindingToNotify_unsafe(QPropertyBindingPrivate *binding);
148 void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler);
149
150 enum class Notify {Everything, OnlyChangeHandlers};
151
152 void notify(QUntypedPropertyData *propertyDataPtr);
153#ifndef QT_NO_DEBUG
154 void noSelfDependencies(QPropertyBindingPrivate *binding);
155#else
156 void noSelfDependencies(QPropertyBindingPrivate *) {}
157#endif
158 void evaluateBindings(PendingBindingObserverList &bindingObservers, QBindingStatus *status);
159 void observeProperty(QPropertyBindingDataPointer property);
160
161 explicit operator bool() const { return ptr != nullptr; }
162
163 QPropertyObserverPointer nextObserver() const { return {.ptr: ptr->next.data()}; }
164
165 QPropertyBindingPrivate *binding() const
166 {
167 Q_ASSERT(ptr->next.tag() == QPropertyObserver::ObserverNotifiesBinding);
168 return ptr->binding;
169 };
170
171private:
172 void unlink_common()
173 {
174 if (ptr->next)
175 ptr->next->prev = ptr->prev;
176 if (ptr->prev)
177 ptr->prev.setPointer(ptr->next.data());
178 ptr->next = nullptr;
179 ptr->prev.clear();
180 }
181};
182
183class QPropertyBindingErrorPrivate : public QSharedData
184{
185public:
186 QPropertyBindingError::Type type = QPropertyBindingError::NoError;
187 QString description;
188};
189
190namespace QtPrivate {
191
192struct BindingEvaluationState
193{
194 BindingEvaluationState(QPropertyBindingPrivate *binding, QBindingStatus *status);
195 ~BindingEvaluationState()
196 {
197 *currentState = previousState;
198 }
199
200 QPropertyBindingPrivate *binding;
201 BindingEvaluationState *previousState = nullptr;
202 BindingEvaluationState **currentState = nullptr;
203 QVarLengthArray<const QPropertyBindingData *, 8> alreadyCaptureProperties;
204};
205
206/*!
207 * \internal
208 * CompatPropertySafePoint needs to be constructed before the setter of
209 * a QObjectCompatProperty runs. It prevents spurious binding dependencies
210 * caused by reads of properties inside the compat property setter.
211 * Moreover, it ensures that we don't destroy bindings when using operator=
212 */
213struct CompatPropertySafePoint
214{
215 Q_CORE_EXPORT CompatPropertySafePoint(QBindingStatus *status, QUntypedPropertyData *property);
216 ~CompatPropertySafePoint()
217 {
218 *currentState = previousState;
219 *currentlyEvaluatingBindingList = bindingState;
220 }
221 QUntypedPropertyData *property;
222 CompatPropertySafePoint *previousState = nullptr;
223 CompatPropertySafePoint **currentState = nullptr;
224 QtPrivate::BindingEvaluationState **currentlyEvaluatingBindingList = nullptr;
225 QtPrivate::BindingEvaluationState *bindingState = nullptr;
226};
227
228}
229
230class Q_CORE_EXPORT QPropertyBindingPrivate : public QtPrivate::RefCounted
231{
232private:
233 friend struct QPropertyBindingDataPointer;
234 friend class QPropertyBindingPrivatePtr;
235
236 using ObserverArray = std::array<QPropertyObserver, 4>;
237
238private:
239
240 // used to detect binding loops for lazy evaluated properties
241 bool updating = false;
242 bool hasStaticObserver = false;
243 bool pendingNotify = false;
244 bool hasBindingWrapper:1;
245 // used to detect binding loops for eagerly evaluated properties
246 bool isQQmlPropertyBinding:1;
247 /* a sticky binding does not get removed in removeBinding
248 this is used to support QQmlPropertyData::DontRemoveBinding
249 in qtdeclarative
250 */
251 bool m_sticky:1;
252
253 const QtPrivate::BindingFunctionVTable *vtable;
254
255 union {
256 QtPrivate::QPropertyObserverCallback staticObserverCallback = nullptr;
257 QtPrivate::QPropertyBindingWrapper staticBindingWrapper;
258 };
259 ObserverArray inlineDependencyObservers; // for things we are observing
260
261 QPropertyObserverPointer firstObserver; // list of observers observing us
262 QScopedPointer<std::vector<QPropertyObserver>> heapObservers; // for things we are observing
263
264protected:
265 QUntypedPropertyData *propertyDataPtr = nullptr;
266
267 /* For bindings set up from C++, location stores where the binding was created in the C++ source
268 For QQmlPropertyBinding that information does not make sense, and the location in the QML file
269 is stored somewhere else. To make efficient use of the space, we instead provide a scratch space
270 for QQmlPropertyBinding (which stores further binding information there).
271 Anything stored in the union must be trivially destructible.
272 (checked in qproperty.cpp)
273 */
274 using DeclarativeErrorCallback = void(*)(QPropertyBindingPrivate *);
275 union {
276 QPropertyBindingSourceLocation location;
277 struct {
278 std::byte declarativeExtraData[sizeof(QPropertyBindingSourceLocation) - sizeof(DeclarativeErrorCallback)];
279 DeclarativeErrorCallback errorCallBack;
280 };
281 };
282private:
283 QPropertyBindingError error;
284
285 QMetaType metaType;
286
287public:
288 static constexpr size_t getSizeEnsuringAlignment() {
289 constexpr auto align = alignof (std::max_align_t) - 1;
290 constexpr size_t sizeEnsuringAlignment = (sizeof(QPropertyBindingPrivate) + align) & ~align;
291 static_assert (sizeEnsuringAlignment % alignof (std::max_align_t) == 0,
292 "Required for placement new'ing the function behind it.");
293 return sizeEnsuringAlignment;
294 }
295
296
297 // public because the auto-tests access it, too.
298 size_t dependencyObserverCount = 0;
299
300 bool isUpdating() {return updating;}
301 void setSticky(bool keep = true) {m_sticky = keep;}
302 bool isSticky() {return m_sticky;}
303 void scheduleNotify() {pendingNotify = true;}
304
305 QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable,
306 const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false)
307 : hasBindingWrapper(false)
308 , isQQmlPropertyBinding(isQQmlPropertyBinding)
309 , m_sticky(false)
310 , vtable(vtable)
311 , location(location)
312 , metaType(metaType)
313 {}
314 ~QPropertyBindingPrivate();
315
316
317 void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; }
318 void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper)
319 {
320 Q_ASSERT(!(callback && bindingWrapper));
321 if (callback) {
322 hasStaticObserver = true;
323 hasBindingWrapper = false;
324 staticObserverCallback = callback;
325 } else if (bindingWrapper) {
326 hasStaticObserver = false;
327 hasBindingWrapper = true;
328 staticBindingWrapper = bindingWrapper;
329 } else {
330 hasStaticObserver = false;
331 hasBindingWrapper = false;
332 staticObserverCallback = nullptr;
333 }
334 }
335 void prependObserver(QPropertyObserverPointer observer)
336 {
337 observer.ptr->prev = const_cast<QPropertyObserver **>(&firstObserver.ptr);
338 firstObserver = observer;
339 }
340
341 QPropertyObserverPointer takeObservers()
342 {
343 auto observers = firstObserver;
344 firstObserver.ptr = nullptr;
345 return observers;
346 }
347
348 void clearDependencyObservers();
349
350 Q_ALWAYS_INLINE QPropertyObserverPointer allocateDependencyObserver() {
351 if (dependencyObserverCount < inlineDependencyObservers.size()) {
352 ++dependencyObserverCount;
353 return {.ptr: &inlineDependencyObservers[dependencyObserverCount - 1]};
354 }
355 return allocateDependencyObserver_slow();
356 }
357
358 QPropertyObserverPointer allocateDependencyObserver_slow();
359
360 QPropertyBindingSourceLocation sourceLocation() const
361 {
362 if (!hasCustomVTable())
363 return this->location;
364 QPropertyBindingSourceLocation location;
365 constexpr auto msg = "Custom location";
366 location.fileName = msg;
367 return location;
368 }
369 QPropertyBindingError bindingError() const { return error; }
370 QMetaType valueMetaType() const { return metaType; }
371
372 void unlinkAndDeref();
373
374 bool evaluateRecursive(PendingBindingObserverList &bindingObservers, QBindingStatus *status = nullptr);
375
376 bool Q_ALWAYS_INLINE evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status);
377
378 void notifyNonRecursive(const PendingBindingObserverList &bindingObservers);
379 enum NotificationState : bool { Delayed, Sent };
380 NotificationState notifyNonRecursive();
381
382 static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding)
383 { return static_cast<QPropertyBindingPrivate *>(binding.d.data()); }
384
385 void setError(QPropertyBindingError &&e)
386 { error = std::move(e); }
387
388 void detachFromProperty()
389 {
390 hasStaticObserver = false;
391 hasBindingWrapper = false;
392 propertyDataPtr = nullptr;
393 clearDependencyObservers();
394 }
395
396 static QPropertyBindingPrivate *currentlyEvaluatingBinding();
397
398 bool hasCustomVTable() const
399 {
400 return vtable->size == 0;
401 }
402
403 static void destroyAndFreeMemory(QPropertyBindingPrivate *priv) {
404 if (priv->hasCustomVTable()) {
405 // special hack for QQmlPropertyBinding which has a
406 // different memory layout than normal QPropertyBindings
407 priv->vtable->destroy(priv);
408 } else{
409 priv->~QPropertyBindingPrivate();
410 delete[] reinterpret_cast<std::byte *>(priv);
411 }
412 }
413};
414
415inline void QPropertyBindingDataPointer::setFirstObserver(QPropertyObserver *observer)
416{
417 if (auto *b = binding()) {
418 b->firstObserver.ptr = observer;
419 return;
420 }
421 auto &d = ptr->d_ref();
422 d = reinterpret_cast<quintptr>(observer);
423}
424
425inline void QPropertyBindingDataPointer::fixupAfterMove(QtPrivate::QPropertyBindingData *ptr)
426{
427 auto &d = ptr->d_ref();
428 if (ptr->isNotificationDelayed()) {
429 QPropertyProxyBindingData *proxy = ptr->proxyData();
430 Q_ASSERT(proxy);
431 proxy->originalBindingData = ptr;
432 }
433 // If QPropertyBindingData has been moved, and it has an observer
434 // we have to adjust the firstObserver's prev pointer to point to
435 // the moved to QPropertyBindingData's d_ptr
436 if (d & QtPrivate::QPropertyBindingData::BindingBit)
437 return; // nothing to do if the observer is stored in the binding
438 if (auto observer = reinterpret_cast<QPropertyObserver *>(d))
439 observer->prev = reinterpret_cast<QPropertyObserver **>(&d);
440}
441
442inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() const
443{
444 if (auto *b = binding())
445 return b->firstObserver;
446 return { .ptr: reinterpret_cast<QPropertyObserver *>(ptr->d()) };
447}
448
449/*!
450 \internal
451 Returns the proxy data of \a ptr, or \c nullptr if \a ptr has no delayed notification
452 */
453inline QPropertyProxyBindingData *QPropertyBindingDataPointer::proxyData(QtPrivate::QPropertyBindingData *ptr)
454{
455 if (!ptr->isNotificationDelayed())
456 return nullptr;
457 return ptr->proxyData();
458}
459
460inline int QPropertyBindingDataPointer::observerCount() const
461{
462 int count = 0;
463 for (auto observer = firstObserver(); observer; observer = observer.nextObserver())
464 ++count;
465 return count;
466}
467
468namespace QtPrivate {
469 Q_CORE_EXPORT bool isPropertyInBindingWrapper(const QUntypedPropertyData *property);
470 void Q_CORE_EXPORT initBindingStatusThreadId();
471}
472
473template<typename Class, typename T, auto Offset, auto Setter, auto Signal = nullptr,
474 auto Getter = nullptr>
475class QObjectCompatProperty : public QPropertyData<T>
476{
477 template<typename Property, typename>
478 friend class QtPrivate::QBindableInterfaceForProperty;
479
480 using ThisType = QObjectCompatProperty<Class, T, Offset, Setter, Signal, Getter>;
481 using SignalTakesValue = std::is_invocable<decltype(Signal), Class, T>;
482 Class *owner()
483 {
484 char *that = reinterpret_cast<char *>(this);
485 return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
486 }
487 const Class *owner() const
488 {
489 char *that = const_cast<char *>(reinterpret_cast<const char *>(this));
490 return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
491 }
492
493 static bool bindingWrapper(QMetaType type, QUntypedPropertyData *dataPtr, QtPrivate::QPropertyBindingFunction binding)
494 {
495 auto *thisData = static_cast<ThisType *>(dataPtr);
496 QPropertyData<T> copy;
497 binding.vtable->call(type, &copy, binding.functor);
498 if constexpr (QTypeTraits::has_operator_equal_v<T>)
499 if (copy.valueBypassingBindings() == thisData->valueBypassingBindings())
500 return false;
501 // ensure value and setValue know we're currently evaluating our binding
502 QBindingStorage *storage = qGetBindingStorage(thisData->owner());
503 QtPrivate::CompatPropertySafePoint guardThis(storage->bindingStatus, thisData);
504 (thisData->owner()->*Setter)(copy.valueBypassingBindings());
505 return true;
506 }
507 bool inBindingWrapper(const QBindingStorage *storage) const
508 {
509 return storage->bindingStatus && storage->bindingStatus->currentCompatProperty
510 && QtPrivate::isPropertyInBindingWrapper(property: this);
511 }
512
513 inline static T getPropertyValue(const QUntypedPropertyData *d) {
514 auto prop = static_cast<const ThisType *>(d);
515 if constexpr (std::is_null_pointer_v<decltype(Getter)>)
516 return prop->value();
517 else
518 return (prop->owner()->*Getter)();
519 }
520
521public:
522 using value_type = typename QPropertyData<T>::value_type;
523 using parameter_type = typename QPropertyData<T>::parameter_type;
524 using arrow_operator_result = typename QPropertyData<T>::arrow_operator_result;
525
526 QObjectCompatProperty() = default;
527 explicit QObjectCompatProperty(const T &initialValue) : QPropertyData<T>(initialValue) {}
528 explicit QObjectCompatProperty(T &&initialValue) : QPropertyData<T>(std::move(initialValue)) {}
529
530 parameter_type value() const
531 {
532 const QBindingStorage *storage = qGetBindingStorage(owner());
533 // make sure we don't register this binding as a dependency to itself
534 if (storage->bindingStatus && storage->bindingStatus->currentlyEvaluatingBinding && !inBindingWrapper(storage))
535 storage->registerDependency_helper(data: this);
536 return this->val;
537 }
538
539 arrow_operator_result operator->() const
540 {
541 if constexpr (QTypeTraits::is_dereferenceable_v<T>) {
542 return value();
543 } else if constexpr (std::is_pointer_v<T>) {
544 value();
545 return this->val;
546 } else {
547 return;
548 }
549 }
550
551 parameter_type operator*() const
552 {
553 return value();
554 }
555
556 operator parameter_type() const
557 {
558 return value();
559 }
560
561 void setValue(parameter_type t)
562 {
563 QBindingStorage *storage = qGetBindingStorage(owner());
564 if (auto *bd = storage->bindingData(this)) {
565 // make sure we don't remove the binding if called from the bindingWrapper
566 if (bd->hasBinding() && !inBindingWrapper(storage))
567 bd->removeBinding_helper();
568 }
569 this->val = t;
570 }
571
572 QObjectCompatProperty &operator=(parameter_type newValue)
573 {
574 setValue(newValue);
575 return *this;
576 }
577
578 QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding)
579 {
580 QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true);
581 QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, propertyDataPtr: this, staticObserverCallback: nullptr, bindingWrapper));
582 // notification is already handled in QPropertyBindingData::setBinding
583 return static_cast<QPropertyBinding<T> &>(oldBinding);
584 }
585
586 bool setBinding(const QUntypedPropertyBinding &newBinding)
587 {
588 if (!newBinding.isNull() && newBinding.valueMetaType() != QMetaType::fromType<T>())
589 return false;
590 setBinding(static_cast<const QPropertyBinding<T> &>(newBinding));
591 return true;
592 }
593
594#ifndef Q_QDOC
595 template <typename Functor>
596 QPropertyBinding<T> setBinding(Functor &&f,
597 const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION,
598 std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr)
599 {
600 return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location));
601 }
602#else
603 template <typename Functor>
604 QPropertyBinding<T> setBinding(Functor f);
605#endif
606
607 bool hasBinding() const {
608 auto *bd = qGetBindingStorage(owner())->bindingData(this);
609 return bd && bd->binding() != nullptr;
610 }
611
612 void removeBindingUnlessInWrapper()
613 {
614 QBindingStorage *storage = qGetBindingStorage(owner());
615 if (auto *bd = storage->bindingData(this)) {
616 // make sure we don't remove the binding if called from the bindingWrapper
617 if (bd->hasBinding() && !inBindingWrapper(storage))
618 bd->removeBinding_helper();
619 }
620 }
621
622 void notify()
623 {
624 QBindingStorage *storage = qGetBindingStorage(owner());
625 if (auto bd = storage->bindingData(this, false)) {
626 // This partly duplicates QPropertyBindingData::notifyObservers because we want to
627 // check for inBindingWrapper() after checking for isNotificationDelayed() and
628 // firstObserver. This is because inBindingWrapper() is the most expensive check.
629 if (!bd->isNotificationDelayed()) {
630 QPropertyBindingDataPointer d{bd};
631 if (QPropertyObserverPointer observer = d.firstObserver()) {
632 if (!inBindingWrapper(storage)) {
633 PendingBindingObserverList bindingObservers;
634 if (bd->notifyObserver_helper(this, storage, observer, bindingObservers)
635 == QtPrivate::QPropertyBindingData::Evaluated) {
636 // evaluateBindings() can trash the observers. We need to re-fetch here.
637 if (QPropertyObserverPointer observer = d.firstObserver())
638 observer.notify(propertyDataPtr: this);
639 for (auto&& bindingObserver: bindingObservers)
640 bindingObserver.binding()->notifyNonRecursive();
641 }
642 }
643 }
644 }
645 }
646 if constexpr (!std::is_null_pointer_v<decltype(Signal)>) {
647 if constexpr (SignalTakesValue::value)
648 (owner()->*Signal)(getPropertyValue(d: this));
649 else
650 (owner()->*Signal)();
651 }
652 }
653
654 QPropertyBinding<T> binding() const
655 {
656 auto *bd = qGetBindingStorage(owner())->bindingData(this);
657 return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr));
658 }
659
660 QPropertyBinding<T> takeBinding()
661 {
662 return setBinding(QPropertyBinding<T>());
663 }
664
665 template<typename Functor>
666 QPropertyChangeHandler<Functor> onValueChanged(Functor f)
667 {
668 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
669 return QPropertyChangeHandler<Functor>(*this, f);
670 }
671
672 template<typename Functor>
673 QPropertyChangeHandler<Functor> subscribe(Functor f)
674 {
675 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
676 f();
677 return onValueChanged(f);
678 }
679
680 template<typename Functor>
681 QPropertyNotifier addNotifier(Functor f)
682 {
683 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
684 return QPropertyNotifier(*this, f);
685 }
686
687 QtPrivate::QPropertyBindingData &bindingData() const
688 {
689 auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
690 return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true);
691 }
692};
693
694namespace QtPrivate {
695template<typename Class, typename Ty, auto Offset, auto Setter, auto Signal, auto Getter>
696class QBindableInterfaceForProperty<
697 QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>, std::void_t<Class>>
698{
699 using Property = QObjectCompatProperty<Class, Ty, Offset, Setter, Signal, Getter>;
700 using T = typename Property::value_type;
701public:
702 static constexpr QBindableInterface iface = {
703 [](const QUntypedPropertyData *d, void *value) -> void
704 { *static_cast<T*>(value) = Property::getPropertyValue(d); },
705 [](QUntypedPropertyData *d, const void *value) -> void
706 {
707 (static_cast<Property *>(d)->owner()->*Setter)(*static_cast<const T*>(value));
708 },
709 [](const QUntypedPropertyData *d) -> QUntypedPropertyBinding
710 { return static_cast<const Property *>(d)->binding(); },
711 [](QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) -> QUntypedPropertyBinding
712 { return static_cast<Property *>(d)->setBinding(static_cast<const QPropertyBinding<T> &>(binding)); },
713 [](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding
714 { return Qt::makePropertyBinding([d]() -> T { return Property::getPropertyValue(d); }, location); },
715 [](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
716 { observer->setSource(static_cast<const Property *>(d)->bindingData()); },
717 []() { return QMetaType::fromType<T>(); }
718 };
719};
720}
721
722#define QT_OBJECT_COMPAT_PROPERTY_4(Class, Type, name, setter) \
723 static constexpr size_t _qt_property_##name##_offset() { \
724 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
725 return offsetof(Class, name); \
726 QT_WARNING_POP \
727 } \
728 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name;
729
730#define QT_OBJECT_COMPAT_PROPERTY_5(Class, Type, name, setter, signal) \
731 static constexpr size_t _qt_property_##name##_offset() { \
732 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
733 return offsetof(Class, name); \
734 QT_WARNING_POP \
735 } \
736 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal> name;
737
738#define Q_OBJECT_COMPAT_PROPERTY(...) \
739 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
740 QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY, __VA_ARGS__) \
741 QT_WARNING_POP
742
743#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_5(Class, Type, name, setter, value) \
744 static constexpr size_t _qt_property_##name##_offset() { \
745 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
746 return offsetof(Class, name); \
747 QT_WARNING_POP \
748 } \
749 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name = \
750 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter>( \
751 value);
752
753#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_6(Class, Type, name, setter, signal, value) \
754 static constexpr size_t _qt_property_##name##_offset() { \
755 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
756 return offsetof(Class, name); \
757 QT_WARNING_POP \
758 } \
759 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal> name = \
760 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \
761 signal>(value);
762
763#define QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS_7(Class, Type, name, setter, signal, getter, value) \
764 static constexpr size_t _qt_property_##name##_offset() { \
765 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
766 return offsetof(Class, name); \
767 QT_WARNING_POP \
768 } \
769 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, signal, getter>\
770 name = QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter, \
771 signal, getter>(value);
772
773#define Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(...) \
774 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
775 QT_OVERLOADED_MACRO(QT_OBJECT_COMPAT_PROPERTY_WITH_ARGS, __VA_ARGS__) \
776 QT_WARNING_POP
777
778
779namespace QtPrivate {
780Q_CORE_EXPORT BindingEvaluationState *suspendCurrentBindingStatus();
781Q_CORE_EXPORT void restoreBindingStatus(BindingEvaluationState *status);
782}
783
784struct QUntypedBindablePrivate
785{
786 static QtPrivate::QBindableInterface const *getInterface(const QUntypedBindable &bindable)
787 {
788 return bindable.iface;
789 }
790
791 static QUntypedPropertyData *getPropertyData(const QUntypedBindable &bindable)
792 {
793 return bindable.data;
794 }
795};
796
797inline bool QPropertyBindingPrivate::evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status)
798{
799 if (updating) {
800 error = QPropertyBindingError(QPropertyBindingError::BindingLoop);
801 if (isQQmlPropertyBinding)
802 errorCallBack(this);
803 return false;
804 }
805
806 /*
807 * Evaluating the binding might lead to the binding being broken. This can
808 * cause ref to reach zero at the end of the function. However, the
809 * updateGuard's destructor will then still trigger, trying to set the
810 * updating bool to its old value
811 * To prevent this, we create a QPropertyBindingPrivatePtr which ensures
812 * that the object is still alive when updateGuard's dtor runs.
813 */
814 QPropertyBindingPrivatePtr keepAlive {this};
815
816 QScopedValueRollback<bool> updateGuard(updating, true);
817
818 QtPrivate::BindingEvaluationState evaluationFrame(this, status);
819
820 auto bindingFunctor = reinterpret_cast<std::byte *>(this) +
821 QPropertyBindingPrivate::getSizeEnsuringAlignment();
822 bool changed = false;
823 if (hasBindingWrapper) {
824 changed = staticBindingWrapper(metaType, propertyDataPtr,
825 {.vtable: vtable, .functor: bindingFunctor});
826 } else {
827 changed = vtable->call(metaType, propertyDataPtr, bindingFunctor);
828 }
829 // If there was a change, we must set pendingNotify.
830 // If there was not, we must not clear it, as that only should happen in notifyRecursive
831 pendingNotify = pendingNotify || changed;
832 if (!changed || !firstObserver)
833 return changed;
834
835 firstObserver.noSelfDependencies(binding: this);
836 firstObserver.evaluateBindings(bindingObservers, status);
837 return true;
838}
839
840/*!
841 \internal
842
843 Walks through the list of property observers, and calls any ChangeHandler
844 found there.
845 It doesn't do anything with bindings, which are only handled in
846 QPropertyBindingPrivate::evaluateRecursive.
847 */
848inline void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr)
849{
850 auto observer = const_cast<QPropertyObserver*>(ptr);
851 /*
852 * The basic idea of the loop is as follows: We iterate over all observers in the linked list,
853 * and execute the functionality corresponding to their tag.
854 * However, complication arise due to the fact that the triggered operations might modify the list,
855 * which includes deletion and move of the current and next nodes.
856 * Therefore, we take a few safety precautions:
857 * 1. Before executing any action which might modify the list, we insert a placeholder node after the current node.
858 * As that one is stack allocated and owned by us, we can rest assured that it is
859 * still there after the action has executed, and placeHolder->next points to the actual next node in the list.
860 * Note that taking next at the beginning of the loop does not work, as the executed action might either move
861 * or delete that node.
862 * 2. After the triggered action has finished, we can use the next pointer in the placeholder node as a safe way to
863 * retrieve the next node.
864 * 3. Some care needs to be taken to avoid infinite recursion with change handlers, so we add an extra test there, that
865 * checks whether we're already have the same change handler in our call stack. This can be done by checking whether
866 * the node after the current one is a placeholder node.
867 */
868 while (observer) {
869 QPropertyObserver *next = observer->next.data();
870 switch (QPropertyObserver::ObserverTag(observer->next.tag())) {
871 case QPropertyObserver::ObserverNotifiesChangeHandler:
872 {
873 auto handlerToCall = observer->changeHandler;
874 // prevent recursion
875 if (next && next->next.tag() == QPropertyObserver::ObserverIsPlaceholder) {
876 observer = next->next.data();
877 continue;
878 }
879 // handlerToCall might modify the list
880 QPropertyObserverNodeProtector protector(observer);
881 handlerToCall(observer, propertyDataPtr);
882 next = protector.next();
883 break;
884 }
885 case QPropertyObserver::ObserverNotifiesBinding:
886 break;
887 case QPropertyObserver::ObserverIsPlaceholder:
888 // recursion is already properly handled somewhere else
889 break;
890#if QT_DEPRECATED_SINCE(6, 6)
891 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
892 case QPropertyObserver::ObserverIsAlias:
893 break;
894 QT_WARNING_POP
895#endif
896 default: Q_UNREACHABLE();
897 }
898 observer = next;
899 }
900}
901
902inline QPropertyObserverNodeProtector::~QPropertyObserverNodeProtector()
903{
904 QPropertyObserverPointer d{.ptr: static_cast<QPropertyObserver *>(&m_placeHolder)};
905 d.unlink_fast();
906}
907
908QBindingObserverPtr::QBindingObserverPtr(QPropertyObserver *observer) noexcept : d(observer)
909{
910 Q_ASSERT(d);
911 QPropertyObserverPointer{.ptr: d}.binding()->addRef();
912}
913
914QBindingObserverPtr::~QBindingObserverPtr() { if (d) QPropertyObserverPointer{.ptr: d}.binding()->deref(); }
915
916QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return QPropertyObserverPointer{.ptr: d}.binding(); }
917
918QPropertyObserver *QBindingObserverPtr::operator->() { return d; }
919
920namespace QtPrivate {
921class QPropertyAdaptorSlotObject : public QUntypedPropertyData, public QSlotObjectBase
922{
923 QPropertyBindingData bindingData_;
924 QObject *obj;
925 QMetaProperty metaProperty_;
926
927#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
928 static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret);
929#else
930 static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret);
931#endif
932
933 QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty& p);
934
935public:
936 static QPropertyAdaptorSlotObject *cast(QSlotObjectBase *ptr, int propertyIndex)
937 {
938 if (ptr->isImpl(f: &QPropertyAdaptorSlotObject::impl)) {
939 auto p = static_cast<QPropertyAdaptorSlotObject *>(ptr);
940 if (p->metaProperty_.propertyIndex() == propertyIndex)
941 return p;
942 }
943 return nullptr;
944 }
945
946 inline const QPropertyBindingData &bindingData() const { return bindingData_; }
947 inline QPropertyBindingData &bindingData() { return bindingData_; }
948 inline QObject *object() const { return obj; }
949 inline const QMetaProperty &metaProperty() const { return metaProperty_; }
950
951 friend class QT_PREPEND_NAMESPACE(QUntypedBindable);
952};
953}
954
955QT_END_NAMESPACE
956
957#endif // QPROPERTY_P_H
958

source code of qtbase/src/corelib/kernel/qproperty_p.h