diff options
Diffstat (limited to 'src/declarative/qml/qdeclarativecontext.cpp')
-rw-r--r-- | src/declarative/qml/qdeclarativecontext.cpp | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/src/declarative/qml/qdeclarativecontext.cpp b/src/declarative/qml/qdeclarativecontext.cpp new file mode 100644 index 00000000..9132d0e1 --- /dev/null +++ b/src/declarative/qml/qdeclarativecontext.cpp @@ -0,0 +1,774 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativecontext.h" +#include "private/qdeclarativecontext_p.h" + +#include "private/qdeclarativecomponent_p.h" +#include "private/qdeclarativeexpression_p.h" +#include "private/qdeclarativeengine_p.h" +#include "qdeclarativeengine.h" +#include "private/qdeclarativecompiledbindings_p.h" +#include "qdeclarativeinfo.h" +#include "private/qdeclarativeglobalscriptclass_p.h" + +#include <qscriptengine.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qdebug.h> + +#include <private/qscriptdeclarativeclass_p.h> + +QT_BEGIN_NAMESPACE + +QDeclarativeContextPrivate::QDeclarativeContextPrivate() +: data(0), notifyIndex(-1) +{ +} + +/*! + \class QDeclarativeContext + \since 4.7 + \brief The QDeclarativeContext class defines a context within a QML engine. + \mainclass + + Contexts allow data to be exposed to the QML components instantiated by the + QML engine. + + Each QDeclarativeContext 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 + QDeclarativeContext::setContextProperty(). The following example shows a Qt model + being bound to a context and then accessed from a QML file. + + \code + QDeclarativeEngine engine; + QStringListModel modelData; + QDeclarativeContext *context = new QDeclarativeContext(engine.rootContext()); + context->setContextProperty("myModel", &modelData); + + QDeclarativeComponent component(&engine); + component.setData("import QtQuick 1.0\nListView { model: myModel }", QUrl()); + QObject *window = component.create(context); + \endcode + + Note it is the responsibility of the creator to delete any QDeclarativeContext 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. + + To simplify binding and maintaining larger data sets, a context object can be set + on a QDeclarativeContext. All the properties of the context object are available + by name in the context, as though they were all individually added through calls + to QDeclarativeContext::setContextProperty(). Changes to the property's values are + detected through the property's notify signal. Setting a context object is both + faster and easier than manually adding and maintaing 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; + QDeclarativeEngine engine; + QDeclarativeContext *context = new QDeclarativeContext(engine.rootContext()); + context->setContextObject(&myDataSet); + + QDeclarativeComponent component(&engine); + component.setData("import QtQuick 1.0\nListView { model: myModel }", QUrl()); + component.create(context); + \endcode + + All properties added explicitly by QDeclarativeContext::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 {QDeclarativeEngine::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 + QDeclarativeEngine engine; + QDeclarativeContext *context1 = new QDeclarativeContext(engine.rootContext()); + QDeclarativeContext *context2 = new QDeclarativeContext(context1); + + context1->setContextProperty("a", 12); + context1->setContextProperty("b", 12); + + context2->setContextProperty("b", 15); + \endcode + + 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 + before using it to create any objects. + + \sa {Using QML Bindings in C++ Applications} +*/ + +/*! \internal */ +QDeclarativeContext::QDeclarativeContext(QDeclarativeEngine *e, bool) +: QObject(*(new QDeclarativeContextPrivate)) +{ + Q_D(QDeclarativeContext); + d->data = new QDeclarativeContextData(this); + + d->data->engine = e; +} + +/*! + Create a new QDeclarativeContext as a child of \a engine's root context, and the + QObject \a parent. +*/ +QDeclarativeContext::QDeclarativeContext(QDeclarativeEngine *engine, QObject *parent) +: QObject(*(new QDeclarativeContextPrivate), parent) +{ + Q_D(QDeclarativeContext); + d->data = new QDeclarativeContextData(this); + + d->data->setParent(engine?QDeclarativeContextData::get(engine->rootContext()):0); +} + +/*! + Create a new QDeclarativeContext with the given \a parentContext, and the + QObject \a parent. +*/ +QDeclarativeContext::QDeclarativeContext(QDeclarativeContext *parentContext, QObject *parent) +: QObject(*(new QDeclarativeContextPrivate), parent) +{ + Q_D(QDeclarativeContext); + d->data = new QDeclarativeContextData(this); + + d->data->setParent(parentContext?QDeclarativeContextData::get(parentContext):0); +} + +/*! + \internal +*/ +QDeclarativeContext::QDeclarativeContext(QDeclarativeContextData *data) +: QObject(*(new QDeclarativeContextPrivate), 0) +{ + Q_D(QDeclarativeContext); + d->data = data; +} + +/*! + Destroys the QDeclarativeContext. + + Any expressions, or sub-contexts dependent on this context will be + invalidated, but not destroyed (unless they are parented to the QDeclarativeContext + object). + */ +QDeclarativeContext::~QDeclarativeContext() +{ + Q_D(QDeclarativeContext); + + if (!d->data->isInternal) + d->data->destroy(); +} + +/*! + Returns whether the context is valid. + + To be valid, a context must have a engine, and it's contextObject(), if any, + must not have been deleted. +*/ +bool QDeclarativeContext::isValid() const +{ + Q_D(const QDeclarativeContext); + return d->data && d->data->isValid(); +} + +/*! + Return the context's QDeclarativeEngine, or 0 if the context has no QDeclarativeEngine or the + QDeclarativeEngine was destroyed. +*/ +QDeclarativeEngine *QDeclarativeContext::engine() const +{ + Q_D(const QDeclarativeContext); + return d->data->engine; +} + +/*! + Return the context's parent QDeclarativeContext, or 0 if this context has no + parent or if the parent has been destroyed. +*/ +QDeclarativeContext *QDeclarativeContext::parentContext() const +{ + Q_D(const QDeclarativeContext); + return d->data->parent?d->data->parent->asQDeclarativeContext():0; +} + +/*! + Return the context object, or 0 if there is no context object. +*/ +QObject *QDeclarativeContext::contextObject() const +{ + Q_D(const QDeclarativeContext); + return d->data->contextObject; +} + +/*! + Set the context \a object. +*/ +void QDeclarativeContext::setContextObject(QObject *object) +{ + Q_D(QDeclarativeContext); + + QDeclarativeContextData *data = d->data; + + if (data->isInternal) { + qWarning("QDeclarativeContext: Cannot set context object for internal context."); + return; + } + + if (!isValid()) { + qWarning("QDeclarativeContext: Cannot set context object on invalid context."); + return; + } + + data->contextObject = object; +} + +/*! + Set a the \a value of the \a name property on this context. +*/ +void QDeclarativeContext::setContextProperty(const QString &name, const QVariant &value) +{ + Q_D(QDeclarativeContext); + if (d->notifyIndex == -1) + d->notifyIndex = this->metaObject()->methodCount(); + + QDeclarativeContextData *data = d->data; + + if (data->isInternal) { + qWarning("QDeclarativeContext: Cannot set property on internal context."); + return; + } + + if (!isValid()) { + qWarning("QDeclarativeContext: Cannot set property on invalid context."); + return; + } + + if (data->engine) { + bool ok; + QObject *o = QDeclarativeEnginePrivate::get(data->engine)->toQObject(value, &ok); + if (ok) { + setContextProperty(name, o); + return; + } + } + + if (!data->propertyNames) data->propertyNames = new QDeclarativeIntegerCache(data->engine); + + int idx = data->propertyNames->value(name); + if (idx == -1) { + data->propertyNames->add(name, data->idValueCount + d->propertyValues.count()); + d->propertyValues.append(value); + + data->refreshExpressions(); + } else { + d->propertyValues[idx] = value; + QMetaObject::activate(this, idx + d->notifyIndex, 0); + } +} + +/*! + Set the \a value of the \a name property on this context. + + QDeclarativeContext does \bold not take ownership of \a value. +*/ +void QDeclarativeContext::setContextProperty(const QString &name, QObject *value) +{ + Q_D(QDeclarativeContext); + if (d->notifyIndex == -1) + d->notifyIndex = this->metaObject()->methodCount(); + + QDeclarativeContextData *data = d->data; + + if (data->isInternal) { + qWarning("QDeclarativeContext: Cannot set property on internal context."); + return; + } + + if (!isValid()) { + qWarning("QDeclarativeContext: Cannot set property on invalid context."); + return; + } + + if (!data->propertyNames) data->propertyNames = new QDeclarativeIntegerCache(data->engine); + int idx = data->propertyNames->value(name); + + if (idx == -1) { + data->propertyNames->add(name, data->idValueCount + d->propertyValues.count()); + d->propertyValues.append(QVariant::fromValue(value)); + + data->refreshExpressions(); + } else { + d->propertyValues[idx] = QVariant::fromValue(value); + QMetaObject::activate(this, idx + d->notifyIndex, 0); + } +} + +/*! + Returns the value of the \a name property for this context + as a QVariant. + */ +QVariant QDeclarativeContext::contextProperty(const QString &name) const +{ + Q_D(const QDeclarativeContext); + QVariant value; + int idx = -1; + + QDeclarativeContextData *data = d->data; + + if (data->propertyNames) + idx = data->propertyNames->value(name); + + if (idx == -1) { + QByteArray utf8Name = name.toUtf8(); + if (data->contextObject) { + QObject *obj = data->contextObject; + QDeclarativePropertyCache::Data local; + QDeclarativePropertyCache::Data *property = + QDeclarativePropertyCache::property(data->engine, obj, name, local); + + if (property) value = obj->metaObject()->property(property->coreIndex).read(obj); + } + if (!value.isValid() && parentContext()) + value = parentContext()->contextProperty(name); + } else { + if (idx >= d->propertyValues.count()) + value = QVariant::fromValue(data->idValues[idx - d->propertyValues.count()].data()); + else + value = d->propertyValues[idx]; + } + + return value; +} + +/*! + Resolves the URL \a src relative to the URL of the + containing component. + + \sa QDeclarativeEngine::baseUrl(), setBaseUrl() +*/ +QUrl QDeclarativeContext::resolvedUrl(const QUrl &src) +{ + Q_D(QDeclarativeContext); + return d->data->resolvedUrl(src); +} + +QUrl QDeclarativeContextData::resolvedUrl(const QUrl &src) +{ + QDeclarativeContextData *ctxt = this; + + if (src.isRelative() && !src.isEmpty()) { + if (ctxt) { + while(ctxt) { + if(ctxt->url.isValid()) + break; + else + ctxt = ctxt->parent; + } + + if (ctxt) + return ctxt->url.resolved(src); + else if (engine) + return engine->baseUrl().resolved(src); + } + return QUrl(); + } else { + return src; + } +} + + +/*! + Explicitly sets the url resolvedUrl() will use for relative references to \a baseUrl. + + Calling this function will override the url of the containing + component used by default. + + \sa resolvedUrl() +*/ +void QDeclarativeContext::setBaseUrl(const QUrl &baseUrl) +{ + Q_D(QDeclarativeContext); + + d->data->url = baseUrl; +} + +/*! + Returns the base url of the component, or the containing component + if none is set. +*/ +QUrl QDeclarativeContext::baseUrl() const +{ + Q_D(const QDeclarativeContext); + const QDeclarativeContextData* data = d->data; + while (data && data->url.isEmpty()) + data = data->parent; + + if (data) + return data->url; + else + return QUrl(); +} + +int QDeclarativeContextPrivate::context_count(QDeclarativeListProperty<QObject> *prop) +{ + QDeclarativeContext *context = static_cast<QDeclarativeContext*>(prop->object); + QDeclarativeContextPrivate *d = QDeclarativeContextPrivate::get(context); + int contextProperty = (int)(quintptr)prop->data; + + if (d->propertyValues.at(contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) { + return 0; + } else { + return ((const QList<QObject> *)d->propertyValues.at(contextProperty).constData())->count(); + } +} + +QObject *QDeclarativeContextPrivate::context_at(QDeclarativeListProperty<QObject> *prop, int index) +{ + QDeclarativeContext *context = static_cast<QDeclarativeContext*>(prop->object); + QDeclarativeContextPrivate *d = QDeclarativeContextPrivate::get(context); + int contextProperty = (int)(quintptr)prop->data; + + if (d->propertyValues.at(contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) { + return 0; + } else { + return ((const QList<QObject*> *)d->propertyValues.at(contextProperty).constData())->at(index); + } +} + + +QDeclarativeContextData::QDeclarativeContextData() +: parent(0), engine(0), isInternal(false), publicContext(0), propertyNames(0), contextObject(0), + imports(0), childContexts(0), nextChild(0), prevChild(0), expressions(0), contextObjects(0), + contextGuards(0), idValues(0), idValueCount(0), optimizedBindings(0), linkedContext(0), + componentAttached(0) +{ +} + +QDeclarativeContextData::QDeclarativeContextData(QDeclarativeContext *ctxt) +: parent(0), engine(0), isInternal(false), publicContext(ctxt), propertyNames(0), contextObject(0), + imports(0), childContexts(0), nextChild(0), prevChild(0), expressions(0), contextObjects(0), + contextGuards(0), idValues(0), idValueCount(0), optimizedBindings(0), linkedContext(0), + componentAttached(0) +{ +} + +void QDeclarativeContextData::invalidate() +{ + while (childContexts) + childContexts->invalidate(); + + while (componentAttached) { + QDeclarativeComponentAttached *a = componentAttached; + componentAttached = a->next; + if (componentAttached) componentAttached->prev = &componentAttached; + + a->next = 0; + a->prev = 0; + + emit a->destruction(); + } + + if (prevChild) { + *prevChild = nextChild; + if (nextChild) nextChild->prevChild = prevChild; + nextChild = 0; + prevChild = 0; + } + + engine = 0; + parent = 0; +} + +void QDeclarativeContextData::clearContext() +{ + if (engine) { + while (componentAttached) { + QDeclarativeComponentAttached *a = componentAttached; + componentAttached = a->next; + if (componentAttached) componentAttached->prev = &componentAttached; + + a->next = 0; + a->prev = 0; + + emit a->destruction(); + } + } + + QDeclarativeAbstractExpression *expression = expressions; + while (expression) { + QDeclarativeAbstractExpression *nextExpression = expression->m_nextExpression; + + expression->m_context = 0; + expression->m_prevExpression = 0; + expression->m_nextExpression = 0; + + expression = nextExpression; + } + expressions = 0; +} + +void QDeclarativeContextData::destroy() +{ + if (linkedContext) + linkedContext->destroy(); + + if (engine) invalidate(); + + clearContext(); + + while (contextObjects) { + QDeclarativeData *co = contextObjects; + contextObjects = contextObjects->nextContextObject; + + co->context = 0; + co->outerContext = 0; + co->nextContextObject = 0; + co->prevContextObject = 0; + } + + QDeclarativeGuardedContextData *contextGuard = contextGuards; + while (contextGuard) { + QDeclarativeGuardedContextData *next = contextGuard->m_next; + contextGuard->m_next = 0; + contextGuard->m_prev = 0; + contextGuard->m_contextData = 0; + contextGuard = next; + } + contextGuards = 0; + + if (propertyNames) + propertyNames->release(); + + if (imports) + imports->release(); + + if (optimizedBindings) + optimizedBindings->release(); + + delete [] idValues; + + if (isInternal) + delete publicContext; + + delete this; +} + +void QDeclarativeContextData::setParent(QDeclarativeContextData *p) +{ + if (p) { + parent = p; + engine = p->engine; + nextChild = p->childContexts; + if (nextChild) nextChild->prevChild = &nextChild; + prevChild = &p->childContexts; + p->childContexts = this; + } +} + +/* +Refreshes all expressions that could possibly depend on this context. Refreshing flushes all +context-tree dependent caches in the expressions, and should occur every time the context tree + *structure* (not values) changes. +*/ +void QDeclarativeContextData::refreshExpressions() +{ + QDeclarativeContextData *child = childContexts; + while (child) { + child->refreshExpressions(); + child = child->nextChild; + } + + QDeclarativeAbstractExpression *expression = expressions; + while (expression) { + expression->refresh(); + expression = expression->m_nextExpression; + } +} + +void QDeclarativeContextData::addObject(QObject *o) +{ + QDeclarativeData *data = QDeclarativeData::get(o, true); + + Q_ASSERT(data->context == 0); + + data->context = this; + data->outerContext = this; + + data->nextContextObject = contextObjects; + if (data->nextContextObject) + data->nextContextObject->prevContextObject = &data->nextContextObject; + data->prevContextObject = &contextObjects; + contextObjects = data; +} + +void QDeclarativeContextData::addImportedScript(const QDeclarativeParser::Object::ScriptBlock &script) +{ + if (!engine) + return; + + QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(engine); + QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); + + const QString &code = script.code; + const QString &url = script.file; + const QDeclarativeParser::Object::ScriptBlock::Pragmas &pragmas = script.pragmas; + + Q_ASSERT(!url.isEmpty()); + + if (pragmas & QDeclarativeParser::Object::ScriptBlock::Shared) { + + QHash<QString, QScriptValue>::Iterator iter = enginePriv->m_sharedScriptImports.find(url); + if (iter == enginePriv->m_sharedScriptImports.end()) { + QScriptContext *scriptContext = QScriptDeclarativeClass::pushCleanContext(scriptEngine); + + scriptContext->pushScope(enginePriv->contextClass->newUrlContext(url)); + scriptContext->pushScope(enginePriv->globalClass->staticGlobalObject()); + + QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(scriptEngine); + scriptContext->pushScope(scope); + + scriptEngine->evaluate(code, url, 1); + + if (scriptEngine->hasUncaughtException()) { + QDeclarativeError error; + QDeclarativeExpressionPrivate::exceptionToError(scriptEngine, error); + enginePriv->warning(error); + } + + scriptEngine->popContext(); + + iter = enginePriv->m_sharedScriptImports.insert(url, scope); + } + + importedScripts.append(*iter); + + } else { + + QScriptContext *scriptContext = QScriptDeclarativeClass::pushCleanContext(scriptEngine); + + scriptContext->pushScope(enginePriv->contextClass->newUrlContext(this, 0, url)); + scriptContext->pushScope(enginePriv->globalClass->staticGlobalObject()); + + QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(scriptEngine); + scriptContext->pushScope(scope); + + scriptEngine->evaluate(code, url, 1); + + if (scriptEngine->hasUncaughtException()) { + QDeclarativeError error; + QDeclarativeExpressionPrivate::exceptionToError(scriptEngine, error); + enginePriv->warning(error); + } + + scriptEngine->popContext(); + + importedScripts.append(scope); + + } +} + +void QDeclarativeContextData::setIdProperty(int idx, QObject *obj) +{ + idValues[idx] = obj; + idValues[idx].context = this; +} + +void QDeclarativeContextData::setIdPropertyData(QDeclarativeIntegerCache *data) +{ + Q_ASSERT(!propertyNames); + propertyNames = data; + propertyNames->addref(); + + idValueCount = data->count(); + idValues = new ContextGuard[idValueCount]; +} + +QString QDeclarativeContextData::findObjectId(const QObject *obj) const +{ + if (!idValues || !propertyNames) + return QString(); + + for (int i=0; i<idValueCount; i++) { + if (idValues[i] == obj) + return propertyNames->findId(i); + } + + if (linkedContext) + return linkedContext->findObjectId(obj); + return QString(); +} + +QDeclarativeContext *QDeclarativeContextData::asQDeclarativeContext() +{ + if (!publicContext) + publicContext = new QDeclarativeContext(this); + return publicContext; +} + +QDeclarativeContextPrivate *QDeclarativeContextData::asQDeclarativeContextPrivate() +{ + return QDeclarativeContextPrivate::get(asQDeclarativeContext()); +} + +QT_END_NAMESPACE |