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 
61 QT_BEGIN_NAMESPACE
62 
63 Q_DECLARE_LOGGING_CATEGORY(lcBindingRemoval)
64 Q_LOGGING_CATEGORY(lcQmlBindingRestoreMode, "qt.qml.binding.restoreMode")
65 
66 class QQmlBindPrivate : public QObjectPrivate
67 {
68 public:
QQmlBindPrivate()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     {}
~QQmlBindPrivate()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 
validate(QObject * binding) const104 void QQmlBindPrivate::validate(QObject *binding) const
105 {
106     if (!obj || (when.isValid() && !when))
107         return;
108 
109     if (!prop.isValid()) {
110         qmlWarning(binding) << "Property '" << propName << "' does not exist on " << QQmlMetaType::prettyTypeName(obj) << ".";
111         return;
112     }
113 
114     if (!prop.isWritable()) {
115         qmlWarning(binding) << "Property '" << propName << "' on " << QQmlMetaType::prettyTypeName(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 */
QQmlBind(QObject * parent)178 QQmlBind::QQmlBind(QObject *parent)
179     : QObject(*(new QQmlBindPrivate), parent)
180 {
181 }
182 
~QQmlBind()183 QQmlBind::~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 */
when() const210 bool QQmlBind::when() const
211 {
212     Q_D(const QQmlBind);
213     return d->when;
214 }
215 
setWhen(bool v)216 void 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(this);
225     eval();
226 }
227 
228 /*!
229     \qmlproperty Object QtQml::Binding::target
230 
231     The object to be updated.
232 */
object()233 QObject *QQmlBind::object()
234 {
235     Q_D(const QQmlBind);
236     return d->obj;
237 }
238 
setObject(QObject * obj)239 void 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(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 */
property() const279 QString QQmlBind::property() const
280 {
281     Q_D(const QQmlBind);
282     return d->propName;
283 }
284 
setProperty(const QString & p)285 void 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(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 */
value() const309 QJSValue QQmlBind::value() const
310 {
311     Q_D(const QQmlBind);
312     return d->value.value;
313 }
314 
setValue(const QJSValue & v)315 void 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 */
delayed() const340 bool QQmlBind::delayed() const
341 {
342     Q_D(const QQmlBind);
343     return d->delayed;
344 }
345 
setDelayed(bool delayed)346 void 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 */
restoreMode() const385 QQmlBind::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 
setRestoreMode(RestorationMode newMode)396 void 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 
setTarget(const QQmlProperty & p)407 void 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(d->prop.index());
414             if (prop.hasNotifySignal()) {
415                 QByteArray signal('2' + prop.notifySignal().methodSignature());
416                 QObject::disconnect(oldObject, signal.constData(),
417                                     this, SLOT(targetValueChanged()));
418             }
419         }
420         p.connectNotifySignal(this, SLOT(targetValueChanged()));
421     }
422 
423     d->prop = p;
424 }
425 
classBegin()426 void QQmlBind::classBegin()
427 {
428     Q_D(QQmlBind);
429     d->componentComplete = false;
430 }
431 
componentComplete()432 void 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(this);
439     }
440     eval();
441 }
442 
prepareEval()443 void QQmlBind::prepareEval()
444 {
445     Q_D(QQmlBind);
446     if (d->delayed) {
447         if (!d->pendingEval)
448             QTimer::singleShot(0, this, &QQmlBind::eval);
449         d->pendingEval = true;
450     } else {
451         eval();
452     }
453 }
454 
clearPrev()455 void QQmlBindPrivate::clearPrev()
456 {
457     prevBind = nullptr;
458     v4Value.clear();
459     prevValue.clear();
460     prevIsVariant = false;
461 }
462 
eval()463 void 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(p.data());
478                 }
479             } else if (!d->v4Value.isEmpty()) {
480                 if (d->restoreValue) {
481                     auto propPriv = QQmlPropertyPrivate::get(d->prop);
482                     QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(propPriv->object);
483                     Q_ASSERT(vmemo);
484                     vmemo->setVMEProperty(propPriv->core.coreIndex(), *d->v4Value.valueRef());
485                     d->clearPrev();
486                 } else if (!d->restoreModeExplicit && lcQmlBindingRestoreMode().isWarningEnabled()) {
487                     qmlWarning(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(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(d->prop);
516 
517             if (!d->prevBind) { // nope, try a V4 value next
518                 auto propPriv = QQmlPropertyPrivate::get(d->prop);
519                 auto propData = propPriv->core;
520                 if (!propPriv->valueTypeData.isValid() && propData.isVarProperty()) {
521                     QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(propPriv->object);
522                     Q_ASSERT(vmemo);
523                     auto retVal = vmemo->vmeProperty(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(d->prop);
533     }
534 
535     d->writingProperty = true;
536     d->prop.write(d->value.value.toVariant());
537     d->writingProperty = false;
538 }
539 
targetValueChanged()540 void 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(this, 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 
565 QT_END_NAMESPACE
566 
567 #include "moc_qqmlbind_p.cpp"
568