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