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