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 memset(data, 0, sizeof(data));
958 ListElement::ListElement(int existingUid)
963 memset(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 if (role.type == ListLayout::Role::List) {
1114 ListModel *subModel = new ListModel(role.subLayout, 0, -1);
1115 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
1116 int arrayLength = subArray->Length();
1117 for (int j=0 ; j < arrayLength ; ++j) {
1118 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
1119 subModel->append(subObject, eng);
1121 roleIndex = setListProperty(role, subModel);
1123 qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
1125 } else if (d->IsBoolean()) {
1126 roleIndex = setBoolProperty(role, d->BooleanValue());
1127 } else if (d->IsObject()) {
1128 QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
1129 if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
1130 QObject *o = QV8QObjectWrapper::toQObject(r);
1131 roleIndex = setQObjectProperty(role, o);
1132 } else if (role.type == ListLayout::Role::VariantMap) {
1133 roleIndex = setVariantMapProperty(role, d->ToObject(), eng);
1135 } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
1136 clearProperty(role);
1142 ModelObject::ModelObject(QQuickListModel *model, int elementIndex)
1143 : m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
1146 setNodeUpdatesEnabled(true);
1149 void ModelObject::updateValues()
1151 int roleCount = m_model->m_listModel->roleCount();
1152 for (int i=0 ; i < roleCount ; ++i) {
1153 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1154 QByteArray name = role.name.toUtf8();
1155 const QVariant &data = m_model->data(m_elementIndex, i);
1156 setValue(name, data, role.type == ListLayout::Role::List);
1160 void ModelObject::updateValues(const QList<int> &roles)
1162 int roleCount = roles.count();
1163 for (int i=0 ; i < roleCount ; ++i) {
1164 int roleIndex = roles.at(i);
1165 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1166 QByteArray name = role.name.toUtf8();
1167 const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1168 setValue(name, data, role.type == ListLayout::Role::List);
1172 ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
1173 : QQmlOpenMetaObject(object), m_enabled(false), m_obj(object)
1177 ModelNodeMetaObject::~ModelNodeMetaObject()
1181 void ModelNodeMetaObject::propertyWritten(int index)
1186 QV8Engine *eng = m_obj->m_model->engine();
1188 QString propName = QString::fromUtf8(name(index));
1189 QVariant value = operator[](index);
1191 v8::HandleScope handle_scope;
1192 v8::Context::Scope scope(eng->context());
1194 v8::Handle<v8::Value> v = eng->fromVariant(value);
1196 int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng);
1197 if (roleIndex != -1) {
1200 m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
1204 DynamicRoleModelNode::DynamicRoleModelNode(QQuickListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1206 setNodeUpdatesEnabled(true);
1209 DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQuickListModel *owner)
1211 DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1213 object->updateValues(obj, roles);
1217 void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQuickListModel *> *targetModelHash)
1219 for (int i=0 ; i < src->m_meta->count() ; ++i) {
1220 const QByteArray &name = src->m_meta->name(i);
1221 QVariant value = src->m_meta->value(i);
1223 QQuickListModel *srcModel = qobject_cast<QQuickListModel *>(value.value<QObject *>());
1224 QQuickListModel *targetModel = qobject_cast<QQuickListModel *>(target->m_meta->value(i).value<QObject *>());
1227 if (targetModel == 0)
1228 targetModel = QQuickListModel::createWithOwner(target->m_owner);
1230 QQuickListModel::sync(srcModel, targetModel, targetModelHash);
1232 QObject *targetModelObject = targetModel;
1233 value = QVariant::fromValue(targetModelObject);
1234 } else if (targetModel) {
1238 target->setValue(name, value);
1242 void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
1244 const QList<QString> &keys = object.keys();
1246 QList<QString>::const_iterator it = keys.begin();
1247 QList<QString>::const_iterator end = keys.end();
1250 const QString &key = *it;
1252 int roleIndex = m_owner->m_roles.indexOf(key);
1253 if (roleIndex == -1) {
1254 roleIndex = m_owner->m_roles.count();
1255 m_owner->m_roles.append(key);
1258 QVariant value = object[key];
1260 if (value.type() == QVariant::List) {
1261 QQuickListModel *subModel = QQuickListModel::createWithOwner(m_owner);
1263 QVariantList subArray = value.toList();
1264 QVariantList::const_iterator subIt = subArray.begin();
1265 QVariantList::const_iterator subEnd = subArray.end();
1266 while (subIt != subEnd) {
1267 const QVariantMap &subObject = subIt->toMap();
1268 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1272 QObject *subModelObject = subModel;
1273 value = QVariant::fromValue(subModelObject);
1276 const QByteArray &keyUtf8 = key.toUtf8();
1278 QQuickListModel *existingModel = qobject_cast<QQuickListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1280 delete existingModel;
1282 if (m_meta->setValue(keyUtf8, value))
1289 DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1290 : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1294 DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1296 for (int i=0 ; i < count() ; ++i) {
1297 QQuickListModel *subModel = qobject_cast<QQuickListModel *>(value(i).value<QObject *>());
1303 void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1308 QVariant v = value(index);
1309 QQuickListModel *model = qobject_cast<QQuickListModel *>(v.value<QObject *>());
1314 void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1319 QQuickListModel *parentModel = m_owner->m_owner;
1321 QVariant v = value(index);
1322 if (v.type() == QVariant::List) {
1323 QQuickListModel *subModel = QQuickListModel::createWithOwner(parentModel);
1325 QVariantList subArray = v.toList();
1326 QVariantList::const_iterator subIt = subArray.begin();
1327 QVariantList::const_iterator subEnd = subArray.end();
1328 while (subIt != subEnd) {
1329 const QVariantMap &subObject = subIt->toMap();
1330 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1334 QObject *subModelObject = subModel;
1335 v = QVariant::fromValue(subModelObject);
1340 int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1341 int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1343 if (elementIndex != -1 && roleIndex != -1) {
1347 parentModel->emitItemsChanged(elementIndex, 1, roles);
1351 QQuickListModelParser::ListInstruction *QQuickListModelParser::ListModelData::instructions() const
1353 return (QQuickListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
1357 \qmlclass ListModel QQuickListModel
1358 \inqmlmodule QtQuick 2
1359 \brief Defines a free-form list data source
1360 \ingroup qtquick-models
1362 The ListModel is a simple container of ListElement definitions, each containing data roles.
1363 The contents can be defined dynamically, or explicitly in QML.
1365 The number of elements in the model can be obtained from its \l count property.
1366 A number of familiar methods are also provided to manipulate the contents of the
1367 model, including append(), insert(), move(), remove() and set(). These methods
1368 accept dictionaries as their arguments; these are translated to ListElement objects
1371 Elements can be manipulated via the model using the setProperty() method, which
1372 allows the roles of the specified element to be set and changed.
1374 \section1 Example Usage
1376 The following example shows a ListModel containing three elements, with the roles
1379 \div {class="float-right"}
1380 \inlineimage listmodel.png
1383 \snippet qml/listmodel/listmodel.qml 0
1385 Roles (properties) in each element must begin with a lower-case letter and
1386 should be common to all elements in a model. The ListElement documentation
1387 provides more guidelines for how elements should be defined.
1389 Since the example model contains an \c id property, it can be referenced
1390 by views, such as the ListView in this example:
1392 \snippet qml/listmodel/listmodel-simple.qml 0
1394 \snippet qml/listmodel/listmodel-simple.qml 1
1396 It is possible for roles to contain list data. In the following example we
1397 create a list of fruit attributes:
1399 \snippet qml/listmodel/listmodel-nested.qml model
1401 The delegate displays all the fruit attributes:
1403 \div {class="float-right"}
1404 \inlineimage listmodel-nested.png
1407 \snippet qml/listmodel/listmodel-nested.qml delegate
1409 \section1 Modifying List Models
1411 The content of a ListModel may be created and modified using the clear(),
1412 append(), set(), insert() and setProperty() methods. For example:
1414 \snippet qml/listmodel/listmodel-modify.qml delegate
1416 Note that when creating content dynamically the set of available properties
1417 cannot be changed once set. Whatever properties are first added to the model
1418 are the only permitted properties in the model.
1420 \section1 Using Threaded List Models with WorkerScript
1422 ListModel can be used together with WorkerScript access a list model
1423 from multiple threads. This is useful if list modifications are
1424 synchronous and take some time: the list operations can be moved to a
1425 different thread to avoid blocking of the main GUI thread.
1427 Here is an example that uses WorkerScript to periodically append the
1428 current time to a list model:
1430 \snippet examples/quick/threading/threadedlistmodel/timedisplay.qml 0
1432 The included file, \tt dataloader.js, looks like this:
1434 \snippet examples/quick/threading/threadedlistmodel/dataloader.js 0
1436 The timer in the main example sends messages to the worker script by calling
1437 \l WorkerScript::sendMessage(). When this message is received,
1438 \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
1439 which appends the current time to the list model.
1441 Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
1442 handler. You must call sync() or else the changes made to the list from the external
1443 thread will not be reflected in the list model in the main thread.
1445 \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml
1448 QQuickListModel::QQuickListModel(QObject *parent)
1449 : QListModelInterface(parent)
1451 m_mainThread = true;
1454 m_uid = uidCounter.fetchAndAddOrdered(1);
1455 m_dynamicRoles = false;
1457 m_layout = new ListLayout;
1458 m_listModel = new ListModel(m_layout, this, -1);
1463 QQuickListModel::QQuickListModel(const QQuickListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
1464 : QListModelInterface(parent)
1466 m_mainThread = owner->m_mainThread;
1468 m_agent = owner->m_agent;
1470 Q_ASSERT(owner->m_dynamicRoles == false);
1471 m_dynamicRoles = false;
1478 QQuickListModel::QQuickListModel(QQuickListModel *orig, QQuickListModelWorkerAgent *agent)
1479 : QListModelInterface(agent)
1481 m_mainThread = false;
1484 m_dynamicRoles = orig->m_dynamicRoles;
1486 m_layout = new ListLayout(orig->m_layout);
1487 m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1490 sync(orig, this, 0);
1492 ListModel::sync(orig->m_listModel, m_listModel, 0);
1497 QQuickListModel::~QQuickListModel()
1499 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1500 delete m_modelObjects[i];
1503 m_listModel->destroy();
1506 if (m_mainThread && m_agent) {
1507 m_agent->modelDestroyed();
1518 QQuickListModel *QQuickListModel::createWithOwner(QQuickListModel *newOwner)
1520 QQuickListModel *model = new QQuickListModel;
1522 model->m_mainThread = newOwner->m_mainThread;
1523 model->m_engine = newOwner->m_engine;
1524 model->m_agent = newOwner->m_agent;
1525 model->m_dynamicRoles = newOwner->m_dynamicRoles;
1527 if (model->m_mainThread && model->m_agent)
1528 model->m_agent->addref();
1530 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1535 QV8Engine *QQuickListModel::engine() const
1537 if (m_engine == 0) {
1538 m_engine = QQmlEnginePrivate::getV8Engine(qmlEngine(this));
1544 void QQuickListModel::sync(QQuickListModel *src, QQuickListModel *target, QHash<int, QQuickListModel *> *targetModelHash)
1546 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1548 target->m_uid = src->m_uid;
1549 if (targetModelHash)
1550 targetModelHash->insert(target->m_uid, target);
1551 target->m_roles = src->m_roles;
1553 // Build hash of elements <-> uid for each of the lists
1554 QHash<int, ElementSync> elementHash;
1555 for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1556 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1557 int uid = e->getUid();
1560 elementHash.insert(uid, sync);
1562 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1563 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1564 int uid = e->getUid();
1566 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1567 if (it == elementHash.end()) {
1570 elementHash.insert(uid, sync);
1572 ElementSync &sync = it.value();
1577 // Get list of elements that are in the target but no longer in the source. These get deleted first.
1578 QHash<int, ElementSync>::iterator it = elementHash.begin();
1579 QHash<int, ElementSync>::iterator end = elementHash.end();
1581 const ElementSync &s = it.value();
1583 int targetIndex = target->m_modelObjects.indexOf(s.target);
1584 target->m_modelObjects.remove(targetIndex, 1);
1590 // Clear the target list, and append in correct order from the source
1591 target->m_modelObjects.clear();
1592 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1593 DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1594 it = elementHash.find(srcElement->getUid());
1595 const ElementSync &s = it.value();
1596 DynamicRoleModelNode *targetElement = s.target;
1597 if (targetElement == 0) {
1598 targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1600 DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1601 target->m_modelObjects.append(targetElement);
1605 void QQuickListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
1608 emit itemsChanged(index, count, roles);
1610 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1611 m_agent->data.changedChange(uid, index, count, roles);
1615 void QQuickListModel::emitItemsRemoved(int index, int count)
1618 emit itemsRemoved(index, count);
1619 emit countChanged();
1621 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1622 if (index == 0 && count == this->count())
1623 m_agent->data.clearChange(uid);
1624 m_agent->data.removeChange(uid, index, count);
1628 void QQuickListModel::emitItemsInserted(int index, int count)
1631 emit itemsInserted(index, count);
1632 emit countChanged();
1634 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1635 m_agent->data.insertChange(uid, index, count);
1639 void QQuickListModel::emitItemsMoved(int from, int to, int n)
1642 emit itemsMoved(from, to, n);
1644 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1645 m_agent->data.moveChange(uid, from, n, to);
1649 QQuickListModelWorkerAgent *QQuickListModel::agent()
1654 m_agent = new QQuickListModelWorkerAgent(this);
1658 QList<int> QQuickListModel::roles() const
1660 QList<int> rolesArray;
1662 if (m_dynamicRoles) {
1663 for (int i=0 ; i < m_roles.count() ; ++i)
1666 for (int i=0 ; i < m_listModel->roleCount() ; ++i)
1673 QString QQuickListModel::toString(int role) const
1677 if (m_dynamicRoles) {
1678 roleName = m_roles[role];
1680 const ListLayout::Role &r = m_listModel->getExistingRole(role);
1687 QVariant QQuickListModel::data(int index, int role) const
1691 if (index >= count() || index < 0)
1695 v = m_modelObjects[index]->getValue(m_roles[role]);
1697 v = m_listModel->getProperty(index, role, this, engine());
1703 \qmlproperty bool QtQuick2::ListModel::dynamicRoles
1705 By default, the type of a role is fixed the first time
1706 the role is used. For example, if you create a role called
1707 "data" and assign a number to it, you can no longer assign
1708 a string to the "data" role. However, when the dynamicRoles
1709 property is enabled, the type of a given role is not fixed
1710 and can be different between elements.
1712 The dynamicRoles property must be set before any data is
1713 added to the ListModel, and must be set from the main
1716 A ListModel that has data statically defined (via the
1717 ListElement QML syntax) cannot have the dynamicRoles
1720 There is a significant performance cost to using a
1721 ListModel with dynamic roles enabled. The cost varies
1722 from platform to platform but is typically somewhere
1723 between 4-6x slower than using static role types.
1725 Due to the performance cost of using dynamic roles,
1726 they are disabled by default.
1728 void QQuickListModel::setDynamicRoles(bool enableDynamicRoles)
1730 if (m_mainThread && m_agent == 0) {
1731 if (enableDynamicRoles) {
1732 if (m_layout->roleCount())
1733 qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
1735 m_dynamicRoles = true;
1737 if (m_roles.count()) {
1738 qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
1740 m_dynamicRoles = false;
1744 qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
1749 \qmlproperty int QtQuick2::ListModel::count
1750 The number of data entries in the model.
1752 int QQuickListModel::count() const
1757 count = m_modelObjects.count();
1759 count = m_listModel->elementCount();
1766 \qmlmethod QtQuick2::ListModel::clear()
1768 Deletes all content from the model.
1770 \sa append(), remove()
1772 void QQuickListModel::clear()
1774 int cleared = count();
1776 if (m_dynamicRoles) {
1777 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1778 delete m_modelObjects[i];
1779 m_modelObjects.clear();
1781 m_listModel->clear();
1784 emitItemsRemoved(0, cleared);
1788 \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
1790 Deletes the content at \a index from the model.
1794 void QQuickListModel::remove(QQmlV8Function *args)
1796 int argLength = args->Length();
1798 if (argLength == 1 || argLength == 2) {
1799 int index = (*args)[0]->Int32Value();
1800 int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1);
1802 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
1803 qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
1807 if (m_dynamicRoles) {
1808 for (int i=0 ; i < removeCount ; ++i)
1809 delete m_modelObjects[index+i];
1810 m_modelObjects.remove(index, removeCount);
1812 m_listModel->remove(index, removeCount);
1815 emitItemsRemoved(index, removeCount);
1817 qmlInfo(this) << tr("remove: incorrect number of arguments");
1822 \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
1824 Adds a new item to the list model at position \a index, with the
1828 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
1831 The \a index must be to an existing item in the list, or one past
1832 the end of the list (equivalent to append).
1837 void QQuickListModel::insert(QQmlV8Function *args)
1839 if (args->Length() == 2) {
1841 v8::Handle<v8::Value> arg0 = (*args)[0];
1842 int index = arg0->Int32Value();
1844 if (index < 0 || index > count()) {
1845 qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
1849 v8::Handle<v8::Value> arg1 = (*args)[1];
1851 if (arg1->IsArray()) {
1852 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
1853 int objectArrayLength = objectArray->Length();
1854 for (int i=0 ; i < objectArrayLength ; ++i) {
1855 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1857 if (m_dynamicRoles) {
1858 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1860 m_listModel->insert(index+i, argObject, args->engine());
1863 emitItemsInserted(index, objectArrayLength);
1864 } else if (arg1->IsObject()) {
1865 v8::Handle<v8::Object> argObject = arg1->ToObject();
1867 if (m_dynamicRoles) {
1868 m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1870 m_listModel->insert(index, argObject, args->engine());
1873 emitItemsInserted(index, 1);
1875 qmlInfo(this) << tr("insert: value is not an object");
1878 qmlInfo(this) << tr("insert: value is not an object");
1883 \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
1885 Moves \a n items \a from one position \a to another.
1887 The from and to ranges must exist; for example, to move the first 3 items
1888 to the end of the list:
1891 fruitModel.move(0, fruitModel.count - 3, 3)
1896 void QQuickListModel::move(int from, int to, int n)
1898 if (n==0 || from==to)
1900 if (!canMove(from, to, n)) {
1901 qmlInfo(this) << tr("move: out of range");
1905 if (m_dynamicRoles) {
1907 int realFrom = from;
1912 // Only move forwards - flip if backwards moving
1920 QPODVector<DynamicRoleModelNode *, 4> store;
1921 for (int i=0 ; i < (realTo-realFrom) ; ++i)
1922 store.append(m_modelObjects[realFrom+realN+i]);
1923 for (int i=0 ; i < realN ; ++i)
1924 store.append(m_modelObjects[realFrom+i]);
1925 for (int i=0 ; i < store.count() ; ++i)
1926 m_modelObjects[realFrom+i] = store[i];
1929 m_listModel->move(from, to, n);
1932 emitItemsMoved(from, to, n);
1936 \qmlmethod QtQuick2::ListModel::append(jsobject dict)
1938 Adds a new item to the end of the list model, with the
1942 fruitModel.append({"cost": 5.95, "name":"Pizza"})
1947 void QQuickListModel::append(QQmlV8Function *args)
1949 if (args->Length() == 1) {
1950 v8::Handle<v8::Value> arg = (*args)[0];
1952 if (arg->IsArray()) {
1953 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
1954 int objectArrayLength = objectArray->Length();
1956 int index = count();
1957 for (int i=0 ; i < objectArrayLength ; ++i) {
1958 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1960 if (m_dynamicRoles) {
1961 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1963 m_listModel->append(argObject, args->engine());
1967 emitItemsInserted(index, objectArrayLength);
1968 } else if (arg->IsObject()) {
1969 v8::Handle<v8::Object> argObject = arg->ToObject();
1973 if (m_dynamicRoles) {
1974 index = m_modelObjects.count();
1975 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1977 index = m_listModel->append(argObject, args->engine());
1980 emitItemsInserted(index, 1);
1982 qmlInfo(this) << tr("append: value is not an object");
1985 qmlInfo(this) << tr("append: value is not an object");
1990 \qmlmethod object QtQuick2::ListModel::get(int index)
1992 Returns the item at \a index in the list model. This allows the item
1993 data to be accessed or modified from JavaScript:
1996 Component.onCompleted: {
1997 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
1998 console.log(fruitModel.get(0).cost);
1999 fruitModel.get(0).cost = 10.95;
2003 The \a index must be an element in the list.
2005 Note that properties of the returned object that are themselves objects
2006 will also be models, and this get() method is used to access elements:
2009 fruitModel.append(..., "attributes":
2010 [{"name":"spikes","value":"7mm"},
2011 {"name":"color","value":"green"}]);
2012 fruitModel.get(0).attributes.get(1).value; // == "green"
2015 \warning The returned object is not guaranteed to remain valid. It
2016 should not be used in \l{Property Binding}{property bindings}.
2020 QQmlV8Handle QQuickListModel::get(int index) const
2022 v8::Handle<v8::Value> result = v8::Undefined();
2024 if (index >= 0 && index < count()) {
2025 QV8Engine *v8engine = engine();
2027 if (m_dynamicRoles) {
2028 DynamicRoleModelNode *object = m_modelObjects[index];
2029 result = v8engine->newQObject(object);
2031 ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQuickListModel *>(this), index);
2032 result = v8engine->newQObject(object);
2036 return QQmlV8Handle::fromHandle(result);
2040 \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
2042 Changes the item at \a index in the list model with the
2043 values in \a dict. Properties not appearing in \a dict
2047 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2050 If \a index is equal to count() then a new item is appended to the
2051 list. Otherwise, \a index must be an element in the list.
2055 void QQuickListModel::set(int index, const QQmlV8Handle &handle)
2057 v8::Handle<v8::Value> valuemap = handle.toHandle();
2059 if (!valuemap->IsObject() || valuemap->IsArray()) {
2060 qmlInfo(this) << tr("set: value is not an object");
2063 if (index > count() || index < 0) {
2064 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2068 v8::Handle<v8::Object> object = valuemap->ToObject();
2070 if (index == count()) {
2072 if (m_dynamicRoles) {
2073 m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
2075 m_listModel->insert(index, object, engine());
2078 emitItemsInserted(index, 1);
2083 if (m_dynamicRoles) {
2084 m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
2086 m_listModel->set(index, object, &roles, engine());
2090 emitItemsChanged(index, 1, roles);
2095 \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
2097 Changes the \a property of the item at \a index in the list model to \a value.
2100 fruitModel.setProperty(3, "cost", 5.95)
2103 The \a index must be an element in the list.
2107 void QQuickListModel::setProperty(int index, const QString& property, const QVariant& value)
2109 if (count() == 0 || index >= count() || index < 0) {
2110 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2114 if (m_dynamicRoles) {
2115 int roleIndex = m_roles.indexOf(property);
2116 if (roleIndex == -1) {
2117 roleIndex = m_roles.count();
2118 m_roles.append(property);
2120 if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
2123 emitItemsChanged(index, 1, roles);
2126 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2127 if (roleIndex != -1) {
2132 emitItemsChanged(index, 1, roles);
2138 \qmlmethod QtQuick2::ListModel::sync()
2140 Writes any unsaved changes to the list model after it has been modified
2141 from a worker script.
2143 void QQuickListModel::sync()
2145 // This is just a dummy method to make it look like sync() exists in
2146 // ListModel (and not just QQuickListModelWorkerAgent) and to let
2147 // us document sync().
2148 qmlInfo(this) << "List sync() can only be called from a WorkerScript";
2151 bool QQuickListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
2153 QList<QVariant> values = prop.assignedValues();
2154 for(int ii = 0; ii < values.count(); ++ii) {
2155 const QVariant &value = values.at(ii);
2157 if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
2158 QQmlCustomParserNode node =
2159 qvariant_cast<QQmlCustomParserNode>(value);
2161 if (node.name() != listElementTypeName) {
2162 const QMetaObject *mo = resolveType(node.name());
2163 if (mo != &QQuickListElement::staticMetaObject) {
2164 error(node, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2167 listElementTypeName = node.name(); // cache right name for next time
2172 li.type = ListInstruction::Push;
2177 QList<QQmlCustomParserProperty> props = node.properties();
2178 for(int jj = 0; jj < props.count(); ++jj) {
2179 const QQmlCustomParserProperty &nodeProp = props.at(jj);
2180 if (nodeProp.name().isEmpty()) {
2181 error(nodeProp, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2184 if (nodeProp.name() == QStringLiteral("id")) {
2185 error(nodeProp, QQuickListModel::tr("ListElement: cannot use reserved \"id\" property"));
2190 int ref = data.count();
2191 data.append(nodeProp.name().toUtf8());
2193 li.type = ListInstruction::Set;
2197 if(!compileProperty(nodeProp, instr, data))
2200 li.type = ListInstruction::Pop;
2207 li.type = ListInstruction::Pop;
2214 QQmlScript::Variant variant =
2215 qvariant_cast<QQmlScript::Variant>(value);
2217 int ref = data.count();
2220 d += char(variant.type()); // type tag
2221 if (variant.isString()) {
2222 d += variant.asString().toUtf8();
2223 } else if (variant.isNumber()) {
2224 d += QByteArray::number(variant.asNumber(),'g',20);
2225 } else if (variant.isBoolean()) {
2226 d += char(variant.asBoolean());
2227 } else if (variant.isScript()) {
2228 if (definesEmptyList(variant.asScript())) {
2229 d[0] = char(QQmlScript::Variant::Invalid); // marks empty list
2231 QByteArray script = variant.asScript().toUtf8();
2232 int v = evaluateEnum(script);
2234 using namespace QQmlJS;
2235 AST::Node *node = variant.asAST();
2236 AST::StringLiteral *literal = 0;
2237 if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
2238 if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
2239 if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
2240 if (callExpr->arguments && !callExpr->arguments->next)
2241 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
2243 error(prop, QQuickListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
2246 } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
2247 if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
2248 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
2250 error(prop, QQuickListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
2258 d[0] = char(QQmlScript::Variant::String);
2259 d += literal->value.toUtf8();
2261 error(prop, QQuickListModel::tr("ListElement: cannot use script for property value"));
2265 d[0] = char(QQmlScript::Variant::Number);
2266 d += QByteArray::number(v);
2274 li.type = ListInstruction::Value;
2283 QByteArray QQuickListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps)
2285 QList<ListInstruction> instr;
2287 listElementTypeName = QString(); // unknown
2289 for(int ii = 0; ii < customProps.count(); ++ii) {
2290 const QQmlCustomParserProperty &prop = customProps.at(ii);
2291 if(!prop.name().isEmpty()) { // isn't default property
2292 error(prop, QQuickListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
2293 return QByteArray();
2296 if(!compileProperty(prop, instr, data)) {
2297 return QByteArray();
2301 int size = sizeof(ListModelData) +
2302 instr.count() * sizeof(ListInstruction) +
2308 ListModelData *lmd = (ListModelData *)rv.data();
2309 lmd->dataOffset = sizeof(ListModelData) +
2310 instr.count() * sizeof(ListInstruction);
2311 lmd->instrCount = instr.count();
2312 for (int ii = 0; ii < instr.count(); ++ii)
2313 lmd->instructions()[ii] = instr.at(ii);
2314 ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
2319 void QQuickListModelParser::setCustomData(QObject *obj, const QByteArray &d)
2321 QQuickListModel *rv = static_cast<QQuickListModel *>(obj);
2323 QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv));
2324 rv->m_engine = engine;
2326 const ListModelData *lmd = (const ListModelData *)d.constData();
2327 const char *data = ((const char *)lmd) + lmd->dataOffset;
2329 QStack<DataStackElement> stack;
2331 for (int ii = 0; ii < lmd->instrCount; ++ii) {
2332 const ListInstruction &instr = lmd->instructions()[ii];
2334 switch(instr.type) {
2335 case ListInstruction::Push:
2337 Q_ASSERT(!rv->m_dynamicRoles);
2339 ListModel *subModel = 0;
2341 if (stack.count() == 0) {
2342 subModel = rv->m_listModel;
2344 const DataStackElement &e0 = stack.at(stack.size() - 1);
2345 DataStackElement &e1 = stack[stack.size() - 2];
2347 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2348 if (role.type == ListLayout::Role::List) {
2349 subModel = e1.model->getListProperty(e1.elementIndex, role);
2351 if (subModel == 0) {
2352 subModel = new ListModel(role.subLayout, 0, -1);
2353 QVariant vModel = QVariant::fromValue(subModel);
2354 e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
2361 e.elementIndex = subModel ? subModel->appendElement() : -1;
2366 case ListInstruction::Pop:
2370 case ListInstruction::Value:
2372 const DataStackElement &e0 = stack.at(stack.size() - 1);
2373 DataStackElement &e1 = stack[stack.size() - 2];
2375 QString name = e0.name;
2378 switch (QQmlScript::Variant::Type(data[instr.dataIdx])) {
2379 case QQmlScript::Variant::Invalid:
2381 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2382 ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2383 value = QVariant::fromValue(emptyModel);
2386 case QQmlScript::Variant::Boolean:
2387 value = bool(data[1 + instr.dataIdx]);
2389 case QQmlScript::Variant::Number:
2390 value = QByteArray(data + 1 + instr.dataIdx).toDouble();
2392 case QQmlScript::Variant::String:
2393 value = QString::fromUtf8(data + 1 + instr.dataIdx);
2396 Q_ASSERT("Format error in ListInstruction");
2399 e1.model->setOrCreateProperty(e1.elementIndex, name, value);
2403 case ListInstruction::Set:
2406 e.name = QString::fromUtf8(data + instr.dataIdx);
2414 bool QQuickListModelParser::definesEmptyList(const QString &s)
2416 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2417 for (int i=1; i<s.length()-1; i++) {
2418 if (!s[i].isSpace())
2428 \qmlclass ListElement QQuickListElement
2429 \inqmlmodule QtQuick 2
2430 \brief Defines a data item in a ListModel
2431 \ingroup qtquick-models
2433 List elements are defined inside ListModel definitions, and represent items in a
2434 list that will be displayed using ListView or \l Repeater items.
2436 List elements are defined like other QML elements except that they contain
2437 a collection of \e role definitions instead of properties. Using the same
2438 syntax as property definitions, roles both define how the data is accessed
2439 and include the data itself.
2441 The names used for roles must begin with a lower-case letter and should be
2442 common to all elements in a given model. Values must be simple constants; either
2443 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2444 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2446 \section1 Referencing Roles
2448 The role names are used by delegates to obtain data from list elements.
2449 Each role name is accessible in the delegate's scope, and refers to the
2450 corresponding role in the current element. Where a role name would be
2451 ambiguous to use, it can be accessed via the \l{ListView::}{model}
2452 property (e.g., \c{model.cost} instead of \c{cost}).
2454 \section1 Example Usage
2456 The following model defines a series of list elements, each of which
2457 contain "name" and "cost" roles and their associated values.
2459 \snippet qml/listmodel/listelements.qml model
2461 The delegate obtains the name and cost for each element by simply referring
2462 to \c name and \c cost:
2464 \snippet qml/listmodel/listelements.qml view