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 | |
28 | QT_BEGIN_NAMESPACE |
29 | |
30 | namespace 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 | */ |
42 | struct QBindingObserverPtr |
43 | { |
44 | private: |
45 | QPropertyObserver *d = nullptr; |
46 | public: |
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 | |
61 | using 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" |
67 | struct 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 | |
97 | struct 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" |
121 | struct 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 | |
171 | private: |
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 | |
183 | class QPropertyBindingErrorPrivate : public QSharedData |
184 | { |
185 | public: |
186 | QPropertyBindingError::Type type = QPropertyBindingError::NoError; |
187 | QString description; |
188 | }; |
189 | |
190 | namespace QtPrivate { |
191 | |
192 | struct 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 | */ |
213 | struct 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 | |
230 | class Q_CORE_EXPORT QPropertyBindingPrivate : public QtPrivate::RefCounted |
231 | { |
232 | private: |
233 | friend struct QPropertyBindingDataPointer; |
234 | friend class QPropertyBindingPrivatePtr; |
235 | |
236 | using ObserverArray = std::array<QPropertyObserver, 4>; |
237 | |
238 | private: |
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 | |
264 | protected: |
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 [sizeof(QPropertyBindingSourceLocation) - sizeof(DeclarativeErrorCallback)]; |
279 | DeclarativeErrorCallback errorCallBack; |
280 | }; |
281 | }; |
282 | private: |
283 | QPropertyBindingError error; |
284 | |
285 | QMetaType metaType; |
286 | |
287 | public: |
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 | |
415 | inline 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 | |
425 | inline 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 | |
442 | inline 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 | */ |
453 | inline QPropertyProxyBindingData *QPropertyBindingDataPointer::proxyData(QtPrivate::QPropertyBindingData *ptr) |
454 | { |
455 | if (!ptr->isNotificationDelayed()) |
456 | return nullptr; |
457 | return ptr->proxyData(); |
458 | } |
459 | |
460 | inline 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 | |
468 | namespace QtPrivate { |
469 | Q_CORE_EXPORT bool isPropertyInBindingWrapper(const QUntypedPropertyData *property); |
470 | void Q_CORE_EXPORT initBindingStatusThreadId(); |
471 | } |
472 | |
473 | template<typename Class, typename T, auto Offset, auto Setter, auto Signal = nullptr, |
474 | auto Getter = nullptr> |
475 | class 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, ©, 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 | |
521 | public: |
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 | |
694 | namespace QtPrivate { |
695 | template<typename Class, typename Ty, auto Offset, auto Setter, auto Signal, auto Getter> |
696 | class 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; |
701 | public: |
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 | |
779 | namespace QtPrivate { |
780 | Q_CORE_EXPORT BindingEvaluationState *suspendCurrentBindingStatus(); |
781 | Q_CORE_EXPORT void restoreBindingStatus(BindingEvaluationState *status); |
782 | } |
783 | |
784 | struct 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 | |
797 | inline 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 | */ |
848 | inline 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 | |
902 | inline QPropertyObserverNodeProtector::~QPropertyObserverNodeProtector() |
903 | { |
904 | QPropertyObserverPointer d{.ptr: static_cast<QPropertyObserver *>(&m_placeHolder)}; |
905 | d.unlink_fast(); |
906 | } |
907 | |
908 | QBindingObserverPtr::QBindingObserverPtr(QPropertyObserver *observer) noexcept : d(observer) |
909 | { |
910 | Q_ASSERT(d); |
911 | QPropertyObserverPointer{.ptr: d}.binding()->addRef(); |
912 | } |
913 | |
914 | QBindingObserverPtr::~QBindingObserverPtr() { if (d) QPropertyObserverPointer{.ptr: d}.binding()->deref(); } |
915 | |
916 | QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return QPropertyObserverPointer{.ptr: d}.binding(); } |
917 | |
918 | QPropertyObserver *QBindingObserverPtr::operator->() { return d; } |
919 | |
920 | namespace QtPrivate { |
921 | class 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 | |
935 | public: |
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 | |
955 | QT_END_NAMESPACE |
956 | |
957 | #endif // QPROPERTY_P_H |
958 | |