diff options
Diffstat (limited to 'src/qml/qml/qqmlcontext.cpp')
-rw-r--r-- | src/qml/qml/qqmlcontext.cpp | 309 |
1 files changed, 159 insertions, 150 deletions
diff --git a/src/qml/qml/qqmlcontext.cpp b/src/qml/qml/qqmlcontext.cpp index 797ab34937..cf6736deb9 100644 --- a/src/qml/qml/qqmlcontext.cpp +++ b/src/qml/qml/qqmlcontext.cpp @@ -1,43 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qqmlcontext.h" +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + #include "qqmlcontext_p.h" #include "qqmlcomponentattached_p.h" @@ -60,30 +23,44 @@ QT_BEGIN_NAMESPACE \brief The QQmlContext class defines a context within a QML engine. \inmodule QtQml - Contexts allow data to be exposed to the QML components instantiated by the - QML engine. + Contexts hold the objects identified by \e id in a QML document. You + can use \l{nameForObject()} and \l{objectForName()} to retrieve them. + + \note It is the responsibility of the creator to delete any QQmlContext it + constructs. If a QQmlContext is no longer needed, it must be destroyed + explicitly. The simplest way to ensure this is to give the QQmlContext a + \l{QObject::setParent()}{parent}. + + \section2 The Context Hierarchy + + Contexts form a hierarchy. The root of this hierarchy is the QML engine's + \l {QQmlEngine::rootContext()}{root context}. Each QML component creates its + own context when instantiated and some QML elements create extra contexts + for themselves. + + While QML objects instantiated in a context are not strictly owned by that + context, their bindings are. If a context is destroyed, the property bindings of + outstanding QML objects will stop evaluating. + + \section2 Context Properties + + Contexts also allow data to be exposed to the QML components instantiated + by the QML engine. Such data is invisible to any tooling, including the + \l{Qt Quick Compiler} and to future readers of the QML documents in + question. It will only be exposed if the QML component is instantiated in + the specific C++ context you are envisioning. In other places, different + context data may be exposed instead. + + Instead of using the QML context to expose data to your QML components, you + should either create additional object properties to hold the data or use + \l{QML_SINGLETON}{singletons}. See + \l{qtqml-cppintegration-exposecppstate.html}{Exposing C++ State to QML} for + a detailed explanation. Each QQmlContext contains a set of properties, distinct from its QObject properties, that allow data to be explicitly bound to a context by name. The - context properties are defined and updated by calling - QQmlContext::setContextProperty(). The following example shows a Qt model - being bound to a context and then accessed from a QML file. - - \code - QQmlEngine engine; - QStringListModel modelData; - QQmlContext *context = new QQmlContext(engine.rootContext()); - context->setContextProperty("myModel", &modelData); - - QQmlComponent component(&engine); - component.setData("import QtQuick 2.0\nListView { model: myModel }", QUrl()); - QObject *window = component.create(context); - \endcode - - Note it is the responsibility of the creator to delete any QQmlContext it - constructs. If the \c context object in the example is no longer needed when the - \c window component instance is destroyed, the \c context must be destroyed explicitly. - The simplest way to ensure this is to set \c window as the parent of \c context. + context properties can be defined and updated by calling + QQmlContext::setContextProperty(). To simplify binding and maintaining larger data sets, a context object can be set on a QQmlContext. All the properties of the context object are available @@ -92,59 +69,17 @@ QT_BEGIN_NAMESPACE detected through the property's notify signal. Setting a context object is both faster and easier than manually adding and maintaining context property values. - The following example has the same effect as the previous one, but it uses a context - object. - - \code - class MyDataSet : ... { - ... - Q_PROPERTY(QAbstractItemModel *myModel READ model NOTIFY modelChanged) - ... - }; - - MyDataSet myDataSet; - QQmlEngine engine; - QQmlContext *context = new QQmlContext(engine.rootContext()); - context->setContextObject(&myDataSet); - - QQmlComponent component(&engine); - component.setData("import QtQuick 2.0\nListView { model: myModel }", QUrl()); - component.create(context); - \endcode - All properties added explicitly by QQmlContext::setContextProperty() take precedence over the context object's properties. - \section2 The Context Hierarchy - - Contexts form a hierarchy. The root of this hierarchy is the QML engine's - \l {QQmlEngine::rootContext()}{root context}. Child contexts inherit - the context properties of their parents; if a child context sets a context property - that already exists in its parent, the new context property overrides that of the - parent. - - The following example defines two contexts - \c context1 and \c context2. The - second context overrides the "b" context property inherited from the first with a - new value. - - \code - QQmlEngine engine; - QQmlContext *context1 = new QQmlContext(engine.rootContext()); - QQmlContext *context2 = new QQmlContext(context1); - - context1->setContextProperty("a", 12); - context1->setContextProperty("b", 12); - - context2->setContextProperty("b", 15); - \endcode + Child contexts inherit the context properties of their parents; if a child + context sets a context property that already exists in its parent, the new + context property overrides that of the parent. - While QML objects instantiated in a context are not strictly owned by that - context, their bindings are. If a context is destroyed, the property bindings of - outstanding QML objects will stop evaluating. - - \warning Setting the context object or adding new context properties after an object - has been created in that context is an expensive operation (essentially forcing all bindings - to reevaluate). Thus whenever possible you should complete "setup" of the context + \warning Setting the context object or adding new context properties after + an object has been created in that context is an expensive operation + (essentially forcing all bindings to re-evaluate). Thus, if you need to use + context properties, you should at least complete the "setup" of the context before using it to create any objects. \sa {qtqml-cppintegration-exposecppattributes.html}{Exposing Attributes of C++ Types to QML} @@ -152,7 +87,7 @@ QT_BEGIN_NAMESPACE /*! \internal */ QQmlContext::QQmlContext(QQmlEngine *e, bool) - : QObject(*(new QQmlContextPrivate(this, nullptr, e))) + : QObject(*(new QQmlContextPrivate(this, QQmlRefPointer<QQmlContextData>(), e))) { } @@ -161,9 +96,9 @@ QQmlContext::QQmlContext(QQmlEngine *e, bool) QObject \a parent. */ QQmlContext::QQmlContext(QQmlEngine *engine, QObject *parent) - : QObject(*(new QQmlContextPrivate( - this, engine ? QQmlContextData::get(engine->rootContext()).data() : nullptr)), - parent) + : QObject(*(new QQmlContextPrivate(this, engine + ? QQmlContextData::get(engine->rootContext()) + : QQmlRefPointer<QQmlContextData>())), parent) { } @@ -172,9 +107,9 @@ QQmlContext::QQmlContext(QQmlEngine *engine, QObject *parent) QObject \a parent. */ QQmlContext::QQmlContext(QQmlContext *parentContext, QObject *parent) - : QObject(*(new QQmlContextPrivate( - this, parentContext ? QQmlContextData::get(parentContext).data() : nullptr)), - parent) + : QObject(*(new QQmlContextPrivate(this, parentContext + ? QQmlContextData::get(parentContext) + : QQmlRefPointer<QQmlContextData>())), parent) { } @@ -212,7 +147,7 @@ bool QQmlContext::isValid() const } /*! - Return the context's QQmlEngine, or 0 if the context has no QQmlEngine or the + Return the context's QQmlEngine, or \nullptr if the context has no QQmlEngine or the QQmlEngine was destroyed. */ QQmlEngine *QQmlContext::engine() const @@ -222,7 +157,7 @@ QQmlEngine *QQmlContext::engine() const } /*! - Return the context's parent QQmlContext, or 0 if this context has no + Return the context's parent QQmlContext, or \nullptr if this context has no parent or if the parent has been destroyed. */ QQmlContext *QQmlContext::parentContext() const @@ -235,7 +170,7 @@ QQmlContext *QQmlContext::parentContext() const } /*! - Return the context object, or 0 if there is no context object. + Return the context object, or \nullptr if there is no context object. */ QObject *QQmlContext::contextObject() const { @@ -245,6 +180,9 @@ QObject *QQmlContext::contextObject() const /*! Set the context \a object. + + \note You should not use context objects to inject values into your QML + components. Use singletons or regular object properties instead. */ void QQmlContext::setContextObject(QObject *object) { @@ -268,6 +206,9 @@ void QQmlContext::setContextObject(QObject *object) /*! Set a the \a value of the \a name property on this context. + + \note You should not use context properties to inject values into your QML + components. Use singletons or regular object properties instead. */ void QQmlContext::setContextProperty(const QString &name, const QVariant &value) { @@ -287,10 +228,14 @@ void QQmlContext::setContextProperty(const QString &name, const QVariant &value) return; } - QV4::IdentifierHash *properties = data->detachedPropertyNames(); - int idx = properties->value(name); + if (bool isNumber = false; name.toUInt(&isNumber), isNumber) { + qWarning("QQmlContext: Using numbers as context properties will be disallowed in a future Qt version."); + QT7_ONLY(return;) + } + + int idx = data->propertyIndex(name); if (idx == -1) { - properties->add(name, data->numIdValues() + d->numPropertyValues()); + data->addPropertyNameAndIndex(name, data->numIdValues() + d->numPropertyValues()); d->appendPropertyValue(value); data->refreshExpressions(); } else { @@ -309,6 +254,9 @@ void QQmlContext::setContextProperty(const QString &name, const QVariant &value) Set the \a value of the \a name property on this context. QQmlContext does \b not take ownership of \a value. + + \note You should not use context properties to inject values into your QML + components. Use singletons or regular object properties instead. */ void QQmlContext::setContextProperty(const QString &name, QObject *value) { @@ -324,6 +272,9 @@ void QQmlContext::setContextProperty(const QString &name, QObject *value) refreshing expressions, and is therefore recommended instead of calling \l setContextProperty() for each individual property. + \note You should not use context properties to inject values into your QML + components. Use singletons or regular object properties instead. + \sa QQmlContext::setContextProperty() */ void QQmlContext::setContextProperties(const QList<PropertyPair> &properties) @@ -354,48 +305,68 @@ void QQmlContext::setContextProperties(const QList<PropertyPair> &properties) \sa QQmlContext::setContextProperties() */ +static bool readObjectProperty( + const QQmlRefPointer<QQmlContextData> &data, QObject *object, const QString &name, + QVariant *target) +{ + QQmlPropertyData local; + if (const QQmlPropertyData *property = QQmlPropertyCache::property(object, name, data, &local)) { + *target = object->metaObject()->property(property->coreIndex()).read(object); + return true; + } + return false; +} + /*! - Returns the value of the \a name property for this context - as a QVariant. + Returns the value of the \a name property for this context as a QVariant. + If you know that the property you're looking for is a QObject assigned using + a QML id in the current context, \l objectForName() is more convenient and + faster. In contrast to \l objectForName() and \l nameForObject(), this method + does traverse the context hierarchy and searches in parent contexts if the + \a name is not found in the current one. It also considers any + \l contextObject() you may have set. + + \sa objectForName(), nameForObject(), contextObject() */ QVariant QQmlContext::contextProperty(const QString &name) const { Q_D(const QQmlContext); - QVariant value; - int idx = -1; - QQmlRefPointer<QQmlContextData> data = d->m_data; - - const QV4::IdentifierHash properties = data->propertyNames(); - if (properties.count()) - idx = properties.value(name); + const QQmlRefPointer<QQmlContextData> data = d->m_data; + const int idx = data->propertyIndex(name); if (idx == -1) { if (QObject *obj = data->contextObject()) { - QQmlPropertyData local; - QQmlPropertyData *property = - QQmlPropertyCache::property(data->engine(), obj, name, data, &local); - - if (property) value = obj->metaObject()->property(property->coreIndex()).read(obj); + QVariant value; + if (readObjectProperty(data, obj, name, &value)) + return value; } - if (!value.isValid() && parentContext()) - value = parentContext()->contextProperty(name); + + if (parentContext()) + return parentContext()->contextProperty(name); } else { if (idx >= d->numPropertyValues()) - value = QVariant::fromValue(data->idValue(idx - d->numPropertyValues())); + return QVariant::fromValue(data->idValue(idx - d->numPropertyValues())); else - value = d->propertyValue(idx); + return d->propertyValue(idx); } - return value; + return QVariant(); } /*! -Returns the name of \a object in this context, or an empty string if \a object -is not named in the context. Objects are named by setContextProperty(), or by ids in -the case of QML created contexts. + Returns the name of \a object in this context, or an empty string if \a object + is not named in the context. Objects are named by \l setContextProperty(), or + as properties of a context object, or by ids in the case of QML created + contexts. + + If the object has multiple names, the first is returned. -If the object has multiple names, the first is returned. + In contrast to \l contextProperty(), this method does not traverse the + context hierarchy. If the name is not found in the current context, an empty + String is returned. + + \sa contextProperty(), objectForName() */ QString QQmlContext::nameForObject(const QObject *object) const { @@ -405,6 +376,40 @@ QString QQmlContext::nameForObject(const QObject *object) const } /*! + \since 6.2 + + Returns the object for a given \a name in this context. Returns nullptr if + \a name is not available in the context or if the value associated with + \a name is not a QObject. Objects are named by \l setContextProperty(), + or as properties of a context object, or by ids in the case of QML created + contexts. In contrast to \l contextProperty(), this method does not traverse + the context hierarchy. If the name is not found in the current context, + nullptr is returned. + + \sa contextProperty(), nameForObject() +*/ +QObject *QQmlContext::objectForName(const QString &name) const +{ + Q_D(const QQmlContext); + + QQmlRefPointer<QQmlContextData> data = d->m_data; + if (const int propertyIndex = data->propertyIndex(name); propertyIndex >= 0) { + const int numPropertyValues = d->numPropertyValues(); + if (propertyIndex < numPropertyValues) + return qvariant_cast<QObject *>(d->propertyValue(propertyIndex)); + return data->idValue(propertyIndex - numPropertyValues); + } + + if (QObject *obj = data->contextObject()) { + QVariant result; + if (readObjectProperty(data, obj, name, &result)) + return qvariant_cast<QObject *>(result); + } + + return nullptr; +} + +/*! Resolves the URL \a src relative to the URL of the containing component. @@ -441,11 +446,14 @@ QUrl QQmlContext::baseUrl() const return d->m_data->baseUrl(); } +/*! + * \internal + */ QJSValue QQmlContext::importedScript(const QString &name) const { Q_D(const QQmlContext); - QQmlTypeNameCache::Result r = d->m_data->imports()->query(name); + QQmlTypeNameCache::Result r = d->m_data->imports()->query(name, QQmlTypeLoader::get(engine())); QV4::Scope scope(engine()->handle()); QV4::ScopedObject scripts(scope, d->m_data->importedScripts().valueRef()); return scripts ? QJSValuePrivate::fromReturnedValue(scripts->get(r.scriptIndex)) @@ -461,7 +469,7 @@ qsizetype QQmlContextPrivate::context_count(QQmlListProperty<QObject> *prop) if (d->propertyValue(contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) return 0; else - return ((const QList<QObject> *)d->propertyValue(contextProperty).constData())->count(); + return ((const QList<QObject> *)d->propertyValue(contextProperty).constData())->size(); } QObject *QQmlContextPrivate::context_at(QQmlListProperty<QObject> *prop, qsizetype index) @@ -481,7 +489,7 @@ void QQmlContextPrivate::dropDestroyedQObject(const QString &name, QObject *dest if (!m_data->isValid()) return; - const int idx = m_data->propertyNames().value(name); + const int idx = m_data->propertyIndex(name); Q_ASSERT(idx >= 0); if (qvariant_cast<QObject *>(propertyValue(idx)) != destroyed) return; @@ -499,7 +507,8 @@ void QQmlContextPrivate::emitDestruction() // deref'd. It's OK to pass a half-created publicContext here. We will not dereference it during // construction. QQmlContextPrivate::QQmlContextPrivate( - QQmlContext *publicContext, QQmlContextData *parent, QQmlEngine *engine) : + QQmlContext *publicContext, const QQmlRefPointer<QQmlContextData> &parent, + QQmlEngine *engine) : m_data(new QQmlContextData(QQmlContextData::OwnedByPublicContext, publicContext, parent, engine)) { |