Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlcontext.cpp
Go to the documentation of this file.
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
6
9#include "qqmlengine_p.h"
10#include "qqmlengine.h"
11#include "qqmlinfo.h"
13
14#include <qjsengine.h>
15#include <QtCore/qvarlengtharray.h>
16#include <private/qmetaobject_p.h>
17#include <QtCore/qdebug.h>
18
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(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(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("QQmlContext: Cannot set context object for internal context.");
195 return;
196 }
197
198 if (!data->isValid()) {
199 qWarning("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(&QQmlContext::staticMetaObject));
218
219 QQmlRefPointer<QQmlContextData> data = d->m_data;
220
221 if (data->isInternal()) {
222 qWarning("QQmlContext: Cannot set property on internal context.");
223 return;
224 }
225
226 if (!data->isValid()) {
227 qWarning("QQmlContext: Cannot set property on invalid context.");
228 return;
229 }
230
231 if (bool isNumber = false; name.toUInt(&isNumber), isNumber) {
232 qWarning("QQmlContext: Using numbers as context properties will be disallowed in a future Qt version.");
233 QT7_ONLY(return;)
234 }
235
236 int idx = data->propertyIndex(name);
237 if (idx == -1) {
238 data->addPropertyNameAndIndex(name, data->numIdValues() + d->numPropertyValues());
239 d->appendPropertyValue(value);
240 data->refreshExpressions();
241 } else {
242 d->setPropertyValue(idx, value);
243 QMetaObject::activate(this, d->notifyIndex(), idx, nullptr);
244 }
245
246 if (auto *obj = qvariant_cast<QObject *>(value)) {
247 connect(obj, &QObject::destroyed, this, [d, name](QObject *destroyed) {
248 d->dropDestroyedQObject(name, destroyed);
249 });
250 }
251}
252
253/*!
254 Set the \a value of the \a name property on this context.
255
256 QQmlContext does \b not take ownership of \a value.
257
258 \note You should not use context properties to inject values into your QML
259 components. Use singletons or regular object properties instead.
260*/
261void QQmlContext::setContextProperty(const QString &name, QObject *value)
262{
263 setContextProperty(name, QVariant::fromValue(value));
264}
265
266/*!
267 \since 5.11
268
269 Set a batch of \a properties on this context.
270
271 Setting all properties in one batch avoids unnecessary
272 refreshing expressions, and is therefore recommended
273 instead of calling \l setContextProperty() for each individual property.
274
275 \note You should not use context properties to inject values into your QML
276 components. Use singletons or regular object properties instead.
277
278 \sa QQmlContext::setContextProperty()
279*/
280void QQmlContext::setContextProperties(const QList<PropertyPair> &properties)
281{
282 Q_D(const QQmlContext);
283
284 QQmlRefPointer<QQmlContextData> data = d->m_data;
285 QQmlJavaScriptExpression *expressions = data->takeExpressions();
286 QQmlRefPointer<QQmlContextData> childContexts = data->takeChildContexts();
287
288 for (const auto &property : properties)
289 setContextProperty(property.name, property.value);
290
291 data->setExpressions(expressions);
292 data->setChildContexts(childContexts);
293 data->refreshExpressions();
294}
295
296/*!
297 \since 5.11
298
299 \class QQmlContext::PropertyPair
300 \inmodule QtQml
301
302 This struct contains a property name and a property value.
303 It is used as a parameter for the \c setContextProperties function.
304
305 \sa QQmlContext::setContextProperties()
306*/
307
309 const QQmlRefPointer<QQmlContextData> &data, QObject *object, const QString &name,
310 QVariant *target)
311{
312 QQmlPropertyData local;
313 if (const QQmlPropertyData *property = QQmlPropertyCache::property(object, name, data, &local)) {
314 *target = object->metaObject()->property(property->coreIndex()).read(object);
315 return true;
316 }
317 return false;
318}
319
320/*!
321 Returns the value of the \a name property for this context as a QVariant.
322 If you know that the property you're looking for is a QObject assigned using
323 a QML id in the current context, \l objectForName() is more convenient and
324 faster. In contrast to \l objectForName() and \l nameForObject(), this method
325 does traverse the context hierarchy and searches in parent contexts if the
326 \a name is not found in the current one. It also considers any
327 \l contextObject() you may have set.
328
329 \sa objectForName(), nameForObject(), contextObject()
330 */
331QVariant QQmlContext::contextProperty(const QString &name) const
332{
333 Q_D(const QQmlContext);
334
335 const QQmlRefPointer<QQmlContextData> data = d->m_data;
336
337 const int idx = data->propertyIndex(name);
338 if (idx == -1) {
339 if (QObject *obj = data->contextObject()) {
340 QVariant value;
341 if (readObjectProperty(data, obj, name, &value))
342 return value;
343 }
344
345 if (parentContext())
346 return parentContext()->contextProperty(name);
347 } else {
348 if (idx >= d->numPropertyValues())
349 return QVariant::fromValue(data->idValue(idx - d->numPropertyValues()));
350 else
351 return d->propertyValue(idx);
352 }
353
354 return QVariant();
355}
356
357/*!
358 Returns the name of \a object in this context, or an empty string if \a object
359 is not named in the context. Objects are named by \l setContextProperty(), or
360 as properties of a context object, or by ids in the case of QML created
361 contexts.
362
363 If the object has multiple names, the first is returned.
364
365 In contrast to \l contextProperty(), this method does not traverse the
366 context hierarchy. If the name is not found in the current context, an empty
367 String is returned.
368
369 \sa contextProperty(), objectForName()
370*/
371QString QQmlContext::nameForObject(const QObject *object) const
372{
373 Q_D(const QQmlContext);
374
375 return d->m_data->findObjectId(object);
376}
377
378/*!
379 \since 6.2
380
381 Returns the object for a given \a name in this context. Returns nullptr if
382 \a name is not available in the context or if the value associated with
383 \a name is not a QObject. Objects are named by \l setContextProperty(),
384 or as properties of a context object, or by ids in the case of QML created
385 contexts. In contrast to \l contextProperty(), this method does not traverse
386 the context hierarchy. If the name is not found in the current context,
387 nullptr is returned.
388
389 \sa contextProperty(), nameForObject()
390*/
391QObject *QQmlContext::objectForName(const QString &name) const
392{
393 Q_D(const QQmlContext);
394
395 QQmlRefPointer<QQmlContextData> data = d->m_data;
396 if (const int propertyIndex = data->propertyIndex(name); propertyIndex >= 0) {
397 const int numPropertyValues = d->numPropertyValues();
398 if (propertyIndex < numPropertyValues)
399 return qvariant_cast<QObject *>(d->propertyValue(propertyIndex));
400 return data->idValue(propertyIndex - numPropertyValues);
401 }
402
403 if (QObject *obj = data->contextObject()) {
404 QVariant result;
405 if (readObjectProperty(data, obj, name, &result))
406 return qvariant_cast<QObject *>(result);
407 }
408
409 return nullptr;
410}
411
412/*!
413 Resolves the URL \a src relative to the URL of the
414 containing component.
415
416 \sa QQmlEngine::baseUrl(), setBaseUrl()
417*/
418QUrl QQmlContext::resolvedUrl(const QUrl &src) const
419{
420 Q_D(const QQmlContext);
421 return d->m_data->resolvedUrl(src);
422}
423
424/*!
425 Explicitly sets the url resolvedUrl() will use for relative references to \a baseUrl.
426
427 Calling this function will override the url of the containing
428 component used by default.
429
430 \sa resolvedUrl()
431*/
432void QQmlContext::setBaseUrl(const QUrl &baseUrl)
433{
434 Q_D(QQmlContext);
435 d->m_data->setBaseUrl(baseUrl);
436 d->m_data->setBaseUrlString(baseUrl.toString());
437}
438
439/*!
440 Returns the base url of the component, or the containing component
441 if none is set.
442*/
443QUrl QQmlContext::baseUrl() const
444{
445 Q_D(const QQmlContext);
446 return d->m_data->baseUrl();
447}
448
449/*!
450 * \internal
451 */
452QJSValue QQmlContext::importedScript(const QString &name) const
453{
454 Q_D(const QQmlContext);
455
456 QV4::ExecutionEngine *v4 = engine()->handle();
457 QQmlTypeNameCache::Result r = d->m_data->imports()->query(name, v4->typeLoader());
458 QV4::Scope scope(v4);
459 QV4::ScopedObject scripts(scope, d->m_data->importedScripts());
460 return scripts ? QJSValuePrivate::fromReturnedValue(scripts->get(r.scriptIndex))
461 : QJSValue(QJSValue::UndefinedValue);
462}
463
464qsizetype QQmlContextPrivate::context_count(QQmlListProperty<QObject> *prop)
465{
466 QQmlContext *context = static_cast<QQmlContext*>(prop->object);
467 QQmlContextPrivate *d = QQmlContextPrivate::get(context);
468 int contextProperty = (int)(quintptr)prop->data;
469
470 if (d->propertyValue(contextProperty).userType() != qMetaTypeId<QList<QObject*> >())
471 return 0;
472 else
473 return ((const QList<QObject> *)d->propertyValue(contextProperty).constData())->size();
474}
475
476QObject *QQmlContextPrivate::context_at(QQmlListProperty<QObject> *prop, qsizetype index)
477{
478 QQmlContext *context = static_cast<QQmlContext*>(prop->object);
479 QQmlContextPrivate *d = QQmlContextPrivate::get(context);
480 int contextProperty = (int)(quintptr)prop->data;
481
482 if (d->propertyValue(contextProperty).userType() != qMetaTypeId<QList<QObject*> >())
483 return nullptr;
484 else
485 return ((const QList<QObject*> *)d->propertyValue(contextProperty).constData())->at(index);
486}
487
488void QQmlContextPrivate::dropDestroyedQObject(const QString &name, QObject *destroyed)
489{
490 if (!m_data->isValid())
491 return;
492
493 const int idx = m_data->propertyIndex(name);
494 Q_ASSERT(idx >= 0);
495 if (qvariant_cast<QObject *>(propertyValue(idx)) != destroyed)
496 return;
497
498 setPropertyValue(idx, QVariant::fromValue<QObject *>(nullptr));
499 QMetaObject::activate(q_func(), notifyIndex(), idx, nullptr);
500}
501
503{
504 m_data->emitDestruction();
505}
506
507// m_data is owned by the public context. When the public context is reset to nullptr, it will be
508// deref'd. It's OK to pass a half-created publicContext here. We will not dereference it during
509// construction.
510QQmlContextPrivate::QQmlContextPrivate(
511 QQmlContext *publicContext, const QQmlRefPointer<QQmlContextData> &parent,
512 QQmlEngine *engine) :
513 m_data(new QQmlContextData(QQmlContextData::OwnedByPublicContext, publicContext,
514 parent, engine))
515{
516 Q_ASSERT(publicContext != nullptr);
517}
518
519QT_END_NAMESPACE
520
521#include "moc_qqmlcontext.cpp"
static bool readObjectProperty(const QQmlRefPointer< QQmlContextData > &data, QObject *object, const QString &name, QVariant *target)