src/declarative/qml/qdeclarativecontext.cpp
changeset 30 5dc02b23752f
child 33 3e2da88830cd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/declarative/qml/qdeclarativecontext.cpp	Tue Jul 06 15:10:48 2010 +0300
@@ -0,0 +1,753 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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;
+    QDeclarativeContext *context = new QDeclarativeContext(engine.rootContext());
+    context->setContextProperty("myModel", modelData);
+
+    QDeclarativeComponent component(&engine, "ListView { model=myModel }");
+    component.create(context);
+    \endcode
+
+    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 = new MyDataSet;
+    QDeclarativeEngine engine;
+    QDeclarativeContext *context = new QDeclarativeContext(engine.rootContext());
+    context->setContextObject(myDataSet);
+
+    QDeclarativeComponent component(&engine, "ListView { model=myModel }");
+    component.create(context);
+    \endcode
+
+    All properties added explicitly by QDeclarativeContext::setContextProperty() take 
+    precedence over the context object's properties.
+
+    Contexts form a hierarchy.  The root of this heirarchy is the QDeclarativeEngine's
+    \l {QDeclarativeEngine::rootContext()}{root context}.  A component instance can 
+    access the data in its own context, as well as all its ancestor contexts.  Data
+    can be made available to all instances by modifying the 
+    \l {QDeclarativeEngine::rootContext()}{root context}.
+
+    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.
+
+    \note 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.
+*/
+
+/*! \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)(intptr_t)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)(intptr_t)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::clearExpressions()
+{
+    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();
+
+    clearExpressions();
+
+    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;
+
+    Q_ASSERT(script.codes.count() == 1);
+
+    QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(engine);
+    QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine);
+
+    const QString &code = script.codes.at(0);
+    const QString &url = script.files.at(0);
+    const QDeclarativeParser::Object::ScriptBlock::Pragmas &pragmas = script.pragmas.at(0);
+
+    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->globalObject());
+        
+            QScriptValue scope = scriptEngine->newObject();
+            scriptContext->setActivationObject(scope);
+            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->globalObject());
+        
+        QScriptValue scope = scriptEngine->newObject();
+        scriptContext->setActivationObject(scope);
+        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