From 542a7d164ad81973f911a54bc53aa385d09b5c1f Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Thu, 1 Dec 2011 10:40:44 +1000 Subject: [PATCH] Add dynamicRoles mode to ListModel for extra flexibility. 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 --- src/declarative/qml/qdeclarativelistmodel.cpp | 495 +++++++++++++++-- src/declarative/qml/qdeclarativelistmodel_p.h | 33 +- src/declarative/qml/qdeclarativelistmodel_p_p.h | 62 ++- .../qml/qdeclarativelistmodelworkeragent.cpp | 49 +- .../qml/qdeclarativelistmodelworkeragent_p.h | 10 +- src/declarative/qml/qdeclarativeopenmetaobject.cpp | 7 +- src/declarative/qml/qdeclarativeopenmetaobject_p.h | 2 +- .../tst_qdeclarativelistmodel.cpp | 601 ++++++++++++++------- 8 files changed, 980 insertions(+), 279 deletions(-) diff --git a/src/declarative/qml/qdeclarativelistmodel.cpp b/src/declarative/qml/qdeclarativelistmodel.cpp index 9e4d726..16391a3 100644 --- a/src/declarative/qml/qdeclarativelistmodel.cpp +++ b/src/declarative/qml/qdeclarativelistmodel.cpp @@ -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 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::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 object, QList 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()); 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 roles; + object->updateValues(obj, roles); + return object; +} + +void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash *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(value.value()); + QDeclarativeListModel *targetModel = qobject_cast(target->m_meta->value(i).value()); + + 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 &roles) +{ + const QList &keys = object.keys(); + + QList::const_iterator it = keys.begin(); + QList::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(m_meta->value(keyUtf8).value()); + 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(value(i).value()); + if (subModel) + delete subModel; + } +} + +void DynamicRoleModelNodeMetaObject::propertyWrite(int index) +{ + if (!m_enabled) + return; + + QVariant v = value(index); + QDeclarativeListModel *model = qobject_cast(v.value()); + 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 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 *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 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::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::iterator it = elementHash.begin(); + QHash::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 &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 QDeclarativeListModel::roles() const { QList 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 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 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 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 objectArray = v8::Handle::Cast(arg); int objectArrayLength = objectArray->Length(); - int index = m_listModel->elementCount(); + + int index = count(); for (int i=0 ; i < objectArrayLength ; ++i) { v8::Handle 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 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 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(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(this), index); + result = v8engine->newQObject(object); + } } return QDeclarativeV8Handle::fromHandle(result); @@ -1678,12 +2061,23 @@ void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle) v8::Handle 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 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 roles; + roles << roleIndex; + emitItemsChanged(index, 1, roles); + } + } else { + int roleIndex = m_listModel->setOrCreateProperty(index, property, value); + if (roleIndex != -1) { - QList roles; - roles << roleIndex; + QList 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) { diff --git a/src/declarative/qml/qdeclarativelistmodel_p.h b/src/declarative/qml/qdeclarativelistmodel_p.h index 2360f42..3b1e037 100644 --- a/src/declarative/qml/qdeclarativelistmodel_p.h +++ b/src/declarative/qml/qdeclarativelistmodel_p.h @@ -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 m_modelObjects; + QVector 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 *targetModelHash); + static QDeclarativeListModel *createWithOwner(QDeclarativeListModel *newOwner); void emitItemsChanged(int index, int count, const QList &roles); void emitItemsRemoved(int index, int count); diff --git a/src/declarative/qml/qdeclarativelistmodel_p_p.h b/src/declarative/qml/qdeclarativelistmodel_p_p.h index ab6c2b2..1ef1a17 100644 --- a/src/declarative/qml/qdeclarativelistmodel_p_p.h +++ b/src/declarative/qml/qdeclarativelistmodel_p_p.h @@ -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 &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 *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 *srcModelHash); ModelObject *getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex); @@ -311,8 +367,6 @@ private: friend class ListElement; friend class QDeclarativeListModelWorkerAgent; - - static QAtomicInt uidCounter; }; QT_END_NAMESPACE diff --git a/src/declarative/qml/qdeclarativelistmodelworkeragent.cpp b/src/declarative/qml/qdeclarativelistmodelworkeragent.cpp index c01025c..3904d35 100644 --- a/src/declarative/qml/qdeclarativelistmodelworkeragent.cpp +++ b/src/declarative/qml/qdeclarativelistmodelworkeragent.cpp @@ -53,10 +53,8 @@ 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() }; + Change c = { uid, Change::Inserted, index, count, 0, QList() }; 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() }; + Change c = { uid, Change::Removed, index, count, 0, QList() }; 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() }; + Change c = { uid, Change::Moved, index, count, to, QList() }; changes << c; } -void QDeclarativeListModelWorkerAgent::Data::changedChange(QDeclarativeListModel *model, int index, int count, const QList &roles) +void QDeclarativeListModelWorkerAgent::Data::changedChange(int uid, int index, int count, const QList &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 targetModelHash; - ListModel::sync(s->list->m_listModel, m_orig->m_listModel, &targetModelHash); + QHash targetModelDynamicHash; + QHash 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; } } diff --git a/src/declarative/qml/qdeclarativelistmodelworkeragent_p.h b/src/declarative/qml/qdeclarativelistmodelworkeragent_p.h index 05c3647..d27a05f 100644 --- a/src/declarative/qml/qdeclarativelistmodelworkeragent_p.h +++ b/src/declarative/qml/qdeclarativelistmodelworkeragent_p.h @@ -130,11 +130,11 @@ private: { QList 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 &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 &roles); }; Data data; diff --git a/src/declarative/qml/qdeclarativeopenmetaobject.cpp b/src/declarative/qml/qdeclarativeopenmetaobject.cpp index d4dd824..9e4cfc5 100644 --- a/src/declarative/qml/qdeclarativeopenmetaobject.cpp +++ b/src/declarative/qml/qdeclarativeopenmetaobject.cpp @@ -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::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() diff --git a/src/declarative/qml/qdeclarativeopenmetaobject_p.h b/src/declarative/qml/qdeclarativeopenmetaobject_p.h index e5cfee7..8f711d1 100644 --- a/src/declarative/qml/qdeclarativeopenmetaobject_p.h +++ b/src/declarative/qml/qdeclarativeopenmetaobject_p.h @@ -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 &); diff --git a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp index c32a904..661b086 100644 --- a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp +++ b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp @@ -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("script"); QTest::addColumn("result"); QTest::addColumn("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 << ": 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 << ": QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]"; - QTest::newRow("remove4a") << "{remove(0)}" << 0 << ": QML ListModel: remove: indices [0 - 1] out of range [0 - 0]"; - QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << ": QML ListModel: remove: indices [0 - 1] out of range [0 - 0]"; - QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << ": 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 << ": QML ListModel: remove: incorrect number of arguments"; - QTest::newRow("remove6b") << "{remove(1,2,3);count}" << 0 << ": QML ListModel: remove: incorrect number of arguments"; - QTest::newRow("remove7a") << "{append({'foo':123});remove(0,0);count}" << 1 << ": QML ListModel: remove: indices [0 - 0] out of range [0 - 1]"; - QTest::newRow("remove7b") << "{append({'foo':123});remove(0,-1);count}" << 1 << ": 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 << ": 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 << ": QML ListModel: insert: index -1 out of range"; - QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << ": 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 << ": QML ListModel: set: index -1 out of range"; - QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << ": QML ListModel: set: value is not an object"; - QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << ": 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 << ": QML ListModel: set: index 0 out of range"; - QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << ": QML ListModel: set: index -1 out of range"; - QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << ": 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 << ": QML ListModel: move: out of range"; - QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << ": QML ListModel: move: out of range"; - QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << ": QML ListModel: move: out of range"; - QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << ": 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 << ": 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 << ": Can't assign to existing role 'a' of different type [Number -> String]"; - QTest::newRow("invalidInsert0") << "{insert(0);}" << 0 << ": QML ListModel: insert: value is not an object"; - QTest::newRow("invalidAppend0") << "{append();}" << 0 << ": QML ListModel: append: value is not an object"; - QTest::newRow("invalidInsert1") << "{insert(0, 34);}" << 0 << ": QML ListModel: insert: value is not an object"; - QTest::newRow("invalidAppend1") << "{append(37);}" << 0 << ": 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("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 << ": 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 << ": QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]" << dr; + QTest::newRow("remove4a") << "{remove(0)}" << 0 << ": QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr; + QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << ": QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr; + QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << ": 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 << ": QML ListModel: remove: incorrect number of arguments" << dr; + QTest::newRow("remove6b") << "{remove(1,2,3);count}" << 0 << ": QML ListModel: remove: incorrect number of arguments" << dr; + QTest::newRow("remove7a") << "{append({'foo':123});remove(0,0);count}" << 1 << ": QML ListModel: remove: indices [0 - 0] out of range [0 - 1]" << dr; + QTest::newRow("remove7b") << "{append({'foo':123});remove(0,-1);count}" << 1 << ": 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 << ": 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 << ": QML ListModel: insert: index -1 out of range" << dr; + QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << ": 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 << ": QML ListModel: set: index -1 out of range" << dr; + QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << ": QML ListModel: set: value is not an object" << dr; + QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << ": 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 << ": QML ListModel: set: index 0 out of range" << dr; + QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << ": QML ListModel: set: index -1 out of range" << dr; + QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << ": 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 << ": 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 << ": 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 << ": 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 << ": 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 << ": 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 << ": Can't assign to existing role 'a' of different type [Number -> String]" << dr; + QTest::newRow("invalidInsert0") << "{insert(0);}" << 0 << ": QML ListModel: insert: value is not an object" << dr; + QTest::newRow("invalidAppend0") << "{append();}" << 0 << ": QML ListModel: append: value is not an object" << dr; + QTest::newRow("invalidInsert1") << "{insert(0, 34);}" << 0 << ": QML ListModel: insert: value is not an object" << dr; + QTest::newRow("invalidAppend1") << "{append(37);}" << 0 << ": 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("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, ": Can't create role for unsupported data type"); + QString warning = QString::fromLatin1(": 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(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("index"); QTest::addColumn("roleName"); QTest::addColumn("roleValue"); + QTest::addColumn("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(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("listIndex"); QTest::addColumn("itemsChanged"); QTest::addColumn("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("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("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("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(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("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("script"); + QTest::addColumn("result"); + QTest::addColumn("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 << ": 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 << ": 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("preamble"); + QTest::addColumn("script"); + QTest::addColumn("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" -- 2.7.4