Allow view items to be preserved.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Wed, 28 Sep 2011 23:58:42 +0000 (09:58 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 12 Oct 2011 00:36:56 +0000 (02:36 +0200)
Do not destroy items that are members of the VisualDataModel
persistedItems group when they are released by the view.

The create function on VisualDataGroup will return a reference to
an instantiated item within that group, any item returned by this
function is automatically added to the persistedItems group and can
be released by removing it from the group.  Uninstantiated items added
to the persistedItems group by any other means are not instantiated
until requested by a view or create.

Task-number: QTBUG-21518
Task-number: QTBUG-20854

Change-Id: I59554711208504c8f20a3ebe783bddab9b21a558
Reviewed-on: http://codereview.qt-project.org/5831
Sanity-Review: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Martin Jones <martin.jones@nokia.com>
examples/declarative/modelviews/visualdatamodel/slideshow.qml [new file with mode: 0644]
src/declarative/items/qsgvisualdatamodel.cpp
src/declarative/items/qsgvisualdatamodel_p.h
src/declarative/util/qdeclarativelistcompositor.cpp
src/declarative/util/qdeclarativelistcompositor_p.h
tests/auto/declarative/qsgvisualdatamodel/data/create.qml [new file with mode: 0644]
tests/auto/declarative/qsgvisualdatamodel/tst_qsgvisualdatamodel.cpp

diff --git a/examples/declarative/modelviews/visualdatamodel/slideshow.qml b/examples/declarative/modelviews/visualdatamodel/slideshow.qml
new file mode 100644 (file)
index 0000000..edf1f0f
--- /dev/null
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+
+    property Item displayItem: null
+
+    width: 300; height: 400
+
+    color: "black"
+
+    VisualDataModel {
+        id: visualModel
+
+        model: XmlListModel {
+            source: "http://api.flickr.com/services/feeds/photos_public.gne?format=rss2"
+            query: "/rss/channel/item"
+            namespaceDeclarations: "declare namespace media=\"http://search.yahoo.com/mrss/\";"
+
+            XmlRole { name: "imagePath"; query: "media:thumbnail/@url/string()" }
+            XmlRole { name: "url"; query: "media:content/@url/string()" }
+        }
+
+        delegate: Item {
+            id: delegateItem
+
+            width: 76; height: 76
+
+            Rectangle {
+                id: image
+                x: 0; y: 0; width: 76; height: 76
+                border.width: 1
+                border.color: "white"
+                color: "black"
+
+                Image {
+                    anchors.fill: parent
+                    anchors.leftMargin: 1
+                    anchors.topMargin: 1
+
+                    source: imagePath
+                    fillMode: Image.PreserveAspectFit
+
+                }
+
+                MouseArea {
+                    id: clickArea
+                    anchors.fill: parent
+
+                    onClicked: root.displayItem = root.displayItem !== delegateItem ? delegateItem : null
+                }
+
+                states: [
+                    State {
+                        when: root.displayItem === delegateItem
+                        name: "inDisplay";
+                        ParentChange { target: image; parent: imageContainer; x: 75; y: 75; width: 150; height: 150 }
+                        PropertyChanges { target: image; z: 2 }
+                        PropertyChanges { target: delegateItem; VisualDataModel.inItems: false }
+                    },
+                    State {
+                        when: root.displayItem !== delegateItem
+                        name: "inList";
+                        ParentChange { target: image; parent: delegateItem; x: 2; y: 2; width: 75; height: 75 }
+                        PropertyChanges { target: image; z: 1 }
+                        PropertyChanges { target: delegateItem; VisualDataModel.inItems: true }
+                    }
+                ]
+
+                transitions: [
+                    Transition {
+                        from: "inList"
+                        SequentialAnimation {
+                            PropertyAction { target: delegateItem; property: "VisualDataModel.inPersistedItems"; value: true }
+                            ParentAnimation {
+                                target: image;
+                                via: root
+                                NumberAnimation { target: image; properties: "x,y,width,height"; duration: 1000 }
+                            }
+                        }
+                    }, Transition {
+                        from: "inDisplay"
+                        SequentialAnimation {
+                            ParentAnimation {
+                                target: image
+                                NumberAnimation { target: image; properties: "x,y,width,height"; duration: 1000 }
+                            }
+                            PropertyAction { target: delegateItem; property: "VisualDataModel.inPersistedItems"; value: false }
+                        }
+                    }
+                ]
+            }
+        }
+    }
+
+
+    PathView {
+        id: imagePath
+
+        anchors { left: parent.left; top: imageContainer.bottom; right: parent.right; bottom: parent.bottom }
+        model: visualModel
+
+        pathItemCount: 7
+        path: Path {
+            startX: -50; startY: 0
+            PathQuad { x: 150; y: 50; controlX: 0; controlY: 50 }
+            PathQuad { x: 350; y: 0; controlX: 300; controlY: 50 }
+        }
+    }
+
+    Item {
+        id: imageContainer
+        anchors { fill: parent; bottomMargin: 100 }
+    }
+}
index e0d353a..47edc5d 100644 (file)
@@ -131,14 +131,14 @@ public:
     void init();
     void connectModel(QSGVisualAdaptorModel *model);
 
-    QObject *object(Compositor::Group group, int index, bool complete);
-    QSGItem *item(Compositor::Group group, int index, bool complete);
+    QObject *object(Compositor::Group group, int index, bool complete, bool reference);
     void destroy(QObject *object);
     QSGVisualDataModel::ReleaseFlags release(QObject *object);
     QString stringValue(Compositor::Group group, int index, const QString &name);
     int cacheIndexOf(QObject *object) const;
     void emitCreatedPackage(Compositor::iterator at, QDeclarativePackage *package);
-    void emitCreatedItem(int index, QSGItem *item) { emit q_func()->createdItem(index, item); }
+    void emitCreatedItem(Compositor::iterator at, QSGItem *item) {
+        emit q_func()->createdItem(at.index[m_compositorGroup], item); }
     void emitDestroyingPackage(QDeclarativePackage *package);
     void emitDestroyingItem(QSGItem *item) { emit q_func()->destroyingItem(item); }
 
@@ -194,6 +194,7 @@ public:
         struct {
             QSGVisualDataGroup *m_cacheItems;
             QSGVisualDataGroup *m_items;
+            QSGVisualDataGroup *m_persistedItems;
         };
         QSGVisualDataGroup *m_groups[Compositor::MaximumGroupCount];
     };
@@ -341,9 +342,10 @@ public:
     }
 
     void referenceObject() { ++objectRef; }
-    bool releaseObject() { return --objectRef == 0; }
+    bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); }
+    bool isObjectReferenced() const { return objectRef == 0 && !(groups & Compositor::PersistedFlag); }
 
-    bool isReferenced() const { return objectRef || scriptRef; }
+    bool isReferenced() const { return objectRef || scriptRef || (groups & Compositor::PersistedFlag); }
 
     void Dispose();
 
@@ -409,7 +411,7 @@ QSGVisualDataModelPrivate::QSGVisualDataModelPrivate(QDeclarativeContext *ctxt)
     , m_filterGroup(QStringLiteral("items"))
     , m_cacheItems(0)
     , m_items(0)
-    , m_groupCount(2)
+    , m_groupCount(3)
 {
 }
 
@@ -427,11 +429,14 @@ void QSGVisualDataModelPrivate::connectModel(QSGVisualAdaptorModel *model)
 void QSGVisualDataModelPrivate::init()
 {
     Q_Q(QSGVisualDataModel);
+    m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag);
+
     m_adaptorModel = new QSGVisualAdaptorModel;
     QObject::connect(m_adaptorModel, SIGNAL(rootIndexChanged()), q, SIGNAL(rootIndexChanged()));
 
     m_items = new QSGVisualDataGroup(QStringLiteral("items"), q, Compositor::Default, q);
     m_items->setDefaultInclude(true);
+    m_persistedItems = new QSGVisualDataGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q);
     QSGVisualDataGroupPrivate::get(m_items)->emitters.insert(this);
 }
 
@@ -477,9 +482,12 @@ void QSGVisualDataModel::componentComplete()
     int defaultGroups = 0;
     QStringList groupNames;
     groupNames.append(QStringLiteral("items"));
+    groupNames.append(QStringLiteral("persistedItems"));
     if (QSGVisualDataGroupPrivate::get(d->m_items)->defaultInclude)
         defaultGroups |= Compositor::DefaultFlag;
-    for (int i = 2; i < d->m_groupCount; ++i) {
+    if (QSGVisualDataGroupPrivate::get(d->m_persistedItems)->defaultInclude)
+        defaultGroups |= Compositor::PersistedFlag;
+    for (int i = 3; i < d->m_groupCount; ++i) {
         QString name = d->m_groups[i]->name();
         if (name.isEmpty()) {
             d->m_groups[i] = d->m_groups[d->m_groupCount - 1];
@@ -683,6 +691,16 @@ int QSGVisualDataModel::count() const
     return d->m_compositor.count(d->m_compositorGroup);
 }
 
+void QSGVisualDataModelPrivate::destroy(QObject *object)
+{
+    QObjectPrivate *p = QObjectPrivate::get(object);
+    Q_ASSERT(p->declarativeData);
+    QDeclarativeData *data = static_cast<QDeclarativeData*>(p->declarativeData);
+    if (data->ownContext && data->context)
+        data->context->clearContext();
+    object->deleteLater();
+}
+
 QSGVisualDataModel::ReleaseFlags QSGVisualDataModelPrivate::release(QObject *object)
 {
     QSGVisualDataModel::ReleaseFlags stat = 0;
@@ -693,12 +711,7 @@ QSGVisualDataModel::ReleaseFlags QSGVisualDataModelPrivate::release(QObject *obj
     if (cacheIndex != -1) {
         QSGVisualDataModelCacheItem *cacheItem = m_cache.at(cacheIndex);
         if (cacheItem->releaseObject()) {
-            QObjectPrivate *p = QObjectPrivate::get(object);
-            Q_ASSERT(p->declarativeData);
-            QDeclarativeData *data = static_cast<QDeclarativeData*>(p->declarativeData);
-            if (data->ownContext && data->context)
-                data->context->clearContext();
-            object->deleteLater();
+            destroy(object);
             cacheItem->object = 0;
             stat |= QSGVisualModel::Destroyed;
             if (!cacheItem->isReferenced()) {
@@ -733,7 +746,7 @@ void QSGVisualDataModelPrivate::group_append(
     QSGVisualDataModelPrivate *d = static_cast<QSGVisualDataModelPrivate *>(property->data);
     if (d->m_complete)
         return;
-    if (d->m_groupCount == 10) {
+    if (d->m_groupCount == 11) {
         qmlInfo(d->q_func()) << QSGVisualDataModel::tr("The maximum number of supported VisualDataGroups is 8");
         return;
     }
@@ -799,6 +812,29 @@ QSGVisualDataGroup *QSGVisualDataModel::items()
 }
 
 /*!
+    \qmlproperty VisualDataGroup QtQuick2::VisualDataModel::persistedItems
+
+    This property holds visual data model's persisted items group.
+
+    Items in this group are not destroyed when released by a view, instead they are persisted
+    until removed from the group.
+
+    An item can be removed from the persistedItems group by setting the
+    VisualDataModel.inPersistedItems property to false.  If the item is not referenced by a view
+    at that time it will be destroyed.  Adding an item to this group will not create a new
+    instance.
+
+    Items returned by the \l QtQuick2::VisualDataGroup::create() function are automatically added
+    to this group.
+*/
+
+QSGVisualDataGroup *QSGVisualDataModel::persistedItems()
+{
+    Q_D(QSGVisualDataModel);
+    return d->m_persistedItems;
+}
+
+/*!
     \qmlproperty string QtQuick2::VisualDataModel::filterOnGroup
 
     This property holds the name of the group used to filter the visual data model.
@@ -918,13 +954,9 @@ void QSGVisualDataModelPrivate::emitDestroyingPackage(QDeclarativePackage *packa
         QSGVisualDataGroupPrivate::get(m_groups[i])->destroyingPackage(package);
 }
 
-QObject *QSGVisualDataModelPrivate::object(Compositor::Group group, int index, bool complete)
+QObject *QSGVisualDataModelPrivate::object(Compositor::Group group, int index, bool complete, bool reference)
 {
     Q_Q(QSGVisualDataModel);
-    if (!m_delegate || index < 0 || index >= m_compositor.count(group)) {
-        qWarning() << "VisualDataModel::item: index out range" << index << m_compositor.count(group);
-        return 0;
-    }
 
     Compositor::iterator it = m_compositor.find(group, index);
     QSGVisualDataModelCacheItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0;
@@ -970,8 +1002,12 @@ QObject *QSGVisualDataModelPrivate::object(Compositor::Group group, int index, b
             new QSGVisualDataModelAttachedMetaObject(cacheItem->attached, m_cacheMetaType);
             cacheItem->attached->emitChanges();
 
-            if (QDeclarativePackage *package = qobject_cast<QDeclarativePackage *>(cacheItem->object))
+            if (QDeclarativePackage *package = qobject_cast<QDeclarativePackage *>(cacheItem->object)) {
                 emitCreatedPackage(it, package);
+            } else if (!reference) {
+                if (QSGItem *item = qobject_cast<QSGItem *>(cacheItem->object))
+                    emitCreatedItem(it, item);
+            }
 
             m_completePending = !complete;
             if (complete)
@@ -987,14 +1023,20 @@ QObject *QSGVisualDataModelPrivate::object(Compositor::Group group, int index, b
 
     if (index == m_compositor.count(group) - 1 && m_adaptorModel->canFetchMore())
         QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
-    cacheItem->referenceObject();
+    if (reference)
+        cacheItem->referenceObject();
     return cacheItem->object;
 }
 
 QSGItem *QSGVisualDataModel::item(int index, bool complete)
 {
     Q_D(QSGVisualDataModel);
-    QObject *object = d->object(d->m_compositorGroup, index, complete);
+    if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) {
+        qWarning() << "VisualDataModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup);
+        return 0;
+    }
+
+    QObject *object = d->object(d->m_compositorGroup, index, complete, true);
     if (QSGItem *item = qobject_cast<QSGItem *>(object))
         return item;
     if (d->m_completePending)
@@ -1251,6 +1293,14 @@ void QSGVisualDataModelPrivate::itemsRemoved(
         } else {
             for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) {
                 QSGVisualDataModelCacheItem *cacheItem = m_cache.at(cacheIndex);
+                if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0) {
+                    destroy(cacheItem->object);
+                    if (QDeclarativePackage *package = qobject_cast<QDeclarativePackage *>(cacheItem->object))
+                        emitDestroyingPackage(package);
+                    else if (QSGItem *item = qobject_cast<QSGItem *>(cacheItem->object))
+                        emitDestroyingItem(item);
+                    cacheItem->object = 0;
+                }
                 if (remove.groups() == cacheItem->groups && !cacheItem->isReferenced()) {
                     m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag);
                     m_cache.removeAt(cacheIndex);
@@ -1742,6 +1792,26 @@ void QSGVisualDataModelAttached::setGroups(const QStringList &groups)
     It is attached to each instance of the delegate.
 */
 
+/*!
+    \qmlattachedproperty int QtQuick2::VisualDataModel::inPersistedItems
+
+    This attached property holds whether the item belongs to the \l persistedItems VisualDataGroup.
+
+    Changing this property will add or remove the item from the items group.  Change with caution
+    as removing an item from the persistedItems group will destroy the current instance if it is
+    not referenced by a model.
+
+    It is attached to each instance of the delegate.
+*/
+
+/*!
+    \qmlattachedproperty int QtQuick2::VisualDataModel::persistedItemsIndex
+
+    This attached property holds the index of the item in the \l persistedItems VisualDataGroup.
+
+    It is attached to each instance of the delegate.
+*/
+
 void QSGVisualDataModelAttached::emitChanges()
 {
     if (m_modelChanged) {
@@ -1925,9 +1995,9 @@ void QSGVisualDataGroup::setDefaultInclude(bool include)
     a delegate
     \o \b groups A list the of names of groups the item is a member of.  This property can be
     written to change the item's membership.
-    \o \b inItems Whether the item belongs to the \l {VisualDataModel::items}{items} group.
+    \o \b inItems Whether the item belongs to the \l {QtQuick2::VisualDataModel::items}{items} group.
     Writing to this property will add or remove the item from the group.
-    \o \b itemsIndex The index of the item within the \l {VisualDataModel::items}{items} group.
+    \o \b itemsIndex The index of the item within the \l {QtQuick2::VisualDataModel::items}{items} group.
     \o \b {in\i{GroupName}} Whether the item belongs to the dynamic group \i groupName.  Writing to
     this property will add or remove the item from the group.
     \o \b {\i{groupName}Index} The index of the item within the dynamic group \i groupName.
@@ -1969,6 +2039,33 @@ QDeclarativeV8Handle QSGVisualDataGroup::get(int index)
 }
 
 /*!
+    \qmlmethod QtQuick2::VisualDataGroup::create(int index)
+
+    Returns a reference to the instantiated item at \a index in the group.
+
+    All items returned by create are added to the persistedItems group.  Items in this
+    group remain instantiated when not referenced by any view.
+*/
+
+QObject *QSGVisualDataGroup::create(int index)
+{
+    Q_D(QSGVisualDataGroup);
+    if (!d->model)
+        return 0;
+
+    QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(d->model);
+    if (index < 0 || index >= model->m_compositor.count(d->group)) {
+        qmlInfo(this) << tr("create: index out of range");
+        return 0;
+    }
+
+    QObject *object = model->object(d->group, index, true, false);
+    if (object)
+        model->addGroups(d->group, index, 1, Compositor::PersistedFlag);
+    return object;
+}
+
+/*!
     \qmlmethod QtQuick2::VisualDataGroup::remove(int index, int count)
 
     Removes \a count items starting at \a index from the group.
@@ -2332,7 +2429,12 @@ QSGItem *QSGVisualPartsModel::item(int index, bool complete)
 {
     QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(m_model);
 
-    QObject *object = model->object(m_compositorGroup, index, complete);
+    if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) {
+        qWarning() << "VisualDataModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup);
+        return 0;
+    }
+
+    QObject *object = model->object(m_compositorGroup, index, complete, true);
 
     if (QDeclarativePackage *package = qobject_cast<QDeclarativePackage *>(object)) {
         QObject *part = package->part(m_part);
index 9b4542d..896c51c 100644 (file)
@@ -79,6 +79,7 @@ class Q_DECLARATIVE_EXPORT QSGVisualDataModel : public QSGVisualModel, public QD
     Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate)
     Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup)
     Q_PROPERTY(QSGVisualDataGroup *items READ items CONSTANT)
+    Q_PROPERTY(QSGVisualDataGroup *persistedItems READ persistedItems CONSTANT)
     Q_PROPERTY(QDeclarativeListProperty<QSGVisualDataGroup> groups READ groups CONSTANT)
     Q_PROPERTY(QObject *parts READ parts CONSTANT)
     Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged)
@@ -120,6 +121,7 @@ public:
     void resetFilterGroup();
 
     QSGVisualDataGroup *items();
+    QSGVisualDataGroup *persistedItems();
     QDeclarativeListProperty<QSGVisualDataGroup> groups();
     QObject *parts();
 
@@ -163,6 +165,7 @@ public:
     void setDefaultInclude(bool include);
 
     Q_INVOKABLE QDeclarativeV8Handle get(int index);
+    Q_INVOKABLE QObject *create(int index);
 
 public Q_SLOTS:
     void remove(QDeclarativeV8Function *);
index 7beefda..be0d543 100644 (file)
@@ -283,6 +283,7 @@ QDeclarativeListCompositor::QDeclarativeListCompositor()
     , m_cacheIt(m_end)
     , m_groupCount(2)
     , m_defaultFlags(PrependFlag | DefaultFlag)
+    , m_removeFlags(AppendFlag | PrependFlag | GroupMask)
 {
 }
 
@@ -896,7 +897,7 @@ void QDeclarativeListCompositor::listItemsRemoved(
                 const int offset = qMax(0, relativeIndex);
                 int removeCount = qMin(it->count, relativeIndex + removal->count) - offset;
                 it->count -= removeCount;
-                int removeFlags = it->flags & RemoveFlags;
+                int removeFlags = it->flags & m_removeFlags;
                 Remove translatedRemoval(it, removeCount, it->flags);
                 for (int i = 0; i < m_groupCount; ++i) {
                     if (it->inGroup(i))
index cd2d9b3..60df819 100644 (file)
@@ -65,23 +65,24 @@ QT_BEGIN_NAMESPACE
 class Q_AUTOTEST_EXPORT QDeclarativeListCompositor
 {
 public:
-    enum { MaximumGroupCount = 10 };
+    enum { MaximumGroupCount = 11 };
 
     enum Group
     {
         Cache   = 0,
-        Default = 1
+        Default = 1,
+        Persisted = 2
     };
 
     enum Flag
     {
         CacheFlag   = 0x000001,
         DefaultFlag = 0x000002,
+        PersistedFlag = 0x000004,
         GroupMask   = 0x00FFFE,
         PrependFlag = 0x100000,
         AppendFlag  = 0x200000,
-        MovedFlag   = 0x400000,
-        RemoveFlags = GroupMask | PrependFlag | AppendFlag
+        MovedFlag   = 0x400000
     };
 
     class Range
@@ -215,6 +216,7 @@ public:
     void setDefaultGroups(int groups) { m_defaultFlags = groups | PrependFlag; }
     void setDefaultGroup(Group group) { m_defaultFlags |= (1 << group); }
     void clearDefaultGroup(Group group) { m_defaultFlags &= ~(1 << group); }
+    void setRemoveGroups(int groups) { m_removeFlags = PrependFlag | AppendFlag | groups; }
     void setGroupCount(int count);
 
     int count(Group group) const;
@@ -272,6 +274,7 @@ private:
     iterator m_cacheIt;
     int m_groupCount;
     int m_defaultFlags;
+    int m_removeFlags;
 
     inline Range *insert(Range *before, void *list, int index, int count, int flags);
     inline Range *erase(Range *range);
diff --git a/tests/auto/declarative/qsgvisualdatamodel/data/create.qml b/tests/auto/declarative/qsgvisualdatamodel/data/create.qml
new file mode 100644 (file)
index 0000000..36ea3ba
--- /dev/null
@@ -0,0 +1,22 @@
+import QtQuick 2.0
+
+ListView {
+    width: 200
+    height: 200
+
+    model: VisualDataModel {
+        id: visualModel
+
+        model: myModel
+        delegate: Item {
+            id: delegate
+            objectName: "delegate"
+            width: 200
+            height: 20
+
+            property bool destroyed: false
+
+            Component.onDestruction: destroyed = true
+        }
+    }
+}
index 50e1b8f..63e40cd 100644 (file)
@@ -135,6 +135,7 @@ private slots:
     void move();
     void groups();
     void get();
+    void create();
 
 private:
     template <int N> void groups_verify(
@@ -1410,6 +1411,119 @@ void tst_qsgvisualdatamodel::get()
     }
 }
 
+void tst_qsgvisualdatamodel::create()
+{
+    QSGView view;
+
+    SingleRoleModel model;
+    model.list = QStringList()
+            << "one"
+            << "two"
+            << "three"
+            << "four"
+            << "five"
+            << "six"
+            << "seven"
+            << "eight"
+            << "nine"
+            << "ten"
+            << "eleven"
+            << "twelve"
+            << "thirteen"
+            << "fourteen"
+            << "fifteen"
+            << "sixteen"
+            << "seventeen"
+            << "eighteen"
+            << "nineteen"
+            << "twenty";
+
+    QDeclarativeContext *ctxt = view.rootContext();
+    ctxt->setContextProperty("myModel", &model);
+
+    view.setSource(QUrl::fromLocalFile(SRCDIR "/data/create.qml"));
+
+    QSGListView *listview = qobject_cast<QSGListView*>(view.rootObject());
+    QVERIFY(listview != 0);
+
+    QSGItem *contentItem = listview->contentItem();
+    QVERIFY(contentItem != 0);
+
+    QSGVisualDataModel *visualModel = qobject_cast<QSGVisualDataModel *>(qvariant_cast<QObject *>(listview->model()));
+    QVERIFY(visualModel);
+
+    QCOMPARE(listview->count(), 20);
+
+    QSGItem *delegate;
+
+    // Request an item instantiated by the view.
+    QVERIFY(findItem<QSGItem>(contentItem, "delegate", 1));
+    QVERIFY(delegate = qobject_cast<QSGItem *>(evaluate<QObject *>(visualModel, "items.create(1)")));
+    QCOMPARE(delegate, findItem<QSGItem>(contentItem, "delegate", 1));
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), true);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 1);
+
+    evaluate<void>(delegate, "VisualDataModel.inPersistedItems = false");
+    QCOMPARE(listview->count(), 20);
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), false);
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), false);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 0);
+
+    // Request an item not instantiated by the view.
+    QVERIFY(!findItem<QSGItem>(contentItem, "delegate", 15));
+    QVERIFY(delegate = qobject_cast<QSGItem *>(evaluate<QObject *>(visualModel, "items.create(15)")));
+    QCOMPARE(delegate, findItem<QSGItem>(contentItem, "delegate", 15));
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), true);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 1);
+
+    evaluate<void>(visualModel, "persistedItems.remove(0)");
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), true);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 0);
+
+    // Request an item not instantiated by the view, then scroll the view so it will request it.
+    QVERIFY(!findItem<QSGItem>(contentItem, "delegate", 16));
+    QVERIFY(delegate = qobject_cast<QSGItem *>(evaluate<QObject *>(visualModel, "items.create(16)")));
+    QCOMPARE(delegate, findItem<QSGItem>(contentItem, "delegate", 16));
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), true);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 1);
+
+    evaluate<void>(listview, "positionViewAtIndex(19, ListView.End)");
+    QCOMPARE(listview->count(), 20);
+    evaluate<void>(delegate, "VisualDataModel.groups = [\"items\"]");
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), false);
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), false);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 0);
+
+    // Request and release an item instantiated by the view, then scroll the view so it releases it.
+    QVERIFY(findItem<QSGItem>(contentItem, "delegate", 17));
+    QVERIFY(delegate = qobject_cast<QSGItem *>(evaluate<QObject *>(visualModel, "items.create(17)")));
+    QCOMPARE(delegate, findItem<QSGItem>(contentItem, "delegate", 17));
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), true);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 1);
+
+    evaluate<void>(visualModel, "items.removeGroups(17, \"persistedItems\")");
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), false);
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), false);
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 0);
+    evaluate<void>(listview, "positionViewAtIndex(1, ListView.Beginning)");
+    QCOMPARE(listview->count(), 20);
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), true);
+
+    // Adding an item to the persistedItems group won't instantiate it, but if later requested by
+    // the view it will be persisted.
+    evaluate<void>(visualModel, "items.addGroups(18, \"persistedItems\")");
+    QCOMPARE(evaluate<int>(visualModel, "persistedItems.count"), 1);
+    QVERIFY(!findItem<QSGItem>(contentItem, "delegate", 18));
+    evaluate<void>(listview, "positionViewAtIndex(19, ListView.End)");
+    QCOMPARE(listview->count(), 20);
+    QVERIFY(delegate = findItem<QSGItem>(contentItem, "delegate", 18));
+    QCOMPARE(evaluate<bool>(delegate, "VisualDataModel.inPersistedItems"), true);
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), false);
+    evaluate<void>(listview, "positionViewAtIndex(1, ListView.Beginning)");
+    QCOMPARE(listview->count(), 20);
+    QCOMPARE(evaluate<bool>(delegate, "destroyed"), false);
+}
+
 template<typename T>
 T *tst_qsgvisualdatamodel::findItem(QSGItem *parent, const QString &objectName, int index)
 {