From ca3dfc52667a036901d640e4f9ed30f267153b2c Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 26 Oct 2011 10:28:01 +1000 Subject: Optimize listmodel and allow nested elements from worker script. Added support for nested listmodels when used from a worker script thread. Optimized the implementation of ListModel, especially the performance of appending a large number of items. Added a batch append mode (with an array of JS objects) to reduce the overhead of calling from JS into native code for each append operation. Task-number:QTBUG-21508 Change-Id: I07b381dc3e8200d92d6e0af458df8850d78b510f Reviewed-by: Martin Jones --- src/declarative/util/qdeclarativelistmodel.cpp | 2758 +++++++++++++----------- 1 file changed, 1468 insertions(+), 1290 deletions(-) (limited to 'src/declarative/util/qdeclarativelistmodel.cpp') diff --git a/src/declarative/util/qdeclarativelistmodel.cpp b/src/declarative/util/qdeclarativelistmodel.cpp index 4e67182048..445944043b 100644 --- a/src/declarative/util/qdeclarativelistmodel.cpp +++ b/src/declarative/util/qdeclarativelistmodel.cpp @@ -59,1692 +59,1870 @@ Q_DECLARE_METATYPE(QListModelInterface *) QT_BEGIN_NAMESPACE -QV8ListModelResource::QV8ListModelResource(FlatListModel *model, FlatNodeData *data, QV8Engine *engine) -: QV8ObjectResource(engine), model(model), nodeData(data) +// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models. +enum { MIN_LISTMODEL_UID = 1024 }; + +QAtomicInt ListModel::uidCounter(MIN_LISTMODEL_UID); + +const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type) { - if (nodeData) nodeData->addData(this); + QStringHash::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 key, Role::DataType type) { - if (nodeData) nodeData->removeData(this); + QHashedV8String hashedKey(key); + QStringHash::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(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) }; + const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(QDeclarativeListModel *), sizeof(ListModel *), sizeof(QObject *) }; - v8::Persistent constructor; + Role *r = new Role; + r->name = key; + r->type = type; - static v8::Local create(QV8Engine *); + if (type == Role::List) { + r->subLayout = new ListLayout; + } else { + r->subLayout = 0; + } - static v8::Handle Getter(v8::Local property, - const v8::AccessorInfo &info); - static v8::Handle Setter(v8::Local property, - v8::Local value, - const v8::AccessorInfo &info); -}; + int dataSize = dataSizes[type]; + int dataAlignment = dataAlignments[type]; -v8::Local 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 ft = v8::FunctionTemplate::New(); - ft->InstanceTemplate()->SetNamedPropertyHandler(Getter, Setter); - ft->InstanceTemplate()->SetHasExternalResource(true); - constructor = qPersistentNew(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 QDeclarativeListModelV8Data::Getter(v8::Local property, - const v8::AccessorInfo &info) +void ListLayout::sync(ListLayout *src, ListLayout *target) { - QV8ListModelResource *r = v8_resource_cast(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 &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 QDeclarativeListModelV8Data::Setter(v8::Local property, - v8::Local value, - const v8::AccessorInfo &info) +ListLayout::Role::Role(const Role *other) { - QV8ListModelResource *r = v8_resource_cast(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 &row = r->model->m_values[index]; - row[role] = r->engine->toVariant(value, -1); - - QList 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 -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 (; ibegin(); it += from; - for (; ibegin(); t += from; - for (; f != replaced.end(); ++f, ++t) - *t = *f; - } + Role *r = 0; + QStringHash::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 key) { - return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData)); + Role *r = 0; + QHashedV8String hashedKey(key); + QStringHash::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 *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 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::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::iterator it = elementHash.begin(); + QHash::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 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 object, QList *roles) +{ + ListElement *e = elements[elementIndex]; - \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0 + v8::Local 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 propertyName = propertyNames->Get(i)->ToString(); + v8::Local 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 jsString = propertyValue->ToString(); + QString qstr; + qstr.resize(jsString->Length()); + jsString->Write(reinterpret_cast(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 subArray = v8::Handle::Cast(propertyValue); + int arrayLength = subArray->Length(); + for (int j=0 ; j < arrayLength ; ++j) { + v8::Handle 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 object) +{ + ListElement *e = elements[elementIndex]; -/* - A ListModel internally uses either a NestedListModel or FlatListModel. + v8::Local 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 propertyName = propertyNames->Get(i)->ToString(); + v8::Local 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 jsString = propertyValue->ToString(); + QString qstr; + qstr.resize(jsString->Length()); + jsString->Write(reinterpret_cast(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 subArray = v8::Handle::Cast(propertyValue); + int arrayLength = subArray->Length(); + for (int j=0 ; j < arrayLength ; ++j) { + v8::Handle 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; im_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 object) { - if (m_flat) - return true; + insertElement(elementIndex); + set(elementIndex, object); +} - QList roles = m_nested->roles(); +int ListModel::append(v8::Handle object) +{ + int elementIndex = appendElement(); + set(elementIndex, object); + return elementIndex; +} - QList > values; - bool hasNested = false; - for (int i=0; icount(); 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; itoString(roles[i]); - flat->m_roles.insert(roles[i], s); - flat->m_strings.insert(s, roles[i]); + if (roleIndex != -1 && e->m_objectCache) { + QList roles; + roles << roleIndex; + e->m_objectCache->updateValues(roles); + } + } } - flat->m_nodeData.reserve(flat->m_values.count()); - for (int i=0; im_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 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 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(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 *o = reinterpret_cast *>(mem); + return o->data(); } -QVariant QDeclarativeListModel::data(int index, int role) const +QDeclarativeGuard *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 *o = reinterpret_cast *>(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(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(mem); + data = *value; + } + break; + case ListLayout::Role::String: + { + QString *value = reinterpret_cast(mem); + if (value->data_ptr() != 0) + data = *value; + } + break; + case ListLayout::Role::Bool: + { + bool *value = reinterpret_cast(mem); + data = *value; + } + break; + case ListLayout::Role::List: + { + ListModel **value = reinterpret_cast(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 *guard = reinterpret_cast *>(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.at(i)); - if (subNode) - subNode->m_model = modelCache->m_nested; + if (role.type == ListLayout::Role::String) { + char *mem = getPropertyMemory(role); + QString *c = reinterpret_cast(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(model), ep->v8engine()); - - QHash::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 *g = reinterpret_cast *>(mem); + bool changed = g->data() != o; + g->~QDeclarativeGuard(); + new (mem) QDeclarativeGuard(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(o); +} - \sa set() append() -*/ -void QDeclarativeListModel::insert(int index, const QDeclarativeV8Handle &handle) +void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m) { - v8::Handle 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 *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 *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()); + break; + default: + break; + } - \code - fruitModel.append({"cost": 5.95, "name":"Pizza"}) - \endcode + return roleIndex; +} + +int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle d) +{ + // Check if this key exists yet + int roleIndex = -1; + + // Add the value now + if (d->IsString()) { + v8::Handle jsString = d->ToString(); + QString qstr; + qstr.resize(jsString->Length()); + jsString->Write(reinterpret_cast(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 subArray = v8::Handle::Cast(d); + int arrayLength = subArray->Length(); + for (int j=0 ; j < arrayLength ; ++j) { + v8::Handle 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 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 &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 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 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 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 *roles) -{ - v8::Handle 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 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 *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 &instr, QByteArray &data) -{ - QList 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 node = - qvariant_cast(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 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(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(node)) { - if (AST::IdentifierExpression *idExpr = AST::cast(callExpr->base)) { - if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) { - if (callExpr->arguments && !callExpr->arguments->next) - literal = AST::cast(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(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 &customProps) +QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent) +: QListModelInterface(parent) { - QList 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(obj); - - ModelNode *root = new ModelNode(rv->m_nested); - rv->m_nested->m_ownsRoot = true; - rv->m_nested->_root = root; - QStack 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; ivalues.count(); ++i) { - ModelNode *node = qvariant_cast(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; idestroy(); + 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 &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 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 QDeclarativeListModel::roles() const { - m_values.clear(); + QList 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 value) +QVariant QDeclarativeListModel::data(int index, int role) const { - Q_ASSERT(index >= 0 && index <= m_values.count()); - - QHash 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 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(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 rv = QDeclarativeListModelV8Data::create(v8engine); - QV8ListModelResource *r = new QV8ListModelResource(that, data, v8engine); - rv->SetExternalResource(r); - return rv; -} - -void FlatListModel::set(int index, v8::Handle value, QList *roles) -{ - Q_ASSERT(index >= 0 && index < m_values.count()); + m_listModel->remove(index); - QHash 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 *roles) -{ - Q_ASSERT(index >= 0 && index < m_values.count()); +/*! + \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict) - QHash::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 > >(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 value, QHash *row, QList *roles) +void QDeclarativeListModel::insert(QDeclarativeV8Function *args) { - if (!value->IsObject()) - return false; + if (args->Length() == 2) { - v8::Local properties = engine()->getOwnPropertyNames(value->ToObject()); - uint32_t length = properties->Length(); - for (uint32_t ii = 0; ii < length; ++ii) { - // XXX TryCatch? - v8::Handle property = properties->Get(ii); - v8::Handle jsv = value->ToObject()->Get(property); + v8::Handle 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 arg1 = (*args)[1]; - QHash::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 objectArray = v8::Handle::Cast(arg1); + int objectArrayLength = objectArray->Length(); + for (int i=0 ; i < objectArrayLength ; ++i) { + v8::Handle argObject = objectArray->Get(i)->ToObject(); + m_listModel->insert(index+i, argObject); + } + emitItemsInserted(index, objectArrayLength); + } else if (arg1->IsObject()) { + v8::Handle 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; iindex = 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 arg = (*args)[0]; + + if (arg->IsArray()) { + v8::Handle objectArray = v8::Handle::Cast(arg); + int objectArrayLength = objectArray->Length(); + int index = m_listModel->elementCount(); + for (int i=0 ; i < objectArrayLength ; ++i) { + v8::Handle argObject = objectArray->Get(i)->ToObject(); + m_listModel->append(argObject); + } + emitItemsInserted(index, objectArrayLength); + } else if (arg->IsObject()) { + v8::Handle argObject = arg->ToObject(); - for (int i=index; iindex = 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 >(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; iindex = 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::Iterator iter = objects.begin(); iter != objects.end(); ++iter) { - QV8ListModelResource *data = *iter; - data->nodeData = 0; + v8::Handle result = v8::Undefined(); + + if (index >= 0 && index < m_listModel->elementCount()) { + QV8Engine *v8engine = engine(); + + ModelObject *object = m_listModel->getOrCreateModelObject(const_cast(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 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(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 object = valuemap->ToObject(); + + if (index == count()) { + m_listModel->insert(index, object); + emitItemsInserted(index, 1); } else { - return QVariant(); + + QList roles; + m_listModel->set(index, object, &roles); + + if (roles.count()) + emitItemsChanged(index, 1, roles); } } -QHash NestedListModel::data(int index, const QList &roles, bool *hasNested) const -{ - Q_ASSERT(_root && index >= 0 && index < _root->values.count()); - checkRoles(); - QHash rv; - - ModelNode *node = qvariant_cast(_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::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(_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 roles; + roles << roleIndex; - QHash::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 &instr, QByteArray &data) { - if (!_root) - return; - ModelNode *node = qvariant_cast(_root->values.at(index)); - _root->values.removeAt(index); - for (int i = 0; i < _root->values.count(); ++i) { - ModelNode *node = qvariant_cast(_root->values.at(i)); - if (node) - node->listIndex = i; - } - if (node) - delete node; -} + QList values = prop.assignedValues(); + for(int ii = 0; ii < values.count(); ++ii) { + const QVariant &value = values.at(ii); -bool NestedListModel::insert(int index, v8::Handle valuemap) -{ - if (!_root) { - _root = new ModelNode(this); - m_ownsRoot = true; - } + if(value.userType() == qMetaTypeId()) { + QDeclarativeCustomParserNode node = + qvariant_cast(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(_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(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(_root->values.at(i)); - if (node) - node->listIndex = i; - } -} + QList 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 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(_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 valuemap, QList *roles) -{ - Q_ASSERT(index >=0 && index < count()); + } else { - if (!valuemap->IsObject()) - return; + QDeclarativeScript::Variant variant = + qvariant_cast(value); - ModelNode *node = qvariant_cast(_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(node)) { + if (AST::IdentifierExpression *idExpr = AST::cast(callExpr->base)) { + if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) { + if (callExpr->arguments && !callExpr->arguments->next) + literal = AST::cast(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(callExpr->arguments->next->expression); + if (!literal) { + error(prop, QDeclarativeListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP")); + return false; + } + } + } + } - v8::Local properties = v8engine->getOwnPropertyNames(valuemap->ToObject()); - uint32_t length = properties->Length(); - for (uint32_t ii = 0; ii < length; ++ii) { - // XXX TryCatch? - v8::Handle 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 *roles) -{ - Q_ASSERT(index >=0 && index < count()); - ModelNode *node = qvariant_cast(_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 &customProps) { - if (_rolesOk || !_root) - return; + QList instr; + QByteArray data; + listElementTypeName = QString(); // unknown - for (int i = 0; i<_root->values.count(); ++i) { - ModelNode *node = qvariant_cast(_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 NestedListModel::roles() const -{ - checkRoles(); - QList 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(obj); -void ModelNode::clear() -{ - ModelNode *node; - for (int ii = 0; ii < values.count(); ++ii) { - node = qvariant_cast(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 valuemap, bool writeToCache) -{ - if (!valuemap->IsObject()) - return false; + QStack 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 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 property = propertyNames->Get(ii); - v8::Handle 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 valuelist) -{ - Q_ASSERT(valuelist->IsArray()); - values.clear(); + break; - QV8Engine *engine = m_model->engine(); + case ListInstruction::Pop: + stack.pop(); + break; - v8::Handle array = v8::Handle::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 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::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::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) { - ModelNode *node = iter.value(); - if (node->isArray) { - for (int i=0; ivalues.count(); ++i) { - ModelNode *subNode = qvariant_cast(node->values.at(i)); - if (subNode) - subNode->listIndex = i; - } + if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) { + for (int i=1; iupdateListIndexes(); + 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 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(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::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 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 -- cgit v1.2.3