QT_BEGIN_NAMESPACE
-QV8ListModelResource::QV8ListModelResource(FlatListModel *model, FlatNodeData *data, QV8Engine *engine)
-: QV8ObjectResource(engine), model(model), nodeData(data)
+// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
+enum { MIN_LISTMODEL_UID = 1024 };
+
+QAtomicInt ListModel::uidCounter(MIN_LISTMODEL_UID);
+
+const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
{
- if (nodeData) nodeData->addData(this);
+ QStringHash<Role *>::Node *node = roleHash.findNode(key);
+ if (node) {
+ const Role &r = *node->value;
+ if (type != r.type) {
+ qmlInfo(0) << "Can't assign to pre-existing role of different type " << r.name;
+ }
+ return r;
+ }
+
+ return createRole(key, type);
}
-QV8ListModelResource::~QV8ListModelResource()
+const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type)
{
- if (nodeData) nodeData->removeData(this);
+ QHashedV8String hashedKey(key);
+ QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
+ if (node) {
+ const Role &r = *node->value;
+ if (type != r.type) {
+ qmlInfo(0) << "Can't assign to pre-existing role of different type " << r.name;
+ }
+ return r;
+ }
+
+ QString qkey;
+ qkey.resize(key->Length());
+ key->Write(reinterpret_cast<uint16_t*>(qkey.data()));
+
+ return createRole(qkey, type);
}
-class QDeclarativeListModelV8Data : public QV8Engine::Deletable
+const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
{
-public:
- QDeclarativeListModelV8Data();
- ~QDeclarativeListModelV8Data();
+ const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QDeclarativeGuard<QObject>) };
+ const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QObject *) };
- v8::Persistent<v8::Function> constructor;
+ Role *r = new Role;
+ r->name = key;
+ r->type = type;
- static v8::Local<v8::Object> create(QV8Engine *);
+ if (type == Role::List) {
+ r->subLayout = new ListLayout;
+ } else {
+ r->subLayout = 0;
+ }
- static v8::Handle<v8::Value> Getter(v8::Local<v8::String> property,
- const v8::AccessorInfo &info);
- static v8::Handle<v8::Value> Setter(v8::Local<v8::String> property,
- v8::Local<v8::Value> value,
- const v8::AccessorInfo &info);
-};
+ int dataSize = dataSizes[type];
+ int dataAlignment = dataAlignments[type];
-v8::Local<v8::Object> QDeclarativeListModelV8Data::create(QV8Engine *engine)
-{
- if (!engine->listModelData()) {
- QDeclarativeListModelV8Data *d = new QDeclarativeListModelV8Data;
- engine->setListModelData(d);
+ int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
+ if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
+ r->blockIndex = ++currentBlock;
+ r->blockOffset = 0;
+ currentBlockOffset = dataSize;
+ } else {
+ r->blockIndex = currentBlock;
+ r->blockOffset = dataOffset;
+ currentBlockOffset = dataOffset + dataSize;
}
- QDeclarativeListModelV8Data *d = (QDeclarativeListModelV8Data *)engine->listModelData();
- return d->constructor->NewInstance();
+ int roleIndex = roles.count();
+ r->index = roleIndex;
+
+ roles.append(r);
+ roleHash.insert(key, r);
+
+ return *r;
}
-QDeclarativeListModelV8Data::QDeclarativeListModelV8Data()
+ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
{
- v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
- ft->InstanceTemplate()->SetNamedPropertyHandler(Getter, Setter);
- ft->InstanceTemplate()->SetHasExternalResource(true);
- constructor = qPersistentNew<v8::Function>(ft->GetFunction());
+ for (int i=0 ; i < other->roles.count() ; ++i) {
+ Role *role = new Role(other->roles[i]);
+ roles.append(role);
+ roleHash.insert(role->name, role);
+ }
+ currentBlockOffset = other->currentBlockOffset;
+ currentBlock = other->currentBlock;
}
-QDeclarativeListModelV8Data::~QDeclarativeListModelV8Data()
+ListLayout::~ListLayout()
{
- qPersistentDispose(constructor);
+ for (int i=0 ; i < roles.count() ; ++i) {
+ delete roles[i];
+ }
}
-v8::Handle<v8::Value> QDeclarativeListModelV8Data::Getter(v8::Local<v8::String> property,
- const v8::AccessorInfo &info)
+void ListLayout::sync(ListLayout *src, ListLayout *target)
{
- QV8ListModelResource *r = v8_resource_cast<QV8ListModelResource>(info.This());
- if (!r)
- return v8::Undefined();
-
- if (!r->nodeData) // Item at this index has been deleted
- return v8::Undefined();
-
-
- int index = r->nodeData->index;
- QString propName = r->engine->toString(property);
+ int roleOffset = target->roles.count();
+ int newRoleCount = src->roles.count() - roleOffset;
- int role = r->model->m_strings.value(propName, -1);
-
- if (role >= 0 && index >=0 ) {
- const QHash<int, QVariant> &row = r->model->m_values[index];
- return r->engine->fromVariant(row[role]);
+ for (int i=0 ; i < newRoleCount ; ++i) {
+ Role *role = new Role(src->roles[roleOffset + i]);
+ target->roles.append(role);
+ target->roleHash.insert(role->name, role);
}
- return v8::Undefined();
+ target->currentBlockOffset = src->currentBlockOffset;
+ target->currentBlock = src->currentBlock;
}
-v8::Handle<v8::Value> QDeclarativeListModelV8Data::Setter(v8::Local<v8::String> property,
- v8::Local<v8::Value> value,
- const v8::AccessorInfo &info)
+ListLayout::Role::Role(const Role *other)
{
- QV8ListModelResource *r = v8_resource_cast<QV8ListModelResource>(info.This());
- if (!r)
- return v8::Undefined();
+ name = other->name;
+ type = other->type;
+ blockIndex = other->blockIndex;
+ blockOffset = other->blockOffset;
+ index = other->index;
+ if (other->subLayout)
+ subLayout = new ListLayout(other->subLayout);
+ else
+ subLayout = 0;
+}
- if (!r->nodeData) // item at this index has been deleted
- return value;
+ListLayout::Role::~Role()
+{
+ delete subLayout;
+}
+const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
+{
+ Role::DataType type;
- if (!value->IsRegExp() && !value->IsDate() && value->IsObject() && !r->engine->isVariant(value)) {
- qmlInfo(r->model->m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
- return value;
+ switch (data.type()) {
+ case QVariant::Double: type = Role::Number; break;
+ case QVariant::Int: type = Role::Number; break;
+ case QVariant::UserType: type = Role::List; break;
+ case QVariant::Bool: type = Role::Bool; break;
+ case QVariant::String: type = Role::String; break;
+ default: type = Role::Invalid; break;
}
- int index = r->nodeData->index;
- QString propName = r->engine->toString(property);
-
- int role = r->model->m_strings.value(propName, -1);
- if (role >= 0 && index >= 0) {
- QHash<int, QVariant> &row = r->model->m_values[index];
- row[role] = r->engine->toVariant(value, -1);
-
- QList<int> roles;
- roles << role;
- if (r->model->m_parentAgent) {
- // This is the list in the worker thread, so tell the agent to
- // emit itemsChanged() later
- r->model->m_parentAgent->changedData(index, 1, roles);
- } else {
- // This is the list in the main thread, so emit itemsChanged()
- emit r->model->m_listModel->itemsChanged(index, 1, roles);
- }
+ if (type == Role::Invalid) {
+ qmlInfo(0) << "Can't create role for unsupported data type";
+ return 0;
}
- return value;
+ return &getRoleOrCreate(key, type);
}
-template<typename T>
-void qdeclarativelistmodel_move(int from, int to, int n, T *items)
+const ListLayout::Role *ListLayout::getExistingRole(const QString &key)
{
- if (n == 1) {
- items->move(from, to);
- } else {
- T replaced;
- int i=0;
- typename T::ConstIterator it=items->begin(); it += from+n;
- for (; i<to-from; ++i,++it)
- replaced.append(*it);
- i=0;
- it=items->begin(); it += from;
- for (; i<n; ++i,++it)
- replaced.append(*it);
- typename T::ConstIterator f=replaced.begin();
- typename T::Iterator t=items->begin(); t += from;
- for (; f != replaced.end(); ++f, ++t)
- *t = *f;
- }
+ Role *r = 0;
+ QStringHash<Role *>::Node *node = roleHash.findNode(key);
+ if (node)
+ r = node->value;
+ return r;
}
-QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListModelData::instructions() const
+const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key)
{
- return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
+ Role *r = 0;
+ QHashedV8String hashedKey(key);
+ QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
+ if (node)
+ r = node->value;
+ return r;
}
-/*!
- \qmlclass ListModel QDeclarativeListModel
- \inqmlmodule QtQuick 2
- \ingroup qml-working-with-data
- \brief The ListModel element defines a free-form list data source.
-
- The ListModel is a simple container of ListElement definitions, each containing data roles.
- The contents can be defined dynamically, or explicitly in QML.
-
- The number of elements in the model can be obtained from its \l count property.
- A number of familiar methods are also provided to manipulate the contents of the
- model, including append(), insert(), move(), remove() and set(). These methods
- accept dictionaries as their arguments; these are translated to ListElement objects
- by the model.
-
- Elements can be manipulated via the model using the setProperty() method, which
- allows the roles of the specified element to be set and changed.
-
- \section1 Example Usage
-
- The following example shows a ListModel containing three elements, with the roles
- "name" and "cost".
+ModelObject *ListModel::getOrCreateModelObject(QDeclarativeListModel *model, int elementIndex)
+{
+ ListElement *e = elements[elementIndex];
+ if (e->m_objectCache == 0) {
+ e->m_objectCache = new ModelObject(model, elementIndex);
+ }
+ return e->m_objectCache;
+}
- \div {class="float-right"}
- \inlineimage listmodel.png
- \enddiv
+void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
+{
+ // Sanity check
+ target->m_uid = src->m_uid;
+ if (targetModelHash)
+ targetModelHash->insert(target->m_uid, target);
- \snippet doc/src/snippets/declarative/listmodel.qml 0
+ // Build hash of elements <-> uid for each of the lists
+ QHash<int, ElementSync> elementHash;
+ for (int i=0 ; i < target->elements.count() ; ++i) {
+ ListElement *e = target->elements.at(i);
+ int uid = e->getUid();
+ ElementSync sync;
+ sync.target = e;
+ elementHash.insert(uid, sync);
+ }
+ for (int i=0 ; i < src->elements.count() ; ++i) {
+ ListElement *e = src->elements.at(i);
+ int uid = e->getUid();
+
+ QHash<int, ElementSync>::iterator it = elementHash.find(uid);
+ if (it == elementHash.end()) {
+ ElementSync sync;
+ sync.src = e;
+ elementHash.insert(uid, sync);
+ } else {
+ ElementSync &sync = it.value();
+ sync.src = e;
+ }
+ }
- \clearfloat
- Roles (properties) in each element must begin with a lower-case letter and
- should be common to all elements in a model. The ListElement documentation
- provides more guidelines for how elements should be defined.
+ // Get list of elements that are in the target but no longer in the source. These get deleted first.
+ QHash<int, ElementSync>::iterator it = elementHash.begin();
+ QHash<int, ElementSync>::iterator end = elementHash.end();
+ while (it != end) {
+ const ElementSync &s = it.value();
+ if (s.src == 0) {
+ s.target->destroy(target->m_layout);
+ target->elements.removeOne(s.target);
+ delete s.target;
+ }
+ ++it;
+ }
- Since the example model contains an \c id property, it can be referenced
- by views, such as the ListView in this example:
+ // Sync the layouts
+ ListLayout::sync(src->m_layout, target->m_layout);
+
+ // Clear the target list, and append in correct order from the source
+ target->elements.clear();
+ for (int i=0 ; i < src->elements.count() ; ++i) {
+ ListElement *srcElement = src->elements.at(i);
+ it = elementHash.find(srcElement->getUid());
+ const ElementSync &s = it.value();
+ ListElement *targetElement = s.target;
+ if (targetElement == 0) {
+ targetElement = new ListElement(srcElement->getUid());
+ }
+ ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
+ target->elements.append(targetElement);
+ }
- \snippet doc/src/snippets/declarative/listmodel-simple.qml 0
- \dots 8
- \snippet doc/src/snippets/declarative/listmodel-simple.qml 1
+ target->updateCacheIndices();
- It is possible for roles to contain list data. In the following example we
- create a list of fruit attributes:
+ // Update values stored in target meta objects
+ for (int i=0 ; i < target->elements.count() ; ++i) {
+ ListElement *e = target->elements[i];
+ if (e->m_objectCache)
+ e->m_objectCache->updateValues();
+ }
+}
- \snippet doc/src/snippets/declarative/listmodel-nested.qml model
+int ListModel::allocateUid()
+{
+ return uidCounter.fetchAndAddOrdered(1);
+}
- The delegate displays all the fruit attributes:
+ListModel::ListModel(ListLayout *layout, QDeclarativeListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
+{
+ if (uid == -1)
+ uid = allocateUid();
+ m_uid = uid;
+}
- \div {class="float-right"}
- \inlineimage listmodel-nested.png
- \enddiv
+void ListModel::destroy()
+{
+ clear();
+ m_uid = -1;
+ m_layout = 0;
+ if (m_modelCache && m_modelCache->m_primary == false)
+ delete m_modelCache;
+ m_modelCache = 0;
+}
- \snippet doc/src/snippets/declarative/listmodel-nested.qml delegate
+int ListModel::appendElement()
+{
+ int elementIndex = elements.count();
+ newElement(elementIndex);
+ return elementIndex;
+}
- \clearfloat
- \section1 Modifying List Models
+void ListModel::insertElement(int index)
+{
+ newElement(index);
+ updateCacheIndices();
+}
- The content of a ListModel may be created and modified using the clear(),
- append(), set(), insert() and setProperty() methods. For example:
+void ListModel::move(int from, int to, int n)
+{
+ if (from > to) {
+ // Only move forwards - flip if backwards moving
+ int tfrom = from;
+ int tto = to;
+ from = tto;
+ to = tto+n;
+ n = tfrom-tto;
+ }
- \snippet doc/src/snippets/declarative/listmodel-modify.qml delegate
+ QPODVector<ListElement *, 4> store;
+ for (int i=0 ; i < (to-from) ; ++i)
+ store.append(elements[from+n+i]);
+ for (int i=0 ; i < n ; ++i)
+ store.append(elements[from+i]);
+ for (int i=0 ; i < store.count() ; ++i)
+ elements[from+i] = store[i];
- Note that when creating content dynamically the set of available properties
- cannot be changed once set. Whatever properties are first added to the model
- are the only permitted properties in the model.
+ updateCacheIndices();
+}
- \section1 Using Threaded List Models with WorkerScript
+void ListModel::newElement(int index)
+{
+ ListElement *e = new ListElement;
+ elements.insert(index, e);
+}
- ListModel can be used together with WorkerScript access a list model
- from multiple threads. This is useful if list modifications are
- synchronous and take some time: the list operations can be moved to a
- different thread to avoid blocking of the main GUI thread.
+void ListModel::updateCacheIndices()
+{
+ for (int i=0 ; i < elements.count() ; ++i) {
+ ListElement *e = elements.at(i);
+ if (e->m_objectCache) {
+ e->m_objectCache->m_elementIndex = i;
+ }
+ }
+}
- Here is an example that uses WorkerScript to periodically append the
- current time to a list model:
+QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QDeclarativeListModel *owner, QV8Engine *eng)
+{
+ ListElement *e = elements[elementIndex];
+ const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
+ return e->getProperty(r, owner, eng);
+}
- \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
+ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
+{
+ ListElement *e = elements[elementIndex];
+ return e->getListProperty(role);
+}
- The included file, \tt dataloader.js, looks like this:
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles)
+{
+ ListElement *e = elements[elementIndex];
- \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
+ v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
+ int propertyCount = propertyNames->Length();
- The timer in the main example sends messages to the worker script by calling
- \l WorkerScript::sendMessage(). When this message is received,
- \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
- which appends the current time to the list model.
+ for (int i=0 ; i < propertyCount ; ++i) {
+ v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
+ v8::Local<v8::Value> propertyValue = object->Get(propertyName);
- Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
- handler. You must call sync() or else the changes made to the list from the external
- thread will not be reflected in the list model in the main thread.
+ // Check if this key exists yet
+ int roleIndex = -1;
- \section1 Restrictions
+ // Add the value now
+ if (propertyValue->IsString()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
+ v8::Handle<v8::String> jsString = propertyValue->ToString();
+ QString qstr;
+ qstr.resize(jsString->Length());
+ jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+ roleIndex = e->setStringProperty(r, qstr);
+ } else if (propertyValue->IsNumber()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue());
+ } else if (propertyValue->IsArray()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
+ ListModel *subModel = new ListModel(r.subLayout, 0, -1);
- If a list model is to be accessed from a WorkerScript, it cannot
- contain list-type data. So, the following model cannot be used from a WorkerScript
- because of the list contained in the "attributes" property:
+ v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
+ int arrayLength = subArray->Length();
+ for (int j=0 ; j < arrayLength ; ++j) {
+ v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+ subModel->append(subObject);
+ }
- \code
- ListModel {
- id: fruitModel
- ListElement {
- name: "Apple"
- cost: 2.45
- attributes: [
- ListElement { description: "Core" },
- ListElement { description: "Deciduous" }
- ]
+ roleIndex = e->setListProperty(r, subModel);
+ } else if (propertyValue->IsInt32()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ roleIndex = e->setDoubleProperty(r, (double) propertyValue->Int32Value());
+ } else if (propertyValue->IsBoolean()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
+ roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue());
+ } else if (propertyValue->IsObject()) {
+ QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
+ if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
+ QObject *o = QV8QObjectWrapper::toQObject(r);
+ const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+ if (role.type == ListLayout::Role::QObject)
+ e->setQObjectProperty(role, o);
+ }
+ } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
+ const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
+ if (r)
+ e->clearProperty(*r);
}
- }
- \endcode
- In addition, the WorkerScript cannot add list-type data to the model.
+ if (roleIndex != -1)
+ roles->append(roleIndex);
+ }
- \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
-*/
+ if (e->m_objectCache) {
+ e->m_objectCache->updateValues(*roles);
+ }
+}
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object)
+{
+ ListElement *e = elements[elementIndex];
-/*
- A ListModel internally uses either a NestedListModel or FlatListModel.
+ v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
+ int propertyCount = propertyNames->Length();
- A NestedListModel can contain lists of ListElements (which
- when retrieved from get() is accessible as a list model within the list
- model) whereas a FlatListModel cannot.
+ for (int i=0 ; i < propertyCount ; ++i) {
+ v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
+ v8::Local<v8::Value> propertyValue = object->Get(propertyName);
- ListModel uses a NestedListModel to begin with, and if the model is later
- used from a WorkerScript, it changes to use a FlatListModel instead. This
- is because ModelNode (which abstracts the nested list model data) needs
- access to the declarative engine and script engine, which cannot be
- safely used from outside of the main thread.
-*/
+ // Add the value now
+ if (propertyValue->IsString()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
+ if (r.type == ListLayout::Role::String) {
+ v8::Handle<v8::String> jsString = propertyValue->ToString();
+ QString qstr;
+ qstr.resize(jsString->Length());
+ jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+ e->setStringPropertyFast(r, qstr);
+ }
+ } else if (propertyValue->IsNumber()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ if (r.type == ListLayout::Role::Number) {
+ e->setDoublePropertyFast(r, propertyValue->NumberValue());
+ }
+ } else if (propertyValue->IsArray()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
+ if (r.type == ListLayout::Role::List) {
+ ListModel *subModel = new ListModel(r.subLayout, 0, -1);
+
+ v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
+ int arrayLength = subArray->Length();
+ for (int j=0 ; j < arrayLength ; ++j) {
+ v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+ subModel->append(subObject);
+ }
-QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
-: QListModelInterface(parent), m_agent(0), m_nested(new NestedListModel(this)), m_flat(0)
-{
+ e->setListPropertyFast(r, subModel);
+ }
+ } else if (propertyValue->IsInt32()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
+ if (r.type == ListLayout::Role::Number) {
+ e->setDoublePropertyFast(r, (double) propertyValue->Int32Value());
+ }
+ } else if (propertyValue->IsBoolean()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
+ if (r.type == ListLayout::Role::Bool) {
+ e->setBoolPropertyFast(r, propertyValue->BooleanValue());
+ }
+ } else if (propertyValue->IsObject()) {
+ QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
+ if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
+ QObject *o = QV8QObjectWrapper::toQObject(r);
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+ if (r.type == ListLayout::Role::QObject)
+ e->setQObjectPropertyFast(r, o);
+ }
+ } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
+ const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
+ if (r)
+ e->clearProperty(*r);
+ }
+ }
}
-QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent)
-: QListModelInterface(parent), m_agent(0), m_nested(0), m_flat(0)
+void ListModel::clear()
{
- m_flat = new FlatListModel(this);
- m_flat->m_parentAgent = parent;
-
- if (orig->m_flat) {
- m_flat->m_roles = orig->m_flat->m_roles;
- m_flat->m_strings = orig->m_flat->m_strings;
- m_flat->m_values = orig->m_flat->m_values;
-
- m_flat->m_nodeData.reserve(m_flat->m_values.count());
- for (int i=0; i<m_flat->m_values.count(); i++)
- m_flat->m_nodeData << 0;
+ int elementCount = elements.count();
+ for (int i=0 ; i < elementCount ; ++i) {
+ elements[i]->destroy(m_layout);
+ delete elements[i];
}
+ elements.clear();
}
-QDeclarativeListModel::~QDeclarativeListModel()
+void ListModel::remove(int index)
{
- if (m_agent)
- m_agent->release();
-
- delete m_nested;
- delete m_flat;
+ elements[index]->destroy(m_layout);
+ delete elements[index];
+ elements.remove(index);
+ updateCacheIndices();
}
-bool QDeclarativeListModel::flatten()
+void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object)
{
- if (m_flat)
- return true;
+ insertElement(elementIndex);
+ set(elementIndex, object);
+}
- QList<int> roles = m_nested->roles();
+int ListModel::append(v8::Handle<v8::Object> object)
+{
+ int elementIndex = appendElement();
+ set(elementIndex, object);
+ return elementIndex;
+}
- QList<QHash<int, QVariant> > values;
- bool hasNested = false;
- for (int i=0; i<m_nested->count(); i++) {
- values.append(m_nested->data(i, roles, &hasNested));
- if (hasNested)
- return false;
- }
+int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
+{
+ int roleIndex = -1;
- FlatListModel *flat = new FlatListModel(this);
- flat->m_values = values;
+ if (elementIndex >= 0 && elementIndex < elements.count()) {
+ ListElement *e = elements[elementIndex];
+ const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
+ if (r) {
+ roleIndex = e->setVariantProperty(*r, data);
- for (int i=0; i<roles.count(); i++) {
- QString s = m_nested->toString(roles[i]);
- flat->m_roles.insert(roles[i], s);
- flat->m_strings.insert(s, roles[i]);
+ if (roleIndex != -1 && e->m_objectCache) {
+ QList<int> roles;
+ roles << roleIndex;
+ e->m_objectCache->updateValues(roles);
+ }
+ }
}
- flat->m_nodeData.reserve(flat->m_values.count());
- for (int i=0; i<flat->m_values.count(); i++)
- flat->m_nodeData << 0;
-
- m_flat = flat;
- delete m_nested;
- m_nested = 0;
- return true;
+ return roleIndex;
}
-bool QDeclarativeListModel::inWorkerThread() const
+int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data)
{
- return m_flat && m_flat->m_parentAgent;
+ int roleIndex = -1;
+
+ if (elementIndex >= 0 && elementIndex < elements.count()) {
+ ListElement *e = elements[elementIndex];
+ const ListLayout::Role *r = m_layout->getExistingRole(key);
+ if (r)
+ roleIndex = e->setJsProperty(*r, data);
+ }
+
+ return roleIndex;
}
-QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
+inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
{
- if (m_agent)
- return m_agent;
-
- if (!flatten()) {
- qmlInfo(this) << "List contains list-type data and cannot be used from a worker script";
- return 0;
+ ListElement *e = this;
+ int blockIndex = 0;
+ while (blockIndex < role.blockIndex) {
+ if (e->next == 0) {
+ e->next = new ListElement;
+ e->next->uid = uid;
+ }
+ e = e->next;
+ ++blockIndex;
}
- m_agent = new QDeclarativeListModelWorkerAgent(this);
- return m_agent;
+ char *mem = &e->data[role.blockOffset];
+ return mem;
}
-QList<int> QDeclarativeListModel::roles() const
+QString *ListElement::getStringProperty(const ListLayout::Role &role)
{
- return m_flat ? m_flat->roles() : m_nested->roles();
+ char *mem = getPropertyMemory(role);
+ QString *s = reinterpret_cast<QString *>(mem);
+ return s->data_ptr() ? s : 0;
}
-QString QDeclarativeListModel::toString(int role) const
+QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
{
- return m_flat ? m_flat->toString(role) : m_nested->toString(role);
+ char *mem = getPropertyMemory(role);
+ QDeclarativeGuard<QObject> *o = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ return o->data();
}
-QVariant QDeclarativeListModel::data(int index, int role) const
+QDeclarativeGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
{
- if (index >= count() || index < 0)
- return QVariant();
-
- return m_flat ? m_flat->data(index, role) : m_nested->data(index, role);
+ char *mem = getPropertyMemory(role);
+ QDeclarativeGuard<QObject> *o = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ return o;
}
-/*!
- \qmlproperty int QtQuick2::ListModel::count
- The number of data entries in the model.
-*/
-int QDeclarativeListModel::count() const
+ListModel *ListElement::getListProperty(const ListLayout::Role &role)
{
- return m_flat ? m_flat->count() : m_nested->count();
+ char *mem = getPropertyMemory(role);
+ ListModel **value = reinterpret_cast<ListModel **>(mem);
+ return *value;
}
-/*!
- \qmlmethod QtQuick2::ListModel::clear()
+QVariant ListElement::getProperty(const ListLayout::Role &role, const QDeclarativeListModel *owner, QV8Engine *eng)
+{
+ char *mem = getPropertyMemory(role);
- Deletes all content from the model.
+ QVariant data;
- \sa append() remove()
-*/
-void QDeclarativeListModel::clear()
-{
- int cleared = count();
- if (m_flat)
- m_flat->clear();
- else
- m_nested->clear();
+ switch (role.type) {
+ case ListLayout::Role::Number:
+ {
+ double *value = reinterpret_cast<double *>(mem);
+ data = *value;
+ }
+ break;
+ case ListLayout::Role::String:
+ {
+ QString *value = reinterpret_cast<QString *>(mem);
+ if (value->data_ptr() != 0)
+ data = *value;
+ }
+ break;
+ case ListLayout::Role::Bool:
+ {
+ bool *value = reinterpret_cast<bool *>(mem);
+ data = *value;
+ }
+ break;
+ case ListLayout::Role::List:
+ {
+ ListModel **value = reinterpret_cast<ListModel **>(mem);
+ ListModel *model = *value;
- if (!inWorkerThread()) {
- emit itemsRemoved(0, cleared);
- emit countChanged();
+ if (model) {
+ if (model->m_modelCache == 0) {
+ model->m_modelCache = new QDeclarativeListModel(owner, model, eng);
+ QDeclarativeEngine::setContextForObject(model->m_modelCache, QDeclarativeEngine::contextForObject(owner));
+ }
+
+ QObject *object = model->m_modelCache;
+ data = QVariant::fromValue(object);
+ }
+ }
+ break;
+ case ListLayout::Role::QObject:
+ {
+ QDeclarativeGuard<QObject> *guard = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ QObject *object = guard->data();
+ if (object)
+ data = QVariant::fromValue(object);
+ }
+ break;
+ default:
+ break;
}
+
+ return data;
}
-QDeclarativeListModel *ModelNode::model(const NestedListModel *model)
+int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
{
- if (!modelCache) {
- modelCache = new QDeclarativeListModel;
- QDeclarativeEngine::setContextForObject(modelCache,QDeclarativeEngine::contextForObject(model->m_listModel));
- modelCache->m_nested->_root = this; // ListModel defaults to nestable model
+ int roleIndex = -1;
- for (int i=0; i<values.count(); ++i) {
- ModelNode *subNode = qvariant_cast<ModelNode *>(values.at(i));
- if (subNode)
- subNode->m_model = modelCache->m_nested;
+ if (role.type == ListLayout::Role::String) {
+ char *mem = getPropertyMemory(role);
+ QString *c = reinterpret_cast<QString *>(mem);
+ bool changed;
+ if (c->data_ptr() == 0) {
+ new (mem) QString(s);
+ changed = true;
+ } else {
+ changed = c->compare(s) != 0;
+ *c = s;
}
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign string to role '" << role.name << "' of different type.";
}
- return modelCache;
+
+ return roleIndex;
}
-ModelObject *ModelNode::object(const NestedListModel *model)
+int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
{
- if (!objectCache) {
- QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(qmlEngine(model->m_listModel));
- objectCache = new ModelObject(this, const_cast<NestedListModel*>(model), ep->v8engine());
-
- QHash<QString, ModelNode *>::iterator it;
- for (it = properties.begin(); it != properties.end(); ++it) {
- objectCache->setValue(it.key().toUtf8(), model->valueForNode(*it));
- }
+ int roleIndex = -1;
- objectCache->setNodeUpdatesEnabled(true);
+ if (role.type == ListLayout::Role::Number) {
+ char *mem = getPropertyMemory(role);
+ double *value = new (mem) double;
+ bool changed = *value != d;
+ *value = d;
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign number to role '" << role.name << "' of different type.";
}
- return objectCache;
+
+ return roleIndex;
}
-/*!
- \qmlmethod QtQuick2::ListModel::remove(int index)
+int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
+{
+ int roleIndex = -1;
- Deletes the content at \a index from the model.
+ if (role.type == ListLayout::Role::Bool) {
+ char *mem = getPropertyMemory(role);
+ bool *value = new (mem) bool;
+ bool changed = *value != b;
+ *value = b;
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign bool to role '" << role.name << "' of different type.";
+ }
- \sa clear()
-*/
-void QDeclarativeListModel::remove(int index)
+ return roleIndex;
+}
+
+int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
{
- if (index < 0 || index >= count()) {
- qmlInfo(this) << tr("remove: index %1 out of range").arg(index);
- return;
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::List) {
+ char *mem = getPropertyMemory(role);
+ ListModel **value = new (mem) ListModel *;
+ if (*value) {
+ (*value)->destroy();
+ delete *value;
+ }
+ *value = m;
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign list to role '" << role.name << "' of different type.";
}
- if (m_flat)
- m_flat->remove(index);
- else
- m_nested->remove(index);
+ return roleIndex;
+}
- if (!inWorkerThread()) {
- emit itemsRemoved(index, 1);
- emit countChanged();
+int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::QObject) {
+ char *mem = getPropertyMemory(role);
+ QDeclarativeGuard<QObject> *g = reinterpret_cast<QDeclarativeGuard<QObject> *>(mem);
+ bool changed = g->data() != o;
+ g->~QDeclarativeGuard();
+ new (mem) QDeclarativeGuard<QObject>(o);
+ if (changed)
+ roleIndex = role.index;
+ } else {
+ qmlInfo(0) << "Unable to assign string to role '" << role.name << "' of different type.";
}
+
+ return roleIndex;
}
-/*!
- \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
+void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
+{
+ char *mem = getPropertyMemory(role);
+ new (mem) QString(s);
+}
- Adds a new item to the list model at position \a index, with the
- values in \a dict.
+void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
+{
+ char *mem = getPropertyMemory(role);
+ double *value = new (mem) double;
+ *value = d;
+}
- \code
- fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
- \endcode
+void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
+{
+ char *mem = getPropertyMemory(role);
+ bool *value = new (mem) bool;
+ *value = b;
+}
- The \a index must be to an existing item in the list, or one past
- the end of the list (equivalent to append).
+void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
+{
+ char *mem = getPropertyMemory(role);
+ new (mem) QDeclarativeGuard<QObject>(o);
+}
- \sa set() append()
-*/
-void QDeclarativeListModel::insert(int index, const QDeclarativeV8Handle &handle)
+void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
{
- v8::Handle<v8::Value> valuemap = handle.toHandle();
+ char *mem = getPropertyMemory(role);
+ ListModel **value = new (mem) ListModel *;
+ *value = m;
+}
- if (!valuemap->IsObject() || valuemap->IsArray()) {
- qmlInfo(this) << tr("insert: value is not an object");
- return;
+void ListElement::clearProperty(const ListLayout::Role &role)
+{
+ switch (role.type) {
+ case ListLayout::Role::String:
+ setStringProperty(role, QString());
+ break;
+ case ListLayout::Role::Number:
+ setDoubleProperty(role, 0.0);
+ break;
+ case ListLayout::Role::Bool:
+ setBoolProperty(role, false);
+ break;
+ case ListLayout::Role::List:
+ setListProperty(role, 0);
+ break;
+ case ListLayout::Role::QObject:
+ setQObjectProperty(role, 0);
+ break;
+ default:
+ break;
}
+}
- if (index < 0 || index > count()) {
- qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
- return;
- }
+ListElement::ListElement()
+{
+ m_objectCache = 0;
+ uid = ListModel::allocateUid();
+ next = 0;
+ qMemSet(data, 0, sizeof(data));
+}
- bool ok = m_flat ? m_flat->insert(index, valuemap) : m_nested->insert(index, valuemap);
+ListElement::ListElement(int existingUid)
+{
+ m_objectCache = 0;
+ uid = existingUid;
+ next = 0;
+ qMemSet(data, 0, sizeof(data));
+}
- if (ok && !inWorkerThread()) {
- emit itemsInserted(index, 1);
- emit countChanged();
- }
+ListElement::~ListElement()
+{
+ delete next;
}
-/*!
- \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
+void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
+{
+ for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
+ const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
+ const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
- Moves \a n items \a from one position \a to another.
+ switch (srcRole.type) {
+ case ListLayout::Role::List:
+ {
+ ListModel *srcSubModel = src->getListProperty(srcRole);
+ ListModel *targetSubModel = target->getListProperty(targetRole);
- The from and to ranges must exist; for example, to move the first 3 items
- to the end of the list:
+ if (srcSubModel) {
+ if (targetSubModel == 0) {
+ targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
+ target->setListPropertyFast(targetRole, targetSubModel);
+ }
+ ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
+ }
+ }
+ break;
+ case ListLayout::Role::QObject:
+ {
+ QObject *object = src->getQObjectProperty(srcRole);
+ target->setQObjectProperty(targetRole, object);
+ }
+ break;
+ case ListLayout::Role::String:
+ case ListLayout::Role::Number:
+ case ListLayout::Role::Bool:
+ {
+ QVariant v = src->getProperty(srcRole, 0, 0);
+ target->setVariantProperty(targetRole, v);
+ }
+ default:
+ break;
+ }
+ }
- \code
- fruitModel.move(0, fruitModel.count - 3, 3)
- \endcode
+}
- \sa append()
-*/
-void QDeclarativeListModel::move(int from, int to, int n)
+void ListElement::destroy(ListLayout *layout)
{
- if (n==0 || from==to)
- return;
- if (!canMove(from, to, n)) {
- qmlInfo(this) << tr("move: out of range");
- return;
- }
+ if (layout) {
+ for (int i=0 ; i < layout->roleCount() ; ++i) {
+ const ListLayout::Role &r = layout->getExistingRole(i);
- int origfrom = from;
- int origto = to;
- int orign = n;
- if (from > to) {
- // Only move forwards - flip if backwards moving
- int tfrom = from;
- int tto = to;
- from = tto;
- to = tto+n;
- n = tfrom-tto;
- }
+ switch (r.type) {
+ case ListLayout::Role::String:
+ {
+ QString *string = getStringProperty(r);
+ if (string)
+ string->~QString();
+ }
+ break;
+ case ListLayout::Role::List:
+ {
+ ListModel *model = getListProperty(r);
+ if (model) {
+ model->destroy();
+ delete model;
+ }
+ }
+ break;
+ case ListLayout::Role::QObject:
+ {
+ QDeclarativeGuard<QObject> *guard = getGuardProperty(r);
+ guard->~QDeclarativeGuard();
+ }
+ break;
+ default:
+ // other types don't need explicit cleanup.
+ break;
+ }
+ }
- if (m_flat)
- m_flat->move(from, to, n);
- else
- m_nested->move(from, to, n);
+ delete m_objectCache;
+ }
- if (!inWorkerThread())
- emit itemsMoved(origfrom, origto, orign);
+ if (next)
+ next->destroy(0);
+ uid = -1;
}
-/*!
- \qmlmethod QtQuick2::ListModel::append(jsobject dict)
+int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
+{
+ int roleIndex = -1;
- Adds a new item to the end of the list model, with the
- values in \a dict.
+ switch (role.type) {
+ case ListLayout::Role::Number:
+ roleIndex = setDoubleProperty(role, d.toDouble());
+ break;
+ case ListLayout::Role::String:
+ roleIndex = setStringProperty(role, d.toString());
+ break;
+ case ListLayout::Role::Bool:
+ roleIndex = setBoolProperty(role, d.toBool());
+ break;
+ case ListLayout::Role::List:
+ roleIndex = setListProperty(role, d.value<ListModel *>());
+ break;
+ default:
+ break;
+ }
- \code
- fruitModel.append({"cost": 5.95, "name":"Pizza"})
- \endcode
+ return roleIndex;
+}
+
+int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d)
+{
+ // Check if this key exists yet
+ int roleIndex = -1;
+
+ // Add the value now
+ if (d->IsString()) {
+ v8::Handle<v8::String> jsString = d->ToString();
+ QString qstr;
+ qstr.resize(jsString->Length());
+ jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
+ roleIndex = setStringProperty(role, qstr);
+ } else if (d->IsNumber()) {
+ roleIndex = setDoubleProperty(role, d->NumberValue());
+ } else if (d->IsArray()) {
+ ListModel *subModel = new ListModel(role.subLayout, 0, -1);
+ v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
+ int arrayLength = subArray->Length();
+ for (int j=0 ; j < arrayLength ; ++j) {
+ v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
+ subModel->append(subObject);
+ }
+ roleIndex = setListProperty(role, subModel);
+ } else if (d->IsInt32()) {
+ roleIndex = setDoubleProperty(role, (double) d->Int32Value());
+ } else if (d->IsBoolean()) {
+ roleIndex = setBoolProperty(role, d->BooleanValue());
+ } else if (d->IsObject()) {
+ QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
+ if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
+ QObject *o = QV8QObjectWrapper::toQObject(r);
+ roleIndex = setQObjectProperty(role, o);
+ }
+ } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
+ clearProperty(role);
+ }
- \sa set() remove()
-*/
-void QDeclarativeListModel::append(const QDeclarativeV8Handle &handle)
+ return roleIndex;
+}
+
+ModelObject::ModelObject(QDeclarativeListModel *model, int elementIndex)
+: m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
{
- v8::Handle<v8::Value> valuemap = handle.toHandle();
+ updateValues();
+ setNodeUpdatesEnabled(true);
+}
- if (!valuemap->IsObject() || valuemap->IsArray()) {
- qmlInfo(this) << tr("append: value is not an object");
- return;
+void ModelObject::updateValues()
+{
+ int roleCount = m_model->m_listModel->roleCount();
+ for (int i=0 ; i < roleCount ; ++i) {
+ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
+ QByteArray name = role.name.toUtf8();
+ const QVariant &data = m_model->data(m_elementIndex, i);
+ setValue(name, data, role.type == ListLayout::Role::List);
+ }
+}
+
+void ModelObject::updateValues(const QList<int> &roles)
+{
+ int roleCount = roles.count();
+ for (int i=0 ; i < roleCount ; ++i) {
+ int roleIndex = roles.at(i);
+ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
+ QByteArray name = role.name.toUtf8();
+ const QVariant &data = m_model->data(m_elementIndex, roleIndex);
+ setValue(name, data, role.type == ListLayout::Role::List);
}
+}
- insert(count(), handle);
+ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
+: QDeclarativeOpenMetaObject(object), m_enabled(false), m_obj(object)
+{
}
-/*!
- \qmlmethod object QtQuick2::ListModel::get(int index)
+ModelNodeMetaObject::~ModelNodeMetaObject()
+{
+}
- Returns the item at \a index in the list model. This allows the item
- data to be accessed or modified from JavaScript:
+void ModelNodeMetaObject::propertyWritten(int index)
+{
+ if (!m_enabled)
+ return;
- \code
- Component.onCompleted: {
- fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
- console.log(fruitModel.get(0).cost);
- fruitModel.get(0).cost = 10.95;
- }
- \endcode
+ QV8Engine *eng = m_obj->m_model->engine();
- The \a index must be an element in the list.
+ QString propName = QString::fromUtf8(name(index));
+ QVariant value = operator[](index);
- Note that properties of the returned object that are themselves objects
- will also be models, and this get() method is used to access elements:
+ v8::HandleScope handle_scope;
+ v8::Context::Scope scope(eng->context());
- \code
- fruitModel.append(..., "attributes":
- [{"name":"spikes","value":"7mm"},
- {"name":"color","value":"green"}]);
- fruitModel.get(0).attributes.get(1).value; // == "green"
- \endcode
+ v8::Handle<v8::Value> v = eng->fromVariant(value);
- \warning The returned object is not guaranteed to remain valid. It
- should not be used in \l{Property Binding}{property bindings}.
+ int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v);
+ if (roleIndex != -1) {
+ QList<int> roles;
+ roles << roleIndex;
+ m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
+ }
+}
- \sa append()
-*/
-QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
+QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListModelData::instructions() const
{
- // the internal flat/nested class checks for bad index
- return QDeclarativeV8Handle::fromHandle(m_flat ? m_flat->get(index) : m_nested->get(index));
+ return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
}
/*!
- \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
+ \qmlclass ListModel QDeclarativeListModel
+ \inqmlmodule QtQuick 2
+ \ingroup qml-working-with-data
+ \brief The ListModel element defines a free-form list data source.
- Changes the item at \a index in the list model with the
- values in \a dict. Properties not appearing in \a dict
- are left unchanged.
+ The ListModel is a simple container of ListElement definitions, each containing data roles.
+ The contents can be defined dynamically, or explicitly in QML.
- \code
- fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
- \endcode
+ The number of elements in the model can be obtained from its \l count property.
+ A number of familiar methods are also provided to manipulate the contents of the
+ model, including append(), insert(), move(), remove() and set(). These methods
+ accept dictionaries as their arguments; these are translated to ListElement objects
+ by the model.
- If \a index is equal to count() then a new item is appended to the
- list. Otherwise, \a index must be an element in the list.
+ Elements can be manipulated via the model using the setProperty() method, which
+ allows the roles of the specified element to be set and changed.
- \sa append()
-*/
-void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &valuemap)
-{
- QList<int> roles;
- set(index, valuemap, &roles);
- if (!roles.isEmpty() && !inWorkerThread())
- emit itemsChanged(index, 1, roles);
-}
+ \section1 Example Usage
-void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle, QList<int> *roles)
-{
- v8::Handle<v8::Value> valuemap = handle.toHandle();
+ The following example shows a ListModel containing three elements, with the roles
+ "name" and "cost".
- if (!valuemap->IsObject() || valuemap->IsArray()) {
- qmlInfo(this) << tr("set: value is not an object");
- return;
- }
- if (index > count() || index < 0) {
- qmlInfo(this) << tr("set: index %1 out of range").arg(index);
- return;
- }
+ \div {class="float-right"}
+ \inlineimage listmodel.png
+ \enddiv
- if (index == count()) {
- append(handle);
- } else {
- if (m_flat)
- m_flat->set(index, valuemap, roles);
- else
- m_nested->set(index, valuemap, roles);
- }
-}
+ \snippet doc/src/snippets/declarative/listmodel.qml 0
-/*!
- \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
+ \clearfloat
+ Roles (properties) in each element must begin with a lower-case letter and
+ should be common to all elements in a model. The ListElement documentation
+ provides more guidelines for how elements should be defined.
- Changes the \a property of the item at \a index in the list model to \a value.
+ Since the example model contains an \c id property, it can be referenced
+ by views, such as the ListView in this example:
- \code
- fruitModel.setProperty(3, "cost", 5.95)
- \endcode
+ \snippet doc/src/snippets/declarative/listmodel-simple.qml 0
+ \dots 8
+ \snippet doc/src/snippets/declarative/listmodel-simple.qml 1
- The \a index must be an element in the list.
+ It is possible for roles to contain list data. In the following example we
+ create a list of fruit attributes:
- \sa append()
-*/
-void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value)
-{
- QList<int> roles;
- setProperty(index, property, value, &roles);
- if (!roles.isEmpty() && !inWorkerThread())
- emit itemsChanged(index, 1, roles);
-}
+ \snippet doc/src/snippets/declarative/listmodel-nested.qml model
-void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
- if (count() == 0 || index >= count() || index < 0) {
- qmlInfo(this) << tr("set: index %1 out of range").arg(index);
- return;
- }
+ The delegate displays all the fruit attributes:
- if (m_flat)
- m_flat->setProperty(index, property, value, roles);
- else
- m_nested->setProperty(index, property, value, roles);
-}
+ \div {class="float-right"}
+ \inlineimage listmodel-nested.png
+ \enddiv
-/*!
- \qmlmethod QtQuick2::ListModel::sync()
+ \snippet doc/src/snippets/declarative/listmodel-nested.qml delegate
- Writes any unsaved changes to the list model after it has been modified
- from a worker script.
-*/
-void QDeclarativeListModel::sync()
-{
- // This is just a dummy method to make it look like sync() exists in
- // ListModel (and not just QDeclarativeListModelWorkerAgent) and to let
- // us document sync().
- qmlInfo(this) << "List sync() can only be called from a WorkerScript";
-}
-
-bool QDeclarativeListModelParser::compileProperty(const QDeclarativeCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
-{
- QList<QVariant> values = prop.assignedValues();
- for(int ii = 0; ii < values.count(); ++ii) {
- const QVariant &value = values.at(ii);
+ \clearfloat
+ \section1 Modifying List Models
- if(value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
- QDeclarativeCustomParserNode node =
- qvariant_cast<QDeclarativeCustomParserNode>(value);
+ The content of a ListModel may be created and modified using the clear(),
+ append(), set(), insert() and setProperty() methods. For example:
- if (node.name() != listElementTypeName) {
- const QMetaObject *mo = resolveType(node.name());
- if (mo != &QDeclarativeListElement::staticMetaObject) {
- error(node, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
- return false;
- }
- listElementTypeName = node.name(); // cache right name for next time
- }
+ \snippet doc/src/snippets/declarative/listmodel-modify.qml delegate
- {
- ListInstruction li;
- li.type = ListInstruction::Push;
- li.dataIdx = -1;
- instr << li;
- }
+ Note that when creating content dynamically the set of available properties
+ cannot be changed once set. Whatever properties are first added to the model
+ are the only permitted properties in the model.
- QList<QDeclarativeCustomParserProperty> props = node.properties();
- for(int jj = 0; jj < props.count(); ++jj) {
- const QDeclarativeCustomParserProperty &nodeProp = props.at(jj);
- if (nodeProp.name().isEmpty()) {
- error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
- return false;
- }
- if (nodeProp.name() == QStringLiteral("id")) {
- error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot use reserved \"id\" property"));
- return false;
- }
+ \section1 Using Threaded List Models with WorkerScript
- ListInstruction li;
- int ref = data.count();
- data.append(nodeProp.name().toUtf8());
- data.append('\0');
- li.type = ListInstruction::Set;
- li.dataIdx = ref;
- instr << li;
+ ListModel can be used together with WorkerScript access a list model
+ from multiple threads. This is useful if list modifications are
+ synchronous and take some time: the list operations can be moved to a
+ different thread to avoid blocking of the main GUI thread.
- if(!compileProperty(nodeProp, instr, data))
- return false;
+ Here is an example that uses WorkerScript to periodically append the
+ current time to a list model:
- li.type = ListInstruction::Pop;
- li.dataIdx = -1;
- instr << li;
- }
+ \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
- {
- ListInstruction li;
- li.type = ListInstruction::Pop;
- li.dataIdx = -1;
- instr << li;
- }
+ The included file, \tt dataloader.js, looks like this:
- } else {
+ \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
- QDeclarativeScript::Variant variant =
- qvariant_cast<QDeclarativeScript::Variant>(value);
+ The timer in the main example sends messages to the worker script by calling
+ \l WorkerScript::sendMessage(). When this message is received,
+ \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
+ which appends the current time to the list model.
- int ref = data.count();
+ Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
+ handler. You must call sync() or else the changes made to the list from the external
+ thread will not be reflected in the list model in the main thread.
- QByteArray d;
- d += char(variant.type()); // type tag
- if (variant.isString()) {
- d += variant.asString().toUtf8();
- } else if (variant.isNumber()) {
- d += QByteArray::number(variant.asNumber(),'g',20);
- } else if (variant.isBoolean()) {
- d += char(variant.asBoolean());
- } else if (variant.isScript()) {
- if (definesEmptyList(variant.asScript())) {
- d[0] = char(QDeclarativeScript::Variant::Invalid); // marks empty list
- } else {
- QByteArray script = variant.asScript().toUtf8();
- int v = evaluateEnum(script);
- if (v<0) {
- using namespace QDeclarativeJS;
- AST::Node *node = variant.asAST();
- AST::StringLiteral *literal = 0;
- if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
- if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
- if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
- if (callExpr->arguments && !callExpr->arguments->next)
- literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
- if (!literal) {
- error(prop, QDeclarativeListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
- return false;
- }
- } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
- if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
- literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
- if (!literal) {
- error(prop, QDeclarativeListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
- return false;
- }
- }
- }
- }
+ \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
+*/
- if (literal) {
- d[0] = char(QDeclarativeScript::Variant::String);
- d += literal->value.toUtf8();
- } else {
- error(prop, QDeclarativeListModel::tr("ListElement: cannot use script for property value"));
- return false;
- }
- } else {
- d[0] = char(QDeclarativeScript::Variant::Number);
- d += QByteArray::number(v);
- }
- }
- }
- d.append('\0');
- data.append(d);
+QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
+: QListModelInterface(parent)
+{
+ m_mainThread = true;
+ m_primary = true;
+ m_agent = 0;
- ListInstruction li;
- li.type = ListInstruction::Value;
- li.dataIdx = ref;
- instr << li;
- }
- }
+ m_layout = new ListLayout;
+ m_listModel = new ListModel(m_layout, this, -1);
- return true;
+ m_engine = 0;
}
-QByteArray QDeclarativeListModelParser::compile(const QList<QDeclarativeCustomParserProperty> &customProps)
+QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
+: QListModelInterface(parent)
{
- QList<ListInstruction> instr;
- QByteArray data;
- listElementTypeName = QString(); // unknown
-
- for(int ii = 0; ii < customProps.count(); ++ii) {
- const QDeclarativeCustomParserProperty &prop = customProps.at(ii);
- if(!prop.name().isEmpty()) { // isn't default property
- error(prop, QDeclarativeListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
- return QByteArray();
- }
+ m_mainThread = owner->m_mainThread;
+ m_primary = false;
+ m_agent = owner->m_agent;
- if(!compileProperty(prop, instr, data)) {
- return QByteArray();
- }
- }
-
- int size = sizeof(ListModelData) +
- instr.count() * sizeof(ListInstruction) +
- data.count();
+ m_layout = 0;
+ m_listModel = data;
- QByteArray rv;
- rv.resize(size);
-
- ListModelData *lmd = (ListModelData *)rv.data();
- lmd->dataOffset = sizeof(ListModelData) +
- instr.count() * sizeof(ListInstruction);
- lmd->instrCount = instr.count();
- for (int ii = 0; ii < instr.count(); ++ii)
- lmd->instructions()[ii] = instr.at(ii);
- ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
-
- return rv;
+ m_engine = eng;
}
-void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &d)
+QDeclarativeListModel::QDeclarativeListModel(QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *agent)
+: QListModelInterface(agent)
{
- QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
-
- ModelNode *root = new ModelNode(rv->m_nested);
- rv->m_nested->m_ownsRoot = true;
- rv->m_nested->_root = root;
- QStack<ModelNode *> nodes;
- nodes << root;
-
- bool processingSet = false;
-
- const ListModelData *lmd = (const ListModelData *)d.constData();
- const char *data = ((const char *)lmd) + lmd->dataOffset;
-
- for (int ii = 0; ii < lmd->instrCount; ++ii) {
- const ListInstruction &instr = lmd->instructions()[ii];
-
- switch(instr.type) {
- case ListInstruction::Push:
- {
- ModelNode *n = nodes.top();
- ModelNode *n2 = new ModelNode(rv->m_nested);
- n->values << QVariant::fromValue(n2);
- nodes.push(n2);
- if (processingSet)
- n->isArray = true;
- }
- break;
-
- case ListInstruction::Pop:
- nodes.pop();
- break;
-
- case ListInstruction::Value:
- {
- ModelNode *n = nodes.top();
- switch (QDeclarativeScript::Variant::Type(data[instr.dataIdx])) {
- case QDeclarativeScript::Variant::Invalid:
- n->isArray = true;
- break;
- case QDeclarativeScript::Variant::Boolean:
- n->values.append(bool(data[1 + instr.dataIdx]));
- break;
- case QDeclarativeScript::Variant::Number:
- n->values.append(QByteArray(data + 1 + instr.dataIdx).toDouble());
- break;
- case QDeclarativeScript::Variant::String:
- n->values.append(QString::fromUtf8(data + 1 + instr.dataIdx));
- break;
- default:
- Q_ASSERT("Format error in ListInstruction");
- }
-
- processingSet = false;
- }
- break;
+ m_mainThread = false;
+ m_primary = true;
+ m_agent = agent;
- case ListInstruction::Set:
- {
- ModelNode *n = nodes.top();
- ModelNode *n2 = new ModelNode(rv->m_nested);
- n->properties.insert(QString::fromUtf8(data + instr.dataIdx), n2);
- nodes.push(n2);
- processingSet = true;
- }
- break;
- }
- }
+ m_layout = new ListLayout(orig->m_layout);
+ m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
+ ListModel::sync(orig->m_listModel, m_listModel, 0);
- ModelNode *rootNode = rv->m_nested->_root;
- for (int i=0; i<rootNode->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(rootNode->values[i]);
- node->listIndex = i;
- node->updateListIndexes();
- }
+ m_engine = 0;
}
-bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
+QDeclarativeListModel::~QDeclarativeListModel()
{
- if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
- for (int i=1; i<s.length()-1; i++) {
- if (!s[i].isSpace())
- return false;
- }
- return true;
- }
- return false;
-}
+ if (m_primary) {
+ m_listModel->destroy();
+ delete m_listModel;
+ if (m_mainThread && m_agent)
+ m_agent->release();
+ }
-/*!
- \qmlclass ListElement QDeclarativeListElement
- \inqmlmodule QtQuick 2
- \ingroup qml-working-with-data
- \brief The ListElement element defines a data item in a ListModel.
-
- List elements are defined inside ListModel definitions, and represent items in a
- list that will be displayed using ListView or \l Repeater items.
-
- List elements are defined like other QML elements except that they contain
- a collection of \e role definitions instead of properties. Using the same
- syntax as property definitions, roles both define how the data is accessed
- and include the data itself.
-
- The names used for roles must begin with a lower-case letter and should be
- common to all elements in a given model. Values must be simple constants; either
- strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
- (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
-
- \section1 Referencing Roles
-
- The role names are used by delegates to obtain data from list elements.
- Each role name is accessible in the delegate's scope, and refers to the
- corresponding role in the current element. Where a role name would be
- ambiguous to use, it can be accessed via the \l{ListView::}{model}
- property (e.g., \c{model.cost} instead of \c{cost}).
-
- \section1 Example Usage
-
- The following model defines a series of list elements, each of which
- contain "name" and "cost" roles and their associated values.
-
- \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml model
-
- The delegate obtains the name and cost for each element by simply referring
- to \c name and \c cost:
-
- \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml view
+ m_listModel = 0;
- \sa ListModel
-*/
+ delete m_layout;
+ m_layout = 0;
+}
-FlatListModel::FlatListModel(QDeclarativeListModel *base)
-: m_engine(0), m_listModel(base), m_parentAgent(0)
+QV8Engine *QDeclarativeListModel::engine() const
{
+ if (m_engine == 0) {
+ m_engine = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this));
+ }
+
+ return m_engine;
}
-FlatListModel::~FlatListModel()
+void QDeclarativeListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
{
- qDeleteAll(m_nodeData);
+ if (m_mainThread) {
+ emit itemsChanged(index, count, roles);
+ } else {
+ m_agent->data.changedChange(this, index, count, roles);
+ }
}
-QVariant FlatListModel::data(int index, int role) const
+void QDeclarativeListModel::emitItemsRemoved(int index, int count)
{
- Q_ASSERT(index >= 0 && index < m_values.count());
- if (m_values[index].contains(role))
- return m_values[index][role];
- return QVariant();
+ if (m_mainThread) {
+ emit itemsRemoved(index, count);
+ emit countChanged();
+ } else {
+ if (index == 0 && count == this->count())
+ m_agent->data.clearChange(this);
+ m_agent->data.removeChange(this, index, count);
+ }
}
-QList<int> FlatListModel::roles() const
+void QDeclarativeListModel::emitItemsInserted(int index, int count)
{
- return m_roles.keys();
+ if (m_mainThread) {
+ emit itemsInserted(index, count);
+ emit countChanged();
+ } else {
+ m_agent->data.insertChange(this, index, count);
+ }
}
-QString FlatListModel::toString(int role) const
+void QDeclarativeListModel::emitItemsMoved(int from, int to, int n)
{
- if (m_roles.contains(role))
- return m_roles[role];
- return QString();
+ if (m_mainThread) {
+ emit itemsMoved(from, to, n);
+ } else {
+ m_agent->data.moveChange(this, from, n, to);
+ }
}
-int FlatListModel::count() const
+QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
{
- return m_values.count();
+ if (m_agent)
+ return m_agent;
+
+ m_agent = new QDeclarativeListModelWorkerAgent(this);
+ return m_agent;
}
-void FlatListModel::clear()
+QList<int> QDeclarativeListModel::roles() const
{
- m_values.clear();
+ QList<int> rolesArray;
+
+ for (int i=0 ; i < m_listModel->roleCount() ; ++i)
+ rolesArray << i;
- qDeleteAll(m_nodeData);
- m_nodeData.clear();
+ return rolesArray;
}
-void FlatListModel::remove(int index)
+QString QDeclarativeListModel::toString(int role) const
{
- m_values.removeAt(index);
- removedNode(index);
+ const ListLayout::Role &r = m_listModel->getExistingRole(role);
+ return r.name;
}
-bool FlatListModel::insert(int index, v8::Handle<v8::Value> value)
+QVariant QDeclarativeListModel::data(int index, int role) const
{
- Q_ASSERT(index >= 0 && index <= m_values.count());
-
- QHash<int, QVariant> row;
- if (!addValue(value, &row, 0))
- return false;
-
- m_values.insert(index, row);
- insertedNode(index);
+ if (index >= count() || index < 0)
+ return QVariant();
- return true;
+ return m_listModel->getProperty(index, role, this, engine());
}
-QV8Engine *FlatListModel::engine() const
+/*!
+ \qmlproperty int QtQuick2::ListModel::count
+ The number of data entries in the model.
+*/
+int QDeclarativeListModel::count() const
{
- return m_engine?m_engine:QDeclarativeEnginePrivate::getV8Engine(qmlEngine(m_listModel));
+ return m_listModel->elementCount();
}
-QV8Engine *NestedListModel::engine() const
-{
- return QDeclarativeEnginePrivate::getV8Engine(qmlEngine(m_listModel));
-}
+/*!
+ \qmlmethod QtQuick2::ListModel::clear()
-v8::Handle<v8::Value> FlatListModel::get(int index) const
+ Deletes all content from the model.
+
+ \sa append() remove()
+*/
+void QDeclarativeListModel::clear()
{
- QV8Engine *v8engine = engine();
+ int cleared = count();
- if (!v8engine)
- return v8::Undefined();
+ m_listModel->clear();
+ emitItemsRemoved(0, cleared);
+}
- if (index < 0 || index >= m_values.count())
- return v8::Undefined();
+/*!
+ \qmlmethod QtQuick2::ListModel::remove(int index)
- FlatListModel *that = const_cast<FlatListModel*>(this);
+ Deletes the content at \a index from the model.
- FlatNodeData *data = m_nodeData.value(index);
- if (!data) {
- data = new FlatNodeData(index);
- that->m_nodeData.replace(index, data);
+ \sa clear()
+*/
+void QDeclarativeListModel::remove(int index)
+{
+ if (index < 0 || index >= count()) {
+ qmlInfo(this) << tr("remove: index %1 out of range").arg(index);
+ return;
}
- v8::Local<v8::Object> rv = QDeclarativeListModelV8Data::create(v8engine);
- QV8ListModelResource *r = new QV8ListModelResource(that, data, v8engine);
- rv->SetExternalResource(r);
- return rv;
-}
-
-void FlatListModel::set(int index, v8::Handle<v8::Value> value, QList<int> *roles)
-{
- Q_ASSERT(index >= 0 && index < m_values.count());
+ m_listModel->remove(index);
- QHash<int, QVariant> row = m_values[index];
- if (addValue(value, &row, roles))
- m_values[index] = row;
+ emitItemsRemoved(index, 1);
}
-void FlatListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
- Q_ASSERT(index >= 0 && index < m_values.count());
+/*!
+ \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
- QHash<QString, int>::Iterator iter = m_strings.find(property);
- int role;
- if (iter == m_strings.end()) {
- role = m_roles.count();
- m_roles.insert(role, property);
- m_strings.insert(property, role);
- } else {
- role = iter.value();
- }
+ Adds a new item to the list model at position \a index, with the
+ values in \a dict.
- if (m_values[index][role] != value) {
- roles->append(role);
- m_values[index][role] = value;
- }
-}
+ \code
+ fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
+ \endcode
-void FlatListModel::move(int from, int to, int n)
-{
- qdeclarativelistmodel_move<QList<QHash<int, QVariant> > >(from, to, n, &m_values);
- moveNodes(from, to, n);
-}
+ The \a index must be to an existing item in the list, or one past
+ the end of the list (equivalent to append).
+
+ \sa set() append()
+*/
-bool FlatListModel::addValue(v8::Handle<v8::Value> value, QHash<int, QVariant> *row, QList<int> *roles)
+void QDeclarativeListModel::insert(QDeclarativeV8Function *args)
{
- if (!value->IsObject())
- return false;
+ if (args->Length() == 2) {
- v8::Local<v8::Array> properties = engine()->getOwnPropertyNames(value->ToObject());
- uint32_t length = properties->Length();
- for (uint32_t ii = 0; ii < length; ++ii) {
- // XXX TryCatch?
- v8::Handle<v8::Value> property = properties->Get(ii);
- v8::Handle<v8::Value> jsv = value->ToObject()->Get(property);
+ v8::Handle<v8::Value> arg0 = (*args)[0];
+ int index = arg0->Int32Value();
- if (!jsv->IsRegExp() && !jsv->IsDate() && jsv->IsObject() && !engine()->isVariant(jsv)) {
- qmlInfo(m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
- return false;
+ if (index < 0 || index > count()) {
+ qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
+ return;
}
- QString name = engine()->toString(property);
- QVariant v = engine()->toVariant(jsv, -1);
+ v8::Handle<v8::Value> arg1 = (*args)[1];
- QHash<QString, int>::Iterator iter = m_strings.find(name);
- if (iter == m_strings.end()) {
- int role = m_roles.count();
- m_roles.insert(role, name);
- iter = m_strings.insert(name, role);
- if (roles)
- roles->append(role);
+ if (arg1->IsArray()) {
+ v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
+ int objectArrayLength = objectArray->Length();
+ for (int i=0 ; i < objectArrayLength ; ++i) {
+ v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
+ m_listModel->insert(index+i, argObject);
+ }
+ emitItemsInserted(index, objectArrayLength);
+ } else if (arg1->IsObject()) {
+ v8::Handle<v8::Object> argObject = arg1->ToObject();
+
+ m_listModel->insert(index, argObject);
+ emitItemsInserted(index, 1);
} else {
- int role = iter.value();
- if (roles && row->contains(role) && row->value(role) != v)
- roles->append(role);
+ qmlInfo(this) << tr("insert: value is not an object");
}
- row->insert(*iter, v);
+ } else {
+ qmlInfo(this) << tr("insert: value is not an object");
}
- return true;
}
-void FlatListModel::insertedNode(int index)
-{
- if (index >= 0 && index <= m_values.count()) {
- m_nodeData.insert(index, 0);
+/*!
+ \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
- for (int i=index + 1; i<m_nodeData.count(); i++) {
- if (m_nodeData[i])
- m_nodeData[i]->index = i;
- }
+ Moves \a n items \a from one position \a to another.
+
+ The from and to ranges must exist; for example, to move the first 3 items
+ to the end of the list:
+
+ \code
+ fruitModel.move(0, fruitModel.count - 3, 3)
+ \endcode
+
+ \sa append()
+*/
+void QDeclarativeListModel::move(int from, int to, int n)
+{
+ if (n==0 || from==to)
+ return;
+ if (!canMove(from, to, n)) {
+ qmlInfo(this) << tr("move: out of range");
+ return;
}
+
+ m_listModel->move(from, to, n);
+ emitItemsMoved(from, to, n);
}
-void FlatListModel::removedNode(int index)
-{
- if (index >= 0 && index < m_nodeData.count()) {
- delete m_nodeData.takeAt(index);
+/*!
+ \qmlmethod QtQuick2::ListModel::append(jsobject dict)
+
+ Adds a new item to the end of the list model, with the
+ values in \a dict.
+
+ \code
+ fruitModel.append({"cost": 5.95, "name":"Pizza"})
+ \endcode
+
+ \sa set() remove()
+*/
+void QDeclarativeListModel::append(QDeclarativeV8Function *args)
+{
+ if (args->Length() == 1) {
+ v8::Handle<v8::Value> arg = (*args)[0];
+
+ if (arg->IsArray()) {
+ v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
+ int objectArrayLength = objectArray->Length();
+ int index = m_listModel->elementCount();
+ for (int i=0 ; i < objectArrayLength ; ++i) {
+ v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
+ m_listModel->append(argObject);
+ }
+ emitItemsInserted(index, objectArrayLength);
+ } else if (arg->IsObject()) {
+ v8::Handle<v8::Object> argObject = arg->ToObject();
- for (int i=index; i<m_nodeData.count(); i++) {
- if (m_nodeData[i])
- m_nodeData[i]->index = i;
+ int index = m_listModel->append(argObject);
+ emitItemsInserted(index, 1);
+
+ } else {
+ qmlInfo(this) << tr("append: value is not an object");
}
+ } else {
+ qmlInfo(this) << tr("append: value is not an object");
}
}
-void FlatListModel::moveNodes(int from, int to, int n)
-{
- if (!m_listModel->canMove(from, to, n))
- return;
+/*!
+ \qmlmethod object QtQuick2::ListModel::get(int index)
- qdeclarativelistmodel_move<QList<FlatNodeData *> >(from, to, n, &m_nodeData);
+ Returns the item at \a index in the list model. This allows the item
+ data to be accessed or modified from JavaScript:
- for (int i=from; i<from + (to-from); i++) {
- if (m_nodeData[i])
- m_nodeData[i]->index = i;
+ \code
+ Component.onCompleted: {
+ fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
+ console.log(fruitModel.get(0).cost);
+ fruitModel.get(0).cost = 10.95;
}
-}
+ \endcode
+
+ The \a index must be an element in the list.
+
+ Note that properties of the returned object that are themselves objects
+ will also be models, and this get() method is used to access elements:
+
+ \code
+ fruitModel.append(..., "attributes":
+ [{"name":"spikes","value":"7mm"},
+ {"name":"color","value":"green"}]);
+ fruitModel.get(0).attributes.get(1).value; // == "green"
+ \endcode
+
+ \warning The returned object is not guaranteed to remain valid. It
+ should not be used in \l{Property Binding}{property bindings}.
-FlatNodeData::~FlatNodeData()
+ \sa append()
+*/
+QDeclarativeV8Handle QDeclarativeListModel::get(int index) const
{
- for (QSet<QV8ListModelResource *>::Iterator iter = objects.begin(); iter != objects.end(); ++iter) {
- QV8ListModelResource *data = *iter;
- data->nodeData = 0;
+ v8::Handle<v8::Value> result = v8::Undefined();
+
+ if (index >= 0 && index < m_listModel->elementCount()) {
+ QV8Engine *v8engine = engine();
+
+ ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QDeclarativeListModel *>(this), index);
+ result = v8engine->newQObject(object);
}
-}
-void FlatNodeData::addData(QV8ListModelResource *data)
-{
- objects.insert(data);
+ return QDeclarativeV8Handle::fromHandle(result);
}
-void FlatNodeData::removeData(QV8ListModelResource *data)
-{
- objects.remove(data);
-}
+/*!
+ \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
-NestedListModel::NestedListModel(QDeclarativeListModel *base)
-: _root(0), m_ownsRoot(false), m_listModel(base), _rolesOk(false)
-{
-}
+ Changes the item at \a index in the list model with the
+ values in \a dict. Properties not appearing in \a dict
+ are left unchanged.
-NestedListModel::~NestedListModel()
-{
- if (m_ownsRoot)
- delete _root;
-}
+ \code
+ fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
+ \endcode
+
+ If \a index is equal to count() then a new item is appended to the
+ list. Otherwise, \a index must be an element in the list.
-QVariant NestedListModel::valueForNode(ModelNode *node, bool *hasNested) const
+ \sa append()
+*/
+void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle)
{
- QObject *rv = 0;
- if (hasNested)
- *hasNested = false;
+ v8::Handle<v8::Value> valuemap = handle.toHandle();
- if (node->isArray) {
- // List
- rv = node->model(this);
- if (hasNested)
- *hasNested = true;
- } else {
- if (!node->properties.isEmpty()) {
- // Object
- rv = node->object(this);
- } else if (node->values.count() == 0) {
- // Invalid
- return QVariant();
- } else if (node->values.count() == 1) {
- // Value
- QVariant &var = node->values[0];
- ModelNode *valueNode = qvariant_cast<ModelNode *>(var);
- if (valueNode) {
- if (!valueNode->properties.isEmpty())
- rv = valueNode->object(this);
- else
- rv = valueNode->model(this);
- } else {
- return var;
- }
- }
+ if (!valuemap->IsObject() || valuemap->IsArray()) {
+ qmlInfo(this) << tr("set: value is not an object");
+ return;
+ }
+ if (index > count() || index < 0) {
+ qmlInfo(this) << tr("set: index %1 out of range").arg(index);
+ return;
}
- if (rv) {
- return QVariant::fromValue(rv);
+ v8::Handle<v8::Object> object = valuemap->ToObject();
+
+ if (index == count()) {
+ m_listModel->insert(index, object);
+ emitItemsInserted(index, 1);
} else {
- return QVariant();
+
+ QList<int> roles;
+ m_listModel->set(index, object, &roles);
+
+ if (roles.count())
+ emitItemsChanged(index, 1, roles);
}
}
-QHash<int,QVariant> NestedListModel::data(int index, const QList<int> &roles, bool *hasNested) const
-{
- Q_ASSERT(_root && index >= 0 && index < _root->values.count());
- checkRoles();
- QHash<int, QVariant> rv;
-
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- if (!node)
- return rv;
+/*!
+ \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
- for (int ii = 0; ii < roles.count(); ++ii) {
- const QString &roleString = roleStrings.at(roles.at(ii));
+ Changes the \a property of the item at \a index in the list model to \a value.
- QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
- if (iter != node->properties.end()) {
- ModelNode *row = *iter;
- rv.insert(roles.at(ii), valueForNode(row, hasNested));
- }
- }
+ \code
+ fruitModel.setProperty(3, "cost", 5.95)
+ \endcode
- return rv;
-}
+ The \a index must be an element in the list.
-QVariant NestedListModel::data(int index, int role) const
+ \sa append()
+*/
+void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value)
{
- Q_ASSERT(_root && index >= 0 && index < _root->values.count());
- checkRoles();
- QVariant rv;
- if (roleStrings.count() < role)
- return rv;
+ if (count() == 0 || index >= count() || index < 0) {
+ qmlInfo(this) << tr("set: index %1 out of range").arg(index);
+ return;
+ }
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- if (!node)
- return rv;
+ int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
+ if (roleIndex != -1) {
- const QString &roleString = roleStrings.at(role);
+ QList<int> roles;
+ roles << roleIndex;
- QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
- if (iter != node->properties.end()) {
- ModelNode *row = *iter;
- rv = valueForNode(row);
+ emitItemsChanged(index, 1, roles);
}
-
- return rv;
}
-int NestedListModel::count() const
-{
- if (!_root) return 0;
- return _root->values.count();
-}
+/*!
+ \qmlmethod QtQuick2::ListModel::sync()
-void NestedListModel::clear()
+ Writes any unsaved changes to the list model after it has been modified
+ from a worker script.
+*/
+void QDeclarativeListModel::sync()
{
- if (_root)
- _root->clear();
+ // This is just a dummy method to make it look like sync() exists in
+ // ListModel (and not just QDeclarativeListModelWorkerAgent) and to let
+ // us document sync().
+ qmlInfo(this) << "List sync() can only be called from a WorkerScript";
}
-void NestedListModel::remove(int index)
+bool QDeclarativeListModelParser::compileProperty(const QDeclarativeCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
{
- if (!_root)
- return;
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- _root->values.removeAt(index);
- for (int i = 0; i < _root->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node)
- node->listIndex = i;
- }
- if (node)
- delete node;
-}
+ QList<QVariant> values = prop.assignedValues();
+ for(int ii = 0; ii < values.count(); ++ii) {
+ const QVariant &value = values.at(ii);
-bool NestedListModel::insert(int index, v8::Handle<v8::Value> valuemap)
-{
- if (!_root) {
- _root = new ModelNode(this);
- m_ownsRoot = true;
- }
+ if(value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
+ QDeclarativeCustomParserNode node =
+ qvariant_cast<QDeclarativeCustomParserNode>(value);
- ModelNode *mn = new ModelNode(this);
- mn->listIndex = index;
- mn->setObjectValue(valuemap);
- _root->values.insert(index,QVariant::fromValue(mn));
- for (int i = index + 1; i < _root->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node)
- node->listIndex = i;
- }
- return true;
-}
+ if (node.name() != listElementTypeName) {
+ const QMetaObject *mo = resolveType(node.name());
+ if (mo != &QDeclarativeListElement::staticMetaObject) {
+ error(node, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
+ return false;
+ }
+ listElementTypeName = node.name(); // cache right name for next time
+ }
+
+ {
+ ListInstruction li;
+ li.type = ListInstruction::Push;
+ li.dataIdx = -1;
+ instr << li;
+ }
-void NestedListModel::move(int from, int to, int n)
-{
- if (!_root)
- return;
- qdeclarativelistmodel_move<QVariantList>(from, to, n, &_root->values);
- for (int i = qMin(from, to), end = qMin(_root->values.count(), qMax(from, to) + n); i < end; ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node)
- node->listIndex = i;
- }
-}
+ QList<QDeclarativeCustomParserProperty> props = node.properties();
+ for(int jj = 0; jj < props.count(); ++jj) {
+ const QDeclarativeCustomParserProperty &nodeProp = props.at(jj);
+ if (nodeProp.name().isEmpty()) {
+ error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
+ return false;
+ }
+ if (nodeProp.name() == QStringLiteral("id")) {
+ error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot use reserved \"id\" property"));
+ return false;
+ }
-v8::Handle<v8::Value> NestedListModel::get(int index) const
-{
- QDeclarativeEngine *eng = qmlEngine(m_listModel);
- if (!eng)
- return v8::Undefined();;
+ ListInstruction li;
+ int ref = data.count();
+ data.append(nodeProp.name().toUtf8());
+ data.append('\0');
+ li.type = ListInstruction::Set;
+ li.dataIdx = ref;
+ instr << li;
- if (index < 0 || index >= count())
- return v8::Undefined();
+ if(!compileProperty(nodeProp, instr, data))
+ return false;
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- if (!node)
- return v8::Undefined();;
+ li.type = ListInstruction::Pop;
+ li.dataIdx = -1;
+ instr << li;
+ }
- return QDeclarativeEnginePrivate::get(eng)->v8engine()->newQObject(node->object(this));
-}
+ {
+ ListInstruction li;
+ li.type = ListInstruction::Pop;
+ li.dataIdx = -1;
+ instr << li;
+ }
-void NestedListModel::set(int index, v8::Handle<v8::Value> valuemap, QList<int> *roles)
-{
- Q_ASSERT(index >=0 && index < count());
+ } else {
- if (!valuemap->IsObject())
- return;
+ QDeclarativeScript::Variant variant =
+ qvariant_cast<QDeclarativeScript::Variant>(value);
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- bool emitItemsChanged = node->setObjectValue(valuemap);
- if (!emitItemsChanged)
- return;
+ int ref = data.count();
- QV8Engine *v8engine = engine();
+ QByteArray d;
+ d += char(variant.type()); // type tag
+ if (variant.isString()) {
+ d += variant.asString().toUtf8();
+ } else if (variant.isNumber()) {
+ d += QByteArray::number(variant.asNumber(),'g',20);
+ } else if (variant.isBoolean()) {
+ d += char(variant.asBoolean());
+ } else if (variant.isScript()) {
+ if (definesEmptyList(variant.asScript())) {
+ d[0] = char(QDeclarativeScript::Variant::Invalid); // marks empty list
+ } else {
+ QByteArray script = variant.asScript().toUtf8();
+ int v = evaluateEnum(script);
+ if (v<0) {
+ using namespace QDeclarativeJS;
+ AST::Node *node = variant.asAST();
+ AST::StringLiteral *literal = 0;
+ if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
+ if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
+ if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
+ if (callExpr->arguments && !callExpr->arguments->next)
+ literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
+ if (!literal) {
+ error(prop, QDeclarativeListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
+ return false;
+ }
+ } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
+ if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
+ literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
+ if (!literal) {
+ error(prop, QDeclarativeListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
+ return false;
+ }
+ }
+ }
+ }
- v8::Local<v8::Array> properties = v8engine->getOwnPropertyNames(valuemap->ToObject());
- uint32_t length = properties->Length();
- for (uint32_t ii = 0; ii < length; ++ii) {
- // XXX TryCatch?
- v8::Handle<v8::Value> property = properties->Get(ii);
- QString name = v8engine->toString(property);
+ if (literal) {
+ d[0] = char(QDeclarativeScript::Variant::String);
+ d += literal->value.toUtf8();
+ } else {
+ error(prop, QDeclarativeListModel::tr("ListElement: cannot use script for property value"));
+ return false;
+ }
+ } else {
+ d[0] = char(QDeclarativeScript::Variant::Number);
+ d += QByteArray::number(v);
+ }
+ }
+ }
+ d.append('\0');
+ data.append(d);
- int r = roleStrings.indexOf(name);
- if (r < 0) {
- r = roleStrings.count();
- roleStrings << name;
+ ListInstruction li;
+ li.type = ListInstruction::Value;
+ li.dataIdx = ref;
+ instr << li;
}
- roles->append(r);
}
-}
-
-void NestedListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
-{
- Q_ASSERT(index >=0 && index < count());
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
- bool emitItemsChanged = node->setProperty(property, value);
- if (!emitItemsChanged)
- return;
-
- int r = roleStrings.indexOf(property);
- if (r < 0) {
- r = roleStrings.count();
- roleStrings << property;
- }
- roles->append(r);
+ return true;
}
-void NestedListModel::checkRoles() const
+QByteArray QDeclarativeListModelParser::compile(const QList<QDeclarativeCustomParserProperty> &customProps)
{
- if (_rolesOk || !_root)
- return;
+ QList<ListInstruction> instr;
+ QByteArray data;
+ listElementTypeName = QString(); // unknown
- for (int i = 0; i<_root->values.count(); ++i) {
- ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
- if (node) {
- foreach (const QString &role, node->properties.keys()) {
- if (!roleStrings.contains(role))
- roleStrings.append(role);
- }
+ for(int ii = 0; ii < customProps.count(); ++ii) {
+ const QDeclarativeCustomParserProperty &prop = customProps.at(ii);
+ if(!prop.name().isEmpty()) { // isn't default property
+ error(prop, QDeclarativeListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
+ return QByteArray();
}
- }
- _rolesOk = true;
-}
+ if(!compileProperty(prop, instr, data)) {
+ return QByteArray();
+ }
+ }
-QList<int> NestedListModel::roles() const
-{
- checkRoles();
- QList<int> rv;
- for (int ii = 0; ii < roleStrings.count(); ++ii)
- rv << ii;
- return rv;
-}
+ int size = sizeof(ListModelData) +
+ instr.count() * sizeof(ListInstruction) +
+ data.count();
-QString NestedListModel::toString(int role) const
-{
- checkRoles();
- if (role < roleStrings.count())
- return roleStrings.at(role);
- else
- return QString();
-}
+ QByteArray rv;
+ rv.resize(size);
+ ListModelData *lmd = (ListModelData *)rv.data();
+ lmd->dataOffset = sizeof(ListModelData) +
+ instr.count() * sizeof(ListInstruction);
+ lmd->instrCount = instr.count();
+ for (int ii = 0; ii < instr.count(); ++ii)
+ lmd->instructions()[ii] = instr.at(ii);
+ ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
-ModelNode::ModelNode(NestedListModel *model)
-: modelCache(0), objectCache(0), isArray(false), m_model(model), listIndex(-1)
-{
+ return rv;
}
-ModelNode::~ModelNode()
+void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &d)
{
- clear();
- if (modelCache) { modelCache->m_nested->_root = 0/* ==this */; delete modelCache; modelCache = 0; }
- if (objectCache) { delete objectCache; objectCache = 0; }
-}
+ QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
-void ModelNode::clear()
-{
- ModelNode *node;
- for (int ii = 0; ii < values.count(); ++ii) {
- node = qvariant_cast<ModelNode *>(values.at(ii));
- if (node) { delete node; node = 0; }
- }
- values.clear();
+ QV8Engine *engine = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(rv));
+ rv->m_engine = engine;
- qDeleteAll(properties.values());
- properties.clear();
-}
+ const ListModelData *lmd = (const ListModelData *)d.constData();
+ const char *data = ((const char *)lmd) + lmd->dataOffset;
-bool ModelNode::setObjectValue(v8::Handle<v8::Value> valuemap, bool writeToCache)
-{
- if (!valuemap->IsObject())
- return false;
+ QStack<DataStackElement> stack;
- bool emitItemsChanged = false;
+ for (int ii = 0; ii < lmd->instrCount; ++ii) {
+ const ListInstruction &instr = lmd->instructions()[ii];
- QV8Engine *v8engine = m_model->engine();
+ switch(instr.type) {
+ case ListInstruction::Push:
+ {
+ ListModel *subModel = 0;
- v8::Local<v8::Array> propertyNames = v8engine->getOwnPropertyNames(valuemap->ToObject());
- uint32_t length = propertyNames->Length();
+ if (stack.count() == 0) {
+ subModel = rv->m_listModel;
+ } else {
+ const DataStackElement &e0 = stack.at(stack.size() - 1);
+ DataStackElement &e1 = stack[stack.size() - 2];
- for (uint32_t ii = 0; ii < length; ++ii) {
- // XXX TryCatch?
- v8::Handle<v8::Value> property = propertyNames->Get(ii);
- v8::Handle<v8::Value> v = valuemap->ToObject()->Get(property);
+ const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
+ if (role.type == ListLayout::Role::List) {
+ subModel = e1.model->getListProperty(e1.elementIndex, role);
- QString name = v8engine->toString(property);
- ModelNode *prev = properties.value(name);
- ModelNode *value = new ModelNode(m_model);
+ if (subModel == 0) {
+ subModel = new ListModel(role.subLayout, 0, -1);
+ QVariant vModel = QVariant::fromValue(subModel);
+ e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
+ }
+ }
+ }
- if (v->IsArray()) {
- value->isArray = true;
- value->setListValue(v);
- if (writeToCache && objectCache)
- objectCache->setValue(name.toUtf8(), QVariant::fromValue(value->model(m_model)));
- emitItemsChanged = true; // for now, too inefficient to check whether list and sublists have changed
- } else {
- value->values << v8engine->toVariant(v, -1);
- if (writeToCache && objectCache)
- objectCache->setValue(name.toUtf8(), value->values.last());
- if (!emitItemsChanged && prev && prev->values.count() == 1
- && prev->values[0] != value->values.last()) {
- emitItemsChanged = true;
+ DataStackElement e;
+ e.model = subModel;
+ e.elementIndex = subModel ? subModel->appendElement() : -1;
+ stack.push(e);
}
- }
- if (properties.contains(name))
- delete properties[name];
- properties.insert(name, value);
- }
- return emitItemsChanged;
-}
-
-void ModelNode::setListValue(v8::Handle<v8::Value> valuelist)
-{
- Q_ASSERT(valuelist->IsArray());
- values.clear();
+ break;
- QV8Engine *engine = m_model->engine();
+ case ListInstruction::Pop:
+ stack.pop();
+ break;
- v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(valuelist);
- uint32_t length = array->Length();
- for (uint32_t ii = 0; ii < length; ++ii) {
- ModelNode *value = new ModelNode(m_model);
+ case ListInstruction::Value:
+ {
+ const DataStackElement &e0 = stack.at(stack.size() - 1);
+ DataStackElement &e1 = stack[stack.size() - 2];
- // XXX TryCatch?
- v8::Handle<v8::Value> v = array->Get(ii);
+ QString name = e0.name;
+ QVariant value;
- if (v->IsArray()) {
- value->isArray = true;
- value->setListValue(v);
- } else if (v->IsObject()) {
- value->listIndex = ii;
- value->setObjectValue(v);
- } else {
- value->listIndex = ii;
- value->values << engine->toVariant(v, -1);
- }
+ switch (QDeclarativeScript::Variant::Type(data[instr.dataIdx])) {
+ case QDeclarativeScript::Variant::Invalid:
+ {
+ const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
+ ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
+ value = QVariant::fromValue(emptyModel);
+ }
+ break;
+ case QDeclarativeScript::Variant::Boolean:
+ value = bool(data[1 + instr.dataIdx]);
+ break;
+ case QDeclarativeScript::Variant::Number:
+ value = QByteArray(data + 1 + instr.dataIdx).toDouble();
+ break;
+ case QDeclarativeScript::Variant::String:
+ value = QString::fromUtf8(data + 1 + instr.dataIdx);
+ break;
+ default:
+ Q_ASSERT("Format error in ListInstruction");
+ }
- values.append(QVariant::fromValue(value));
- }
-}
+ e1.model->setOrCreateProperty(e1.elementIndex, name, value);
+ }
+ break;
-bool ModelNode::setProperty(const QString& prop, const QVariant& val) {
- QHash<QString, ModelNode *>::const_iterator it = properties.find(prop);
- bool emitItemsChanged = false;
- if (it != properties.end()) {
- if (val != (*it)->values[0])
- emitItemsChanged = true;
- (*it)->values[0] = val;
- } else {
- ModelNode *n = new ModelNode(m_model);
- n->values << val;
- properties.insert(prop,n);
+ case ListInstruction::Set:
+ {
+ DataStackElement e;
+ e.name = QString::fromUtf8(data + instr.dataIdx);
+ stack.push(e);
+ }
+ break;
+ }
}
- if (objectCache)
- objectCache->setValue(prop.toUtf8(), val);
- return emitItemsChanged;
}
-void ModelNode::updateListIndexes()
+bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
{
- for (QHash<QString, ModelNode *>::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) {
- ModelNode *node = iter.value();
- if (node->isArray) {
- for (int i=0; i<node->values.count(); ++i) {
- ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(i));
- if (subNode)
- subNode->listIndex = i;
- }
+ if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
+ for (int i=1; i<s.length()-1; i++) {
+ if (!s[i].isSpace())
+ return false;
}
- node->updateListIndexes();
+ return true;
}
+ return false;
}
-/*
- Need to call this to emit itemsChanged() for modifications outside of set()
- and setProperty(), i.e. if an item returned from get() is modified
-*/
-void ModelNode::changedProperty(const QString &name) const
-{
- if (listIndex < 0)
- return;
- m_model->checkRoles();
- QList<int> roles;
- int role = m_model->roleStrings.indexOf(name);
- if (role < 0)
- roles = m_model->roles();
- else
- roles << role;
- emit m_model->m_listModel->itemsChanged(listIndex, 1, roles);
-}
+/*!
+ \qmlclass ListElement QDeclarativeListElement
+ \inqmlmodule QtQuick 2
+ \ingroup qml-working-with-data
+ \brief The ListElement element defines a data item in a ListModel.
-void ModelNode::dump(ModelNode *node, int ind)
-{
- QByteArray indentBa(ind * 4, ' ');
- const char *indent = indentBa.constData();
+ List elements are defined inside ListModel definitions, and represent items in a
+ list that will be displayed using ListView or \l Repeater items.
- for (int ii = 0; ii < node->values.count(); ++ii) {
- ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii));
- if (subNode) {
- qWarning().nospace() << indent << "Sub-node " << ii;
- dump(subNode, ind + 1);
- } else {
- qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString();
- }
- }
+ List elements are defined like other QML elements except that they contain
+ a collection of \e role definitions instead of properties. Using the same
+ syntax as property definitions, roles both define how the data is accessed
+ and include the data itself.
- for (QHash<QString, ModelNode *>::ConstIterator iter = node->properties.begin(); iter != node->properties.end(); ++iter) {
- qWarning().nospace() << indent << "Property " << iter.key() << ':';
- dump(iter.value(), ind + 1);
- }
-}
+ The names used for roles must begin with a lower-case letter and should be
+ common to all elements in a given model. Values must be simple constants; either
+ strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
+ (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
-ModelObject::ModelObject(ModelNode *node, NestedListModel *model, QV8Engine *eng)
-: m_model(model), m_node(node), m_meta(new ModelNodeMetaObject(eng, this))
-{
-}
+ \section1 Referencing Roles
-void ModelObject::setValue(const QByteArray &name, const QVariant &val)
-{
- m_meta->setValue(name, val);
- //setProperty(name.constData(), val);
-}
+ The role names are used by delegates to obtain data from list elements.
+ Each role name is accessible in the delegate's scope, and refers to the
+ corresponding role in the current element. Where a role name would be
+ ambiguous to use, it can be accessed via the \l{ListView::}{model}
+ property (e.g., \c{model.cost} instead of \c{cost}).
-void ModelObject::setNodeUpdatesEnabled(bool enable)
-{
- m_meta->m_enabled = enable;
-}
+ \section1 Example Usage
-ModelNodeMetaObject::ModelNodeMetaObject(QV8Engine *eng, ModelObject *object)
-: QDeclarativeOpenMetaObject(object), m_enabled(false), m_engine(eng), m_obj(object)
-{
-}
+ The following model defines a series of list elements, each of which
+ contain "name" and "cost" roles and their associated values.
-void ModelNodeMetaObject::propertyWritten(int index)
-{
- if (!m_enabled)
- return;
+ \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml model
- QString propName = QString::fromUtf8(name(index));
- QVariant value = operator[](index);
+ The delegate obtains the name and cost for each element by simply referring
+ to \c name and \c cost:
- v8::HandleScope handle_scope;
- v8::Context::Scope scope(m_engine->context());
- v8::Local<v8::Object> object = v8::Object::New();
- object->Set(m_engine->toString(propName), m_engine->variantWrapper()->newVariant(value));
- bool changed = m_obj->m_node->setObjectValue(object, false);
- if (changed)
- m_obj->m_node->changedProperty(propName);
-}
+ \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml view
+
+ \sa ListModel
+*/
QT_END_NAMESPACE
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
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();
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();
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();
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;
}
{
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(),
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;
}
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"
" ListElement {\n"
" attributes: [\n";
componentStr += elementsStr.toUtf8().constData();
- componentStr +=
+ componentStr +=
" ]\n"
" }\n"
" }\n"
QTest::addColumn<QString>("warning");
// Simple flat model
-
QTest::newRow("count") << "count" << 0 << "";
QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
- QTest::newRow("append4b") << "{append([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 << "";
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 << "";
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 << "";
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;
// 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"));
// 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()
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);
}
/*
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();
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()
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()
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()
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
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();
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();
"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);
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);
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"