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 "qqmlbinding_p.h" |
41 | |
42 | #include "qqml.h" |
43 | #include "qqmlcontext.h" |
44 | #include "qqmlinfo.h" |
45 | #include "qqmldata_p.h" |
46 | #include <private/qqmlprofiler_p.h> |
47 | #include <private/qqmlexpression_p.h> |
48 | #include <private/qqmlscriptstring_p.h> |
49 | #include <private/qqmlbuiltinfunctions_p.h> |
50 | #include <private/qqmlvmemetaobject_p.h> |
51 | #include <private/qqmlvaluetypewrapper_p.h> |
52 | #include <private/qv4qmlcontext_p.h> |
53 | #include <private/qv4qobjectwrapper_p.h> |
54 | #include <private/qv4variantobject_p.h> |
55 | #include <private/qv4jscall_p.h> |
56 | |
57 | #include <QVariant> |
58 | #include <QtCore/qdebug.h> |
59 | #include <QVector> |
60 | |
61 | QT_BEGIN_NAMESPACE |
62 | |
63 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) |
64 | { |
65 | QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); |
66 | |
67 | if (ctxt && !ctxt->isValid()) |
68 | return b; |
69 | |
70 | const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); |
71 | if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) |
72 | return b; |
73 | |
74 | QString url; |
75 | QV4::Function *runtimeFunction = nullptr; |
76 | |
77 | QQmlContextData *ctxtdata = QQmlContextData::get(scriptPrivate->context); |
78 | QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine()); |
79 | if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit) { |
80 | url = ctxtdata->urlString(); |
81 | if (scriptPrivate->bindingId != QQmlBinding::Invalid) |
82 | runtimeFunction = ctxtdata->typeCompilationUnit->runtimeFunctions.at(scriptPrivate->bindingId); |
83 | } |
84 | |
85 | b->setNotifyOnValueChanged(true); |
86 | b->QQmlJavaScriptExpression::setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context)); |
87 | b->setScopeObject(obj ? obj : scriptPrivate->scope); |
88 | |
89 | QV4::ExecutionEngine *v4 = b->context()->engine->handle(); |
90 | if (runtimeFunction) { |
91 | QV4::Scope scope(v4); |
92 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxtdata, b->scopeObject())); |
93 | b->setupFunction(qmlContext, runtimeFunction); |
94 | } else { |
95 | QString code = scriptPrivate->script; |
96 | b->createQmlBinding(b->context(), b->scopeObject(), code, url, scriptPrivate->lineNumber); |
97 | } |
98 | |
99 | return b; |
100 | } |
101 | |
102 | QQmlSourceLocation QQmlBinding::sourceLocation() const |
103 | { |
104 | if (m_sourceLocation) |
105 | return *m_sourceLocation; |
106 | return QQmlJavaScriptExpression::sourceLocation(); |
107 | } |
108 | |
109 | void QQmlBinding::setSourceLocation(const QQmlSourceLocation &location) |
110 | { |
111 | if (m_sourceLocation) |
112 | delete m_sourceLocation; |
113 | m_sourceLocation = new QQmlSourceLocation(location); |
114 | } |
115 | |
116 | |
117 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QString &str, QObject *obj, |
118 | QQmlContextData *ctxt, const QString &url, quint16 lineNumber) |
119 | { |
120 | QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); |
121 | |
122 | b->setNotifyOnValueChanged(true); |
123 | b->QQmlJavaScriptExpression::setContext(ctxt); |
124 | b->setScopeObject(obj); |
125 | |
126 | b->createQmlBinding(ctxt, obj, str, url, lineNumber); |
127 | |
128 | return b; |
129 | } |
130 | |
131 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, QV4::Function *function, |
132 | QObject *obj, QQmlContextData *ctxt, QV4::ExecutionContext *scope) |
133 | { |
134 | QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); |
135 | |
136 | b->setNotifyOnValueChanged(true); |
137 | b->QQmlJavaScriptExpression::setContext(ctxt); |
138 | b->setScopeObject(obj); |
139 | |
140 | Q_ASSERT(scope); |
141 | b->setupFunction(scope, function); |
142 | |
143 | return b; |
144 | } |
145 | |
146 | QQmlBinding::~QQmlBinding() |
147 | { |
148 | delete m_sourceLocation; |
149 | } |
150 | |
151 | void QQmlBinding::setNotifyOnValueChanged(bool v) |
152 | { |
153 | QQmlJavaScriptExpression::setNotifyOnValueChanged(v); |
154 | } |
155 | |
156 | void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) |
157 | { |
158 | if (!enabledFlag() || !context() || !context()->isValid()) |
159 | return; |
160 | |
161 | // Check that the target has not been deleted |
162 | if (QQmlData::wasDeleted(targetObject())) |
163 | return; |
164 | |
165 | // Check for a binding update loop |
166 | if (Q_UNLIKELY(updatingFlag())) { |
167 | QQmlPropertyData *d = nullptr; |
168 | QQmlPropertyData vtd; |
169 | getPropertyData(&d, &vtd); |
170 | Q_ASSERT(d); |
171 | QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), *d, &vtd, nullptr); |
172 | QQmlAbstractBinding::printBindingLoopError(p); |
173 | return; |
174 | } |
175 | setUpdatingFlag(true); |
176 | |
177 | DeleteWatcher watcher(this); |
178 | |
179 | QQmlEngine *engine = context()->engine; |
180 | QV4::Scope scope(engine->handle()); |
181 | |
182 | if (canUseAccessor()) |
183 | flags.setFlag(QQmlPropertyData::BypassInterceptor); |
184 | |
185 | QQmlBindingProfiler prof(QQmlEnginePrivate::get(engine)->profiler, function()); |
186 | doUpdate(watcher, flags, scope); |
187 | |
188 | if (!watcher.wasDeleted()) |
189 | setUpdatingFlag(false); |
190 | } |
191 | |
192 | QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) |
193 | { |
194 | QV4::ExecutionEngine *v4 = context()->engine->handle(); |
195 | int argc = 0; |
196 | const QV4::Value *argv = nullptr; |
197 | const QV4::Value *thisObject = nullptr; |
198 | QV4::BoundFunction *b = nullptr; |
199 | if ((b = static_cast<QV4::BoundFunction *>(m_boundFunction.valueRef()))) { |
200 | QV4::Heap::MemberData *args = b->boundArgs(); |
201 | if (args) { |
202 | argc = args->values.size; |
203 | argv = args->values.data(); |
204 | } |
205 | thisObject = &b->d()->boundThis; |
206 | } |
207 | QV4::Scope scope(v4); |
208 | QV4::JSCallData jsCall(scope, argc, argv, thisObject); |
209 | |
210 | return QQmlJavaScriptExpression::evaluate(jsCall.callData(), isUndefined); |
211 | } |
212 | |
213 | |
214 | // QQmlBindingBinding is for target properties which are of type "binding" (instead of, say, int or |
215 | // double). The reason for being is that GenericBinding::fastWrite needs a compile-time constant |
216 | // expression for the switch for the compiler to generate the optimal code, but |
217 | // qMetaTypeId<QQmlBinding *>() needs to be used for the ID. So QQmlBinding::newBinding uses that |
218 | // to instantiate this class. |
219 | class QQmlBindingBinding: public QQmlBinding |
220 | { |
221 | protected: |
222 | void doUpdate(const DeleteWatcher &, |
223 | QQmlPropertyData::WriteFlags flags, QV4::Scope &) override final |
224 | { |
225 | Q_ASSERT(!m_targetIndex.hasValueTypeIndex()); |
226 | QQmlPropertyData *pd = nullptr; |
227 | getPropertyData(&pd, nullptr); |
228 | QQmlBinding *thisPtr = this; |
229 | pd->writeProperty(*m_target, &thisPtr, flags); |
230 | } |
231 | }; |
232 | |
233 | // For any target that's not a binding, we have a common doUpdate. However, depending on the type |
234 | // of the target property, there is a specialized write method. |
235 | class QQmlNonbindingBinding: public QQmlBinding |
236 | { |
237 | protected: |
238 | void doUpdate(const DeleteWatcher &watcher, |
239 | QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override |
240 | { |
241 | auto ep = QQmlEnginePrivate::get(scope.engine); |
242 | ep->referenceScarceResources(); |
243 | |
244 | bool isUndefined = false; |
245 | |
246 | QV4::ScopedValue result(scope, evaluate(&isUndefined)); |
247 | |
248 | bool error = false; |
249 | if (!watcher.wasDeleted() && isAddedToObject() && !hasError()) |
250 | error = !write(result, isUndefined, flags); |
251 | |
252 | if (!watcher.wasDeleted()) { |
253 | |
254 | if (error) { |
255 | delayedError()->setErrorLocation(sourceLocation()); |
256 | delayedError()->setErrorObject(m_target.data()); |
257 | } |
258 | |
259 | if (hasError()) { |
260 | if (!delayedError()->addError(ep)) ep->warning(this->error(context()->engine)); |
261 | } else { |
262 | clearError(); |
263 | } |
264 | } |
265 | |
266 | ep->dereferenceScarceResources(); |
267 | } |
268 | |
269 | virtual bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) = 0; |
270 | }; |
271 | |
272 | template<int StaticPropType> |
273 | class GenericBinding: public QQmlNonbindingBinding |
274 | { |
275 | protected: |
276 | // Returns true if successful, false if an error description was set on expression |
277 | Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, |
278 | QQmlPropertyData::WriteFlags flags) override final |
279 | { |
280 | Q_ASSERT(targetObject()); |
281 | |
282 | QQmlPropertyData *pd; |
283 | QQmlPropertyData vpd; |
284 | getPropertyData(&pd, &vpd); |
285 | Q_ASSERT(pd); |
286 | |
287 | int propertyType = StaticPropType; // If the binding is specialized to a type, the if and switch below will be constant-folded. |
288 | if (propertyType == QMetaType::UnknownType) |
289 | propertyType = pd->propType(); |
290 | |
291 | if (Q_LIKELY(!isUndefined && !vpd.isValid())) { |
292 | switch (propertyType) { |
293 | case QMetaType::Bool: |
294 | if (result.isBoolean()) |
295 | return doStore<bool>(result.booleanValue(), pd, flags); |
296 | else |
297 | return doStore<bool>(result.toBoolean(), pd, flags); |
298 | case QMetaType::Int: |
299 | if (result.isInteger()) |
300 | return doStore<int>(result.integerValue(), pd, flags); |
301 | else if (result.isNumber()) |
302 | return doStore<int>(result.doubleValue(), pd, flags); |
303 | break; |
304 | case QMetaType::Double: |
305 | if (result.isNumber()) |
306 | return doStore<double>(result.asDouble(), pd, flags); |
307 | break; |
308 | case QMetaType::Float: |
309 | if (result.isNumber()) |
310 | return doStore<float>(result.asDouble(), pd, flags); |
311 | break; |
312 | case QMetaType::QString: |
313 | if (result.isString()) |
314 | return doStore<QString>(result.toQStringNoThrow(), pd, flags); |
315 | break; |
316 | default: |
317 | if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) { |
318 | if (vtw->d()->valueType->metaType.id() == pd->propType()) { |
319 | return vtw->write(m_target.data(), pd->coreIndex()); |
320 | } |
321 | } |
322 | break; |
323 | } |
324 | } |
325 | |
326 | return slowWrite(*pd, vpd, result, isUndefined, flags); |
327 | } |
328 | |
329 | template <typename T> |
330 | Q_ALWAYS_INLINE bool doStore(T value, const QQmlPropertyData *pd, QQmlPropertyData::WriteFlags flags) const |
331 | { |
332 | void *o = &value; |
333 | return pd->writeProperty(targetObject(), o, flags); |
334 | } |
335 | }; |
336 | |
337 | class QQmlTranslationBinding : public GenericBinding<QMetaType::QString> { |
338 | public: |
339 | QQmlTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding) |
340 | { |
341 | setCompilationUnit(compilationUnit); |
342 | m_binding = binding; |
343 | } |
344 | |
345 | QQmlSourceLocation sourceLocation() const override final |
346 | { |
347 | return QQmlSourceLocation(m_compilationUnit->fileName(), m_binding->valueLocation.line, m_binding->valueLocation.column); |
348 | } |
349 | |
350 | |
351 | void doUpdate(const DeleteWatcher &watcher, |
352 | QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override final |
353 | { |
354 | if (watcher.wasDeleted()) |
355 | return; |
356 | |
357 | if (!isAddedToObject() || hasError()) |
358 | return; |
359 | |
360 | const QString result = m_compilationUnit->bindingValueAsString(m_binding); |
361 | |
362 | Q_ASSERT(targetObject()); |
363 | |
364 | QQmlPropertyData *pd; |
365 | QQmlPropertyData vpd; |
366 | getPropertyData(&pd, &vpd); |
367 | Q_ASSERT(pd); |
368 | if (pd->propType() == QMetaType::QString) { |
369 | doStore(result, pd, flags); |
370 | } else { |
371 | QV4::ScopedString value(scope, scope.engine->newString(result)); |
372 | slowWrite(*pd, vpd, value, /*isUndefined*/false, flags); |
373 | } |
374 | } |
375 | |
376 | bool hasDependencies() const override final { return true; } |
377 | |
378 | private: |
379 | const QV4::CompiledData::Binding *m_binding; |
380 | }; |
381 | |
382 | QQmlBinding *QQmlBinding::createTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, const QV4::CompiledData::Binding *binding, QObject *obj, QQmlContextData *ctxt) |
383 | { |
384 | QQmlTranslationBinding *b = new QQmlTranslationBinding(unit, binding); |
385 | |
386 | b->setNotifyOnValueChanged(true); |
387 | b->QQmlJavaScriptExpression::setContext(ctxt); |
388 | b->setScopeObject(obj); |
389 | |
390 | return b; |
391 | } |
392 | |
393 | Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, |
394 | const QQmlPropertyData &valueTypeData, |
395 | const QV4::Value &result, |
396 | bool isUndefined, QQmlPropertyData::WriteFlags flags) |
397 | { |
398 | QQmlEngine *engine = context()->engine; |
399 | QV4::ExecutionEngine *v4engine = engine->handle(); |
400 | |
401 | int type = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); |
402 | |
403 | QQmlJavaScriptExpression::DeleteWatcher watcher(this); |
404 | |
405 | QVariant value; |
406 | bool isVarProperty = core.isVarProperty(); |
407 | |
408 | if (isUndefined) { |
409 | } else if (core.isQList()) { |
410 | value = v4engine->toVariant(result, qMetaTypeId<QList<QObject *> >()); |
411 | } else if (result.isNull() && core.isQObject()) { |
412 | value = QVariant::fromValue((QObject *)nullptr); |
413 | } else if (core.propType() == qMetaTypeId<QList<QUrl> >()) { |
414 | value = QQmlPropertyPrivate::resolvedUrlSequence(v4engine->toVariant(result, qMetaTypeId<QList<QUrl> >()), context()); |
415 | } else if (!isVarProperty && type != qMetaTypeId<QJSValue>()) { |
416 | value = v4engine->toVariant(result, type); |
417 | } |
418 | |
419 | if (hasError()) { |
420 | return false; |
421 | } else if (isVarProperty) { |
422 | const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); |
423 | if (f && f->isBinding()) { |
424 | // we explicitly disallow this case to avoid confusion. Users can still store one |
425 | // in an array in a var property if they need to, but the common case is user error. |
426 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
427 | return false; |
428 | } |
429 | |
430 | QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(m_target.data()); |
431 | Q_ASSERT(vmemo); |
432 | vmemo->setVMEProperty(core.coreIndex(), result); |
433 | } else if (isUndefined && core.isResettable()) { |
434 | void *args[] = { nullptr }; |
435 | QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); |
436 | } else if (isUndefined && type == qMetaTypeId<QVariant>()) { |
437 | QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant(), context(), flags); |
438 | } else if (type == qMetaTypeId<QJSValue>()) { |
439 | const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); |
440 | if (f && f->isBinding()) { |
441 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
442 | return false; |
443 | } |
444 | QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant::fromValue( |
445 | QJSValue(v4engine, result.asReturnedValue())), |
446 | context(), flags); |
447 | } else if (isUndefined) { |
448 | const QLatin1String typeName(QMetaType::typeName(type) |
449 | ? QMetaType::typeName(type) |
450 | : "[unknown property type]" ); |
451 | delayedError()->setErrorDescription(QLatin1String("Unable to assign [undefined] to " ) |
452 | + typeName); |
453 | return false; |
454 | } else if (const QV4::FunctionObject *f = result.as<QV4::FunctionObject>()) { |
455 | if (f->isBinding()) |
456 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
457 | else |
458 | delayedError()->setErrorDescription(QLatin1String("Unable to assign a function to a property of any type other than var." )); |
459 | return false; |
460 | } else if (!QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, value, context(), flags)) { |
461 | |
462 | if (watcher.wasDeleted()) |
463 | return true; |
464 | |
465 | const char *valueType = nullptr; |
466 | const char *propertyType = nullptr; |
467 | |
468 | const int userType = value.userType(); |
469 | if (userType == QMetaType::QObjectStar) { |
470 | if (QObject *o = *(QObject *const *)value.constData()) { |
471 | valueType = o->metaObject()->className(); |
472 | |
473 | QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(QQmlEnginePrivate::get(engine), type); |
474 | if (!propertyMetaObject.isNull()) |
475 | propertyType = propertyMetaObject.className(); |
476 | } |
477 | } else if (userType != QVariant::Invalid) { |
478 | if (userType == QMetaType::Nullptr || userType == QMetaType::VoidStar) |
479 | valueType = "null" ; |
480 | else |
481 | valueType = QMetaType::typeName(userType); |
482 | } |
483 | |
484 | if (!valueType) |
485 | valueType = "undefined" ; |
486 | if (!propertyType) |
487 | propertyType = QMetaType::typeName(type); |
488 | if (!propertyType) |
489 | propertyType = "[unknown property type]" ; |
490 | |
491 | delayedError()->setErrorDescription(QLatin1String("Unable to assign " ) + |
492 | QLatin1String(valueType) + |
493 | QLatin1String(" to " ) + |
494 | QLatin1String(propertyType)); |
495 | return false; |
496 | } |
497 | |
498 | return true; |
499 | } |
500 | |
501 | QVariant QQmlBinding::evaluate() |
502 | { |
503 | QQmlEngine *engine = context()->engine; |
504 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); |
505 | ep->referenceScarceResources(); |
506 | |
507 | bool isUndefined = false; |
508 | |
509 | QV4::Scope scope(engine->handle()); |
510 | QV4::ScopedValue result(scope, QQmlJavaScriptExpression::evaluate(&isUndefined)); |
511 | |
512 | ep->dereferenceScarceResources(); |
513 | |
514 | return scope.engine->toVariant(result, qMetaTypeId<QList<QObject*> >()); |
515 | } |
516 | |
517 | QString QQmlBinding::expressionIdentifier() const |
518 | { |
519 | if (auto f = function()) { |
520 | QString url = f->sourceFile(); |
521 | uint lineNumber = f->compiledFunction->location.line; |
522 | uint columnNumber = f->compiledFunction->location.column; |
523 | return url + QString::asprintf(":%u:%u" , lineNumber, columnNumber); |
524 | } |
525 | |
526 | return QStringLiteral("[native code]" ); |
527 | } |
528 | |
529 | void QQmlBinding::expressionChanged() |
530 | { |
531 | update(); |
532 | } |
533 | |
534 | void QQmlBinding::refresh() |
535 | { |
536 | update(); |
537 | } |
538 | |
539 | void QQmlBinding::setEnabled(bool e, QQmlPropertyData::WriteFlags flags) |
540 | { |
541 | const bool wasEnabled = enabledFlag(); |
542 | setEnabledFlag(e); |
543 | setNotifyOnValueChanged(e); |
544 | |
545 | m_nextBinding.setFlag2(); // Always use accessors, only not when: |
546 | if (auto interceptorMetaObject = QQmlInterceptorMetaObject::get(targetObject())) { |
547 | if (!m_targetIndex.isValid() || interceptorMetaObject->intercepts(m_targetIndex)) |
548 | m_nextBinding.clearFlag2(); |
549 | } |
550 | |
551 | if (e && !wasEnabled) |
552 | update(flags); |
553 | } |
554 | |
555 | QString QQmlBinding::expression() const |
556 | { |
557 | return QStringLiteral("function() { [native code] }" ); |
558 | } |
559 | |
560 | void QQmlBinding::setTarget(const QQmlProperty &prop) |
561 | { |
562 | auto pd = QQmlPropertyPrivate::get(prop); |
563 | setTarget(prop.object(), pd->core, &pd->valueTypeData); |
564 | } |
565 | |
566 | bool QQmlBinding::setTarget(QObject *object, const QQmlPropertyData &core, const QQmlPropertyData *valueType) |
567 | { |
568 | m_target = object; |
569 | |
570 | if (!object) { |
571 | m_targetIndex = QQmlPropertyIndex(); |
572 | return false; |
573 | } |
574 | |
575 | int coreIndex = core.coreIndex(); |
576 | int valueTypeIndex = valueType ? valueType->coreIndex() : -1; |
577 | for (bool isAlias = core.isAlias(); isAlias; ) { |
578 | QQmlVMEMetaObject *vme = QQmlVMEMetaObject::getForProperty(object, coreIndex); |
579 | |
580 | int aValueTypeIndex; |
581 | if (!vme->aliasTarget(coreIndex, &object, &coreIndex, &aValueTypeIndex)) { |
582 | // can't resolve id (yet) |
583 | m_target = nullptr; |
584 | m_targetIndex = QQmlPropertyIndex(); |
585 | return false; |
586 | } |
587 | if (valueTypeIndex == -1) |
588 | valueTypeIndex = aValueTypeIndex; |
589 | |
590 | QQmlData *data = QQmlData::get(object, false); |
591 | if (!data || !data->propertyCache) { |
592 | m_target = nullptr; |
593 | m_targetIndex = QQmlPropertyIndex(); |
594 | return false; |
595 | } |
596 | QQmlPropertyData *propertyData = data->propertyCache->property(coreIndex); |
597 | Q_ASSERT(propertyData); |
598 | |
599 | m_target = object; |
600 | isAlias = propertyData->isAlias(); |
601 | coreIndex = propertyData->coreIndex(); |
602 | } |
603 | m_targetIndex = QQmlPropertyIndex(coreIndex, valueTypeIndex); |
604 | |
605 | QQmlData *data = QQmlData::get(*m_target, true); |
606 | if (!data->propertyCache) { |
607 | data->propertyCache = QQmlEnginePrivate::get(context()->engine)->cache(m_target->metaObject()); |
608 | data->propertyCache->addref(); |
609 | } |
610 | |
611 | return true; |
612 | } |
613 | |
614 | void QQmlBinding::getPropertyData(QQmlPropertyData **propertyData, QQmlPropertyData *valueTypeData) const |
615 | { |
616 | Q_ASSERT(propertyData); |
617 | |
618 | QQmlData *data = QQmlData::get(*m_target, false); |
619 | Q_ASSERT(data); |
620 | |
621 | if (Q_UNLIKELY(!data->propertyCache)) { |
622 | data->propertyCache = QQmlEnginePrivate::get(context()->engine)->cache(m_target->metaObject()); |
623 | data->propertyCache->addref(); |
624 | } |
625 | |
626 | *propertyData = data->propertyCache->property(m_targetIndex.coreIndex()); |
627 | Q_ASSERT(*propertyData); |
628 | |
629 | if (Q_UNLIKELY(m_targetIndex.hasValueTypeIndex() && valueTypeData)) { |
630 | const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType((*propertyData)->propType()); |
631 | Q_ASSERT(valueTypeMetaObject); |
632 | QMetaProperty vtProp = valueTypeMetaObject->property(m_targetIndex.valueTypeIndex()); |
633 | valueTypeData->setFlags(QQmlPropertyData::flagsForProperty(vtProp)); |
634 | valueTypeData->setPropType(vtProp.userType()); |
635 | valueTypeData->setCoreIndex(m_targetIndex.valueTypeIndex()); |
636 | } |
637 | } |
638 | |
639 | QVector<QQmlProperty> QQmlBinding::dependencies() const |
640 | { |
641 | QVector<QQmlProperty> dependencies; |
642 | if (!m_target.data()) |
643 | return dependencies; |
644 | |
645 | for (QQmlJavaScriptExpressionGuard *guard = activeGuards.first(); guard; guard = activeGuards.next(guard)) { |
646 | if (guard->signalIndex() == -1) // guard's sender is a QQmlNotifier, not a QObject*. |
647 | continue; |
648 | |
649 | QObject *senderObject = guard->senderAsObject(); |
650 | if (!senderObject) |
651 | continue; |
652 | |
653 | const QMetaObject *senderMeta = senderObject->metaObject(); |
654 | if (!senderMeta) |
655 | continue; |
656 | |
657 | for (int i = 0; i < senderMeta->propertyCount(); i++) { |
658 | QMetaProperty property = senderMeta->property(i); |
659 | if (property.notifySignalIndex() == QMetaObjectPrivate::signal(senderMeta, guard->signalIndex()).methodIndex()) { |
660 | dependencies.push_back(QQmlProperty(senderObject, QString::fromUtf8(senderObject->metaObject()->property(i).name()))); |
661 | } |
662 | } |
663 | } |
664 | |
665 | return dependencies; |
666 | } |
667 | |
668 | bool QQmlBinding::hasDependencies() const |
669 | { |
670 | return !activeGuards.isEmpty() || translationsCaptured(); |
671 | } |
672 | |
673 | class QObjectPointerBinding: public QQmlNonbindingBinding |
674 | { |
675 | QQmlMetaObject targetMetaObject; |
676 | |
677 | public: |
678 | QObjectPointerBinding(QQmlEnginePrivate *engine, int propertyType) |
679 | : targetMetaObject(QQmlPropertyPrivate::rawMetaObjectForType(engine, propertyType)) |
680 | {} |
681 | |
682 | protected: |
683 | Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, |
684 | QQmlPropertyData::WriteFlags flags) override final |
685 | { |
686 | QQmlPropertyData *pd; |
687 | QQmlPropertyData vtpd; |
688 | getPropertyData(&pd, &vtpd); |
689 | if (Q_UNLIKELY(isUndefined || vtpd.isValid())) |
690 | return slowWrite(*pd, vtpd, result, isUndefined, flags); |
691 | |
692 | // Check if the result is a QObject: |
693 | QObject *resultObject = nullptr; |
694 | QQmlMetaObject resultMo; |
695 | if (result.isNull()) { |
696 | // Special case: we can always write a nullptr. Don't bother checking anything else. |
697 | return pd->writeProperty(targetObject(), &resultObject, flags); |
698 | } else if (auto wrapper = result.as<QV4::QObjectWrapper>()) { |
699 | resultObject = wrapper->object(); |
700 | if (!resultObject) |
701 | return pd->writeProperty(targetObject(), &resultObject, flags); |
702 | if (QQmlData *ddata = QQmlData::get(resultObject, false)) |
703 | resultMo = ddata->propertyCache; |
704 | if (resultMo.isNull()) { |
705 | resultMo = resultObject->metaObject(); |
706 | } |
707 | } else if (auto variant = result.as<QV4::VariantObject>()) { |
708 | QVariant value = variant->d()->data(); |
709 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context()); |
710 | resultMo = QQmlPropertyPrivate::rawMetaObjectForType(ep, value.userType()); |
711 | if (resultMo.isNull()) |
712 | return slowWrite(*pd, vtpd, result, isUndefined, flags); |
713 | resultObject = *static_cast<QObject *const *>(value.constData()); |
714 | } else { |
715 | return slowWrite(*pd, vtpd, result, isUndefined, flags); |
716 | } |
717 | |
718 | // Compare & set: |
719 | if (QQmlMetaObject::canConvert(resultMo, targetMetaObject)) { |
720 | return pd->writeProperty(targetObject(), &resultObject, flags); |
721 | } else if (!resultObject && QQmlMetaObject::canConvert(targetMetaObject, resultMo)) { |
722 | // In the case of a null QObject, we assign the null if there is |
723 | // any change that the null variant type could be up or down cast to |
724 | // the property type. |
725 | return pd->writeProperty(targetObject(), &resultObject, flags); |
726 | } else { |
727 | return slowWrite(*pd, vtpd, result, isUndefined, flags); |
728 | } |
729 | } |
730 | }; |
731 | |
732 | QQmlBinding *QQmlBinding::newBinding(QQmlEnginePrivate *engine, const QQmlPropertyData *property) |
733 | { |
734 | if (property && property->isQObject()) |
735 | return new QObjectPointerBinding(engine, property->propType()); |
736 | |
737 | const int type = (property && property->isFullyResolved()) ? property->propType() : QMetaType::UnknownType; |
738 | |
739 | if (type == qMetaTypeId<QQmlBinding *>()) { |
740 | return new QQmlBindingBinding; |
741 | } |
742 | |
743 | switch (type) { |
744 | case QMetaType::Bool: |
745 | return new GenericBinding<QMetaType::Bool>; |
746 | case QMetaType::Int: |
747 | return new GenericBinding<QMetaType::Int>; |
748 | case QMetaType::Double: |
749 | return new GenericBinding<QMetaType::Double>; |
750 | case QMetaType::Float: |
751 | return new GenericBinding<QMetaType::Float>; |
752 | case QMetaType::QString: |
753 | return new GenericBinding<QMetaType::QString>; |
754 | default: |
755 | return new GenericBinding<QMetaType::UnknownType>; |
756 | } |
757 | } |
758 | |
759 | QT_END_NAMESPACE |
760 | |