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
19QT_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 */
89QQmlContext::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*/
98QQmlContext::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*/
109QQmlContext::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*/
119QQmlContext::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 */
131QQmlContext::~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*/
143bool 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*/
153QQmlEngine *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*/
163QQmlContext *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*/
175QObject *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*/
187void 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*/
213void 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*/
256void 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*/
275void 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
303static 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 */
326QVariant 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*/
366QString 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*/
386QObject *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*/
413QUrl 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*/
427void 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*/
438QUrl QQmlContext::baseUrl() const
439{
440 Q_D(const QQmlContext);
441 return d->m_data->baseUrl();
442}
443
444/*!
445 * \internal
446 */
447QJSValue 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
458qsizetype 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
470QObject *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
482void 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
496void 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.
504QQmlContextPrivate::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
513QT_END_NAMESPACE
514
515#include "moc_qqmlcontext.cpp"
516

source code of qtdeclarative/src/qml/qml/qqmlcontext.cpp