1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquicklistmodel_p_p.h"
43 #include "qquicklistmodelworkeragent_p.h"
44 #include "qqmlopenmetaobject_p.h"
45 #include <private/qqmljsast_p.h>
46 #include <private/qqmljsengine_p.h>
48 #include <private/qqmlcustomparser_p.h>
49 #include <private/qqmlscript_p.h>
50 #include <private/qqmlengine_p.h>
51 #include <qqmlcontext.h>
54 #include <QtCore/qdebug.h>
55 #include <QtCore/qstack.h>
56 #include <QXmlStreamReader>
58 Q_DECLARE_METATYPE(QListModelInterface *)
62 // Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
63 enum { MIN_LISTMODEL_UID = 1024 };
65 static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
68 static bool isMemoryUsed(const char *mem)
70 for (size_t i=0 ; i < sizeof(T) ; ++i) {
78 static QString roleTypeName(ListLayout::Role::DataType t)
81 const char *roleTypeNames[] = { "String", "Number", "Bool", "List", "QObject", "VariantMap" };
83 if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
84 result = QString::fromLatin1(roleTypeNames[t]);
89 const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
91 QStringHash<Role *>::Node *node = roleHash.findNode(key);
93 const Role &r = *node->value;
95 qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
99 return createRole(key, type);
102 const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type)
104 QHashedV8String hashedKey(key);
105 QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
107 const Role &r = *node->value;
109 qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
114 qkey.resize(key->Length());
115 key->Write(reinterpret_cast<uint16_t*>(qkey.data()));
117 return createRole(qkey, type);
120 const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
122 const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QQmlGuard<QObject>), sizeof(QVariantMap) };
123 const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap) };
129 if (type == Role::List) {
130 r->subLayout = new ListLayout;
135 int dataSize = dataSizes[type];
136 int dataAlignment = dataAlignments[type];
138 int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
139 if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
140 r->blockIndex = ++currentBlock;
142 currentBlockOffset = dataSize;
144 r->blockIndex = currentBlock;
145 r->blockOffset = dataOffset;
146 currentBlockOffset = dataOffset + dataSize;
149 int roleIndex = roles.count();
150 r->index = roleIndex;
153 roleHash.insert(key, r);
158 ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
160 for (int i=0 ; i < other->roles.count() ; ++i) {
161 Role *role = new Role(other->roles[i]);
163 roleHash.insert(role->name, role);
165 currentBlockOffset = other->currentBlockOffset;
166 currentBlock = other->currentBlock;
169 ListLayout::~ListLayout()
171 for (int i=0 ; i < roles.count() ; ++i) {
176 void ListLayout::sync(ListLayout *src, ListLayout *target)
178 int roleOffset = target->roles.count();
179 int newRoleCount = src->roles.count() - roleOffset;
181 for (int i=0 ; i < newRoleCount ; ++i) {
182 Role *role = new Role(src->roles[roleOffset + i]);
183 target->roles.append(role);
184 target->roleHash.insert(role->name, role);
187 target->currentBlockOffset = src->currentBlockOffset;
188 target->currentBlock = src->currentBlock;
191 ListLayout::Role::Role(const Role *other)
195 blockIndex = other->blockIndex;
196 blockOffset = other->blockOffset;
197 index = other->index;
198 if (other->subLayout)
199 subLayout = new ListLayout(other->subLayout);
204 ListLayout::Role::~Role()
209 const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
213 switch (data.type()) {
214 case QVariant::Double: type = Role::Number; break;
215 case QVariant::Int: type = Role::Number; break;
216 case QVariant::UserType: type = Role::List; break;
217 case QVariant::Bool: type = Role::Bool; break;
218 case QVariant::String: type = Role::String; break;
219 case QVariant::Map: type = Role::VariantMap; break;
220 default: type = Role::Invalid; break;
223 if (type == Role::Invalid) {
224 qmlInfo(0) << "Can't create role for unsupported data type";
228 return &getRoleOrCreate(key, type);
231 const ListLayout::Role *ListLayout::getExistingRole(const QString &key)
234 QStringHash<Role *>::Node *node = roleHash.findNode(key);
240 const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key)
243 QHashedV8String hashedKey(key);
244 QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
250 ModelObject *ListModel::getOrCreateModelObject(QQuickListModel *model, int elementIndex)
252 ListElement *e = elements[elementIndex];
253 if (e->m_objectCache == 0) {
254 e->m_objectCache = new ModelObject(model, elementIndex);
256 return e->m_objectCache;
259 void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
262 target->m_uid = src->m_uid;
264 targetModelHash->insert(target->m_uid, target);
266 // Build hash of elements <-> uid for each of the lists
267 QHash<int, ElementSync> elementHash;
268 for (int i=0 ; i < target->elements.count() ; ++i) {
269 ListElement *e = target->elements.at(i);
270 int uid = e->getUid();
273 elementHash.insert(uid, sync);
275 for (int i=0 ; i < src->elements.count() ; ++i) {
276 ListElement *e = src->elements.at(i);
277 int uid = e->getUid();
279 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
280 if (it == elementHash.end()) {
283 elementHash.insert(uid, sync);
285 ElementSync &sync = it.value();
290 // Get list of elements that are in the target but no longer in the source. These get deleted first.
291 QHash<int, ElementSync>::iterator it = elementHash.begin();
292 QHash<int, ElementSync>::iterator end = elementHash.end();
294 const ElementSync &s = it.value();
296 s.target->destroy(target->m_layout);
297 target->elements.removeOne(s.target);
304 ListLayout::sync(src->m_layout, target->m_layout);
306 // Clear the target list, and append in correct order from the source
307 target->elements.clear();
308 for (int i=0 ; i < src->elements.count() ; ++i) {
309 ListElement *srcElement = src->elements.at(i);
310 it = elementHash.find(srcElement->getUid());
311 const ElementSync &s = it.value();
312 ListElement *targetElement = s.target;
313 if (targetElement == 0) {
314 targetElement = new ListElement(srcElement->getUid());
316 ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
317 target->elements.append(targetElement);
320 target->updateCacheIndices();
322 // Update values stored in target meta objects
323 for (int i=0 ; i < target->elements.count() ; ++i) {
324 ListElement *e = target->elements[i];
325 if (e->m_objectCache)
326 e->m_objectCache->updateValues();
330 ListModel::ListModel(ListLayout *layout, QQuickListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
333 uid = uidCounter.fetchAndAddOrdered(1);
337 void ListModel::destroy()
342 if (m_modelCache && m_modelCache->m_primary == false)
347 int ListModel::appendElement()
349 int elementIndex = elements.count();
350 newElement(elementIndex);
354 void ListModel::insertElement(int index)
357 updateCacheIndices();
360 void ListModel::move(int from, int to, int n)
363 // Only move forwards - flip if backwards moving
371 QPODVector<ListElement *, 4> store;
372 for (int i=0 ; i < (to-from) ; ++i)
373 store.append(elements[from+n+i]);
374 for (int i=0 ; i < n ; ++i)
375 store.append(elements[from+i]);
376 for (int i=0 ; i < store.count() ; ++i)
377 elements[from+i] = store[i];
379 updateCacheIndices();
382 void ListModel::newElement(int index)
384 ListElement *e = new ListElement;
385 elements.insert(index, e);
388 void ListModel::updateCacheIndices()
390 for (int i=0 ; i < elements.count() ; ++i) {
391 ListElement *e = elements.at(i);
392 if (e->m_objectCache) {
393 e->m_objectCache->m_elementIndex = i;
398 QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQuickListModel *owner, QV8Engine *eng)
400 ListElement *e = elements[elementIndex];
401 const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
402 return e->getProperty(r, owner, eng);
405 ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
407 ListElement *e = elements[elementIndex];
408 return e->getListProperty(role);
411 void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles, QV8Engine *eng)
413 ListElement *e = elements[elementIndex];
415 v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
416 int propertyCount = propertyNames->Length();
418 for (int i=0 ; i < propertyCount ; ++i) {
419 v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
420 v8::Local<v8::Value> propertyValue = object->Get(propertyName);
422 // Check if this key exists yet
426 if (propertyValue->IsString()) {
427 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
428 v8::Handle<v8::String> jsString = propertyValue->ToString();
430 qstr.resize(jsString->Length());
431 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
432 roleIndex = e->setStringProperty(r, qstr);
433 } else if (propertyValue->IsNumber()) {
434 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
435 roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue());
436 } else if (propertyValue->IsArray()) {
437 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
438 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
440 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
441 int arrayLength = subArray->Length();
442 for (int j=0 ; j < arrayLength ; ++j) {
443 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
444 subModel->append(subObject, eng);
447 roleIndex = e->setListProperty(r, subModel);
448 } else if (propertyValue->IsBoolean()) {
449 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
450 roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue());
451 } else if (propertyValue->IsObject()) {
452 QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
453 if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
454 QObject *o = QV8QObjectWrapper::toQObject(r);
455 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
456 if (role.type == ListLayout::Role::QObject)
457 roleIndex = e->setQObjectProperty(role, o);
459 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
460 if (role.type == ListLayout::Role::VariantMap)
461 roleIndex = e->setVariantMapProperty(role, propertyValue->ToObject(), eng);
463 } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
464 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
466 e->clearProperty(*r);
470 roles->append(roleIndex);
473 if (e->m_objectCache) {
474 e->m_objectCache->updateValues(*roles);
478 void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
480 ListElement *e = elements[elementIndex];
482 v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
483 int propertyCount = propertyNames->Length();
485 for (int i=0 ; i < propertyCount ; ++i) {
486 v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
487 v8::Local<v8::Value> propertyValue = object->Get(propertyName);
490 if (propertyValue->IsString()) {
491 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
492 if (r.type == ListLayout::Role::String) {
493 v8::Handle<v8::String> jsString = propertyValue->ToString();
495 qstr.resize(jsString->Length());
496 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
497 e->setStringPropertyFast(r, qstr);
499 } else if (propertyValue->IsNumber()) {
500 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
501 if (r.type == ListLayout::Role::Number) {
502 e->setDoublePropertyFast(r, propertyValue->NumberValue());
504 } else if (propertyValue->IsArray()) {
505 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
506 if (r.type == ListLayout::Role::List) {
507 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
509 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
510 int arrayLength = subArray->Length();
511 for (int j=0 ; j < arrayLength ; ++j) {
512 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
513 subModel->append(subObject, eng);
516 e->setListPropertyFast(r, subModel);
518 } else if (propertyValue->IsBoolean()) {
519 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
520 if (r.type == ListLayout::Role::Bool) {
521 e->setBoolPropertyFast(r, propertyValue->BooleanValue());
523 } else if (propertyValue->IsObject()) {
524 QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
525 if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
526 QObject *o = QV8QObjectWrapper::toQObject(r);
527 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
528 if (r.type == ListLayout::Role::QObject)
529 e->setQObjectPropertyFast(r, o);
531 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
532 if (role.type == ListLayout::Role::VariantMap)
533 e->setVariantMapFast(role, propertyValue->ToObject(), eng);
535 } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
536 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
538 e->clearProperty(*r);
543 void ListModel::clear()
545 int elementCount = elements.count();
546 for (int i=0 ; i < elementCount ; ++i) {
547 elements[i]->destroy(m_layout);
553 void ListModel::remove(int index, int count)
555 for (int i=0 ; i < count ; ++i) {
556 elements[index+i]->destroy(m_layout);
557 delete elements[index+i];
559 elements.remove(index, count);
560 updateCacheIndices();
563 void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
565 insertElement(elementIndex);
566 set(elementIndex, object, eng);
569 int ListModel::append(v8::Handle<v8::Object> object, QV8Engine *eng)
571 int elementIndex = appendElement();
572 set(elementIndex, object, eng);
576 int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
580 if (elementIndex >= 0 && elementIndex < elements.count()) {
581 ListElement *e = elements[elementIndex];
583 const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
585 roleIndex = e->setVariantProperty(*r, data);
587 if (roleIndex != -1 && e->m_objectCache) {
590 e->m_objectCache->updateValues(roles);
598 int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng)
602 if (elementIndex >= 0 && elementIndex < elements.count()) {
603 ListElement *e = elements[elementIndex];
604 const ListLayout::Role *r = m_layout->getExistingRole(key);
606 roleIndex = e->setJsProperty(*r, data, eng);
612 inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
614 ListElement *e = this;
616 while (blockIndex < role.blockIndex) {
618 e->next = new ListElement;
625 char *mem = &e->data[role.blockOffset];
629 QString *ListElement::getStringProperty(const ListLayout::Role &role)
631 char *mem = getPropertyMemory(role);
632 QString *s = reinterpret_cast<QString *>(mem);
633 return s->data_ptr() ? s : 0;
636 QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
638 char *mem = getPropertyMemory(role);
639 QQmlGuard<QObject> *o = reinterpret_cast<QQmlGuard<QObject> *>(mem);
643 QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
645 QVariantMap *map = 0;
647 char *mem = getPropertyMemory(role);
648 if (isMemoryUsed<QVariantMap>(mem))
649 map = reinterpret_cast<QVariantMap *>(mem);
654 QQmlGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
656 char *mem = getPropertyMemory(role);
658 bool existingGuard = false;
659 for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) {
661 existingGuard = true;
666 QQmlGuard<QObject> *o = 0;
669 o = reinterpret_cast<QQmlGuard<QObject> *>(mem);
674 ListModel *ListElement::getListProperty(const ListLayout::Role &role)
676 char *mem = getPropertyMemory(role);
677 ListModel **value = reinterpret_cast<ListModel **>(mem);
681 QVariant ListElement::getProperty(const ListLayout::Role &role, const QQuickListModel *owner, QV8Engine *eng)
683 char *mem = getPropertyMemory(role);
688 case ListLayout::Role::Number:
690 double *value = reinterpret_cast<double *>(mem);
694 case ListLayout::Role::String:
696 QString *value = reinterpret_cast<QString *>(mem);
697 if (value->data_ptr() != 0)
701 case ListLayout::Role::Bool:
703 bool *value = reinterpret_cast<bool *>(mem);
707 case ListLayout::Role::List:
709 ListModel **value = reinterpret_cast<ListModel **>(mem);
710 ListModel *model = *value;
713 if (model->m_modelCache == 0) {
714 model->m_modelCache = new QQuickListModel(owner, model, eng);
715 QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
718 QObject *object = model->m_modelCache;
719 data = QVariant::fromValue(object);
723 case ListLayout::Role::QObject:
725 QQmlGuard<QObject> *guard = reinterpret_cast<QQmlGuard<QObject> *>(mem);
726 QObject *object = guard->data();
728 data = QVariant::fromValue(object);
731 case ListLayout::Role::VariantMap:
733 if (isMemoryUsed<QVariantMap>(mem)) {
734 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
746 int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
750 if (role.type == ListLayout::Role::String) {
751 char *mem = getPropertyMemory(role);
752 QString *c = reinterpret_cast<QString *>(mem);
754 if (c->data_ptr() == 0) {
755 new (mem) QString(s);
758 changed = c->compare(s) != 0;
762 roleIndex = role.index;
768 int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
772 if (role.type == ListLayout::Role::Number) {
773 char *mem = getPropertyMemory(role);
774 double *value = new (mem) double;
775 bool changed = *value != d;
778 roleIndex = role.index;
784 int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
788 if (role.type == ListLayout::Role::Bool) {
789 char *mem = getPropertyMemory(role);
790 bool *value = new (mem) bool;
791 bool changed = *value != b;
794 roleIndex = role.index;
800 int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
804 if (role.type == ListLayout::Role::List) {
805 char *mem = getPropertyMemory(role);
806 ListModel **value = new (mem) ListModel *;
812 roleIndex = role.index;
818 int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
822 if (role.type == ListLayout::Role::QObject) {
823 char *mem = getPropertyMemory(role);
824 QQmlGuard<QObject> *g = reinterpret_cast<QQmlGuard<QObject> *>(mem);
825 bool existingGuard = false;
826 for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) {
828 existingGuard = true;
834 changed = g->data() != o;
839 new (mem) QQmlGuard<QObject>(o);
841 roleIndex = role.index;
847 int ListElement::setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
851 if (role.type == ListLayout::Role::VariantMap) {
852 char *mem = getPropertyMemory(role);
853 if (isMemoryUsed<QVariantMap>(mem)) {
854 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
857 new (mem) QVariantMap(eng->variantMapFromJS(o));
858 roleIndex = role.index;
864 int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
868 if (role.type == ListLayout::Role::VariantMap) {
869 char *mem = getPropertyMemory(role);
870 if (isMemoryUsed<QVariantMap>(mem)) {
871 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
875 new (mem) QVariantMap(*m);
877 new (mem) QVariantMap;
878 roleIndex = role.index;
884 void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
886 char *mem = getPropertyMemory(role);
887 new (mem) QString(s);
890 void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
892 char *mem = getPropertyMemory(role);
893 double *value = new (mem) double;
897 void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
899 char *mem = getPropertyMemory(role);
900 bool *value = new (mem) bool;
904 void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
906 char *mem = getPropertyMemory(role);
907 new (mem) QQmlGuard<QObject>(o);
910 void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
912 char *mem = getPropertyMemory(role);
913 ListModel **value = new (mem) ListModel *;
917 void ListElement::setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
919 char *mem = getPropertyMemory(role);
920 QVariantMap *map = new (mem) QVariantMap;
921 *map = eng->variantMapFromJS(o);
924 void ListElement::clearProperty(const ListLayout::Role &role)
927 case ListLayout::Role::String:
928 setStringProperty(role, QString());
930 case ListLayout::Role::Number:
931 setDoubleProperty(role, 0.0);
933 case ListLayout::Role::Bool:
934 setBoolProperty(role, false);
936 case ListLayout::Role::List:
937 setListProperty(role, 0);
939 case ListLayout::Role::QObject:
940 setQObjectProperty(role, 0);
942 case ListLayout::Role::VariantMap:
943 setVariantMapProperty(role, 0);
950 ListElement::ListElement()
953 uid = uidCounter.fetchAndAddOrdered(1);
955 qMemSet(data, 0, sizeof(data));
958 ListElement::ListElement(int existingUid)
963 qMemSet(data, 0, sizeof(data));
966 ListElement::~ListElement()
971 void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
973 for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
974 const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
975 const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
977 switch (srcRole.type) {
978 case ListLayout::Role::List:
980 ListModel *srcSubModel = src->getListProperty(srcRole);
981 ListModel *targetSubModel = target->getListProperty(targetRole);
984 if (targetSubModel == 0) {
985 targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
986 target->setListPropertyFast(targetRole, targetSubModel);
988 ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
992 case ListLayout::Role::QObject:
994 QObject *object = src->getQObjectProperty(srcRole);
995 target->setQObjectProperty(targetRole, object);
998 case ListLayout::Role::String:
999 case ListLayout::Role::Number:
1000 case ListLayout::Role::Bool:
1002 QVariant v = src->getProperty(srcRole, 0, 0);
1003 target->setVariantProperty(targetRole, v);
1005 case ListLayout::Role::VariantMap:
1007 QVariantMap *map = src->getVariantMapProperty(srcRole);
1008 target->setVariantMapProperty(targetRole, map);
1018 void ListElement::destroy(ListLayout *layout)
1021 for (int i=0 ; i < layout->roleCount() ; ++i) {
1022 const ListLayout::Role &r = layout->getExistingRole(i);
1025 case ListLayout::Role::String:
1027 QString *string = getStringProperty(r);
1032 case ListLayout::Role::List:
1034 ListModel *model = getListProperty(r);
1041 case ListLayout::Role::QObject:
1043 QQmlGuard<QObject> *guard = getGuardProperty(r);
1045 guard->~QQmlGuard();
1048 case ListLayout::Role::VariantMap:
1050 QVariantMap *map = getVariantMapProperty(r);
1056 // other types don't need explicit cleanup.
1061 delete m_objectCache;
1069 int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
1073 switch (role.type) {
1074 case ListLayout::Role::Number:
1075 roleIndex = setDoubleProperty(role, d.toDouble());
1077 case ListLayout::Role::String:
1078 roleIndex = setStringProperty(role, d.toString());
1080 case ListLayout::Role::Bool:
1081 roleIndex = setBoolProperty(role, d.toBool());
1083 case ListLayout::Role::List:
1084 roleIndex = setListProperty(role, d.value<ListModel *>());
1086 case ListLayout::Role::VariantMap: {
1087 QVariantMap map = d.toMap();
1088 roleIndex = setVariantMapProperty(role, &map);
1098 int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng)
1100 // Check if this key exists yet
1103 // Add the value now
1104 if (d->IsString()) {
1105 v8::Handle<v8::String> jsString = d->ToString();
1107 qstr.resize(jsString->Length());
1108 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
1109 roleIndex = setStringProperty(role, qstr);
1110 } else if (d->IsNumber()) {
1111 roleIndex = setDoubleProperty(role, d->NumberValue());
1112 } else if (d->IsArray()) {
1113 ListModel *subModel = new ListModel(role.subLayout, 0, -1);
1114 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
1115 int arrayLength = subArray->Length();
1116 for (int j=0 ; j < arrayLength ; ++j) {
1117 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
1118 subModel->append(subObject, eng);
1120 roleIndex = setListProperty(role, subModel);
1121 } else if (d->IsBoolean()) {
1122 roleIndex = setBoolProperty(role, d->BooleanValue());
1123 } else if (d->IsObject()) {
1124 QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
1125 if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
1126 QObject *o = QV8QObjectWrapper::toQObject(r);
1127 roleIndex = setQObjectProperty(role, o);
1128 } else if (role.type == ListLayout::Role::VariantMap) {
1129 roleIndex = setVariantMapProperty(role, d->ToObject(), eng);
1131 } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
1132 clearProperty(role);
1138 ModelObject::ModelObject(QQuickListModel *model, int elementIndex)
1139 : m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
1142 setNodeUpdatesEnabled(true);
1145 void ModelObject::updateValues()
1147 int roleCount = m_model->m_listModel->roleCount();
1148 for (int i=0 ; i < roleCount ; ++i) {
1149 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1150 QByteArray name = role.name.toUtf8();
1151 const QVariant &data = m_model->data(m_elementIndex, i);
1152 setValue(name, data, role.type == ListLayout::Role::List);
1156 void ModelObject::updateValues(const QList<int> &roles)
1158 int roleCount = roles.count();
1159 for (int i=0 ; i < roleCount ; ++i) {
1160 int roleIndex = roles.at(i);
1161 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1162 QByteArray name = role.name.toUtf8();
1163 const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1164 setValue(name, data, role.type == ListLayout::Role::List);
1168 ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
1169 : QQmlOpenMetaObject(object), m_enabled(false), m_obj(object)
1173 ModelNodeMetaObject::~ModelNodeMetaObject()
1177 void ModelNodeMetaObject::propertyWritten(int index)
1182 QV8Engine *eng = m_obj->m_model->engine();
1184 QString propName = QString::fromUtf8(name(index));
1185 QVariant value = operator[](index);
1187 v8::HandleScope handle_scope;
1188 v8::Context::Scope scope(eng->context());
1190 v8::Handle<v8::Value> v = eng->fromVariant(value);
1192 int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng);
1193 if (roleIndex != -1) {
1196 m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
1200 DynamicRoleModelNode::DynamicRoleModelNode(QQuickListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1202 setNodeUpdatesEnabled(true);
1205 DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQuickListModel *owner)
1207 DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1209 object->updateValues(obj, roles);
1213 void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQuickListModel *> *targetModelHash)
1215 for (int i=0 ; i < src->m_meta->count() ; ++i) {
1216 const QByteArray &name = src->m_meta->name(i);
1217 QVariant value = src->m_meta->value(i);
1219 QQuickListModel *srcModel = qobject_cast<QQuickListModel *>(value.value<QObject *>());
1220 QQuickListModel *targetModel = qobject_cast<QQuickListModel *>(target->m_meta->value(i).value<QObject *>());
1223 if (targetModel == 0)
1224 targetModel = QQuickListModel::createWithOwner(target->m_owner);
1226 QQuickListModel::sync(srcModel, targetModel, targetModelHash);
1228 QObject *targetModelObject = targetModel;
1229 value = QVariant::fromValue(targetModelObject);
1230 } else if (targetModel) {
1234 target->setValue(name, value);
1238 void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
1240 const QList<QString> &keys = object.keys();
1242 QList<QString>::const_iterator it = keys.begin();
1243 QList<QString>::const_iterator end = keys.end();
1246 const QString &key = *it;
1248 int roleIndex = m_owner->m_roles.indexOf(key);
1249 if (roleIndex == -1) {
1250 roleIndex = m_owner->m_roles.count();
1251 m_owner->m_roles.append(key);
1254 QVariant value = object[key];
1256 if (value.type() == QVariant::List) {
1257 QQuickListModel *subModel = QQuickListModel::createWithOwner(m_owner);
1259 QVariantList subArray = value.toList();
1260 QVariantList::const_iterator subIt = subArray.begin();
1261 QVariantList::const_iterator subEnd = subArray.end();
1262 while (subIt != subEnd) {
1263 const QVariantMap &subObject = subIt->toMap();
1264 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1268 QObject *subModelObject = subModel;
1269 value = QVariant::fromValue(subModelObject);
1272 const QByteArray &keyUtf8 = key.toUtf8();
1274 QQuickListModel *existingModel = qobject_cast<QQuickListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1276 delete existingModel;
1278 if (m_meta->setValue(keyUtf8, value))
1285 DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1286 : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1290 DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1292 for (int i=0 ; i < count() ; ++i) {
1293 QQuickListModel *subModel = qobject_cast<QQuickListModel *>(value(i).value<QObject *>());
1299 void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1304 QVariant v = value(index);
1305 QQuickListModel *model = qobject_cast<QQuickListModel *>(v.value<QObject *>());
1310 void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1315 QQuickListModel *parentModel = m_owner->m_owner;
1317 QVariant v = value(index);
1318 if (v.type() == QVariant::List) {
1319 QQuickListModel *subModel = QQuickListModel::createWithOwner(parentModel);
1321 QVariantList subArray = v.toList();
1322 QVariantList::const_iterator subIt = subArray.begin();
1323 QVariantList::const_iterator subEnd = subArray.end();
1324 while (subIt != subEnd) {
1325 const QVariantMap &subObject = subIt->toMap();
1326 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1330 QObject *subModelObject = subModel;
1331 v = QVariant::fromValue(subModelObject);
1336 int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1337 int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1339 if (elementIndex != -1 && roleIndex != -1) {
1343 parentModel->emitItemsChanged(elementIndex, 1, roles);
1347 QQuickListModelParser::ListInstruction *QQuickListModelParser::ListModelData::instructions() const
1349 return (QQuickListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
1353 \qmlclass ListModel QQuickListModel
1354 \inqmlmodule QtQuick 2
1355 \ingroup qml-working-with-data
1356 \brief The ListModel element defines a free-form list data source.
1358 The ListModel is a simple container of ListElement definitions, each containing data roles.
1359 The contents can be defined dynamically, or explicitly in QML.
1361 The number of elements in the model can be obtained from its \l count property.
1362 A number of familiar methods are also provided to manipulate the contents of the
1363 model, including append(), insert(), move(), remove() and set(). These methods
1364 accept dictionaries as their arguments; these are translated to ListElement objects
1367 Elements can be manipulated via the model using the setProperty() method, which
1368 allows the roles of the specified element to be set and changed.
1370 \section1 Example Usage
1372 The following example shows a ListModel containing three elements, with the roles
1375 \div {class="float-right"}
1376 \inlineimage listmodel.png
1379 \snippet doc/src/snippets/qml/listmodel.qml 0
1382 Roles (properties) in each element must begin with a lower-case letter and
1383 should be common to all elements in a model. The ListElement documentation
1384 provides more guidelines for how elements should be defined.
1386 Since the example model contains an \c id property, it can be referenced
1387 by views, such as the ListView in this example:
1389 \snippet doc/src/snippets/qml/listmodel-simple.qml 0
1391 \snippet doc/src/snippets/qml/listmodel-simple.qml 1
1393 It is possible for roles to contain list data. In the following example we
1394 create a list of fruit attributes:
1396 \snippet doc/src/snippets/qml/listmodel-nested.qml model
1398 The delegate displays all the fruit attributes:
1400 \div {class="float-right"}
1401 \inlineimage listmodel-nested.png
1404 \snippet doc/src/snippets/qml/listmodel-nested.qml delegate
1407 \section1 Modifying List Models
1409 The content of a ListModel may be created and modified using the clear(),
1410 append(), set(), insert() and setProperty() methods. For example:
1412 \snippet doc/src/snippets/qml/listmodel-modify.qml delegate
1414 Note that when creating content dynamically the set of available properties
1415 cannot be changed once set. Whatever properties are first added to the model
1416 are the only permitted properties in the model.
1418 \section1 Using Threaded List Models with WorkerScript
1420 ListModel can be used together with WorkerScript access a list model
1421 from multiple threads. This is useful if list modifications are
1422 synchronous and take some time: the list operations can be moved to a
1423 different thread to avoid blocking of the main GUI thread.
1425 Here is an example that uses WorkerScript to periodically append the
1426 current time to a list model:
1428 \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
1430 The included file, \tt dataloader.js, looks like this:
1432 \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
1434 The timer in the main example sends messages to the worker script by calling
1435 \l WorkerScript::sendMessage(). When this message is received,
1436 \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
1437 which appends the current time to the list model.
1439 Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
1440 handler. You must call sync() or else the changes made to the list from the external
1441 thread will not be reflected in the list model in the main thread.
1443 \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml
1446 QQuickListModel::QQuickListModel(QObject *parent)
1447 : QListModelInterface(parent)
1449 m_mainThread = true;
1452 m_uid = uidCounter.fetchAndAddOrdered(1);
1453 m_dynamicRoles = false;
1455 m_layout = new ListLayout;
1456 m_listModel = new ListModel(m_layout, this, -1);
1461 QQuickListModel::QQuickListModel(const QQuickListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
1462 : QListModelInterface(parent)
1464 m_mainThread = owner->m_mainThread;
1466 m_agent = owner->m_agent;
1468 Q_ASSERT(owner->m_dynamicRoles == false);
1469 m_dynamicRoles = false;
1476 QQuickListModel::QQuickListModel(QQuickListModel *orig, QQuickListModelWorkerAgent *agent)
1477 : QListModelInterface(agent)
1479 m_mainThread = false;
1482 m_dynamicRoles = orig->m_dynamicRoles;
1484 m_layout = new ListLayout(orig->m_layout);
1485 m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1488 sync(orig, this, 0);
1490 ListModel::sync(orig->m_listModel, m_listModel, 0);
1495 QQuickListModel::~QQuickListModel()
1497 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1498 delete m_modelObjects[i];
1501 m_listModel->destroy();
1504 if (m_mainThread && m_agent) {
1505 m_agent->modelDestroyed();
1516 QQuickListModel *QQuickListModel::createWithOwner(QQuickListModel *newOwner)
1518 QQuickListModel *model = new QQuickListModel;
1520 model->m_mainThread = newOwner->m_mainThread;
1521 model->m_engine = newOwner->m_engine;
1522 model->m_agent = newOwner->m_agent;
1523 model->m_dynamicRoles = newOwner->m_dynamicRoles;
1525 if (model->m_mainThread && model->m_agent)
1526 model->m_agent->addref();
1528 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1533 QV8Engine *QQuickListModel::engine() const
1535 if (m_engine == 0) {
1536 m_engine = QQmlEnginePrivate::getV8Engine(qmlEngine(this));
1542 void QQuickListModel::sync(QQuickListModel *src, QQuickListModel *target, QHash<int, QQuickListModel *> *targetModelHash)
1544 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1546 target->m_uid = src->m_uid;
1547 if (targetModelHash)
1548 targetModelHash->insert(target->m_uid, target);
1549 target->m_roles = src->m_roles;
1551 // Build hash of elements <-> uid for each of the lists
1552 QHash<int, ElementSync> elementHash;
1553 for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1554 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1555 int uid = e->getUid();
1558 elementHash.insert(uid, sync);
1560 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1561 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1562 int uid = e->getUid();
1564 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1565 if (it == elementHash.end()) {
1568 elementHash.insert(uid, sync);
1570 ElementSync &sync = it.value();
1575 // Get list of elements that are in the target but no longer in the source. These get deleted first.
1576 QHash<int, ElementSync>::iterator it = elementHash.begin();
1577 QHash<int, ElementSync>::iterator end = elementHash.end();
1579 const ElementSync &s = it.value();
1581 int targetIndex = target->m_modelObjects.indexOf(s.target);
1582 target->m_modelObjects.remove(targetIndex, 1);
1588 // Clear the target list, and append in correct order from the source
1589 target->m_modelObjects.clear();
1590 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1591 DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1592 it = elementHash.find(srcElement->getUid());
1593 const ElementSync &s = it.value();
1594 DynamicRoleModelNode *targetElement = s.target;
1595 if (targetElement == 0) {
1596 targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1598 DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1599 target->m_modelObjects.append(targetElement);
1603 void QQuickListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
1606 emit itemsChanged(index, count, roles);
1608 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1609 m_agent->data.changedChange(uid, index, count, roles);
1613 void QQuickListModel::emitItemsRemoved(int index, int count)
1616 emit itemsRemoved(index, count);
1617 emit countChanged();
1619 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1620 if (index == 0 && count == this->count())
1621 m_agent->data.clearChange(uid);
1622 m_agent->data.removeChange(uid, index, count);
1626 void QQuickListModel::emitItemsInserted(int index, int count)
1629 emit itemsInserted(index, count);
1630 emit countChanged();
1632 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1633 m_agent->data.insertChange(uid, index, count);
1637 void QQuickListModel::emitItemsMoved(int from, int to, int n)
1640 emit itemsMoved(from, to, n);
1642 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1643 m_agent->data.moveChange(uid, from, n, to);
1647 QQuickListModelWorkerAgent *QQuickListModel::agent()
1652 m_agent = new QQuickListModelWorkerAgent(this);
1656 QList<int> QQuickListModel::roles() const
1658 QList<int> rolesArray;
1660 if (m_dynamicRoles) {
1661 for (int i=0 ; i < m_roles.count() ; ++i)
1664 for (int i=0 ; i < m_listModel->roleCount() ; ++i)
1671 QString QQuickListModel::toString(int role) const
1675 if (m_dynamicRoles) {
1676 roleName = m_roles[role];
1678 const ListLayout::Role &r = m_listModel->getExistingRole(role);
1685 QVariant QQuickListModel::data(int index, int role) const
1689 if (index >= count() || index < 0)
1693 v = m_modelObjects[index]->getValue(m_roles[role]);
1695 v = m_listModel->getProperty(index, role, this, engine());
1701 \qmlproperty bool QtQuick2::ListModel::dynamicRoles
1703 By default, the type of a role is fixed the first time
1704 the role is used. For example, if you create a role called
1705 "data" and assign a number to it, you can no longer assign
1706 a string to the "data" role. However, when the dynamicRoles
1707 property is enabled, the type of a given role is not fixed
1708 and can be different between elements.
1710 The dynamicRoles property must be set before any data is
1711 added to the ListModel, and must be set from the main
1714 A ListModel that has data statically defined (via the
1715 ListElement QML syntax) cannot have the dynamicRoles
1718 There is a significant performance cost to using a
1719 ListModel with dynamic roles enabled. The cost varies
1720 from platform to platform but is typically somewhere
1721 between 4-6x slower than using static role types.
1723 Due to the performance cost of using dynamic roles,
1724 they are disabled by default.
1726 void QQuickListModel::setDynamicRoles(bool enableDynamicRoles)
1728 if (m_mainThread && m_agent == 0) {
1729 if (enableDynamicRoles) {
1730 if (m_layout->roleCount())
1731 qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
1733 m_dynamicRoles = true;
1735 if (m_roles.count()) {
1736 qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
1738 m_dynamicRoles = false;
1742 qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
1747 \qmlproperty int QtQuick2::ListModel::count
1748 The number of data entries in the model.
1750 int QQuickListModel::count() const
1755 count = m_modelObjects.count();
1757 count = m_listModel->elementCount();
1764 \qmlmethod QtQuick2::ListModel::clear()
1766 Deletes all content from the model.
1768 \sa append() remove()
1770 void QQuickListModel::clear()
1772 int cleared = count();
1774 if (m_dynamicRoles) {
1775 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1776 delete m_modelObjects[i];
1777 m_modelObjects.clear();
1779 m_listModel->clear();
1782 emitItemsRemoved(0, cleared);
1786 \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
1788 Deletes the content at \a index from the model.
1792 void QQuickListModel::remove(QQmlV8Function *args)
1794 int argLength = args->Length();
1796 if (argLength == 1 || argLength == 2) {
1797 int index = (*args)[0]->Int32Value();
1798 int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1);
1800 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
1801 qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
1805 if (m_dynamicRoles) {
1806 for (int i=0 ; i < removeCount ; ++i)
1807 delete m_modelObjects[index+i];
1808 m_modelObjects.remove(index, removeCount);
1810 m_listModel->remove(index, removeCount);
1813 emitItemsRemoved(index, removeCount);
1815 qmlInfo(this) << tr("remove: incorrect number of arguments");
1820 \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
1822 Adds a new item to the list model at position \a index, with the
1826 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
1829 The \a index must be to an existing item in the list, or one past
1830 the end of the list (equivalent to append).
1835 void QQuickListModel::insert(QQmlV8Function *args)
1837 if (args->Length() == 2) {
1839 v8::Handle<v8::Value> arg0 = (*args)[0];
1840 int index = arg0->Int32Value();
1842 if (index < 0 || index > count()) {
1843 qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
1847 v8::Handle<v8::Value> arg1 = (*args)[1];
1849 if (arg1->IsArray()) {
1850 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
1851 int objectArrayLength = objectArray->Length();
1852 for (int i=0 ; i < objectArrayLength ; ++i) {
1853 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1855 if (m_dynamicRoles) {
1856 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1858 m_listModel->insert(index+i, argObject, args->engine());
1861 emitItemsInserted(index, objectArrayLength);
1862 } else if (arg1->IsObject()) {
1863 v8::Handle<v8::Object> argObject = arg1->ToObject();
1865 if (m_dynamicRoles) {
1866 m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1868 m_listModel->insert(index, argObject, args->engine());
1871 emitItemsInserted(index, 1);
1873 qmlInfo(this) << tr("insert: value is not an object");
1876 qmlInfo(this) << tr("insert: value is not an object");
1881 \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
1883 Moves \a n items \a from one position \a to another.
1885 The from and to ranges must exist; for example, to move the first 3 items
1886 to the end of the list:
1889 fruitModel.move(0, fruitModel.count - 3, 3)
1894 void QQuickListModel::move(int from, int to, int n)
1896 if (n==0 || from==to)
1898 if (!canMove(from, to, n)) {
1899 qmlInfo(this) << tr("move: out of range");
1903 if (m_dynamicRoles) {
1905 int realFrom = from;
1910 // Only move forwards - flip if backwards moving
1918 QPODVector<DynamicRoleModelNode *, 4> store;
1919 for (int i=0 ; i < (realTo-realFrom) ; ++i)
1920 store.append(m_modelObjects[realFrom+realN+i]);
1921 for (int i=0 ; i < realN ; ++i)
1922 store.append(m_modelObjects[realFrom+i]);
1923 for (int i=0 ; i < store.count() ; ++i)
1924 m_modelObjects[realFrom+i] = store[i];
1927 m_listModel->move(from, to, n);
1930 emitItemsMoved(from, to, n);
1934 \qmlmethod QtQuick2::ListModel::append(jsobject dict)
1936 Adds a new item to the end of the list model, with the
1940 fruitModel.append({"cost": 5.95, "name":"Pizza"})
1945 void QQuickListModel::append(QQmlV8Function *args)
1947 if (args->Length() == 1) {
1948 v8::Handle<v8::Value> arg = (*args)[0];
1950 if (arg->IsArray()) {
1951 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
1952 int objectArrayLength = objectArray->Length();
1954 int index = count();
1955 for (int i=0 ; i < objectArrayLength ; ++i) {
1956 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1958 if (m_dynamicRoles) {
1959 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1961 m_listModel->append(argObject, args->engine());
1965 emitItemsInserted(index, objectArrayLength);
1966 } else if (arg->IsObject()) {
1967 v8::Handle<v8::Object> argObject = arg->ToObject();
1971 if (m_dynamicRoles) {
1972 index = m_modelObjects.count();
1973 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1975 index = m_listModel->append(argObject, args->engine());
1978 emitItemsInserted(index, 1);
1980 qmlInfo(this) << tr("append: value is not an object");
1983 qmlInfo(this) << tr("append: value is not an object");
1988 \qmlmethod object QtQuick2::ListModel::get(int index)
1990 Returns the item at \a index in the list model. This allows the item
1991 data to be accessed or modified from JavaScript:
1994 Component.onCompleted: {
1995 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
1996 console.log(fruitModel.get(0).cost);
1997 fruitModel.get(0).cost = 10.95;
2001 The \a index must be an element in the list.
2003 Note that properties of the returned object that are themselves objects
2004 will also be models, and this get() method is used to access elements:
2007 fruitModel.append(..., "attributes":
2008 [{"name":"spikes","value":"7mm"},
2009 {"name":"color","value":"green"}]);
2010 fruitModel.get(0).attributes.get(1).value; // == "green"
2013 \warning The returned object is not guaranteed to remain valid. It
2014 should not be used in \l{Property Binding}{property bindings}.
2018 QQmlV8Handle QQuickListModel::get(int index) const
2020 v8::Handle<v8::Value> result = v8::Undefined();
2022 if (index >= 0 && index < count()) {
2023 QV8Engine *v8engine = engine();
2025 if (m_dynamicRoles) {
2026 DynamicRoleModelNode *object = m_modelObjects[index];
2027 result = v8engine->newQObject(object);
2029 ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQuickListModel *>(this), index);
2030 result = v8engine->newQObject(object);
2034 return QQmlV8Handle::fromHandle(result);
2038 \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
2040 Changes the item at \a index in the list model with the
2041 values in \a dict. Properties not appearing in \a dict
2045 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2048 If \a index is equal to count() then a new item is appended to the
2049 list. Otherwise, \a index must be an element in the list.
2053 void QQuickListModel::set(int index, const QQmlV8Handle &handle)
2055 v8::Handle<v8::Value> valuemap = handle.toHandle();
2057 if (!valuemap->IsObject() || valuemap->IsArray()) {
2058 qmlInfo(this) << tr("set: value is not an object");
2061 if (index > count() || index < 0) {
2062 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2066 v8::Handle<v8::Object> object = valuemap->ToObject();
2068 if (index == count()) {
2070 if (m_dynamicRoles) {
2071 m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
2073 m_listModel->insert(index, object, engine());
2076 emitItemsInserted(index, 1);
2081 if (m_dynamicRoles) {
2082 m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
2084 m_listModel->set(index, object, &roles, engine());
2088 emitItemsChanged(index, 1, roles);
2093 \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
2095 Changes the \a property of the item at \a index in the list model to \a value.
2098 fruitModel.setProperty(3, "cost", 5.95)
2101 The \a index must be an element in the list.
2105 void QQuickListModel::setProperty(int index, const QString& property, const QVariant& value)
2107 if (count() == 0 || index >= count() || index < 0) {
2108 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2112 if (m_dynamicRoles) {
2113 int roleIndex = m_roles.indexOf(property);
2114 if (roleIndex == -1) {
2115 roleIndex = m_roles.count();
2116 m_roles.append(property);
2118 if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
2121 emitItemsChanged(index, 1, roles);
2124 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2125 if (roleIndex != -1) {
2130 emitItemsChanged(index, 1, roles);
2136 \qmlmethod QtQuick2::ListModel::sync()
2138 Writes any unsaved changes to the list model after it has been modified
2139 from a worker script.
2141 void QQuickListModel::sync()
2143 // This is just a dummy method to make it look like sync() exists in
2144 // ListModel (and not just QQuickListModelWorkerAgent) and to let
2145 // us document sync().
2146 qmlInfo(this) << "List sync() can only be called from a WorkerScript";
2149 bool QQuickListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
2151 QList<QVariant> values = prop.assignedValues();
2152 for(int ii = 0; ii < values.count(); ++ii) {
2153 const QVariant &value = values.at(ii);
2155 if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
2156 QQmlCustomParserNode node =
2157 qvariant_cast<QQmlCustomParserNode>(value);
2159 if (node.name() != listElementTypeName) {
2160 const QMetaObject *mo = resolveType(node.name());
2161 if (mo != &QQuickListElement::staticMetaObject) {
2162 error(node, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2165 listElementTypeName = node.name(); // cache right name for next time
2170 li.type = ListInstruction::Push;
2175 QList<QQmlCustomParserProperty> props = node.properties();
2176 for(int jj = 0; jj < props.count(); ++jj) {
2177 const QQmlCustomParserProperty &nodeProp = props.at(jj);
2178 if (nodeProp.name().isEmpty()) {
2179 error(nodeProp, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2182 if (nodeProp.name() == QStringLiteral("id")) {
2183 error(nodeProp, QQuickListModel::tr("ListElement: cannot use reserved \"id\" property"));
2188 int ref = data.count();
2189 data.append(nodeProp.name().toUtf8());
2191 li.type = ListInstruction::Set;
2195 if(!compileProperty(nodeProp, instr, data))
2198 li.type = ListInstruction::Pop;
2205 li.type = ListInstruction::Pop;
2212 QQmlScript::Variant variant =
2213 qvariant_cast<QQmlScript::Variant>(value);
2215 int ref = data.count();
2218 d += char(variant.type()); // type tag
2219 if (variant.isString()) {
2220 d += variant.asString().toUtf8();
2221 } else if (variant.isNumber()) {
2222 d += QByteArray::number(variant.asNumber(),'g',20);
2223 } else if (variant.isBoolean()) {
2224 d += char(variant.asBoolean());
2225 } else if (variant.isScript()) {
2226 if (definesEmptyList(variant.asScript())) {
2227 d[0] = char(QQmlScript::Variant::Invalid); // marks empty list
2229 QByteArray script = variant.asScript().toUtf8();
2230 int v = evaluateEnum(script);
2232 using namespace QQmlJS;
2233 AST::Node *node = variant.asAST();
2234 AST::StringLiteral *literal = 0;
2235 if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
2236 if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
2237 if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
2238 if (callExpr->arguments && !callExpr->arguments->next)
2239 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
2241 error(prop, QQuickListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
2244 } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
2245 if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
2246 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
2248 error(prop, QQuickListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
2256 d[0] = char(QQmlScript::Variant::String);
2257 d += literal->value.toUtf8();
2259 error(prop, QQuickListModel::tr("ListElement: cannot use script for property value"));
2263 d[0] = char(QQmlScript::Variant::Number);
2264 d += QByteArray::number(v);
2272 li.type = ListInstruction::Value;
2281 QByteArray QQuickListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps)
2283 QList<ListInstruction> instr;
2285 listElementTypeName = QString(); // unknown
2287 for(int ii = 0; ii < customProps.count(); ++ii) {
2288 const QQmlCustomParserProperty &prop = customProps.at(ii);
2289 if(!prop.name().isEmpty()) { // isn't default property
2290 error(prop, QQuickListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
2291 return QByteArray();
2294 if(!compileProperty(prop, instr, data)) {
2295 return QByteArray();
2299 int size = sizeof(ListModelData) +
2300 instr.count() * sizeof(ListInstruction) +
2306 ListModelData *lmd = (ListModelData *)rv.data();
2307 lmd->dataOffset = sizeof(ListModelData) +
2308 instr.count() * sizeof(ListInstruction);
2309 lmd->instrCount = instr.count();
2310 for (int ii = 0; ii < instr.count(); ++ii)
2311 lmd->instructions()[ii] = instr.at(ii);
2312 ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
2317 void QQuickListModelParser::setCustomData(QObject *obj, const QByteArray &d)
2319 QQuickListModel *rv = static_cast<QQuickListModel *>(obj);
2321 QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv));
2322 rv->m_engine = engine;
2324 const ListModelData *lmd = (const ListModelData *)d.constData();
2325 const char *data = ((const char *)lmd) + lmd->dataOffset;
2327 QStack<DataStackElement> stack;
2329 for (int ii = 0; ii < lmd->instrCount; ++ii) {
2330 const ListInstruction &instr = lmd->instructions()[ii];
2332 switch(instr.type) {
2333 case ListInstruction::Push:
2335 Q_ASSERT(!rv->m_dynamicRoles);
2337 ListModel *subModel = 0;
2339 if (stack.count() == 0) {
2340 subModel = rv->m_listModel;
2342 const DataStackElement &e0 = stack.at(stack.size() - 1);
2343 DataStackElement &e1 = stack[stack.size() - 2];
2345 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2346 if (role.type == ListLayout::Role::List) {
2347 subModel = e1.model->getListProperty(e1.elementIndex, role);
2349 if (subModel == 0) {
2350 subModel = new ListModel(role.subLayout, 0, -1);
2351 QVariant vModel = QVariant::fromValue(subModel);
2352 e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
2359 e.elementIndex = subModel ? subModel->appendElement() : -1;
2364 case ListInstruction::Pop:
2368 case ListInstruction::Value:
2370 const DataStackElement &e0 = stack.at(stack.size() - 1);
2371 DataStackElement &e1 = stack[stack.size() - 2];
2373 QString name = e0.name;
2376 switch (QQmlScript::Variant::Type(data[instr.dataIdx])) {
2377 case QQmlScript::Variant::Invalid:
2379 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2380 ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2381 value = QVariant::fromValue(emptyModel);
2384 case QQmlScript::Variant::Boolean:
2385 value = bool(data[1 + instr.dataIdx]);
2387 case QQmlScript::Variant::Number:
2388 value = QByteArray(data + 1 + instr.dataIdx).toDouble();
2390 case QQmlScript::Variant::String:
2391 value = QString::fromUtf8(data + 1 + instr.dataIdx);
2394 Q_ASSERT("Format error in ListInstruction");
2397 e1.model->setOrCreateProperty(e1.elementIndex, name, value);
2401 case ListInstruction::Set:
2404 e.name = QString::fromUtf8(data + instr.dataIdx);
2412 bool QQuickListModelParser::definesEmptyList(const QString &s)
2414 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2415 for (int i=1; i<s.length()-1; i++) {
2416 if (!s[i].isSpace())
2426 \qmlclass ListElement QQuickListElement
2427 \inqmlmodule QtQuick 2
2428 \ingroup qml-working-with-data
2429 \brief The ListElement element defines a data item in a ListModel.
2431 List elements are defined inside ListModel definitions, and represent items in a
2432 list that will be displayed using ListView or \l Repeater items.
2434 List elements are defined like other QML elements except that they contain
2435 a collection of \e role definitions instead of properties. Using the same
2436 syntax as property definitions, roles both define how the data is accessed
2437 and include the data itself.
2439 The names used for roles must begin with a lower-case letter and should be
2440 common to all elements in a given model. Values must be simple constants; either
2441 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2442 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2444 \section1 Referencing Roles
2446 The role names are used by delegates to obtain data from list elements.
2447 Each role name is accessible in the delegate's scope, and refers to the
2448 corresponding role in the current element. Where a role name would be
2449 ambiguous to use, it can be accessed via the \l{ListView::}{model}
2450 property (e.g., \c{model.cost} instead of \c{cost}).
2452 \section1 Example Usage
2454 The following model defines a series of list elements, each of which
2455 contain "name" and "cost" roles and their associated values.
2457 \snippet doc/src/snippets/qml/qml-data-models/listelements.qml model
2459 The delegate obtains the name and cost for each element by simply referring
2460 to \c name and \c cost:
2462 \snippet doc/src/snippets/qml/qml-data-models/listelements.qml view