aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlenn Watson <glenn.watson@nokia.com>2011-10-26 10:28:01 +1000
committerQt by Nokia <qt-info@nokia.com>2011-10-26 03:41:53 +0200
commitca3dfc52667a036901d640e4f9ed30f267153b2c (patch)
tree99c036580179b73e74e3e798cd91ff2b3fc5341d
parent31d34d98f192035c42bd1d1c07bf6e297da76220 (diff)
Optimize listmodel and allow nested elements from worker script.
Added support for nested listmodels when used from a worker script thread. Optimized the implementation of ListModel, especially the performance of appending a large number of items. Added a batch append mode (with an array of JS objects) to reduce the overhead of calling from JS into native code for each append operation. Task-number:QTBUG-21508 Change-Id: I07b381dc3e8200d92d6e0af458df8850d78b510f Reviewed-by: Martin Jones <martin.jones@nokia.com>
-rw-r--r--src/declarative/qml/v8/qv8qobjectwrapper_p.h2
-rw-r--r--src/declarative/qml/v8/qv8variantwrapper_p.h4
-rw-r--r--src/declarative/util/qdeclarativelistmodel.cpp2218
-rw-r--r--src/declarative/util/qdeclarativelistmodel_p.h50
-rw-r--r--src/declarative/util/qdeclarativelistmodel_p_p.h324
-rw-r--r--src/declarative/util/qdeclarativelistmodelworkeragent.cpp140
-rw-r--r--src/declarative/util/qdeclarativelistmodelworkeragent_p.h27
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js8
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml33
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js9
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml33
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workersync.js8
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml32
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp560
14 files changed, 1966 insertions, 1482 deletions
diff --git a/src/declarative/qml/v8/qv8qobjectwrapper_p.h b/src/declarative/qml/v8/qv8qobjectwrapper_p.h
index be118a9c34..564510f15d 100644
--- a/src/declarative/qml/v8/qv8qobjectwrapper_p.h
+++ b/src/declarative/qml/v8/qv8qobjectwrapper_p.h
@@ -83,7 +83,7 @@ public:
v8::Handle<v8::Value> newQObject(QObject *object);
bool isQObject(v8::Handle<v8::Object>);
QObject *toQObject(v8::Handle<v8::Object>);
- QObject *toQObject(QV8ObjectResource *);
+ static QObject *toQObject(QV8ObjectResource *);
enum RevisionMode { IgnoreRevision, CheckRevision };
inline v8::Handle<v8::Value> getProperty(QObject *, const QHashedV8String &, RevisionMode);
diff --git a/src/declarative/qml/v8/qv8variantwrapper_p.h b/src/declarative/qml/v8/qv8variantwrapper_p.h
index 7e9924b3c0..8bb0bcc073 100644
--- a/src/declarative/qml/v8/qv8variantwrapper_p.h
+++ b/src/declarative/qml/v8/qv8variantwrapper_p.h
@@ -72,8 +72,8 @@ public:
v8::Local<v8::Object> newVariant(const QVariant &);
bool isVariant(v8::Handle<v8::Value>);
- QVariant toVariant(v8::Handle<v8::Object>);
- QVariant toVariant(QV8ObjectResource *);
+ static QVariant toVariant(v8::Handle<v8::Object>);
+ static QVariant toVariant(QV8ObjectResource *);
QVariant &variantValue(v8::Handle<v8::Value>);
private:
diff --git a/src/declarative/util/qdeclarativelistmodel.cpp b/src/declarative/util/qdeclarativelistmodel.cpp
index 4e67182048..445944043b 100644
--- a/src/declarative/util/qdeclarativelistmodel.cpp
+++ b/src/declarative/util/qdeclarativelistmodel.cpp
@@ -59,141 +59,1022 @@ Q_DECLARE_METATYPE(QListModelInterface *)
QT_BEGIN_NAMESPACE
-QV8ListModelResource::QV8ListModelResource(FlatListModel *model, FlatNodeData *data, QV8Engine *engine)
-: QV8ObjectResource(engine), model(model), nodeData(data)
+// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
+enum { MIN_LISTMODEL_UID = 1024 };
+
+QAtomicInt ListModel::uidCounter(MIN_LISTMODEL_UID);
+
+const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
+{
+ QStringHash<Role *>::Node *node = roleHash.findNode(key);
+ if (node) {
+ const Role &r = *node->value;
+ if (type != r.type) {
+ qmlInfo(0) << "Can't assign to pre-existing role of different type " << r.name;
+ }
+ return r;
+ }
+
+ return createRole(key, type);
+}
+
+const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type)
+{
+ QHashedV8String hashedKey(key);
+ QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
+ if (node) {
+ const Role &r = *node->value;
+ if (type != r.type) {
+ qmlInfo(0) << "Can't assign to pre-existing role of different type " << r.name;
+ }
+ return r;
+ }
+
+ QString qkey;
+ qkey.resize(key->Length());
+ key->Write(reinterpret_cast<uint16_t*>(qkey.data()));
+
+ return createRole(qkey, type);
+}
+
+const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
+{
+ const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QDeclarativeGuard<QObject>) };
+ const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QObject *) };
+
+ Role *r = new Role;
+ r->name = key;
+ r->type = type;
+
+ if (type == Role::List) {
+ r->subLayout = new ListLayout;
+ } else {
+ r->subLayout = 0;
+ }
+
+ int dataSize = dataSizes[type];
+ int dataAlignment = dataAlignments[type];
+
+ int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
+ if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
+ r->blockIndex = ++currentBlock;
+ r->blockOffset = 0;
+ currentBlockOffset = dataSize;
+ } else {
+ r->blockIndex = currentBlock;
+ r->blockOffset = dataOffset;
+ currentBlockOffset = dataOffset + dataSize;
+ }
+
+ int roleIndex = roles.count();
+ r->index = roleIndex;
+
+ roles.append(r);
+ roleHash.insert(key, r);
+
+ return *r;
+}
+
+ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
{
- if (nodeData) nodeData->addData(this);
+ for (int i=0 ; i < other->roles.count() ; ++i) {
+ Role *role = new Role(other->roles[i]);
+ roles.append(role);
+ roleHash.insert(role->name, role);
+ }
+ currentBlockOffset = other->currentBlockOffset;
+ currentBlock = other->currentBlock;
}
-QV8ListModelResource::~QV8ListModelResource()
+ListLayout::~ListLayout()
{
- if (nodeData) nodeData->removeData(this);
+ for (int i=0 ; i < roles.count() ; ++i) {
+ delete roles[i];
+ }
}
-class QDeclarativeListModelV8Data : public QV8Engine::Deletable
+void ListLayout::sync(ListLayout *src, ListLayout *target)
{
-public:
- QDeclarativeListModelV8Data();
- ~QDeclarativeListModelV8Data();
+ int roleOffset = target->roles.count();
+ int newRoleCount = src->roles.count() - roleOffset;
+
+ for (int i=0 ; i < newRoleCount ; ++i) {
+ Role *role = new Role(src->roles[roleOffset + i]);
+ target->roles.append(role);
+ target->roleHash.insert(role->name, role);
+ }
- v8::Persistent<v8::Function> constructor;
+ target->currentBlockOffset = src->currentBlockOffset;
+ target->currentBlock = src->currentBlock;
+}
- static v8::Local<v8::Object> create(QV8Engine *);
+ListLayout::Role::Role(const Role *other)
+{
+ name = other->name;
+ type = other->type;
+ blockIndex = other->blockIndex;
+ blockOffset = other->blockOffset;
+ index = other->index;
+ if (other->subLayout)
+ subLayout = new ListLayout(other->subLayout);
+ else
+ subLayout = 0;
+}
- static v8::Handle<v8::Value> Getter(v8::Local<v8::String> property,
- const v8::AccessorInfo &info);
- static v8::Handle<v8::Value> Setter(v8::Local<v8::String> property,
- v8::Local<v8::Value> value,
- const v8::AccessorInfo &info);
-};
+ListLayout::Role::~Role()
+{
+ delete subLayout;
+}
-v8::Local<v8::Object> QDeclarativeListModelV8Data::create(QV8Engine *engine)
+const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
{
- if (!engine->listModelData()) {
- QDeclarativeListModelV8Data *d = new QDeclarativeListModelV8Data;
- engine->setListModelData(d);
+ Role::DataType type;
+
+ switch (data.type()) {
+ case QVariant::Double: type = Role::Number; break;
+ case QVariant::Int: type = Role::Number; break;
+ case QVariant::UserType: type = Role::List; break;
+ case QVariant::Bool: type = Role::Bool; break;
+ case QVariant::String: type = Role::String; break;
+ default: type = Role::Invalid; break;
+ }
+
+ if (type == Role::Invalid) {
+ qmlInfo(0) << "Can't create role for unsupported data type";
+ return 0;
}
- QDeclarativeListModelV8Data *d = (QDeclarativeListModelV8Data *)engine->listModelData();
- return d->constructor->NewInstance();
+ return &getRoleOrCreate(key, type);
+}
+
+const ListLayout::Role *ListLayout::getExistingRole(const QString &key)
+{
+ Role *r = 0;
+ QStringHash<Role *>::Node *node = roleHash.findNode(key);
+ if (node)
+ r = node->value;
+ return r;
}
-QDeclarativeListModelV8Data::QDeclarativeListModelV8Data()
+const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key)
{
- v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
- ft->InstanceTemplate()->SetNamedPropertyHandler(Getter, Setter);
- ft->InstanceTemplate()->SetHasExternalResource(true);
- constructor = qPersistentNew<v8::Function>(ft->GetFunction());
+ Role *r = 0;
+ QHashedV8String hashedKey(key);
+ QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
+ if (node)
+ r = node->value;
+ return r;
}
-QDeclarativeListModelV8Data::~QDeclarativeListModelV8Data()
+ModelObject *ListModel::getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex)
{
- qPersistentDispose(constructor);
+ ListElement *e = elements[elementIndex];
+ if (e->m_objectCache == 0) {
+ e->m_objectCache = new ModelObject(model, elementIndex);
+ }
+ return e->m_objectCache;
}
-v8::Handle<v8::Value> QDeclarativeListModelV8Data::Getter(v8::Local<v8::String> property,
- const v8::AccessorInfo &info)
+void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
{
- QV8ListModelResource *r = v8_resource_cast<QV8ListModelResource>(info.This());
- if (!r)
- return v8::Undefined();
+ // Sanity check
+ target->m_uid = src->m_uid;
+ if (targetModelHash)
+ targetModelHash->insert(target->m_uid, target);
- if (!r->nodeData) // Item at this index has been deleted
- return v8::Undefined();
+ // Build hash of elements <-> uid for each of the lists
+ QHash<int, ElementSync> elementHash;
+ for (int i=0 ; i < target->elements.count() ; ++i) {
+ ListElement *e = target->elements.at(i);
+ int uid = e->getUid();
+ ElementSync sync;
+ sync.target = e;
+ elementHash.insert(uid, sync);
+ }
+ for (int i=0 ; i < src->elements.count() ; ++i) {
+ ListElement *e = src->elements.at(i);
+ int uid = e->getUid();
+
+ QHash<int, ElementSync>::iterator it = elementHash.find(uid);
+ if (it == elementHash.end()) {
+ ElementSync sync;
+ sync.src = e;
+ elementHash.insert(uid, sync);
+ } else {
+ ElementSync &sync = it.value();
+ sync.src = e;
+ }
+ }
+ // Get list of elements that are in the target but no longer in the source. These get deleted first.
+ QHash<int, ElementSync>::iterator it = elementHash.begin();
+ QHash<int, ElementSync>::iterator end = elementHash.end();
+ while (it != end) {
+ const ElementSync &s = it.value();
+ if (s.src == 0) {
+ s.target->destroy(target->m_layout);
+ target->elements.removeOne(s.target);
+ delete s.target;
+ }
+ ++it;
+ }
- int index = r->nodeData->index;
- QString propName = r->engine->toString(property);
+ // Sync the layouts
+ ListLayout::sync(src->m_layout, target->m_layout);
+
+ // Clear the target list, and append in correct order from the source
+ target->elements.clear();
+ for (int i=0 ; i < src->elements.count() ; ++i) {
+ ListElement *srcElement = src->elements.at(i);
+ it = elementHash.find(srcElement->getUid());
+ const ElementSync &s = it.value();
+ ListElement *targetElement = s.target;
+ if (targetElement == 0) {
+ targetElement = new ListElement(srcElement->getUid());
+ }
+ ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
+ target->elements.append(targetElement);
+ }
- int role = r->model->m_strings.value(propName, -1);
+ target->updateCacheIndices();
- if (role >= 0 && index >=0 ) {
- const QHash<int, QVariant> &row = r->model->m_values[index];
- return r->engine->fromVariant(row[role]);
+ // Update values stored in target meta objects
+ for (int i=0 ; i < target->elements.count() ; ++i) {
+ ListElement *e = target->elements[i];
+ if (e->m_objectCache)
+ e->m_objectCache->updateValues();
}
+}
+
+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();
+ m_uid = uid;
+}
+
+void ListModel::destroy()
+{
+ clear();
+ m_uid = -1;
+ m_layout = 0;
+ if (m_modelCache && m_modelCache->m_primary == false)
+ delete m_modelCache;
+ m_modelCache = 0;
+}
+
+int ListModel::appendElement()
+{
+ int elementIndex = elements.count();
+ newElement(elementIndex);
+ return elementIndex;
+}
- return v8::Undefined();
+void ListModel::insertElement(int index)
+{
+ newElement(index);
+ updateCacheIndices();
}
-v8::Handle<v8::Value> QDeclarativeListModelV8Data::Setter(v8::Local<v8::String> property,
- v8::Local<v8::Value> value,
- const v8::AccessorInfo &info)
+void ListModel::move(int from, int to, int n)
{
- QV8ListModelResource *r = v8_resource_cast<QV8ListModelResource>(info.This());
- if (!r)
- return v8::Undefined();
+ if (from > to) {
+ // Only move forwards - flip if backwards moving
+ int tfrom = from;
+ int tto = to;
+ from = tto;
+ to = tto+n;
+ n = tfrom-tto;
+ }
+
+ QPODVector<ListElement *, 4> store;
+ for (int i=0 ; i < (to-from) ; ++i)
+ store.append(elements[from+n+i]);
+ for (int i=0 ; i < n ; ++i)
+ store.append(elements[from+i]);
+ for (int i=0 ; i < store.count() ; ++i)
+ elements[from+i] = store[i];
- if (!r->nodeData) // item at this index has been deleted
- return value;
+ updateCacheIndices();
+}
+void ListModel::newElement(int index)
+{
+ ListElement *e = new ListElement;
+ elements.insert(index, e);
+}
- if (!value->IsRegExp() && !value->IsDate() && value->IsObject() && !r->engine->isVariant(value)) {
- qmlInfo(r->model->m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
- return value;
+void ListModel::updateCacheIndices()
+{
+ for (int i=0 ; i < elements.count() ; ++i) {
+ ListElement *e = elements.at(i);
+ if (e->m_objectCache) {
+ e->m_objectCache->m_elementIndex = i;
+ }
}
+}
+
+QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QDeclarativeListModel *owner, QV8Engine *eng)
+{
+ ListElement *e = elements[elementIndex];
+ const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
+ return e->getProperty(r, owner, eng);
+}
- int index = r->nodeData->index;
- QString propName = r->engine->toString(property);
+ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
+{
+ ListElement *e = elements[elementIndex];
+ return e->getListProperty(role);
+}
+
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles)
+{
+ ListElement *e = elements[elementIndex];
- int role = r->model->m_strings.value(propName, -1);
- if (role >= 0 && index >= 0) {
- QHash<int, QVariant> &row = r->model->m_values[index];
- row[role] = r->engine->toVariant(value, -1);
+ v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
+ int propertyCount = propertyNames->Length();
- QList<int> roles;
- roles << role;
- if (r->model->m_parentAgent) {
- // This is the list in the worker thread, so tell the agent to
- // emit itemsChanged() later
- r->model->m_parentAgent->changedData(index, 1, roles);
+ for (int i=0 ; i < propertyCount ; ++i) {
+ v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
+ v8::Local<v8::Value> propertyValue = object->Get(propertyName);
+
+ // Check if this key exists yet
+ int roleIndex = -1;
+
+ // Add the value now
+ if (propertyValue->IsString()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
+ v8::Handle<v8::String> jsString = propertyValue->ToString();
+ QString qstr;
+ qstr.resize(jsString->Length());
+ jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+ roleIndex = e->setStringProperty(r, qstr);
+ } else if (propertyValue->IsNumber()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue());
+ } else if (propertyValue->IsArray()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
+ ListModel *subModel = new ListModel(r.subLayout, 0, -1);
+
+ v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
+ int arrayLength = subArray->Length();
+ for (int j=0 ; j < arrayLength ; ++j) {
+ v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+ subModel->append(subObject);
+ }
+
+ roleIndex = e->setListProperty(r, subModel);
+ } else if (propertyValue->IsInt32()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ roleIndex = e->setDoubleProperty(r, (double) propertyValue->Int32Value());
+ } else if (propertyValue->IsBoolean()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
+ roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue());
+ } else if (propertyValue->IsObject()) {
+ QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
+ if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
+ QObject *o = QV8QObjectWrapper::toQObject(r);
+ const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+ if (role.type == ListLayout::Role::QObject)
+ e->setQObjectProperty(role, o);
+ }
+ } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
+ const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
+ if (r)
+ e->clearProperty(*r);
+ }
+
+ if (roleIndex != -1)
+ roles->append(roleIndex);
+ }
+
+ if (e->m_objectCache) {
+ e->m_objectCache->updateValues(*roles);
+ }
+}
+
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object)
+{
+ ListElement *e = elements[elementIndex];
+
+ v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
+ int propertyCount = propertyNames->Length();
+
+ for (int i=0 ; i < propertyCount ; ++i) {
+ v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
+ v8::Local<v8::Value> propertyValue = object->Get(propertyName);
+
+ // Add the value now
+ if (propertyValue->IsString()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
+ if (r.type == ListLayout::Role::String) {
+ v8::Handle<v8::String> jsString = propertyValue->ToString();
+ QString qstr;
+ qstr.resize(jsString->Length());
+ jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+ e->setStringPropertyFast(r, qstr);
+ }
+ } else if (propertyValue->IsNumber()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ if (r.type == ListLayout::Role::Number) {
+ e->setDoublePropertyFast(r, propertyValue->NumberValue());
+ }
+ } else if (propertyValue->IsArray()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
+ if (r.type == ListLayout::Role::List) {
+ ListModel *subModel = new ListModel(r.subLayout, 0, -1);
+
+ v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
+ int arrayLength = subArray->Length();
+ for (int j=0 ; j < arrayLength ; ++j) {
+ v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+ subModel->append(subObject);
+ }
+
+ e->setListPropertyFast(r, subModel);
+ }
+ } else if (propertyValue->IsInt32()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ if (r.type == ListLayout::Role::Number) {
+ e->setDoublePropertyFast(r, (double) propertyValue->Int32Value());
+ }
+ } else if (propertyValue->IsBoolean()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
+ if (r.type == ListLayout::Role::Bool) {
+ e->setBoolPropertyFast(r, propertyValue->BooleanValue());
+ }
+ } else if (propertyValue->IsObject()) {
+ QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
+ if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
+ QObject *o = QV8QObjectWrapper::toQObject(r);
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+ if (r.type == ListLayout::Role::QObject)
+ e->setQObjectPropertyFast(r, o);
+ }
+ } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
+ const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
+ if (r)
+ e->clearProperty(*r);
+ }
+ }
+}
+
+void ListModel::clear()
+{
+ int elementCount = elements.count();
+ for (int i=0 ; i < elementCount ; ++i) {
+ elements[i]->destroy(m_layout);
+ delete elements[i];
+ }
+ elements.clear();
+}
+
+void ListModel::remove(int index)
+{
+ elements[index]->destroy(m_layout);
+ delete elements[index];
+ elements.remove(index);
+ updateCacheIndices();
+}
+
+void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object)
+{
+ insertElement(elementIndex);
+ set(elementIndex, object);
+}
+
+int ListModel::append(v8::Handle<v8::Object> object)
+{
+ int elementIndex = appendElement();
+ set(elementIndex, object);
+ return elementIndex;
+}
+
+int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
+{
+ int roleIndex = -1;
+
+ 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);
+
+ if (roleIndex != -1 && e->m_objectCache) {
+ QList<int> roles;
+ roles << roleIndex;
+ e->m_objectCache->updateValues(roles);
+ }
+ }
+ }
+
+ return roleIndex;
+}
+
+int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data)
+{
+ int roleIndex = -1;
+
+ if (elementIndex >= 0 && elementIndex < elements.count()) {
+ ListElement *e = elements[elementIndex];
+ const ListLayout::Role *r = m_layout->getExistingRole(key);
+ if (r)
+ roleIndex = e->setJsProperty(*r, data);
+ }
+
+ return roleIndex;
+}
+
+inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
+{
+ ListElement *e = this;
+ int blockIndex = 0;
+ while (blockIndex < role.blockIndex) {
+ if (e->next == 0) {
+ e->next = new ListElement;
+ e->next->uid = uid;
+ }
+ e = e->next;
+ ++blockIndex;
+ }
+
+ char *mem = &e->data[role.blockOffset];
+ return mem;
+}
+
+QString *ListElement::getStringProperty(const ListLayout::Role &role)
+{
+ char *mem = getPropertyMemory(role);
+ QString *s = reinterpret_cast<QString *>(mem);
+ return s->data_ptr() ? s : 0;
+}
+
+QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
+{
+ char *mem = getPropertyMemory(role);
+ QDeclarativeGuard<QObject> *o = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ return o->data();
+}
+
+QDeclarativeGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
+{
+ char *mem = getPropertyMemory(role);
+ QDeclarativeGuard<QObject> *o = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ return o;
+}
+
+ListModel *ListElement::getListProperty(const ListLayout::Role &role)
+{
+ char *mem = getPropertyMemory(role);
+ ListModel **value = reinterpret_cast<ListModel **>(mem);
+ return *value;
+}
+
+QVariant ListElement::getProperty(const ListLayout::Role &role, const QDeclarativeListModel *owner, QV8Engine *eng)
+{
+ char *mem = getPropertyMemory(role);
+
+ QVariant data;
+
+ switch (role.type) {
+ case ListLayout::Role::Number:
+ {
+ double *value = reinterpret_cast<double *>(mem);
+ data = *value;
+ }
+ break;
+ case ListLayout::Role::String:
+ {
+ QString *value = reinterpret_cast<QString *>(mem);
+ if (value->data_ptr() != 0)
+ data = *value;
+ }
+ break;
+ case ListLayout::Role::Bool:
+ {
+ bool *value = reinterpret_cast<bool *>(mem);
+ data = *value;
+ }
+ break;
+ case ListLayout::Role::List:
+ {
+ ListModel **value = reinterpret_cast<ListModel **>(mem);
+ ListModel *model = *value;
+
+ if (model) {
+ if (model->m_modelCache == 0) {
+ model->m_modelCache = new QDeclarativeListModel(owner, model, eng);
+ QDeclarativeEngine::setContextForObject(model->m_modelCache, QDeclarativeEngine::contextForObject(owner));
+ }
+
+ QObject *object = model->m_modelCache;
+ data = QVariant::fromValue(object);
+ }
+ }
+ break;
+ case ListLayout::Role::QObject:
+ {
+ QDeclarativeGuard<QObject> *guard = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ QObject *object = guard->data();
+ if (object)
+ data = QVariant::fromValue(object);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return data;
+}
+
+int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::String) {
+ char *mem = getPropertyMemory(role);
+ QString *c = reinterpret_cast<QString *>(mem);
+ bool changed;
+ if (c->data_ptr() == 0) {
+ new (mem) QString(s);
+ changed = true;
} else {
- // This is the list in the main thread, so emit itemsChanged()
- emit r->model->m_listModel->itemsChanged(index, 1, roles);
+ changed = c->compare(s) != 0;
+ *c = s;
+ }
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign string to role '" << role.name << "' of different type.";
+ }
+
+ return roleIndex;
+}
+
+int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::Number) {
+ char *mem = getPropertyMemory(role);
+ double *value = new (mem) double;
+ bool changed = *value != d;
+ *value = d;
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign number to role '" << role.name << "' of different type.";
+ }
+
+ return roleIndex;
+}
+
+int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::Bool) {
+ char *mem = getPropertyMemory(role);
+ bool *value = new (mem) bool;
+ bool changed = *value != b;
+ *value = b;
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign bool to role '" << role.name << "' of different type.";
+ }
+
+ return roleIndex;
+}
+
+int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::List) {
+ char *mem = getPropertyMemory(role);
+ ListModel **value = new (mem) ListModel *;
+ if (*value) {
+ (*value)->destroy();
+ delete *value;
}
+ *value = m;
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign list to role '" << role.name << "' of different type.";
}
- return value;
+ return roleIndex;
}
-template<typename T>
-void qdeclarativelistmodel_move(int from, int to, int n, T *items)
+int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
{
- if (n == 1) {
- items->move(from, to);
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::QObject) {
+ char *mem = getPropertyMemory(role);
+ QDeclarativeGuard<QObject> *g = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ bool changed = g->data() != o;
+ g->~QDeclarativeGuard();
+ new (mem) QDeclarativeGuard<QObject>(o);
+ if (changed)
+ roleIndex = role.index;
} else {
- T replaced;
- int i=0;
- typename T::ConstIterator it=items->begin(); it += from+n;
- for (; i<to-from; ++i,++it)
- replaced.append(*it);
- i=0;
- it=items->begin(); it += from;
- for (; i<n; ++i,++it)
- replaced.append(*it);
- typename T::ConstIterator f=replaced.begin();
- typename T::Iterator t=items->begin(); t += from;
- for (; f != replaced.end(); ++f, ++t)
- *t = *f;
+ qmlInfo(0) << "Unable to assign string to role '" << role.name << "' of different type.";
+ }
+
+ return roleIndex;
+}
+
+void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
+{
+ char *mem = getPropertyMemory(role);
+ new (mem) QString(s);
+}
+
+void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
+{
+ char *mem = getPropertyMemory(role);
+ double *value = new (mem) double;
+ *value = d;
+}
+
+void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
+{
+ char *mem = getPropertyMemory(role);
+ bool *value = new (mem) bool;
+ *value = b;
+}
+
+void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
+{
+ char *mem = getPropertyMemory(role);
+ new (mem) QDeclarativeGuard<QObject>(o);
+}
+
+void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
+{
+ char *mem = getPropertyMemory(role);
+ ListModel **value = new (mem) ListModel *;
+ *value = m;
+}
+
+void ListElement::clearProperty(const ListLayout::Role &role)
+{
+ switch (role.type) {
+ case ListLayout::Role::String:
+ setStringProperty(role, QString());
+ break;
+ case ListLayout::Role::Number:
+ setDoubleProperty(role, 0.0);
+ break;
+ case ListLayout::Role::Bool:
+ setBoolProperty(role, false);
+ break;
+ case ListLayout::Role::List:
+ setListProperty(role, 0);
+ break;
+ case ListLayout::Role::QObject:
+ setQObjectProperty(role, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+ListElement::ListElement()
+{
+ m_objectCache = 0;
+ uid = ListModel::allocateUid();
+ next = 0;
+ qMemSet(data, 0, sizeof(data));
+}
+
+ListElement::ListElement(int existingUid)
+{
+ m_objectCache = 0;
+ uid = existingUid;
+ next = 0;
+ qMemSet(data, 0, sizeof(data));
+}
+
+ListElement::~ListElement()
+{
+ delete next;
+}
+
+void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
+{
+ for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
+ const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
+ const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
+
+ switch (srcRole.type) {
+ case ListLayout::Role::List:
+ {
+ ListModel *srcSubModel = src->getListProperty(srcRole);
+ ListModel *targetSubModel = target->getListProperty(targetRole);
+
+ if (srcSubModel) {
+ if (targetSubModel == 0) {
+ targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
+ target->setListPropertyFast(targetRole, targetSubModel);
+ }
+ ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
+ }
+ }
+ break;
+ case ListLayout::Role::QObject:
+ {
+ QObject *object = src->getQObjectProperty(srcRole);
+ target->setQObjectProperty(targetRole, object);
+ }
+ break;
+ case ListLayout::Role::String:
+ case ListLayout::Role::Number:
+ case ListLayout::Role::Bool:
+ {
+ QVariant v = src->getProperty(srcRole, 0, 0);
+ target->setVariantProperty(targetRole, v);
+ }
+ default:
+ break;
+ }
+ }
+
+}
+
+void ListElement::destroy(ListLayout *layout)
+{
+ if (layout) {
+ for (int i=0 ; i < layout->roleCount() ; ++i) {
+ const ListLayout::Role &r = layout->getExistingRole(i);
+
+ switch (r.type) {
+ case ListLayout::Role::String:
+ {
+ QString *string = getStringProperty(r);
+ if (string)
+ string->~QString();
+ }
+ break;
+ case ListLayout::Role::List:
+ {
+ ListModel *model = getListProperty(r);
+ if (model) {
+ model->destroy();
+ delete model;
+ }
+ }
+ break;
+ case ListLayout::Role::QObject:
+ {
+ QDeclarativeGuard<QObject> *guard = getGuardProperty(r);
+ guard->~QDeclarativeGuard();
+ }
+ break;
+ default:
+ // other types don't need explicit cleanup.
+ break;
+ }
+ }
+
+ delete m_objectCache;
+ }
+
+ if (next)
+ next->destroy(0);
+ uid = -1;
+}
+
+int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
+{
+ int roleIndex = -1;
+
+ switch (role.type) {
+ case ListLayout::Role::Number:
+ roleIndex = setDoubleProperty(role, d.toDouble());
+ break;
+ case ListLayout::Role::String:
+ roleIndex = setStringProperty(role, d.toString());
+ break;
+ case ListLayout::Role::Bool:
+ roleIndex = setBoolProperty(role, d.toBool());
+ break;
+ case ListLayout::Role::List:
+ roleIndex = setListProperty(role, d.value<ListModel *>());
+ break;
+ default:
+ break;
+ }
+
+ return roleIndex;
+}
+
+int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d)
+{
+ // Check if this key exists yet
+ int roleIndex = -1;
+
+ // Add the value now
+ if (d->IsString()) {
+ v8::Handle<v8::String> jsString = d->ToString();
+ QString qstr;
+ qstr.resize(jsString->Length());
+ jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+ roleIndex = setStringProperty(role, qstr);
+ } else if (d->IsNumber()) {
+ roleIndex = setDoubleProperty(role, d->NumberValue());
+ } else if (d->IsArray()) {
+ ListModel *subModel = new ListModel(role.subLayout, 0, -1);
+ v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
+ int arrayLength = subArray->Length();
+ for (int j=0 ; j < arrayLength ; ++j) {
+ v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+ subModel->append(subObject);
+ }
+ roleIndex = setListProperty(role, subModel);
+ } else if (d->IsInt32()) {
+ roleIndex = setDoubleProperty(role, (double) d->Int32Value());
+ } else if (d->IsBoolean()) {
+ roleIndex = setBoolProperty(role, d->BooleanValue());
+ } else if (d->IsObject()) {
+ QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
+ if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
+ QObject *o = QV8QObjectWrapper::toQObject(r);
+ roleIndex = setQObjectProperty(role, o);
+ }
+ } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
+ clearProperty(role);
+ }
+
+ return roleIndex;
+}
+
+ModelObject::ModelObject(QDeclarativeListModel *model, int elementIndex)
+: m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
+{
+ updateValues();
+ setNodeUpdatesEnabled(true);
+}
+
+void ModelObject::updateValues()
+{
+ int roleCount = m_model->m_listModel->roleCount();
+ for (int i=0 ; i < roleCount ; ++i) {
+ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
+ QByteArray name = role.name.toUtf8();
+ const QVariant &data = m_model->data(m_elementIndex, i);
+ setValue(name, data, role.type == ListLayout::Role::List);
+ }
+}
+
+void ModelObject::updateValues(const QList<int> &roles)
+{
+ int roleCount = roles.count();
+ for (int i=0 ; i < roleCount ; ++i) {
+ int roleIndex = roles.at(i);
+ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
+ QByteArray name = role.name.toUtf8();
+ const QVariant &data = m_model->data(m_elementIndex, roleIndex);
+ setValue(name, data, role.type == ListLayout::Role::List);
+ }
+}
+
+ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
+: QDeclarativeOpenMetaObject(object), m_enabled(false), m_obj(object)
+{
+}
+
+ModelNodeMetaObject::~ModelNodeMetaObject()
+{
+}
+
+void ModelNodeMetaObject::propertyWritten(int index)
+{
+ if (!m_enabled)
+ return;
+
+ QV8Engine *eng = m_obj->m_model->engine();
+
+ QString propName = QString::fromUtf8(name(index));
+ QVariant value = operator[](index);
+
+ v8::HandleScope handle_scope;
+ v8::Context::Scope scope(eng->context());
+
+ v8::Handle<v8::Value> v = eng->fromVariant(value);
+
+ int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v);
+ if (roleIndex != -1) {
+ QList<int> roles;
+ roles << roleIndex;
+ m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
}
}
@@ -293,114 +1174,112 @@ QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListM
handler. You must call sync() or else the changes made to the list from the external
thread will not be reflected in the list model in the main thread.
- \section1 Restrictions
-
- If a list model is to be accessed from a WorkerScript, it cannot
- contain list-type data. So, the following model cannot be used from a WorkerScript
- because of the list contained in the "attributes" property:
-
- \code
- ListModel {
- id: fruitModel
- ListElement {
- name: "Apple"
- cost: 2.45
- attributes: [
- ListElement { description: "Core" },
- ListElement { description: "Deciduous" }
- ]
- }
- }
- \endcode
-
- In addition, the WorkerScript cannot add list-type data to the model.
-
\sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
*/
+QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
+: QListModelInterface(parent)
+{
+ m_mainThread = true;
+ m_primary = true;
+ m_agent = 0;
-/*
- A ListModel internally uses either a NestedListModel or FlatListModel.
-
- A NestedListModel can contain lists of ListElements (which
- when retrieved from get() is accessible as a list model within the list
- model) whereas a FlatListModel cannot.
+ m_layout = new ListLayout;
+ m_listModel = new ListModel(m_layout, this, -1);
- ListModel uses a NestedListModel to begin with, and if the model is later
- used from a WorkerScript, it changes to use a FlatListModel instead. This
- is because ModelNode (which abstracts the nested list model data) needs
- access to the declarative engine and script engine, which cannot be
- safely used from outside of the main thread.
-*/
+ m_engine = 0;
+}
-QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
-: QListModelInterface(parent), m_agent(0), m_nested(new NestedListModel(this)), m_flat(0)
+QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
+: QListModelInterface(parent)
{
+ m_mainThread = owner->m_mainThread;
+ m_primary = false;
+ m_agent = owner->m_agent;
+
+ m_layout = 0;
+ m_listModel = data;
+
+ m_engine = eng;
}
-QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent)
-: QListModelInterface(parent), m_agent(0), m_nested(0), m_flat(0)
+QDeclarativeListModel::QDeclarativeListModel(QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *agent)
+: QListModelInterface(agent)
{
- m_flat = new FlatListModel(this);
- m_flat->m_parentAgent = parent;
+ m_mainThread = false;
+ m_primary = true;
+ m_agent = agent;
- if (orig->m_flat) {
- m_flat->m_roles = orig->m_flat->m_roles;
- m_flat->m_strings = orig->m_flat->m_strings;
- m_flat->m_values = orig->m_flat->m_values;
+ m_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);
- m_flat->m_nodeData.reserve(m_flat->m_values.count());
- for (int i=0; i<m_flat->m_values.count(); i++)
- m_flat->m_nodeData << 0;
- }
+ m_engine = 0;
}
QDeclarativeListModel::~QDeclarativeListModel()
{
- if (m_agent)
- m_agent->release();
+ if (m_primary) {
+ m_listModel->destroy();
+ delete m_listModel;
- delete m_nested;
- delete m_flat;
-}
+ if (m_mainThread && m_agent)
+ m_agent->release();
+ }
-bool QDeclarativeListModel::flatten()
-{
- if (m_flat)
- return true;
+ m_listModel = 0;
- QList<int> roles = m_nested->roles();
+ delete m_layout;
+ m_layout = 0;
+}
- QList<QHash<int, QVariant> > values;
- bool hasNested = false;
- for (int i=0; i<m_nested->count(); i++) {
- values.append(m_nested->data(i, roles, &hasNested));
- if (hasNested)
- return false;
+QV8Engine *QDeclarativeListModel::engine() const
+{
+ if (m_engine == 0) {
+ m_engine = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this));
}
- FlatListModel *flat = new FlatListModel(this);
- flat->m_values = values;
+ return m_engine;
+}
- for (int i=0; i<roles.count(); i++) {
- QString s = m_nested->toString(roles[i]);
- flat->m_roles.insert(roles[i], s);
- flat->m_strings.insert(s, roles[i]);
+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);
}
+}
- flat->m_nodeData.reserve(flat->m_values.count());
- for (int i=0; i<flat->m_values.count(); i++)
- flat->m_nodeData << 0;
+void QDeclarativeListModel::emitItemsRemoved(int index, int count)
+{
+ if (m_mainThread) {
+ emit itemsRemoved(index, count);
+ emit countChanged();
+ } else {
+ if (index == 0 && count == this->count())
+ m_agent->data.clearChange(this);
+ m_agent->data.removeChange(this, index, count);
+ }
+}
- m_flat = flat;
- delete m_nested;
- m_nested = 0;
- return true;
+void QDeclarativeListModel::emitItemsInserted(int index, int count)
+{
+ if (m_mainThread) {
+ emit itemsInserted(index, count);
+ emit countChanged();
+ } else {
+ m_agent->data.insertChange(this, index, count);
+ }
}
-bool QDeclarativeListModel::inWorkerThread() const
+void QDeclarativeListModel::emitItemsMoved(int from, int to, int n)
{
- return m_flat && m_flat->m_parentAgent;
+ if (m_mainThread) {
+ emit itemsMoved(from, to, n);
+ } else {
+ m_agent->data.moveChange(this, from, n, to);
+ }
}
QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
@@ -408,23 +1287,24 @@ QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
if (m_agent)
return m_agent;
- if (!flatten()) {
- qmlInfo(this) << "List contains list-type data and cannot be used from a worker script";
- return 0;
- }
-
m_agent = new QDeclarativeListModelWorkerAgent(this);
return m_agent;
}
QList<int> QDeclarativeListModel::roles() const
{
- return m_flat ? m_flat->roles() : m_nested->roles();
+ QList<int> rolesArray;
+
+ for (int i=0 ; i < m_listModel->roleCount() ; ++i)
+ rolesArray << i;
+
+ return rolesArray;
}
QString QDeclarativeListModel::toString(int role) const
{
- return m_flat ? m_flat->toString(role) : m_nested->toString(role);
+ const ListLayout::Role &r = m_listModel->getExistingRole(role);
+ return r.name;
}
QVariant QDeclarativeListModel::data(int index, int role) const
@@ -432,7 +1312,7 @@ QVariant QDeclarativeListModel::data(int index, int role) const
if (index >= count() || index < 0)
return QVariant();
- return m_flat ? m_flat->data(index, role) : m_nested->data(index, role);
+ return m_listModel->getProperty(index, role, this, engine());
}
/*!
@@ -441,7 +1321,7 @@ QVariant QDeclarativeListModel::data(int index, int role) const
*/
int QDeclarativeListModel::count() const
{
- return m_flat ? m_flat->count() : m_nested->count();
+ return m_listModel->elementCount();
}
/*!
@@ -454,47 +1334,9 @@ int QDeclarativeListModel::count() const
void QDeclarativeListModel::clear()
{
int cleared = count();
- if (m_flat)
- m_flat->clear();
- else
- m_nested->clear();
- if (!inWorkerThread()) {
- emit itemsRemoved(0, cleared);
- emit countChanged();
- }
-}
-
-QDeclarativeListModel *ModelNode::model(const NestedListModel *model)
-{
- if (!modelCache) {
- modelCache = new QDeclarativeListModel;
- QDeclarativeEngine::setContextForObject(modelCache,QDeclarativeEngine::contextForObject(model->m_listModel));
- modelCache->m_nested->_root = this; // ListModel defaults to nestable model
-
- for (int i=0; i<values.count(); ++i) {
- ModelNode *subNode = qvariant_cast<ModelNode *>(values.at(i));
- if (subNode)
- subNode->m_model = modelCache->m_nested;
- }
- }
- return modelCache;
-}
-
-ModelObject *ModelNode::object(const NestedListModel *model)
-{
- if (!objectCache) {
- QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(qmlEngine(model->m_listModel));
- objectCache = new ModelObject(this, const_cast<NestedListModel*>(model), ep->v8engine());
-
- QHash<QString, ModelNode *>::iterator it;
- for (it = properties.begin(); it != properties.end(); ++it) {
- objectCache->setValue(it.key().toUtf8(), model->valueForNode(*it));
- }
-
- objectCache->setNodeUpdatesEnabled(true);
- }
- return objectCache;
+ m_listModel->clear();
+ emitItemsRemoved(0, cleared);
}
/*!
@@ -511,15 +1353,9 @@ void QDeclarativeListModel::remove(int index)
return;
}
- if (m_flat)
- m_flat->remove(index);
- else
- m_nested->remove(index);
+ m_listModel->remove(index);
- if (!inWorkerThread()) {
- emit itemsRemoved(index, 1);
- emit countChanged();
- }
+ emitItemsRemoved(index, 1);
}
/*!
@@ -537,25 +1373,39 @@ void QDeclarativeListModel::remove(int index)
\sa set() append()
*/
-void QDeclarativeListModel::insert(int index, const QDeclarativeV8Handle &handle)
+
+void QDeclarativeListModel::insert(QDeclarativeV8Function *args)
{
- v8::Handle<v8::Value> valuemap = handle.toHandle();
+ if (args->Length() == 2) {
- if (!valuemap->IsObject() || valuemap->IsArray()) {
- qmlInfo(this) << tr("insert: value is not an object");
- return;
- }
+ v8::Handle<v8::Value> arg0 = (*args)[0];
+ int index = arg0->Int32Value();
- if (index < 0 || index > count()) {
- qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
- return;
- }
+ if (index < 0 || index > count()) {
+ qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
+ return;
+ }
- bool ok = m_flat ? m_flat->insert(index, valuemap) : m_nested->insert(index, valuemap);
+ v8::Handle<v8::Value> arg1 = (*args)[1];
- if (ok && !inWorkerThread()) {
- emit itemsInserted(index, 1);
- emit countChanged();
+ if (arg1->IsArray()) {
+ v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
+ int objectArrayLength = objectArray->Length();
+ for (int i=0 ; i < objectArrayLength ; ++i) {
+ v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
+ m_listModel->insert(index+i, argObject);
+ }
+ emitItemsInserted(index, objectArrayLength);
+ } else if (arg1->IsObject()) {
+ v8::Handle<v8::Object> argObject = arg1->ToObject();
+
+ m_listModel->insert(index, argObject);
+ emitItemsInserted(index, 1);
+ } else {
+ qmlInfo(this) << tr("insert: value is not an object");
+ }
+ } else {
+ qmlInfo(this) << tr("insert: value is not an object");
}
}
@@ -582,25 +1432,8 @@ void QDeclarativeListModel::move(int from, int to, int n)
return;
}
- int origfrom = from;
- int origto = to;
- int orign = n;
- if (from > to) {
- // Only move forwards - flip if backwards moving
- int tfrom = from;
- int tto = to;
- from = tto;
- to = tto+n;
- n = tfrom-tto;
- }
-
- if (m_flat)
- m_flat->move(from, to, n);
- else
- m_nested->move(from, to, n);
-
- if (!inWorkerThread())
- emit itemsMoved(origfrom, origto, orign);
+ m_listModel->move(from, to, n);
+ emitItemsMoved(from, to, n);
}
/*!
@@ -615,16 +1448,32 @@ void QDeclarativeListModel::move(int from, int to, int n)
\sa set() remove()
*/
-void QDeclarativeListModel::append(const QDeclarativeV8Handle &handle)
-{
- v8::Handle<v8::Value> valuemap = handle.toHandle();
+void QDeclarativeListModel::append(QDeclarativeV8Function *args)
+{
+ if (args->Length() == 1) {
+ v8::Handle<v8::Value> arg = (*args)[0];
+
+ if (arg->IsArray()) {
+ v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
+ int objectArrayLength = objectArray->Length();
+ int index = m_listModel->elementCount();
+ for (int i=0 ; i < objectArrayLength ; ++i) {
+ v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
+ m_listModel->append(argObject);
+ }
+ emitItemsInserted(index, objectArrayLength);
+ } else if (arg->IsObject()) {
+ v8::Handle<v8::Object> argObject = arg->ToObject();
- if (!valuemap->IsObject() || valuemap->IsArray()) {
+ int index = m_listModel->append(argObject);
+ emitItemsInserted(index, 1);
+
+ } else {
+ qmlInfo(this) << tr("append: value is not an object");
+ }
+ } else {
qmlInfo(this) << tr("append: value is not an object");
- return;
}
-
- insert(count(), handle);
}
/*!
@@ -660,8 +1509,16 @@ void QDeclarativeListModel::append(const QDeclarativeV8Handle &handle)
*/
QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
{
- // the internal flat/nested class checks for bad index
- return QDeclarativeV8Handle::fromHandle(m_flat ? m_flat->get(index) : m_nested->get(index));
+ v8::Handle<v8::Value> result = v8::Undefined();
+
+ if (index >= 0 && index < m_listModel->elementCount()) {
+ QV8Engine *v8engine = engine();
+
+ ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QDeclarativeListModel *>(this), index);
+ result = v8engine->newQObject(object);
+ }
+
+ return QDeclarativeV8Handle::fromHandle(result);
}
/*!
@@ -680,15 +1537,7 @@ QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
\sa append()
*/
-void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &valuemap)
-{
- QList<int> roles;
- set(index, valuemap, &roles);
- if (!roles.isEmpty() && !inWorkerThread())
- emit itemsChanged(index, 1, roles);
-}
-
-void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle, QList<int> *roles)
+void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle)
{
v8::Handle<v8::Value> valuemap = handle.toHandle();
@@ -701,13 +1550,18 @@ void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle, Q
return;
}
+ v8::Handle<v8::Object> object = valuemap->ToObject();
+
if (index == count()) {
- append(handle);
+ m_listModel->insert(index, object);
+ emitItemsInserted(index, 1);
} else {
- if (m_flat)
- m_flat->set(index, valuemap, roles);
- else
- m_nested->set(index, valuemap, roles);
+
+ QList<int> roles;
+ m_listModel->set(index, object, &roles);
+
+ if (roles.count())
+ emitItemsChanged(index, 1, roles);
}
}
@@ -726,23 +1580,19 @@ void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle, Q
*/
void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value)
{
- QList<int> roles;
- setProperty(index, property, value, &roles);
- if (!roles.isEmpty() && !inWorkerThread())
- emit itemsChanged(index, 1, roles);
-}
-
-void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
if (count() == 0 || index >= count() || index < 0) {
qmlInfo(this) << tr("set: index %1 out of range").arg(index);
return;
}
- if (m_flat)
- m_flat->setProperty(index, property, value, roles);
- else
- m_nested->setProperty(index, property, value, roles);
+ int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
+ if (roleIndex != -1) {
+
+ QList<int> roles;
+ roles << roleIndex;
+
+ emitItemsChanged(index, 1, roles);
+ }
}
/*!
@@ -931,78 +1781,93 @@ void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &
{
QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
- ModelNode *root = new ModelNode(rv->m_nested);
- rv->m_nested->m_ownsRoot = true;
- rv->m_nested->_root = root;
- QStack<ModelNode *> nodes;
- nodes << root;
-
- bool processingSet = false;
+ QV8Engine *engine = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(rv));
+ rv->m_engine = engine;
const ListModelData *lmd = (const ListModelData *)d.constData();
const char *data = ((const char *)lmd) + lmd->dataOffset;
+ QStack<DataStackElement> stack;
+
for (int ii = 0; ii < lmd->instrCount; ++ii) {
const ListInstruction &instr = lmd->instructions()[ii];
switch(instr.type) {
case ListInstruction::Push:
{
- ModelNode *n = nodes.top();
- ModelNode *n2 = new ModelNode(rv->m_nested);
- n->values << QVariant::fromValue(n2);
- nodes.push(n2);
- if (processingSet)
- n->isArray = true;
+ ListModel *subModel = 0;
+
+ if (stack.count() == 0) {
+ subModel = rv->m_listModel;
+ } else {
+ const DataStackElement &e0 = stack.at(stack.size() - 1);
+ DataStackElement &e1 = stack[stack.size() - 2];
+
+ const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
+ if (role.type == ListLayout::Role::List) {
+ subModel = e1.model->getListProperty(e1.elementIndex, role);
+
+ if (subModel == 0) {
+ subModel = new ListModel(role.subLayout, 0, -1);
+ QVariant vModel = QVariant::fromValue(subModel);
+ e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
+ }
+ }
+ }
+
+ DataStackElement e;
+ e.model = subModel;
+ e.elementIndex = subModel ? subModel->appendElement() : -1;
+ stack.push(e);
}
break;
case ListInstruction::Pop:
- nodes.pop();
+ stack.pop();
break;
case ListInstruction::Value:
{
- ModelNode *n = nodes.top();
+ const DataStackElement &e0 = stack.at(stack.size() - 1);
+ DataStackElement &e1 = stack[stack.size() - 2];
+
+ QString name = e0.name;
+ QVariant value;
+
switch (QDeclarativeScript::Variant::Type(data[instr.dataIdx])) {
- case QDeclarativeScript::Variant::Invalid:
- n->isArray = true;
- break;
- case QDeclarativeScript::Variant::Boolean:
- n->values.append(bool(data[1 + instr.dataIdx]));
- break;
- case QDeclarativeScript::Variant::Number:
- n->values.append(QByteArray(data + 1 + instr.dataIdx).toDouble());
- break;
- case QDeclarativeScript::Variant::String:
- n->values.append(QString::fromUtf8(data + 1 + instr.dataIdx));
- break;
- default:
- Q_ASSERT("Format error in ListInstruction");
+ case QDeclarativeScript::Variant::Invalid:
+ {
+ const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
+ ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
+ value = QVariant::fromValue(emptyModel);
+ }
+ break;
+ case QDeclarativeScript::Variant::Boolean:
+ value = bool(data[1 + instr.dataIdx]);
+ break;
+ case QDeclarativeScript::Variant::Number:
+ value = QByteArray(data + 1 + instr.dataIdx).toDouble();
+ break;
+ case QDeclarativeScript::Variant::String:
+ value = QString::fromUtf8(data + 1 + instr.dataIdx);
+ break;
+ default:
+ Q_ASSERT("Format error in ListInstruction");
}
- processingSet = false;
+ e1.model->setOrCreateProperty(e1.elementIndex, name, value);
}
break;
case ListInstruction::Set:
{
- ModelNode *n = nodes.top();
- ModelNode *n2 = new ModelNode(rv->m_nested);
- n->properties.insert(QString::fromUtf8(data + instr.dataIdx), n2);
- nodes.push(n2);
- processingSet = true;
+ DataStackElement e;
+ e.name = QString::fromUtf8(data + instr.dataIdx);
+ stack.push(e);
}
break;
}
}
-
- ModelNode *rootNode = rv->m_nested->_root;
- for (int i=0; i<rootNode->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(rootNode->values[i]);
- node->listIndex = i;
- node->updateListIndexes();
- }
}
bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
@@ -1060,691 +1925,4 @@ bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
\sa ListModel
*/
-FlatListModel::FlatListModel(QDeclarativeListModel *base)
-: m_engine(0), m_listModel(base), m_parentAgent(0)
-{
-}
-
-FlatListModel::~FlatListModel()
-{
- qDeleteAll(m_nodeData);
-}
-
-QVariant FlatListModel::data(int index, int role) const
-{
- Q_ASSERT(index >= 0 && index < m_values.count());
- if (m_values[index].contains(role))
- return m_values[index][role];
- return QVariant();
-}
-
-QList<int> FlatListModel::roles() const
-{
- return m_roles.keys();
-}
-
-QString FlatListModel::toString(int role) const
-{
- if (m_roles.contains(role))
- return m_roles[role];
- return QString();
-}
-
-int FlatListModel::count() const
-{
- return m_values.count();
-}
-
-void FlatListModel::clear()
-{
- m_values.clear();
-
- qDeleteAll(m_nodeData);
- m_nodeData.clear();
-}
-
-void FlatListModel::remove(int index)
-{
- m_values.removeAt(index);
- removedNode(index);
-}
-
-bool FlatListModel::insert(int index, v8::Handle<v8::Value> value)
-{
- Q_ASSERT(index >= 0 && index <= m_values.count());
-
- QHash<int, QVariant> row;
- if (!addValue(value, &row, 0))
- return false;
-
- m_values.insert(index, row);
- insertedNode(index);
-
- return true;
-}
-
-QV8Engine *FlatListModel::engine() const
-{
- return m_engine?m_engine:QDeclarativeEnginePrivate::getV8Engine(qmlEngine(m_listModel));
-}
-
-QV8Engine *NestedListModel::engine() const
-{
- return QDeclarativeEnginePrivate::getV8Engine(qmlEngine(m_listModel));
-}
-
-v8::Handle<v8::Value> FlatListModel::get(int index) const
-{
- QV8Engine *v8engine = engine();
-
- if (!v8engine)
- return v8::Undefined();
-
- if (index < 0 || index >= m_values.count())
- return v8::Undefined();
-
- FlatListModel *that = const_cast<FlatListModel*>(this);
-
- FlatNodeData *data = m_nodeData.value(index);
- if (!data) {
- data = new FlatNodeData(index);
- that->m_nodeData.replace(index, data);
- }
-
- v8::Local<v8::Object> rv = QDeclarativeListModelV8Data::create(v8engine);
- QV8ListModelResource *r = new QV8ListModelResource(that, data, v8engine);
- rv->SetExternalResource(r);
- return rv;
-}
-
-void FlatListModel::set(int index, v8::Handle<v8::Value> value, QList<int> *roles)
-{
- Q_ASSERT(index >= 0 && index < m_values.count());
-
- QHash<int, QVariant> row = m_values[index];
- if (addValue(value, &row, roles))
- m_values[index] = row;
-}
-
-void FlatListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
- Q_ASSERT(index >= 0 && index < m_values.count());
-
- QHash<QString, int>::Iterator iter = m_strings.find(property);
- int role;
- if (iter == m_strings.end()) {
- role = m_roles.count();
- m_roles.insert(role, property);
- m_strings.insert(property, role);
- } else {
- role = iter.value();
- }
-
- if (m_values[index][role] != value) {
- roles->append(role);
- m_values[index][role] = value;
- }
-}
-
-void FlatListModel::move(int from, int to, int n)
-{
- qdeclarativelistmodel_move<QList<QHash<int, QVariant> > >(from, to, n, &m_values);
- moveNodes(from, to, n);
-}
-
-bool FlatListModel::addValue(v8::Handle<v8::Value> value, QHash<int, QVariant> *row, QList<int> *roles)
-{
- if (!value->IsObject())
- return false;
-
- v8::Local<v8::Array> properties = engine()->getOwnPropertyNames(value->ToObject());
- uint32_t length = properties->Length();
- for (uint32_t ii = 0; ii < length; ++ii) {
- // XXX TryCatch?
- v8::Handle<v8::Value> property = properties->Get(ii);
- v8::Handle<v8::Value> jsv = value->ToObject()->Get(property);
-
- if (!jsv->IsRegExp() && !jsv->IsDate() && jsv->IsObject() && !engine()->isVariant(jsv)) {
- qmlInfo(m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
- return false;
- }
-
- QString name = engine()->toString(property);
- QVariant v = engine()->toVariant(jsv, -1);
-
- QHash<QString, int>::Iterator iter = m_strings.find(name);
- if (iter == m_strings.end()) {
- int role = m_roles.count();
- m_roles.insert(role, name);
- iter = m_strings.insert(name, role);
- if (roles)
- roles->append(role);
- } else {
- int role = iter.value();
- if (roles && row->contains(role) && row->value(role) != v)
- roles->append(role);
- }
- row->insert(*iter, v);
- }
- return true;
-}
-
-void FlatListModel::insertedNode(int index)
-{
- if (index >= 0 && index <= m_values.count()) {
- m_nodeData.insert(index, 0);
-
- for (int i=index + 1; i<m_nodeData.count(); i++) {
- if (m_nodeData[i])
- m_nodeData[i]->index = i;
- }
- }
-}
-
-void FlatListModel::removedNode(int index)
-{
- if (index >= 0 && index < m_nodeData.count()) {
- delete m_nodeData.takeAt(index);
-
- for (int i=index; i<m_nodeData.count(); i++) {
- if (m_nodeData[i])
- m_nodeData[i]->index = i;
- }
- }
-}
-
-void FlatListModel::moveNodes(int from, int to, int n)
-{
- if (!m_listModel->canMove(from, to, n))
- return;
-
- qdeclarativelistmodel_move<QList<FlatNodeData *> >(from, to, n, &m_nodeData);
-
- for (int i=from; i<from + (to-from); i++) {
- if (m_nodeData[i])
- m_nodeData[i]->index = i;
- }
-}
-
-FlatNodeData::~FlatNodeData()
-{
- for (QSet<QV8ListModelResource *>::Iterator iter = objects.begin(); iter != objects.end(); ++iter) {
- QV8ListModelResource *data = *iter;
- data->nodeData = 0;
- }
-}
-
-void FlatNodeData::addData(QV8ListModelResource *data)
-{
- objects.insert(data);
-}
-
-void FlatNodeData::removeData(QV8ListModelResource *data)
-{
- objects.remove(data);
-}
-
-NestedListModel::NestedListModel(QDeclarativeListModel *base)
-: _root(0), m_ownsRoot(false), m_listModel(base), _rolesOk(false)
-{
-}
-
-NestedListModel::~NestedListModel()
-{
- if (m_ownsRoot)
- delete _root;
-}
-
-QVariant NestedListModel::valueForNode(ModelNode *node, bool *hasNested) const
-{
- QObject *rv = 0;
- if (hasNested)
- *hasNested = false;
-
- if (node->isArray) {
- // List
- rv = node->model(this);
- if (hasNested)
- *hasNested = true;
- } else {
- if (!node->properties.isEmpty()) {
- // Object
- rv = node->object(this);
- } else if (node->values.count() == 0) {
- // Invalid
- return QVariant();
- } else if (node->values.count() == 1) {
- // Value
- QVariant &var = node->values[0];
- ModelNode *valueNode = qvariant_cast<ModelNode *>(var);
- if (valueNode) {
- if (!valueNode->properties.isEmpty())
- rv = valueNode->object(this);
- else
- rv = valueNode->model(this);
- } else {
- return var;
- }
- }
- }
-
- if (rv) {
- return QVariant::fromValue(rv);
- } else {
- return QVariant();
- }
-}
-
-QHash<int,QVariant> NestedListModel::data(int index, const QList<int> &roles, bool *hasNested) const
-{
- Q_ASSERT(_root && index >= 0 && index < _root->values.count());
- checkRoles();
- QHash<int, QVariant> rv;
-
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- if (!node)
- return rv;
-
- for (int ii = 0; ii < roles.count(); ++ii) {
- const QString &roleString = roleStrings.at(roles.at(ii));
-
- QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
- if (iter != node->properties.end()) {
- ModelNode *row = *iter;
- rv.insert(roles.at(ii), valueForNode(row, hasNested));
- }
- }
-
- return rv;
-}
-
-QVariant NestedListModel::data(int index, int role) const
-{
- Q_ASSERT(_root && index >= 0 && index < _root->values.count());
- checkRoles();
- QVariant rv;
- if (roleStrings.count() < role)
- return rv;
-
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- if (!node)
- return rv;
-
- const QString &roleString = roleStrings.at(role);
-
- QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
- if (iter != node->properties.end()) {
- ModelNode *row = *iter;
- rv = valueForNode(row);
- }
-
- return rv;
-}
-
-int NestedListModel::count() const
-{
- if (!_root) return 0;
- return _root->values.count();
-}
-
-void NestedListModel::clear()
-{
- if (_root)
- _root->clear();
-}
-
-void NestedListModel::remove(int index)
-{
- if (!_root)
- return;
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- _root->values.removeAt(index);
- for (int i = 0; i < _root->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node)
- node->listIndex = i;
- }
- if (node)
- delete node;
-}
-
-bool NestedListModel::insert(int index, v8::Handle<v8::Value> valuemap)
-{
- if (!_root) {
- _root = new ModelNode(this);
- m_ownsRoot = true;
- }
-
- ModelNode *mn = new ModelNode(this);
- mn->listIndex = index;
- mn->setObjectValue(valuemap);
- _root->values.insert(index,QVariant::fromValue(mn));
- for (int i = index + 1; i < _root->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node)
- node->listIndex = i;
- }
- return true;
-}
-
-void NestedListModel::move(int from, int to, int n)
-{
- if (!_root)
- return;
- qdeclarativelistmodel_move<QVariantList>(from, to, n, &_root->values);
- for (int i = qMin(from, to), end = qMin(_root->values.count(), qMax(from, to) + n); i < end; ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node)
- node->listIndex = i;
- }
-}
-
-v8::Handle<v8::Value> NestedListModel::get(int index) const
-{
- QDeclarativeEngine *eng = qmlEngine(m_listModel);
- if (!eng)
- return v8::Undefined();;
-
- if (index < 0 || index >= count())
- return v8::Undefined();
-
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- if (!node)
- return v8::Undefined();;
-
- return QDeclarativeEnginePrivate::get(eng)->v8engine()->newQObject(node->object(this));
-}
-
-void NestedListModel::set(int index, v8::Handle<v8::Value> valuemap, QList<int> *roles)
-{
- Q_ASSERT(index >=0 && index < count());
-
- if (!valuemap->IsObject())
- return;
-
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- bool emitItemsChanged = node->setObjectValue(valuemap);
- if (!emitItemsChanged)
- return;
-
- QV8Engine *v8engine = engine();
-
- v8::Local<v8::Array> properties = v8engine->getOwnPropertyNames(valuemap->ToObject());
- uint32_t length = properties->Length();
- for (uint32_t ii = 0; ii < length; ++ii) {
- // XXX TryCatch?
- v8::Handle<v8::Value> property = properties->Get(ii);
- QString name = v8engine->toString(property);
-
- int r = roleStrings.indexOf(name);
- if (r < 0) {
- r = roleStrings.count();
- roleStrings << name;
- }
- roles->append(r);
- }
-}
-
-void NestedListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
- Q_ASSERT(index >=0 && index < count());
-
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- bool emitItemsChanged = node->setProperty(property, value);
- if (!emitItemsChanged)
- return;
-
- int r = roleStrings.indexOf(property);
- if (r < 0) {
- r = roleStrings.count();
- roleStrings << property;
- }
- roles->append(r);
-}
-
-void NestedListModel::checkRoles() const
-{
- if (_rolesOk || !_root)
- return;
-
- for (int i = 0; i<_root->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node) {
- foreach (const QString &role, node->properties.keys()) {
- if (!roleStrings.contains(role))
- roleStrings.append(role);
- }
- }
- }
-
- _rolesOk = true;
-}
-
-QList<int> NestedListModel::roles() const
-{
- checkRoles();
- QList<int> rv;
- for (int ii = 0; ii < roleStrings.count(); ++ii)
- rv << ii;
- return rv;
-}
-
-QString NestedListModel::toString(int role) const
-{
- checkRoles();
- if (role < roleStrings.count())
- return roleStrings.at(role);
- else
- return QString();
-}
-
-
-ModelNode::ModelNode(NestedListModel *model)
-: modelCache(0), objectCache(0), isArray(false), m_model(model), listIndex(-1)
-{
-}
-
-ModelNode::~ModelNode()
-{
- clear();
- if (modelCache) { modelCache->m_nested->_root = 0/* ==this */; delete modelCache; modelCache = 0; }
- if (objectCache) { delete objectCache; objectCache = 0; }
-}
-
-void ModelNode::clear()
-{
- ModelNode *node;
- for (int ii = 0; ii < values.count(); ++ii) {
- node = qvariant_cast<ModelNode *>(values.at(ii));
- if (node) { delete node; node = 0; }
- }
- values.clear();
-
- qDeleteAll(properties.values());
- properties.clear();
-}
-
-bool ModelNode::setObjectValue(v8::Handle<v8::Value> valuemap, bool writeToCache)
-{
- if (!valuemap->IsObject())
- return false;
-
- bool emitItemsChanged = false;
-
- QV8Engine *v8engine = m_model->engine();
-
- v8::Local<v8::Array> propertyNames = v8engine->getOwnPropertyNames(valuemap->ToObject());
- uint32_t length = propertyNames->Length();
-
- for (uint32_t ii = 0; ii < length; ++ii) {
- // XXX TryCatch?
- v8::Handle<v8::Value> property = propertyNames->Get(ii);
- v8::Handle<v8::Value> v = valuemap->ToObject()->Get(property);
-
- QString name = v8engine->toString(property);
- ModelNode *prev = properties.value(name);
- ModelNode *value = new ModelNode(m_model);
-
- if (v->IsArray()) {
- value->isArray = true;
- value->setListValue(v);
- if (writeToCache && objectCache)
- objectCache->setValue(name.toUtf8(), QVariant::fromValue(value->model(m_model)));
- emitItemsChanged = true; // for now, too inefficient to check whether list and sublists have changed
- } else {
- value->values << v8engine->toVariant(v, -1);
- if (writeToCache && objectCache)
- objectCache->setValue(name.toUtf8(), value->values.last());
- if (!emitItemsChanged && prev && prev->values.count() == 1
- && prev->values[0] != value->values.last()) {
- emitItemsChanged = true;
- }
- }
- if (properties.contains(name))
- delete properties[name];
- properties.insert(name, value);
- }
- return emitItemsChanged;
-}
-
-void ModelNode::setListValue(v8::Handle<v8::Value> valuelist)
-{
- Q_ASSERT(valuelist->IsArray());
- values.clear();
-
- QV8Engine *engine = m_model->engine();
-
- v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(valuelist);
- uint32_t length = array->Length();
- for (uint32_t ii = 0; ii < length; ++ii) {
- ModelNode *value = new ModelNode(m_model);
-
- // XXX TryCatch?
- v8::Handle<v8::Value> v = array->Get(ii);
-
- if (v->IsArray()) {
- value->isArray = true;
- value->setListValue(v);
- } else if (v->IsObject()) {
- value->listIndex = ii;
- value->setObjectValue(v);
- } else {
- value->listIndex = ii;
- value->values << engine->toVariant(v, -1);
- }
-
- values.append(QVariant::fromValue(value));
- }
-}
-
-bool ModelNode::setProperty(const QString& prop, const QVariant& val) {
- QHash<QString, ModelNode *>::const_iterator it = properties.find(prop);
- bool emitItemsChanged = false;
- if (it != properties.end()) {
- if (val != (*it)->values[0])
- emitItemsChanged = true;
- (*it)->values[0] = val;
- } else {
- ModelNode *n = new ModelNode(m_model);
- n->values << val;
- properties.insert(prop,n);
- }
- if (objectCache)
- objectCache->setValue(prop.toUtf8(), val);
- return emitItemsChanged;
-}
-
-void ModelNode::updateListIndexes()
-{
- for (QHash<QString, ModelNode *>::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) {
- ModelNode *node = iter.value();
- if (node->isArray) {
- for (int i=0; i<node->values.count(); ++i) {
- ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(i));
- if (subNode)
- subNode->listIndex = i;
- }
- }
- node->updateListIndexes();
- }
-}
-
-/*
- Need to call this to emit itemsChanged() for modifications outside of set()
- and setProperty(), i.e. if an item returned from get() is modified
-*/
-void ModelNode::changedProperty(const QString &name) const
-{
- if (listIndex < 0)
- return;
-
- m_model->checkRoles();
- QList<int> roles;
- int role = m_model->roleStrings.indexOf(name);
- if (role < 0)
- roles = m_model->roles();
- else
- roles << role;
- emit m_model->m_listModel->itemsChanged(listIndex, 1, roles);
-}
-
-void ModelNode::dump(ModelNode *node, int ind)
-{
- QByteArray indentBa(ind * 4, ' ');
- const char *indent = indentBa.constData();
-
- for (int ii = 0; ii < node->values.count(); ++ii) {
- ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii));
- if (subNode) {
- qWarning().nospace() << indent << "Sub-node " << ii;
- dump(subNode, ind + 1);
- } else {
- qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString();
- }
- }
-
- for (QHash<QString, ModelNode *>::ConstIterator iter = node->properties.begin(); iter != node->properties.end(); ++iter) {
- qWarning().nospace() << indent << "Property " << iter.key() << ':';
- dump(iter.value(), ind + 1);
- }
-}
-
-ModelObject::ModelObject(ModelNode *node, NestedListModel *model, QV8Engine *eng)
-: m_model(model), m_node(node), m_meta(new ModelNodeMetaObject(eng, this))
-{
-}
-
-void ModelObject::setValue(const QByteArray &name, const QVariant &val)
-{
- m_meta->setValue(name, val);
- //setProperty(name.constData(), val);
-}
-
-void ModelObject::setNodeUpdatesEnabled(bool enable)
-{
- m_meta->m_enabled = enable;
-}
-
-ModelNodeMetaObject::ModelNodeMetaObject(QV8Engine *eng, ModelObject *object)
-: QDeclarativeOpenMetaObject(object), m_enabled(false), m_engine(eng), m_obj(object)
-{
-}
-
-void ModelNodeMetaObject::propertyWritten(int index)
-{
- if (!m_enabled)
- return;
-
- QString propName = QString::fromUtf8(name(index));
- QVariant value = operator[](index);
-
- v8::HandleScope handle_scope;
- v8::Context::Scope scope(m_engine->context());
- v8::Local<v8::Object> object = v8::Object::New();
- object->Set(m_engine->toString(propName), m_engine->variantWrapper()->newVariant(value));
- bool changed = m_obj->m_node->setObjectValue(object, false);
- if (changed)
- m_obj->m_node->changedProperty(propName);
-}
-
QT_END_NAMESPACE
diff --git a/src/declarative/util/qdeclarativelistmodel_p.h b/src/declarative/util/qdeclarativelistmodel_p.h
index ea83ae1e7e..05cc8c218f 100644
--- a/src/declarative/util/qdeclarativelistmodel_p.h
+++ b/src/declarative/util/qdeclarativelistmodel_p.h
@@ -53,6 +53,7 @@
#include <private/qlistmodelinterface_p.h>
#include <private/qv8engine_p.h>
+#include <private/qpodvector_p.h>
QT_BEGIN_HEADER
@@ -60,10 +61,10 @@ QT_BEGIN_NAMESPACE
QT_MODULE(Declarative)
-class FlatListModel;
-class NestedListModel;
class QDeclarativeListModelWorkerAgent;
-struct ModelNode;
+class ListModel;
+class ListLayout;
+
class Q_DECLARATIVE_PRIVATE_EXPORT QDeclarativeListModel : public QListModelInterface
{
Q_OBJECT
@@ -80,8 +81,8 @@ public:
Q_INVOKABLE void clear();
Q_INVOKABLE void remove(int index);
- Q_INVOKABLE void append(const QDeclarativeV8Handle &);
- Q_INVOKABLE void insert(int index, const QDeclarativeV8Handle &);
+ Q_INVOKABLE void append(QDeclarativeV8Function *args);
+ Q_INVOKABLE void insert(QDeclarativeV8Function *args);
Q_INVOKABLE QDeclarativeV8Handle get(int index) const;
Q_INVOKABLE void set(int index, const QDeclarativeV8Handle &);
Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value);
@@ -96,24 +97,31 @@ Q_SIGNALS:
private:
friend class QDeclarativeListModelParser;
friend class QDeclarativeListModelWorkerAgent;
- friend class FlatListModel;
- friend class QDeclarativeListModelV8Data;
- friend struct ModelNode;
+ friend class ModelObject;
+ friend class ModelNodeMetaObject;
+ friend class ListModel;
+ friend class ListElement;
// Constructs a flat list model for a worker agent
- QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent);
-
- void set(int index, const QDeclarativeV8Handle &, QList<int> *roles);
- void setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles);
+ QDeclarativeListModel(QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *agent);
+ QDeclarativeListModel(const QDeclarativeListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent=0);
- bool flatten();
- bool inWorkerThread() const;
+ QV8Engine *engine() const;
inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); }
+ ListLayout *m_layout;
+ ListModel *m_listModel;
+
QDeclarativeListModelWorkerAgent *m_agent;
- NestedListModel *m_nested;
- FlatListModel *m_flat;
+ bool m_mainThread;
+ bool m_primary;
+ mutable QV8Engine *m_engine;
+
+ void emitItemsChanged(int index, int count, const QList<int> &roles);
+ void emitItemsRemoved(int index, int count);
+ void emitItemsInserted(int index, int count);
+ void emitItemsMoved(int from, int to, int n);
};
// ### FIXME
@@ -146,8 +154,16 @@ private:
bool definesEmptyList(const QString &);
QString listElementTypeName;
-};
+ struct DataStackElement
+ {
+ DataStackElement() : model(0), elementIndex(0) {}
+
+ QString name;
+ ListModel *model;
+ int elementIndex;
+ };
+};
QT_END_NAMESPACE
diff --git a/src/declarative/util/qdeclarativelistmodel_p_p.h b/src/declarative/util/qdeclarativelistmodel_p_p.h
index 99cab4cab9..f1e47d7e15 100644
--- a/src/declarative/util/qdeclarativelistmodel_p_p.h
+++ b/src/declarative/util/qdeclarativelistmodel_p_p.h
@@ -64,189 +64,253 @@ QT_BEGIN_NAMESPACE
QT_MODULE(Declarative)
-class QDeclarativeOpenMetaObject;
-class QDeclarativeListModelWorkerAgent;
-struct ModelNode;
-class FlatNodeData;
+class ModelObject;
-class FlatListModel
+class ModelNodeMetaObject : public QDeclarativeOpenMetaObject
{
public:
- FlatListModel(QDeclarativeListModel *base);
- ~FlatListModel();
+ ModelNodeMetaObject(ModelObject *object);
+ ~ModelNodeMetaObject();
- QVariant data(int index, int role) const;
+ bool m_enabled;
- QList<int> roles() const;
- QString toString(int role) const;
+protected:
+ void propertyWritten(int index);
- int count() const;
- void clear();
- void remove(int index);
- bool insert(int index, v8::Handle<v8::Value>);
- v8::Handle<v8::Value> get(int index) const;
- void set(int index, v8::Handle<v8::Value>, QList<int> *roles);
- void setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles);
- void move(int from, int to, int count);
+private:
-private:
- friend class QDeclarativeListModelWorkerAgent;
- friend class QDeclarativeListModel;
- friend class QDeclarativeListModelV8Data;
- friend class FlatNodeData;
-
- bool addValue(v8::Handle<v8::Value> value, QHash<int, QVariant> *row, QList<int> *roles);
- void insertedNode(int index);
- void removedNode(int index);
- void moveNodes(int from, int to, int n);
-
- QV8Engine *engine() const;
- QV8Engine *m_engine;
- QHash<int, QString> m_roles;
- QHash<QString, int> m_strings;
- QList<QHash<int, QVariant> > m_values;
- QDeclarativeListModel *m_listModel;
-
- QList<FlatNodeData *> m_nodeData;
- QDeclarativeListModelWorkerAgent *m_parentAgent;
+ ModelObject *m_obj;
};
-/*
- FlatNodeData and FlatNodeObjectData allow objects returned by get() to still
- point to the correct list index if move(), insert() or remove() are called.
-*/
-class QV8ListModelResource;
-class FlatNodeData
+class ModelObject : public QObject
{
+ Q_OBJECT
public:
- FlatNodeData(int i)
- : index(i) {}
-
- ~FlatNodeData();
-
- void addData(QV8ListModelResource *data);
- void removeData(QV8ListModelResource *data);
-
- int index;
+ ModelObject(QDeclarativeListModel *model, int elementIndex);
+
+ void setValue(const QByteArray &name, const QVariant &val, bool force)
+ {
+ if (force) {
+ QVariant existingValue = m_meta->value(name);
+ if (existingValue.isValid()) {
+ (*m_meta)[name] = QVariant();
+ }
+ }
+ m_meta->setValue(name, val);
+ }
+
+ void setNodeUpdatesEnabled(bool enable)
+ {
+ m_meta->m_enabled = enable;
+ }
+
+ void updateValues();
+ void updateValues(const QList<int> &roles);
+
+ QDeclarativeListModel *m_model;
+ int m_elementIndex;
private:
- QSet<QV8ListModelResource*> objects;
+ ModelNodeMetaObject *m_meta;
};
-class QV8ListModelResource : public QV8ObjectResource
+class ListLayout
{
- V8_RESOURCE_TYPE(ListModelType);
public:
- QV8ListModelResource(FlatListModel *model, FlatNodeData *data, QV8Engine *engine);
- ~QV8ListModelResource();
+ ListLayout() : currentBlock(0), currentBlockOffset(0) {}
+ ListLayout(const ListLayout *other);
+ ~ListLayout();
- FlatListModel *model;
- FlatNodeData *nodeData;
-};
+ class Role
+ {
+ public:
-class NestedListModel
-{
-public:
- NestedListModel(QDeclarativeListModel *base);
- ~NestedListModel();
+ Role() : type(Invalid), blockIndex(-1), blockOffset(-1), index(-1), subLayout(0) {}
+ explicit Role(const Role *other);
+ ~Role();
- QHash<int,QVariant> data(int index, const QList<int> &roles, bool *hasNested = 0) const;
- QVariant data(int index, int role) const;
+ enum DataType
+ {
+ Invalid = -1,
- QList<int> roles() const;
- QString toString(int role) const;
+ String,
+ Number,
+ Bool,
+ List,
+ QObject
+ };
- int count() const;
- void clear();
- void remove(int index);
- bool insert(int index, v8::Handle<v8::Value>);
- v8::Handle<v8::Value> get(int index) const;
- void set(int index, v8::Handle<v8::Value>, QList<int> *roles);
- void setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles);
- void move(int from, int to, int count);
+ QString name;
+ DataType type;
+ int blockIndex;
+ int blockOffset;
+ int index;
+ ListLayout *subLayout;
+ };
+
+ const Role *getRoleOrCreate(const QString &key, const QVariant &data);
+ const Role &getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type);
+ const Role &getRoleOrCreate(const QString &key, Role::DataType type);
+
+ const Role &getExistingRole(int index) { return *roles.at(index); }
+ const Role *getExistingRole(const QString &key);
+ const Role *getExistingRole(v8::Handle<v8::String> key);
- QVariant valueForNode(ModelNode *, bool *hasNested = 0) const;
- void checkRoles() const;
+ int roleCount() const { return roles.count(); }
- ModelNode *_root;
- bool m_ownsRoot;
- QDeclarativeListModel *m_listModel;
+ static void sync(ListLayout *src, ListLayout *target);
- QV8Engine *engine() const;
private:
- friend struct ModelNode;
- mutable QStringList roleStrings;
- mutable bool _rolesOk;
-};
+ const Role &createRole(const QString &key, Role::DataType type);
+ int currentBlock;
+ int currentBlockOffset;
+ QVector<Role *> roles;
+ QStringHash<Role *> roleHash;
+};
-class ModelNodeMetaObject;
-class ModelObject : public QObject
+class ListElement
{
- Q_OBJECT
public:
- ModelObject(ModelNode *node, NestedListModel *model, QV8Engine *eng);
- void setValue(const QByteArray &name, const QVariant &val);
- void setNodeUpdatesEnabled(bool enable);
- NestedListModel *m_model;
- ModelNode *m_node;
+ ListElement();
+ ListElement(int existingUid);
+ ~ListElement();
+
+ static void sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash);
+
+ enum
+ {
+ BLOCK_SIZE = 64 - sizeof(int) - sizeof(ListElement *) - sizeof(ModelObject *)
+ };
private:
- ModelNodeMetaObject *m_meta;
+
+ void destroy(ListLayout *layout);
+
+ int setVariantProperty(const ListLayout::Role &role, const QVariant &d);
+
+ int setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d);
+
+ int setStringProperty(const ListLayout::Role &role, const QString &s);
+ int setDoubleProperty(const ListLayout::Role &role, double n);
+ int setBoolProperty(const ListLayout::Role &role, bool b);
+ int setListProperty(const ListLayout::Role &role, ListModel *m);
+ int setQObjectProperty(const ListLayout::Role &role, QObject *o);
+
+ void setStringPropertyFast(const ListLayout::Role &role, const QString &s);
+ void setDoublePropertyFast(const ListLayout::Role &role, double n);
+ void setBoolPropertyFast(const ListLayout::Role &role, bool b);
+ void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o);
+ void setListPropertyFast(const ListLayout::Role &role, ListModel *m);
+
+ void clearProperty(const ListLayout::Role &role);
+
+ QVariant getProperty(const ListLayout::Role &role, const QDeclarativeListModel *owner, QV8Engine *eng);
+ ListModel *getListProperty(const ListLayout::Role &role);
+ QString *getStringProperty(const ListLayout::Role &role);
+ QObject *getQObjectProperty(const ListLayout::Role &role);
+ QDeclarativeGuard<QObject> *getGuardProperty(const ListLayout::Role &role);
+
+ inline char *getPropertyMemory(const ListLayout::Role &role);
+
+ int getUid() const { return uid; }
+
+ char data[BLOCK_SIZE];
+ ListElement *next;
+
+ int uid;
+ ModelObject *m_objectCache;
+
+ friend class ListModel;
};
-class ModelNodeMetaObject : public QDeclarativeOpenMetaObject
+class ListModel
{
public:
- ModelNodeMetaObject(QV8Engine *eng, ModelObject *object);
- bool m_enabled;
+ ListModel(ListLayout *layout, QDeclarativeListModel *modelCache, int uid);
+ ~ListModel() {}
-protected:
- void propertyWritten(int index);
+ void destroy();
-private:
- QV8Engine *m_engine;
- ModelObject *m_obj;
-};
+ int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data);
+ int setExistingProperty(int uid, const QString &key, v8::Handle<v8::Value> data);
-/*
- A ModelNode is created for each item in a NestedListModel.
-*/
-struct ModelNode
-{
- ModelNode(NestedListModel *model);
- ~ModelNode();
+ QVariant getProperty(int elementIndex, int roleIndex, const QDeclarativeListModel *owner, QV8Engine *eng);
+ ListModel *getListProperty(int elementIndex, const ListLayout::Role &role);
+
+ int roleCount() const
+ {
+ return m_layout->roleCount();
+ }
+
+ const ListLayout::Role &getExistingRole(int index)
+ {
+ return m_layout->getExistingRole(index);
+ }
- QList<QVariant> values;
- QHash<QString, ModelNode *> properties;
+ const ListLayout::Role &getOrCreateListRole(const QString &name)
+ {
+ return m_layout->getRoleOrCreate(name, ListLayout::Role::List);
+ }
+
+ int elementCount() const
+ {
+ return elements.count();
+ }
+
+ void set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles);
+ void set(int elementIndex, v8::Handle<v8::Object> object);
+
+ int append(v8::Handle<v8::Object> object);
+ void insert(int elementIndex, v8::Handle<v8::Object> object);
void clear();
+ void remove(int index);
+
+ int appendElement();
+ void insertElement(int index);
- QDeclarativeListModel *model(const NestedListModel *model);
- ModelObject *object(const NestedListModel *model);
+ void move(int from, int to, int n);
- bool setObjectValue(v8::Handle<v8::Value> valuemap, bool writeToCache = true);
- void setListValue(v8::Handle<v8::Value> valuelist);
- bool setProperty(const QString& prop, const QVariant& val);
- void changedProperty(const QString &name) const;
- void updateListIndexes();
- static void dump(ModelNode *node, int ind);
+ int getUid() const { return m_uid; }
- QDeclarativeListModel *modelCache;
- ModelObject *objectCache;
- bool isArray;
+ static int allocateUid();
+
+ static void sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *srcModelHash);
+
+ ModelObject *getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex);
+
+private:
+ QPODVector<ListElement *, 4> elements;
+ ListLayout *m_layout;
+ int m_uid;
- NestedListModel *m_model;
- int listIndex; // only used for top-level nodes within a list
+ QDeclarativeListModel *m_modelCache;
+
+ struct ElementSync
+ {
+ ElementSync() : src(0), target(0) {}
+
+ ListElement *src;
+ ListElement *target;
+ };
+
+ void newElement(int index);
+
+ void updateCacheIndices();
+
+ friend class ListElement;
+ friend class QDeclarativeListModelWorkerAgent;
+
+ static QAtomicInt uidCounter;
};
+Q_DECLARE_METATYPE(ListModel *);
QT_END_NAMESPACE
-Q_DECLARE_METATYPE(ModelNode *)
-
QT_END_HEADER
#endif // QDECLARATIVELISTMODEL_P_P_H
diff --git a/src/declarative/util/qdeclarativelistmodelworkeragent.cpp b/src/declarative/util/qdeclarativelistmodelworkeragent.cpp
index b27e3e387f..2fed035b27 100644
--- a/src/declarative/util/qdeclarativelistmodelworkeragent.cpp
+++ b/src/declarative/util/qdeclarativelistmodelworkeragent.cpp
@@ -53,37 +53,44 @@
QT_BEGIN_NAMESPACE
-void QDeclarativeListModelWorkerAgent::Data::clearChange()
-{
- changes.clear();
+void QDeclarativeListModelWorkerAgent::Data::clearChange(QDeclarativeListModel *model)
+{
+ int uid = model->m_listModel->getUid();
+
+ for (int i=0 ; i < changes.count() ; ++i) {
+ if (changes[i].modelUid == uid) {
+ changes.removeAt(i);
+ --i;
+ }
+ }
}
-void QDeclarativeListModelWorkerAgent::Data::insertChange(int index, int count)
+void QDeclarativeListModelWorkerAgent::Data::insertChange(QDeclarativeListModel *model, int index, int count)
{
- Change c = { Change::Inserted, index, count, 0, QList<int>() };
+ Change c = { model->m_listModel->getUid(), Change::Inserted, index, count, 0, QList<int>() };
changes << c;
}
-void QDeclarativeListModelWorkerAgent::Data::removeChange(int index, int count)
+void QDeclarativeListModelWorkerAgent::Data::removeChange(QDeclarativeListModel *model, int index, int count)
{
- Change c = { Change::Removed, index, count, 0, QList<int>() };
+ Change c = { model->m_listModel->getUid(), Change::Removed, index, count, 0, QList<int>() };
changes << c;
}
-void QDeclarativeListModelWorkerAgent::Data::moveChange(int index, int count, int to)
+void QDeclarativeListModelWorkerAgent::Data::moveChange(QDeclarativeListModel *model, int index, int count, int to)
{
- Change c = { Change::Moved, index, count, to, QList<int>() };
+ Change c = { model->m_listModel->getUid(), Change::Moved, index, count, to, QList<int>() };
changes << c;
}
-void QDeclarativeListModelWorkerAgent::Data::changedChange(int index, int count, const QList<int> &roles)
+void QDeclarativeListModelWorkerAgent::Data::changedChange(QDeclarativeListModel *model, int index, int count, const QList<int> &roles)
{
- Change c = { Change::Changed, index, count, 0, roles };
+ Change c = { model->m_listModel->getUid(), Change::Changed, index, count, 0, roles };
changes << c;
}
QDeclarativeListModelWorkerAgent::QDeclarativeListModelWorkerAgent(QDeclarativeListModel *model)
-: m_engine(0), m_ref(1), m_orig(model), m_copy(new QDeclarativeListModel(model, this))
+: m_ref(1), m_orig(model), m_copy(new QDeclarativeListModel(model, this))
{
}
@@ -93,14 +100,7 @@ QDeclarativeListModelWorkerAgent::~QDeclarativeListModelWorkerAgent()
void QDeclarativeListModelWorkerAgent::setV8Engine(QV8Engine *eng)
{
- m_engine = eng;
- if (m_copy->m_flat)
- m_copy->m_flat->m_engine = eng;
-}
-
-QV8Engine *QDeclarativeListModelWorkerAgent::v8engine() const
-{
- return m_engine;
+ m_copy->m_engine = eng;
}
void QDeclarativeListModelWorkerAgent::addref()
@@ -123,36 +123,22 @@ int QDeclarativeListModelWorkerAgent::count() const
void QDeclarativeListModelWorkerAgent::clear()
{
- data.clearChange();
- data.removeChange(0, m_copy->count());
m_copy->clear();
}
void QDeclarativeListModelWorkerAgent::remove(int index)
{
- int count = m_copy->count();
m_copy->remove(index);
-
- if (m_copy->count() != count)
- data.removeChange(index, 1);
}
-void QDeclarativeListModelWorkerAgent::append(const QDeclarativeV8Handle &value)
+void QDeclarativeListModelWorkerAgent::append(QDeclarativeV8Function *args)
{
- int count = m_copy->count();
- m_copy->append(value);
-
- if (m_copy->count() != count)
- data.insertChange(m_copy->count() - 1, 1);
+ m_copy->append(args);
}
-void QDeclarativeListModelWorkerAgent::insert(int index, const QDeclarativeV8Handle &value)
+void QDeclarativeListModelWorkerAgent::insert(QDeclarativeV8Function *args)
{
- int count = m_copy->count();
- m_copy->insert(index, value);
-
- if (m_copy->count() != count)
- data.insertChange(index, 1);
+ m_copy->insert(args);
}
QDeclarativeV8Handle QDeclarativeListModelWorkerAgent::get(int index) const
@@ -162,24 +148,17 @@ QDeclarativeV8Handle QDeclarativeListModelWorkerAgent::get(int index) const
void QDeclarativeListModelWorkerAgent::set(int index, const QDeclarativeV8Handle &value)
{
- QList<int> roles;
- m_copy->set(index, value, &roles);
- if (!roles.isEmpty())
- data.changedChange(index, 1, roles);
+ m_copy->set(index, value);
}
void QDeclarativeListModelWorkerAgent::setProperty(int index, const QString& property, const QVariant& value)
{
- QList<int> roles;
- m_copy->setProperty(index, property, value, &roles);
- if (!roles.isEmpty())
- data.changedChange(index, 1, roles);
+ m_copy->setProperty(index, property, value);
}
void QDeclarativeListModelWorkerAgent::move(int from, int to, int count)
{
m_copy->move(from, to, count);
- data.moveChange(from, to, count);
}
void QDeclarativeListModelWorkerAgent::sync()
@@ -195,14 +174,10 @@ void QDeclarativeListModelWorkerAgent::sync()
mutex.unlock();
}
-void QDeclarativeListModelWorkerAgent::changedData(int index, int count, const QList<int> &roles)
-{
- data.changedChange(index, count, roles);
-}
-
bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
{
if (e->type() == QEvent::User) {
+
QMutexLocker locker(&mutex);
Sync *s = static_cast<Sync *>(e);
@@ -211,56 +186,35 @@ bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
if (m_copy) {
bool cc = m_orig->count() != s->list->count();
- FlatListModel *orig = m_orig->m_flat;
- FlatListModel *copy = s->list->m_flat;
- if (!orig || !copy) {
- syncDone.wakeAll();
- return QObject::event(e);
- }
-
- orig->m_roles = copy->m_roles;
- orig->m_strings = copy->m_strings;
- orig->m_values = copy->m_values;
+ QHash<int, ListModel *> targetModelHash;
+ ListModel::sync(s->list->m_listModel, m_orig->m_listModel, &targetModelHash);
- // update the orig->m_nodeData list
for (int ii = 0; ii < changes.count(); ++ii) {
const Change &change = changes.at(ii);
- switch (change.type) {
- case Change::Inserted:
- orig->insertedNode(change.index);
- break;
- case Change::Removed:
- orig->removedNode(change.index);
- break;
- case Change::Moved:
- orig->moveNodes(change.index, change.to, change.count);
- break;
- case Change::Changed:
- break;
+
+ ListModel *model = targetModelHash.value(change.modelUid);
+
+ if (model && model->m_modelCache) {
+ switch (change.type) {
+ case Change::Inserted:
+ emit model->m_modelCache->itemsInserted(change.index, change.count);
+ break;
+ case Change::Removed:
+ emit model->m_modelCache->itemsRemoved(change.index, change.count);
+ break;
+ case Change::Moved:
+ emit model->m_modelCache->itemsMoved(change.index, change.to, change.count);
+ break;
+ case Change::Changed:
+ emit model->m_modelCache->itemsChanged(change.index, change.count, change.roles);
+ break;
+ }
}
}
syncDone.wakeAll();
locker.unlock();
- for (int ii = 0; ii < changes.count(); ++ii) {
- const Change &change = changes.at(ii);
- switch (change.type) {
- case Change::Inserted:
- emit m_orig->itemsInserted(change.index, change.count);
- break;
- case Change::Removed:
- emit m_orig->itemsRemoved(change.index, change.count);
- break;
- case Change::Moved:
- emit m_orig->itemsMoved(change.index, change.to, change.count);
- break;
- case Change::Changed:
- emit m_orig->itemsChanged(change.index, change.count, change.roles);
- break;
- }
- }
-
if (cc)
emit m_orig->countChanged();
} else {
diff --git a/src/declarative/util/qdeclarativelistmodelworkeragent_p.h b/src/declarative/util/qdeclarativelistmodelworkeragent_p.h
index 12505e942c..b6c42ae167 100644
--- a/src/declarative/util/qdeclarativelistmodelworkeragent_p.h
+++ b/src/declarative/util/qdeclarativelistmodelworkeragent_p.h
@@ -79,7 +79,6 @@ public:
~QDeclarativeListModelWorkerAgent();
void setV8Engine(QV8Engine *eng);
- QV8Engine *v8engine() const;
void addref();
void release();
@@ -88,8 +87,8 @@ public:
Q_INVOKABLE void clear();
Q_INVOKABLE void remove(int index);
- Q_INVOKABLE void append(const QDeclarativeV8Handle &);
- Q_INVOKABLE void insert(int index, const QDeclarativeV8Handle &);
+ Q_INVOKABLE void append(QDeclarativeV8Function *args);
+ Q_INVOKABLE void insert(QDeclarativeV8Function *args);
Q_INVOKABLE QDeclarativeV8Handle get(int index) const;
Q_INVOKABLE void set(int index, const QDeclarativeV8Handle &);
Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value);
@@ -116,10 +115,11 @@ protected:
private:
friend class QDeclarativeWorkerScriptEnginePrivate;
- friend class QDeclarativeListModelV8Data;
- QV8Engine *m_engine;
+ friend class QDeclarativeListModel;
- struct Change {
+ struct Change
+ {
+ int modelUid;
enum { Inserted, Removed, Moved, Changed } type;
int index; // Inserted/Removed/Moved/Changed
int count; // Inserted/Removed/Moved/Changed
@@ -127,14 +127,15 @@ private:
QList<int> roles;
};
- struct Data {
+ struct Data
+ {
QList<Change> changes;
- void clearChange();
- void insertChange(int index, int count);
- void removeChange(int index, int count);
- void moveChange(int index, int count, int to);
- void changedChange(int index, int count, const QList<int> &roles);
+ void clearChange(QDeclarativeListModel *model);
+ void insertChange(QDeclarativeListModel *model, int index, int count);
+ void removeChange(QDeclarativeListModel *model, int index, int count);
+ void moveChange(QDeclarativeListModel *model, int index, int count, int to);
+ void changedChange(QDeclarativeListModel *model, int index, int count, const QList<int> &roles);
};
Data data;
@@ -144,8 +145,6 @@ private:
QDeclarativeListModel *list;
};
- void changedData(int index, int count, const QList<int> &roles);
-
QAtomicInt m_ref;
QDeclarativeListModel *m_orig;
QDeclarativeListModel *m_copy;
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js
new file mode 100644
index 0000000000..cb9dfa66aa
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js
@@ -0,0 +1,8 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.action == 'removeItem') {
+ msg.model.remove(0);
+ } else if (msg.action == 'dosync') {
+ msg.model.sync();
+ }
+ WorkerScript.sendMessage({'done': true})
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml
new file mode 100644
index 0000000000..e2361acf6b
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Item {
+ id: item
+ property variant model
+ property bool done: false
+
+ WorkerScript {
+ id: worker
+ source: "workerremoveelement.js"
+ onMessage: {
+ item.done = true
+ }
+ }
+
+ function addItem() {
+ model.append({ 'data': 1 });
+
+ var element = model.get(0);
+ }
+
+ function removeItemViaWorker() {
+ done = false
+ var msg = { 'action': 'removeItem', 'model': model }
+ worker.sendMessage(msg);
+ }
+
+ function doSync() {
+ done = false
+ var msg = { 'action': 'dosync', 'model': model }
+ worker.sendMessage(msg);
+ }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js
new file mode 100644
index 0000000000..f63dd68839
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js
@@ -0,0 +1,9 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.action == 'removeList') {
+ msg.model.remove(0);
+ } else if (msg.action == 'dosync') {
+ msg.model.sync();
+ }
+ WorkerScript.sendMessage({'done': true})
+}
+
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml
new file mode 100644
index 0000000000..bdb5e024d8
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Item {
+ id: item
+ property variant model
+ property bool done: false
+
+ WorkerScript {
+ id: worker
+ source: "workerremovelist.js"
+ onMessage: {
+ item.done = true
+ }
+ }
+
+ function addList() {
+ model.append({ 'data': [ { 'subData': 1 } ] });
+
+ var element = model.get(0);
+ }
+
+ function removeListViaWorker() {
+ done = false
+ var msg = { 'action': 'removeList', 'model': model }
+ worker.sendMessage(msg);
+ }
+
+ function doSync() {
+ done = false
+ var msg = { 'action': 'dosync', 'model': model }
+ worker.sendMessage(msg);
+ }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js
new file mode 100644
index 0000000000..9b8d8fa7f3
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js
@@ -0,0 +1,8 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.action == 'addItem') {
+ msg.model.get(0).level0.append({ 'level1': 33 });
+ } else if (msg.action == 'dosync') {
+ msg.model.sync();
+ }
+ WorkerScript.sendMessage({'done': true})
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml
new file mode 100644
index 0000000000..c21cd43e7e
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+
+Item {
+ id: item
+ property variant model
+ property bool done: false
+
+ WorkerScript {
+ id: worker
+ source: "workersync.js"
+ onMessage: {
+ item.done = true
+ }
+ }
+
+ function addItem0() {
+ model.append({ 'level0': [ { 'level1': 29 } ] });
+ model.append({ 'level0': [ { 'level1': 37 } ] });
+ }
+
+ function addItemViaWorker() {
+ done = false
+ var msg = { 'action': 'addItem', 'model': model }
+ worker.sendMessage(msg);
+ }
+
+ function doSync() {
+ done = false
+ var msg = { 'action': 'dosync', 'model': model }
+ worker.sendMessage(msg);
+ }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
index bc6c99102e..23e9fc4c07 100644
--- a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
+++ b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
@@ -54,6 +54,17 @@
Q_DECLARE_METATYPE(QList<int>)
Q_DECLARE_METATYPE(QList<QVariantHash>)
+#define RUNEVAL(object, string) \
+ QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
+
+inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
+{
+ QDeclarativeExpression expr(engine->rootContext(), 0, str);
+ return expr.evaluate();
+}
+
+#define RUNEXPR(string) runexpr(&engine, QString(string))
+
class tst_qdeclarativelistmodel : public QObject
{
Q_OBJECT
@@ -65,6 +76,8 @@ private:
QQuickItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
void waitForWorker(QQuickItem *item);
+ static bool compareVariantList(const QVariantList &testList, QVariant object);
+
private slots:
void static_types();
void static_types_data();
@@ -78,16 +91,12 @@ private slots:
void dynamic_worker();
void dynamic_worker_sync_data();
void dynamic_worker_sync();
- void convertNestedToFlat_fail();
- void convertNestedToFlat_fail_data();
- void convertNestedToFlat_ok();
- void convertNestedToFlat_ok_data();
void enumerate();
void error_data();
void error();
void syncError();
- void set();
void get();
+ void set();
void get_data();
void get_worker();
void get_worker_data();
@@ -101,8 +110,64 @@ private slots:
void property_changes_worker_data();
void clear();
void signal_handlers();
+ void worker_sync();
+ void worker_remove_element();
+ void worker_remove_list();
};
+bool tst_qdeclarativelistmodel::compareVariantList(const QVariantList &testList, QVariant object)
+{
+ bool allOk = true;
+
+ QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel *>(object.value<QObject *>());
+ if (model == 0)
+ return false;
+
+ if (model->count() != testList.count())
+ return false;
+
+ for (int i=0 ; i < testList.count() ; ++i) {
+ const QVariant &testVariant = testList.at(i);
+ if (testVariant.type() != QVariant::Map)
+ return false;
+ const QVariantMap &map = testVariant.toMap();
+
+ const QList<int> &roles = model->roles();
+
+ QVariantMap::const_iterator it = map.begin();
+ QVariantMap::const_iterator end = map.end();
+
+ while (it != end) {
+ const QString &testKey = it.key();
+ const QVariant &testData = it.value();
+
+ int roleIndex = -1;
+ for (int j=0 ; j < roles.count() ; ++j) {
+ if (model->toString(roles[j]).compare(testKey) == 0) {
+ roleIndex = j;
+ break;
+ }
+ }
+
+ if (roleIndex == -1)
+ return false;
+
+ const QVariant &modelData = model->data(i, roleIndex);
+
+ if (testData.type() == QVariant::List) {
+ const QVariantList &subList = testData.toList();
+ allOk = allOk && compareVariantList(subList, modelData);
+ } else {
+ allOk = allOk && (testData == modelData);
+ }
+
+ ++it;
+ }
+ }
+
+ return allOk;
+}
+
int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
{
QList<int> roles = model->roles();
@@ -118,7 +183,7 @@ QQuickItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng,
QQuickItem *item = qobject_cast<QQuickItem*>(component->create());
QDeclarativeEngine::setContextForObject(model, eng->rootContext());
if (item)
- item->setProperty("model", qVariantFromValue(model));
+ item->setProperty("model", qVariantFromValue(model));
return item;
}
@@ -141,43 +206,66 @@ void tst_qdeclarativelistmodel::static_types_data()
{
QTest::addColumn<QString>("qml");
QTest::addColumn<QVariant>("value");
+ QTest::addColumn<QString>("error");
QTest::newRow("string")
<< "ListElement { foo: \"bar\" }"
- << QVariant(QString("bar"));
+ << QVariant(QString("bar"))
+ << QString();
QTest::newRow("real")
<< "ListElement { foo: 10.5 }"
- << QVariant(10.5);
+ << QVariant(10.5)
+ << QString();
QTest::newRow("real0")
<< "ListElement { foo: 0 }"
- << QVariant(double(0));
+ << QVariant(double(0))
+ << QString();
QTest::newRow("bool")
<< "ListElement { foo: false }"
- << QVariant(false);
+ << QVariant(false)
+ << QString();
QTest::newRow("bool")
<< "ListElement { foo: true }"
- << QVariant(true);
+ << QVariant(true)
+ << QString();
QTest::newRow("enum")
<< "ListElement { foo: Text.AlignHCenter }"
- << QVariant(double(QQuickText::AlignHCenter));
+ << QVariant(double(QQuickText::AlignHCenter))
+ << QString();
QTest::newRow("Qt enum")
<< "ListElement { foo: Qt.AlignBottom }"
- << QVariant(double(Qt::AlignBottom));
+ << QVariant(double(Qt::AlignBottom))
+ << QString();
+
+ QTest::newRow("role error")
+ << "ListElement { foo: 1 } ListElement { foo: 'string' }"
+ << QVariant()
+ << QString("<Unknown File>: Can't assign to pre-existing role of different type foo");
+
+ QTest::newRow("list type error")
+ << "ListElement { foo: 1 } ListElement { foo: ListElement { bar: 1 } }"
+ << QVariant()
+ << QString("<Unknown File>: Can't assign to pre-existing role of different type foo");
}
void tst_qdeclarativelistmodel::static_types()
{
QFETCH(QString, qml);
QFETCH(QVariant, value);
+ QFETCH(QString, error);
qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
+ if (!error.isEmpty()) {
+ QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
+ }
+
QDeclarativeEngine engine;
QDeclarativeComponent component(&engine);
component.setData(qml.toUtf8(),
@@ -188,10 +276,12 @@ void tst_qdeclarativelistmodel::static_types()
QObject *obj = component.create();
QVERIFY(obj != 0);
- QVariant actual = obj->property("test");
+ if (error.isEmpty()) {
+ QVariant actual = obj->property("test");
- QCOMPARE(actual, value);
- QCOMPARE(actual.toString(), value.toString());
+ QCOMPARE(actual, value);
+ QCOMPARE(actual.toString(), value.toString());
+ }
delete obj;
}
@@ -270,11 +360,11 @@ void tst_qdeclarativelistmodel::static_nestedElements()
QFETCH(int, elementCount);
QStringList elements;
- for (int i=0; i<elementCount; i++)
+ for (int i=0; i<elementCount; i++)
elements.append("ListElement { a: 1; b: 2 }");
QString elementsStr = elements.join(",\n") + "\n";
- QString componentStr =
+ QString componentStr =
"import QtQuick 2.0\n"
"Item {\n"
" property variant count: model.get(0).attributes.count\n"
@@ -283,7 +373,7 @@ void tst_qdeclarativelistmodel::static_nestedElements()
" ListElement {\n"
" attributes: [\n";
componentStr += elementsStr.toUtf8().constData();
- componentStr +=
+ componentStr +=
" ]\n"
" }\n"
" }\n"
@@ -320,7 +410,6 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::addColumn<QString>("warning");
// Simple flat model
-
QTest::newRow("count") << "count" << 0 << "";
QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
@@ -336,7 +425,8 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
- QTest::newRow("append4b") << "{append([1,2,3])}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
+ QTest::newRow("append4b") << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "";
+ QTest::newRow("append4c") << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "";
QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "";
QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "";
@@ -361,7 +451,8 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "";
QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range";
QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
- QTest::newRow("insert5b") << "{insert(0,[1,2,3])}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
+ QTest::newRow("insert5b") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "";
+ QTest::newRow("insert5c") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "";
QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "";
QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "";
@@ -396,8 +487,11 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
- // Nested models
+ QTest::newRow("large1") << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "";
+
+ QTest::newRow("datatypes1") << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to pre-existing role of different type a";
+ // Nested models
QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";
QTest::newRow("nested-append3") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.append({'a':4});get(0).bars.get(3).a}" << 4 << "";
@@ -446,12 +540,8 @@ void tst_qdeclarativelistmodel::dynamic_worker()
QFETCH(int, result);
QFETCH(QString, warning);
- if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
- return;
-
- // This is same as dynamic() except it applies the test to a ListModel called
- // from a WorkerScript (i.e. testing the internal FlatListModel that is created
- // by the WorkerListModelAgent)
+ // This is same as dynamic() except it applies the test to a ListModel called
+ // from a WorkerScript.
QDeclarativeListModel model;
QDeclarativeEngine eng;
@@ -498,7 +588,7 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
// This is the same as dynamic_worker() except that it executes a set of list operations
// from the worker script, calls sync(), and tests the changes are reflected in the
// list in the main thread
-
+
QDeclarativeListModel model;
QDeclarativeEngine eng;
QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
@@ -518,140 +608,56 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
// execute a set of commands on the worker list model, then check the
// changes are reflected in the list model in the main thread
- if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
-
- if (QByteArray(QTest::currentDataTag()).startsWith("nested-set"))
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
-
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
+ QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
waitForWorker(item);
QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
- if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
- QCOMPARE(e.evaluate().toInt(), result);
-
- delete item;
- qApp->processEvents();
-}
-
-#define RUNEVAL(object, string) \
- QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
-
-inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
-{
- QDeclarativeExpression expr(engine->rootContext(), 0, str);
- return expr.evaluate();
-}
-
-#define RUNEXPR(string) runexpr(&engine, QString(string))
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_fail()
-{
- // If a model has nested data, it cannot be used at all from a worker script
-
- QFETCH(QString, script);
-
- QDeclarativeListModel model;
- QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
- QQuickItem *item = createWorkerTest(&eng, &component, &model);
- QVERIFY(item != 0);
-
- RUNEVAL(item, "model.append({foo: 123})");
- RUNEVAL(item, "model.append({foo: [{}, {}]})");
-
- QCOMPARE(model.count(), 2);
-
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: List contains list-type data and cannot be used from a worker script");
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
- waitForWorker(item);
-
- QCOMPARE(model.count(), 2);
+ QCOMPARE(e.evaluate().toInt(), result);
delete item;
qApp->processEvents();
}
-void tst_qdeclarativelistmodel::convertNestedToFlat_fail_data()
-{
- QTest::addColumn<QString>("script");
-
- QTest::newRow("clear") << "clear()";
- QTest::newRow("remove") << "remove(0)";
- QTest::newRow("append") << "append({'x':1})";
- QTest::newRow("insert") << "insert(0, {'x':1})";
- QTest::newRow("set") << "set(0, {'foo':1})";
- QTest::newRow("setProperty") << "setProperty(0, 'foo', 1})";
- QTest::newRow("move") << "move(0, 1, 1})";
- QTest::newRow("get") << "get(0)";
-}
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_ok()
-
+void tst_qdeclarativelistmodel::enumerate()
{
- // If a model only has plain data, it can be modified from a worker script. However,
- // once the model is used from a worker script, it no longer accepts nested data
-
- QFETCH(QString, script);
-
- QDeclarativeListModel model;
QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
- QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
+ QVERIFY(!component.isError());
+ QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
QVERIFY(item != 0);
- RUNEVAL(item, "model.append({foo: 123})");
-
- QCOMPARE(model.count(), 1);
-
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
- waitForWorker(item);
-
- // can still add plain data
- int count = model.count();
-
- RUNEVAL(item, "model.append({foo: 123})");
+ QLatin1String expectedStrings[] = {
+ QLatin1String("val1=1Y"),
+ QLatin1String("val2=2Y"),
+ QLatin1String("val3=strY"),
+ QLatin1String("val4=falseN"),
+ QLatin1String("val5=trueY")
+ };
- QCOMPARE(model.count(), count+1);
+ int expectedStringCount = sizeof(expectedStrings) / sizeof(expectedStrings[0]);
- const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
+ QStringList r = item->property("result").toString().split(":");
- QTest::ignoreMessage(QtWarningMsg, warning);
- RUNEVAL(item, "model.append({foo: [{}, {}]})");
+ int matchCount = 0;
+ for (int i=0 ; i < expectedStringCount ; ++i) {
+ const QLatin1String &expectedString = expectedStrings[i];
- QTest::ignoreMessage(QtWarningMsg, warning);
- RUNEVAL(item, "model.insert(0, {foo: [{}, {}]})");
+ QStringList::const_iterator it = r.begin();
+ QStringList::const_iterator end = r.end();
- QTest::ignoreMessage(QtWarningMsg, warning);
- RUNEVAL(item, "model.set(0, {foo: [{}, {}]})");
+ while (it != end) {
+ if (it->compare(expectedString) == 0) {
+ ++matchCount;
+ break;
+ }
+ ++it;
+ }
+ }
- QCOMPARE(model.count(), count+1);
+ QVERIFY(matchCount == expectedStringCount);
delete item;
- qApp->processEvents();
-}
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_ok_data()
-{
- convertNestedToFlat_fail_data();
-}
-
-void tst_qdeclarativelistmodel::enumerate()
-{
- QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
- QVERIFY(!component.isError());
- QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
- QVERIFY(item != 0);
- QStringList r = item->property("result").toString().split(":");
- QCOMPARE(r[0],QLatin1String("val1=1Y"));
- QCOMPARE(r[1],QLatin1String("val2=2Y"));
- QCOMPARE(r[2],QLatin1String("val3=strY"));
- QCOMPARE(r[3],QLatin1String("val4=falseN"));
- QCOMPARE(r[4],QLatin1String("val5=trueY"));
- delete item;
}
void tst_qdeclarativelistmodel::error_data()
@@ -752,11 +758,15 @@ void tst_qdeclarativelistmodel::set()
RUNEXPR("model.set(0, {test:true})");
QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache
- QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
+ QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
RUNEXPR("model.set(0, {test:false})");
QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
- QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
+ QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
+
+ QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Can't create role for unsupported data type");
+ QVariant invalidData = QColor();
+ model.setProperty(0, "test", invalidData);
}
/*
@@ -773,21 +783,31 @@ void tst_qdeclarativelistmodel::get()
QDeclarativeComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
- "ListModel { \n"
- "ListElement { roleA: 100 }\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "}", QUrl());
+ "ListModel {}\n", QUrl());
QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
- int role = roleFromName(model, roleName);
- QVERIFY(role >= 0);
+ engine.rootContext()->setContextProperty("model", model);
+
+ RUNEXPR("model.append({roleA: 100})");
+ RUNEXPR("model.append({roleA: 200, roleB: 400})");
+ RUNEXPR("model.append({roleA: 200, roleB: 400})");
+ RUNEXPR("model.append({roleC: {} })");
+ RUNEXPR("model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
QDeclarativeExpression expr(engine.rootContext(), model, expression);
expr.evaluate();
QVERIFY(!expr.hasError());
- QCOMPARE(model->data(index, role), roleValue);
+ int role = roleFromName(model, roleName);
+ QVERIFY(role >= 0);
+
+ if (roleValue.type() == QVariant::List) {
+ const QVariantList &list = roleValue.toList();
+ QVERIFY(compareVariantList(list, model->data(index, role)));
+ } else {
+ QCOMPARE(model->data(index, role), roleValue);
+ }
+
QCOMPARE(spy.count(), 1);
QList<QVariant> spyResult = spy.takeFirst();
@@ -805,19 +825,16 @@ void tst_qdeclarativelistmodel::get_data()
QTest::addColumn<QString>("roleName");
QTest::addColumn<QVariant>("roleValue");
- QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
- QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
+ QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
+ QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
QVariantMap map;
- map["zzz"] = 123;
- QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
-
QVariantList list;
map.clear(); map["a"] = 50; map["b"] = 500;
list << map;
map.clear(); map["c"] = 1000;
list << map;
- QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
+ QTest::newRow("list of objects") << "get(2).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list);
}
void tst_qdeclarativelistmodel::get_worker()
@@ -837,13 +854,12 @@ void tst_qdeclarativelistmodel::get_worker()
RUNEVAL(item, "model.append({roleA: 100})");
RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
+ RUNEVAL(item, "model.append({roleC: {} })");
+ RUNEVAL(item, "model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
int role = roleFromName(&model, roleName);
QVERIFY(role >= 0);
- const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
- if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
- QTest::ignoreMessage(QtWarningMsg, warning);
QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
// in the worker thread, change the model data and call sync()
@@ -852,18 +868,19 @@ void tst_qdeclarativelistmodel::get_worker()
waitForWorker(item);
// see if we receive the model changes in the main thread's model
- if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
- QVERIFY(model.data(index, role) != roleValue);
- QCOMPARE(spy.count(), 0);
+ if (roleValue.type() == QVariant::List) {
+ const QVariantList &list = roleValue.toList();
+ QVERIFY(compareVariantList(list, model.data(index, role)));
} else {
QCOMPARE(model.data(index, role), roleValue);
- QCOMPARE(spy.count(), 1);
-
- QList<QVariant> spyResult = spy.takeFirst();
- QCOMPARE(spyResult.at(0).toInt(), index);
- QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
- QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
}
+
+ QCOMPARE(spy.count(), 1);
+
+ QList<QVariant> spyResult = spy.takeFirst();
+ QCOMPARE(spyResult.at(0).toInt(), index);
+ QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
+ QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
}
void tst_qdeclarativelistmodel::get_worker_data()
@@ -881,39 +898,48 @@ void tst_qdeclarativelistmodel::get_nested()
QFETCH(QString, roleName);
QFETCH(QVariant, roleValue);
- QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng);
+ if (roleValue.type() == QVariant::Map)
+ return;
+
+ QDeclarativeEngine engine;
+ QDeclarativeComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
- "ListModel { \n"
- "ListElement {\n"
- "listRoleA: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "}\n"
- "ListElement {\n"
- "listRoleA: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "listRoleB: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "listRoleC: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "}\n"
- "}", QUrl());
+ "ListModel {}", QUrl());
QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
QVERIFY(component.errorString().isEmpty());
QDeclarativeListModel *childModel;
+ engine.rootContext()->setContextProperty("model", model);
+
+ RUNEXPR("model.append({ listRoleA: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "] })\n");
+
+ RUNEXPR("model.append({ listRoleA: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "],\n"
+ "listRoleB: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "],\n"
+ "listRoleC: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "] })\n");
// Test setting the inner list data for:
// get(0).listRoleA
@@ -937,7 +963,7 @@ void tst_qdeclarativelistmodel::get_nested()
QVERIFY(childModel);
QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
- QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
+ QDeclarativeExpression expr(engine.rootContext(), model, extendedExpression);
QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
expr.evaluate();
@@ -945,7 +971,11 @@ void tst_qdeclarativelistmodel::get_nested()
int role = roleFromName(childModel, roleName);
QVERIFY(role >= 0);
- QCOMPARE(childModel->data(index, role), roleValue);
+ if (roleValue.type() == QVariant::List) {
+ QVERIFY(compareVariantList(roleValue.toList(), childModel->data(index, role)));
+ } else {
+ QCOMPARE(childModel->data(index, role), roleValue);
+ }
QCOMPARE(spy.count(), 1);
QList<QVariant> spyResult = spy.takeFirst();
@@ -1017,6 +1047,7 @@ void tst_qdeclarativelistmodel::property_changes()
"target: model.get(" + QString::number(listIndex) + ")\n"
+ signalHandler + " gotSignal = true\n"
"}\n";
+
QDeclarativeComponent component(&engine);
component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
engine.rootContext()->setContextProperty("model", &model);
@@ -1117,10 +1148,6 @@ void tst_qdeclarativelistmodel::property_changes_data()
void tst_qdeclarativelistmodel::property_changes_worker()
{
- // nested models are not supported when WorkerScript is involved
- if (QByteArray(QTest::currentDataTag()).startsWith("nested-"))
- return;
-
QFETCH(QString, script_setup);
QFETCH(QString, script_change);
QFETCH(QString, roleName);
@@ -1208,6 +1235,129 @@ void tst_qdeclarativelistmodel::signal_handlers()
delete model;
}
+void tst_qdeclarativelistmodel::worker_sync()
+{
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workersync.qml"));
+ QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ QVERIFY(model.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItem0"));
+
+ QVERIFY(model.count() == 2);
+ QVariant childData = model.data(0, 0);
+ QDeclarativeListModel *childModel = qobject_cast<QDeclarativeListModel *>(childData.value<QObject *>());
+ QVERIFY(childModel);
+ QVERIFY(childModel->count() == 1);
+
+ QSignalSpy spyModelInserted(&model, SIGNAL(itemsInserted(int,int)));
+ QSignalSpy spyChildInserted(childModel, SIGNAL(itemsInserted(int,int)));
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 1);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 2);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 2);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 3);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 2);
+
+ delete item;
+ qApp->processEvents();
+}
+
+void tst_qdeclarativelistmodel::worker_remove_element()
+{
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workerremoveelement.qml"));
+ QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItem"));
+
+ QVERIFY(model.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 1);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 1);
+
+ delete item;
+ qApp->processEvents();
+}
+
+void tst_qdeclarativelistmodel::worker_remove_list()
+{
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workerremovelist.qml"));
+ QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addList"));
+
+ QVERIFY(model.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "removeListViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 1);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 1);
+
+ delete item;
+ qApp->processEvents();
+}
+
QTEST_MAIN(tst_qdeclarativelistmodel)
#include "tst_qdeclarativelistmodel.moc"