Optimize listmodel and allow nested elements from worker script.
authorGlenn Watson <glenn.watson@nokia.com>
Wed, 26 Oct 2011 00:28:01 +0000 (10:28 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 26 Oct 2011 01:41:53 +0000 (03:41 +0200)
Added support for nested listmodels when used from a worker script
thread. Optimized the implementation of ListModel, especially the
performance of appending a large number of items. Added a batch
append mode (with an array of JS objects) to reduce the overhead
of calling from JS into native code for each append operation.

Task-number:QTBUG-21508
Change-Id: I07b381dc3e8200d92d6e0af458df8850d78b510f
Reviewed-by: Martin Jones <martin.jones@nokia.com>
14 files changed:
src/declarative/qml/v8/qv8qobjectwrapper_p.h
src/declarative/qml/v8/qv8variantwrapper_p.h
src/declarative/util/qdeclarativelistmodel.cpp
src/declarative/util/qdeclarativelistmodel_p.h
src/declarative/util/qdeclarativelistmodel_p_p.h
src/declarative/util/qdeclarativelistmodelworkeragent.cpp
src/declarative/util/qdeclarativelistmodelworkeragent_p.h
tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js [new file with mode: 0644]
tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js [new file with mode: 0644]
tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativelistmodel/data/workersync.js [new file with mode: 0644]
tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp

index be118a9..564510f 100644 (file)
@@ -83,7 +83,7 @@ public:
     v8::Handle<v8::Value> newQObject(QObject *object);
     bool isQObject(v8::Handle<v8::Object>);
     QObject *toQObject(v8::Handle<v8::Object>);
-    QObject *toQObject(QV8ObjectResource *);
+    static QObject *toQObject(QV8ObjectResource *);
 
     enum RevisionMode { IgnoreRevision, CheckRevision };
     inline v8::Handle<v8::Value> getProperty(QObject *, const QHashedV8String &, RevisionMode);
index 7e9924b..8bb0bcc 100644 (file)
@@ -72,8 +72,8 @@ public:
 
     v8::Local<v8::Object> newVariant(const QVariant &);
     bool isVariant(v8::Handle<v8::Value>);
-    QVariant toVariant(v8::Handle<v8::Object>);
-    QVariant toVariant(QV8ObjectResource *);
+    static QVariant toVariant(v8::Handle<v8::Object>);
+    static QVariant toVariant(QV8ObjectResource *);
     QVariant &variantValue(v8::Handle<v8::Value>);
 
 private:
index 4e67182..4459440 100644 (file)
@@ -59,1692 +59,1870 @@ Q_DECLARE_METATYPE(QListModelInterface *)
 
 QT_BEGIN_NAMESPACE
 
-QV8ListModelResource::QV8ListModelResource(FlatListModel *model, FlatNodeData *data, QV8Engine *engine)
-: QV8ObjectResource(engine), model(model), nodeData(data)
+// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
+enum { MIN_LISTMODEL_UID = 1024 };
+
+QAtomicInt ListModel::uidCounter(MIN_LISTMODEL_UID);
+
+const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
 {
-    if (nodeData) nodeData->addData(this);
+    QStringHash<Role *>::Node *node = roleHash.findNode(key);
+    if (node) {
+        const Role &r = *node->value;
+        if (type != r.type) {
+            qmlInfo(0) << "Can't assign to pre-existing role of different type " << r.name;
+        }
+        return r;
+    }
+
+    return createRole(key, type);
 }
 
-QV8ListModelResource::~QV8ListModelResource()
+const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type)
 {
-    if (nodeData) nodeData->removeData(this);
+    QHashedV8String hashedKey(key);
+    QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
+    if (node) {
+        const Role &r = *node->value;
+        if (type != r.type) {
+            qmlInfo(0) << "Can't assign to pre-existing role of different type " << r.name;
+        }
+        return r;
+    }
+
+    QString qkey;
+    qkey.resize(key->Length());
+    key->Write(reinterpret_cast<uint16_t*>(qkey.data()));
+
+    return createRole(qkey, type);
 }
 
-class QDeclarativeListModelV8Data : public QV8Engine::Deletable
+const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
 {
-public:
-    QDeclarativeListModelV8Data();
-    ~QDeclarativeListModelV8Data();
+    const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QDeclarativeGuard<QObject>) };
+    const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QObject *) };
 
-    v8::Persistent<v8::Function> constructor;
+    Role *r = new Role;
+    r->name = key;
+    r->type = type;
 
-    static v8::Local<v8::Object> create(QV8Engine *);
+    if (type == Role::List) {
+        r->subLayout = new ListLayout;
+    } else {
+        r->subLayout = 0;
+    }
 
-    static v8::Handle<v8::Value> Getter(v8::Local<v8::String> property, 
-                                        const v8::AccessorInfo &info);
-    static v8::Handle<v8::Value> Setter(v8::Local<v8::String> property, 
-                                        v8::Local<v8::Value> value,
-                                        const v8::AccessorInfo &info);
-};
+    int dataSize = dataSizes[type];
+    int dataAlignment = dataAlignments[type];
 
-v8::Local<v8::Object> QDeclarativeListModelV8Data::create(QV8Engine *engine)
-{
-    if (!engine->listModelData()) {
-        QDeclarativeListModelV8Data *d = new QDeclarativeListModelV8Data;
-        engine->setListModelData(d);
+    int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
+    if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
+        r->blockIndex = ++currentBlock;
+        r->blockOffset = 0;
+        currentBlockOffset = dataSize;
+    } else {
+        r->blockIndex = currentBlock;
+        r->blockOffset = dataOffset;
+        currentBlockOffset = dataOffset + dataSize;
     }
 
-    QDeclarativeListModelV8Data *d = (QDeclarativeListModelV8Data *)engine->listModelData();
-    return d->constructor->NewInstance();
+    int roleIndex = roles.count();
+    r->index = roleIndex;
+
+    roles.append(r);
+    roleHash.insert(key, r);
+
+    return *r;
 }
 
-QDeclarativeListModelV8Data::QDeclarativeListModelV8Data()
+ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
 {
-    v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
-    ft->InstanceTemplate()->SetNamedPropertyHandler(Getter, Setter);
-    ft->InstanceTemplate()->SetHasExternalResource(true);
-    constructor = qPersistentNew<v8::Function>(ft->GetFunction());
+    for (int i=0 ; i < other->roles.count() ; ++i) {
+        Role *role = new Role(other->roles[i]);
+        roles.append(role);
+        roleHash.insert(role->name, role);
+    }
+    currentBlockOffset = other->currentBlockOffset;
+    currentBlock = other->currentBlock;
 }
 
-QDeclarativeListModelV8Data::~QDeclarativeListModelV8Data()
+ListLayout::~ListLayout()
 {
-    qPersistentDispose(constructor);
+    for (int i=0 ; i < roles.count() ; ++i) {
+        delete roles[i];
+    }
 }
 
-v8::Handle<v8::Value> QDeclarativeListModelV8Data::Getter(v8::Local<v8::String> property, 
-                                                          const v8::AccessorInfo &info)
+void ListLayout::sync(ListLayout *src, ListLayout *target)
 {
-    QV8ListModelResource *r =  v8_resource_cast<QV8ListModelResource>(info.This());
-    if (!r)
-        return v8::Undefined();
-
-    if (!r->nodeData) // Item at this index has been deleted
-        return v8::Undefined();
-
-
-    int index = r->nodeData->index;
-    QString propName = r->engine->toString(property);
+    int roleOffset = target->roles.count();
+    int newRoleCount = src->roles.count() - roleOffset;
 
-    int role = r->model->m_strings.value(propName, -1);
-
-    if (role >= 0 && index >=0 ) {
-        const QHash<int, QVariant> &row = r->model->m_values[index];
-        return r->engine->fromVariant(row[role]);
+    for (int i=0 ; i < newRoleCount ; ++i) {
+        Role *role = new Role(src->roles[roleOffset + i]);
+        target->roles.append(role);
+        target->roleHash.insert(role->name, role);
     }
 
-    return v8::Undefined();
+    target->currentBlockOffset = src->currentBlockOffset;
+    target->currentBlock = src->currentBlock;
 }
 
-v8::Handle<v8::Value> QDeclarativeListModelV8Data::Setter(v8::Local<v8::String> property, 
-                                                          v8::Local<v8::Value> value,
-                                                          const v8::AccessorInfo &info)
+ListLayout::Role::Role(const Role *other)
 {
-    QV8ListModelResource *r =  v8_resource_cast<QV8ListModelResource>(info.This());
-    if (!r)
-        return v8::Undefined();
+    name = other->name;
+    type = other->type;
+    blockIndex = other->blockIndex;
+    blockOffset = other->blockOffset;
+    index = other->index;
+    if (other->subLayout)
+        subLayout = new ListLayout(other->subLayout);
+    else
+        subLayout = 0;
+}
 
-    if (!r->nodeData) // item at this index has been deleted
-        return value;
+ListLayout::Role::~Role()
+{
+    delete subLayout;
+}
 
+const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
+{
+    Role::DataType type;
 
-    if (!value->IsRegExp() && !value->IsDate() && value->IsObject() && !r->engine->isVariant(value)) {
-        qmlInfo(r->model->m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
-        return value;
+    switch (data.type()) {
+        case QVariant::Double:      type = Role::Number;      break;
+        case QVariant::Int:         type = Role::Number;      break;
+        case QVariant::UserType:    type = Role::List;        break;
+        case QVariant::Bool:        type = Role::Bool;        break;
+        case QVariant::String:      type = Role::String;      break;
+        default:                    type = Role::Invalid;     break;
     }
 
-    int index = r->nodeData->index;
-    QString propName = r->engine->toString(property);
-
-    int role = r->model->m_strings.value(propName, -1);
-    if (role >= 0 && index >= 0) {
-        QHash<int, QVariant> &row = r->model->m_values[index];
-        row[role] = r->engine->toVariant(value, -1);
-
-        QList<int> roles;
-        roles << role;
-        if (r->model->m_parentAgent) {
-            // This is the list in the worker thread, so tell the agent to
-            // emit itemsChanged() later
-            r->model->m_parentAgent->changedData(index, 1, roles);
-        } else {
-            // This is the list in the main thread, so emit itemsChanged()
-            emit r->model->m_listModel->itemsChanged(index, 1, roles);
-        }
+    if (type == Role::Invalid) {
+        qmlInfo(0) << "Can't create role for unsupported data type";
+        return 0;
     }
 
-    return value;
+    return &getRoleOrCreate(key, type);
 }
 
-template<typename T>
-void qdeclarativelistmodel_move(int from, int to, int n, T *items)
+const ListLayout::Role *ListLayout::getExistingRole(const QString &key)
 {
-    if (n == 1) {
-        items->move(from, to);
-    } else {
-        T replaced;
-        int i=0;
-        typename T::ConstIterator it=items->begin(); it += from+n;
-        for (; i<to-from; ++i,++it)
-            replaced.append(*it);
-        i=0;
-        it=items->begin(); it += from;
-        for (; i<n; ++i,++it)
-            replaced.append(*it);
-        typename T::ConstIterator f=replaced.begin();
-        typename T::Iterator t=items->begin(); t += from;
-        for (; f != replaced.end(); ++f, ++t)
-            *t = *f;
-    }
+    Role *r = 0;
+    QStringHash<Role *>::Node *node = roleHash.findNode(key);
+    if (node)
+        r = node->value;
+    return r;
 }
 
-QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListModelData::instructions() const
+const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key)
 {
-    return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
+    Role *r = 0;
+    QHashedV8String hashedKey(key);
+    QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
+    if (node)
+        r = node->value;
+    return r;
 }
 
-/*!
-    \qmlclass ListModel QDeclarativeListModel
-    \inqmlmodule QtQuick 2
-    \ingroup qml-working-with-data
-    \brief The ListModel element defines a free-form list data source.
-
-    The ListModel is a simple container of ListElement definitions, each containing data roles.
-    The contents can be defined dynamically, or explicitly in QML.
-
-    The number of elements in the model can be obtained from its \l count property.
-    A number of familiar methods are also provided to manipulate the contents of the
-    model, including append(), insert(), move(), remove() and set(). These methods
-    accept dictionaries as their arguments; these are translated to ListElement objects
-    by the model.
-
-    Elements can be manipulated via the model using the setProperty() method, which
-    allows the roles of the specified element to be set and changed.
-
-    \section1 Example Usage
-
-    The following example shows a ListModel containing three elements, with the roles
-    "name" and "cost".
+ModelObject *ListModel::getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex)
+{
+    ListElement *e = elements[elementIndex];
+    if (e->m_objectCache == 0) {
+        e->m_objectCache = new ModelObject(model, elementIndex);
+    }
+    return e->m_objectCache;
+}
 
-    \div {class="float-right"}
-    \inlineimage listmodel.png
-    \enddiv
+void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
+{
+    // Sanity check
+    target->m_uid = src->m_uid;
+    if (targetModelHash)
+        targetModelHash->insert(target->m_uid, target);
 
-    \snippet doc/src/snippets/declarative/listmodel.qml 0
+    // Build hash of elements <-> uid for each of the lists
+    QHash<int, ElementSync> elementHash;
+    for (int i=0 ; i < target->elements.count() ; ++i) {
+        ListElement *e = target->elements.at(i);
+        int uid = e->getUid();
+        ElementSync sync;
+        sync.target = e;
+        elementHash.insert(uid, sync);
+    }
+    for (int i=0 ; i < src->elements.count() ; ++i) {
+        ListElement *e = src->elements.at(i);
+        int uid = e->getUid();
+
+        QHash<int, ElementSync>::iterator it = elementHash.find(uid);
+        if (it == elementHash.end()) {
+            ElementSync sync;
+            sync.src = e;
+            elementHash.insert(uid, sync);
+        } else {
+            ElementSync &sync = it.value();
+            sync.src = e;
+        }
+    }
 
-    \clearfloat
-    Roles (properties) in each element must begin with a lower-case letter and
-    should be common to all elements in a model. The ListElement documentation
-    provides more guidelines for how elements should be defined.
+    // Get list of elements that are in the target but no longer in the source. These get deleted first.
+    QHash<int, ElementSync>::iterator it = elementHash.begin();
+    QHash<int, ElementSync>::iterator end = elementHash.end();
+    while (it != end) {
+        const ElementSync &s = it.value();
+        if (s.src == 0) {
+            s.target->destroy(target->m_layout);
+            target->elements.removeOne(s.target);
+            delete s.target;
+        }
+        ++it;
+    }
 
-    Since the example model contains an \c id property, it can be referenced
-    by views, such as the ListView in this example:
+    // Sync the layouts
+    ListLayout::sync(src->m_layout, target->m_layout);
+
+    // Clear the target list, and append in correct order from the source
+    target->elements.clear();
+    for (int i=0 ; i < src->elements.count() ; ++i) {
+        ListElement *srcElement = src->elements.at(i);
+        it = elementHash.find(srcElement->getUid());
+        const ElementSync &s = it.value();
+        ListElement *targetElement = s.target;
+        if (targetElement == 0) {
+            targetElement = new ListElement(srcElement->getUid());
+        }
+        ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
+        target->elements.append(targetElement);
+    }
 
-    \snippet doc/src/snippets/declarative/listmodel-simple.qml 0
-    \dots 8
-    \snippet doc/src/snippets/declarative/listmodel-simple.qml 1
+    target->updateCacheIndices();
 
-    It is possible for roles to contain list data.  In the following example we
-    create a list of fruit attributes:
+    // Update values stored in target meta objects
+    for (int i=0 ; i < target->elements.count() ; ++i) {
+        ListElement *e = target->elements[i];
+        if (e->m_objectCache)
+            e->m_objectCache->updateValues();
+    }
+}
 
-    \snippet doc/src/snippets/declarative/listmodel-nested.qml model
+int ListModel::allocateUid()
+{
+    return uidCounter.fetchAndAddOrdered(1);
+}
 
-    The delegate displays all the fruit attributes:
+ListModel::ListModel(ListLayout *layout, QDeclarativeListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
+{
+    if (uid == -1)
+        uid = allocateUid();
+    m_uid = uid;
+}
 
-    \div {class="float-right"}
-    \inlineimage listmodel-nested.png
-    \enddiv
+void ListModel::destroy()
+{
+    clear();
+    m_uid = -1;
+    m_layout = 0;
+    if (m_modelCache && m_modelCache->m_primary == false)
+        delete m_modelCache;
+    m_modelCache = 0;
+}
 
-    \snippet doc/src/snippets/declarative/listmodel-nested.qml delegate
+int ListModel::appendElement()
+{
+    int elementIndex = elements.count();
+    newElement(elementIndex);
+    return elementIndex;
+}
 
-    \clearfloat
-    \section1 Modifying List Models
+void ListModel::insertElement(int index)
+{
+    newElement(index);
+    updateCacheIndices();
+}
 
-    The content of a ListModel may be created and modified using the clear(),
-    append(), set(), insert() and setProperty() methods.  For example:
+void ListModel::move(int from, int to, int n)
+{
+    if (from > to) {
+        // Only move forwards - flip if backwards moving
+        int tfrom = from;
+        int tto = to;
+        from = tto;
+        to = tto+n;
+        n = tfrom-tto;
+    }
 
-    \snippet doc/src/snippets/declarative/listmodel-modify.qml delegate
+    QPODVector<ListElement *, 4> store;
+    for (int i=0 ; i < (to-from) ; ++i)
+        store.append(elements[from+n+i]);
+    for (int i=0 ; i < n ; ++i)
+        store.append(elements[from+i]);
+    for (int i=0 ; i < store.count() ; ++i)
+        elements[from+i] = store[i];
 
-    Note that when creating content dynamically the set of available properties
-    cannot be changed once set. Whatever properties are first added to the model
-    are the only permitted properties in the model.
+    updateCacheIndices();
+}
 
-    \section1 Using Threaded List Models with WorkerScript
+void ListModel::newElement(int index)
+{
+    ListElement *e = new ListElement;
+    elements.insert(index, e);
+}
 
-    ListModel can be used together with WorkerScript access a list model
-    from multiple threads. This is useful if list modifications are
-    synchronous and take some time: the list operations can be moved to a
-    different thread to avoid blocking of the main GUI thread.
+void ListModel::updateCacheIndices()
+{
+    for (int i=0 ; i < elements.count() ; ++i) {
+        ListElement *e = elements.at(i);
+        if (e->m_objectCache) {
+            e->m_objectCache->m_elementIndex = i;
+        }
+    }
+}
 
-    Here is an example that uses WorkerScript to periodically append the
-    current time to a list model:
+QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QDeclarativeListModel *owner, QV8Engine *eng)
+{
+    ListElement *e = elements[elementIndex];
+    const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
+    return e->getProperty(r, owner, eng);
+}
 
-    \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
+ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
+{
+    ListElement *e = elements[elementIndex];
+    return e->getListProperty(role);
+}
 
-    The included file, \tt dataloader.js, looks like this:
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles)
+{
+    ListElement *e = elements[elementIndex];
 
-    \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
+    v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
+    int propertyCount = propertyNames->Length();
 
-    The timer in the main example sends messages to the worker script by calling
-    \l WorkerScript::sendMessage(). When this message is received,
-    \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
-    which appends the current time to the list model.
+    for (int i=0 ; i < propertyCount ; ++i) {
+        v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
+        v8::Local<v8::Value> propertyValue = object->Get(propertyName);
 
-    Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
-    handler. You must call sync() or else the changes made to the list from the external
-    thread will not be reflected in the list model in the main thread.
+        // Check if this key exists yet
+        int roleIndex = -1;
 
-    \section1 Restrictions
+        // Add the value now
+        if (propertyValue->IsString()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
+            v8::Handle<v8::String> jsString = propertyValue->ToString();
+            QString qstr;
+            qstr.resize(jsString->Length());
+            jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+            roleIndex = e->setStringProperty(r, qstr);
+        } else if (propertyValue->IsNumber()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+            roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue());
+        } else if (propertyValue->IsArray()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
+            ListModel *subModel = new ListModel(r.subLayout, 0, -1);
 
-    If a list model is to be accessed from a WorkerScript, it cannot
-    contain list-type data. So, the following model cannot be used from a WorkerScript
-    because of the list contained in the "attributes" property:
+            v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
+            int arrayLength = subArray->Length();
+            for (int j=0 ; j < arrayLength ; ++j) {
+                v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+                subModel->append(subObject);
+            }
 
-    \code
-    ListModel {
-        id: fruitModel
-        ListElement {
-            name: "Apple"
-            cost: 2.45
-            attributes: [
-                ListElement { description: "Core" },
-                ListElement { description: "Deciduous" }
-            ]
+            roleIndex = e->setListProperty(r, subModel);
+        } else if (propertyValue->IsInt32()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+            roleIndex = e->setDoubleProperty(r, (double) propertyValue->Int32Value());
+        } else if (propertyValue->IsBoolean()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
+            roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue());
+        } else if (propertyValue->IsObject()) {
+            QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
+            if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
+                QObject *o = QV8QObjectWrapper::toQObject(r);
+                const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+                if (role.type == ListLayout::Role::QObject)
+                    e->setQObjectProperty(role, o);
+            }
+        } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
+            const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
+            if (r)
+                e->clearProperty(*r);
         }
-    }
-    \endcode
 
-    In addition, the WorkerScript cannot add list-type data to the model.
+        if (roleIndex != -1)
+            roles->append(roleIndex);
+    }
 
-    \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
-*/
+    if (e->m_objectCache) {
+        e->m_objectCache->updateValues(*roles);
+    }
+}
 
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object)
+{
+    ListElement *e = elements[elementIndex];
 
-/*
-    A ListModel internally uses either a NestedListModel or FlatListModel.
+    v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
+    int propertyCount = propertyNames->Length();
 
-    A NestedListModel can contain lists of ListElements (which
-    when retrieved from get() is accessible as a list model within the list
-    model) whereas a FlatListModel cannot.
+    for (int i=0 ; i < propertyCount ; ++i) {
+        v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
+        v8::Local<v8::Value> propertyValue = object->Get(propertyName);
 
-    ListModel uses a NestedListModel to begin with, and if the model is later 
-    used from a WorkerScript, it changes to use a FlatListModel instead. This
-    is because ModelNode (which abstracts the nested list model data) needs
-    access to the declarative engine and script engine, which cannot be
-    safely used from outside of the main thread.
-*/
+        // Add the value now
+        if (propertyValue->IsString()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
+            if (r.type == ListLayout::Role::String) {
+                v8::Handle<v8::String> jsString = propertyValue->ToString();
+                QString qstr;
+                qstr.resize(jsString->Length());
+                jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+                e->setStringPropertyFast(r, qstr);
+            }
+        } else if (propertyValue->IsNumber()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+            if (r.type == ListLayout::Role::Number) {
+                e->setDoublePropertyFast(r, propertyValue->NumberValue());
+            }
+        } else if (propertyValue->IsArray()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
+            if (r.type == ListLayout::Role::List) {
+                ListModel *subModel = new ListModel(r.subLayout, 0, -1);
+
+                v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
+                int arrayLength = subArray->Length();
+                for (int j=0 ; j < arrayLength ; ++j) {
+                    v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+                    subModel->append(subObject);
+                }
 
-QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
-: QListModelInterface(parent), m_agent(0), m_nested(new NestedListModel(this)), m_flat(0)
-{
+                e->setListPropertyFast(r, subModel);
+            }
+        } else if (propertyValue->IsInt32()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+            if (r.type == ListLayout::Role::Number) {
+                e->setDoublePropertyFast(r, (double) propertyValue->Int32Value());
+            }
+        } else if (propertyValue->IsBoolean()) {
+            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
+            if (r.type == ListLayout::Role::Bool) {
+                e->setBoolPropertyFast(r, propertyValue->BooleanValue());
+            }
+        } else if (propertyValue->IsObject()) {
+            QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
+            if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
+                QObject *o = QV8QObjectWrapper::toQObject(r);
+                const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+                if (r.type == ListLayout::Role::QObject)
+                    e->setQObjectPropertyFast(r, o);
+            }
+        } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
+            const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
+            if (r)
+                e->clearProperty(*r);
+        }
+    }
 }
 
-QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent)
-: QListModelInterface(parent), m_agent(0), m_nested(0), m_flat(0)
+void ListModel::clear()
 {
-    m_flat = new FlatListModel(this);
-    m_flat->m_parentAgent = parent;
-
-    if (orig->m_flat) {
-        m_flat->m_roles = orig->m_flat->m_roles;
-        m_flat->m_strings = orig->m_flat->m_strings;
-        m_flat->m_values = orig->m_flat->m_values;
-
-        m_flat->m_nodeData.reserve(m_flat->m_values.count());
-        for (int i=0; i<m_flat->m_values.count(); i++)
-            m_flat->m_nodeData << 0;
+    int elementCount = elements.count();
+    for (int i=0 ; i < elementCount ; ++i) {
+        elements[i]->destroy(m_layout);
+        delete elements[i];
     }
+    elements.clear();
 }
 
-QDeclarativeListModel::~QDeclarativeListModel()
+void ListModel::remove(int index)
 {
-    if (m_agent)
-        m_agent->release();
-
-    delete m_nested;
-    delete m_flat;
+    elements[index]->destroy(m_layout);
+    delete elements[index];
+    elements.remove(index);
+    updateCacheIndices();
 }
 
-bool QDeclarativeListModel::flatten()
+void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object)
 {
-    if (m_flat)
-        return true;
+    insertElement(elementIndex);
+    set(elementIndex, object);
+}
 
-    QList<int> roles = m_nested->roles();
+int ListModel::append(v8::Handle<v8::Object> object)
+{
+    int elementIndex = appendElement();
+    set(elementIndex, object);
+    return elementIndex;
+}
 
-    QList<QHash<int, QVariant> > values;
-    bool hasNested = false;
-    for (int i=0; i<m_nested->count(); i++) {
-        values.append(m_nested->data(i, roles, &hasNested));
-        if (hasNested)
-            return false;
-    }
+int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
+{
+    int roleIndex = -1;
 
-    FlatListModel *flat = new FlatListModel(this);
-    flat->m_values = values;
+    if (elementIndex >= 0 && elementIndex < elements.count()) {
+        ListElement *e = elements[elementIndex];
+        const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
+        if (r) {
+            roleIndex = e->setVariantProperty(*r, data);
 
-    for (int i=0; i<roles.count(); i++) {
-        QString s = m_nested->toString(roles[i]);
-        flat->m_roles.insert(roles[i], s);
-        flat->m_strings.insert(s, roles[i]);
+            if (roleIndex != -1 && e->m_objectCache) {
+                QList<int> roles;
+                roles << roleIndex;
+                e->m_objectCache->updateValues(roles);
+            }
+        }
     }
 
-    flat->m_nodeData.reserve(flat->m_values.count());
-    for (int i=0; i<flat->m_values.count(); i++)
-        flat->m_nodeData << 0;
-
-    m_flat = flat;
-    delete m_nested;
-    m_nested = 0;
-    return true;
+    return roleIndex;
 }
 
-bool QDeclarativeListModel::inWorkerThread() const
+int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data)
 {
-    return m_flat && m_flat->m_parentAgent;
+    int roleIndex = -1;
+
+    if (elementIndex >= 0 && elementIndex < elements.count()) {
+        ListElement *e = elements[elementIndex];
+        const ListLayout::Role *r = m_layout->getExistingRole(key);
+        if (r)
+            roleIndex = e->setJsProperty(*r, data);
+    }
+
+    return roleIndex;
 }
 
-QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
+inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
 {
-    if (m_agent)
-        return m_agent;
-
-    if (!flatten()) {
-        qmlInfo(this) << "List contains list-type data and cannot be used from a worker script";
-        return 0;
+    ListElement *e = this;
+    int blockIndex = 0;
+    while (blockIndex < role.blockIndex) {
+        if (e->next == 0) {
+            e->next = new ListElement;
+            e->next->uid = uid;
+        }
+        e = e->next;
+        ++blockIndex;
     }
 
-    m_agent = new QDeclarativeListModelWorkerAgent(this);
-    return m_agent;
+    char *mem = &e->data[role.blockOffset];
+    return mem;
 }
 
-QList<int> QDeclarativeListModel::roles() const
+QString *ListElement::getStringProperty(const ListLayout::Role &role)
 {
-    return m_flat ? m_flat->roles() : m_nested->roles();
+    char *mem = getPropertyMemory(role);
+    QString *s = reinterpret_cast<QString *>(mem);
+    return s->data_ptr() ? s : 0;
 }
 
-QString QDeclarativeListModel::toString(int role) const
+QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
 {
-    return m_flat ? m_flat->toString(role) : m_nested->toString(role);
+    char *mem = getPropertyMemory(role);
+    QDeclarativeGuard<QObject> *o = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+    return o->data();
 }
 
-QVariant QDeclarativeListModel::data(int index, int role) const
+QDeclarativeGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
 {
-    if (index >= count() || index < 0)
-        return QVariant();
-
-    return m_flat ? m_flat->data(index, role) : m_nested->data(index, role);
+    char *mem = getPropertyMemory(role);
+    QDeclarativeGuard<QObject> *o = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+    return o;
 }
 
-/*!
-    \qmlproperty int QtQuick2::ListModel::count
-    The number of data entries in the model.
-*/
-int QDeclarativeListModel::count() const
+ListModel *ListElement::getListProperty(const ListLayout::Role &role)
 {
-    return m_flat ? m_flat->count() : m_nested->count();
+    char *mem = getPropertyMemory(role);
+    ListModel **value = reinterpret_cast<ListModel **>(mem);
+    return *value;
 }
 
-/*!
-    \qmlmethod QtQuick2::ListModel::clear()
+QVariant ListElement::getProperty(const ListLayout::Role &role, const QDeclarativeListModel *owner, QV8Engine *eng)
+{
+    char *mem = getPropertyMemory(role);
 
-    Deletes all content from the model.
+    QVariant data;
 
-    \sa append() remove()
-*/
-void QDeclarativeListModel::clear()
-{
-    int cleared = count();
-    if (m_flat)
-        m_flat->clear();
-    else
-        m_nested->clear();
+    switch (role.type) {
+        case ListLayout::Role::Number:
+            {
+                double *value = reinterpret_cast<double *>(mem);
+                data = *value;
+            }
+            break;
+        case ListLayout::Role::String:
+            {
+                QString *value = reinterpret_cast<QString *>(mem);
+                if (value->data_ptr() != 0)
+                    data = *value;
+            }
+            break;
+        case ListLayout::Role::Bool:
+            {
+                bool *value = reinterpret_cast<bool *>(mem);
+                data = *value;
+            }
+            break;
+        case ListLayout::Role::List:
+            {
+                ListModel **value = reinterpret_cast<ListModel **>(mem);
+                ListModel *model = *value;
 
-    if (!inWorkerThread()) {
-        emit itemsRemoved(0, cleared);
-        emit countChanged();
+                if (model) {
+                    if (model->m_modelCache == 0) {
+                        model->m_modelCache = new QDeclarativeListModel(owner, model, eng);
+                        QDeclarativeEngine::setContextForObject(model->m_modelCache, QDeclarativeEngine::contextForObject(owner));
+                    }
+
+                    QObject *object = model->m_modelCache;
+                    data = QVariant::fromValue(object);
+                }
+            }
+            break;
+        case ListLayout::Role::QObject:
+            {
+                QDeclarativeGuard<QObject> *guard = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+                QObject *object = guard->data();
+                if (object)
+                    data = QVariant::fromValue(object);
+            }
+            break;
+        default:
+            break;
     }
+
+    return data;
 }
 
-QDeclarativeListModel *ModelNode::model(const NestedListModel *model)
+int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
 {
-    if (!modelCache) { 
-        modelCache = new QDeclarativeListModel;
-        QDeclarativeEngine::setContextForObject(modelCache,QDeclarativeEngine::contextForObject(model->m_listModel));
-        modelCache->m_nested->_root = this;  // ListModel defaults to nestable model
+    int roleIndex = -1;
 
-        for (int i=0; i<values.count(); ++i) {
-            ModelNode *subNode = qvariant_cast<ModelNode *>(values.at(i));
-            if (subNode)
-                subNode->m_model = modelCache->m_nested;
+    if (role.type == ListLayout::Role::String) {
+        char *mem = getPropertyMemory(role);
+        QString *c = reinterpret_cast<QString *>(mem);
+        bool changed;
+        if (c->data_ptr() == 0) {
+            new (mem) QString(s);
+            changed = true;
+        } else {
+            changed = c->compare(s) != 0;
+            *c = s;
         }
+        if (changed)
+            roleIndex = role.index;
+    } else {
+        qmlInfo(0) << "Unable to assign string to role '" << role.name << "' of different type.";
     }
-    return modelCache;
+
+    return roleIndex;
 }
 
-ModelObject *ModelNode::object(const NestedListModel *model)
+int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
 {
-    if (!objectCache) {
-        QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(qmlEngine(model->m_listModel));
-        objectCache = new ModelObject(this, const_cast<NestedListModel*>(model), ep->v8engine());
-
-        QHash<QString, ModelNode *>::iterator it;
-        for (it = properties.begin(); it != properties.end(); ++it) {
-            objectCache->setValue(it.key().toUtf8(), model->valueForNode(*it));
-        }
+    int roleIndex = -1;
 
-        objectCache->setNodeUpdatesEnabled(true);
+    if (role.type == ListLayout::Role::Number) {
+        char *mem = getPropertyMemory(role);
+        double *value = new (mem) double;
+        bool changed = *value != d;
+        *value = d;
+        if (changed)
+            roleIndex = role.index;
+    } else {
+        qmlInfo(0) << "Unable to assign number to role '" << role.name << "' of different type.";
     }
-    return objectCache;
+
+    return roleIndex;
 }
 
-/*!
-    \qmlmethod QtQuick2::ListModel::remove(int index)
+int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
+{
+    int roleIndex = -1;
 
-    Deletes the content at \a index from the model.
+    if (role.type == ListLayout::Role::Bool) {
+        char *mem = getPropertyMemory(role);
+        bool *value = new (mem) bool;
+        bool changed = *value != b;
+        *value = b;
+        if (changed)
+            roleIndex = role.index;
+    } else {
+        qmlInfo(0) << "Unable to assign bool to role '" << role.name << "' of different type.";
+    }
 
-    \sa clear()
-*/
-void QDeclarativeListModel::remove(int index)
+    return roleIndex;
+}
+
+int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
 {
-    if (index < 0 || index >= count()) {
-        qmlInfo(this) << tr("remove: index %1 out of range").arg(index);
-        return;
+    int roleIndex = -1;
+
+    if (role.type == ListLayout::Role::List) {
+        char *mem = getPropertyMemory(role);
+        ListModel **value = new (mem) ListModel *;
+        if (*value) {
+            (*value)->destroy();
+            delete *value;
+        }
+        *value = m;
+        roleIndex = role.index;
+    } else {
+        qmlInfo(0) << "Unable to assign list to role '" << role.name << "' of different type.";
     }
 
-    if (m_flat)
-        m_flat->remove(index);
-    else
-        m_nested->remove(index);
+    return roleIndex;
+}
 
-    if (!inWorkerThread()) {
-        emit itemsRemoved(index, 1);
-        emit countChanged();
+int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
+{
+    int roleIndex = -1;
+
+    if (role.type == ListLayout::Role::QObject) {
+        char *mem = getPropertyMemory(role);
+        QDeclarativeGuard<QObject> *g = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+        bool changed = g->data() != o;
+        g->~QDeclarativeGuard();
+        new (mem) QDeclarativeGuard<QObject>(o);
+        if (changed)
+            roleIndex = role.index;
+    } else {
+        qmlInfo(0) << "Unable to assign string to role '" << role.name << "' of different type.";
     }
+
+    return roleIndex;
 }
 
-/*!
-    \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
+void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
+{
+    char *mem = getPropertyMemory(role);
+    new (mem) QString(s);
+}
 
-    Adds a new item to the list model at position \a index, with the
-    values in \a dict.
+void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
+{
+    char *mem = getPropertyMemory(role);
+    double *value = new (mem) double;
+    *value = d;
+}
 
-    \code
-        fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
-    \endcode
+void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
+{
+    char *mem = getPropertyMemory(role);
+    bool *value = new (mem) bool;
+    *value = b;
+}
 
-    The \a index must be to an existing item in the list, or one past
-    the end of the list (equivalent to append).
+void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
+{
+    char *mem = getPropertyMemory(role);
+    new (mem) QDeclarativeGuard<QObject>(o);
+}
 
-    \sa set() append()
-*/
-void QDeclarativeListModel::insert(int index, const QDeclarativeV8Handle &handle)
+void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
 {
-    v8::Handle<v8::Value> valuemap = handle.toHandle();
+    char *mem = getPropertyMemory(role);
+    ListModel **value = new (mem) ListModel *;
+    *value = m;
+}
 
-    if (!valuemap->IsObject() || valuemap->IsArray()) {
-        qmlInfo(this) << tr("insert: value is not an object");
-        return;
+void ListElement::clearProperty(const ListLayout::Role &role)
+{
+    switch (role.type) {
+    case ListLayout::Role::String:
+        setStringProperty(role, QString());
+        break;
+    case ListLayout::Role::Number:
+        setDoubleProperty(role, 0.0);
+        break;
+    case ListLayout::Role::Bool:
+        setBoolProperty(role, false);
+        break;
+    case ListLayout::Role::List:
+        setListProperty(role, 0);
+        break;
+    case ListLayout::Role::QObject:
+        setQObjectProperty(role, 0);
+        break;
+    default:
+        break;
     }
+}
 
-    if (index < 0 || index > count()) {
-        qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
-        return;
-    }
+ListElement::ListElement()
+{
+    m_objectCache = 0;
+    uid = ListModel::allocateUid();
+    next = 0;
+    qMemSet(data, 0, sizeof(data));
+}
 
-    bool ok = m_flat ?  m_flat->insert(index, valuemap) : m_nested->insert(index, valuemap);
+ListElement::ListElement(int existingUid)
+{
+    m_objectCache = 0;
+    uid = existingUid;
+    next = 0;
+    qMemSet(data, 0, sizeof(data));
+}
 
-    if (ok && !inWorkerThread()) {
-        emit itemsInserted(index, 1);
-        emit countChanged();
-    }
+ListElement::~ListElement()
+{
+    delete next;
 }
 
-/*!
-    \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
+void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
+{
+    for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
+        const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
+        const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
 
-    Moves \a n items \a from one position \a to another.
+        switch (srcRole.type) {
+            case ListLayout::Role::List:
+                {
+                    ListModel *srcSubModel = src->getListProperty(srcRole);
+                    ListModel *targetSubModel = target->getListProperty(targetRole);
 
-    The from and to ranges must exist; for example, to move the first 3 items
-    to the end of the list:
+                    if (srcSubModel) {
+                        if (targetSubModel == 0) {
+                            targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
+                            target->setListPropertyFast(targetRole, targetSubModel);
+                        }
+                        ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
+                    }
+                }
+                break;
+            case ListLayout::Role::QObject:
+                {
+                    QObject *object = src->getQObjectProperty(srcRole);
+                    target->setQObjectProperty(targetRole, object);
+                }
+                break;
+            case ListLayout::Role::String:
+            case ListLayout::Role::Number:
+            case ListLayout::Role::Bool:
+                {
+                    QVariant v = src->getProperty(srcRole, 0, 0);
+                    target->setVariantProperty(targetRole, v);
+                }
+            default:
+                break;
+        }
+    }
 
-    \code
-        fruitModel.move(0, fruitModel.count - 3, 3)
-    \endcode
+}
 
-    \sa append()
-*/
-void QDeclarativeListModel::move(int from, int to, int n)
+void ListElement::destroy(ListLayout *layout)
 {
-    if (n==0 || from==to)
-        return;
-    if (!canMove(from, to, n)) {
-        qmlInfo(this) << tr("move: out of range");
-        return;
-    }
+    if (layout) {
+        for (int i=0 ; i < layout->roleCount() ; ++i) {
+            const ListLayout::Role &r = layout->getExistingRole(i);
 
-    int origfrom = from;
-    int origto = to;
-    int orign = n;
-    if (from > to) {
-        // Only move forwards - flip if backwards moving
-        int tfrom = from;
-        int tto = to;
-        from = tto;
-        to = tto+n;
-        n = tfrom-tto;
-    }
+            switch (r.type) {
+                case ListLayout::Role::String:
+                    {
+                        QString *string = getStringProperty(r);
+                        if (string)
+                            string->~QString();
+                    }
+                    break;
+                case ListLayout::Role::List:
+                    {
+                        ListModel *model = getListProperty(r);
+                        if (model) {
+                            model->destroy();
+                            delete model;
+                        }
+                    }
+                    break;
+                case ListLayout::Role::QObject:
+                    {
+                        QDeclarativeGuard<QObject> *guard = getGuardProperty(r);
+                        guard->~QDeclarativeGuard();
+                    }
+                    break;
+                default:
+                    // other types don't need explicit cleanup.
+                    break;
+            }
+        }
 
-    if (m_flat)
-        m_flat->move(from, to, n);
-    else
-        m_nested->move(from, to, n);
+        delete m_objectCache;
+    }
 
-    if (!inWorkerThread())
-        emit itemsMoved(origfrom, origto, orign);
+    if (next)
+        next->destroy(0);
+    uid = -1;
 }
 
-/*!
-    \qmlmethod QtQuick2::ListModel::append(jsobject dict)
+int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
+{
+    int roleIndex = -1;
 
-    Adds a new item to the end of the list model, with the
-    values in \a dict.
+    switch (role.type) {
+        case ListLayout::Role::Number:
+            roleIndex = setDoubleProperty(role, d.toDouble());
+            break;
+        case ListLayout::Role::String:
+            roleIndex = setStringProperty(role, d.toString());
+            break;
+        case ListLayout::Role::Bool:
+            roleIndex = setBoolProperty(role, d.toBool());
+            break;
+        case ListLayout::Role::List:
+            roleIndex = setListProperty(role, d.value<ListModel *>());
+            break;
+        default:
+            break;
+    }
 
-    \code
-        fruitModel.append({"cost": 5.95, "name":"Pizza"})
-    \endcode
+    return roleIndex;
+}
+
+int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d)
+{
+    // Check if this key exists yet
+    int roleIndex = -1;
+
+    // Add the value now
+    if (d->IsString()) {
+        v8::Handle<v8::String> jsString = d->ToString();
+        QString qstr;
+        qstr.resize(jsString->Length());
+        jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+        roleIndex = setStringProperty(role, qstr);
+    } else if (d->IsNumber()) {
+        roleIndex = setDoubleProperty(role, d->NumberValue());
+    } else if (d->IsArray()) {
+        ListModel *subModel = new ListModel(role.subLayout, 0, -1);
+        v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
+        int arrayLength = subArray->Length();
+        for (int j=0 ; j < arrayLength ; ++j) {
+            v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+            subModel->append(subObject);
+        }
+        roleIndex = setListProperty(role, subModel);
+    } else if (d->IsInt32()) {
+        roleIndex = setDoubleProperty(role, (double) d->Int32Value());
+    } else if (d->IsBoolean()) {
+        roleIndex = setBoolProperty(role, d->BooleanValue());
+    } else if (d->IsObject()) {
+        QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
+        if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
+            QObject *o = QV8QObjectWrapper::toQObject(r);
+            roleIndex = setQObjectProperty(role, o);
+        }
+    } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
+        clearProperty(role);
+    }
 
-    \sa set() remove()
-*/
-void QDeclarativeListModel::append(const QDeclarativeV8Handle &handle)
+    return roleIndex;
+}
+
+ModelObject::ModelObject(QDeclarativeListModel *model, int elementIndex)
+: m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
 {
-    v8::Handle<v8::Value> valuemap = handle.toHandle();
+    updateValues();
+    setNodeUpdatesEnabled(true);
+}
 
-    if (!valuemap->IsObject() || valuemap->IsArray()) {
-        qmlInfo(this) << tr("append: value is not an object");
-        return;
+void ModelObject::updateValues()
+{
+    int roleCount = m_model->m_listModel->roleCount();
+    for (int i=0 ; i < roleCount ; ++i) {
+        const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
+        QByteArray name = role.name.toUtf8();
+        const QVariant &data = m_model->data(m_elementIndex, i);
+        setValue(name, data, role.type == ListLayout::Role::List);
+    }
+}
+
+void ModelObject::updateValues(const QList<int> &roles)
+{
+    int roleCount = roles.count();
+    for (int i=0 ; i < roleCount ; ++i) {
+        int roleIndex = roles.at(i);
+        const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
+        QByteArray name = role.name.toUtf8();
+        const QVariant &data = m_model->data(m_elementIndex, roleIndex);
+        setValue(name, data, role.type == ListLayout::Role::List);
     }
+}
 
-    insert(count(), handle);
+ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
+: QDeclarativeOpenMetaObject(object), m_enabled(false), m_obj(object)
+{
 }
 
-/*!
-    \qmlmethod object QtQuick2::ListModel::get(int index)
+ModelNodeMetaObject::~ModelNodeMetaObject()
+{
+}
 
-    Returns the item at \a index in the list model. This allows the item
-    data to be accessed or modified from JavaScript:
+void ModelNodeMetaObject::propertyWritten(int index)
+{
+    if (!m_enabled)
+        return;
 
-    \code
-    Component.onCompleted: {
-        fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
-        console.log(fruitModel.get(0).cost);
-        fruitModel.get(0).cost = 10.95;
-    }
-    \endcode
+    QV8Engine *eng = m_obj->m_model->engine();
 
-    The \a index must be an element in the list.
+    QString propName = QString::fromUtf8(name(index));
+    QVariant value = operator[](index);
 
-    Note that properties of the returned object that are themselves objects
-    will also be models, and this get() method is used to access elements:
+    v8::HandleScope handle_scope;
+    v8::Context::Scope scope(eng->context());
 
-    \code
-        fruitModel.append(..., "attributes":
-            [{"name":"spikes","value":"7mm"},
-             {"name":"color","value":"green"}]);
-        fruitModel.get(0).attributes.get(1).value; // == "green"
-    \endcode
+    v8::Handle<v8::Value> v = eng->fromVariant(value);
 
-    \warning The returned object is not guaranteed to remain valid. It
-    should not be used in \l{Property Binding}{property bindings}.
+    int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v);
+    if (roleIndex != -1) {
+        QList<int> roles;
+        roles << roleIndex;
+        m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
+    }
+}
 
-    \sa append()
-*/
-QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
+QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListModelData::instructions() const
 {
-    // the internal flat/nested class checks for bad index
-    return QDeclarativeV8Handle::fromHandle(m_flat ? m_flat->get(index) : m_nested->get(index));
+    return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
 }
 
 /*!
-    \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
+    \qmlclass ListModel QDeclarativeListModel
+    \inqmlmodule QtQuick 2
+    \ingroup qml-working-with-data
+    \brief The ListModel element defines a free-form list data source.
 
-    Changes the item at \a index in the list model with the
-    values in \a dict. Properties not appearing in \a dict
-    are left unchanged.
+    The ListModel is a simple container of ListElement definitions, each containing data roles.
+    The contents can be defined dynamically, or explicitly in QML.
 
-    \code
-        fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
-    \endcode
+    The number of elements in the model can be obtained from its \l count property.
+    A number of familiar methods are also provided to manipulate the contents of the
+    model, including append(), insert(), move(), remove() and set(). These methods
+    accept dictionaries as their arguments; these are translated to ListElement objects
+    by the model.
 
-    If \a index is equal to count() then a new item is appended to the
-    list. Otherwise, \a index must be an element in the list.
+    Elements can be manipulated via the model using the setProperty() method, which
+    allows the roles of the specified element to be set and changed.
 
-    \sa append()
-*/
-void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &valuemap)
-{
-    QList<int> roles;
-    set(index, valuemap, &roles);
-    if (!roles.isEmpty() && !inWorkerThread())
-        emit itemsChanged(index, 1, roles);
-}
+    \section1 Example Usage
 
-void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle, QList<int> *roles)
-{
-    v8::Handle<v8::Value> valuemap = handle.toHandle();
+    The following example shows a ListModel containing three elements, with the roles
+    "name" and "cost".
 
-    if (!valuemap->IsObject() || valuemap->IsArray()) {
-        qmlInfo(this) << tr("set: value is not an object");
-        return;
-    }
-    if (index > count() || index < 0) {
-        qmlInfo(this) << tr("set: index %1 out of range").arg(index);
-        return;
-    }
+    \div {class="float-right"}
+    \inlineimage listmodel.png
+    \enddiv
 
-    if (index == count()) {
-        append(handle);
-    } else {
-        if (m_flat)
-            m_flat->set(index, valuemap, roles);
-        else
-            m_nested->set(index, valuemap, roles);
-    }
-}
+    \snippet doc/src/snippets/declarative/listmodel.qml 0
 
-/*!
-    \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
+    \clearfloat
+    Roles (properties) in each element must begin with a lower-case letter and
+    should be common to all elements in a model. The ListElement documentation
+    provides more guidelines for how elements should be defined.
 
-    Changes the \a property of the item at \a index in the list model to \a value.
+    Since the example model contains an \c id property, it can be referenced
+    by views, such as the ListView in this example:
 
-    \code
-        fruitModel.setProperty(3, "cost", 5.95)
-    \endcode
+    \snippet doc/src/snippets/declarative/listmodel-simple.qml 0
+    \dots 8
+    \snippet doc/src/snippets/declarative/listmodel-simple.qml 1
 
-    The \a index must be an element in the list.
+    It is possible for roles to contain list data.  In the following example we
+    create a list of fruit attributes:
 
-    \sa append()
-*/
-void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value)
-{
-    QList<int> roles;
-    setProperty(index, property, value, &roles);
-    if (!roles.isEmpty() && !inWorkerThread())
-        emit itemsChanged(index, 1, roles);
-}
+    \snippet doc/src/snippets/declarative/listmodel-nested.qml model
 
-void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
-    if (count() == 0 || index >= count() || index < 0) {
-        qmlInfo(this) << tr("set: index %1 out of range").arg(index);
-        return;
-    }
+    The delegate displays all the fruit attributes:
 
-    if (m_flat)
-        m_flat->setProperty(index, property, value, roles);
-    else
-        m_nested->setProperty(index, property, value, roles);
-}
+    \div {class="float-right"}
+    \inlineimage listmodel-nested.png
+    \enddiv
 
-/*!
-    \qmlmethod QtQuick2::ListModel::sync()
+    \snippet doc/src/snippets/declarative/listmodel-nested.qml delegate
 
-    Writes any unsaved changes to the list model after it has been modified
-    from a worker script.
-*/
-void QDeclarativeListModel::sync()
-{
-    // This is just a dummy method to make it look like sync() exists in
-    // ListModel (and not just QDeclarativeListModelWorkerAgent) and to let
-    // us document sync().
-    qmlInfo(this) << "List sync() can only be called from a WorkerScript";
-}
-
-bool QDeclarativeListModelParser::compileProperty(const QDeclarativeCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
-{
-    QList<QVariant> values = prop.assignedValues();
-    for(int ii = 0; ii < values.count(); ++ii) {
-        const QVariant &value = values.at(ii);
+    \clearfloat
+    \section1 Modifying List Models
 
-        if(value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
-            QDeclarativeCustomParserNode node =
-                qvariant_cast<QDeclarativeCustomParserNode>(value);
+    The content of a ListModel may be created and modified using the clear(),
+    append(), set(), insert() and setProperty() methods.  For example:
 
-            if (node.name() != listElementTypeName) {
-                const QMetaObject *mo = resolveType(node.name());
-                if (mo != &QDeclarativeListElement::staticMetaObject) {
-                    error(node, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
-                    return false;
-                }
-                listElementTypeName = node.name(); // cache right name for next time
-            }
+    \snippet doc/src/snippets/declarative/listmodel-modify.qml delegate
 
-            {
-            ListInstruction li;
-            li.type = ListInstruction::Push;
-            li.dataIdx = -1;
-            instr << li;
-            }
+    Note that when creating content dynamically the set of available properties
+    cannot be changed once set. Whatever properties are first added to the model
+    are the only permitted properties in the model.
 
-            QList<QDeclarativeCustomParserProperty> props = node.properties();
-            for(int jj = 0; jj < props.count(); ++jj) {
-                const QDeclarativeCustomParserProperty &nodeProp = props.at(jj);
-                if (nodeProp.name().isEmpty()) {
-                    error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
-                    return false;
-                }
-                if (nodeProp.name() == QStringLiteral("id")) {
-                    error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot use reserved \"id\" property"));
-                    return false;
-                }
+    \section1 Using Threaded List Models with WorkerScript
 
-                ListInstruction li;
-                int ref = data.count();
-                data.append(nodeProp.name().toUtf8());
-                data.append('\0');
-                li.type = ListInstruction::Set;
-                li.dataIdx = ref;
-                instr << li;
+    ListModel can be used together with WorkerScript access a list model
+    from multiple threads. This is useful if list modifications are
+    synchronous and take some time: the list operations can be moved to a
+    different thread to avoid blocking of the main GUI thread.
 
-                if(!compileProperty(nodeProp, instr, data))
-                    return false;
+    Here is an example that uses WorkerScript to periodically append the
+    current time to a list model:
 
-                li.type = ListInstruction::Pop;
-                li.dataIdx = -1;
-                instr << li;
-            }
+    \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
 
-            {
-            ListInstruction li;
-            li.type = ListInstruction::Pop;
-            li.dataIdx = -1;
-            instr << li;
-            }
+    The included file, \tt dataloader.js, looks like this:
 
-        } else {
+    \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
 
-            QDeclarativeScript::Variant variant =
-                qvariant_cast<QDeclarativeScript::Variant>(value);
+    The timer in the main example sends messages to the worker script by calling
+    \l WorkerScript::sendMessage(). When this message is received,
+    \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
+    which appends the current time to the list model.
 
-            int ref = data.count();
+    Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
+    handler. You must call sync() or else the changes made to the list from the external
+    thread will not be reflected in the list model in the main thread.
 
-            QByteArray d;
-            d += char(variant.type()); // type tag
-            if (variant.isString()) {
-                d += variant.asString().toUtf8();
-            } else if (variant.isNumber()) {
-                d += QByteArray::number(variant.asNumber(),'g',20);
-            } else if (variant.isBoolean()) {
-                d += char(variant.asBoolean());
-            } else if (variant.isScript()) {
-                if (definesEmptyList(variant.asScript())) {
-                    d[0] = char(QDeclarativeScript::Variant::Invalid); // marks empty list
-                } else {
-                    QByteArray script = variant.asScript().toUtf8();
-                    int v = evaluateEnum(script);
-                    if (v<0) {
-                        using namespace QDeclarativeJS;
-                        AST::Node *node = variant.asAST();
-                        AST::StringLiteral *literal = 0;
-                        if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
-                            if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
-                                if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
-                                    if (callExpr->arguments && !callExpr->arguments->next)
-                                        literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
-                                    if (!literal) {
-                                        error(prop, QDeclarativeListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
-                                        return false;
-                                    }
-                                } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
-                                    if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
-                                        literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
-                                    if (!literal) {
-                                        error(prop, QDeclarativeListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
-                                        return false;
-                                    }
-                                }
-                            }
-                        }
+    \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
+*/
 
-                        if (literal) {
-                            d[0] = char(QDeclarativeScript::Variant::String);
-                            d += literal->value.toUtf8();
-                        } else {
-                            error(prop, QDeclarativeListModel::tr("ListElement: cannot use script for property value"));
-                            return false;
-                        }
-                    } else {
-                        d[0] = char(QDeclarativeScript::Variant::Number);
-                        d += QByteArray::number(v);
-                    }
-                }
-            }
-            d.append('\0');
-            data.append(d);
+QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
+: QListModelInterface(parent)
+{
+    m_mainThread = true;
+    m_primary = true;
+    m_agent = 0;
 
-            ListInstruction li;
-            li.type = ListInstruction::Value;
-            li.dataIdx = ref;
-            instr << li;
-        }
-    }
+    m_layout = new ListLayout;
+    m_listModel = new ListModel(m_layout, this, -1);
 
-    return true;
+    m_engine = 0;
 }
 
-QByteArray QDeclarativeListModelParser::compile(const QList<QDeclarativeCustomParserProperty> &customProps)
+QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
+: QListModelInterface(parent)
 {
-    QList<ListInstruction> instr;
-    QByteArray data;
-    listElementTypeName = QString(); // unknown
-
-    for(int ii = 0; ii < customProps.count(); ++ii) {
-        const QDeclarativeCustomParserProperty &prop = customProps.at(ii);
-        if(!prop.name().isEmpty()) { // isn't default property
-            error(prop, QDeclarativeListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
-            return QByteArray();
-        }
+    m_mainThread = owner->m_mainThread;
+    m_primary = false;
+    m_agent = owner->m_agent;
 
-        if(!compileProperty(prop, instr, data)) {
-            return QByteArray();
-        }
-    }
-
-    int size = sizeof(ListModelData) +
-               instr.count() * sizeof(ListInstruction) +
-               data.count();
+    m_layout = 0;
+    m_listModel = data;
 
-    QByteArray rv;
-    rv.resize(size);
-
-    ListModelData *lmd = (ListModelData *)rv.data();
-    lmd->dataOffset = sizeof(ListModelData) +
-                     instr.count() * sizeof(ListInstruction);
-    lmd->instrCount = instr.count();
-    for (int ii = 0; ii < instr.count(); ++ii)
-        lmd->instructions()[ii] = instr.at(ii);
-    ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
-
-    return rv;
+    m_engine = eng;
 }
 
-void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &d)
+QDeclarativeListModel::QDeclarativeListModel(QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *agent)
+: QListModelInterface(agent)
 {
-    QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
-
-    ModelNode *root = new ModelNode(rv->m_nested);
-    rv->m_nested->m_ownsRoot = true;
-    rv->m_nested->_root = root;
-    QStack<ModelNode *> nodes;
-    nodes << root;
-
-    bool processingSet = false;
-
-    const ListModelData *lmd = (const ListModelData *)d.constData();
-    const char *data = ((const char *)lmd) + lmd->dataOffset;
-
-    for (int ii = 0; ii < lmd->instrCount; ++ii) {
-        const ListInstruction &instr = lmd->instructions()[ii];
-
-        switch(instr.type) {
-        case ListInstruction::Push:
-            {
-                ModelNode *n = nodes.top();
-                ModelNode *n2 = new ModelNode(rv->m_nested);
-                n->values << QVariant::fromValue(n2);
-                nodes.push(n2);
-                if (processingSet)
-                    n->isArray = true;
-            }
-            break;
-
-        case ListInstruction::Pop:
-            nodes.pop();
-            break;
-
-        case ListInstruction::Value:
-            {
-                ModelNode *n = nodes.top();
-                switch (QDeclarativeScript::Variant::Type(data[instr.dataIdx])) {
-                 case QDeclarativeScript::Variant::Invalid:
-                    n->isArray = true;
-                    break;
-                 case QDeclarativeScript::Variant::Boolean:
-                    n->values.append(bool(data[1 + instr.dataIdx]));
-                    break;
-                 case QDeclarativeScript::Variant::Number:
-                    n->values.append(QByteArray(data + 1 + instr.dataIdx).toDouble());
-                    break;
-                 case QDeclarativeScript::Variant::String:
-                    n->values.append(QString::fromUtf8(data + 1 + instr.dataIdx));
-                    break;
-                 default:
-                    Q_ASSERT("Format error in ListInstruction");
-                }
-
-                processingSet = false;
-            }
-            break;
+    m_mainThread = false;
+    m_primary = true;
+    m_agent = agent;
 
-        case ListInstruction::Set:
-            {
-                ModelNode *n = nodes.top();
-                ModelNode *n2 = new ModelNode(rv->m_nested);
-                n->properties.insert(QString::fromUtf8(data + instr.dataIdx), n2);
-                nodes.push(n2);
-                processingSet = true;
-            }
-            break;
-        }
-    }
+    m_layout = new ListLayout(orig->m_layout);
+    m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
+    ListModel::sync(orig->m_listModel, m_listModel, 0);
 
-    ModelNode *rootNode = rv->m_nested->_root;
-    for (int i=0; i<rootNode->values.count(); ++i) {
-        ModelNode *node = qvariant_cast<ModelNode *>(rootNode->values[i]);
-        node->listIndex = i;
-        node->updateListIndexes();
-    }
+    m_engine = 0;
 }
 
-bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
+QDeclarativeListModel::~QDeclarativeListModel()
 {
-    if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
-        for (int i=1; i<s.length()-1; i++) {
-            if (!s[i].isSpace())
-                return false;
-        }
-        return true;
-    }
-    return false;
-}
+    if (m_primary) {
+        m_listModel->destroy();
+        delete m_listModel;
 
+        if (m_mainThread && m_agent)
+            m_agent->release();
+    }
 
-/*!
-    \qmlclass ListElement QDeclarativeListElement
-    \inqmlmodule QtQuick 2
-    \ingroup qml-working-with-data
-    \brief The ListElement element defines a data item in a ListModel.
-
-    List elements are defined inside ListModel definitions, and represent items in a
-    list that will be displayed using ListView or \l Repeater items.
-
-    List elements are defined like other QML elements except that they contain
-    a collection of \e role definitions instead of properties. Using the same
-    syntax as property definitions, roles both define how the data is accessed
-    and include the data itself.
-
-    The names used for roles must begin with a lower-case letter and should be
-    common to all elements in a given model. Values must be simple constants; either
-    strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
-    (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
-
-    \section1 Referencing Roles
-
-    The role names are used by delegates to obtain data from list elements.
-    Each role name is accessible in the delegate's scope, and refers to the
-    corresponding role in the current element. Where a role name would be
-    ambiguous to use, it can be accessed via the \l{ListView::}{model}
-    property (e.g., \c{model.cost} instead of \c{cost}).
-
-    \section1 Example Usage
-
-    The following model defines a series of list elements, each of which
-    contain "name" and "cost" roles and their associated values.
-
-    \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml model
-
-    The delegate obtains the name and cost for each element by simply referring
-    to \c name and \c cost:
-
-    \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml view
+    m_listModel = 0;
 
-    \sa ListModel
-*/
+    delete m_layout;
+    m_layout = 0;
+}
 
-FlatListModel::FlatListModel(QDeclarativeListModel *base)
-: m_engine(0), m_listModel(base), m_parentAgent(0)
+QV8Engine *QDeclarativeListModel::engine() const
 {
+    if (m_engine == 0) {
+        m_engine  = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this));
+    }
+
+    return m_engine;
 }
 
-FlatListModel::~FlatListModel()
+void QDeclarativeListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
 {
-    qDeleteAll(m_nodeData);
+    if (m_mainThread) {
+        emit itemsChanged(index, count, roles);
+    } else {
+        m_agent->data.changedChange(this, index, count, roles);
+    }
 }
 
-QVariant FlatListModel::data(int index, int role) const
+void QDeclarativeListModel::emitItemsRemoved(int index, int count)
 {
-    Q_ASSERT(index >= 0 && index < m_values.count());
-    if (m_values[index].contains(role))
-        return m_values[index][role];
-    return QVariant();
+    if (m_mainThread) {
+        emit itemsRemoved(index, count);
+        emit countChanged();
+    } else {
+        if (index == 0 && count == this->count())
+            m_agent->data.clearChange(this);
+        m_agent->data.removeChange(this, index, count);
+    }
 }
 
-QList<int> FlatListModel::roles() const
+void QDeclarativeListModel::emitItemsInserted(int index, int count)
 {
-    return m_roles.keys();
+    if (m_mainThread) {
+        emit itemsInserted(index, count);
+        emit countChanged();
+    } else {
+        m_agent->data.insertChange(this, index, count);
+    }
 }
 
-QString FlatListModel::toString(int role) const
+void QDeclarativeListModel::emitItemsMoved(int from, int to, int n)
 {
-    if (m_roles.contains(role))
-        return m_roles[role];
-    return QString();
+    if (m_mainThread) {
+        emit itemsMoved(from, to, n);
+    } else {
+        m_agent->data.moveChange(this, from, n, to);
+    }
 }
 
-int FlatListModel::count() const
+QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
 {
-    return m_values.count();
+    if (m_agent)
+        return m_agent;
+
+    m_agent = new QDeclarativeListModelWorkerAgent(this);
+    return m_agent;
 }
 
-void FlatListModel::clear()
+QList<int> QDeclarativeListModel::roles() const
 {
-    m_values.clear();
+    QList<int> rolesArray;
+
+    for (int i=0 ; i < m_listModel->roleCount() ; ++i)
+        rolesArray << i;
 
-    qDeleteAll(m_nodeData);
-    m_nodeData.clear();
+    return rolesArray;
 }
 
-void FlatListModel::remove(int index)
+QString QDeclarativeListModel::toString(int role) const
 {
-    m_values.removeAt(index);
-    removedNode(index);
+    const ListLayout::Role &r = m_listModel->getExistingRole(role);
+    return r.name;
 }
 
-bool FlatListModel::insert(int index, v8::Handle<v8::Value> value)
+QVariant QDeclarativeListModel::data(int index, int role) const
 {
-    Q_ASSERT(index >= 0 && index <= m_values.count());
-
-    QHash<int, QVariant> row;
-    if (!addValue(value, &row, 0))
-        return false;
-
-    m_values.insert(index, row);
-    insertedNode(index);
+    if (index >= count() || index < 0)
+        return QVariant();
 
-    return true;
+    return m_listModel->getProperty(index, role, this, engine());
 }
 
-QV8Engine *FlatListModel::engine() const
+/*!
+    \qmlproperty int QtQuick2::ListModel::count
+    The number of data entries in the model.
+*/
+int QDeclarativeListModel::count() const
 {
-    return m_engine?m_engine:QDeclarativeEnginePrivate::getV8Engine(qmlEngine(m_listModel));
+    return m_listModel->elementCount();
 }
 
-QV8Engine *NestedListModel::engine() const
-{
-    return QDeclarativeEnginePrivate::getV8Engine(qmlEngine(m_listModel));
-}
+/*!
+    \qmlmethod QtQuick2::ListModel::clear()
 
-v8::Handle<v8::Value> FlatListModel::get(int index) const
+    Deletes all content from the model.
+
+    \sa append() remove()
+*/
+void QDeclarativeListModel::clear()
 {
-    QV8Engine *v8engine = engine();
+    int cleared = count();
 
-    if (!v8engine) 
-        return v8::Undefined();
+    m_listModel->clear();
+    emitItemsRemoved(0, cleared);
+}
 
-    if (index < 0 || index >= m_values.count())
-        return v8::Undefined();
+/*!
+    \qmlmethod QtQuick2::ListModel::remove(int index)
 
-    FlatListModel *that = const_cast<FlatListModel*>(this);
+    Deletes the content at \a index from the model.
 
-    FlatNodeData *data = m_nodeData.value(index);
-    if (!data) {
-        data = new FlatNodeData(index);
-        that->m_nodeData.replace(index, data);
+    \sa clear()
+*/
+void QDeclarativeListModel::remove(int index)
+{
+    if (index < 0 || index >= count()) {
+        qmlInfo(this) << tr("remove: index %1 out of range").arg(index);
+        return;
     }
 
-    v8::Local<v8::Object> rv = QDeclarativeListModelV8Data::create(v8engine);
-    QV8ListModelResource *r = new QV8ListModelResource(that, data, v8engine);
-    rv->SetExternalResource(r);
-    return rv;
-}
-
-void FlatListModel::set(int index, v8::Handle<v8::Value> value, QList<int> *roles)
-{
-    Q_ASSERT(index >= 0 && index < m_values.count());
+    m_listModel->remove(index);
 
-    QHash<int, QVariant> row = m_values[index];
-    if (addValue(value, &row, roles))
-        m_values[index] = row;
+    emitItemsRemoved(index, 1);
 }
 
-void FlatListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
-    Q_ASSERT(index >= 0 && index < m_values.count());
+/*!
+    \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
 
-    QHash<QString, int>::Iterator iter = m_strings.find(property);
-    int role;
-    if (iter == m_strings.end()) {
-        role = m_roles.count();
-        m_roles.insert(role, property);
-        m_strings.insert(property, role);
-    } else {
-        role = iter.value();
-    }
+    Adds a new item to the list model at position \a index, with the
+    values in \a dict.
 
-    if (m_values[index][role] != value) {
-        roles->append(role);
-        m_values[index][role] = value;
-    }
-}
+    \code
+        fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
+    \endcode
 
-void FlatListModel::move(int from, int to, int n)
-{
-    qdeclarativelistmodel_move<QList<QHash<int, QVariant> > >(from, to, n, &m_values);
-    moveNodes(from, to, n);
-}
+    The \a index must be to an existing item in the list, or one past
+    the end of the list (equivalent to append).
+
+    \sa set() append()
+*/
 
-bool FlatListModel::addValue(v8::Handle<v8::Value> value, QHash<int, QVariant> *row, QList<int> *roles)
+void QDeclarativeListModel::insert(QDeclarativeV8Function *args)
 {
-    if (!value->IsObject())
-        return false;
+    if (args->Length() == 2) {
 
-    v8::Local<v8::Array> properties = engine()->getOwnPropertyNames(value->ToObject());
-    uint32_t length = properties->Length();
-    for (uint32_t ii = 0; ii < length; ++ii) {
-        // XXX TryCatch?
-        v8::Handle<v8::Value> property = properties->Get(ii);
-        v8::Handle<v8::Value> jsv = value->ToObject()->Get(property);
+        v8::Handle<v8::Value> arg0 = (*args)[0];
+        int index = arg0->Int32Value();
 
-        if (!jsv->IsRegExp() && !jsv->IsDate() && jsv->IsObject() && !engine()->isVariant(jsv)) {
-            qmlInfo(m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
-            return false;
+        if (index < 0 || index > count()) {
+            qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
+            return;
         }
 
-        QString name = engine()->toString(property);
-        QVariant v = engine()->toVariant(jsv, -1);
+        v8::Handle<v8::Value> arg1 = (*args)[1];
 
-        QHash<QString, int>::Iterator iter = m_strings.find(name);
-        if (iter == m_strings.end()) {
-            int role = m_roles.count();
-            m_roles.insert(role, name);
-            iter = m_strings.insert(name, role);
-            if (roles)
-                roles->append(role);
+        if (arg1->IsArray()) {
+            v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
+            int objectArrayLength = objectArray->Length();
+            for (int i=0 ; i < objectArrayLength ; ++i) {
+                v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
+                m_listModel->insert(index+i, argObject);
+            }
+            emitItemsInserted(index, objectArrayLength);
+        } else if (arg1->IsObject()) {
+            v8::Handle<v8::Object> argObject = arg1->ToObject();
+
+            m_listModel->insert(index, argObject);
+            emitItemsInserted(index, 1);
         } else {
-            int role = iter.value();
-            if (roles && row->contains(role) && row->value(role) != v)
-                roles->append(role);
+            qmlInfo(this) << tr("insert: value is not an object");
         }
-        row->insert(*iter, v);
+    } else {
+        qmlInfo(this) << tr("insert: value is not an object");
     }
-    return true;
 }
 
-void FlatListModel::insertedNode(int index)
-{
-    if (index >= 0 && index <= m_values.count()) {
-        m_nodeData.insert(index, 0);
+/*!
+    \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
 
-        for (int i=index + 1; i<m_nodeData.count(); i++) {
-            if (m_nodeData[i])
-                m_nodeData[i]->index = i;
-        }
+    Moves \a n items \a from one position \a to another.
+
+    The from and to ranges must exist; for example, to move the first 3 items
+    to the end of the list:
+
+    \code
+        fruitModel.move(0, fruitModel.count - 3, 3)
+    \endcode
+
+    \sa append()
+*/
+void QDeclarativeListModel::move(int from, int to, int n)
+{
+    if (n==0 || from==to)
+        return;
+    if (!canMove(from, to, n)) {
+        qmlInfo(this) << tr("move: out of range");
+        return;
     }
+
+    m_listModel->move(from, to, n);
+    emitItemsMoved(from, to, n);
 }
 
-void FlatListModel::removedNode(int index)
-{
-    if (index >= 0 && index < m_nodeData.count()) {
-        delete m_nodeData.takeAt(index);
+/*!
+    \qmlmethod QtQuick2::ListModel::append(jsobject dict)
+
+    Adds a new item to the end of the list model, with the
+    values in \a dict.
+
+    \code
+        fruitModel.append({"cost": 5.95, "name":"Pizza"})
+    \endcode
+
+    \sa set() remove()
+*/
+void QDeclarativeListModel::append(QDeclarativeV8Function *args)
+{
+    if (args->Length() == 1) {
+        v8::Handle<v8::Value> arg = (*args)[0];
+
+        if (arg->IsArray()) {
+            v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
+            int objectArrayLength = objectArray->Length();
+            int index = m_listModel->elementCount();
+            for (int i=0 ; i < objectArrayLength ; ++i) {
+                v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
+                m_listModel->append(argObject);
+            }
+            emitItemsInserted(index, objectArrayLength);
+        } else if (arg->IsObject()) {
+            v8::Handle<v8::Object> argObject = arg->ToObject();
 
-        for (int i=index; i<m_nodeData.count(); i++) {
-            if (m_nodeData[i])
-                m_nodeData[i]->index = i;
+            int index = m_listModel->append(argObject);
+            emitItemsInserted(index, 1);
+
+        } else {
+            qmlInfo(this) << tr("append: value is not an object");
         }
+    } else {
+        qmlInfo(this) << tr("append: value is not an object");
     }
 }
 
-void FlatListModel::moveNodes(int from, int to, int n)
-{
-    if (!m_listModel->canMove(from, to, n))
-        return;
+/*!
+    \qmlmethod object QtQuick2::ListModel::get(int index)
 
-    qdeclarativelistmodel_move<QList<FlatNodeData *> >(from, to, n, &m_nodeData);
+    Returns the item at \a index in the list model. This allows the item
+    data to be accessed or modified from JavaScript:
 
-    for (int i=from; i<from + (to-from); i++)  {
-        if (m_nodeData[i]) 
-            m_nodeData[i]->index = i;
+    \code
+    Component.onCompleted: {
+        fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
+        console.log(fruitModel.get(0).cost);
+        fruitModel.get(0).cost = 10.95;
     }
-}
+    \endcode
+
+    The \a index must be an element in the list.
+
+    Note that properties of the returned object that are themselves objects
+    will also be models, and this get() method is used to access elements:
+
+    \code
+        fruitModel.append(..., "attributes":
+            [{"name":"spikes","value":"7mm"},
+             {"name":"color","value":"green"}]);
+        fruitModel.get(0).attributes.get(1).value; // == "green"
+    \endcode
+
+    \warning The returned object is not guaranteed to remain valid. It
+    should not be used in \l{Property Binding}{property bindings}.
 
-FlatNodeData::~FlatNodeData()
+    \sa append()
+*/
+QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
 {
-    for (QSet<QV8ListModelResource *>::Iterator iter = objects.begin(); iter != objects.end(); ++iter) {
-        QV8ListModelResource *data = *iter;
-        data->nodeData = 0;
+    v8::Handle<v8::Value> result = v8::Undefined();
+
+    if (index >= 0 && index < m_listModel->elementCount()) {
+        QV8Engine *v8engine = engine();
+
+        ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QDeclarativeListModel *>(this), index);
+        result = v8engine->newQObject(object);
     }
-}
 
-void FlatNodeData::addData(QV8ListModelResource *data) 
-{
-    objects.insert(data);
+    return QDeclarativeV8Handle::fromHandle(result);
 }
 
-void FlatNodeData::removeData(QV8ListModelResource *data)
-{
-    objects.remove(data);
-}
+/*!
+    \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
 
-NestedListModel::NestedListModel(QDeclarativeListModel *base)
-: _root(0), m_ownsRoot(false), m_listModel(base), _rolesOk(false)
-{
-}
+    Changes the item at \a index in the list model with the
+    values in \a dict. Properties not appearing in \a dict
+    are left unchanged.
 
-NestedListModel::~NestedListModel()
-{
-    if (m_ownsRoot)
-        delete _root;
-}
+    \code
+        fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
+    \endcode
+
+    If \a index is equal to count() then a new item is appended to the
+    list. Otherwise, \a index must be an element in the list.
 
-QVariant NestedListModel::valueForNode(ModelNode *node, bool *hasNested) const
+    \sa append()
+*/
+void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle)
 {
-    QObject *rv = 0;
-    if (hasNested)
-        *hasNested = false;
+    v8::Handle<v8::Value> valuemap = handle.toHandle();
 
-    if (node->isArray) {
-        // List
-        rv = node->model(this);
-        if (hasNested)
-            *hasNested = true;
-    } else {
-        if (!node->properties.isEmpty()) {
-            // Object
-            rv = node->object(this);
-        } else if (node->values.count() == 0) {
-            // Invalid
-            return QVariant();
-        } else if (node->values.count() == 1) {
-            // Value
-            QVariant &var = node->values[0];
-            ModelNode *valueNode = qvariant_cast<ModelNode *>(var);
-            if (valueNode) {
-                if (!valueNode->properties.isEmpty())
-                    rv = valueNode->object(this);
-                else
-                    rv = valueNode->model(this);
-            } else {
-                return var;
-            }
-        }
+    if (!valuemap->IsObject() || valuemap->IsArray()) {
+        qmlInfo(this) << tr("set: value is not an object");
+        return;
+    }
+    if (index > count() || index < 0) {
+        qmlInfo(this) << tr("set: index %1 out of range").arg(index);
+        return;
     }
 
-    if (rv) {
-        return QVariant::fromValue(rv);
+    v8::Handle<v8::Object> object = valuemap->ToObject();
+
+    if (index == count()) {
+        m_listModel->insert(index, object);
+        emitItemsInserted(index, 1);
     } else {
-        return QVariant();
+
+        QList<int> roles;
+        m_listModel->set(index, object, &roles);
+
+        if (roles.count())
+            emitItemsChanged(index, 1, roles);
     }
 }
 
-QHash<int,QVariant> NestedListModel::data(int index, const QList<int> &roles, bool *hasNested) const
-{
-    Q_ASSERT(_root && index >= 0 && index < _root->values.count());
-    checkRoles();
-    QHash<int, QVariant> rv;
-
-    ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
-    if (!node)
-        return rv;
+/*!
+    \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
 
-    for (int ii = 0; ii < roles.count(); ++ii) {
-        const QString &roleString = roleStrings.at(roles.at(ii));
+    Changes the \a property of the item at \a index in the list model to \a value.
 
-        QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
-        if (iter != node->properties.end()) {
-            ModelNode *row = *iter;
-            rv.insert(roles.at(ii), valueForNode(row, hasNested));
-        }
-    }
+    \code
+        fruitModel.setProperty(3, "cost", 5.95)
+    \endcode
 
-    return rv;
-}
+    The \a index must be an element in the list.
 
-QVariant NestedListModel::data(int index, int role) const
+    \sa append()
+*/
+void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value)
 {
-    Q_ASSERT(_root && index >= 0 && index < _root->values.count());
-    checkRoles();
-    QVariant rv;
-    if (roleStrings.count() < role)
-        return rv;
+    if (count() == 0 || index >= count() || index < 0) {
+        qmlInfo(this) << tr("set: index %1 out of range").arg(index);
+        return;
+    }
 
-    ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
-    if (!node)
-        return rv;
+    int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
+    if (roleIndex != -1) {
 
-    const QString &roleString = roleStrings.at(role);
+        QList<int> roles;
+        roles << roleIndex;
 
-    QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
-    if (iter != node->properties.end()) {
-        ModelNode *row = *iter;
-        rv = valueForNode(row);
+        emitItemsChanged(index, 1, roles);
     }
-
-    return rv;
 }
 
-int NestedListModel::count() const
-{
-    if (!_root) return 0;
-    return _root->values.count();
-}
+/*!
+    \qmlmethod QtQuick2::ListModel::sync()
 
-void NestedListModel::clear()
+    Writes any unsaved changes to the list model after it has been modified
+    from a worker script.
+*/
+void QDeclarativeListModel::sync()
 {
-    if (_root)
-        _root->clear();
+    // This is just a dummy method to make it look like sync() exists in
+    // ListModel (and not just QDeclarativeListModelWorkerAgent) and to let
+    // us document sync().
+    qmlInfo(this) << "List sync() can only be called from a WorkerScript";
 }
 
-void NestedListModel::remove(int index)
+bool QDeclarativeListModelParser::compileProperty(const QDeclarativeCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
 {
-    if (!_root)
-        return;
-    ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
-    _root->values.removeAt(index);
-    for (int i = 0; i < _root->values.count(); ++i) {
-        ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
-        if (node)
-            node->listIndex = i;
-    }
-    if (node)
-        delete node;
-}
+    QList<QVariant> values = prop.assignedValues();
+    for(int ii = 0; ii < values.count(); ++ii) {
+        const QVariant &value = values.at(ii);
 
-bool NestedListModel::insert(int index, v8::Handle<v8::Value> valuemap)
-{
-    if (!_root) {
-        _root = new ModelNode(this);
-        m_ownsRoot = true;
-    }
+        if(value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
+            QDeclarativeCustomParserNode node =
+                qvariant_cast<QDeclarativeCustomParserNode>(value);
 
-    ModelNode *mn = new ModelNode(this);
-    mn->listIndex = index;
-    mn->setObjectValue(valuemap);
-    _root->values.insert(index,QVariant::fromValue(mn));
-    for (int i = index + 1; i < _root->values.count(); ++i) {
-        ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
-        if (node)
-            node->listIndex = i;
-    }
-    return true;
-}
+            if (node.name() != listElementTypeName) {
+                const QMetaObject *mo = resolveType(node.name());
+                if (mo != &QDeclarativeListElement::staticMetaObject) {
+                    error(node, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
+                    return false;
+                }
+                listElementTypeName = node.name(); // cache right name for next time
+            }
+
+            {
+            ListInstruction li;
+            li.type = ListInstruction::Push;
+            li.dataIdx = -1;
+            instr << li;
+            }
 
-void NestedListModel::move(int from, int to, int n)
-{
-    if (!_root)
-        return;
-    qdeclarativelistmodel_move<QVariantList>(from, to, n, &_root->values);
-    for (int i = qMin(from, to), end = qMin(_root->values.count(), qMax(from, to) + n); i < end; ++i) {
-        ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
-        if (node)
-            node->listIndex = i;
-    }
-}
+            QList<QDeclarativeCustomParserProperty> props = node.properties();
+            for(int jj = 0; jj < props.count(); ++jj) {
+                const QDeclarativeCustomParserProperty &nodeProp = props.at(jj);
+                if (nodeProp.name().isEmpty()) {
+                    error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
+                    return false;
+                }
+                if (nodeProp.name() == QStringLiteral("id")) {
+                    error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot use reserved \"id\" property"));
+                    return false;
+                }
 
-v8::Handle<v8::Value> NestedListModel::get(int index) const
-{   
-    QDeclarativeEngine *eng = qmlEngine(m_listModel);
-    if (!eng) 
-        return v8::Undefined();;
+                ListInstruction li;
+                int ref = data.count();
+                data.append(nodeProp.name().toUtf8());
+                data.append('\0');
+                li.type = ListInstruction::Set;
+                li.dataIdx = ref;
+                instr << li;
 
-    if (index < 0 || index >= count()) 
-        return v8::Undefined();
+                if(!compileProperty(nodeProp, instr, data))
+                    return false;
 
-    ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
-    if (!node)
-        return v8::Undefined();;
+                li.type = ListInstruction::Pop;
+                li.dataIdx = -1;
+                instr << li;
+            }
 
-    return QDeclarativeEnginePrivate::get(eng)->v8engine()->newQObject(node->object(this));
-}
+            {
+            ListInstruction li;
+            li.type = ListInstruction::Pop;
+            li.dataIdx = -1;
+            instr << li;
+            }
 
-void NestedListModel::set(int index, v8::Handle<v8::Value> valuemap, QList<int> *roles)
-{
-    Q_ASSERT(index >=0 && index < count());
+        } else {
 
-    if (!valuemap->IsObject())
-        return;
+            QDeclarativeScript::Variant variant =
+                qvariant_cast<QDeclarativeScript::Variant>(value);
 
-    ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
-    bool emitItemsChanged = node->setObjectValue(valuemap);
-    if (!emitItemsChanged)
-        return;
+            int ref = data.count();
 
-    QV8Engine *v8engine = engine();
+            QByteArray d;
+            d += char(variant.type()); // type tag
+            if (variant.isString()) {
+                d += variant.asString().toUtf8();
+            } else if (variant.isNumber()) {
+                d += QByteArray::number(variant.asNumber(),'g',20);
+            } else if (variant.isBoolean()) {
+                d += char(variant.asBoolean());
+            } else if (variant.isScript()) {
+                if (definesEmptyList(variant.asScript())) {
+                    d[0] = char(QDeclarativeScript::Variant::Invalid); // marks empty list
+                } else {
+                    QByteArray script = variant.asScript().toUtf8();
+                    int v = evaluateEnum(script);
+                    if (v<0) {
+                        using namespace QDeclarativeJS;
+                        AST::Node *node = variant.asAST();
+                        AST::StringLiteral *literal = 0;
+                        if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
+                            if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
+                                if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
+                                    if (callExpr->arguments && !callExpr->arguments->next)
+                                        literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
+                                    if (!literal) {
+                                        error(prop, QDeclarativeListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
+                                        return false;
+                                    }
+                                } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
+                                    if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
+                                        literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
+                                    if (!literal) {
+                                        error(prop, QDeclarativeListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
+                                        return false;
+                                    }
+                                }
+                            }
+                        }
 
-    v8::Local<v8::Array> properties = v8engine->getOwnPropertyNames(valuemap->ToObject());
-    uint32_t length = properties->Length();
-    for (uint32_t ii = 0; ii < length; ++ii) {
-        // XXX TryCatch?
-        v8::Handle<v8::Value> property = properties->Get(ii);
-        QString name = v8engine->toString(property);
+                        if (literal) {
+                            d[0] = char(QDeclarativeScript::Variant::String);
+                            d += literal->value.toUtf8();
+                        } else {
+                            error(prop, QDeclarativeListModel::tr("ListElement: cannot use script for property value"));
+                            return false;
+                        }
+                    } else {
+                        d[0] = char(QDeclarativeScript::Variant::Number);
+                        d += QByteArray::number(v);
+                    }
+                }
+            }
+            d.append('\0');
+            data.append(d);
 
-        int r = roleStrings.indexOf(name);
-        if (r < 0) {
-            r = roleStrings.count();
-            roleStrings << name;
+            ListInstruction li;
+            li.type = ListInstruction::Value;
+            li.dataIdx = ref;
+            instr << li;
         }
-        roles->append(r);
     }
-}
-
-void NestedListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
-    Q_ASSERT(index >=0 && index < count());
 
-    ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
-    bool emitItemsChanged = node->setProperty(property, value);
-    if (!emitItemsChanged)
-        return;
-
-    int r = roleStrings.indexOf(property);
-    if (r < 0) {
-        r = roleStrings.count();
-        roleStrings << property;
-    }
-    roles->append(r);
+    return true;
 }
 
-void NestedListModel::checkRoles() const
+QByteArray QDeclarativeListModelParser::compile(const QList<QDeclarativeCustomParserProperty> &customProps)
 {
-    if (_rolesOk || !_root)
-        return;
+    QList<ListInstruction> instr;
+    QByteArray data;
+    listElementTypeName = QString(); // unknown
 
-    for (int i = 0; i<_root->values.count(); ++i) {
-        ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
-        if (node) {
-            foreach (const QString &role, node->properties.keys()) {
-                if (!roleStrings.contains(role))
-                    roleStrings.append(role);
-            }
+    for(int ii = 0; ii < customProps.count(); ++ii) {
+        const QDeclarativeCustomParserProperty &prop = customProps.at(ii);
+        if(!prop.name().isEmpty()) { // isn't default property
+            error(prop, QDeclarativeListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
+            return QByteArray();
         }
-    }
 
-    _rolesOk = true;
-}
+        if(!compileProperty(prop, instr, data)) {
+            return QByteArray();
+        }
+    }
 
-QList<int> NestedListModel::roles() const
-{
-    checkRoles();
-    QList<int> rv;
-    for (int ii = 0; ii < roleStrings.count(); ++ii)
-        rv << ii;
-    return rv;
-}
+    int size = sizeof(ListModelData) +
+               instr.count() * sizeof(ListInstruction) +
+               data.count();
 
-QString NestedListModel::toString(int role) const
-{
-    checkRoles();
-    if (role < roleStrings.count())
-        return roleStrings.at(role);
-    else
-        return QString();
-}
+    QByteArray rv;
+    rv.resize(size);
 
+    ListModelData *lmd = (ListModelData *)rv.data();
+    lmd->dataOffset = sizeof(ListModelData) +
+                     instr.count() * sizeof(ListInstruction);
+    lmd->instrCount = instr.count();
+    for (int ii = 0; ii < instr.count(); ++ii)
+        lmd->instructions()[ii] = instr.at(ii);
+    ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
 
-ModelNode::ModelNode(NestedListModel *model)
-: modelCache(0), objectCache(0), isArray(false), m_model(model), listIndex(-1)
-{
+    return rv;
 }
 
-ModelNode::~ModelNode()
+void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &d)
 {
-    clear();
-    if (modelCache) { modelCache->m_nested->_root = 0/* ==this */; delete modelCache; modelCache = 0; }
-    if (objectCache) { delete objectCache; objectCache = 0; }
-}
+    QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
 
-void ModelNode::clear()
-{
-    ModelNode *node;
-    for (int ii = 0; ii < values.count(); ++ii) {
-        node = qvariant_cast<ModelNode *>(values.at(ii));
-        if (node) { delete node; node = 0; }
-    }
-    values.clear();
+    QV8Engine *engine = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(rv));
+    rv->m_engine = engine;
 
-    qDeleteAll(properties.values());
-    properties.clear();
-}
+    const ListModelData *lmd = (const ListModelData *)d.constData();
+    const char *data = ((const char *)lmd) + lmd->dataOffset;
 
-bool ModelNode::setObjectValue(v8::Handle<v8::Value> valuemap, bool writeToCache)
-{
-    if (!valuemap->IsObject())
-        return false;
+    QStack<DataStackElement> stack;
 
-    bool emitItemsChanged = false;
+    for (int ii = 0; ii < lmd->instrCount; ++ii) {
+        const ListInstruction &instr = lmd->instructions()[ii];
 
-    QV8Engine *v8engine = m_model->engine();
+        switch(instr.type) {
+        case ListInstruction::Push:
+            {
+                ListModel *subModel = 0;
 
-    v8::Local<v8::Array> propertyNames = v8engine->getOwnPropertyNames(valuemap->ToObject());
-    uint32_t length = propertyNames->Length();
+                if (stack.count() == 0) {
+                    subModel = rv->m_listModel;
+                } else {
+                    const DataStackElement &e0 = stack.at(stack.size() - 1);
+                    DataStackElement &e1 = stack[stack.size() - 2];
 
-    for (uint32_t ii = 0; ii < length; ++ii) {
-        // XXX TryCatch?
-        v8::Handle<v8::Value> property = propertyNames->Get(ii);
-        v8::Handle<v8::Value> v = valuemap->ToObject()->Get(property);
+                    const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
+                    if (role.type == ListLayout::Role::List) {
+                        subModel = e1.model->getListProperty(e1.elementIndex, role);
 
-        QString name = v8engine->toString(property);
-        ModelNode *prev = properties.value(name);
-        ModelNode *value = new ModelNode(m_model);
+                        if (subModel == 0) {
+                            subModel = new ListModel(role.subLayout, 0, -1);
+                            QVariant vModel = QVariant::fromValue(subModel);
+                            e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
+                        }
+                    }
+                }
 
-        if (v->IsArray()) {
-            value->isArray = true;
-            value->setListValue(v);
-            if (writeToCache && objectCache)
-                objectCache->setValue(name.toUtf8(), QVariant::fromValue(value->model(m_model)));
-            emitItemsChanged = true;    // for now, too inefficient to check whether list and sublists have changed
-        } else {
-            value->values << v8engine->toVariant(v, -1);
-            if (writeToCache && objectCache)
-                objectCache->setValue(name.toUtf8(), value->values.last());
-            if (!emitItemsChanged && prev && prev->values.count() == 1
-                    && prev->values[0] != value->values.last()) {
-                emitItemsChanged = true;
+                DataStackElement e;
+                e.model = subModel;
+                e.elementIndex = subModel ? subModel->appendElement() : -1;
+                stack.push(e);
             }
-        }
-        if (properties.contains(name))
-            delete properties[name];
-        properties.insert(name, value);
-    }
-    return emitItemsChanged;
-}
-
-void ModelNode::setListValue(v8::Handle<v8::Value> valuelist) 
-{
-    Q_ASSERT(valuelist->IsArray());
-    values.clear();
+            break;
 
-    QV8Engine *engine = m_model->engine();
+        case ListInstruction::Pop:
+            stack.pop();
+            break;
 
-    v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(valuelist);
-    uint32_t length = array->Length();
-    for (uint32_t ii = 0; ii < length; ++ii) {
-        ModelNode *value = new ModelNode(m_model);
+        case ListInstruction::Value:
+            {
+                const DataStackElement &e0 = stack.at(stack.size() - 1);
+                DataStackElement &e1 = stack[stack.size() - 2];
 
-        // XXX TryCatch?
-        v8::Handle<v8::Value> v = array->Get(ii);
+                QString name = e0.name;
+                QVariant value;
 
-        if (v->IsArray()) {
-            value->isArray = true;
-            value->setListValue(v);
-        } else if (v->IsObject()) {
-            value->listIndex = ii;
-            value->setObjectValue(v);
-        } else {
-            value->listIndex = ii;
-            value->values << engine->toVariant(v, -1);
-        }
+                switch (QDeclarativeScript::Variant::Type(data[instr.dataIdx])) {
+                    case QDeclarativeScript::Variant::Invalid:
+                        {
+                            const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
+                            ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
+                            value = QVariant::fromValue(emptyModel);
+                        }
+                        break;
+                    case QDeclarativeScript::Variant::Boolean:
+                        value = bool(data[1 + instr.dataIdx]);
+                        break;
+                    case QDeclarativeScript::Variant::Number:
+                        value = QByteArray(data + 1 + instr.dataIdx).toDouble();
+                        break;
+                    case QDeclarativeScript::Variant::String:
+                        value = QString::fromUtf8(data + 1 + instr.dataIdx);
+                        break;
+                    default:
+                        Q_ASSERT("Format error in ListInstruction");
+                }
 
-        values.append(QVariant::fromValue(value));
-    }
-}
+                e1.model->setOrCreateProperty(e1.elementIndex, name, value);
+            }
+            break;
 
-bool ModelNode::setProperty(const QString& prop, const QVariant& val) {
-    QHash<QString, ModelNode *>::const_iterator it = properties.find(prop);
-    bool emitItemsChanged = false;
-    if (it != properties.end()) {
-        if (val != (*it)->values[0])
-            emitItemsChanged = true;
-        (*it)->values[0] = val;
-    } else {
-        ModelNode *n = new ModelNode(m_model);
-        n->values << val;
-        properties.insert(prop,n);
+        case ListInstruction::Set:
+            {
+                DataStackElement e;
+                e.name = QString::fromUtf8(data + instr.dataIdx);
+                stack.push(e);
+            }
+            break;
+        }
     }
-    if (objectCache)
-        objectCache->setValue(prop.toUtf8(), val);
-    return emitItemsChanged;
 }
 
-void ModelNode::updateListIndexes()
+bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
 {
-    for (QHash<QString, ModelNode *>::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) {
-        ModelNode *node = iter.value();
-        if (node->isArray) {
-            for (int i=0; i<node->values.count(); ++i) {
-                ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(i));
-                if (subNode)
-                    subNode->listIndex = i;
-            }
+    if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
+        for (int i=1; i<s.length()-1; i++) {
+            if (!s[i].isSpace())
+                return false;
         }
-        node->updateListIndexes();
+        return true;
     }
+    return false;
 }
 
-/*
-    Need to call this to emit itemsChanged() for modifications outside of set()
-    and setProperty(), i.e. if an item returned from get() is modified
-*/
-void ModelNode::changedProperty(const QString &name) const
-{
-    if (listIndex < 0)
-        return;
 
-    m_model->checkRoles();
-    QList<int> roles;
-    int role = m_model->roleStrings.indexOf(name);
-    if (role < 0)
-        roles = m_model->roles();
-    else
-        roles << role;
-    emit m_model->m_listModel->itemsChanged(listIndex, 1, roles);
-}
+/*!
+    \qmlclass ListElement QDeclarativeListElement
+    \inqmlmodule QtQuick 2
+    \ingroup qml-working-with-data
+    \brief The ListElement element defines a data item in a ListModel.
 
-void ModelNode::dump(ModelNode *node, int ind)
-{
-    QByteArray indentBa(ind * 4, ' ');
-    const char *indent = indentBa.constData();
+    List elements are defined inside ListModel definitions, and represent items in a
+    list that will be displayed using ListView or \l Repeater items.
 
-    for (int ii = 0; ii < node->values.count(); ++ii) {
-        ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii));
-        if (subNode) {
-            qWarning().nospace() << indent << "Sub-node " << ii;
-            dump(subNode, ind + 1);
-        } else {
-            qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString();
-        }
-    }
+    List elements are defined like other QML elements except that they contain
+    a collection of \e role definitions instead of properties. Using the same
+    syntax as property definitions, roles both define how the data is accessed
+    and include the data itself.
 
-    for (QHash<QString, ModelNode *>::ConstIterator iter = node->properties.begin(); iter != node->properties.end(); ++iter) {
-        qWarning().nospace() << indent << "Property " << iter.key() << ':';
-        dump(iter.value(), ind + 1);
-    }
-}
+    The names used for roles must begin with a lower-case letter and should be
+    common to all elements in a given model. Values must be simple constants; either
+    strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
+    (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
 
-ModelObject::ModelObject(ModelNode *node, NestedListModel *model, QV8Engine *eng)
-: m_model(model), m_node(node), m_meta(new ModelNodeMetaObject(eng, this))
-{
-}
+    \section1 Referencing Roles
 
-void ModelObject::setValue(const QByteArray &name, const QVariant &val)
-{
-    m_meta->setValue(name, val);
-    //setProperty(name.constData(), val);
-}
+    The role names are used by delegates to obtain data from list elements.
+    Each role name is accessible in the delegate's scope, and refers to the
+    corresponding role in the current element. Where a role name would be
+    ambiguous to use, it can be accessed via the \l{ListView::}{model}
+    property (e.g., \c{model.cost} instead of \c{cost}).
 
-void ModelObject::setNodeUpdatesEnabled(bool enable)
-{
-    m_meta->m_enabled = enable;
-}
+    \section1 Example Usage
 
-ModelNodeMetaObject::ModelNodeMetaObject(QV8Engine *eng, ModelObject *object)
-: QDeclarativeOpenMetaObject(object), m_enabled(false), m_engine(eng), m_obj(object)
-{
-}
+    The following model defines a series of list elements, each of which
+    contain "name" and "cost" roles and their associated values.
 
-void ModelNodeMetaObject::propertyWritten(int index)
-{
-    if (!m_enabled)
-        return;
+    \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml model
 
-    QString propName = QString::fromUtf8(name(index));
-    QVariant value = operator[](index);
+    The delegate obtains the name and cost for each element by simply referring
+    to \c name and \c cost:
 
-    v8::HandleScope handle_scope;
-    v8::Context::Scope scope(m_engine->context());
-    v8::Local<v8::Object> object = v8::Object::New();
-    object->Set(m_engine->toString(propName), m_engine->variantWrapper()->newVariant(value));
-    bool changed = m_obj->m_node->setObjectValue(object, false);
-    if (changed)
-        m_obj->m_node->changedProperty(propName);
-}
+    \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml view
+
+    \sa ListModel
+*/
 
 QT_END_NAMESPACE
index ea83ae1..05cc8c2 100644 (file)
@@ -53,6 +53,7 @@
 #include <private/qlistmodelinterface_p.h>
 
 #include <private/qv8engine_p.h>
+#include <private/qpodvector_p.h>
 
 QT_BEGIN_HEADER
 
@@ -60,10 +61,10 @@ QT_BEGIN_NAMESPACE
 
 QT_MODULE(Declarative)
 
-class FlatListModel;
-class NestedListModel;
 class QDeclarativeListModelWorkerAgent;
-struct ModelNode;
+class ListModel;
+class ListLayout;
+
 class Q_DECLARATIVE_PRIVATE_EXPORT QDeclarativeListModel : public QListModelInterface
 {
     Q_OBJECT
@@ -80,8 +81,8 @@ public:
 
     Q_INVOKABLE void clear();
     Q_INVOKABLE void remove(int index);
-    Q_INVOKABLE void append(const QDeclarativeV8Handle &);
-    Q_INVOKABLE void insert(int index, const QDeclarativeV8Handle &);
+    Q_INVOKABLE void append(QDeclarativeV8Function *args);
+    Q_INVOKABLE void insert(QDeclarativeV8Function *args);
     Q_INVOKABLE QDeclarativeV8Handle get(int index) const;
     Q_INVOKABLE void set(int index, const QDeclarativeV8Handle &);
     Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value);
@@ -96,24 +97,31 @@ Q_SIGNALS:
 private:
     friend class QDeclarativeListModelParser;
     friend class QDeclarativeListModelWorkerAgent;
-    friend class FlatListModel;
-    friend class QDeclarativeListModelV8Data;
-    friend struct ModelNode;
+    friend class ModelObject;
+    friend class ModelNodeMetaObject;
+    friend class ListModel;
+    friend class ListElement;
 
     // Constructs a flat list model for a worker agent
-    QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent);
-
-    void set(int index, const QDeclarativeV8Handle &, QList<int> *roles);
-    void setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles);
+    QDeclarativeListModel(QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *agent);
+    QDeclarativeListModel(const QDeclarativeListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent=0);
 
-    bool flatten();
-    bool inWorkerThread() const;
+    QV8Engine *engine() const;
 
     inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); }
 
+    ListLayout *m_layout;
+    ListModel *m_listModel;
+
     QDeclarativeListModelWorkerAgent *m_agent;
-    NestedListModel *m_nested;
-    FlatListModel *m_flat;
+    bool m_mainThread;
+    bool m_primary;
+    mutable QV8Engine *m_engine;
+
+    void emitItemsChanged(int index, int count, const QList<int> &roles);
+    void emitItemsRemoved(int index, int count);
+    void emitItemsInserted(int index, int count);
+    void emitItemsMoved(int from, int to, int n);
 };
 
 // ### FIXME
@@ -146,8 +154,16 @@ private:
     bool definesEmptyList(const QString &);
 
     QString listElementTypeName;
-};
 
+    struct DataStackElement
+    {
+        DataStackElement() : model(0), elementIndex(0) {}
+
+        QString name;
+        ListModel *model;
+        int elementIndex;
+    };
+};
 
 QT_END_NAMESPACE
 
index 99cab4c..f1e47d7 100644 (file)
@@ -64,189 +64,253 @@ QT_BEGIN_NAMESPACE
 
 QT_MODULE(Declarative)
 
-class QDeclarativeOpenMetaObject;
-class QDeclarativeListModelWorkerAgent;
-struct ModelNode;
-class FlatNodeData;
+class ModelObject;
 
-class FlatListModel
+class ModelNodeMetaObject : public QDeclarativeOpenMetaObject
 {
 public:
-    FlatListModel(QDeclarativeListModel *base);
-    ~FlatListModel();
+    ModelNodeMetaObject(ModelObject *object);
+    ~ModelNodeMetaObject();
 
-    QVariant data(int index, int role) const;
+    bool m_enabled;
 
-    QList<int> roles() const;
-    QString toString(int role) const;
+protected:
+    void propertyWritten(int index);
 
-    int count() const;
-    void clear();
-    void remove(int index);
-    bool insert(int index, v8::Handle<v8::Value>);
-    v8::Handle<v8::Value> get(int index) const;
-    void set(int index, v8::Handle<v8::Value>, QList<int> *roles);
-    void setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles);
-    void move(int from, int to, int count);
+private:
 
-private:    
-    friend class QDeclarativeListModelWorkerAgent;
-    friend class QDeclarativeListModel;
-    friend class QDeclarativeListModelV8Data;
-    friend class FlatNodeData;
-
-    bool addValue(v8::Handle<v8::Value> value, QHash<int, QVariant> *row, QList<int> *roles);
-    void insertedNode(int index);
-    void removedNode(int index);
-    void moveNodes(int from, int to, int n);
-
-    QV8Engine *engine() const;
-    QV8Engine *m_engine;
-    QHash<int, QString> m_roles;
-    QHash<QString, int> m_strings;
-    QList<QHash<int, QVariant> > m_values;
-    QDeclarativeListModel *m_listModel;
-
-    QList<FlatNodeData *> m_nodeData;
-    QDeclarativeListModelWorkerAgent *m_parentAgent;
+    ModelObject *m_obj;
 };
 
-/*
-    FlatNodeData and FlatNodeObjectData allow objects returned by get() to still
-    point to the correct list index if move(), insert() or remove() are called.
-*/
-class QV8ListModelResource;
-class FlatNodeData
+class ModelObject : public QObject
 {
+    Q_OBJECT
 public:
-    FlatNodeData(int i)
-        : index(i) {}
-
-    ~FlatNodeData();
-
-    void addData(QV8ListModelResource *data);
-    void removeData(QV8ListModelResource *data);
-
-    int index;
+    ModelObject(QDeclarativeListModel *model, int elementIndex);
+
+    void setValue(const QByteArray &name, const QVariant &val, bool force)
+    {
+        if (force) {
+            QVariant existingValue = m_meta->value(name);
+            if (existingValue.isValid()) {
+                (*m_meta)[name] = QVariant();
+            }
+        }
+        m_meta->setValue(name, val);
+    }
+
+    void setNodeUpdatesEnabled(bool enable)
+    {
+        m_meta->m_enabled = enable;
+    }
+
+    void updateValues();
+    void updateValues(const QList<int> &roles);
+
+    QDeclarativeListModel *m_model;
+    int m_elementIndex;
 
 private:
-    QSet<QV8ListModelResource*> objects;
+    ModelNodeMetaObject *m_meta;
 };
 
-class QV8ListModelResource : public QV8ObjectResource
+class ListLayout
 {
-    V8_RESOURCE_TYPE(ListModelType);
 public:
-    QV8ListModelResource(FlatListModel *model, FlatNodeData *data, QV8Engine *engine);
-    ~QV8ListModelResource();
+    ListLayout() : currentBlock(0), currentBlockOffset(0) {}
+    ListLayout(const ListLayout *other);
+    ~ListLayout();
 
-    FlatListModel *model;
-    FlatNodeData *nodeData;
-};
+    class Role
+    {
+    public:
 
-class NestedListModel
-{
-public:
-    NestedListModel(QDeclarativeListModel *base);
-    ~NestedListModel();
+        Role() : type(Invalid), blockIndex(-1), blockOffset(-1), index(-1), subLayout(0) {}
+        explicit Role(const Role *other);
+        ~Role();
 
-    QHash<int,QVariant> data(int index, const QList<int> &roles, bool *hasNested = 0) const;
-    QVariant data(int index, int role) const;
+        enum DataType
+        {
+            Invalid = -1,
 
-    QList<int> roles() const;
-    QString toString(int role) const;
+            String,
+            Number,
+            Bool,
+            List,
+            QObject
+        };
 
-    int count() const;
-    void clear();
-    void remove(int index);
-    bool insert(int index, v8::Handle<v8::Value>);
-    v8::Handle<v8::Value> get(int index) const;
-    void set(int index, v8::Handle<v8::Value>, QList<int> *roles);
-    void setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles);
-    void move(int from, int to, int count);
+        QString name;
+        DataType type;
+        int blockIndex;
+        int blockOffset;
+        int index;
+        ListLayout *subLayout;
+    };
+
+    const Role *getRoleOrCreate(const QString &key, const QVariant &data);
+    const Role &getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type);
+    const Role &getRoleOrCreate(const QString &key, Role::DataType type);
+
+    const Role &getExistingRole(int index) { return *roles.at(index); }
+    const Role *getExistingRole(const QString &key);
+    const Role *getExistingRole(v8::Handle<v8::String> key);
 
-    QVariant valueForNode(ModelNode *, bool *hasNested = 0) const;
-    void checkRoles() const;
+    int roleCount() const { return roles.count(); }
 
-    ModelNode *_root;
-    bool m_ownsRoot;
-    QDeclarativeListModel *m_listModel;
+    static void sync(ListLayout *src, ListLayout *target);
 
-    QV8Engine *engine() const;
 private:
-    friend struct ModelNode;
-    mutable QStringList roleStrings;
-    mutable bool _rolesOk;
-};
+    const Role &createRole(const QString &key, Role::DataType type);
 
+    int currentBlock;
+    int currentBlockOffset;
+    QVector<Role *> roles;
+    QStringHash<Role *> roleHash;
+};
 
-class ModelNodeMetaObject;
-class ModelObject : public QObject
+class ListElement
 {
-    Q_OBJECT
 public:
-    ModelObject(ModelNode *node, NestedListModel *model, QV8Engine *eng);
-    void setValue(const QByteArray &name, const QVariant &val);
-    void setNodeUpdatesEnabled(bool enable);
 
-    NestedListModel *m_model;
-    ModelNode *m_node;
+    ListElement();
+    ListElement(int existingUid);
+    ~ListElement();
+
+    static void sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash);
+
+    enum
+    {
+        BLOCK_SIZE = 64 - sizeof(int) - sizeof(ListElement *) - sizeof(ModelObject *)
+    };
 
 private:
-    ModelNodeMetaObject *m_meta;
+
+    void destroy(ListLayout *layout);
+
+    int setVariantProperty(const ListLayout::Role &role, const QVariant &d);
+
+    int setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d);
+
+    int setStringProperty(const ListLayout::Role &role, const QString &s);
+    int setDoubleProperty(const ListLayout::Role &role, double n);
+    int setBoolProperty(const ListLayout::Role &role, bool b);
+    int setListProperty(const ListLayout::Role &role, ListModel *m);
+    int setQObjectProperty(const ListLayout::Role &role, QObject *o);
+
+    void setStringPropertyFast(const ListLayout::Role &role, const QString &s);
+    void setDoublePropertyFast(const ListLayout::Role &role, double n);
+    void setBoolPropertyFast(const ListLayout::Role &role, bool b);
+    void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o);
+    void setListPropertyFast(const ListLayout::Role &role, ListModel *m);
+
+    void clearProperty(const ListLayout::Role &role);
+
+    QVariant getProperty(const ListLayout::Role &role, const QDeclarativeListModel *owner, QV8Engine *eng);
+    ListModel *getListProperty(const ListLayout::Role &role);
+    QString *getStringProperty(const ListLayout::Role &role);
+    QObject *getQObjectProperty(const ListLayout::Role &role);
+    QDeclarativeGuard<QObject> *getGuardProperty(const ListLayout::Role &role);
+
+    inline char *getPropertyMemory(const ListLayout::Role &role);
+
+    int getUid() const { return uid; }
+
+    char data[BLOCK_SIZE];
+    ListElement *next;
+
+    int uid;
+    ModelObject *m_objectCache;
+
+    friend class ListModel;
 };
 
-class ModelNodeMetaObject : public QDeclarativeOpenMetaObject
+class ListModel
 {
 public:
-    ModelNodeMetaObject(QV8Engine *eng, ModelObject *object);
 
-    bool m_enabled;
+    ListModel(ListLayout *layout, QDeclarativeListModel *modelCache, int uid);
+    ~ListModel() {}
 
-protected:
-    void propertyWritten(int index);
+    void destroy();
 
-private:
-    QV8Engine *m_engine;
-    ModelObject *m_obj;
-};
+    int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data);
+    int setExistingProperty(int uid, const QString &key, v8::Handle<v8::Value> data);
 
-/*
-    A ModelNode is created for each item in a NestedListModel.
-*/
-struct ModelNode
-{
-    ModelNode(NestedListModel *model);
-    ~ModelNode();
+    QVariant getProperty(int elementIndex, int roleIndex, const QDeclarativeListModel *owner, QV8Engine *eng);
+    ListModel *getListProperty(int elementIndex, const ListLayout::Role &role);
+
+    int roleCount() const
+    {
+        return m_layout->roleCount();
+    }
+
+    const ListLayout::Role &getExistingRole(int index)
+    {
+        return m_layout->getExistingRole(index);
+    }
 
-    QList<QVariant> values;
-    QHash<QString, ModelNode *> properties;
+    const ListLayout::Role &getOrCreateListRole(const QString &name)
+    {
+        return m_layout->getRoleOrCreate(name, ListLayout::Role::List);
+    }
+
+    int elementCount() const
+    {
+        return elements.count();
+    }
+
+    void set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles);
+    void set(int elementIndex, v8::Handle<v8::Object> object);
+
+    int append(v8::Handle<v8::Object> object);
+    void insert(int elementIndex, v8::Handle<v8::Object> object);
 
     void clear();
+    void remove(int index);
+
+    int appendElement();
+    void insertElement(int index);
 
-    QDeclarativeListModel *model(const NestedListModel *model);
-    ModelObject *object(const NestedListModel *model);
+    void move(int from, int to, int n);
 
-    bool setObjectValue(v8::Handle<v8::Value> valuemap, bool writeToCache = true);
-    void setListValue(v8::Handle<v8::Value> valuelist);
-    bool setProperty(const QString& prop, const QVariant& val);
-    void changedProperty(const QString &name) const;
-    void updateListIndexes();
-    static void dump(ModelNode *node, int ind);
+    int getUid() const { return m_uid; }
 
-    QDeclarativeListModel *modelCache;
-    ModelObject *objectCache;
-    bool isArray;
+    static int allocateUid();
+
+    static void sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *srcModelHash);
+
+    ModelObject *getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex);
+
+private:
+    QPODVector<ListElement *, 4> elements;
+    ListLayout *m_layout;
+    int m_uid;
 
-    NestedListModel *m_model;
-    int listIndex;  // only used for top-level nodes within a list
+    QDeclarativeListModel *m_modelCache;
+
+    struct ElementSync
+    {
+        ElementSync() : src(0), target(0) {}
+
+        ListElement *src;
+        ListElement *target;
+    };
+
+    void newElement(int index);
+
+    void updateCacheIndices();
+
+    friend class ListElement;
+    friend class QDeclarativeListModelWorkerAgent;
+
+    static QAtomicInt uidCounter;
 };
 
+Q_DECLARE_METATYPE(ListModel *);
 
 QT_END_NAMESPACE
 
-Q_DECLARE_METATYPE(ModelNode *)
-
 QT_END_HEADER
 
 #endif // QDECLARATIVELISTMODEL_P_P_H
index b27e3e3..2fed035 100644 (file)
 QT_BEGIN_NAMESPACE
 
 
-void QDeclarativeListModelWorkerAgent::Data::clearChange() 
-{ 
-    changes.clear(); 
+void QDeclarativeListModelWorkerAgent::Data::clearChange(QDeclarativeListModel *model)
+{
+    int uid = model->m_listModel->getUid();
+
+    for (int i=0 ; i < changes.count() ; ++i) {
+        if (changes[i].modelUid == uid) {
+            changes.removeAt(i);
+            --i;
+        }
+    }
 }
 
-void QDeclarativeListModelWorkerAgent::Data::insertChange(int index, int count) 
+void QDeclarativeListModelWorkerAgent::Data::insertChange(QDeclarativeListModel *model, int index, int count)
 {
-    Change c = { Change::Inserted, index, count, 0, QList<int>() };
+    Change c = { model->m_listModel->getUid(), Change::Inserted, index, count, 0, QList<int>() };
     changes << c;
 }
 
-void QDeclarativeListModelWorkerAgent::Data::removeChange(int index, int count) 
+void QDeclarativeListModelWorkerAgent::Data::removeChange(QDeclarativeListModel *model, int index, int count)
 {
-    Change c = { Change::Removed, index, count, 0, QList<int>() };
+    Change c = { model->m_listModel->getUid(), Change::Removed, index, count, 0, QList<int>() };
     changes << c;
 }
 
-void QDeclarativeListModelWorkerAgent::Data::moveChange(int index, int count, int to)
+void QDeclarativeListModelWorkerAgent::Data::moveChange(QDeclarativeListModel *model, int index, int count, int to)
 {
-    Change c = { Change::Moved, index, count, to, QList<int>() };
+    Change c = { model->m_listModel->getUid(), Change::Moved, index, count, to, QList<int>() };
     changes << c;
 }
 
-void QDeclarativeListModelWorkerAgent::Data::changedChange(int index, int count, const QList<int> &roles)
+void QDeclarativeListModelWorkerAgent::Data::changedChange(QDeclarativeListModel *model, int index, int count, const QList<int> &roles)
 {
-    Change c = { Change::Changed, index, count, 0, roles };
+    Change c = { model->m_listModel->getUid(), Change::Changed, index, count, 0, roles };
     changes << c;
 }
 
 QDeclarativeListModelWorkerAgent::QDeclarativeListModelWorkerAgent(QDeclarativeListModel *model)
-: m_engine(0), m_ref(1), m_orig(model), m_copy(new QDeclarativeListModel(model, this))
+: m_ref(1), m_orig(model), m_copy(new QDeclarativeListModel(model, this))
 {
 }
 
@@ -93,14 +100,7 @@ QDeclarativeListModelWorkerAgent::~QDeclarativeListModelWorkerAgent()
 
 void QDeclarativeListModelWorkerAgent::setV8Engine(QV8Engine *eng)
 {
-    m_engine = eng;
-    if (m_copy->m_flat)
-        m_copy->m_flat->m_engine = eng;
-}
-
-QV8Engine *QDeclarativeListModelWorkerAgent::v8engine() const
-{
-    return m_engine;
+    m_copy->m_engine = eng;
 }
 
 void QDeclarativeListModelWorkerAgent::addref()
@@ -123,36 +123,22 @@ int QDeclarativeListModelWorkerAgent::count() const
 
 void QDeclarativeListModelWorkerAgent::clear()
 {
-    data.clearChange();
-    data.removeChange(0, m_copy->count());
     m_copy->clear();
 }
 
 void QDeclarativeListModelWorkerAgent::remove(int index)
 {
-    int count = m_copy->count();
     m_copy->remove(index);
-
-    if (m_copy->count() != count)
-        data.removeChange(index, 1);
 }
 
-void QDeclarativeListModelWorkerAgent::append(const QDeclarativeV8Handle &value)
+void QDeclarativeListModelWorkerAgent::append(QDeclarativeV8Function *args)
 {
-    int count = m_copy->count();
-    m_copy->append(value);
-
-    if (m_copy->count() != count)
-        data.insertChange(m_copy->count() - 1, 1);
+    m_copy->append(args);
 }
 
-void QDeclarativeListModelWorkerAgent::insert(int index, const QDeclarativeV8Handle &value)
+void QDeclarativeListModelWorkerAgent::insert(QDeclarativeV8Function *args)
 {
-    int count = m_copy->count();
-    m_copy->insert(index, value);
-
-    if (m_copy->count() != count)
-        data.insertChange(index, 1);
+    m_copy->insert(args);
 }
 
 QDeclarativeV8Handle QDeclarativeListModelWorkerAgent::get(int index) const
@@ -162,24 +148,17 @@ QDeclarativeV8Handle QDeclarativeListModelWorkerAgent::get(int index) const
 
 void QDeclarativeListModelWorkerAgent::set(int index, const QDeclarativeV8Handle &value)
 {
-    QList<int> roles;
-    m_copy->set(index, value, &roles);
-    if (!roles.isEmpty())
-        data.changedChange(index, 1, roles);
+    m_copy->set(index, value);
 }
 
 void QDeclarativeListModelWorkerAgent::setProperty(int index, const QString& property, const QVariant& value)
 {
-    QList<int> roles;
-    m_copy->setProperty(index, property, value, &roles);
-    if (!roles.isEmpty())
-        data.changedChange(index, 1, roles);
+    m_copy->setProperty(index, property, value);
 }
 
 void QDeclarativeListModelWorkerAgent::move(int from, int to, int count)
 {
     m_copy->move(from, to, count);
-    data.moveChange(from, to, count);
 }
 
 void QDeclarativeListModelWorkerAgent::sync()
@@ -195,14 +174,10 @@ void QDeclarativeListModelWorkerAgent::sync()
     mutex.unlock();
 }
 
-void QDeclarativeListModelWorkerAgent::changedData(int index, int count, const QList<int> &roles)
-{
-    data.changedChange(index, count, roles);
-}
-
 bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
 {
     if (e->type() == QEvent::User) {
+
         QMutexLocker locker(&mutex);
         Sync *s = static_cast<Sync *>(e);
 
@@ -211,56 +186,35 @@ bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
         if (m_copy) {
             bool cc = m_orig->count() != s->list->count();
 
-            FlatListModel *orig = m_orig->m_flat;
-            FlatListModel *copy = s->list->m_flat;
-            if (!orig || !copy) {
-                syncDone.wakeAll();
-                return QObject::event(e);
-            }
-
-            orig->m_roles = copy->m_roles;
-            orig->m_strings = copy->m_strings;
-            orig->m_values = copy->m_values;
+            QHash<int, ListModel *> targetModelHash;
+            ListModel::sync(s->list->m_listModel, m_orig->m_listModel, &targetModelHash);
 
-            // update the orig->m_nodeData list
             for (int ii = 0; ii < changes.count(); ++ii) {
                 const Change &change = changes.at(ii);
-                switch (change.type) {
-                case Change::Inserted:
-                    orig->insertedNode(change.index);
-                    break;
-                case Change::Removed:
-                    orig->removedNode(change.index);
-                    break;
-                case Change::Moved:
-                    orig->moveNodes(change.index, change.to, change.count);
-                    break;
-                case Change::Changed:
-                    break;
+
+                ListModel *model = targetModelHash.value(change.modelUid);
+
+                if (model && model->m_modelCache) {
+                    switch (change.type) {
+                    case Change::Inserted:
+                        emit model->m_modelCache->itemsInserted(change.index, change.count);
+                        break;
+                    case Change::Removed:
+                        emit model->m_modelCache->itemsRemoved(change.index, change.count);
+                        break;
+                    case Change::Moved:
+                        emit model->m_modelCache->itemsMoved(change.index, change.to, change.count);
+                        break;
+                    case Change::Changed:
+                        emit model->m_modelCache->itemsChanged(change.index, change.count, change.roles);
+                        break;
+                    }
                 }
             }
 
             syncDone.wakeAll();
             locker.unlock();
 
-            for (int ii = 0; ii < changes.count(); ++ii) {
-                const Change &change = changes.at(ii);
-                switch (change.type) {
-                case Change::Inserted:
-                    emit m_orig->itemsInserted(change.index, change.count);
-                    break;
-                case Change::Removed:
-                    emit m_orig->itemsRemoved(change.index, change.count);
-                    break;
-                case Change::Moved:
-                    emit m_orig->itemsMoved(change.index, change.to, change.count);
-                    break;
-                case Change::Changed:
-                    emit m_orig->itemsChanged(change.index, change.count, change.roles);
-                    break;
-                }
-            }
-
             if (cc)
                 emit m_orig->countChanged();
         } else {
index 12505e9..b6c42ae 100644 (file)
@@ -79,7 +79,6 @@ public:
     ~QDeclarativeListModelWorkerAgent();
 
     void setV8Engine(QV8Engine *eng);
-    QV8Engine *v8engine() const;
 
     void addref();
     void release();
@@ -88,8 +87,8 @@ public:
 
     Q_INVOKABLE void clear();
     Q_INVOKABLE void remove(int index);
-    Q_INVOKABLE void append(const QDeclarativeV8Handle &);
-    Q_INVOKABLE void insert(int index, const QDeclarativeV8Handle &);
+    Q_INVOKABLE void append(QDeclarativeV8Function *args);
+    Q_INVOKABLE void insert(QDeclarativeV8Function *args);
     Q_INVOKABLE QDeclarativeV8Handle get(int index) const;
     Q_INVOKABLE void set(int index, const QDeclarativeV8Handle &);
     Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value);
@@ -116,10 +115,11 @@ protected:
 
 private:
     friend class QDeclarativeWorkerScriptEnginePrivate;
-    friend class QDeclarativeListModelV8Data;
-    QV8Engine *m_engine;
+    friend class QDeclarativeListModel;
 
-    struct Change {
+    struct Change
+    {
+        int modelUid;
         enum { Inserted, Removed, Moved, Changed } type;
         int index; // Inserted/Removed/Moved/Changed
         int count; // Inserted/Removed/Moved/Changed
@@ -127,14 +127,15 @@ private:
         QList<int> roles;
     };
 
-    struct Data {
+    struct Data
+    {
         QList<Change> changes;
 
-        void clearChange();
-        void insertChange(int index, int count);
-        void removeChange(int index, int count);
-        void moveChange(int index, int count, int to);
-        void changedChange(int index, int count, const QList<int> &roles);
+        void clearChange(QDeclarativeListModel *model);
+        void insertChange(QDeclarativeListModel *model, int index, int count);
+        void removeChange(QDeclarativeListModel *model, int index, int count);
+        void moveChange(QDeclarativeListModel *model, int index, int count, int to);
+        void changedChange(QDeclarativeListModel *model, int index, int count, const QList<int> &roles);
     };
     Data data;
 
@@ -144,8 +145,6 @@ private:
         QDeclarativeListModel *list;
     };
 
-    void changedData(int index, int count, const QList<int> &roles);
-
     QAtomicInt m_ref;
     QDeclarativeListModel *m_orig;
     QDeclarativeListModel *m_copy;
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js
new file mode 100644 (file)
index 0000000..cb9dfa6
--- /dev/null
@@ -0,0 +1,8 @@
+WorkerScript.onMessage = function(msg) {
+    if (msg.action == 'removeItem') {
+        msg.model.remove(0);
+    } else if (msg.action == 'dosync') {
+        msg.model.sync();
+    }
+    WorkerScript.sendMessage({'done': true})
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml
new file mode 100644 (file)
index 0000000..e2361ac
--- /dev/null
@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Item {
+  id: item
+  property variant model
+  property bool done: false
+
+  WorkerScript {
+    id: worker
+    source: "workerremoveelement.js"
+    onMessage: {
+      item.done = true
+    }
+  }
+
+  function addItem() {
+    model.append({ 'data': 1 });
+
+    var element = model.get(0);
+  }
+
+  function removeItemViaWorker() {
+    done = false
+    var msg = { 'action': 'removeItem', 'model': model }
+    worker.sendMessage(msg);
+  }
+
+  function doSync() {
+    done = false
+    var msg = { 'action': 'dosync', 'model': model }
+    worker.sendMessage(msg);
+  }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js
new file mode 100644 (file)
index 0000000..f63dd68
--- /dev/null
@@ -0,0 +1,9 @@
+WorkerScript.onMessage = function(msg) {
+    if (msg.action == 'removeList') {
+        msg.model.remove(0);
+    } else if (msg.action == 'dosync') {
+        msg.model.sync();
+    }
+    WorkerScript.sendMessage({'done': true})
+}
+
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml
new file mode 100644 (file)
index 0000000..bdb5e02
--- /dev/null
@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Item {
+  id: item
+  property variant model
+  property bool done: false
+
+  WorkerScript {
+    id: worker
+    source: "workerremovelist.js"
+    onMessage: {
+      item.done = true
+    }
+  }
+
+  function addList() {
+    model.append({ 'data': [ { 'subData': 1 } ] });
+
+    var element = model.get(0);
+  }
+
+  function removeListViaWorker() {
+    done = false
+    var msg = { 'action': 'removeList', 'model': model }
+    worker.sendMessage(msg);
+  }
+
+  function doSync() {
+    done = false
+    var msg = { 'action': 'dosync', 'model': model }
+    worker.sendMessage(msg);
+  }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js
new file mode 100644 (file)
index 0000000..9b8d8fa
--- /dev/null
@@ -0,0 +1,8 @@
+WorkerScript.onMessage = function(msg) {
+    if (msg.action == 'addItem') {
+        msg.model.get(0).level0.append({ 'level1': 33 });
+    } else if (msg.action == 'dosync') {
+        msg.model.sync();
+    }
+    WorkerScript.sendMessage({'done': true})
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml
new file mode 100644 (file)
index 0000000..c21cd43
--- /dev/null
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+
+Item {
+  id: item
+  property variant model
+  property bool done: false
+
+  WorkerScript {
+    id: worker
+    source: "workersync.js"
+    onMessage: {
+      item.done = true
+    }
+  }
+
+  function addItem0() {
+    model.append({ 'level0': [ { 'level1': 29 } ] });
+    model.append({ 'level0': [ { 'level1': 37 } ] });
+  }
+
+  function addItemViaWorker() {
+    done = false
+    var msg = { 'action': 'addItem', 'model': model }
+    worker.sendMessage(msg);
+  }
+
+  function doSync() {
+    done = false
+    var msg = { 'action': 'dosync', 'model': model }
+    worker.sendMessage(msg);
+  }
+}
index bc6c991..23e9fc4 100644 (file)
 Q_DECLARE_METATYPE(QList<int>)
 Q_DECLARE_METATYPE(QList<QVariantHash>)
 
+#define RUNEVAL(object, string) \
+    QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
+
+inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
+{
+    QDeclarativeExpression expr(engine->rootContext(), 0, str);
+    return expr.evaluate();
+}
+
+#define RUNEXPR(string) runexpr(&engine, QString(string))
+
 class tst_qdeclarativelistmodel : public QObject
 {
     Q_OBJECT
@@ -65,6 +76,8 @@ private:
     QQuickItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
     void waitForWorker(QQuickItem *item);
 
+    static bool compareVariantList(const QVariantList &testList, QVariant object);
+
 private slots:
     void static_types();
     void static_types_data();
@@ -78,16 +91,12 @@ private slots:
     void dynamic_worker();
     void dynamic_worker_sync_data();
     void dynamic_worker_sync();
-    void convertNestedToFlat_fail();
-    void convertNestedToFlat_fail_data();
-    void convertNestedToFlat_ok();
-    void convertNestedToFlat_ok_data();
     void enumerate();
     void error_data();
     void error();
     void syncError();
-    void set();
     void get();
+    void set();
     void get_data();
     void get_worker();
     void get_worker_data();
@@ -101,8 +110,64 @@ private slots:
     void property_changes_worker_data();
     void clear();
     void signal_handlers();
+    void worker_sync();
+    void worker_remove_element();
+    void worker_remove_list();
 };
 
+bool tst_qdeclarativelistmodel::compareVariantList(const QVariantList &testList, QVariant object)
+{
+    bool allOk = true;
+
+    QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel *>(object.value<QObject *>());
+    if (model == 0)
+        return false;
+
+    if (model->count() != testList.count())
+        return false;
+
+    for (int i=0 ; i < testList.count() ; ++i) {
+        const QVariant &testVariant = testList.at(i);
+        if (testVariant.type() != QVariant::Map)
+            return false;
+        const QVariantMap &map = testVariant.toMap();
+
+        const QList<int> &roles = model->roles();
+
+        QVariantMap::const_iterator it = map.begin();
+        QVariantMap::const_iterator end = map.end();
+
+        while (it != end) {
+            const QString &testKey = it.key();
+            const QVariant &testData = it.value();
+
+            int roleIndex = -1;
+            for (int j=0 ; j < roles.count() ; ++j) {
+                if (model->toString(roles[j]).compare(testKey) == 0) {
+                    roleIndex = j;
+                    break;
+                }
+            }
+
+            if (roleIndex == -1)
+                return false;
+
+            const QVariant &modelData = model->data(i, roleIndex);
+
+            if (testData.type() == QVariant::List) {
+                const QVariantList &subList = testData.toList();
+                allOk = allOk && compareVariantList(subList, modelData);
+            } else {
+                allOk = allOk && (testData == modelData);
+            }
+
+            ++it;
+        }
+    }
+
+    return allOk;
+}
+
 int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
 {
     QList<int> roles = model->roles();
@@ -118,7 +183,7 @@ QQuickItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng,
     QQuickItem *item = qobject_cast<QQuickItem*>(component->create());
     QDeclarativeEngine::setContextForObject(model, eng->rootContext());
     if (item)
-        item->setProperty("model", qVariantFromValue(model)); 
+        item->setProperty("model", qVariantFromValue(model));
     return item;
 }
 
@@ -141,43 +206,66 @@ void tst_qdeclarativelistmodel::static_types_data()
 {
     QTest::addColumn<QString>("qml");
     QTest::addColumn<QVariant>("value");
+    QTest::addColumn<QString>("error");
 
     QTest::newRow("string")
         << "ListElement { foo: \"bar\" }"
-        << QVariant(QString("bar"));
+        << QVariant(QString("bar"))
+        << QString();
 
     QTest::newRow("real")
         << "ListElement { foo: 10.5 }"
-        << QVariant(10.5);
+        << QVariant(10.5)
+        << QString();
 
     QTest::newRow("real0")
         << "ListElement { foo: 0 }"
-        << QVariant(double(0));
+        << QVariant(double(0))
+        << QString();
 
     QTest::newRow("bool")
         << "ListElement { foo: false }"
-        << QVariant(false);
+        << QVariant(false)
+        << QString();
 
     QTest::newRow("bool")
         << "ListElement { foo: true }"
-        << QVariant(true);
+        << QVariant(true)
+        << QString();
 
     QTest::newRow("enum")
         << "ListElement { foo: Text.AlignHCenter }"
-        << QVariant(double(QQuickText::AlignHCenter));
+        << QVariant(double(QQuickText::AlignHCenter))
+        << QString();
 
     QTest::newRow("Qt enum")
         << "ListElement { foo: Qt.AlignBottom }"
-        << QVariant(double(Qt::AlignBottom));
+        << QVariant(double(Qt::AlignBottom))
+        << QString();
+
+    QTest::newRow("role error")
+        << "ListElement { foo: 1 } ListElement { foo: 'string' }"
+        << QVariant()
+        << QString("<Unknown File>: Can't assign to pre-existing role of different type foo");
+
+    QTest::newRow("list type error")
+        << "ListElement { foo: 1 } ListElement { foo: ListElement { bar: 1 } }"
+        << QVariant()
+        << QString("<Unknown File>: Can't assign to pre-existing role of different type foo");
 }
 
 void tst_qdeclarativelistmodel::static_types()
 {
     QFETCH(QString, qml);
     QFETCH(QVariant, value);
+    QFETCH(QString, error);
 
     qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
 
+    if (!error.isEmpty()) {
+        QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
+    }
+
     QDeclarativeEngine engine;
     QDeclarativeComponent component(&engine);
     component.setData(qml.toUtf8(),
@@ -188,10 +276,12 @@ void tst_qdeclarativelistmodel::static_types()
     QObject *obj = component.create();
     QVERIFY(obj != 0);
 
-    QVariant actual = obj->property("test");
+    if (error.isEmpty()) {
+        QVariant actual = obj->property("test");
 
-    QCOMPARE(actual, value);
-    QCOMPARE(actual.toString(), value.toString());
+        QCOMPARE(actual, value);
+        QCOMPARE(actual.toString(), value.toString());
+    }
 
     delete obj;
 }
@@ -270,11 +360,11 @@ void tst_qdeclarativelistmodel::static_nestedElements()
     QFETCH(int, elementCount);
 
     QStringList elements;
-    for (int i=0; i<elementCount; i++) 
+    for (int i=0; i<elementCount; i++)
         elements.append("ListElement { a: 1; b: 2 }");
     QString elementsStr = elements.join(",\n") + "\n";
 
-    QString componentStr = 
+    QString componentStr =
         "import QtQuick 2.0\n"
         "Item {\n"
         "    property variant count: model.get(0).attributes.count\n"
@@ -283,7 +373,7 @@ void tst_qdeclarativelistmodel::static_nestedElements()
         "        ListElement {\n"
         "            attributes: [\n";
     componentStr += elementsStr.toUtf8().constData();
-    componentStr += 
+    componentStr +=
         "            ]\n"
         "        }\n"
         "    }\n"
@@ -320,7 +410,6 @@ void tst_qdeclarativelistmodel::dynamic_data()
     QTest::addColumn<QString>("warning");
 
     // Simple flat model
-
     QTest::newRow("count") << "count" << 0 << "";
 
     QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
@@ -336,7 +425,8 @@ void tst_qdeclarativelistmodel::dynamic_data()
     QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
     QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
     QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
-    QTest::newRow("append4b") << "{append([1,2,3])}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
+    QTest::newRow("append4b") << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "";
+    QTest::newRow("append4c") << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "";
 
     QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "";
     QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "";
@@ -361,7 +451,8 @@ void tst_qdeclarativelistmodel::dynamic_data()
     QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "";
     QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range";
     QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
-    QTest::newRow("insert5b") << "{insert(0,[1,2,3])}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
+    QTest::newRow("insert5b") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "";
+    QTest::newRow("insert5c") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "";
 
     QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "";
     QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "";
@@ -396,8 +487,11 @@ void tst_qdeclarativelistmodel::dynamic_data()
     QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
     QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
 
-    // Nested models
+    QTest::newRow("large1") << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "";
+
+    QTest::newRow("datatypes1") << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to pre-existing role of different type a";
 
+    // Nested models
     QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
     QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";
     QTest::newRow("nested-append3") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.append({'a':4});get(0).bars.get(3).a}" << 4 << "";
@@ -446,12 +540,8 @@ void tst_qdeclarativelistmodel::dynamic_worker()
     QFETCH(int, result);
     QFETCH(QString, warning);
 
-    if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
-        return;
-
-    // This is same as dynamic() except it applies the test to a ListModel called 
-    // from a WorkerScript (i.e. testing the internal FlatListModel that is created
-    // by the WorkerListModelAgent)
+    // This is same as dynamic() except it applies the test to a ListModel called
+    // from a WorkerScript.
 
     QDeclarativeListModel model;
     QDeclarativeEngine eng;
@@ -498,7 +588,7 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
     // This is the same as dynamic_worker() except that it executes a set of list operations
     // from the worker script, calls sync(), and tests the changes are reflected in the
     // list in the main thread
-    
+
     QDeclarativeListModel model;
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
@@ -518,140 +608,56 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
 
     // execute a set of commands on the worker list model, then check the
     // changes are reflected in the list model in the main thread
-    if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
-        QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
-
-    if (QByteArray(QTest::currentDataTag()).startsWith("nested-set"))
-        QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
-
-    QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", 
+    QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
             Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
     waitForWorker(item);
 
     QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
-    if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
-        QCOMPARE(e.evaluate().toInt(), result);
-
-    delete item;
-    qApp->processEvents();
-}
-
-#define RUNEVAL(object, string) \
-    QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
-
-inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
-{
-    QDeclarativeExpression expr(engine->rootContext(), 0, str);
-    return expr.evaluate();
-}
-
-#define RUNEXPR(string) runexpr(&engine, QString(string))
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_fail()
-{
-    // If a model has nested data, it cannot be used at all from a worker script
-
-    QFETCH(QString, script);
-
-    QDeclarativeListModel model;
-    QDeclarativeEngine eng;
-    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
-    QQuickItem *item = createWorkerTest(&eng, &component, &model);
-    QVERIFY(item != 0);
-
-    RUNEVAL(item, "model.append({foo: 123})");
-    RUNEVAL(item, "model.append({foo: [{}, {}]})");
-
-    QCOMPARE(model.count(), 2);
-
-    QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: List contains list-type data and cannot be used from a worker script");
-    QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
-    waitForWorker(item);
-
-    QCOMPARE(model.count(), 2);
+    QCOMPARE(e.evaluate().toInt(), result);
 
     delete item;
     qApp->processEvents();
 }
 
-void tst_qdeclarativelistmodel::convertNestedToFlat_fail_data()
-{
-    QTest::addColumn<QString>("script");
-
-    QTest::newRow("clear") << "clear()";
-    QTest::newRow("remove") << "remove(0)";
-    QTest::newRow("append") << "append({'x':1})";
-    QTest::newRow("insert") << "insert(0, {'x':1})";
-    QTest::newRow("set") << "set(0, {'foo':1})";
-    QTest::newRow("setProperty") << "setProperty(0, 'foo', 1})";
-    QTest::newRow("move") << "move(0, 1, 1})";
-    QTest::newRow("get") << "get(0)";
-}
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_ok()
-
+void tst_qdeclarativelistmodel::enumerate()
 {
-    // If a model only has plain data, it can be modified from a worker script. However,
-    // once the model is used from a worker script, it no longer accepts nested data
-
-    QFETCH(QString, script);
-
-    QDeclarativeListModel model;
     QDeclarativeEngine eng;
-    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
-    QQuickItem *item = createWorkerTest(&eng, &component, &model);
+    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
+    QVERIFY(!component.isError());
+    QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
     QVERIFY(item != 0);
 
-    RUNEVAL(item, "model.append({foo: 123})");
-
-    QCOMPARE(model.count(), 1);
-
-    QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
-    waitForWorker(item);
-
-    // can still add plain data
-    int count = model.count();
-
-    RUNEVAL(item, "model.append({foo: 123})");
+    QLatin1String expectedStrings[] = {
+        QLatin1String("val1=1Y"),
+        QLatin1String("val2=2Y"),
+        QLatin1String("val3=strY"),
+        QLatin1String("val4=falseN"),
+        QLatin1String("val5=trueY")
+    };
 
-    QCOMPARE(model.count(), count+1);
+    int expectedStringCount = sizeof(expectedStrings) / sizeof(expectedStrings[0]);
 
-    const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
+    QStringList r = item->property("result").toString().split(":");
 
-    QTest::ignoreMessage(QtWarningMsg, warning);
-    RUNEVAL(item, "model.append({foo: [{}, {}]})");
+    int matchCount = 0;
+    for (int i=0 ; i < expectedStringCount ; ++i) {
+        const QLatin1String &expectedString = expectedStrings[i];
 
-    QTest::ignoreMessage(QtWarningMsg, warning);
-    RUNEVAL(item, "model.insert(0, {foo: [{}, {}]})");
+        QStringList::const_iterator it = r.begin();
+        QStringList::const_iterator end = r.end();
 
-    QTest::ignoreMessage(QtWarningMsg, warning);
-    RUNEVAL(item, "model.set(0, {foo: [{}, {}]})");
+        while (it != end) {
+            if (it->compare(expectedString) == 0) {
+                ++matchCount;
+                break;
+            }
+            ++it;
+        }
+    }
 
-    QCOMPARE(model.count(), count+1);
+    QVERIFY(matchCount == expectedStringCount);
 
     delete item;
-    qApp->processEvents();
-}
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_ok_data()
-{
-    convertNestedToFlat_fail_data();
-}
-
-void tst_qdeclarativelistmodel::enumerate()
-{
-    QDeclarativeEngine eng;
-    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
-    QVERIFY(!component.isError());
-    QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
-    QVERIFY(item != 0);
-    QStringList r = item->property("result").toString().split(":");
-    QCOMPARE(r[0],QLatin1String("val1=1Y"));
-    QCOMPARE(r[1],QLatin1String("val2=2Y"));
-    QCOMPARE(r[2],QLatin1String("val3=strY"));
-    QCOMPARE(r[3],QLatin1String("val4=falseN"));
-    QCOMPARE(r[4],QLatin1String("val5=trueY"));
-    delete item;
 }
 
 void tst_qdeclarativelistmodel::error_data()
@@ -752,11 +758,15 @@ void tst_qdeclarativelistmodel::set()
     RUNEXPR("model.set(0, {test:true})");
 
     QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache
-    QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true)); 
+    QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
 
     RUNEXPR("model.set(0, {test:false})");
     QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
-    QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false)); 
+    QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
+
+    QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Can't create role for unsupported data type");
+    QVariant invalidData = QColor();
+    model.setProperty(0, "test", invalidData);
 }
 
 /*
@@ -773,21 +783,31 @@ void tst_qdeclarativelistmodel::get()
     QDeclarativeComponent component(&engine);
     component.setData(
         "import QtQuick 2.0\n"
-        "ListModel { \n"
-            "ListElement { roleA: 100 }\n"
-            "ListElement { roleA: 200; roleB: 400 } \n"
-            "ListElement { roleA: 200; roleB: 400 } \n"
-         "}", QUrl());
+        "ListModel {}\n", QUrl());
     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
-    int role = roleFromName(model, roleName);
-    QVERIFY(role >= 0);
+    engine.rootContext()->setContextProperty("model", model);
+
+    RUNEXPR("model.append({roleA: 100})");
+    RUNEXPR("model.append({roleA: 200, roleB: 400})");
+    RUNEXPR("model.append({roleA: 200, roleB: 400})");
+    RUNEXPR("model.append({roleC: {} })");
+    RUNEXPR("model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
 
     QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
     QDeclarativeExpression expr(engine.rootContext(), model, expression);
     expr.evaluate();
     QVERIFY(!expr.hasError());
 
-    QCOMPARE(model->data(index, role), roleValue);
+    int role = roleFromName(model, roleName);
+    QVERIFY(role >= 0);
+
+    if (roleValue.type() == QVariant::List) {
+        const QVariantList &list = roleValue.toList();
+        QVERIFY(compareVariantList(list, model->data(index, role)));
+    } else {
+        QCOMPARE(model->data(index, role), roleValue);
+    }
+
     QCOMPARE(spy.count(), 1);
 
     QList<QVariant> spyResult = spy.takeFirst();
@@ -805,19 +825,16 @@ void tst_qdeclarativelistmodel::get_data()
     QTest::addColumn<QString>("roleName");
     QTest::addColumn<QVariant>("roleValue");
 
-    QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500); 
-    QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500); 
+    QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
+    QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
 
     QVariantMap map;
-    map["zzz"] = 123;
-    QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
-
     QVariantList list;
     map.clear(); map["a"] = 50; map["b"] = 500;
     list << map;
     map.clear(); map["c"] = 1000;
     list << map;
-    QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
+    QTest::newRow("list of objects") << "get(2).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list);
 }
 
 void tst_qdeclarativelistmodel::get_worker()
@@ -837,13 +854,12 @@ void tst_qdeclarativelistmodel::get_worker()
     RUNEVAL(item, "model.append({roleA: 100})");
     RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
     RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
+    RUNEVAL(item, "model.append({roleC: {} })");
+    RUNEVAL(item, "model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
 
     int role = roleFromName(&model, roleName);
     QVERIFY(role >= 0);
 
-    const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
-    if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
-        QTest::ignoreMessage(QtWarningMsg, warning);
     QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
 
     // in the worker thread, change the model data and call sync()
@@ -852,18 +868,19 @@ void tst_qdeclarativelistmodel::get_worker()
     waitForWorker(item);
 
     // see if we receive the model changes in the main thread's model
-    if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
-        QVERIFY(model.data(index, role) != roleValue);
-        QCOMPARE(spy.count(), 0);
+    if (roleValue.type() == QVariant::List) {
+        const QVariantList &list = roleValue.toList();
+        QVERIFY(compareVariantList(list, model.data(index, role)));
     } else {
         QCOMPARE(model.data(index, role), roleValue);
-        QCOMPARE(spy.count(), 1);
-
-        QList<QVariant> spyResult = spy.takeFirst();
-        QCOMPARE(spyResult.at(0).toInt(), index);
-        QCOMPARE(spyResult.at(1).toInt(), 1);  // only 1 item is modified at a time
-        QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
     }
+
+    QCOMPARE(spy.count(), 1);
+
+    QList<QVariant> spyResult = spy.takeFirst();
+    QCOMPARE(spyResult.at(0).toInt(), index);
+    QCOMPARE(spyResult.at(1).toInt(), 1);  // only 1 item is modified at a time
+    QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
 }
 
 void tst_qdeclarativelistmodel::get_worker_data()
@@ -881,39 +898,48 @@ void tst_qdeclarativelistmodel::get_nested()
     QFETCH(QString, roleName);
     QFETCH(QVariant, roleValue);
 
-    QDeclarativeEngine eng;
-    QDeclarativeComponent component(&eng);
+    if (roleValue.type() == QVariant::Map)
+        return;
+
+    QDeclarativeEngine engine;
+    QDeclarativeComponent component(&engine);
     component.setData(
         "import QtQuick 2.0\n"
-        "ListModel { \n"
-            "ListElement {\n"
-                "listRoleA: [\n"
-                    "ListElement { roleA: 100 },\n"
-                    "ListElement { roleA: 200; roleB: 400 },\n"
-                    "ListElement { roleA: 200; roleB: 400 } \n"
-                "]\n"
-            "}\n"
-            "ListElement {\n"
-                "listRoleA: [\n"
-                    "ListElement { roleA: 100 },\n"
-                    "ListElement { roleA: 200; roleB: 400 },\n"
-                    "ListElement { roleA: 200; roleB: 400 } \n"
-                "]\n"
-                "listRoleB: [\n"
-                    "ListElement { roleA: 100 },\n"
-                    "ListElement { roleA: 200; roleB: 400 },\n"
-                    "ListElement { roleA: 200; roleB: 400 } \n"
-                "]\n"
-                "listRoleC: [\n"
-                    "ListElement { roleA: 100 },\n"
-                    "ListElement { roleA: 200; roleB: 400 },\n"
-                    "ListElement { roleA: 200; roleB: 400 } \n"
-                "]\n"
-            "}\n"
-         "}", QUrl());
+        "ListModel {}", QUrl());
     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
     QVERIFY(component.errorString().isEmpty());
     QDeclarativeListModel *childModel;
+    engine.rootContext()->setContextProperty("model", model);
+
+    RUNEXPR("model.append({ listRoleA: [\n"
+                            "{ roleA: 100 },\n"
+                            "{ roleA: 200, roleB: 400 },\n"
+                            "{ roleA: 200, roleB: 400 }, \n"
+                            "{ roleC: {} }, \n"
+                            "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+                            "] })\n");
+
+    RUNEXPR("model.append({ listRoleA: [\n"
+                            "{ roleA: 100 },\n"
+                            "{ roleA: 200, roleB: 400 },\n"
+                            "{ roleA: 200, roleB: 400 }, \n"
+                            "{ roleC: {} }, \n"
+                            "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+                            "],\n"
+                            "listRoleB: [\n"
+                            "{ roleA: 100 },\n"
+                            "{ roleA: 200, roleB: 400 },\n"
+                            "{ roleA: 200, roleB: 400 }, \n"
+                            "{ roleC: {} }, \n"
+                            "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+                            "],\n"
+                            "listRoleC: [\n"
+                            "{ roleA: 100 },\n"
+                            "{ roleA: 200, roleB: 400 },\n"
+                            "{ roleA: 200, roleB: 400 }, \n"
+                            "{ roleC: {} }, \n"
+                            "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+                            "] })\n");
 
     // Test setting the inner list data for:
     //  get(0).listRoleA
@@ -937,7 +963,7 @@ void tst_qdeclarativelistmodel::get_nested()
         QVERIFY(childModel);
 
         QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
-        QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
+        QDeclarativeExpression expr(engine.rootContext(), model, extendedExpression);
 
         QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
         expr.evaluate();
@@ -945,7 +971,11 @@ void tst_qdeclarativelistmodel::get_nested()
 
         int role = roleFromName(childModel, roleName);
         QVERIFY(role >= 0);
-        QCOMPARE(childModel->data(index, role), roleValue);
+        if (roleValue.type() == QVariant::List) {
+            QVERIFY(compareVariantList(roleValue.toList(), childModel->data(index, role)));
+        } else {
+            QCOMPARE(childModel->data(index, role), roleValue);
+        }
         QCOMPARE(spy.count(), 1);
 
         QList<QVariant> spyResult = spy.takeFirst();
@@ -1017,6 +1047,7 @@ void tst_qdeclarativelistmodel::property_changes()
                         "target: model.get(" + QString::number(listIndex) + ")\n"
                         + signalHandler + " gotSignal = true\n"
                   "}\n";
+
     QDeclarativeComponent component(&engine);
     component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
     engine.rootContext()->setContextProperty("model", &model);
@@ -1117,10 +1148,6 @@ void tst_qdeclarativelistmodel::property_changes_data()
 
 void tst_qdeclarativelistmodel::property_changes_worker()
 {
-    // nested models are not supported when WorkerScript is involved
-    if (QByteArray(QTest::currentDataTag()).startsWith("nested-"))
-        return;
-
     QFETCH(QString, script_setup);
     QFETCH(QString, script_change);
     QFETCH(QString, roleName);
@@ -1208,6 +1235,129 @@ void tst_qdeclarativelistmodel::signal_handlers()
     delete model;
 }
 
+void tst_qdeclarativelistmodel::worker_sync()
+{
+    QDeclarativeListModel model;
+    QDeclarativeEngine eng;
+    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workersync.qml"));
+    QQuickItem *item = createWorkerTest(&eng, &component, &model);
+    QVERIFY(item != 0);
+
+    QVERIFY(model.count() == 0);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "addItem0"));
+
+    QVERIFY(model.count() == 2);
+    QVariant childData = model.data(0, 0);
+    QDeclarativeListModel *childModel = qobject_cast<QDeclarativeListModel *>(childData.value<QObject *>());
+    QVERIFY(childModel);
+    QVERIFY(childModel->count() == 1);
+
+    QSignalSpy spyModelInserted(&model, SIGNAL(itemsInserted(int,int)));
+    QSignalSpy spyChildInserted(childModel, SIGNAL(itemsInserted(int,int)));
+
+    QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 2);
+    QVERIFY(childModel->count() == 1);
+    QVERIFY(spyModelInserted.count() == 0);
+    QVERIFY(spyChildInserted.count() == 0);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 2);
+    QVERIFY(childModel->count() == 2);
+    QVERIFY(spyModelInserted.count() == 0);
+    QVERIFY(spyChildInserted.count() == 1);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 2);
+    QVERIFY(childModel->count() == 2);
+    QVERIFY(spyModelInserted.count() == 0);
+    QVERIFY(spyChildInserted.count() == 1);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 2);
+    QVERIFY(childModel->count() == 3);
+    QVERIFY(spyModelInserted.count() == 0);
+    QVERIFY(spyChildInserted.count() == 2);
+
+    delete item;
+    qApp->processEvents();
+}
+
+void tst_qdeclarativelistmodel::worker_remove_element()
+{
+    QDeclarativeListModel model;
+    QDeclarativeEngine eng;
+    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workerremoveelement.qml"));
+    QQuickItem *item = createWorkerTest(&eng, &component, &model);
+    QVERIFY(item != 0);
+
+    QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
+
+    QVERIFY(model.count() == 0);
+    QVERIFY(spyModelRemoved.count() == 0);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "addItem"));
+
+    QVERIFY(model.count() == 1);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 1);
+    QVERIFY(spyModelRemoved.count() == 0);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 0);
+    QVERIFY(spyModelRemoved.count() == 1);
+
+    delete item;
+    qApp->processEvents();
+}
+
+void tst_qdeclarativelistmodel::worker_remove_list()
+{
+    QDeclarativeListModel model;
+    QDeclarativeEngine eng;
+    QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workerremovelist.qml"));
+    QQuickItem *item = createWorkerTest(&eng, &component, &model);
+    QVERIFY(item != 0);
+
+    QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
+
+    QVERIFY(model.count() == 0);
+    QVERIFY(spyModelRemoved.count() == 0);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "addList"));
+
+    QVERIFY(model.count() == 1);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "removeListViaWorker"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 1);
+    QVERIFY(spyModelRemoved.count() == 0);
+
+    QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+    waitForWorker(item);
+
+    QVERIFY(model.count() == 0);
+    QVERIFY(spyModelRemoved.count() == 1);
+
+    delete item;
+    qApp->processEvents();
+}
+
 QTEST_MAIN(tst_qdeclarativelistmodel)
 
 #include "tst_qdeclarativelistmodel.moc"