From: Chris Adams Date: Wed, 17 Aug 2011 07:46:54 +0000 (+1000) Subject: Allow initial property values to be defined with QSGLoader X-Git-Url: http://git.silmor.de/gitweb/?a=commitdiff_plain;h=16f60d8ab6cdddeff860c3e2179cf2189908fc3e;p=konrad%2Fqtdeclarative.git Allow initial property values to be defined with QSGLoader This commit adds an "active" property to QSGLoader, which can be used to delay instantiation of the item until the user wishes to activate the loader. The property is true by default in order to maintain compatibility with previous behaviour. The commit also adds a "setSource(v8object)" function to QSGLoader, which behaves identically to setSource() property mutator except that it takes a JavaScript object parameter which defines the initial property values of the item (in a manner similar to that of QDeclarativeComponent::createObject()). Task-number: QTBUG-17009 Change-Id: Ifd824b518b60ef7aa3017c384835abb552e65cf1 Reviewed-on: http://codereview.qt.nokia.com/3364 Reviewed-by: Qt Sanity Bot Reviewed-by: Michael Brasser --- diff --git a/src/declarative/items/qsgloader.cpp b/src/declarative/items/qsgloader.cpp index 999c174..0a0a3b9 100644 --- a/src/declarative/items/qsgloader.cpp +++ b/src/declarative/items/qsgloader.cpp @@ -46,16 +46,21 @@ #include #include +#include + +#include + QT_BEGIN_NAMESPACE QSGLoaderPrivate::QSGLoaderPrivate() : item(0), component(0), ownComponent(false), updatingSize(false), - itemWidthValid(false), itemHeightValid(false) + itemWidthValid(false), itemHeightValid(false), active(true) { } QSGLoaderPrivate::~QSGLoaderPrivate() { + disposeInitialPropertyValues(); } void QSGLoaderPrivate::itemGeometryChanged(QSGItem *resizeItem, const QRectF &newGeometry, const QRectF &oldGeometry) @@ -72,6 +77,8 @@ void QSGLoaderPrivate::itemGeometryChanged(QSGItem *resizeItem, const QRectF &ne void QSGLoaderPrivate::clear() { + disposeInitialPropertyValues(); + if (ownComponent) { component->deleteLater(); component = 0; @@ -228,6 +235,59 @@ QSGLoader::~QSGLoader() } /*! + \qmlproperty bool QtQuick2::Loader::active + This property is \c true if the Loader is currently active. + The default value for the \l active property is \c true. + + If the Loader is inactive, changing the \l source or \l sourceComponent + will not cause the item to be instantiated until the Loader is made active. + + Setting the value to inactive will cause any \l item loaded by the loader + to be released, but will not affect the \l source or \l sourceComponent. + + The \l status of an inactive loader is always \c Null. + + \sa source, sourceComponent + */ +bool QSGLoader::active() const +{ + Q_D(const QSGLoader); + return d->active; +} + +void QSGLoader::setActive(bool newVal) +{ + Q_D(QSGLoader); + if (d->active != newVal) { + d->active = newVal; + if (newVal == true) { + if (d->loadingFromSource) { + loadFromSource(); + } else { + loadFromSourceComponent(); + } + d->disposeInitialPropertyValues(); // release persistent handles + } else { + if (d->item) { + QSGItemPrivate *p = QSGItemPrivate::get(d->item); + p->removeItemChangeListener(d, QSGItemPrivate::Geometry); + + // We can't delete immediately because our item may have triggered + // the Loader to load a different item. + d->item->setParentItem(0); + d->item->setVisible(false); + d->item->deleteLater(); + d->item = 0; + emit itemChanged(); + } + emit statusChanged(); + } + emit activeChanged(); + } +} + + +/*! \qmlproperty url QtQuick2::Loader::source This property holds the URL of the QML component to instantiate. @@ -248,13 +308,30 @@ QUrl QSGLoader::source() const void QSGLoader::setSource(const QUrl &url) { + setSource(url, true); // clear previous values +} + +void QSGLoader::setSource(const QUrl &url, bool needsClear) +{ Q_D(QSGLoader); if (d->source == url) return; - d->clear(); + if (needsClear) + d->clear(); d->source = url; + d->loadingFromSource = true; + + if (d->active) + loadFromSource(); + else + emit sourceChanged(); +} + +void QSGLoader::loadFromSource() +{ + Q_D(QSGLoader); if (d->source.isEmpty()) { emit sourceChanged(); emit statusChanged(); @@ -308,6 +385,22 @@ void QSGLoader::setSourceComponent(QDeclarativeComponent *comp) d->component = comp; d->ownComponent = false; + d->loadingFromSource = false; + + if (d->active) + loadFromSourceComponent(); + else + emit sourceComponentChanged(); +} + +void QSGLoader::resetSourceComponent() +{ + setSourceComponent(0); +} + +void QSGLoader::loadFromSourceComponent() +{ + Q_D(QSGLoader); if (!d->component) { emit sourceComponentChanged(); emit statusChanged(); @@ -320,9 +413,95 @@ void QSGLoader::setSourceComponent(QDeclarativeComponent *comp) d->load(); } -void QSGLoader::resetSourceComponent() +/*! + \qmlmethod object QtQuick2::Loader::setSource(url source, object properties) + + Creates an object instance of the given \a source component that will have + the given \a properties. The \a properties argument is optional. The instance + will be accessible via the \l item property once loading and instantiation + is complete. + + If the \l active property is \c false at the time when this function is called, + the given \a source component will not be loaded but the \a source and initial + \a properties will be cached. When the loader is made \l active, an instance of + the \a source component will be created with the initial \a properties set. + + Setting the initial property values of an instance of a component in this manner + will \e not trigger any associated \l{Behavior}s. + + Note that the cached \a properties will be cleared if the \l source or \l sourceComponent + is changed after calling this function but prior to setting the loader \l active. + + Example: + \table + \row + \o + \qml + // ExampleComponent.qml + import QtQuick 2.0 + Rectangle { + id: rect + color: "red" + width: 10 + height: 10 + + Behavior on color { + NumberAnimation { + target: rect + property: "width" + to: (rect.width + 20) + duration: 0 + } + } + } + \endqml + \o + \qml + // example.qml + import QtQuick 2.0 + Item { + Loader { + id: squareLoader + onLoaded: console.log(squareLoader.item.width); // prints [10], not [30] + } + + Component.onCompleted: { + squareLoader.setSource("ExampleComponent.qml", { "color": "blue" }); + // will trigger the onLoaded code when complete. + } + } + \endqml + \endtable + + \sa source, active +*/ +void QSGLoader::setSource(QDeclarativeV8Function *args) { - setSourceComponent(0); + Q_ASSERT(args); + Q_D(QSGLoader); + + bool ipvError = false; + args->returnValue(v8::Undefined()); + v8::Handle ipv = d->extractInitialPropertyValues(args, this, &ipvError); + if (ipvError) + return; + + d->clear(); + QUrl sourceUrl = d->resolveSourceUrl(args); + if (!ipv.IsEmpty()) { + d->initialPropertyValues = qPersistentNew(ipv); + d->qmlGlobalForIpv = qPersistentNew(args->qmlGlobal()); + } + + setSource(sourceUrl, false); // already cleared and set ipv above. +} + +void QSGLoaderPrivate::disposeInitialPropertyValues() +{ + if (!initialPropertyValues.IsEmpty()) + qPersistentDispose(initialPropertyValues); + if (!qmlGlobalForIpv.IsEmpty()) + qPersistentDispose(qmlGlobalForIpv); } void QSGLoaderPrivate::load() @@ -377,7 +556,7 @@ void QSGLoaderPrivate::_q_sourceLoaded() // component to be set to something else. In that case we just // need to cleanup. if (c) - c->completeCreate(); + completeCreateWithInitialPropertyValues(c, obj, initialPropertyValues, qmlGlobalForIpv); delete obj; delete ctxt; return; @@ -402,7 +581,7 @@ void QSGLoaderPrivate::_q_sourceLoaded() delete ctxt; source = QUrl(); } - component->completeCreate(); + completeCreateWithInitialPropertyValues(component, obj, initialPropertyValues, qmlGlobalForIpv); if (ownComponent) emit q->sourceChanged(); else @@ -419,7 +598,7 @@ void QSGLoaderPrivate::_q_sourceLoaded() This property holds the status of QML loading. It can be one of: \list - \o Loader.Null - no QML source has been set + \o Loader.Null - the loader is inactive or no QML source has been set \o Loader.Ready - the QML source has been loaded \o Loader.Loading - the QML source is currently being loaded \o Loader.Error - an error occurred while loading the QML source @@ -458,6 +637,9 @@ QSGLoader::Status QSGLoader::status() const { Q_D(const QSGLoader); + if (!d->active) + return Null; + if (d->component) return static_cast(d->component->status()); @@ -548,6 +730,47 @@ void QSGLoader::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeom QSGItem::geometryChanged(newGeometry, oldGeometry); } +QUrl QSGLoaderPrivate::resolveSourceUrl(QDeclarativeV8Function *args) +{ + QV8Engine *v8engine = args->engine(); + QString arg = v8engine->toString((*args)[0]->ToString()); + if (arg.isEmpty()) + return QUrl(); + + QDeclarativeContextData *context = args->context(); + Q_ASSERT(context); + return context->resolvedUrl(QUrl(arg)); +} + +v8::Handle QSGLoaderPrivate::extractInitialPropertyValues(QDeclarativeV8Function *args, QObject *loader, bool *error) +{ + v8::Local valuemap; + if (args->Length() >= 2) { + v8::Local v = (*args)[1]; + if (!v->IsObject() || v->IsArray()) { + *error = true; + qmlInfo(loader) << loader->tr("setSource: value is not an object"); + } else { + *error = false; + valuemap = v8::Local::Cast(v); + } + } + + return valuemap; +} + +void QSGLoaderPrivate::completeCreateWithInitialPropertyValues(QDeclarativeComponent *component, QObject *object, v8::Handle initialPropertyValues, v8::Handle qmlGlobal) +{ + if (initialPropertyValues.IsEmpty()) { + component->completeCreate(); + return; + } + + QDeclarativeComponentPrivate *d = QDeclarativeComponentPrivate::get(component); + Q_ASSERT(d && d->engine); + d->completeCreateObjectWithInitialProperties(qmlGlobal, initialPropertyValues, object); +} + #include QT_END_NAMESPACE diff --git a/src/declarative/items/qsgloader_p.h b/src/declarative/items/qsgloader_p.h index 832d3a6..c3ce160 100644 --- a/src/declarative/items/qsgloader_p.h +++ b/src/declarative/items/qsgloader_p.h @@ -57,6 +57,7 @@ class Q_AUTOTEST_EXPORT QSGLoader : public QSGImplicitSizeItem Q_OBJECT Q_ENUMS(Status) + Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(QDeclarativeComponent *sourceComponent READ sourceComponent WRITE setSourceComponent RESET resetSourceComponent NOTIFY sourceComponentChanged) Q_PROPERTY(QSGItem *item READ item NOTIFY itemChanged) @@ -67,6 +68,11 @@ public: QSGLoader(QSGItem *parent = 0); virtual ~QSGLoader(); + bool active() const; + void setActive(bool newVal); + + Q_INVOKABLE void setSource(QDeclarativeV8Function *); + QUrl source() const; void setSource(const QUrl &); @@ -82,6 +88,7 @@ public: Q_SIGNALS: void itemChanged(); + void activeChanged(); void sourceChanged(); void sourceComponentChanged(); void statusChanged(); @@ -93,6 +100,9 @@ protected: void componentComplete(); private: + void setSource(const QUrl &sourceUrl, bool needsClear); + void loadFromSource(); + void loadFromSourceComponent(); Q_DISABLE_COPY(QSGLoader) Q_DECLARE_PRIVATE(QSGLoader) Q_PRIVATE_SLOT(d_func(), void _q_sourceLoaded()) diff --git a/src/declarative/items/qsgloader_p_p.h b/src/declarative/items/qsgloader_p_p.h index 306e5ea..732ec86 100644 --- a/src/declarative/items/qsgloader_p_p.h +++ b/src/declarative/items/qsgloader_p_p.h @@ -58,6 +58,8 @@ #include "qsgimplicitsizeitem_p_p.h" #include "qsgitemchangelistener_p.h" +#include + QT_BEGIN_NAMESPACE class QDeclarativeContext; @@ -74,13 +76,22 @@ public: void initResize(); void load(); + void disposeInitialPropertyValues(); + QUrl resolveSourceUrl(QDeclarativeV8Function *args); + v8::Handle extractInitialPropertyValues(QDeclarativeV8Function *args, QObject *loader, bool *error); + void completeCreateWithInitialPropertyValues(QDeclarativeComponent *component, QObject *object, v8::Handle initialPropertyValues, v8::Handle qmlGlobal); + QUrl source; QSGItem *item; QDeclarativeComponent *component; + v8::Persistent initialPropertyValues; + v8::Persistent qmlGlobalForIpv; bool ownComponent : 1; bool updatingSize: 1; bool itemWidthValid : 1; bool itemHeightValid : 1; + bool active : 1; + bool loadingFromSource : 1; void _q_sourceLoaded(); void _q_updateSize(bool loaderGeometryChanged = true); diff --git a/src/declarative/qml/qdeclarativecomponent.cpp b/src/declarative/qml/qdeclarativecomponent.cpp index eea94ce..49aa6d9 100644 --- a/src/declarative/qml/qdeclarativecomponent.cpp +++ b/src/declarative/qml/qdeclarativecomponent.cpp @@ -55,6 +55,9 @@ #include "private/qdeclarativedebugtrace_p.h" #include "private/qdeclarativeenginedebug_p.h" +#include "private/qv8engine_p.h" +#include "private/qv8include_p.h" + #include #include #include @@ -663,35 +666,45 @@ void QDeclarativeComponent::createObject(QDeclarativeV8Function *args) { Q_ASSERT(args); -#define RETURN(result) { args->returnValue((result)); return; } - Q_D(QDeclarativeComponent); Q_ASSERT(d->engine); - QDeclarativeEngine *engine = d->engine; - QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine); - QV8Engine *v8engine = ep->v8engine(); - - QDeclarativeContext *ctxt = creationContext(); - if (!ctxt) ctxt = engine->rootContext(); + QObject *parent = args->Length()?QDeclarativeEnginePrivate::get(d->engine)->v8engine()->toQObject((*args)[0]):0; v8::Local valuemap; if (args->Length() >= 2) { v8::Local v = (*args)[1]; if (!v->IsObject() || v->IsArray()) { qmlInfo(this) << tr("createObject: value is not an object"); - RETURN(v8::Null()); + args->returnValue(v8::Null()); + return; } valuemap = v8::Local::Cast(v); } - QObject *parent = args->Length()?v8engine->toQObject((*args)[0]):0; + QV8Engine *v8engine = QDeclarativeEnginePrivate::get(d->engine)->v8engine(); + QObject *retn = d->createObjectWithInitialProperties(args->qmlGlobal(), valuemap, parent); + if (!retn) + args->returnValue(v8::Null()); + else + args->returnValue(v8engine->newQObject(retn)); +} + +QObject *QDeclarativeComponentPrivate::createObjectWithInitialProperties(v8::Handle qmlGlobal, v8::Handle valuemap, QObject *parentObject) +{ + Q_Q(QDeclarativeComponent); + Q_ASSERT(engine); - QObject *ret = beginCreate(ctxt); + QDeclarativeContext *ctxt = q->creationContext(); + if (!ctxt) ctxt = engine->rootContext(); + + QObject *parent = parentObject; + + QObject *ret = q->beginCreate(ctxt); if (!ret) { - completeCreate(); - RETURN(v8::Null()); + q->completeCreate(); + return 0; } if (parent) { @@ -715,7 +728,14 @@ void QDeclarativeComponent::createObject(QDeclarativeV8Function *args) qWarning("QDeclarativeComponent: Created graphical object was not placed in the graphics scene."); } - v8::Handle ov = v8engine->newQObject(ret); + return completeCreateObjectWithInitialProperties(qmlGlobal, valuemap, ret); +} + +QObject *QDeclarativeComponentPrivate::completeCreateObjectWithInitialProperties(v8::Handle qmlGlobal, v8::Handle valuemap, QObject *toCreate) +{ + QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine); + QV8Engine *v8engine = ep->v8engine(); + v8::Handle ov = v8engine->newQObject(toCreate); Q_ASSERT(ov->IsObject()); v8::Handle object = v8::Handle::Cast(ov); @@ -738,7 +758,7 @@ void QDeclarativeComponent::createObject(QDeclarativeV8Function *args) "})" v8::Local script = v8engine->qmlModeCompile(SET_ARGS_SOURCE); - v8::Local function = v8::Local::Cast(script->Run(args->qmlGlobal())); + v8::Local function = v8::Local::Cast(script->Run(qmlGlobal)); // Try catch isn't needed as the function itself is loaded with try/catch v8::Handle args[] = { object, valuemap }; @@ -746,14 +766,12 @@ void QDeclarativeComponent::createObject(QDeclarativeV8Function *args) } completeCreate(); - - QDeclarativeData *ddata = QDeclarativeData::get(ret); + + QDeclarativeData *ddata = QDeclarativeData::get(toCreate); Q_ASSERT(ddata); ddata->setImplicitDestructible(); - RETURN(object); - -#undef RETURN + return v8engine->toQObject(object); } /*! diff --git a/src/declarative/qml/qdeclarativecomponent_p.h b/src/declarative/qml/qdeclarativecomponent_p.h index 7677f30..ba93749 100644 --- a/src/declarative/qml/qdeclarativecomponent_p.h +++ b/src/declarative/qml/qdeclarativecomponent_p.h @@ -55,6 +55,7 @@ #include "qdeclarativecomponent.h" +#include "private/qv8_p.h" #include "private/qdeclarativeengine_p.h" #include "private/qdeclarativetypeloader_p.h" #include "private/qbitfield_p.h" @@ -69,6 +70,8 @@ QT_BEGIN_NAMESPACE +class QV8Engine; + class QDeclarativeComponent; class QDeclarativeEngine; class QDeclarativeCompiledData; @@ -83,6 +86,8 @@ public: QObject *beginCreate(QDeclarativeContextData *, const QBitField &); void completeCreate(); + QObject *createObjectWithInitialProperties(v8::Handle qmlGlobal, v8::Handle valuemap, QObject *parentObject); + QObject *completeCreateObjectWithInitialProperties(v8::Handle qmlGlobal, v8::Handle valuemap, QObject *toCreate); QDeclarativeTypeData *typeData; virtual void typeDataReady(QDeclarativeTypeData *); @@ -115,6 +120,7 @@ public: ConstructionState *state); static void complete(QDeclarativeEnginePrivate *enginePriv, ConstructionState *state); + QDeclarativeEngine *engine; QDeclarativeGuardedContextData creationContext; diff --git a/tests/auto/declarative/qsgloader/data/ActiveComponent.qml b/tests/auto/declarative/qsgloader/data/ActiveComponent.qml new file mode 100644 index 0000000..24c6f7a --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/ActiveComponent.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 + +Item { + id: behaviorCounter + property int behaviorCount: 0 + property int canary: 0 + + Behavior on canary { + NumberAnimation { target: behaviorCounter; property: "behaviorCount"; to: (behaviorCounter.behaviorCount + 1); duration: 0 } + } +} diff --git a/tests/auto/declarative/qsgloader/data/InitialPropertyValuesComponent.qml b/tests/auto/declarative/qsgloader/data/InitialPropertyValuesComponent.qml new file mode 100644 index 0000000..24c6f7a --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/InitialPropertyValuesComponent.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 + +Item { + id: behaviorCounter + property int behaviorCount: 0 + property int canary: 0 + + Behavior on canary { + NumberAnimation { target: behaviorCounter; property: "behaviorCount"; to: (behaviorCounter.behaviorCount + 1); duration: 0 } + } +} diff --git a/tests/auto/declarative/qsgloader/data/InvalidSourceComponent.qml b/tests/auto/declarative/qsgloader/data/InvalidSourceComponent.qml new file mode 100644 index 0000000..7efa4a5 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/InvalidSourceComponent.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + RandomError +} diff --git a/tests/auto/declarative/qsgloader/data/active.1.qml b/tests/auto/declarative/qsgloader/data/active.1.qml new file mode 100644 index 0000000..2dbd1a0 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/active.1.qml @@ -0,0 +1,31 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + active: false + } + + Component { + id: inlineTestComponent + Item { + id: inlineTestItem + property int someProperty: 5 + } + } + + function doSetSource() { + loader.source = "ActiveComponent.qml"; + } + + function doSetSourceComponent() { + loader.sourceComponent = inlineTestComponent; + } + + function doSetActive() { + loader.active = true; + } +} diff --git a/tests/auto/declarative/qsgloader/data/active.2.qml b/tests/auto/declarative/qsgloader/data/active.2.qml new file mode 100644 index 0000000..e561744 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/active.2.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + source: "ActiveComponent.qml"; + + property int statusChangedCount: 0 + onStatusChanged: statusChangedCount = statusChangedCount + 1 + } + + function doSetInactive() { + loader.active = false; + } +} diff --git a/tests/auto/declarative/qsgloader/data/active.3.qml b/tests/auto/declarative/qsgloader/data/active.3.qml new file mode 100644 index 0000000..0fbba95 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/active.3.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + source: "ActiveComponent.qml"; + + property int sourceChangedCount: 0 + onSourceChanged: sourceChangedCount = sourceChangedCount + 1 + } + + function doSetInactive() { + loader.active = false; + } +} diff --git a/tests/auto/declarative/qsgloader/data/active.4.qml b/tests/auto/declarative/qsgloader/data/active.4.qml new file mode 100644 index 0000000..63fd46e --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/active.4.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 + +Item { + id: root + + Component { + id: inlineTestComponent + Item { + id: inlineTestItem + property int someProperty: 5 + } + } + + Loader { + id: loader + objectName: "loader" + sourceComponent: inlineTestComponent + + property int sourceComponentChangedCount: 0 + onSourceComponentChanged: sourceComponentChangedCount = sourceComponentChangedCount + 1 + } + + function doSetInactive() { + loader.active = false; + } +} diff --git a/tests/auto/declarative/qsgloader/data/active.5.qml b/tests/auto/declarative/qsgloader/data/active.5.qml new file mode 100644 index 0000000..903f458 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/active.5.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + source: "ActiveComponent.qml"; + + property int itemChangedCount: 0 + onItemChanged: itemChangedCount = itemChangedCount + 1 + } + + function doSetInactive() { + loader.active = false; + } +} diff --git a/tests/auto/declarative/qsgloader/data/active.6.qml b/tests/auto/declarative/qsgloader/data/active.6.qml new file mode 100644 index 0000000..f769a4e --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/active.6.qml @@ -0,0 +1,21 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + + property int activeChangedCount: 0 + onActiveChanged: activeChangedCount = activeChangedCount + 1 + } + + function doSetActive() { + loader.active = true; + } + + function doSetInactive() { + loader.active = false; + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.1.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.1.qml new file mode 100644 index 0000000..ae37179 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.1.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + id: root + property int initialValue: 0 + property int behaviorCount: 0 + + Loader { + id: loader + objectName: "loader" + + onLoaded: { + loader.item.canary = 1; // will trigger the behavior, setting behaviorCount -> 1 + } + } + + Component.onCompleted: { + loader.source = "InitialPropertyValuesComponent.qml"; + root.initialValue = loader.item.canary; // should be one, since onLoaded will have triggered by now + root.behaviorCount = loader.item.behaviorCount; // should be one, since onLoaded will have triggered by now + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.2.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.2.qml new file mode 100644 index 0000000..76c7bc2 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.2.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 + +Item { + id: root + property int initialValue: 0 + property int behaviorCount: 0 + + Loader { + id: loader + objectName: "loader" + onLoaded: { + root.initialValue = loader.item.canary; // should be two + root.behaviorCount = loader.item.behaviorCount; // should be zero + } + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", {"canary": 2}); + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.3.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.3.qml new file mode 100644 index 0000000..3b08e6e --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.3.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + id: root + property int initialValue: 0 + property int behaviorCount: 0 + + Loader { + id: loader + objectName: "loader" + active: false + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", {"canary": 3}); + root.initialValue = loader.item.canary; // error - item should not yet exist. + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.4.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.4.qml new file mode 100644 index 0000000..e831004 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.4.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + id: root + property int initialValue: 0 + property int behaviorCount: 0 + + Loader { + id: loader + objectName: "loader" + active: false + onLoaded: { + root.initialValue = loader.item.canary; // should be four + root.behaviorCount = loader.item.behaviorCount; // should be zero + } + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", {"canary": 4}); + loader.active = true + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.5.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.5.qml new file mode 100644 index 0000000..03ee599 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.5.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 + +Item { + id: root + property int initialValue: 0 + property int behaviorCount: 0 + + Loader { + id: loader + objectName: "loader" + onLoaded: { + root.initialValue = loader.item.canary; // should be zero, but no error + root.behaviorCount = loader.item.behaviorCount; // should be zero + } + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml"); + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.6.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.6.qml new file mode 100644 index 0000000..66452b5 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.6.qml @@ -0,0 +1,25 @@ +import QtQuick 2.0 + +Item { + id: root + property int initialValue: 0 + property int behaviorCount: 0 + + Loader { + id: loader + objectName: "loader" + onLoaded: { + root.initialValue = loader.item.canary; // should be six + root.behaviorCount = loader.item.behaviorCount; // should be zero + } + } + + Item { + id: child + property int bindable: 6 + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", {"canary": child.bindable}); + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.7.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.7.qml new file mode 100644 index 0000000..02349f7 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.7.qml @@ -0,0 +1,29 @@ +import QtQuick 2.0 + +Item { + id: root + property int loaderValue: 0 + property int createObjectValue: 0 + + Loader { + id: loader + objectName: "loader" + onLoaded: { + root.loaderValue = loader.item.canary; // should still be one + } + } + + Item { + id: child + property int bindable: 1; + } + + property InitialPropertyValuesComponent ipvc + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", {"canary": child.bindable}); + var dynComp = Qt.createComponent("InitialPropertyValuesComponent.qml"); + ipvc = dynComp.createObject(root, {"canary": child.bindable}); + child.bindable = 7; // won't cause re-evaluation, since not used in a binding. + root.createObjectValue = ipvc.canary; // should still be one + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.binding.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.binding.qml new file mode 100644 index 0000000..e0df50a --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.binding.qml @@ -0,0 +1,21 @@ +import QtQuick 2.0 + +Item { + id: root + + property InitialPropertyValuesComponent testInstance + testInstance: loader.item + + property int bindable: 1 + property int canaryValue: testInstance.canary + property int behaviorCount: testInstance.behaviorCount + + Loader { + id: loader + objectName: "loader" + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", {"canary": (function() { return root.bindable })}); + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.1.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.1.qml new file mode 100644 index 0000000..f324dbd --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.1.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", 3); // invalid initial properties object + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.2.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.2.qml new file mode 100644 index 0000000..89aba31 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.2.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + } + + Component.onCompleted: { + loader.setSource("NonexistentSourceComponent.qml", {"canary":3}); + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.3.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.3.qml new file mode 100644 index 0000000..c862007 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.3.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 + +Item { + id: root + + Loader { + id: loader + objectName: "loader" + } + + Component.onCompleted: { + loader.setSource("InvalidSourceComponent.qml", {"canary":3}); + } +} diff --git a/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.4.qml b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.4.qml new file mode 100644 index 0000000..9a80b21 --- /dev/null +++ b/tests/auto/declarative/qsgloader/data/initialPropertyValues.error.4.qml @@ -0,0 +1,15 @@ +import QtQuick 2.0 + +Item { + id: root + property int canary: loader.item.canary + + Loader { + id: loader + objectName: "loader" + } + + Component.onCompleted: { + loader.setSource("InitialPropertyValuesComponent.qml", 3); // invalid initial properties object + } +} diff --git a/tests/auto/declarative/qsgloader/tst_qsgloader.cpp b/tests/auto/declarative/qsgloader/tst_qsgloader.cpp index c7e01da..daf9e2a 100644 --- a/tests/auto/declarative/qsgloader/tst_qsgloader.cpp +++ b/tests/auto/declarative/qsgloader/tst_qsgloader.cpp @@ -41,6 +41,7 @@ #include #include + #include #include #include @@ -79,6 +80,12 @@ private slots: void networkRequestUrl(); void failNetworkRequest(); // void networkComponent(); + void active(); + void initialPropertyValues_data(); + void initialPropertyValues(); + void initialPropertyValuesBinding(); + void initialPropertyValuesError_data(); + void initialPropertyValuesError(); void deleteComponentCrash(); void nonItem(); @@ -465,6 +472,237 @@ void tst_QSGLoader::failNetworkRequest() delete loader; } +void tst_QSGLoader::active() +{ + // check that the item isn't instantiated until active is set to true + { + QDeclarativeComponent component(&engine, TEST_FILE("active.1.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + + QVERIFY(loader->active() == false); // set manually to false + QVERIFY(loader->item() == 0); + QMetaObject::invokeMethod(object, "doSetSourceComponent"); + QVERIFY(loader->item() == 0); + QMetaObject::invokeMethod(object, "doSetSource"); + QVERIFY(loader->item() == 0); + QMetaObject::invokeMethod(object, "doSetActive"); + QVERIFY(loader->item() != 0); + + delete object; + } + + // check that the status is Null if active is set to false + { + QDeclarativeComponent component(&engine, TEST_FILE("active.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + + QVERIFY(loader->active() == true); // active is true by default + QCOMPARE(loader->status(), QSGLoader::Ready); + int currStatusChangedCount = loader->property("statusChangedCount").toInt(); + QMetaObject::invokeMethod(object, "doSetInactive"); + QCOMPARE(loader->status(), QSGLoader::Null); + QCOMPARE(loader->property("statusChangedCount").toInt(), (currStatusChangedCount+1)); + + delete object; + } + + // check that the source is not cleared if active is set to false + { + QDeclarativeComponent component(&engine, TEST_FILE("active.3.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + + QVERIFY(loader->active() == true); // active is true by default + QVERIFY(!loader->source().isEmpty()); + int currSourceChangedCount = loader->property("sourceChangedCount").toInt(); + QMetaObject::invokeMethod(object, "doSetInactive"); + QVERIFY(!loader->source().isEmpty()); + QCOMPARE(loader->property("sourceChangedCount").toInt(), currSourceChangedCount); + + delete object; + } + + // check that the sourceComponent is not cleared if active is set to false + { + QDeclarativeComponent component(&engine, TEST_FILE("active.4.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + + QVERIFY(loader->active() == true); // active is true by default + QVERIFY(loader->sourceComponent() != 0); + int currSourceComponentChangedCount = loader->property("sourceComponentChangedCount").toInt(); + QMetaObject::invokeMethod(object, "doSetInactive"); + QVERIFY(loader->sourceComponent() != 0); + QCOMPARE(loader->property("sourceComponentChangedCount").toInt(), currSourceComponentChangedCount); + + delete object; + } + + // check that the item is released if active is set to false + { + QDeclarativeComponent component(&engine, TEST_FILE("active.5.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + + QVERIFY(loader->active() == true); // active is true by default + QVERIFY(loader->item() != 0); + int currItemChangedCount = loader->property("itemChangedCount").toInt(); + QMetaObject::invokeMethod(object, "doSetInactive"); + QVERIFY(loader->item() == 0); + QCOMPARE(loader->property("itemChangedCount").toInt(), (currItemChangedCount+1)); + + delete object; + } + + // check that the activeChanged signal is emitted correctly + { + QDeclarativeComponent component(&engine, TEST_FILE("active.6.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + + QVERIFY(loader->active() == true); // active is true by default + loader->setActive(true); // no effect + QCOMPARE(loader->property("activeChangedCount").toInt(), 0); + loader->setActive(false); // change signal should be emitted + QCOMPARE(loader->property("activeChangedCount").toInt(), 1); + loader->setActive(false); // no effect + QCOMPARE(loader->property("activeChangedCount").toInt(), 1); + loader->setActive(true); // change signal should be emitted + QCOMPARE(loader->property("activeChangedCount").toInt(), 2); + loader->setActive(false); // change signal should be emitted + QCOMPARE(loader->property("activeChangedCount").toInt(), 3); + QMetaObject::invokeMethod(object, "doSetActive"); + QCOMPARE(loader->property("activeChangedCount").toInt(), 4); + QMetaObject::invokeMethod(object, "doSetActive"); + QCOMPARE(loader->property("activeChangedCount").toInt(), 4); + QMetaObject::invokeMethod(object, "doSetInactive"); + QCOMPARE(loader->property("activeChangedCount").toInt(), 5); + loader->setActive(true); // change signal should be emitted + QCOMPARE(loader->property("activeChangedCount").toInt(), 6); + + delete object; + } +} + +void tst_QSGLoader::initialPropertyValues_data() +{ + QTest::addColumn("qmlFile"); + QTest::addColumn("expectedWarnings"); + QTest::addColumn("propertyNames"); + QTest::addColumn("propertyValues"); + + QTest::newRow("source url with value set in onLoaded, initially active = true") << TEST_FILE("initialPropertyValues.1.qml") + << QStringList() + << (QStringList() << "initialValue" << "behaviorCount") + << (QVariantList() << 1 << 1); + + QTest::newRow("set source with initial property values specified, active = true") << TEST_FILE("initialPropertyValues.2.qml") + << QStringList() + << (QStringList() << "initialValue" << "behaviorCount") + << (QVariantList() << 2 << 0); + + QTest::newRow("set source with initial property values specified, active = false") << TEST_FILE("initialPropertyValues.3.qml") + << (QStringList() << QString(QLatin1String("file://") + TEST_FILE("initialPropertyValues.3.qml").toLocalFile() + QLatin1String(":16: TypeError: Cannot read property 'canary' of null"))) + << (QStringList()) + << (QVariantList()); + + QTest::newRow("set source with initial property values specified, active = false, with active set true later") << TEST_FILE("initialPropertyValues.4.qml") + << QStringList() + << (QStringList() << "initialValue" << "behaviorCount") + << (QVariantList() << 4 << 0); + + QTest::newRow("set source without initial property values specified, active = true") << TEST_FILE("initialPropertyValues.5.qml") + << QStringList() + << (QStringList() << "initialValue" << "behaviorCount") + << (QVariantList() << 0 << 0); + + QTest::newRow("set source with initial property values specified with binding, active = true") << TEST_FILE("initialPropertyValues.6.qml") + << QStringList() + << (QStringList() << "initialValue" << "behaviorCount") + << (QVariantList() << 6 << 0); + + QTest::newRow("ensure initial property value semantics mimic createObject") << TEST_FILE("initialPropertyValues.7.qml") + << QStringList() + << (QStringList() << "loaderValue" << "createObjectValue") + << (QVariantList() << 1 << 1); +} + +void tst_QSGLoader::initialPropertyValues() +{ + QFETCH(QUrl, qmlFile); + QFETCH(QStringList, expectedWarnings); + QFETCH(QStringList, propertyNames); + QFETCH(QVariantList, propertyValues); + + foreach (const QString &warning, expectedWarnings) + QTest::ignoreMessage(QtWarningMsg, warning.toAscii().constData()); + + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + + for (int i = 0; i < propertyNames.size(); ++i) + QCOMPARE(object->property(propertyNames.at(i).toAscii().constData()), propertyValues.at(i)); + + delete object; +} + +void tst_QSGLoader::initialPropertyValuesBinding() +{ + QDeclarativeComponent component(&engine, TEST_FILE("initialPropertyValues.binding.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QVERIFY(object->setProperty("bindable", QVariant(8))); + QCOMPARE(object->property("canaryValue").toInt(), 8); + + delete object; +} + +void tst_QSGLoader::initialPropertyValuesError_data() +{ + QTest::addColumn("qmlFile"); + QTest::addColumn("expectedWarnings"); + + QTest::newRow("invalid initial property values object") << TEST_FILE("initialPropertyValues.error.1.qml") + << (QStringList() << QString(TEST_FILE("initialPropertyValues.error.1.qml").toString() + ":6:5: QML Loader: setSource: value is not an object")); + + QTest::newRow("nonexistent source url") << TEST_FILE("initialPropertyValues.error.2.qml") + << (QStringList() << QString(TEST_FILE("NonexistentSourceComponent.qml").toString() + ": File not found")); + + QTest::newRow("invalid source url") << TEST_FILE("initialPropertyValues.error.3.qml") + << (QStringList() << QString(TEST_FILE("InvalidSourceComponent.qml").toString() + ":5:1: Syntax error")); + + QTest::newRow("invalid initial property values object with invalid property access") << TEST_FILE("initialPropertyValues.error.4.qml") + << (QStringList() << QString(TEST_FILE("initialPropertyValues.error.4.qml").toString() + ":7:5: QML Loader: setSource: value is not an object") + << QString(TEST_FILE("initialPropertyValues.error.4.qml").toString() + ":5: TypeError: Cannot read property 'canary' of null")); +} + +void tst_QSGLoader::initialPropertyValuesError() +{ + QFETCH(QUrl, qmlFile); + QFETCH(QStringList, expectedWarnings); + + foreach (const QString &warning, expectedWarnings) + QTest::ignoreMessage(QtWarningMsg, warning.toUtf8().constData()); + + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + QSGLoader *loader = object->findChild("loader"); + QVERIFY(loader != 0); + QVERIFY(loader->item() == 0); + delete object; +} + // QTBUG-9241 void tst_QSGLoader::deleteComponentCrash() {