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
22QT_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 */
44class QQmlAnyBinding {
45public:
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(); }
452private:
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
471QT_END_NAMESPACE
472
473
474#endif // QQMLANYBINDINGPTR_P_H
475

source code of qtdeclarative/src/qml/qml/qqmlanybinding_p.h