1 | // Copyright (C) 2016 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 | #include "qqmlcontext_p.h" |
5 | #include "qqmlcomponentattached_p.h" |
6 | |
7 | #include "qqmlcomponent_p.h" |
8 | #include "qqmlexpression_p.h" |
9 | #include "qqmlengine_p.h" |
10 | #include "qqmlengine.h" |
11 | #include "qqmlinfo.h" |
12 | #include "qqmlabstracturlinterceptor.h" |
13 | |
14 | #include <qjsengine.h> |
15 | #include <QtCore/qvarlengtharray.h> |
16 | #include <private/qmetaobject_p.h> |
17 | #include <QtCore/qdebug.h> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | /*! |
22 | \class QQmlContext |
23 | \brief The QQmlContext class defines a context within a QML engine. |
24 | \inmodule QtQml |
25 | |
26 | Contexts hold the objects identified by \e id in a QML document. You |
27 | can use \l{nameForObject()} and \l{objectForName()} to retrieve them. |
28 | |
29 | \note It is the responsibility of the creator to delete any QQmlContext it |
30 | constructs. If a QQmlContext is no longer needed, it must be destroyed |
31 | explicitly. The simplest way to ensure this is to give the QQmlContext a |
32 | \l{QObject::setParent()}{parent}. |
33 | |
34 | \section2 The Context Hierarchy |
35 | |
36 | Contexts form a hierarchy. The root of this hierarchy is the QML engine's |
37 | \l {QQmlEngine::rootContext()}{root context}. Each QML component creates its |
38 | own context when instantiated and some QML elements create extra contexts |
39 | for themselves. |
40 | |
41 | While QML objects instantiated in a context are not strictly owned by that |
42 | context, their bindings are. If a context is destroyed, the property bindings of |
43 | outstanding QML objects will stop evaluating. |
44 | |
45 | \section2 Context Properties |
46 | |
47 | Contexts also allow data to be exposed to the QML components instantiated |
48 | by the QML engine. Such data is invisible to any tooling, including the |
49 | \l{Qt Quick Compiler} and to future readers of the QML documents in |
50 | question. It will only be exposed if the QML component is instantiated in |
51 | the specific C++ context you are envisioning. In other places, different |
52 | context data may be exposed instead. |
53 | |
54 | Instead of using the QML context to expose data to your QML components, you |
55 | should either create additional object properties to hold the data or use |
56 | \l{QML_SINGLETON}{singletons}. See |
57 | \l{qtqml-cppintegration-exposecppstate.html}{Exposing C++ State to QML} for |
58 | a detailed explanation. |
59 | |
60 | Each QQmlContext contains a set of properties, distinct from its QObject |
61 | properties, that allow data to be explicitly bound to a context by name. The |
62 | context properties can be defined and updated by calling |
63 | QQmlContext::setContextProperty(). |
64 | |
65 | To simplify binding and maintaining larger data sets, a context object can be set |
66 | on a QQmlContext. All the properties of the context object are available |
67 | by name in the context, as though they were all individually added through calls |
68 | to QQmlContext::setContextProperty(). Changes to the property's values are |
69 | detected through the property's notify signal. Setting a context object is both |
70 | faster and easier than manually adding and maintaining context property values. |
71 | |
72 | All properties added explicitly by QQmlContext::setContextProperty() take |
73 | precedence over the context object's properties. |
74 | |
75 | Child contexts inherit the context properties of their parents; if a child |
76 | context sets a context property that already exists in its parent, the new |
77 | context property overrides that of the parent. |
78 | |
79 | \warning Setting the context object or adding new context properties after |
80 | an object has been created in that context is an expensive operation |
81 | (essentially forcing all bindings to re-evaluate). Thus, if you need to use |
82 | context properties, you should at least complete the "setup" of the context |
83 | before using it to create any objects. |
84 | |
85 | \sa {qtqml-cppintegration-exposecppattributes.html}{Exposing Attributes of C++ Types to QML} |
86 | */ |
87 | |
88 | /*! \internal */ |
89 | QQmlContext::QQmlContext(QQmlEngine *e, bool) |
90 | : QObject(*(new QQmlContextPrivate(this, QQmlRefPointer<QQmlContextData>(), e))) |
91 | { |
92 | } |
93 | |
94 | /*! |
95 | Create a new QQmlContext as a child of \a engine's root context, and the |
96 | QObject \a parent. |
97 | */ |
98 | QQmlContext::QQmlContext(QQmlEngine *engine, QObject *parent) |
99 | : QObject(*(new QQmlContextPrivate(this, engine |
100 | ? QQmlContextData::get(context: engine->rootContext()) |
101 | : QQmlRefPointer<QQmlContextData>())), parent) |
102 | { |
103 | } |
104 | |
105 | /*! |
106 | Create a new QQmlContext with the given \a parentContext, and the |
107 | QObject \a parent. |
108 | */ |
109 | QQmlContext::QQmlContext(QQmlContext *parentContext, QObject *parent) |
110 | : QObject(*(new QQmlContextPrivate(this, parentContext |
111 | ? QQmlContextData::get(context: parentContext) |
112 | : QQmlRefPointer<QQmlContextData>())), parent) |
113 | { |
114 | } |
115 | |
116 | /*! |
117 | \internal |
118 | */ |
119 | QQmlContext::QQmlContext(QQmlContextPrivate &dd, QObject *parent) |
120 | : QObject(dd, parent) |
121 | { |
122 | } |
123 | |
124 | /*! |
125 | Destroys the QQmlContext. |
126 | |
127 | Any expressions, or sub-contexts dependent on this context will be |
128 | invalidated, but not destroyed (unless they are parented to the QQmlContext |
129 | object). |
130 | */ |
131 | QQmlContext::~QQmlContext() |
132 | { |
133 | Q_D(QQmlContext); |
134 | d->m_data->clearPublicContext(); |
135 | } |
136 | |
137 | /*! |
138 | Returns whether the context is valid. |
139 | |
140 | To be valid, a context must have a engine, and it's contextObject(), if any, |
141 | must not have been deleted. |
142 | */ |
143 | bool QQmlContext::isValid() const |
144 | { |
145 | Q_D(const QQmlContext); |
146 | return d->m_data->isValid(); |
147 | } |
148 | |
149 | /*! |
150 | Return the context's QQmlEngine, or \nullptr if the context has no QQmlEngine or the |
151 | QQmlEngine was destroyed. |
152 | */ |
153 | QQmlEngine *QQmlContext::engine() const |
154 | { |
155 | Q_D(const QQmlContext); |
156 | return d->m_data->engine(); |
157 | } |
158 | |
159 | /*! |
160 | Return the context's parent QQmlContext, or \nullptr if this context has no |
161 | parent or if the parent has been destroyed. |
162 | */ |
163 | QQmlContext *QQmlContext::parentContext() const |
164 | { |
165 | Q_D(const QQmlContext); |
166 | |
167 | if (QQmlRefPointer<QQmlContextData> parent = d->m_data->parent()) |
168 | return parent->asQQmlContext(); |
169 | return nullptr; |
170 | } |
171 | |
172 | /*! |
173 | Return the context object, or \nullptr if there is no context object. |
174 | */ |
175 | QObject *QQmlContext::contextObject() const |
176 | { |
177 | Q_D(const QQmlContext); |
178 | return d->m_data->contextObject(); |
179 | } |
180 | |
181 | /*! |
182 | Set the context \a object. |
183 | |
184 | \note You should not use context objects to inject values into your QML |
185 | components. Use singletons or regular object properties instead. |
186 | */ |
187 | void QQmlContext::setContextObject(QObject *object) |
188 | { |
189 | Q_D(QQmlContext); |
190 | |
191 | QQmlRefPointer<QQmlContextData> data = d->m_data; |
192 | |
193 | if (data->isInternal()) { |
194 | qWarning(msg: "QQmlContext: Cannot set context object for internal context." ); |
195 | return; |
196 | } |
197 | |
198 | if (!data->isValid()) { |
199 | qWarning(msg: "QQmlContext: Cannot set context object on invalid context." ); |
200 | return; |
201 | } |
202 | |
203 | data->setContextObject(object); |
204 | data->refreshExpressions(); |
205 | } |
206 | |
207 | /*! |
208 | Set a the \a value of the \a name property on this context. |
209 | |
210 | \note You should not use context properties to inject values into your QML |
211 | components. Use singletons or regular object properties instead. |
212 | */ |
213 | void QQmlContext::setContextProperty(const QString &name, const QVariant &value) |
214 | { |
215 | Q_D(QQmlContext); |
216 | if (d->notifyIndex() == -1) |
217 | d->setNotifyIndex(QMetaObjectPrivate::absoluteSignalCount(m: &QQmlContext::staticMetaObject)); |
218 | |
219 | QQmlRefPointer<QQmlContextData> data = d->m_data; |
220 | |
221 | if (data->isInternal()) { |
222 | qWarning(msg: "QQmlContext: Cannot set property on internal context." ); |
223 | return; |
224 | } |
225 | |
226 | if (!data->isValid()) { |
227 | qWarning(msg: "QQmlContext: Cannot set property on invalid context." ); |
228 | return; |
229 | } |
230 | |
231 | int idx = data->propertyIndex(name); |
232 | if (idx == -1) { |
233 | data->addPropertyNameAndIndex(name, index: data->numIdValues() + d->numPropertyValues()); |
234 | d->appendPropertyValue(value); |
235 | data->refreshExpressions(); |
236 | } else { |
237 | d->setPropertyValue(index: idx, value); |
238 | QMetaObject::activate(sender: this, signal_offset: d->notifyIndex(), local_signal_index: idx, argv: nullptr); |
239 | } |
240 | |
241 | if (auto *obj = qvariant_cast<QObject *>(v: value)) { |
242 | connect(sender: obj, signal: &QObject::destroyed, context: this, slot: [d, name](QObject *destroyed) { |
243 | d->dropDestroyedQObject(name, destroyed); |
244 | }); |
245 | } |
246 | } |
247 | |
248 | /*! |
249 | Set the \a value of the \a name property on this context. |
250 | |
251 | QQmlContext does \b not take ownership of \a value. |
252 | |
253 | \note You should not use context properties to inject values into your QML |
254 | components. Use singletons or regular object properties instead. |
255 | */ |
256 | void QQmlContext::setContextProperty(const QString &name, QObject *value) |
257 | { |
258 | setContextProperty(name, value: QVariant::fromValue(value)); |
259 | } |
260 | |
261 | /*! |
262 | \since 5.11 |
263 | |
264 | Set a batch of \a properties on this context. |
265 | |
266 | Setting all properties in one batch avoids unnecessary |
267 | refreshing expressions, and is therefore recommended |
268 | instead of calling \l setContextProperty() for each individual property. |
269 | |
270 | \note You should not use context properties to inject values into your QML |
271 | components. Use singletons or regular object properties instead. |
272 | |
273 | \sa QQmlContext::setContextProperty() |
274 | */ |
275 | void QQmlContext::setContextProperties(const QList<PropertyPair> &properties) |
276 | { |
277 | Q_D(const QQmlContext); |
278 | |
279 | QQmlRefPointer<QQmlContextData> data = d->m_data; |
280 | QQmlJavaScriptExpression *expressions = data->takeExpressions(); |
281 | QQmlRefPointer<QQmlContextData> childContexts = data->takeChildContexts(); |
282 | |
283 | for (const auto &property : properties) |
284 | setContextProperty(name: property.name, value: property.value); |
285 | |
286 | data->setExpressions(expressions); |
287 | data->setChildContexts(childContexts); |
288 | data->refreshExpressions(); |
289 | } |
290 | |
291 | /*! |
292 | \since 5.11 |
293 | |
294 | \class QQmlContext::PropertyPair |
295 | \inmodule QtQml |
296 | |
297 | This struct contains a property name and a property value. |
298 | It is used as a parameter for the \c setContextProperties function. |
299 | |
300 | \sa QQmlContext::setContextProperties() |
301 | */ |
302 | |
303 | static bool readObjectProperty( |
304 | const QQmlRefPointer<QQmlContextData> &data, QObject *object, const QString &name, |
305 | QVariant *target) |
306 | { |
307 | QQmlPropertyData local; |
308 | if (const QQmlPropertyData *property = QQmlPropertyCache::property(object, name, data, &local)) { |
309 | *target = object->metaObject()->property(index: property->coreIndex()).read(obj: object); |
310 | return true; |
311 | } |
312 | return false; |
313 | } |
314 | |
315 | /*! |
316 | Returns the value of the \a name property for this context as a QVariant. |
317 | If you know that the property you're looking for is a QObject assigned using |
318 | a QML id in the current context, \l objectForName() is more convenient and |
319 | faster. In contrast to \l objectForName() and \l nameForObject(), this method |
320 | does traverse the context hierarchy and searches in parent contexts if the |
321 | \a name is not found in the current one. It also considers any |
322 | \l contextObject() you may have set. |
323 | |
324 | \sa objectForName(), nameForObject(), contextObject() |
325 | */ |
326 | QVariant QQmlContext::contextProperty(const QString &name) const |
327 | { |
328 | Q_D(const QQmlContext); |
329 | |
330 | const QQmlRefPointer<QQmlContextData> data = d->m_data; |
331 | |
332 | const int idx = data->propertyIndex(name); |
333 | if (idx == -1) { |
334 | if (QObject *obj = data->contextObject()) { |
335 | QVariant value; |
336 | if (readObjectProperty(data, object: obj, name, target: &value)) |
337 | return value; |
338 | } |
339 | |
340 | if (parentContext()) |
341 | return parentContext()->contextProperty(name); |
342 | } else { |
343 | if (idx >= d->numPropertyValues()) |
344 | return QVariant::fromValue(value: data->idValue(index: idx - d->numPropertyValues())); |
345 | else |
346 | return d->propertyValue(index: idx); |
347 | } |
348 | |
349 | return QVariant(); |
350 | } |
351 | |
352 | /*! |
353 | Returns the name of \a object in this context, or an empty string if \a object |
354 | is not named in the context. Objects are named by \l setContextProperty(), or |
355 | as properties of a context object, or by ids in the case of QML created |
356 | contexts. |
357 | |
358 | If the object has multiple names, the first is returned. |
359 | |
360 | In contrast to \l contextProperty(), this method does not traverse the |
361 | context hierarchy. If the name is not found in the current context, an empty |
362 | String is returned. |
363 | |
364 | \sa contextProperty(), objectForName() |
365 | */ |
366 | QString QQmlContext::nameForObject(const QObject *object) const |
367 | { |
368 | Q_D(const QQmlContext); |
369 | |
370 | return d->m_data->findObjectId(obj: object); |
371 | } |
372 | |
373 | /*! |
374 | \since 6.2 |
375 | |
376 | Returns the object for a given \a name in this context. Returns nullptr if |
377 | \a name is not available in the context or if the value associated with |
378 | \a name is not a QObject. Objects are named by \l setContextProperty(), |
379 | or as properties of a context object, or by ids in the case of QML created |
380 | contexts. In contrast to \l contextProperty(), this method does not traverse |
381 | the context hierarchy. If the name is not found in the current context, |
382 | nullptr is returned. |
383 | |
384 | \sa contextProperty(), nameForObject() |
385 | */ |
386 | QObject *QQmlContext::objectForName(const QString &name) const |
387 | { |
388 | Q_D(const QQmlContext); |
389 | |
390 | QQmlRefPointer<QQmlContextData> data = d->m_data; |
391 | if (const int propertyIndex = data->propertyIndex(name); propertyIndex >= 0) { |
392 | const int numPropertyValues = d->numPropertyValues(); |
393 | if (propertyIndex < numPropertyValues) |
394 | return qvariant_cast<QObject *>(v: d->propertyValue(index: propertyIndex)); |
395 | return data->idValue(index: propertyIndex - numPropertyValues); |
396 | } |
397 | |
398 | if (QObject *obj = data->contextObject()) { |
399 | QVariant result; |
400 | if (readObjectProperty(data, object: obj, name, target: &result)) |
401 | return qvariant_cast<QObject *>(v: result); |
402 | } |
403 | |
404 | return nullptr; |
405 | } |
406 | |
407 | /*! |
408 | Resolves the URL \a src relative to the URL of the |
409 | containing component. |
410 | |
411 | \sa QQmlEngine::baseUrl(), setBaseUrl() |
412 | */ |
413 | QUrl QQmlContext::resolvedUrl(const QUrl &src) const |
414 | { |
415 | Q_D(const QQmlContext); |
416 | return d->m_data->resolvedUrl(src); |
417 | } |
418 | |
419 | /*! |
420 | Explicitly sets the url resolvedUrl() will use for relative references to \a baseUrl. |
421 | |
422 | Calling this function will override the url of the containing |
423 | component used by default. |
424 | |
425 | \sa resolvedUrl() |
426 | */ |
427 | void QQmlContext::setBaseUrl(const QUrl &baseUrl) |
428 | { |
429 | Q_D(QQmlContext); |
430 | d->m_data->setBaseUrl(baseUrl); |
431 | d->m_data->setBaseUrlString(baseUrl.toString()); |
432 | } |
433 | |
434 | /*! |
435 | Returns the base url of the component, or the containing component |
436 | if none is set. |
437 | */ |
438 | QUrl QQmlContext::baseUrl() const |
439 | { |
440 | Q_D(const QQmlContext); |
441 | return d->m_data->baseUrl(); |
442 | } |
443 | |
444 | /*! |
445 | * \internal |
446 | */ |
447 | QJSValue QQmlContext::importedScript(const QString &name) const |
448 | { |
449 | Q_D(const QQmlContext); |
450 | |
451 | QQmlTypeNameCache::Result r = d->m_data->imports()->query(key: name); |
452 | QV4::Scope scope(engine()->handle()); |
453 | QV4::ScopedObject scripts(scope, d->m_data->importedScripts().valueRef()); |
454 | return scripts ? QJSValuePrivate::fromReturnedValue(d: scripts->get(idx: r.scriptIndex)) |
455 | : QJSValue(QJSValue::UndefinedValue); |
456 | } |
457 | |
458 | qsizetype QQmlContextPrivate::context_count(QQmlListProperty<QObject> *prop) |
459 | { |
460 | QQmlContext *context = static_cast<QQmlContext*>(prop->object); |
461 | QQmlContextPrivate *d = QQmlContextPrivate::get(context); |
462 | int contextProperty = (int)(quintptr)prop->data; |
463 | |
464 | if (d->propertyValue(index: contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) |
465 | return 0; |
466 | else |
467 | return ((const QList<QObject> *)d->propertyValue(index: contextProperty).constData())->size(); |
468 | } |
469 | |
470 | QObject *QQmlContextPrivate::context_at(QQmlListProperty<QObject> *prop, qsizetype index) |
471 | { |
472 | QQmlContext *context = static_cast<QQmlContext*>(prop->object); |
473 | QQmlContextPrivate *d = QQmlContextPrivate::get(context); |
474 | int contextProperty = (int)(quintptr)prop->data; |
475 | |
476 | if (d->propertyValue(index: contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) |
477 | return nullptr; |
478 | else |
479 | return ((const QList<QObject*> *)d->propertyValue(index: contextProperty).constData())->at(i: index); |
480 | } |
481 | |
482 | void QQmlContextPrivate::dropDestroyedQObject(const QString &name, QObject *destroyed) |
483 | { |
484 | if (!m_data->isValid()) |
485 | return; |
486 | |
487 | const int idx = m_data->propertyIndex(name); |
488 | Q_ASSERT(idx >= 0); |
489 | if (qvariant_cast<QObject *>(v: propertyValue(index: idx)) != destroyed) |
490 | return; |
491 | |
492 | setPropertyValue(index: idx, value: QVariant::fromValue<QObject *>(value: nullptr)); |
493 | QMetaObject::activate(sender: q_func(), signal_offset: notifyIndex(), local_signal_index: idx, argv: nullptr); |
494 | } |
495 | |
496 | void QQmlContextPrivate::emitDestruction() |
497 | { |
498 | m_data->emitDestruction(); |
499 | } |
500 | |
501 | // m_data is owned by the public context. When the public context is reset to nullptr, it will be |
502 | // deref'd. It's OK to pass a half-created publicContext here. We will not dereference it during |
503 | // construction. |
504 | QQmlContextPrivate::QQmlContextPrivate( |
505 | QQmlContext *publicContext, const QQmlRefPointer<QQmlContextData> &parent, |
506 | QQmlEngine *engine) : |
507 | m_data(new QQmlContextData(QQmlContextData::OwnedByPublicContext, publicContext, |
508 | parent, engine)) |
509 | { |
510 | Q_ASSERT(publicContext != nullptr); |
511 | } |
512 | |
513 | QT_END_NAMESPACE |
514 | |
515 | #include "moc_qqmlcontext.cpp" |
516 | |