Add dynamicRoles mode to ListModel for extra flexibility.
authorGlenn Watson <glenn.watson@nokia.com>
Thu, 1 Dec 2011 00:40:44 +0000 (10:40 +1000)
committerQt by Nokia <qt-info@nokia.com>
Fri, 2 Dec 2011 09:13:40 +0000 (10:13 +0100)
ListModel contains a new property called dynamicRoles.
By default this property is disabled, meaning that
the type of a given role cannot be changed dynamically.
However, when this property is set, the type of a
role can change between elements. This allows more
flexibility but comes at a significant performance
cost over the default static role mode.

Change-Id: I478d48c882f51ba2dde37f88e2aac744e919b68b
Reviewed-by: Martin Jones <martin.jones@nokia.com>
src/declarative/qml/qdeclarativelistmodel.cpp
src/declarative/qml/qdeclarativelistmodel_p.h
src/declarative/qml/qdeclarativelistmodel_p_p.h
src/declarative/qml/qdeclarativelistmodelworkeragent.cpp
src/declarative/qml/qdeclarativelistmodelworkeragent_p.h
src/declarative/qml/qdeclarativeopenmetaobject.cpp
src/declarative/qml/qdeclarativeopenmetaobject_p.h
tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp

index 9e4d726..16391a3 100644 (file)
@@ -62,7 +62,7 @@ QT_BEGIN_NAMESPACE
 // 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);
+static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
 
 template <typename T>
 static bool isMemoryUsed(const char *mem)
@@ -78,7 +78,7 @@ static bool isMemoryUsed(const char *mem)
 static QString roleTypeName(ListLayout::Role::DataType t)
 {
     QString result;
-    const char *roleTypeNames[] = { "String", "Number", "Bool", "List", "QObject" };
+    const char *roleTypeNames[] = { "String", "Number", "Bool", "List", "QObject", "VariantMap" };
 
     if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
         result = QString::fromLatin1(roleTypeNames[t]);
@@ -216,6 +216,7 @@ const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QV
         case QVariant::UserType:    type = Role::List;        break;
         case QVariant::Bool:        type = Role::Bool;        break;
         case QVariant::String:      type = Role::String;      break;
+        case QVariant::Map:         type = Role::VariantMap;  break;
         default:                    type = Role::Invalid;     break;
     }
 
@@ -326,15 +327,10 @@ void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *>
     }
 }
 
-int ListModel::allocateUid()
-{
-    return uidCounter.fetchAndAddOrdered(1);
-}
-
 ListModel::ListModel(ListLayout *layout, QDeclarativeListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
 {
     if (uid == -1)
-        uid = allocateUid();
+        uid = uidCounter.fetchAndAddOrdered(1);
     m_uid = uid;
 }
 
@@ -458,11 +454,11 @@ void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int>
                 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);
+                    roleIndex = e->setQObjectProperty(role, o);
             } else {
                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
                 if (role.type == ListLayout::Role::VariantMap)
-                    e->setVariantMapProperty(role, propertyValue->ToObject(), eng);
+                    roleIndex = e->setVariantMapProperty(role, propertyValue->ToObject(), eng);
             }
         } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
             const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
@@ -583,6 +579,7 @@ int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const Q
 
     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);
@@ -953,7 +950,7 @@ void ListElement::clearProperty(const ListLayout::Role &role)
 ListElement::ListElement()
 {
     m_objectCache = 0;
-    uid = ListModel::allocateUid();
+    uid = uidCounter.fetchAndAddOrdered(1);
     next = 0;
     qMemSet(data, 0, sizeof(data));
 }
@@ -1086,6 +1083,11 @@ int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant
         case ListLayout::Role::List:
             roleIndex = setListProperty(role, d.value<ListModel *>());
             break;
+        case ListLayout::Role::VariantMap: {
+                QVariantMap map = d.toMap();
+                roleIndex = setVariantMapProperty(role, &map);
+            }
+            break;
         default:
             break;
     }
@@ -1195,6 +1197,153 @@ void ModelNodeMetaObject::propertyWritten(int index)
     }
 }
 
+DynamicRoleModelNode::DynamicRoleModelNode(QDeclarativeListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
+{
+    setNodeUpdatesEnabled(true);
+}
+
+DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QDeclarativeListModel *owner)
+{
+    DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
+    QList<int> roles;
+    object->updateValues(obj, roles);
+    return object;
+}
+
+void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QDeclarativeListModel *> *targetModelHash)
+{
+    for (int i=0 ; i < src->m_meta->count() ; ++i) {
+        const QByteArray &name = src->m_meta->name(i);
+        QVariant value = src->m_meta->value(i);
+
+        QDeclarativeListModel *srcModel = qobject_cast<QDeclarativeListModel *>(value.value<QObject *>());
+        QDeclarativeListModel *targetModel = qobject_cast<QDeclarativeListModel *>(target->m_meta->value(i).value<QObject *>());
+
+        if (srcModel) {
+            if (targetModel == 0)
+                targetModel = QDeclarativeListModel::createWithOwner(target->m_owner);
+
+            QDeclarativeListModel::sync(srcModel, targetModel, targetModelHash);
+
+            QObject *targetModelObject = targetModel;
+            value = QVariant::fromValue(targetModelObject);
+        } else if (targetModel) {
+            delete targetModel;
+        }
+
+        target->setValue(name, value);
+    }
+}
+
+void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
+{
+    const QList<QString> &keys = object.keys();
+
+    QList<QString>::const_iterator it = keys.begin();
+    QList<QString>::const_iterator end = keys.end();
+
+    while (it != end) {
+        const QString &key = *it;
+
+        int roleIndex = m_owner->m_roles.indexOf(key);
+        if (roleIndex == -1) {
+            roleIndex = m_owner->m_roles.count();
+            m_owner->m_roles.append(key);
+        }
+
+        QVariant value = object[key];
+
+        if (value.type() == QVariant::List) {
+            QDeclarativeListModel *subModel = QDeclarativeListModel::createWithOwner(m_owner);
+
+            QVariantList subArray = value.toList();
+            QVariantList::const_iterator subIt = subArray.begin();
+            QVariantList::const_iterator subEnd = subArray.end();
+            while (subIt != subEnd) {
+                const QVariantMap &subObject = subIt->toMap();
+                subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
+                ++subIt;
+            }
+
+            QObject *subModelObject = subModel;
+            value = QVariant::fromValue(subModelObject);
+        }
+
+        const QByteArray &keyUtf8 = key.toUtf8();
+
+        QDeclarativeListModel *existingModel = qobject_cast<QDeclarativeListModel *>(m_meta->value(keyUtf8).value<QObject *>());
+        if (existingModel)
+            delete existingModel;
+
+        if (m_meta->setValue(keyUtf8, value))
+            roles << roleIndex;
+
+        ++it;
+    }
+}
+
+DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
+    : QDeclarativeOpenMetaObject(object), m_enabled(false), m_owner(object)
+{
+}
+
+DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
+{
+    for (int i=0 ; i < count() ; ++i) {
+        QDeclarativeListModel *subModel = qobject_cast<QDeclarativeListModel *>(value(i).value<QObject *>());
+        if (subModel)
+            delete subModel;
+    }
+}
+
+void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
+{
+    if (!m_enabled)
+        return;
+
+    QVariant v = value(index);
+    QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel *>(v.value<QObject *>());
+    if (model)
+        delete model;
+}
+
+void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
+{
+    if (!m_enabled)
+        return;
+
+    QDeclarativeListModel *parentModel = m_owner->m_owner;
+
+    QVariant v = value(index);
+    if (v.type() == QVariant::List) {
+        QDeclarativeListModel *subModel = QDeclarativeListModel::createWithOwner(parentModel);
+
+        QVariantList subArray = v.toList();
+        QVariantList::const_iterator subIt = subArray.begin();
+        QVariantList::const_iterator subEnd = subArray.end();
+        while (subIt != subEnd) {
+            const QVariantMap &subObject = subIt->toMap();
+            subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
+            ++subIt;
+        }
+
+        QObject *subModelObject = subModel;
+        v = QVariant::fromValue(subModelObject);
+
+        setValue(index, v);
+    }
+
+    int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
+    int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
+
+    if (elementIndex != -1 && roleIndex != -1) {
+        QList<int> roles;
+        roles << roleIndex;
+
+        parentModel->emitItemsChanged(elementIndex, 1, roles);
+    }
+}
+
 QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListModelData::instructions() const
 {
     return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
@@ -1300,6 +1449,8 @@ QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
     m_mainThread = true;
     m_primary = true;
     m_agent = 0;
+    m_uid = uidCounter.fetchAndAddOrdered(1);
+    m_dynamicRoles = false;
 
     m_layout = new ListLayout;
     m_listModel = new ListModel(m_layout, this, -1);
@@ -1314,6 +1465,8 @@ QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *owner,
     m_primary = false;
     m_agent = owner->m_agent;
 
+    Q_ASSERT(owner->m_dynamicRoles == false);
+    m_dynamicRoles = false;
     m_layout = 0;
     m_listModel = data;
 
@@ -1326,16 +1479,24 @@ QDeclarativeListModel::QDeclarativeListModel(QDeclarativeListModel *orig, QDecla
     m_mainThread = false;
     m_primary = true;
     m_agent = agent;
+    m_dynamicRoles = orig->m_dynamicRoles;
 
     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);
+
+    if (m_dynamicRoles)
+        sync(orig, this, 0);
+    else
+        ListModel::sync(orig->m_listModel, m_listModel, 0);
 
     m_engine = 0;
 }
 
 QDeclarativeListModel::~QDeclarativeListModel()
 {
+    for (int i=0 ; i < m_modelObjects.count() ; ++i)
+        delete m_modelObjects[i];
+
     if (m_primary) {
         m_listModel->destroy();
         delete m_listModel;
@@ -1350,6 +1511,20 @@ QDeclarativeListModel::~QDeclarativeListModel()
     m_layout = 0;
 }
 
+QDeclarativeListModel *QDeclarativeListModel::createWithOwner(QDeclarativeListModel *newOwner)
+{
+    QDeclarativeListModel *model = new QDeclarativeListModel;
+
+    model->m_mainThread = newOwner->m_mainThread;
+    model->m_engine = newOwner->m_engine;
+    model->m_agent = newOwner->m_agent;
+    model->m_dynamicRoles = newOwner->m_dynamicRoles;
+
+    QDeclarativeEngine::setContextForObject(model, QDeclarativeEngine::contextForObject(newOwner));
+
+    return model;
+}
+
 QV8Engine *QDeclarativeListModel::engine() const
 {
     if (m_engine == 0) {
@@ -1359,12 +1534,74 @@ QV8Engine *QDeclarativeListModel::engine() const
     return m_engine;
 }
 
+void QDeclarativeListModel::sync(QDeclarativeListModel *src, QDeclarativeListModel *target, QHash<int, QDeclarativeListModel *> *targetModelHash)
+{
+    Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
+
+    target->m_uid = src->m_uid;
+    if (targetModelHash)
+        targetModelHash->insert(target->m_uid, target);
+    target->m_roles = src->m_roles;
+
+    // Build hash of elements <-> uid for each of the lists
+    QHash<int, ElementSync> elementHash;
+    for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
+        DynamicRoleModelNode *e = target->m_modelObjects.at(i);
+        int uid = e->getUid();
+        ElementSync sync;
+        sync.target = e;
+        elementHash.insert(uid, sync);
+    }
+    for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
+        DynamicRoleModelNode *e = src->m_modelObjects.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;
+        }
+    }
+
+    // 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) {
+            int targetIndex = target->m_modelObjects.indexOf(s.target);
+            target->m_modelObjects.remove(targetIndex, 1);
+            delete s.target;
+        }
+        ++it;
+    }
+
+    // Clear the target list, and append in correct order from the source
+    target->m_modelObjects.clear();
+    for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
+        DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
+        it = elementHash.find(srcElement->getUid());
+        const ElementSync &s = it.value();
+        DynamicRoleModelNode *targetElement = s.target;
+        if (targetElement == 0) {
+            targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
+        }
+        DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
+        target->m_modelObjects.append(targetElement);
+    }
+}
+
 void QDeclarativeListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
 {
     if (m_mainThread) {
         emit itemsChanged(index, count, roles);
     } else {
-        m_agent->data.changedChange(this, index, count, roles);
+        int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
+        m_agent->data.changedChange(uid, index, count, roles);
     }
 }
 
@@ -1374,9 +1611,10 @@ void QDeclarativeListModel::emitItemsRemoved(int index, int count)
         emit itemsRemoved(index, count);
         emit countChanged();
     } else {
+        int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
         if (index == 0 && count == this->count())
-            m_agent->data.clearChange(this);
-        m_agent->data.removeChange(this, index, count);
+            m_agent->data.clearChange(uid);
+        m_agent->data.removeChange(uid, index, count);
     }
 }
 
@@ -1386,7 +1624,8 @@ void QDeclarativeListModel::emitItemsInserted(int index, int count)
         emit itemsInserted(index, count);
         emit countChanged();
     } else {
-        m_agent->data.insertChange(this, index, count);
+        int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
+        m_agent->data.insertChange(uid, index, count);
     }
 }
 
@@ -1395,7 +1634,8 @@ void QDeclarativeListModel::emitItemsMoved(int from, int to, int n)
     if (m_mainThread) {
         emit itemsMoved(from, to, n);
     } else {
-        m_agent->data.moveChange(this, from, n, to);
+        int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
+        m_agent->data.moveChange(uid, from, n, to);
     }
 }
 
@@ -1412,24 +1652,90 @@ QList<int> QDeclarativeListModel::roles() const
 {
     QList<int> rolesArray;
 
-    for (int i=0 ; i < m_listModel->roleCount() ; ++i)
-        rolesArray << i;
+    if (m_dynamicRoles) {
+        for (int i=0 ; i < m_roles.count() ; ++i)
+            rolesArray << i;
+    } else {
+        for (int i=0 ; i < m_listModel->roleCount() ; ++i)
+            rolesArray << i;
+    }
 
     return rolesArray;
 }
 
 QString QDeclarativeListModel::toString(int role) const
 {
-    const ListLayout::Role &r = m_listModel->getExistingRole(role);
-    return r.name;
+    QString roleName;
+
+    if (m_dynamicRoles) {
+        roleName = m_roles[role];
+    } else {
+        const ListLayout::Role &r = m_listModel->getExistingRole(role);
+        roleName = r.name;
+    }
+
+    return roleName;
 }
 
 QVariant QDeclarativeListModel::data(int index, int role) const
 {
+    QVariant v;
+
     if (index >= count() || index < 0)
-        return QVariant();
+        return v;
 
-    return m_listModel->getProperty(index, role, this, engine());
+    if (m_dynamicRoles)
+        v = m_modelObjects[index]->getValue(m_roles[role]);
+    else
+        v = m_listModel->getProperty(index, role, this, engine());
+
+    return v;
+}
+
+/*!
+    \qmlproperty bool QtQuick2::ListModel::dynamicRoles
+
+    By default, the type of a role is fixed the first time
+    the role is used. For example, if you create a role called
+    "data" and assign a number to it, you can no longer assign
+    a string to the "data" role. However, when the dynamicRoles
+    property is enabled, the type of a given role is not fixed
+    and can be different between elements.
+
+    The dynamicRoles property must be set before any data is
+    added to the ListModel, and must be set from the main
+    thread.
+
+    A ListModel that has data statically defined (via the
+    ListElement QML syntax) cannot have the dynamicRoles
+    property enabled.
+
+    There is a significant performance cost to using a
+    ListModel with dynamic roles enabled. The cost varies
+    from platform to platform but is typically somewhere
+    between 4-6x slower than using static role types.
+
+    Due to the performance cost of using dynamic roles,
+    they are disabled by default.
+*/
+void QDeclarativeListModel::setDynamicRoles(bool enableDynamicRoles)
+{
+    if (m_mainThread && m_agent == 0) {
+        if (enableDynamicRoles) {
+            if (m_layout->roleCount())
+                qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
+            else
+                m_dynamicRoles = true;
+        } else {
+            if (m_roles.count()) {
+                qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
+            } else {
+                m_dynamicRoles = false;
+            }
+        }
+    } else {
+        qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
+    }
 }
 
 /*!
@@ -1438,7 +1744,15 @@ QVariant QDeclarativeListModel::data(int index, int role) const
 */
 int QDeclarativeListModel::count() const
 {
-    return m_listModel->elementCount();
+    int count;
+
+    if (m_dynamicRoles)
+        count = m_modelObjects.count();
+    else {
+        count = m_listModel->elementCount();
+    }
+
+    return count;
 }
 
 /*!
@@ -1452,7 +1766,14 @@ void QDeclarativeListModel::clear()
 {
     int cleared = count();
 
-    m_listModel->clear();
+    if (m_dynamicRoles) {
+        for (int i=0 ; i < m_modelObjects.count() ; ++i)
+            delete m_modelObjects[i];
+        m_modelObjects.clear();
+    } else {
+        m_listModel->clear();
+    }
+
     emitItemsRemoved(0, cleared);
 }
 
@@ -1476,7 +1797,14 @@ void QDeclarativeListModel::remove(QDeclarativeV8Function *args)
             return;
         }
 
-        m_listModel->remove(index, removeCount);
+        if (m_dynamicRoles) {
+            for (int i=0 ; i < removeCount ; ++i)
+                delete m_modelObjects[index+i];
+            m_modelObjects.remove(index, removeCount);
+        } else {
+            m_listModel->remove(index, removeCount);
+        }
+
         emitItemsRemoved(index, removeCount);
     } else {
         qmlInfo(this) << tr("remove: incorrect number of arguments");
@@ -1518,13 +1846,23 @@ void QDeclarativeListModel::insert(QDeclarativeV8Function *args)
             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, args->engine());
+
+                if (m_dynamicRoles) {
+                    m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
+                } else {
+                    m_listModel->insert(index+i, argObject, args->engine());
+                }
             }
             emitItemsInserted(index, objectArrayLength);
         } else if (arg1->IsObject()) {
             v8::Handle<v8::Object> argObject = arg1->ToObject();
 
-            m_listModel->insert(index, argObject, args->engine());
+            if (m_dynamicRoles) {
+                m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
+            } else {
+                m_listModel->insert(index, argObject, args->engine());
+            }
+
             emitItemsInserted(index, 1);
         } else {
             qmlInfo(this) << tr("insert: value is not an object");
@@ -1557,7 +1895,33 @@ void QDeclarativeListModel::move(int from, int to, int n)
         return;
     }
 
-    m_listModel->move(from, to, n);
+    if (m_dynamicRoles) {
+
+        int realFrom = from;
+        int realTo = to;
+        int realN = n;
+
+        if (from > to) {
+            // Only move forwards - flip if backwards moving
+            int tfrom = from;
+            int tto = to;
+            realFrom = tto;
+            realTo = tto+n;
+            realN = tfrom-tto;
+        }
+
+        QPODVector<DynamicRoleModelNode *, 4> store;
+        for (int i=0 ; i < (realTo-realFrom) ; ++i)
+            store.append(m_modelObjects[realFrom+realN+i]);
+        for (int i=0 ; i < realN ; ++i)
+            store.append(m_modelObjects[realFrom+i]);
+        for (int i=0 ; i < store.count() ; ++i)
+            m_modelObjects[realFrom+i] = store[i];
+
+    } else {
+        m_listModel->move(from, to, n);
+    }
+
     emitItemsMoved(from, to, n);
 }
 
@@ -1581,18 +1945,32 @@ void QDeclarativeListModel::append(QDeclarativeV8Function *args)
         if (arg->IsArray()) {
             v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
             int objectArrayLength = objectArray->Length();
-            int index = m_listModel->elementCount();
+
+            int index = count();
             for (int i=0 ; i < objectArrayLength ; ++i) {
                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
-                m_listModel->append(argObject, args->engine());
+
+                if (m_dynamicRoles) {
+                    m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
+                } else {
+                    m_listModel->append(argObject, args->engine());
+                }
             }
+
             emitItemsInserted(index, objectArrayLength);
         } else if (arg->IsObject()) {
             v8::Handle<v8::Object> argObject = arg->ToObject();
 
-            int index = m_listModel->append(argObject, args->engine());
-            emitItemsInserted(index, 1);
+            int index;
+
+            if (m_dynamicRoles) {
+                index = m_modelObjects.count();
+                m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
+            } else {
+                index = m_listModel->append(argObject, args->engine());
+            }
 
+            emitItemsInserted(index, 1);
         } else {
             qmlInfo(this) << tr("append: value is not an object");
         }
@@ -1636,11 +2014,16 @@ QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
 {
     v8::Handle<v8::Value> result = v8::Undefined();
 
-    if (index >= 0 && index < m_listModel->elementCount()) {
+    if (index >= 0 && index < count()) {
         QV8Engine *v8engine = engine();
 
-        ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QDeclarativeListModel *>(this), index);
-        result = v8engine->newQObject(object);
+        if (m_dynamicRoles) {
+            DynamicRoleModelNode *object = m_modelObjects[index];
+            result = v8engine->newQObject(object);
+        } else {
+            ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QDeclarativeListModel *>(this), index);
+            result = v8engine->newQObject(object);
+        }
     }
 
     return QDeclarativeV8Handle::fromHandle(result);
@@ -1678,12 +2061,23 @@ void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle)
     v8::Handle<v8::Object> object = valuemap->ToObject();
 
     if (index == count()) {
-        m_listModel->insert(index, object, engine());
+
+        if (m_dynamicRoles) {
+            m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
+        } else {
+            m_listModel->insert(index, object, engine());
+        }
+
         emitItemsInserted(index, 1);
     } else {
 
         QList<int> roles;
-        m_listModel->set(index, object, &roles, engine());
+
+        if (m_dynamicRoles) {
+            m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
+        } else {
+            m_listModel->set(index, object, &roles, engine());
+        }
 
         if (roles.count())
             emitItemsChanged(index, 1, roles);
@@ -1710,13 +2104,26 @@ void QDeclarativeListModel::setProperty(int index, const QString& property, cons
         return;
     }
 
-    int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
-    if (roleIndex != -1) {
+    if (m_dynamicRoles) {
+        int roleIndex = m_roles.indexOf(property);
+        if (roleIndex == -1) {
+            roleIndex = m_roles.count();
+            m_roles.append(property);
+        }
+        if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
+            QList<int> roles;
+            roles << roleIndex;
+            emitItemsChanged(index, 1, roles);
+        }
+    } else {
+        int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
+        if (roleIndex != -1) {
 
-        QList<int> roles;
-        roles << roleIndex;
+            QList<int> roles;
+            roles << roleIndex;
 
-        emitItemsChanged(index, 1, roles);
+            emitItemsChanged(index, 1, roles);
+        }
     }
 }
 
@@ -1920,6 +2327,8 @@ void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &
         switch(instr.type) {
         case ListInstruction::Push:
             {
+                Q_ASSERT(!rv->m_dynamicRoles);
+
                 ListModel *subModel = 0;
 
                 if (stack.count() == 0) {
index 2360f42..3b1e037 100644 (file)
@@ -69,6 +69,7 @@ class Q_DECLARATIVE_PRIVATE_EXPORT QDeclarativeListModel : public QListModelInte
 {
     Q_OBJECT
     Q_PROPERTY(int count READ count NOTIFY countChanged)
+    Q_PROPERTY(bool dynamicRoles READ dynamicRoles WRITE setDynamicRoles)
 
 public:
     QDeclarativeListModel(QObject *parent=0);
@@ -91,6 +92,9 @@ public:
 
     QDeclarativeListModelWorkerAgent *agent();
 
+    bool dynamicRoles() const { return m_dynamicRoles; }
+    void setDynamicRoles(bool enableDynamicRoles);
+
 Q_SIGNALS:
     void countChanged();
 
@@ -101,6 +105,8 @@ private:
     friend class ModelNodeMetaObject;
     friend class ListModel;
     friend class ListElement;
+    friend class DynamicRoleModelNode;
+    friend class DynamicRoleModelNodeMetaObject;
 
     // Constructs a flat list model for a worker agent
     QDeclarativeListModel(QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *agent);
@@ -110,13 +116,32 @@ private:
 
     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;
+    mutable QV8Engine *m_engine;
     bool m_mainThread;
     bool m_primary;
-    mutable QV8Engine *m_engine;
+
+    bool m_dynamicRoles;
+
+    ListLayout *m_layout;
+    ListModel *m_listModel;
+
+    QVector<class DynamicRoleModelNode *> m_modelObjects;
+    QVector<QString> m_roles;
+    int m_uid;
+
+    struct ElementSync
+    {
+        ElementSync() : src(0), target(0) {}
+
+        DynamicRoleModelNode *src;
+        DynamicRoleModelNode *target;
+    };
+
+    int getUid() const { return m_uid; }
+
+    static void sync(QDeclarativeListModel *src, QDeclarativeListModel *target, QHash<int, QDeclarativeListModel *> *targetModelHash);
+    static QDeclarativeListModel *createWithOwner(QDeclarativeListModel *newOwner);
 
     void emitItemsChanged(int index, int count, const QList<int> &roles);
     void emitItemsRemoved(int index, int count);
index ab6c2b2..1ef1a17 100644 (file)
@@ -64,6 +64,64 @@ QT_BEGIN_NAMESPACE
 
 QT_MODULE(Declarative)
 
+class DynamicRoleModelNode;
+
+class DynamicRoleModelNodeMetaObject : public QDeclarativeOpenMetaObject
+{
+public:
+    DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object);
+    ~DynamicRoleModelNodeMetaObject();
+
+    bool m_enabled;
+
+protected:
+    void propertyWrite(int index);
+    void propertyWritten(int index);
+
+private:
+    DynamicRoleModelNode *m_owner;
+};
+
+class DynamicRoleModelNode : public QObject
+{
+    Q_OBJECT
+public:
+    DynamicRoleModelNode(QDeclarativeListModel *owner, int uid);
+
+    static DynamicRoleModelNode *create(const QVariantMap &obj, QDeclarativeListModel *owner);
+
+    void updateValues(const QVariantMap &object, QList<int> &roles);
+
+    QVariant getValue(const QString &name)
+    {
+        return m_meta->value(name.toUtf8());
+    }
+
+    bool setValue(const QByteArray &name, const QVariant &val)
+    {
+        return m_meta->setValue(name, val);
+    }
+
+    void setNodeUpdatesEnabled(bool enable)
+    {
+        m_meta->m_enabled = enable;
+    }
+
+    int getUid() const
+    {
+        return m_uid;
+    }
+
+    static void sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QDeclarativeListModel *> *targetModelHash);
+
+private:
+    QDeclarativeListModel *m_owner;
+    int m_uid;
+    DynamicRoleModelNodeMetaObject *m_meta;
+
+    friend class DynamicRoleModelNodeMetaObject;
+};
+
 class ModelObject;
 
 class ModelNodeMetaObject : public QDeclarativeOpenMetaObject
@@ -284,8 +342,6 @@ public:
 
     int getUid() const { return m_uid; }
 
-    static int allocateUid();
-
     static void sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *srcModelHash);
 
     ModelObject *getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex);
@@ -311,8 +367,6 @@ private:
 
     friend class ListElement;
     friend class QDeclarativeListModelWorkerAgent;
-
-    static QAtomicInt uidCounter;
 };
 
 QT_END_NAMESPACE
index c01025c..3904d35 100644 (file)
 QT_BEGIN_NAMESPACE
 
 
-void QDeclarativeListModelWorkerAgent::Data::clearChange(QDeclarativeListModel *model)
+void QDeclarativeListModelWorkerAgent::Data::clearChange(int uid)
 {
-    int uid = model->m_listModel->getUid();
-
     for (int i=0 ; i < changes.count() ; ++i) {
         if (changes[i].modelUid == uid) {
             changes.removeAt(i);
@@ -65,27 +63,27 @@ void QDeclarativeListModelWorkerAgent::Data::clearChange(QDeclarativeListModel *
     }
 }
 
-void QDeclarativeListModelWorkerAgent::Data::insertChange(QDeclarativeListModel *model, int index, int count)
+void QDeclarativeListModelWorkerAgent::Data::insertChange(int uid, int index, int count)
 {
-    Change c = { model->m_listModel->getUid(), Change::Inserted, index, count, 0, QList<int>() };
+    Change c = { uid, Change::Inserted, index, count, 0, QList<int>() };
     changes << c;
 }
 
-void QDeclarativeListModelWorkerAgent::Data::removeChange(QDeclarativeListModel *model, int index, int count)
+void QDeclarativeListModelWorkerAgent::Data::removeChange(int uid, int index, int count)
 {
-    Change c = { model->m_listModel->getUid(), Change::Removed, index, count, 0, QList<int>() };
+    Change c = { uid, Change::Removed, index, count, 0, QList<int>() };
     changes << c;
 }
 
-void QDeclarativeListModelWorkerAgent::Data::moveChange(QDeclarativeListModel *model, int index, int count, int to)
+void QDeclarativeListModelWorkerAgent::Data::moveChange(int uid, int index, int count, int to)
 {
-    Change c = { model->m_listModel->getUid(), Change::Moved, index, count, to, QList<int>() };
+    Change c = { uid, Change::Moved, index, count, to, QList<int>() };
     changes << c;
 }
 
-void QDeclarativeListModelWorkerAgent::Data::changedChange(QDeclarativeListModel *model, int index, int count, const QList<int> &roles)
+void QDeclarativeListModelWorkerAgent::Data::changedChange(int uid, int index, int count, const QList<int> &roles)
 {
-    Change c = { model->m_listModel->getUid(), Change::Changed, index, count, 0, roles };
+    Change c = { uid, Change::Changed, index, count, 0, roles };
     changes << c;
 }
 
@@ -181,27 +179,40 @@ bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
 
         bool cc = m_orig->count() != s->list->count();
 
-        QHash<int, ListModel *> targetModelHash;
-        ListModel::sync(s->list->m_listModel, m_orig->m_listModel, &targetModelHash);
+        QHash<int, QDeclarativeListModel *> targetModelDynamicHash;
+        QHash<int, ListModel *> targetModelStaticHash;
+
+        Q_ASSERT(m_orig->m_dynamicRoles == s->list->m_dynamicRoles);
+        if (m_orig->m_dynamicRoles)
+            QDeclarativeListModel::sync(s->list, m_orig, &targetModelDynamicHash);
+        else
+            ListModel::sync(s->list->m_listModel, m_orig->m_listModel, &targetModelStaticHash);
 
         for (int ii = 0; ii < changes.count(); ++ii) {
             const Change &change = changes.at(ii);
 
-            ListModel *model = targetModelHash.value(change.modelUid);
+            QDeclarativeListModel *model = 0;
+            if (m_orig->m_dynamicRoles) {
+                model = targetModelDynamicHash.value(change.modelUid);
+            } else {
+                ListModel *lm = targetModelStaticHash.value(change.modelUid);
+                if (lm)
+                    model = lm->m_modelCache;
+            }
 
-            if (model && model->m_modelCache) {
+            if (model) {
                 switch (change.type) {
                 case Change::Inserted:
-                    emit model->m_modelCache->itemsInserted(change.index, change.count);
+                    emit model->itemsInserted(change.index, change.count);
                     break;
                 case Change::Removed:
-                    emit model->m_modelCache->itemsRemoved(change.index, change.count);
+                    emit model->itemsRemoved(change.index, change.count);
                     break;
                 case Change::Moved:
-                    emit model->m_modelCache->itemsMoved(change.index, change.to, change.count);
+                    emit model->itemsMoved(change.index, change.to, change.count);
                     break;
                 case Change::Changed:
-                    emit model->m_modelCache->itemsChanged(change.index, change.count, change.roles);
+                    emit model->itemsChanged(change.index, change.count, change.roles);
                     break;
                 }
             }
index 05c3647..d27a05f 100644 (file)
@@ -130,11 +130,11 @@ private:
     {
         QList<Change> changes;
 
-        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);
+        void clearChange(int uid);
+        void insertChange(int uid, int index, int count);
+        void removeChange(int uid, int index, int count);
+        void moveChange(int uid, int index, int count, int to);
+        void changedChange(int uid, int index, int count, const QList<int> &roles);
     };
     Data data;
 
index d4dd824..9e4cfc5 100644 (file)
@@ -287,7 +287,7 @@ QVariant &QDeclarativeOpenMetaObject::operator[](int id)
     return d->getData(id);
 }
 
-void QDeclarativeOpenMetaObject::setValue(const QByteArray &name, const QVariant &val)
+bool QDeclarativeOpenMetaObject::setValue(const QByteArray &name, const QVariant &val)
 {
     QHash<QByteArray, int>::ConstIterator iter = d->type->d->names.find(name);
 
@@ -301,11 +301,14 @@ void QDeclarativeOpenMetaObject::setValue(const QByteArray &name, const QVariant
     if (id >= 0) {
         QVariant &dataVal = d->getData(id);
         if (dataVal == val)
-            return;
+            return false;
 
         dataVal = val;
         activate(d->object, id + d->type->d->signalOffset, 0);
+        return true;
     }
+
+    return false;
 }
 
 // returns true if this value has been initialized by a call to either value() or setValue()
index e5cfee7..8f711d1 100644 (file)
@@ -89,7 +89,7 @@ public:
     ~QDeclarativeOpenMetaObject();
 
     QVariant value(const QByteArray &) const;
-    void setValue(const QByteArray &, const QVariant &);
+    bool setValue(const QByteArray &, const QVariant &);
     QVariant value(int) const;
     void setValue(int, const QVariant &);
     QVariant &operator[](const QByteArray &);
index c32a904..661b086 100644 (file)
@@ -67,6 +67,20 @@ inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
 
 #define RUNEXPR(string) runexpr(&engine, QString(string))
 
+static bool isValidErrorMessage(const QString &msg, bool dynamicRoleTest)
+{
+    bool valid = true;
+
+    if (msg.isEmpty()) {
+        valid = false;
+    } else if (dynamicRoleTest) {
+        if (msg.contains("Can't assign to existing role") || msg.contains("Can't create role for unsupported data type"))
+            valid = false;
+    }
+
+    return valid;
+}
+
 class tst_qdeclarativelistmodel : public QObject
 {
     Q_OBJECT
@@ -98,6 +112,7 @@ private slots:
     void error();
     void syncError();
     void get();
+    void set_data();
     void set();
     void get_data();
     void get_worker();
@@ -110,11 +125,20 @@ private slots:
     void property_changes_data();
     void property_changes_worker();
     void property_changes_worker_data();
+    void clear_data();
     void clear();
+    void signal_handlers_data();
     void signal_handlers();
+    void worker_sync_data();
     void worker_sync();
+    void worker_remove_element_data();
     void worker_remove_element();
+    void worker_remove_list_data();
     void worker_remove_list();
+    void role_mode_data();
+    void role_mode();
+    void dynamic_role();
+    void dynamic_role_data();
 };
 
 bool tst_qdeclarativelistmodel::compareVariantList(const QVariantList &testList, QVariant object)
@@ -410,136 +434,140 @@ void tst_qdeclarativelistmodel::dynamic_data()
     QTest::addColumn<QString>("script");
     QTest::addColumn<int>("result");
     QTest::addColumn<QString>("warning");
-
-    // Simple flat model
-    QTest::newRow("count") << "count" << 0 << "";
-
-    QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
-    QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "";
-    QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "";
-    QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "";
-
-    QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "";
-    QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "";
-
-    QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "";
-    QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "";
-    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([{'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 << "";
-    QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "";
-
-    QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "";
-    QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "";
-    QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "";
-    QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "";
-    QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "";
-    QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]";
-    QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]";
-    QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]";
-    QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [1 - 2] out of range [0 - 1]";
-    QTest::newRow("remove5a") << "{append({'foo':123});append({'foo':456});remove(0,2);count}" << 0 << "";
-    QTest::newRow("remove5b") << "{append({'foo':123});append({'foo':456});remove(0,1);count}" << 1 << "";
-    QTest::newRow("remove5c") << "{append({'foo':123});append({'foo':456});remove(1,1);count}" << 1 << "";
-    QTest::newRow("remove5d") << "{append({'foo':123});append({'foo':456});remove(0,1);get(0).foo}" << 456 << "";
-    QTest::newRow("remove5e") << "{append({'foo':123});append({'foo':456});remove(1,1);get(0).foo}" << 123 << "";
-    QTest::newRow("remove5f") << "{append({'foo':123});append({'foo':456});append({'foo':789});remove(0,1);remove(1,1);get(0).foo}" << 456 << "";
-    QTest::newRow("remove6a") << "{remove();count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments";
-    QTest::newRow("remove6b") << "{remove(1,2,3);count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments";
-    QTest::newRow("remove7a") << "{append({'foo':123});remove(0,0);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - 0] out of range [0 - 1]";
-    QTest::newRow("remove7b") << "{append({'foo':123});remove(0,-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - -1] out of range [0 - 1]";
-
-    QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "";
-    QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range";
-    QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "";
-    QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "";
-    QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "";
-    QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "";
-    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,[{'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 << "";
-    QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "";
-    QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "";
-    QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "";
-    QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
-    QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
-    QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
-    QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "";
-
-    QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "";
-    QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "";
-    QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "";
-    QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "";
-    QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range";
-    QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
-    QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range";
-    QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "";
-
-    QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "";
-    QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "";
-    QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "";
-    QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "";
-    QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "";
-    QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "";
-    QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "";
-    QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "";
-    QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "";
-    QTest::newRow("move3a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
-    QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
-    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";
-
-    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 existing role 'a' of different type [String -> Number]";
-
-    QTest::newRow("null") << "{append({'a':null});}" << 0 << "";
-    QTest::newRow("setNull") << "{append({'a':1});set(0, {'a':null});}" << 0 << "";
-    QTest::newRow("setString") << "{append({'a':'hello'});set(0, {'a':'world'});get(0).a == 'world'}" << 1 << "";
-    QTest::newRow("setInt") << "{append({'a':5});set(0, {'a':10});get(0).a}" << 10 << "";
-    QTest::newRow("setNumber") << "{append({'a':6});set(0, {'a':5.5});get(0).a < 5.6}" << 1 << "";
-    QTest::newRow("badType0") << "{append({'a':'hello'});set(0, {'a':1});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [Number -> String]";
-    QTest::newRow("invalidInsert0") << "{insert(0);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
-    QTest::newRow("invalidAppend0") << "{append();}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
-    QTest::newRow("invalidInsert1") << "{insert(0, 34);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
-    QTest::newRow("invalidAppend1") << "{append(37);}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
-
-    // QObjects
-    QTest::newRow("qobject0") << "{append({'a':dummyItem0});}" << 0 << "";
-    QTest::newRow("qobject1") << "{append({'a':dummyItem0});set(0,{'a':dummyItem1});get(0).a == dummyItem1;}" << 1 << "";
-    QTest::newRow("qobject2") << "{append({'a':dummyItem0});get(0).a == dummyItem0;}" << 1 << "";
-    QTest::newRow("qobject3") << "{append({'a':dummyItem0});append({'b':1});}" << 0 << "";
-
-    // JS objects
-    QTest::newRow("js1") << "{append({'foo':{'prop':1}});count}" << 1 << "";
-    QTest::newRow("js2") << "{append({'foo':{'prop':27}});get(0).foo.prop}" << 27 << "";
-    QTest::newRow("js3") << "{append({'foo':{'prop':27}});append({'bar':1});count}" << 2 << "";
-    QTest::newRow("js4") << "{append({'foo':{'prop':27}});append({'bar':1});set(0, {'foo':{'prop':28}});get(0).foo.prop}" << 28 << "";
-    QTest::newRow("js5") << "{append({'foo':{'prop':27}});append({'bar':1});set(1, {'foo':{'prop':33}});get(1).foo.prop}" << 33 << "";
-    QTest::newRow("js6") << "{append({'foo':{'prop':27}});clear();count}" << 0 << "";
-    QTest::newRow("js7") << "{append({'foo':{'prop':27}});set(0, {'foo':null});count}" << 1 << "";
-    QTest::newRow("js8") << "{append({'foo':{'prop':27}});set(0, {'foo':{'prop2':31}});get(0).foo.prop2}" << 31 << "";
-
-    // 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 << "";
-
-    QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "";
-    QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "";
-
-    QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "";
-    QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "";
+    QTest::addColumn<bool>("dynamicRoles");
+
+    for (int i=0 ; i < 2 ; ++i) {
+        bool dr = (i != 0);
+
+        // Simple flat model
+        QTest::newRow("count") << "count" << 0 << "" << dr;
+
+        QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "" << dr;
+        QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "" << dr;
+        QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "" << dr;
+        QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "" << dr;
+        QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "" << dr;
+        QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "" << dr;
+
+        QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "" << dr;
+        QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "" << dr;
+        QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "" << dr;
+        QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "" << dr;
+        QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
+        QTest::newRow("append4b") << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "" << dr;
+        QTest::newRow("append4c") << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "" << dr;
+
+        QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "" << dr;
+        QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "" << dr;
+        QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "" << dr;
+
+        QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "" << dr;
+        QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "" << dr;
+        QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "" << dr;
+        QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "" << dr;
+        QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]" << dr;
+        QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr;
+        QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr;
+        QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [1 - 2] out of range [0 - 1]" << dr;
+        QTest::newRow("remove5a") << "{append({'foo':123});append({'foo':456});remove(0,2);count}" << 0 << "" << dr;
+        QTest::newRow("remove5b") << "{append({'foo':123});append({'foo':456});remove(0,1);count}" << 1 << "" << dr;
+        QTest::newRow("remove5c") << "{append({'foo':123});append({'foo':456});remove(1,1);count}" << 1 << "" << dr;
+        QTest::newRow("remove5d") << "{append({'foo':123});append({'foo':456});remove(0,1);get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("remove5e") << "{append({'foo':123});append({'foo':456});remove(1,1);get(0).foo}" << 123 << "" << dr;
+        QTest::newRow("remove5f") << "{append({'foo':123});append({'foo':456});append({'foo':789});remove(0,1);remove(1,1);get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("remove6a") << "{remove();count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr;
+        QTest::newRow("remove6b") << "{remove(1,2,3);count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr;
+        QTest::newRow("remove7a") << "{append({'foo':123});remove(0,0);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - 0] out of range [0 - 1]" << dr;
+        QTest::newRow("remove7b") << "{append({'foo':123});remove(0,-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - -1] out of range [0 - 1]" << dr;
+
+        QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "" << dr;
+        QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range" << dr;
+        QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "" << dr;
+        QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "" << dr;
+        QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "" << dr;
+        QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "" << dr;
+        QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range" << dr;
+        QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
+        QTest::newRow("insert5b") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "" << dr;
+        QTest::newRow("insert5c") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "" << dr;
+
+        QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "" << dr;
+        QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "" << dr;
+        QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "" << dr;
+        QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "" << dr;
+        QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr;
+        QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object" << dr;
+        QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object" << dr;
+        QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "" << dr;
+
+        QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "" << dr;
+        QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "" << dr;
+        QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "" << dr;
+        QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range" << dr;
+        QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr;
+        QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range" << dr;
+        QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "" << dr;
+
+        QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "" << dr;
+        QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "" << dr;
+        QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "" << dr;
+        QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "" << dr;
+        QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "" << dr;
+        QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "" << dr;
+        QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "" << dr;
+        QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "" << dr;
+        QTest::newRow("move3a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr;
+        QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr;
+        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" << dr;
+        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" << dr;
+
+        QTest::newRow("large1") << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "" << dr;
+
+        QTest::newRow("datatypes1") << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [String -> Number]" << dr;
+
+        QTest::newRow("null") << "{append({'a':null});}" << 0 << "" << dr;
+        QTest::newRow("setNull") << "{append({'a':1});set(0, {'a':null});}" << 0 << "" << dr;
+        QTest::newRow("setString") << "{append({'a':'hello'});set(0, {'a':'world'});get(0).a == 'world'}" << 1 << "" << dr;
+        QTest::newRow("setInt") << "{append({'a':5});set(0, {'a':10});get(0).a}" << 10 << "" << dr;
+        QTest::newRow("setNumber") << "{append({'a':6});set(0, {'a':5.5});get(0).a < 5.6}" << 1 << "" << dr;
+        QTest::newRow("badType0") << "{append({'a':'hello'});set(0, {'a':1});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [Number -> String]" << dr;
+        QTest::newRow("invalidInsert0") << "{insert(0);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
+        QTest::newRow("invalidAppend0") << "{append();}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
+        QTest::newRow("invalidInsert1") << "{insert(0, 34);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
+        QTest::newRow("invalidAppend1") << "{append(37);}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
+
+        // QObjects
+        QTest::newRow("qobject0") << "{append({'a':dummyItem0});}" << 0 << "" << dr;
+        QTest::newRow("qobject1") << "{append({'a':dummyItem0});set(0,{'a':dummyItem1});get(0).a == dummyItem1;}" << 1 << "" << dr;
+        QTest::newRow("qobject2") << "{append({'a':dummyItem0});get(0).a == dummyItem0;}" << 1 << "" << dr;
+        QTest::newRow("qobject3") << "{append({'a':dummyItem0});append({'b':1});}" << 0 << "" << dr;
+
+        // JS objects
+        QTest::newRow("js1") << "{append({'foo':{'prop':1}});count}" << 1 << "" << dr;
+        QTest::newRow("js2") << "{append({'foo':{'prop':27}});get(0).foo.prop}" << 27 << "" << dr;
+        QTest::newRow("js3") << "{append({'foo':{'prop':27}});append({'bar':1});count}" << 2 << "" << dr;
+        QTest::newRow("js4") << "{append({'foo':{'prop':27}});append({'bar':1});set(0, {'foo':{'prop':28}});get(0).foo.prop}" << 28 << "" << dr;
+        QTest::newRow("js5") << "{append({'foo':{'prop':27}});append({'bar':1});set(1, {'foo':{'prop':33}});get(1).foo.prop}" << 33 << "" << dr;
+        QTest::newRow("js6") << "{append({'foo':{'prop':27}});clear();count}" << 0 << "" << dr;
+        QTest::newRow("js7") << "{append({'foo':{'prop':27}});set(0, {'foo':null});count}" << 1 << "" << dr;
+        QTest::newRow("js8") << "{append({'foo':{'prop':27}});set(0, {'foo':{'prop2':31}});get(0).foo.prop2}" << 31 << "" << dr;
+
+        // Nested models
+        QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "" << dr;
+        QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "" << dr;
+        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 << "" << dr;
+
+        QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "" << dr;
+        QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "" << dr;
+
+        QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "" << dr;
+        QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "" << dr;
+    }
 }
 
 void tst_qdeclarativelistmodel::dynamic()
@@ -547,16 +575,18 @@ void tst_qdeclarativelistmodel::dynamic()
     QFETCH(QString, script);
     QFETCH(int, result);
     QFETCH(QString, warning);
+    QFETCH(bool, dynamicRoles);
 
     QQuickItem dummyItem0, dummyItem1;
     QDeclarativeEngine engine;
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
     engine.rootContext()->setContextObject(&model);
     engine.rootContext()->setContextProperty("dummyItem0", QVariant::fromValue(&dummyItem0));
     engine.rootContext()->setContextProperty("dummyItem1", QVariant::fromValue(&dummyItem1));
     QDeclarativeExpression e(engine.rootContext(), &model, script);
-    if (!warning.isEmpty())
+    if (isValidErrorMessage(warning, dynamicRoles))
         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
 
     QSignalSpy spyCount(&model, SIGNAL(countChanged()));
@@ -581,6 +611,7 @@ void tst_qdeclarativelistmodel::dynamic_worker()
     QFETCH(QString, script);
     QFETCH(int, result);
     QFETCH(QString, warning);
+    QFETCH(bool, dynamicRoles);
 
     if (QByteArray(QTest::currentDataTag()).startsWith("qobject"))
         return;
@@ -589,6 +620,7 @@ void tst_qdeclarativelistmodel::dynamic_worker()
     // from a WorkerScript.
 
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("model.qml")));
     QQuickItem *item = createWorkerTest(&eng, &component, &model);
@@ -604,7 +636,7 @@ void tst_qdeclarativelistmodel::dynamic_worker()
             operations << s;
     }
 
-    if (!warning.isEmpty())
+    if (isValidErrorMessage(warning, dynamicRoles))
         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
 
     QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
@@ -629,6 +661,7 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
     QFETCH(QString, script);
     QFETCH(int, result);
     QFETCH(QString, warning);
+    QFETCH(bool, dynamicRoles);
 
     if (QByteArray(QTest::currentDataTag()).startsWith("qobject"))
         return;
@@ -638,6 +671,7 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
     // list in the main thread
 
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("model.qml")));
     QQuickItem *item = createWorkerTest(&eng, &component, &model);
@@ -651,7 +685,7 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
             operations << s;
     }
 
-    if (!warning.isEmpty())
+    if (isValidErrorMessage(warning, dynamicRoles))
         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
 
     // execute a set of commands on the worker list model, then check the
@@ -795,10 +829,21 @@ void tst_qdeclarativelistmodel::syncError()
 /*
     Test model changes from set() are available to the view
 */
+void tst_qdeclarativelistmodel::set_data()
+{
+    QTest::addColumn<bool>("dynamicRoles");
+
+    QTest::newRow("staticRoles") << false;
+    QTest::newRow("dynamicRoles") << true;
+}
+
 void tst_qdeclarativelistmodel::set()
 {
+    QFETCH(bool, dynamicRoles);
+
     QDeclarativeEngine engine;
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
     engine.rootContext()->setContextProperty("model", &model);
 
@@ -812,7 +857,9 @@ void tst_qdeclarativelistmodel::set()
     QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
     QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
 
-    QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Can't create role for unsupported data type");
+    QString warning = QString::fromLatin1("<Unknown File>: Can't create role for unsupported data type");
+    if (isValidErrorMessage(warning, dynamicRoles))
+        QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
     QVariant invalidData = QColor();
     model.setProperty(0, "test", invalidData);
 }
@@ -826,6 +873,7 @@ void tst_qdeclarativelistmodel::get()
     QFETCH(int, index);
     QFETCH(QString, roleName);
     QFETCH(QVariant, roleValue);
+    QFETCH(bool, dynamicRoles);
 
     QDeclarativeEngine engine;
     QDeclarativeComponent component(&engine);
@@ -833,6 +881,7 @@ void tst_qdeclarativelistmodel::get()
         "import QtQuick 2.0\n"
         "ListModel {}\n", QUrl());
     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
+    model->setDynamicRoles(dynamicRoles);
     engine.rootContext()->setContextProperty("model", model);
 
     RUNEXPR("model.append({roleA: 100})");
@@ -872,17 +921,22 @@ void tst_qdeclarativelistmodel::get_data()
     QTest::addColumn<int>("index");
     QTest::addColumn<QString>("roleName");
     QTest::addColumn<QVariant>("roleValue");
+    QTest::addColumn<bool>("dynamicRoles");
+
+    for (int i=0 ; i < 2 ; ++i) {
+        bool dr = (i != 0);
 
-    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) << dr;
+        QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500) << dr;
 
-    QVariantMap 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).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list);
+        QVariantMap 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).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list) << dr;
+    }
 }
 
 void tst_qdeclarativelistmodel::get_worker()
@@ -891,8 +945,10 @@ void tst_qdeclarativelistmodel::get_worker()
     QFETCH(int, index);
     QFETCH(QString, roleName);
     QFETCH(QVariant, roleValue);
+    QFETCH(bool, dynamicRoles);
 
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("model.qml")));
     QQuickItem *item = createWorkerTest(&eng, &component, &model);
@@ -945,6 +1001,7 @@ void tst_qdeclarativelistmodel::get_nested()
     QFETCH(int, index);
     QFETCH(QString, roleName);
     QFETCH(QVariant, roleValue);
+    QFETCH(bool, dynamicRoles);
 
     if (roleValue.type() == QVariant::Map)
         return;
@@ -955,6 +1012,7 @@ void tst_qdeclarativelistmodel::get_nested()
         "import QtQuick 2.0\n"
         "ListModel {}", QUrl());
     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
+    model->setDynamicRoles(dynamicRoles);
     QVERIFY(component.errorString().isEmpty());
     QDeclarativeListModel *childModel;
     engine.rootContext()->setContextProperty("model", model);
@@ -1078,9 +1136,11 @@ void tst_qdeclarativelistmodel::property_changes()
     QFETCH(int, listIndex);
     QFETCH(bool, itemsChanged);
     QFETCH(QString, testExpression);
+    QFETCH(bool, dynamicRoles);
 
     QDeclarativeEngine engine;
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
     engine.rootContext()->setContextObject(&model);
 
@@ -1134,64 +1194,69 @@ void tst_qdeclarativelistmodel::property_changes_data()
     QTest::addColumn<int>("listIndex");
     QTest::addColumn<bool>("itemsChanged");
     QTest::addColumn<QString>("testExpression");
-
-    QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
-            << "b" << 0 << true << "get(0).b == 123";
-    QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
-            << "b" << 0 << true << "get(0).b == 123";
-
-    QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
-            << "b" << 0 << false << "get(0).b == 456";
-    QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
-            << "b" << 0 << false << "get(0).b == 456";
-
-    QTest::newRow("set: inserted item")
-            << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
-            << "set(1, {'a':456});"
-            << "a" << 1 << true << "get(1).a == 456";
-    QTest::newRow("setProperty: inserted item")
-            << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
-            << "setProperty(1, 'a', 456);"
-            << "a" << 1 << true << "get(1).a == 456";
-    QTest::newRow("get: inserted item")
-            << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
-            << "get(1).a = 456;"
-            << "a" << 1 << true << "get(1).a == 456";
-    QTest::newRow("set: removed item")
-            << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
-            << "set(0, {'a':456});"
-            << "a" << 0 << true << "get(0).a == 456";
-    QTest::newRow("setProperty: removed item")
-            << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
-            << "setProperty(0, 'a', 456);"
-            << "a" << 0 << true << "get(0).a == 456";
-    QTest::newRow("get: removed item")
-            << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
-            << "get(0).a = 456;"
-            << "a" << 0 << true << "get(0).a == 456";
-
-    // Following tests only call set() since setProperty() only allows plain
-    // values, not lists, as the argument.
-    // Note that when a list is changed, itemsChanged() is currently always
-    // emitted regardless of whether it actually changed or not.
-
-    QTest::newRow("nested-set: list, new size") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});"
-            << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2";
-
-    QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
-            << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
-
-    QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
-            << "b" << 0 << true << "get(0).b.count == 0";
-
-    QTest::newRow("nested-set: list, same size, different values") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':222},{'a':3}]});"
-            << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3";
-
-    QTest::newRow("nested-set: list, no changes") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
-            << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
-
-    QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
-            << "b" << 0 << true << "get(0).b.count == 0";
+    QTest::addColumn<bool>("dynamicRoles");
+
+    for (int i=0 ; i < 2 ; ++i) {
+        bool dr = (i != 0);
+
+        QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
+                << "b" << 0 << true << "get(0).b == 123" << dr;
+        QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
+                << "b" << 0 << true << "get(0).b == 123" << dr;
+
+        QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
+                << "b" << 0 << false << "get(0).b == 456" << dr;
+        QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
+                << "b" << 0 << false << "get(0).b == 456" << dr;
+
+        QTest::newRow("set: inserted item")
+                << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
+                << "set(1, {'a':456});"
+                << "a" << 1 << true << "get(1).a == 456" << dr;
+        QTest::newRow("setProperty: inserted item")
+                << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
+                << "setProperty(1, 'a', 456);"
+                << "a" << 1 << true << "get(1).a == 456" << dr;
+        QTest::newRow("get: inserted item")
+                << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
+                << "get(1).a = 456;"
+                << "a" << 1 << true << "get(1).a == 456" << dr;
+        QTest::newRow("set: removed item")
+                << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
+                << "set(0, {'a':456});"
+                << "a" << 0 << true << "get(0).a == 456" << dr;
+        QTest::newRow("setProperty: removed item")
+                << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
+                << "setProperty(0, 'a', 456);"
+                << "a" << 0 << true << "get(0).a == 456" << dr;
+        QTest::newRow("get: removed item")
+                << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
+                << "get(0).a = 456;"
+                << "a" << 0 << true << "get(0).a == 456" << dr;
+
+        // Following tests only call set() since setProperty() only allows plain
+        // values, not lists, as the argument.
+        // Note that when a list is changed, itemsChanged() is currently always
+        // emitted regardless of whether it actually changed or not.
+
+        QTest::newRow("nested-set: list, new size") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});"
+                << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2" << dr;
+
+        QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
+                << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr;
+
+        QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
+                << "b" << 0 << true << "get(0).b.count == 0" << dr;
+
+        QTest::newRow("nested-set: list, same size, different values") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':222},{'a':3}]});"
+                << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3" << dr;
+
+        QTest::newRow("nested-set: list, no changes") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
+                << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr;
+
+        QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
+                << "b" << 0 << true << "get(0).b.count == 0" << dr;
+    }
 }
 
 void tst_qdeclarativelistmodel::property_changes_worker()
@@ -1201,8 +1266,10 @@ void tst_qdeclarativelistmodel::property_changes_worker()
     QFETCH(QString, roleName);
     QFETCH(int, listIndex);
     QFETCH(bool, itemsChanged);
+    QFETCH(bool, dynamicRoles);
 
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine engine;
     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
     QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
@@ -1237,10 +1304,21 @@ void tst_qdeclarativelistmodel::property_changes_worker_data()
     property_changes_data();
 }
 
+void tst_qdeclarativelistmodel::clear_data()
+{
+    QTest::addColumn<bool>("dynamicRoles");
+
+    QTest::newRow("staticRoles") << false;
+    QTest::newRow("dynamicRoles") << true;
+}
+
 void tst_qdeclarativelistmodel::clear()
 {
+    QFETCH(bool, dynamicRoles);
+
     QDeclarativeEngine engine;
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
     engine.rootContext()->setContextProperty("model", &model);
 
@@ -1271,11 +1349,24 @@ void tst_qdeclarativelistmodel::clear()
     QCOMPARE(model.toString(roles[2]), QString("propertyC"));
 }
 
+void tst_qdeclarativelistmodel::signal_handlers_data()
+{
+    QTest::addColumn<bool>("dynamicRoles");
+
+    QTest::newRow("staticRoles") << false;
+    QTest::newRow("dynamicRoles") << true;
+}
+
 void tst_qdeclarativelistmodel::signal_handlers()
 {
+    QFETCH(bool, dynamicRoles);
+
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("signalhandlers.qml")));
     QObject *model = component.create();
+    QDeclarativeListModel *lm = qobject_cast<QDeclarativeListModel *>(model);
+    QVERIFY(lm != 0);
+    lm->setDynamicRoles(dynamicRoles);
     QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
     QVERIFY(model != 0);
     QVERIFY(model->property("ok").toBool());
@@ -1283,9 +1374,20 @@ void tst_qdeclarativelistmodel::signal_handlers()
     delete model;
 }
 
+void tst_qdeclarativelistmodel::worker_sync_data()
+{
+    QTest::addColumn<bool>("dynamicRoles");
+
+    QTest::newRow("staticRoles") << false;
+    QTest::newRow("dynamicRoles") << true;
+}
+
 void tst_qdeclarativelistmodel::worker_sync()
 {
+    QFETCH(bool, dynamicRoles);
+
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("workersync.qml")));
     QQuickItem *item = createWorkerTest(&eng, &component, &model);
@@ -1340,9 +1442,17 @@ void tst_qdeclarativelistmodel::worker_sync()
     qApp->processEvents();
 }
 
+void tst_qdeclarativelistmodel::worker_remove_element_data()
+{
+    worker_sync_data();
+}
+
 void tst_qdeclarativelistmodel::worker_remove_element()
 {
+    QFETCH(bool, dynamicRoles);
+
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("workerremoveelement.qml")));
     QQuickItem *item = createWorkerTest(&eng, &component, &model);
@@ -1373,9 +1483,17 @@ void tst_qdeclarativelistmodel::worker_remove_element()
     qApp->processEvents();
 }
 
+void tst_qdeclarativelistmodel::worker_remove_list_data()
+{
+    worker_sync_data();
+}
+
 void tst_qdeclarativelistmodel::worker_remove_list()
 {
+    QFETCH(bool, dynamicRoles);
+
     QDeclarativeListModel model;
+    model.setDynamicRoles(dynamicRoles);
     QDeclarativeEngine eng;
     QDeclarativeComponent component(&eng, QUrl::fromLocalFile(TESTDATA("workerremovelist.qml")));
     QQuickItem *item = createWorkerTest(&eng, &component, &model);
@@ -1406,6 +1524,87 @@ void tst_qdeclarativelistmodel::worker_remove_list()
     qApp->processEvents();
 }
 
+void tst_qdeclarativelistmodel::role_mode_data()
+{
+    QTest::addColumn<QString>("script");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("warning");
+
+    QTest::newRow("default0") << "{dynamicRoles}" << 0 << "";
+    QTest::newRow("default1") << "{append({'a':1});dynamicRoles}" << 0 << "";
+
+    QTest::newRow("enableDynamic0") << "{dynamicRoles=true;dynamicRoles}" << 1 << "";
+    QTest::newRow("enableDynamic1") << "{append({'a':1});dynamicRoles=true;dynamicRoles}" << 0 << "<Unknown File>: QML ListModel: unable to enable dynamic roles as this model is not empty!";
+    QTest::newRow("enableDynamic2") << "{dynamicRoles=true;append({'a':1});dynamicRoles=false;dynamicRoles}" << 1 << "<Unknown File>: QML ListModel: unable to enable static roles as this model is not empty!";
+}
+
+void tst_qdeclarativelistmodel::role_mode()
+{
+    QFETCH(QString, script);
+    QFETCH(int, result);
+    QFETCH(QString, warning);
+
+    QDeclarativeEngine engine;
+    QDeclarativeListModel model;
+    QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
+    engine.rootContext()->setContextObject(&model);
+    QDeclarativeExpression e(engine.rootContext(), &model, script);
+    if (!warning.isEmpty())
+        QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
+
+    int actual = e.evaluate().toInt();
+    if (e.hasError())
+        qDebug() << e.error(); // errors not expected
+
+    QCOMPARE(actual,result);
+}
+
+void tst_qdeclarativelistmodel::dynamic_role_data()
+{
+    QTest::addColumn<QString>("preamble");
+    QTest::addColumn<QString>("script");
+    QTest::addColumn<int>("result");
+
+    QTest::newRow("sync1") << "{append({'a':[{'b':1},{'b':2}]})}" << "{get(0).a = 'string';count}" << 1;
+}
+
+void tst_qdeclarativelistmodel::dynamic_role()
+{
+    QFETCH(QString, preamble);
+    QFETCH(QString, script);
+    QFETCH(int, result);
+
+    QDeclarativeListModel model;
+    model.setDynamicRoles(true);
+    QDeclarativeEngine engine;
+    QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
+    QQuickItem *item = createWorkerTest(&engine, &component, &model);
+    QVERIFY(item != 0);
+
+    QDeclarativeExpression preExp(engine.rootContext(), &model, preamble);
+    QCOMPARE(preExp.evaluate().toInt(), 0);
+
+    if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
+        script = script.mid(1, script.length() - 2);
+    QVariantList operations;
+    foreach (const QString &s, script.split(';')) {
+        if (!s.isEmpty())
+            operations << s;
+    }
+
+    // execute a set of commands on the worker list model, then check the
+    // changes are reflected in the list model in the main thread
+    QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
+            Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
+    waitForWorker(item);
+
+    QDeclarativeExpression e(engine.rootContext(), &model, operations.last().toString());
+    QCOMPARE(e.evaluate().toInt(), result);
+
+    delete item;
+    qApp->processEvents();
+}
+
 QTEST_MAIN(tst_qdeclarativelistmodel)
 
 #include "tst_qdeclarativelistmodel.moc"