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