1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml 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#include "qqmlbind_p.h"
41
42#include <private/qqmlnullablevalue_p.h>
43#include <private/qqmlproperty_p.h>
44#include <private/qqmlbinding_p.h>
45#include <private/qqmlmetatype_p.h>
46#include <private/qqmlvmemetaobject_p.h>
47#include <private/qv4persistent_p.h>
48
49#include <qqmlengine.h>
50#include <qqmlcontext.h>
51#include <qqmlproperty.h>
52#include <qqmlinfo.h>
53
54#include <QtCore/qfile.h>
55#include <QtCore/qdebug.h>
56#include <QtCore/qtimer.h>
57#include <QtCore/qloggingcategory.h>
58
59#include <private/qobject_p.h>
60
61QT_BEGIN_NAMESPACE
62
63Q_DECLARE_LOGGING_CATEGORY(lcBindingRemoval)
64Q_LOGGING_CATEGORY(lcQmlBindingRestoreMode, "qt.qml.binding.restoreMode")
65
66class QQmlBindPrivate : public QObjectPrivate
67{
68public:
69 QQmlBindPrivate()
70 : obj(nullptr)
71 , prevBind(QQmlAbstractBinding::Ptr())
72 , prevIsVariant(false)
73 , componentComplete(true)
74 , delayed(false)
75 , pendingEval(false)
76 , restoreBinding(true)
77 , restoreValue(false)
78 , restoreModeExplicit(false)
79 , writingProperty(false)
80 {}
81 ~QQmlBindPrivate() { }
82
83 QQmlNullableValue<bool> when;
84 QPointer<QObject> obj;
85 QString propName;
86 QQmlNullableValue<QJSValue> value;
87 QQmlProperty prop;
88 QQmlAbstractBinding::Ptr prevBind;
89 QV4::PersistentValue v4Value;
90 QVariant prevValue;
91 bool prevIsVariant:1;
92 bool componentComplete:1;
93 bool delayed:1;
94 bool pendingEval:1;
95 bool restoreBinding:1;
96 bool restoreValue:1;
97 bool restoreModeExplicit:1;
98 bool writingProperty: 1;
99
100 void validate(QObject *binding) const;
101 void clearPrev();
102};
103
104void QQmlBindPrivate::validate(QObject *binding) const
105{
106 if (!obj || (when.isValid() && !when))
107 return;
108
109 if (!prop.isValid()) {
110 qmlWarning(me: binding) << "Property '" << propName << "' does not exist on " << QQmlMetaType::prettyTypeName(object: obj) << ".";
111 return;
112 }
113
114 if (!prop.isWritable()) {
115 qmlWarning(me: binding) << "Property '" << propName << "' on " << QQmlMetaType::prettyTypeName(object: obj) << " is read-only.";
116 return;
117 }
118}
119
120/*!
121 \qmltype Binding
122 \instantiates QQmlBind
123 \inqmlmodule QtQml
124 \ingroup qtquick-interceptors
125 \brief Enables the arbitrary creation of property bindings.
126
127 In QML, property bindings result in a dependency between the properties of
128 different objects.
129
130 \section1 Binding to an Inaccessible Property
131
132 Sometimes it is necessary to bind an object's property to
133 that of another object that isn't directly instantiated by QML, such as a
134 property of a class exported to QML by C++. You can use the Binding type
135 to establish this dependency; binding any value to any object's property.
136
137 For example, in a C++ application that maps an "app.enteredText" property
138 into QML, you can use Binding to update the enteredText property.
139
140 \code
141 TextEdit { id: myTextField; text: "Please type here..." }
142 Binding { target: app; property: "enteredText"; value: myTextField.text }
143 \endcode
144
145 When \c{text} changes, the C++ property \c{enteredText} will update
146 automatically.
147
148 \section1 Conditional Bindings
149
150 In some cases you may want to modify the value of a property when a certain
151 condition is met but leave it unmodified otherwise. Often, it's not possible
152 to do this with direct bindings, as you have to supply values for all
153 possible branches.
154
155 For example, the code snippet below results in a warning whenever you
156 release the mouse. This is because the value of the binding is undefined
157 when the mouse isn't pressed.
158
159 \qml
160 // produces warning: "Unable to assign [undefined] to double value"
161 value: if (mouse.pressed) mouse.mouseX
162 \endqml
163
164 The Binding type can prevent this warning.
165
166 \qml
167 Binding on value {
168 when: mouse.pressed
169 value: mouse.mouseX
170 }
171 \endqml
172
173 The Binding type restores any previously set direct bindings on the
174 property.
175
176 \sa {Qt QML}
177*/
178QQmlBind::QQmlBind(QObject *parent)
179 : QObject(*(new QQmlBindPrivate), parent)
180{
181}
182
183QQmlBind::~QQmlBind()
184{
185}
186
187/*!
188 \qmlproperty bool QtQml::Binding::when
189
190 This property holds when the binding is active.
191 This should be set to an expression that evaluates to true when you want the binding to be active.
192
193 \code
194 Binding {
195 target: contactName; property: 'text'
196 value: name; when: list.ListView.isCurrentItem
197 }
198 \endcode
199
200 When the binding becomes inactive again, any direct bindings that were previously
201 set on the property will be restored.
202
203 \note By default, a previously set literal value is not restored when the Binding becomes
204 inactive. Rather, the last value set by the now inactive Binding is retained. You can customize
205 the restoration behavior for literal values as well as bindings using the \l restoreMode
206 property. The default will change in Qt 6.0.
207
208 \sa restoreMode
209*/
210bool QQmlBind::when() const
211{
212 Q_D(const QQmlBind);
213 return d->when;
214}
215
216void QQmlBind::setWhen(bool v)
217{
218 Q_D(QQmlBind);
219 if (!d->when.isNull && d->when == v)
220 return;
221
222 d->when = v;
223 if (v && d->componentComplete)
224 d->validate(binding: this);
225 eval();
226}
227
228/*!
229 \qmlproperty Object QtQml::Binding::target
230
231 The object to be updated.
232*/
233QObject *QQmlBind::object()
234{
235 Q_D(const QQmlBind);
236 return d->obj;
237}
238
239void QQmlBind::setObject(QObject *obj)
240{
241 Q_D(QQmlBind);
242 if (d->obj && d->when.isValid() && d->when) {
243 /* if we switch the object at runtime, we need to restore the
244 previous binding on the old object before continuing */
245 d->when = false;
246 eval();
247 d->when = true;
248 }
249 d->obj = obj;
250 if (d->componentComplete) {
251 setTarget(QQmlProperty(d->obj, d->propName, qmlContext(this)));
252 d->validate(binding: this);
253 }
254 eval();
255}
256
257/*!
258 \qmlproperty string QtQml::Binding::property
259
260 The property to be updated.
261
262 This can be a group property if the expression results in accessing a
263 property of a \l {QML Basic Types}{value type}. For example:
264
265 \qml
266 Item {
267 id: item
268
269 property rect rectangle: Qt.rect(0, 0, 200, 200)
270 }
271
272 Binding {
273 target: item
274 property: "rectangle.x"
275 value: 100
276 }
277 \endqml
278*/
279QString QQmlBind::property() const
280{
281 Q_D(const QQmlBind);
282 return d->propName;
283}
284
285void QQmlBind::setProperty(const QString &p)
286{
287 Q_D(QQmlBind);
288 if (!d->propName.isEmpty() && d->when.isValid() && d->when) {
289 /* if we switch the property name at runtime, we need to restore the
290 previous binding on the old object before continuing */
291 d->when = false;
292 eval();
293 d->when = true;
294 }
295 d->propName = p;
296 if (d->componentComplete) {
297 setTarget(QQmlProperty(d->obj, d->propName, qmlContext(this)));
298 d->validate(binding: this);
299 }
300 eval();
301}
302
303/*!
304 \qmlproperty any QtQml::Binding::value
305
306 The value to be set on the target object and property. This can be a
307 constant (which isn't very useful), or a bound expression.
308*/
309QJSValue QQmlBind::value() const
310{
311 Q_D(const QQmlBind);
312 return d->value.value;
313}
314
315void QQmlBind::setValue(const QJSValue &v)
316{
317 Q_D(QQmlBind);
318 d->value = v;
319 prepareEval();
320}
321
322/*!
323 \qmlproperty bool QtQml::Binding::delayed
324 \since 5.8
325
326 This property holds whether the binding should be delayed.
327
328 A delayed binding will not immediately update the target, but rather wait
329 until the event queue has been cleared. This can be used as an optimization,
330 or to prevent intermediary values from being assigned.
331
332 \code
333 Binding {
334 target: contactName; property: 'text'
335 value: givenName + " " + familyName; when: list.ListView.isCurrentItem
336 delayed: true
337 }
338 \endcode
339*/
340bool QQmlBind::delayed() const
341{
342 Q_D(const QQmlBind);
343 return d->delayed;
344}
345
346void QQmlBind::setDelayed(bool delayed)
347{
348 Q_D(QQmlBind);
349 if (d->delayed == delayed)
350 return;
351
352 d->delayed = delayed;
353
354 if (!d->delayed)
355 eval();
356}
357
358/*!
359 \qmlproperty enumeration QtQml::Binding::restoreMode
360 \since 5.14
361
362 This property can be used to describe if and how the original value should
363 be restored when the binding is disabled.
364
365 The possible values are:
366 \list
367 \li Binding.RestoreNone The original value is not restored at all
368 \li Binding.RestoreBinding The original value is restored if it was another
369 binding. In that case the old binding is in effect again.
370 \li Binding.RestoreValue The original value is restored if it was a plain
371 value rather than a binding.
372 \li Binding.RestoreBindingOrValue The original value is always restored.
373 \endlist
374
375 \warning The default value is Binding.RestoreBinding. This will change in
376 Qt 6.0 to Binding.RestoreBindingOrValue.
377
378 If you rely on any specific behavior regarding the restoration of plain
379 values when bindings get disabled you should migrate to explicitly set the
380 restoreMode.
381
382 Reliance on a restoreMode that doesn't restore the previous binding or value
383 for a specific property results in a run-time warning.
384*/
385QQmlBind::RestorationMode QQmlBind::restoreMode() const
386{
387 Q_D(const QQmlBind);
388 unsigned result = RestoreNone;
389 if (d->restoreValue)
390 result |= RestoreValue;
391 if (d->restoreBinding)
392 result |= RestoreBinding;
393 return RestorationMode(result);
394}
395
396void QQmlBind::setRestoreMode(RestorationMode newMode)
397{
398 Q_D(QQmlBind);
399 d->restoreModeExplicit = true;
400 if (newMode != restoreMode()) {
401 d->restoreValue = (newMode & RestoreValue);
402 d->restoreBinding = (newMode & RestoreBinding);
403 emit restoreModeChanged();
404 }
405}
406
407void QQmlBind::setTarget(const QQmlProperty &p)
408{
409 Q_D(QQmlBind);
410
411 if (Q_UNLIKELY(lcBindingRemoval().isInfoEnabled())) {
412 if (QObject *oldObject = d->prop.object()) {
413 QMetaProperty prop = oldObject->metaObject()->property(index: d->prop.index());
414 if (prop.hasNotifySignal()) {
415 QByteArray signal('2' + prop.notifySignal().methodSignature());
416 QObject::disconnect(sender: oldObject, signal: signal.constData(),
417 receiver: this, SLOT(targetValueChanged()));
418 }
419 }
420 p.connectNotifySignal(dest: this, SLOT(targetValueChanged()));
421 }
422
423 d->prop = p;
424}
425
426void QQmlBind::classBegin()
427{
428 Q_D(QQmlBind);
429 d->componentComplete = false;
430}
431
432void QQmlBind::componentComplete()
433{
434 Q_D(QQmlBind);
435 d->componentComplete = true;
436 if (!d->prop.isValid()) {
437 setTarget(QQmlProperty(d->obj, d->propName, qmlContext(this)));
438 d->validate(binding: this);
439 }
440 eval();
441}
442
443void QQmlBind::prepareEval()
444{
445 Q_D(QQmlBind);
446 if (d->delayed) {
447 if (!d->pendingEval)
448 QTimer::singleShot(interval: 0, receiver: this, slot: &QQmlBind::eval);
449 d->pendingEval = true;
450 } else {
451 eval();
452 }
453}
454
455void QQmlBindPrivate::clearPrev()
456{
457 prevBind = nullptr;
458 v4Value.clear();
459 prevValue.clear();
460 prevIsVariant = false;
461}
462
463void QQmlBind::eval()
464{
465 Q_D(QQmlBind);
466 d->pendingEval = false;
467 if (!d->prop.isValid() || d->value.isNull || !d->componentComplete)
468 return;
469
470 if (d->when.isValid()) {
471 if (!d->when) {
472 //restore any previous binding
473 if (d->prevBind) {
474 if (d->restoreBinding) {
475 QQmlAbstractBinding::Ptr p = d->prevBind;
476 d->clearPrev(); // Do that before setBinding(), as setBinding() may recurse.
477 QQmlPropertyPrivate::setBinding(binding: p.data());
478 }
479 } else if (!d->v4Value.isEmpty()) {
480 if (d->restoreValue) {
481 auto propPriv = QQmlPropertyPrivate::get(p: d->prop);
482 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: propPriv->object);
483 Q_ASSERT(vmemo);
484 vmemo->setVMEProperty(index: propPriv->core.coreIndex(), v: *d->v4Value.valueRef());
485 d->clearPrev();
486 } else if (!d->restoreModeExplicit && lcQmlBindingRestoreMode().isWarningEnabled()) {
487 qmlWarning(me: this)
488 << "Not restoring previous value because restoreMode has not been set.\n"
489 << "This behavior is deprecated.\n"
490 << "You have to import QtQml 2.15 after any QtQuick imports and set\n"
491 << "the restoreMode of the binding to fix this warning.\n"
492 << "In Qt < 6.0 the default is Binding.RestoreBinding.\n"
493 << "In Qt >= 6.0 the default is Binding.RestoreBindingOrValue.";
494 }
495 } else if (d->prevIsVariant) {
496 if (d->restoreValue) {
497 d->prop.write(d->prevValue);
498 d->clearPrev();
499 } else if (!d->restoreModeExplicit && lcQmlBindingRestoreMode().isWarningEnabled()) {
500 qmlWarning(me: this)
501 << "Not restoring previous value because restoreMode has not been set.\n"
502 << "This behavior is deprecated.\n"
503 << "You have to import QtQml 2.15 after any QtQuick imports and set\n"
504 << "the restoreMode of the binding to fix this warning.\n"
505 << "In Qt < 6.0 the default is Binding.RestoreBinding.\n"
506 << "In Qt >= 6.0 the default is Binding.RestoreBindingOrValue.\n";
507 }
508 }
509 return;
510 }
511
512 //save any set binding for restoration
513 if (!d->prevBind && d->v4Value.isEmpty() && !d->prevIsVariant) {
514 // try binding first
515 d->prevBind = QQmlPropertyPrivate::binding(that: d->prop);
516
517 if (!d->prevBind) { // nope, try a V4 value next
518 auto propPriv = QQmlPropertyPrivate::get(p: d->prop);
519 auto propData = propPriv->core;
520 if (!propPriv->valueTypeData.isValid() && propData.isVarProperty()) {
521 QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: propPriv->object);
522 Q_ASSERT(vmemo);
523 auto retVal = vmemo->vmeProperty(index: propData.coreIndex());
524 d->v4Value = QV4::PersistentValue(vmemo->engine, retVal);
525 } else { // nope, use the meta object to get a QVariant
526 d->prevValue = d->prop.read();
527 d->prevIsVariant = true;
528 }
529 }
530 }
531
532 QQmlPropertyPrivate::removeBinding(that: d->prop);
533 }
534
535 d->writingProperty = true;
536 d->prop.write(d->value.value.toVariant());
537 d->writingProperty = false;
538}
539
540void QQmlBind::targetValueChanged()
541{
542 Q_D(QQmlBind);
543 if (d->writingProperty)
544 return;
545
546 if (d->when.isValid() && !d->when)
547 return;
548
549 QUrl url;
550 quint16 line = 0;
551
552 const QQmlData *ddata = QQmlData::get(object: this, create: false);
553 if (ddata && ddata->outerContext) {
554 url = ddata->outerContext->url();
555 line = ddata->lineNumber;
556 }
557
558 qCInfo(lcBindingRemoval,
559 "The target property of the Binding element created at %s:%d was changed from "
560 "elsewhere. This does not overwrite the binding. The target property will still be "
561 "updated when the value of the Binding element changes.",
562 qPrintable(url.toString()), line);
563}
564
565QT_END_NAMESPACE
566
567#include "moc_qqmlbind_p.cpp"
568

source code of qtdeclarative/src/qml/types/qqmlbind.cpp