1 | // Copyright (C) 2021 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 QQMLANYBINDINGPTR_P_H |
5 | #define QQMLANYBINDINGPTR_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 purely as an |
12 | // implementation detail. This header file may change from version to |
13 | // version without notice, or even be removed. |
14 | // |
15 | // We mean it. |
16 | // |
17 | |
18 | #include <qqmlproperty.h> |
19 | #include <private/qqmlpropertybinding_p.h> |
20 | #include <private/qqmlbinding_p.h> |
21 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | // Fully inline so that subsequent prop.isBindable check might get ellided. |
25 | |
26 | /*! |
27 | \internal |
28 | \brief QQmlAnyBinding is an abstraction over the various bindings in QML |
29 | |
30 | QQmlAnyBinding can store both classical bindings (derived from QQmlAbstractBinding) |
31 | as well as new-style bindings (derived from QPropertyBindingPrivate). For both, it keeps |
32 | a strong reference to them, and knows how to delete them in case the reference count |
33 | becomes zero. In that sense it can be thought of as a union of QUntypedPropertyBinding |
34 | and QQmlAbstractBinding::Ptr. |
35 | |
36 | It also offers methods to create bindings (from QV4::Function, from translation bindings |
37 | and from code strings). Moreover, it allows the retrieval, the removal and the |
38 | installation of bindings on a QQmlProperty. |
39 | |
40 | Note that the class intentionally does not allow construction from QUntypedProperty and |
41 | QQmlAbstractBinding::Ptr. This is meant to catch code which doesn't handle bindable properties |
42 | yet when porting existing code. |
43 | */ |
44 | class QQmlAnyBinding { |
45 | public: |
46 | |
47 | constexpr QQmlAnyBinding() noexcept = default; |
48 | QQmlAnyBinding(std::nullptr_t) : d(static_cast<QQmlAbstractBinding *>(nullptr)) {} |
49 | |
50 | /*! |
51 | \internal |
52 | Returns the binding of the property \a prop as a QQmlAnyBinding. |
53 | The binding continues to be active and set on the property. |
54 | If there was no binding set, the returned QQmlAnyBinding is null. |
55 | */ |
56 | static QQmlAnyBinding ofProperty(const QQmlProperty &prop) { |
57 | QQmlAnyBinding binding; |
58 | if (prop.isBindable()) { |
59 | QUntypedBindable bindable = prop.property().bindable(object: prop.object()); |
60 | binding = bindable.binding(); |
61 | } else { |
62 | binding = QQmlPropertyPrivate::binding(that: prop); |
63 | } |
64 | return binding; |
65 | } |
66 | |
67 | /*! |
68 | \overload |
69 | |
70 | \a object must be non-null |
71 | */ |
72 | static QQmlAnyBinding ofProperty(QObject *object, QQmlPropertyIndex index) |
73 | { |
74 | QQmlAnyBinding binding; |
75 | Q_ASSERT(object); |
76 | QQmlData *data = QQmlData::get(object, create: true); |
77 | auto coreIndex = index.coreIndex(); |
78 | // we don't support bindable properties on value types so far |
79 | if (!index.hasValueTypeIndex() && data->propertyCache->property(index: coreIndex)->isBindable()) { |
80 | auto metaProp = object->metaObject()->property(index: coreIndex); |
81 | QUntypedBindable bindable = metaProp.bindable(object); |
82 | binding = bindable.binding(); |
83 | } else { |
84 | binding = QQmlPropertyPrivate::binding(object, index); |
85 | } |
86 | return binding; |
87 | } |
88 | |
89 | /*! |
90 | Removes the binding from the property \a prop, and returns it as a |
91 | QQmlAnyBinding if there was any. Otherwise returns a null |
92 | QQmlAnyBinding. |
93 | */ |
94 | static QQmlAnyBinding takeFrom(const QQmlProperty &prop) |
95 | { |
96 | QQmlAnyBinding binding; |
97 | if (prop.isBindable()) { |
98 | QUntypedBindable bindable = prop.property().bindable(object: prop.object()); |
99 | binding = bindable.takeBinding(); |
100 | } else { |
101 | auto qmlBinding = QQmlPropertyPrivate::binding(that: prop); |
102 | if (qmlBinding) { |
103 | binding = qmlBinding; // this needs to run before removeFromObject, else the refcount might reach zero |
104 | qmlBinding->setEnabled(e: false, f: QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor); |
105 | qmlBinding->removeFromObject(); |
106 | } |
107 | } |
108 | return binding; |
109 | } |
110 | |
111 | /*! |
112 | \internal |
113 | Creates a binding for property \a prop from \a function. |
114 | \a obj is the scope object which shall be used for the function and \a scope its QML scope. |
115 | The binding is not installed on the property (but if a QQmlBinding is created, it has its |
116 | target set to \a prop). |
117 | */ |
118 | static QQmlAnyBinding createFromFunction(const QQmlProperty &prop, QV4::Function *function, |
119 | QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, |
120 | QV4::ExecutionContext *scope) |
121 | { |
122 | QQmlAnyBinding binding; |
123 | auto propPriv = QQmlPropertyPrivate::get(p: prop); |
124 | if (prop.isBindable()) { |
125 | auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1); |
126 | binding = QQmlPropertyBinding::create(pd: &propPriv->core, |
127 | function, obj, ctxt, |
128 | scope, target: prop.object(), targetIndex: index); |
129 | } else { |
130 | auto qmlBinding = QQmlBinding::create(property: &propPriv->core, function, obj, ctxt, scope); |
131 | qmlBinding->setTarget(prop); |
132 | binding = qmlBinding; |
133 | } |
134 | return binding; |
135 | } |
136 | |
137 | /*! |
138 | \internal |
139 | Creates a binding for property \a prop from \a script. |
140 | \a obj is the scope object which shall be used for the function and \a ctxt its QML scope. |
141 | The binding is not installed on the property (but if a QQmlBinding is created, it has its |
142 | target set to \a prop). |
143 | */ |
144 | static QQmlAnyBinding createFromScriptString(const QQmlProperty &prop, const QQmlScriptString &script, |
145 | QObject *obj, QQmlContext *ctxt) |
146 | { |
147 | QQmlAnyBinding binding; |
148 | auto propPriv = QQmlPropertyPrivate::get(p: prop); |
149 | if (prop.isBindable()) { |
150 | auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1); |
151 | binding = QQmlPropertyBinding::createFromScriptString(property: &propPriv->core, script, obj, ctxt, target: prop.object(), targetIndex: index); |
152 | } else { |
153 | auto qmlBinding = QQmlBinding::create(&propPriv->core, script, obj, ctxt); |
154 | qmlBinding->setTarget(prop); |
155 | binding = qmlBinding; |
156 | } |
157 | return binding; |
158 | } |
159 | |
160 | |
161 | /*! |
162 | \internal |
163 | Removes the binding from \a prop if there is any. |
164 | */ |
165 | static void removeBindingFrom(QQmlProperty &prop) |
166 | { |
167 | if (prop.isBindable()) |
168 | prop.property().bindable(object: prop.object()).takeBinding(); |
169 | else |
170 | QQmlPropertyPrivate::removeBinding(that: prop); |
171 | } |
172 | |
173 | /*! |
174 | \internal |
175 | Creates a binding for property \a prop from \a function. |
176 | \a obj is the scope object which shall be used for the function and \a scope its QML scope. |
177 | The binding is not installed on the property (but if a QQmlBinding is created, it has its |
178 | target set to \a prop). |
179 | */ |
180 | static QQmlAnyBinding createFromCodeString(const QQmlProperty &prop, const QString& code, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber) { |
181 | QQmlAnyBinding binding; |
182 | auto propPriv = QQmlPropertyPrivate::get(p: prop); |
183 | if (prop.isBindable()) { |
184 | auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1); |
185 | binding = QQmlPropertyBinding::createFromCodeString(property: &propPriv->core, |
186 | str: code, obj, ctxt, |
187 | url, lineNumber, |
188 | target: prop.object(), targetIndex: index); |
189 | } else { |
190 | auto qmlBinding = QQmlBinding::create(&propPriv->core, code, obj, ctxt, url, lineNumber); |
191 | qmlBinding->setTarget(prop); |
192 | binding = qmlBinding; |
193 | } |
194 | return binding; |
195 | } |
196 | |
197 | /*! |
198 | \internal |
199 | Creates a translattion binding for \a prop from \a compilationUnit and \a transationBinding. |
200 | \a obj is the context object, \a context the qml context. |
201 | */ |
202 | static QQmlAnyBinding createTranslationBinding(const QQmlProperty &prop, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *translationBinding, QObject *scopeObject=nullptr, QQmlRefPointer<QQmlContextData> context={}) |
203 | { |
204 | QQmlAnyBinding binding; |
205 | auto propPriv = QQmlPropertyPrivate::get(p: prop); |
206 | if (prop.isBindable()) { |
207 | binding = QQmlTranslationPropertyBinding::create(pd: &propPriv->core, compilationUnit, binding: translationBinding); |
208 | } else { |
209 | auto qmlBinding = QQmlBinding::createTranslationBinding(unit: compilationUnit, binding: translationBinding, obj: scopeObject, ctxt: context); |
210 | binding = qmlBinding; |
211 | qmlBinding->setTarget(prop); |
212 | } |
213 | return binding; |
214 | } |
215 | |
216 | /*! |
217 | \internal |
218 | Installs the binding referenced by this QQmlAnyBinding on the target. |
219 | If \a mode is set to RespectInterceptors, interceptors are honored, otherwise |
220 | writes and binding installation bypass them (the default). |
221 | Preconditions: |
222 | - The binding is non-null. |
223 | - If the binding is QQmlAbstractBinding derived, the target is non-bindable. |
224 | - If the binding is a QUntypedPropertyBinding, then the target is bindable. |
225 | */ |
226 | enum InterceptorMode : bool { |
227 | IgnoreInterceptors, |
228 | RespectInterceptors |
229 | }; |
230 | |
231 | void installOn(const QQmlProperty &target, InterceptorMode mode = IgnoreInterceptors) |
232 | { |
233 | Q_ASSERT(!d.isNull()); |
234 | if (isAbstractPropertyBinding()) { |
235 | auto abstractBinding = asAbstractBinding(); |
236 | Q_ASSERT(abstractBinding->targetObject() == target.object() || QQmlPropertyPrivate::get(target)->core.isAlias()); |
237 | Q_ASSERT(!target.isBindable()); |
238 | if (mode == IgnoreInterceptors) |
239 | QQmlPropertyPrivate::setBinding(binding: abstractBinding, flags: QQmlPropertyPrivate::None, writeFlags: QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor); |
240 | else |
241 | QQmlPropertyPrivate::setBinding(binding: abstractBinding); |
242 | } else { |
243 | Q_ASSERT(target.isBindable()); |
244 | QUntypedBindable bindable; |
245 | void *argv[] = {&bindable}; |
246 | if (mode == IgnoreInterceptors) { |
247 | target.object()->qt_metacall(QMetaObject::BindableProperty, target.index(), argv); |
248 | } else { |
249 | QMetaObject::metacall(target.object(), QMetaObject::BindableProperty, target.index(), argv); |
250 | } |
251 | bindable.setBinding(asUntypedPropertyBinding()); |
252 | } |
253 | } |
254 | |
255 | /*! |
256 | \internal |
257 | Returns true if the binding is in an error state (e.g. binding loop), false otherwise. |
258 | |
259 | \note For ValueTypeProxyBindings, this methods will always return false |
260 | */ |
261 | bool hasError() { |
262 | if (isAbstractPropertyBinding()) { |
263 | auto abstractBinding = asAbstractBinding(); |
264 | if (abstractBinding->kind() != QQmlAbstractBinding::QmlBinding) |
265 | return false; |
266 | return static_cast<QQmlBinding *>(abstractBinding)->hasError(); |
267 | } else { |
268 | return asUntypedPropertyBinding().error().hasError(); |
269 | } |
270 | } |
271 | |
272 | /*! |
273 | Stores a null binding. For purpose of classification, the null bindings is |
274 | treated as a QQmlAbstractPropertyBindings. |
275 | */ |
276 | QQmlAnyBinding &operator=(std::nullptr_t) |
277 | { |
278 | clear(); |
279 | return *this; |
280 | } |
281 | |
282 | operator bool() const{ |
283 | return !d.isNull(); |
284 | } |
285 | |
286 | /*! |
287 | \internal |
288 | Returns true if a binding derived from QQmlAbstractPropertyBinding is stored. |
289 | The binding migh still be null. |
290 | */ |
291 | bool isAbstractPropertyBinding() const |
292 | { return d.isT1(); } |
293 | |
294 | /*! |
295 | \internal |
296 | Returns true if a binding derived from QPropertyBindingPrivate is stored. |
297 | The binding might still be null. |
298 | */ |
299 | bool isUntypedPropertyBinding() const |
300 | { return d.isT2(); } |
301 | |
302 | /*! |
303 | \internal |
304 | Returns the stored QPropertyBindingPrivate as a QUntypedPropertyBinding. |
305 | If no such binding is currently stored, a null QUntypedPropertyBinding is returned. |
306 | */ |
307 | QUntypedPropertyBinding asUntypedPropertyBinding() const |
308 | { |
309 | if (d.isT1() || d.isNull()) |
310 | return {}; |
311 | auto priv = d.asT2(); |
312 | return QUntypedPropertyBinding {priv}; |
313 | } |
314 | |
315 | /*! |
316 | \internal |
317 | Returns the stored QQmlAbstractBinding. |
318 | If no such binding is currently stored, a null pointer is returned. |
319 | */ |
320 | QQmlAbstractBinding *asAbstractBinding() const |
321 | { |
322 | if (d.isT2() || d.isNull()) |
323 | return nullptr; |
324 | return d.asT1(); |
325 | } |
326 | |
327 | /*! |
328 | \internal |
329 | Reevaluates the binding. If the binding was disabled, |
330 | it gets enabled. |
331 | */ |
332 | void refresh() |
333 | { |
334 | if (d.isNull()) |
335 | return; |
336 | if (d.isT1()) { |
337 | auto binding = static_cast<QQmlBinding *>(d.asT1()); |
338 | binding->setEnabledFlag(true); |
339 | binding->refresh(); |
340 | } else { |
341 | auto bindingPriv = d.asT2(); |
342 | PendingBindingObserverList bindingObservers; |
343 | bindingPriv->evaluateRecursive(bindingObservers); |
344 | bindingPriv->notifyNonRecursive(bindingObservers); |
345 | } |
346 | |
347 | } |
348 | |
349 | /*! |
350 | \internal |
351 | Stores \a binding and keeps a reference to it. |
352 | */ |
353 | QQmlAnyBinding &operator=(QQmlAbstractBinding *binding) |
354 | { |
355 | clear(); |
356 | if (binding) { |
357 | d = binding; |
358 | binding->ref.ref(); |
359 | } |
360 | return *this; |
361 | } |
362 | |
363 | /*! |
364 | \internal |
365 | Stores the binding stored in \a binding and keeps a reference to it. |
366 | */ |
367 | QQmlAnyBinding &operator=(const QQmlAbstractBinding::Ptr &binding) |
368 | { |
369 | clear(); |
370 | if (binding) { |
371 | d = binding.data(); |
372 | binding->ref.ref(); |
373 | } |
374 | return *this; |
375 | } |
376 | |
377 | /*! |
378 | \internal |
379 | Stores \a binding's binding, taking ownership from \a binding. |
380 | */ |
381 | QQmlAnyBinding &operator=(QQmlAbstractBinding::Ptr &&binding) |
382 | { |
383 | clear(); |
384 | if (binding) { |
385 | d = binding.take(); |
386 | } |
387 | return *this; |
388 | } |
389 | |
390 | /*! |
391 | \internal |
392 | Stores the binding stored in \a untypedBinding and keeps a reference to it. |
393 | */ |
394 | QQmlAnyBinding &operator=(const QUntypedPropertyBinding &untypedBinding) |
395 | { |
396 | clear(); |
397 | auto binding = QPropertyBindingPrivate::get(binding: untypedBinding); |
398 | if (binding) { |
399 | d = binding; |
400 | binding->addRef(); |
401 | } |
402 | return *this; |
403 | } |
404 | |
405 | /*! |
406 | \internal |
407 | \overload |
408 | Stores the binding stored in \a untypedBinding, taking ownership from it. |
409 | */ |
410 | QQmlAnyBinding &operator=(QUntypedPropertyBinding &&untypedBinding) |
411 | { |
412 | clear(); |
413 | auto binding = QPropertyBindingPrivate::get(binding: untypedBinding); |
414 | QPropertyBindingPrivatePtr ptr(binding); |
415 | if (binding) { |
416 | d = static_cast<QPropertyBindingPrivate *>(ptr.take()); |
417 | } |
418 | return *this; |
419 | } |
420 | |
421 | QQmlAnyBinding(QQmlAnyBinding &&other) noexcept |
422 | : d(std::exchange(obj&: other.d, new_val: QBiPointer<QQmlAbstractBinding, QPropertyBindingPrivate>())) |
423 | {} |
424 | |
425 | QQmlAnyBinding(const QQmlAnyBinding &other) noexcept { *this = other; } |
426 | QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QQmlAnyBinding) |
427 | |
428 | void swap(QQmlAnyBinding &other) noexcept { d.swap(other&: other.d); } |
429 | friend void swap(QQmlAnyBinding &lhs, QQmlAnyBinding &rhs) noexcept { lhs.swap(other&: rhs); } |
430 | |
431 | QQmlAnyBinding &operator=(const QQmlAnyBinding &other) noexcept |
432 | { |
433 | clear(); |
434 | if (auto abstractBinding = other.asAbstractBinding()) |
435 | *this = abstractBinding; |
436 | else if (auto untypedBinding = other.asUntypedPropertyBinding(); !untypedBinding.isNull()) |
437 | *this = untypedBinding; |
438 | return *this; |
439 | } |
440 | |
441 | friend inline bool operator==(const QQmlAnyBinding &p1, const QQmlAnyBinding &p2) |
442 | { |
443 | return p1.d == p2.d; |
444 | } |
445 | |
446 | friend inline bool operator!=(const QQmlAnyBinding &p1, const QQmlAnyBinding &p2) |
447 | { |
448 | return p1.d != p2.d; |
449 | } |
450 | |
451 | ~QQmlAnyBinding() noexcept { clear(); } |
452 | private: |
453 | void clear() noexcept { |
454 | if (d.isNull()) |
455 | return; |
456 | if (d.isT1()) { |
457 | QQmlAbstractBinding *qqmlptr = d.asT1(); |
458 | if (!qqmlptr->ref.deref()) |
459 | delete qqmlptr; |
460 | } else if (d.isT2()) { |
461 | QPropertyBindingPrivate *priv = d.asT2(); |
462 | priv->ref--; |
463 | if (!priv->ref) |
464 | QPropertyBindingPrivate::destroyAndFreeMemory(priv); |
465 | } |
466 | d = static_cast<QQmlAbstractBinding *>(nullptr); |
467 | } |
468 | QBiPointer<QQmlAbstractBinding, QPropertyBindingPrivate> d; |
469 | }; |
470 | |
471 | QT_END_NAMESPACE |
472 | |
473 | |
474 | #endif // QQMLANYBINDINGPTR_P_H |
475 | |