1/***************************************************************************
2**
3** Copyright (C) 2020 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#ifndef QPROPERTY_P_H
41#define QPROPERTY_P_H
42
43//
44// W A R N I N G
45// -------------
46//
47// This file is not part of the Qt API. It exists for the convenience
48// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header
49// file may change from version to version without notice, or even be removed.
50//
51// We mean it.
52//
53
54#include <qglobal.h>
55#include <qproperty.h>
56
57#include <qvarlengtharray.h>
58#include <qscopedpointer.h>
59#include <vector>
60
61
62QT_BEGIN_NAMESPACE
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"
68struct Q_AUTOTEST_EXPORT QPropertyBindingDataPointer
69{
70 const QtPrivate::QPropertyBindingData *ptr = nullptr;
71
72 QPropertyBindingPrivate *bindingPtr() const
73 {
74 if (ptr->d_ptr & QtPrivate::QPropertyBindingData::BindingBit)
75 return reinterpret_cast<QPropertyBindingPrivate*>(ptr->d_ptr & ~QtPrivate::QPropertyBindingData::FlagMask);
76 return nullptr;
77 }
78
79 void setObservers(QPropertyObserver *observer)
80 {
81 observer->prev = reinterpret_cast<QPropertyObserver**>(&(ptr->d_ptr));
82 ptr->d_ptr = (reinterpret_cast<quintptr>(observer) & ~QtPrivate::QPropertyBindingData::FlagMask);
83 }
84 void fixupFirstObserverAfterMove() const;
85 void addObserver(QPropertyObserver *observer);
86 void setFirstObserver(QPropertyObserver *observer);
87 QPropertyObserverPointer firstObserver() const;
88
89 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// This is a helper "namespace"
99struct QPropertyObserverPointer
100{
101 QPropertyObserver *ptr = nullptr;
102
103 void unlink();
104
105 void setBindingToMarkDirty(QPropertyBindingPrivate *binding);
106 void setChangeHandler(QPropertyObserver::ChangeHandler changeHandler);
107 void setAliasedProperty(QUntypedPropertyData *propertyPtr);
108
109 void notify(QPropertyBindingPrivate *triggeringBinding, QUntypedPropertyData *propertyDataPtr, const bool alreadyKnownToHaveChanged = false);
110 void observeProperty(QPropertyBindingDataPointer property);
111
112 explicit operator bool() const { return ptr != nullptr; }
113
114 QPropertyObserverPointer nextObserver() const { return {ptr->next.data()}; }
115};
116
117class QPropertyBindingErrorPrivate : public QSharedData
118{
119public:
120 QPropertyBindingError::Type type = QPropertyBindingError::NoError;
121 QString description;
122};
123
124namespace QtPrivate {
125
126struct BindingEvaluationState
127{
128 BindingEvaluationState(QPropertyBindingPrivate *binding);
129 ~BindingEvaluationState()
130 {
131 *currentState = previousState;
132 }
133
134 QPropertyBindingPrivate *binding;
135 BindingEvaluationState *previousState = nullptr;
136 BindingEvaluationState **currentState = nullptr;
137};
138
139struct CurrentCompatProperty
140{
141 Q_CORE_EXPORT CurrentCompatProperty(QBindingStatus *status, QUntypedPropertyData *property);
142 ~CurrentCompatProperty()
143 {
144 *currentState = previousState;
145 }
146 QUntypedPropertyData *property;
147 CurrentCompatProperty *previousState = nullptr;
148 CurrentCompatProperty **currentState = nullptr;
149};
150
151}
152
153struct QBindingStatus
154{
155 QtPrivate::BindingEvaluationState *currentlyEvaluatingBinding = nullptr;
156 QtPrivate::CurrentCompatProperty *currentCompatProperty = nullptr;
157};
158
159class Q_CORE_EXPORT QPropertyBindingPrivate : public QSharedData
160{
161private:
162 friend struct QPropertyBindingDataPointer;
163
164 using ObserverArray = std::array<QPropertyObserver, 4>;
165
166 // QSharedData is 4 bytes. Use the padding for the bools as we need 8 byte alignment below.
167
168 // a dependent property has changed, and the binding needs to be reevaluated on access
169 bool dirty = false;
170 // used to detect binding loops for lazy evaluated properties
171 bool updating = false;
172 bool hasStaticObserver = false;
173 bool hasBindingWrapper:1;
174 // used to detect binding loops for eagerly evaluated properties
175 bool eagerlyUpdating:1;
176
177 QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction;
178
179 union {
180 QtPrivate::QPropertyObserverCallback staticObserverCallback = nullptr;
181 QtPrivate::QPropertyBindingWrapper staticBindingWrapper;
182 };
183 ObserverArray inlineDependencyObservers;
184
185 QPropertyObserverPointer firstObserver;
186 QScopedPointer<std::vector<QPropertyObserver>> heapObservers;
187
188 QUntypedPropertyData *propertyDataPtr = nullptr;
189
190 QPropertyBindingSourceLocation location;
191 QPropertyBindingError error;
192
193 QMetaType metaType;
194
195public:
196 // public because the auto-tests access it, too.
197 size_t dependencyObserverCount = 0;
198
199 QPropertyBindingPrivate(QMetaType metaType, QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction,
200 const QPropertyBindingSourceLocation &location)
201 : hasBindingWrapper(false)
202 , eagerlyUpdating(false)
203 , evaluationFunction(std::move(evaluationFunction))
204 , inlineDependencyObservers() // Explicit initialization required because of union
205 , location(location)
206 , metaType(metaType)
207 {}
208 virtual ~QPropertyBindingPrivate();
209
210 void setDirty(bool d) { dirty = d; }
211 void setProperty(QUntypedPropertyData *propertyPtr) { propertyDataPtr = propertyPtr; }
212 void setStaticObserver(QtPrivate::QPropertyObserverCallback callback, QtPrivate::QPropertyBindingWrapper bindingWrapper)
213 {
214 Q_ASSERT(!(callback && bindingWrapper));
215 if (callback) {
216 hasStaticObserver = true;
217 hasBindingWrapper = false;
218 staticObserverCallback = callback;
219 } else if (bindingWrapper) {
220 hasStaticObserver = false;
221 hasBindingWrapper = true;
222 staticBindingWrapper = bindingWrapper;
223 } else {
224 hasStaticObserver = false;
225 hasBindingWrapper = false;
226 staticObserverCallback = nullptr;
227 }
228 }
229 void prependObserver(QPropertyObserverPointer observer)
230 {
231 observer.ptr->prev = const_cast<QPropertyObserver **>(&firstObserver.ptr);
232 firstObserver = observer;
233 }
234
235 QPropertyObserverPointer takeObservers()
236 {
237 auto observers = firstObserver;
238 firstObserver.ptr = nullptr;
239 return observers;
240 }
241
242 void clearDependencyObservers() {
243 for (size_t i = 0; i < qMin(dependencyObserverCount, inlineDependencyObservers.size()); ++i) {
244 QPropertyObserverPointer p{&inlineDependencyObservers[i]};
245 if (p.ptr->next.tag() == QPropertyObserver::ActivelyExecuting) {
246 *(p.ptr->nodeState) = nullptr;
247 p.ptr->nodeState = nullptr;
248
249 // set tag to "safer" value, as we return the same observer pointer from allocateDependencyObserver
250 p.ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler);
251 }
252 p.unlink();
253 }
254 if (heapObservers)
255 heapObservers->clear();
256 dependencyObserverCount = 0;
257 }
258 QPropertyObserverPointer allocateDependencyObserver()
259 {
260 if (dependencyObserverCount < inlineDependencyObservers.size()) {
261 ++dependencyObserverCount;
262 return {&inlineDependencyObservers[dependencyObserverCount - 1]};
263 }
264 ++dependencyObserverCount;
265 if (!heapObservers)
266 heapObservers.reset(new std::vector<QPropertyObserver>());
267 return {&heapObservers->emplace_back()};
268 }
269
270 QPropertyBindingSourceLocation sourceLocation() const { return location; }
271 QPropertyBindingError bindingError() const { return error; }
272 QMetaType valueMetaType() const { return metaType; }
273
274 void unlinkAndDeref();
275
276 void markDirtyAndNotifyObservers();
277 bool evaluateIfDirtyAndReturnTrueIfValueChanged(const QUntypedPropertyData *data);
278
279 static QPropertyBindingPrivate *get(const QUntypedPropertyBinding &binding)
280 { return binding.d.data(); }
281
282 void setError(QPropertyBindingError &&e)
283 { error = std::move(e); }
284
285 void detachFromProperty()
286 {
287 hasStaticObserver = false;
288 hasBindingWrapper = false;
289 propertyDataPtr = nullptr;
290 clearDependencyObservers();
291 }
292
293 bool requiresEagerEvaluation() const { return hasBindingWrapper; }
294
295 static QPropertyBindingPrivate *currentlyEvaluatingBinding();
296};
297
298inline void QPropertyBindingDataPointer::setFirstObserver(QPropertyObserver *observer)
299{
300 if (auto *binding = bindingPtr()) {
301 binding->firstObserver.ptr = observer;
302 return;
303 }
304 ptr->d_ptr = reinterpret_cast<quintptr>(observer) | (ptr->d_ptr & QtPrivate::QPropertyBindingData::FlagMask);
305}
306
307inline void QPropertyBindingDataPointer::fixupFirstObserverAfterMove() const
308{
309 // If QPropertyBindingData has been moved, and it has an observer
310 // we have to adjust the firstObesrver's prev pointer to point to
311 // the moved to QPropertyBindingData's d_ptr
312 if (ptr->d_ptr & QtPrivate::QPropertyBindingData::BindingBit)
313 return; // nothing to do if the observer is stored in the binding
314 if (auto observer = firstObserver())
315 observer.ptr->prev = reinterpret_cast<QPropertyObserver **>(&(ptr->d_ptr));
316}
317
318inline QPropertyObserverPointer QPropertyBindingDataPointer::firstObserver() const
319{
320 if (auto *binding = bindingPtr())
321 return binding->firstObserver;
322 return { reinterpret_cast<QPropertyObserver *>(ptr->d_ptr
323 & ~QtPrivate::QPropertyBindingData::FlagMask) };
324}
325
326template<typename Class, typename T, auto Offset, auto Setter>
327class QObjectCompatProperty : public QPropertyData<T>
328{
329 using ThisType = QObjectCompatProperty<Class, T, Offset, Setter>;
330 Class *owner()
331 {
332 char *that = reinterpret_cast<char *>(this);
333 return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
334 }
335 const Class *owner() const
336 {
337 char *that = const_cast<char *>(reinterpret_cast<const char *>(this));
338 return reinterpret_cast<Class *>(that - QtPrivate::detail::getOffset(Offset));
339 }
340 static bool bindingWrapper(QMetaType type, QUntypedPropertyData *dataPtr, const QtPrivate::QPropertyBindingFunction &binding)
341 {
342 auto *thisData = static_cast<ThisType *>(dataPtr);
343 QPropertyData<T> copy;
344 binding(type, &copy);
345 if constexpr (QTypeTraits::has_operator_equal_v<T>)
346 if (copy.valueBypassingBindings() == thisData->valueBypassingBindings())
347 return false;
348 // ensure value and setValue know we're currently evaluating our binding
349 QBindingStorage *storage = qGetBindingStorage(thisData->owner());
350 QtPrivate::CurrentCompatProperty guardThis(storage->bindingStatus, thisData);
351 (thisData->owner()->*Setter)(copy.valueBypassingBindings());
352 return true;
353 }
354 inline bool inBindingWrapper(const QBindingStorage *storage) const
355 {
356 return storage->bindingStatus->currentCompatProperty &&
357 storage->bindingStatus->currentCompatProperty->property == this;
358 }
359
360public:
361 using value_type = typename QPropertyData<T>::value_type;
362 using parameter_type = typename QPropertyData<T>::parameter_type;
363 using arrow_operator_result = typename QPropertyData<T>::arrow_operator_result;
364
365 QObjectCompatProperty() = default;
366 explicit QObjectCompatProperty(const T &initialValue) : QPropertyData<T>(initialValue) {}
367 explicit QObjectCompatProperty(T &&initialValue) : QPropertyData<T>(std::move(initialValue)) {}
368
369 parameter_type value() const
370 {
371 const QBindingStorage *storage = qGetBindingStorage(owner());
372 // make sure we don't register this binding as a dependency to itself
373 if (!inBindingWrapper(storage))
374 storage->maybeUpdateBindingAndRegister(this);
375 return this->val;
376 }
377
378 arrow_operator_result operator->() const
379 {
380 if constexpr (QTypeTraits::is_dereferenceable_v<T>) {
381 return value();
382 } else if constexpr (std::is_pointer_v<T>) {
383 value();
384 return this->val;
385 } else {
386 return;
387 }
388 }
389
390 parameter_type operator*() const
391 {
392 return value();
393 }
394
395 operator parameter_type() const
396 {
397 return value();
398 }
399
400 void setValue(parameter_type t)
401 {
402 QBindingStorage *storage = qGetBindingStorage(owner());
403 auto *bd = storage->bindingData(this);
404 // make sure we don't remove the binding if called from the bindingWrapper
405 if (bd && !inBindingWrapper(storage))
406 bd->removeBinding();
407 if constexpr (QTypeTraits::has_operator_equal_v<T>)
408 if (this->val == t)
409 return;
410 this->val = t;
411 notify(bd);
412 }
413
414 QObjectCompatProperty &operator=(parameter_type newValue)
415 {
416 setValue(newValue);
417 return *this;
418 }
419
420 QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding)
421 {
422 QtPrivate::QPropertyBindingData *bd = qGetBindingStorage(owner())->bindingData(this, true);
423 QUntypedPropertyBinding oldBinding(bd->setBinding(newBinding, this, nullptr, bindingWrapper));
424 // notification is already handled in QPropertyBindingData::setBinding
425 return static_cast<QPropertyBinding<T> &>(oldBinding);
426 }
427
428 bool setBinding(const QUntypedPropertyBinding &newBinding)
429 {
430 if (!newBinding.isNull() && newBinding.valueMetaType() != QMetaType::fromType<T>())
431 return false;
432 setBinding(static_cast<const QPropertyBinding<T> &>(newBinding));
433 return true;
434 }
435
436#ifndef Q_CLANG_QDOC
437 template <typename Functor>
438 QPropertyBinding<T> setBinding(Functor &&f,
439 const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION,
440 std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr)
441 {
442 return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location));
443 }
444#else
445 template <typename Functor>
446 QPropertyBinding<T> setBinding(Functor f);
447#endif
448
449 bool hasBinding() const {
450 auto *bd = qGetBindingStorage(owner())->bindingData(this);
451 return bd && bd->binding() != nullptr;
452 }
453
454 QPropertyBinding<T> binding() const
455 {
456 auto *bd = qGetBindingStorage(owner())->bindingData(this);
457 return static_cast<QPropertyBinding<T> &&>(QUntypedPropertyBinding(bd ? bd->binding() : nullptr));
458 }
459
460 QPropertyBinding<T> takeBinding()
461 {
462 return setBinding(QPropertyBinding<T>());
463 }
464
465 template<typename Functor>
466 QPropertyChangeHandler<Functor> onValueChanged(Functor f)
467 {
468 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
469 return QPropertyChangeHandler<Functor>(*this, f);
470 }
471
472 template<typename Functor>
473 QPropertyChangeHandler<Functor> subscribe(Functor f)
474 {
475 static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
476 f();
477 return onValueChanged(f);
478 }
479
480 QtPrivate::QPropertyBindingData &bindingData() const
481 {
482 auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
483 return *storage->bindingData(const_cast<QObjectCompatProperty *>(this), true);
484 }
485private:
486 void notify(const QtPrivate::QPropertyBindingData *binding)
487 {
488 if (binding)
489 binding->notifyObservers(this);
490 }
491};
492
493#define Q_OBJECT_COMPAT_PROPERTY(Class, Type, name, setter) \
494 static constexpr size_t _qt_property_##name##_offset() { \
495 QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF \
496 return offsetof(Class, name); \
497 QT_WARNING_POP \
498 } \
499 QObjectCompatProperty<Class, Type, Class::_qt_property_##name##_offset, setter> name;
500
501
502QT_END_NAMESPACE
503
504#endif // QPROPERTY_P_H
505