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