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>
49 #include <private/qqmlcustomparser_p.h>
50 #include <private/qqmlscript_p.h>
51 #include <private/qqmlengine_p.h>
52 #include <qqmlcontext.h>
55 #include <QtCore/qdebug.h>
56 #include <QtCore/qstack.h>
57 #include <QXmlStreamReader>
61 // Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
62 enum { MIN_LISTMODEL_UID = 1024 };
64 static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
67 static bool isMemoryUsed(const char *mem)
69 for (size_t i=0 ; i < sizeof(T) ; ++i) {
77 static QString roleTypeName(ListLayout::Role::DataType t)
80 const char *roleTypeNames[] = { "String", "Number", "Bool", "List", "QObject", "VariantMap", "DateTime" };
82 if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
83 result = QString::fromLatin1(roleTypeNames[t]);
88 const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
90 QStringHash<Role *>::Node *node = roleHash.findNode(key);
92 const Role &r = *node->value;
94 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));
98 return createRole(key, type);
101 const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type)
103 QHashedV8String hashedKey(key);
104 QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
106 const Role &r = *node->value;
108 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));
113 qkey.resize(key->Length());
114 key->Write(reinterpret_cast<uint16_t*>(qkey.data()));
116 return createRole(qkey, type);
119 const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
121 const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QQmlGuard<QObject>), sizeof(QVariantMap), sizeof(QDateTime) };
122 const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime) };
128 if (type == Role::List) {
129 r->subLayout = new ListLayout;
134 int dataSize = dataSizes[type];
135 int dataAlignment = dataAlignments[type];
137 int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
138 if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
139 r->blockIndex = ++currentBlock;
141 currentBlockOffset = dataSize;
143 r->blockIndex = currentBlock;
144 r->blockOffset = dataOffset;
145 currentBlockOffset = dataOffset + dataSize;
148 int roleIndex = roles.count();
149 r->index = roleIndex;
152 roleHash.insert(key, r);
157 ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
159 for (int i=0 ; i < other->roles.count() ; ++i) {
160 Role *role = new Role(other->roles[i]);
162 roleHash.insert(role->name, role);
164 currentBlockOffset = other->currentBlockOffset;
165 currentBlock = other->currentBlock;
168 ListLayout::~ListLayout()
170 for (int i=0 ; i < roles.count() ; ++i) {
175 void ListLayout::sync(ListLayout *src, ListLayout *target)
177 int roleOffset = target->roles.count();
178 int newRoleCount = src->roles.count() - roleOffset;
180 for (int i=0 ; i < newRoleCount ; ++i) {
181 Role *role = new Role(src->roles[roleOffset + i]);
182 target->roles.append(role);
183 target->roleHash.insert(role->name, role);
186 target->currentBlockOffset = src->currentBlockOffset;
187 target->currentBlock = src->currentBlock;
190 ListLayout::Role::Role(const Role *other)
194 blockIndex = other->blockIndex;
195 blockOffset = other->blockOffset;
196 index = other->index;
197 if (other->subLayout)
198 subLayout = new ListLayout(other->subLayout);
203 ListLayout::Role::~Role()
208 const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
212 switch (data.type()) {
213 case QVariant::Double: type = Role::Number; break;
214 case QVariant::Int: type = Role::Number; break;
215 case QVariant::UserType: type = Role::List; break;
216 case QVariant::Bool: type = Role::Bool; break;
217 case QVariant::String: type = Role::String; break;
218 case QVariant::Map: type = Role::VariantMap; break;
219 default: type = Role::Invalid; break;
222 if (type == Role::Invalid) {
223 qmlInfo(0) << "Can't create role for unsupported data type";
227 return &getRoleOrCreate(key, type);
230 const ListLayout::Role *ListLayout::getExistingRole(const QString &key)
233 QStringHash<Role *>::Node *node = roleHash.findNode(key);
239 const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key)
242 QHashedV8String hashedKey(key);
243 QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
249 ModelObject *ListModel::getOrCreateModelObject(QQuickListModel *model, int elementIndex)
251 ListElement *e = elements[elementIndex];
252 if (e->m_objectCache == 0) {
253 e->m_objectCache = new ModelObject(model, elementIndex);
255 return e->m_objectCache;
258 void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
261 target->m_uid = src->m_uid;
263 targetModelHash->insert(target->m_uid, target);
265 // Build hash of elements <-> uid for each of the lists
266 QHash<int, ElementSync> elementHash;
267 for (int i=0 ; i < target->elements.count() ; ++i) {
268 ListElement *e = target->elements.at(i);
269 int uid = e->getUid();
272 elementHash.insert(uid, sync);
274 for (int i=0 ; i < src->elements.count() ; ++i) {
275 ListElement *e = src->elements.at(i);
276 int uid = e->getUid();
278 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
279 if (it == elementHash.end()) {
282 elementHash.insert(uid, sync);
284 ElementSync &sync = it.value();
289 // Get list of elements that are in the target but no longer in the source. These get deleted first.
290 QHash<int, ElementSync>::iterator it = elementHash.begin();
291 QHash<int, ElementSync>::iterator end = elementHash.end();
293 const ElementSync &s = it.value();
295 s.target->destroy(target->m_layout);
296 target->elements.removeOne(s.target);
303 ListLayout::sync(src->m_layout, target->m_layout);
305 // Clear the target list, and append in correct order from the source
306 target->elements.clear();
307 for (int i=0 ; i < src->elements.count() ; ++i) {
308 ListElement *srcElement = src->elements.at(i);
309 it = elementHash.find(srcElement->getUid());
310 const ElementSync &s = it.value();
311 ListElement *targetElement = s.target;
312 if (targetElement == 0) {
313 targetElement = new ListElement(srcElement->getUid());
315 ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
316 target->elements.append(targetElement);
319 target->updateCacheIndices();
321 // Update values stored in target meta objects
322 for (int i=0 ; i < target->elements.count() ; ++i) {
323 ListElement *e = target->elements[i];
324 if (e->m_objectCache)
325 e->m_objectCache->updateValues();
329 ListModel::ListModel(ListLayout *layout, QQuickListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
332 uid = uidCounter.fetchAndAddOrdered(1);
336 void ListModel::destroy()
341 if (m_modelCache && m_modelCache->m_primary == false)
346 int ListModel::appendElement()
348 int elementIndex = elements.count();
349 newElement(elementIndex);
353 void ListModel::insertElement(int index)
356 updateCacheIndices();
359 void ListModel::move(int from, int to, int n)
362 // Only move forwards - flip if backwards moving
370 QPODVector<ListElement *, 4> store;
371 for (int i=0 ; i < (to-from) ; ++i)
372 store.append(elements[from+n+i]);
373 for (int i=0 ; i < n ; ++i)
374 store.append(elements[from+i]);
375 for (int i=0 ; i < store.count() ; ++i)
376 elements[from+i] = store[i];
378 updateCacheIndices();
381 void ListModel::newElement(int index)
383 ListElement *e = new ListElement;
384 elements.insert(index, e);
387 void ListModel::updateCacheIndices()
389 for (int i=0 ; i < elements.count() ; ++i) {
390 ListElement *e = elements.at(i);
391 if (e->m_objectCache) {
392 e->m_objectCache->m_elementIndex = i;
397 QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQuickListModel *owner, QV8Engine *eng)
399 ListElement *e = elements[elementIndex];
400 const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
401 return e->getProperty(r, owner, eng);
404 ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
406 ListElement *e = elements[elementIndex];
407 return e->getListProperty(role);
410 void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QVector<int> *roles, QV8Engine *eng)
412 ListElement *e = elements[elementIndex];
414 v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
415 int propertyCount = propertyNames->Length();
417 for (int i=0 ; i < propertyCount ; ++i) {
418 v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
419 v8::Local<v8::Value> propertyValue = object->Get(propertyName);
421 // Check if this key exists yet
425 if (propertyValue->IsString()) {
426 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
427 v8::Handle<v8::String> jsString = propertyValue->ToString();
429 qstr.resize(jsString->Length());
430 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
431 roleIndex = e->setStringProperty(r, qstr);
432 } else if (propertyValue->IsNumber()) {
433 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
434 roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue());
435 } else if (propertyValue->IsArray()) {
436 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
437 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
439 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
440 int arrayLength = subArray->Length();
441 for (int j=0 ; j < arrayLength ; ++j) {
442 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
443 subModel->append(subObject, eng);
446 roleIndex = e->setListProperty(r, subModel);
447 } else if (propertyValue->IsBoolean()) {
448 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
449 roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue());
450 } else if (propertyValue->IsDate()) {
451 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
452 QDateTime dt = QV8Engine::qtDateTimeFromJsDate(v8::Handle<v8::Date>::Cast(propertyValue)->NumberValue());
453 roleIndex = e->setDateTimeProperty(r, dt);
454 } else if (propertyValue->IsObject()) {
455 QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
456 if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
457 QObject *o = QV8QObjectWrapper::toQObject(r);
458 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
459 if (role.type == ListLayout::Role::QObject)
460 roleIndex = e->setQObjectProperty(role, o);
462 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
463 if (role.type == ListLayout::Role::VariantMap)
464 roleIndex = e->setVariantMapProperty(role, propertyValue->ToObject(), eng);
466 } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
467 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
469 e->clearProperty(*r);
473 roles->append(roleIndex);
476 if (e->m_objectCache) {
477 e->m_objectCache->updateValues(*roles);
481 void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
483 ListElement *e = elements[elementIndex];
485 v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
486 int propertyCount = propertyNames->Length();
488 for (int i=0 ; i < propertyCount ; ++i) {
489 v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
490 v8::Local<v8::Value> propertyValue = object->Get(propertyName);
493 if (propertyValue->IsString()) {
494 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
495 if (r.type == ListLayout::Role::String) {
496 v8::Handle<v8::String> jsString = propertyValue->ToString();
498 qstr.resize(jsString->Length());
499 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
500 e->setStringPropertyFast(r, qstr);
502 } else if (propertyValue->IsNumber()) {
503 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
504 if (r.type == ListLayout::Role::Number) {
505 e->setDoublePropertyFast(r, propertyValue->NumberValue());
507 } else if (propertyValue->IsArray()) {
508 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
509 if (r.type == ListLayout::Role::List) {
510 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
512 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
513 int arrayLength = subArray->Length();
514 for (int j=0 ; j < arrayLength ; ++j) {
515 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
516 subModel->append(subObject, eng);
519 e->setListPropertyFast(r, subModel);
521 } else if (propertyValue->IsBoolean()) {
522 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
523 if (r.type == ListLayout::Role::Bool) {
524 e->setBoolPropertyFast(r, propertyValue->BooleanValue());
526 } else if (propertyValue->IsDate()) {
527 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
528 if (r.type == ListLayout::Role::DateTime) {
529 QDateTime dt = QV8Engine::qtDateTimeFromJsDate(v8::Handle<v8::Date>::Cast(propertyValue)->NumberValue());
530 e->setDateTimePropertyFast(r, dt);
532 } else if (propertyValue->IsObject()) {
533 QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
534 if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
535 QObject *o = QV8QObjectWrapper::toQObject(r);
536 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
537 if (r.type == ListLayout::Role::QObject)
538 e->setQObjectPropertyFast(r, o);
540 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
541 if (role.type == ListLayout::Role::VariantMap)
542 e->setVariantMapFast(role, propertyValue->ToObject(), eng);
544 } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
545 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
547 e->clearProperty(*r);
552 void ListModel::clear()
554 int elementCount = elements.count();
555 for (int i=0 ; i < elementCount ; ++i) {
556 elements[i]->destroy(m_layout);
562 void ListModel::remove(int index, int count)
564 for (int i=0 ; i < count ; ++i) {
565 elements[index+i]->destroy(m_layout);
566 delete elements[index+i];
568 elements.remove(index, count);
569 updateCacheIndices();
572 void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
574 insertElement(elementIndex);
575 set(elementIndex, object, eng);
578 int ListModel::append(v8::Handle<v8::Object> object, QV8Engine *eng)
580 int elementIndex = appendElement();
581 set(elementIndex, object, eng);
585 int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
589 if (elementIndex >= 0 && elementIndex < elements.count()) {
590 ListElement *e = elements[elementIndex];
592 const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
594 roleIndex = e->setVariantProperty(*r, data);
596 if (roleIndex != -1 && e->m_objectCache) {
599 e->m_objectCache->updateValues(roles);
607 int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng)
611 if (elementIndex >= 0 && elementIndex < elements.count()) {
612 ListElement *e = elements[elementIndex];
613 const ListLayout::Role *r = m_layout->getExistingRole(key);
615 roleIndex = e->setJsProperty(*r, data, eng);
621 inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
623 ListElement *e = this;
625 while (blockIndex < role.blockIndex) {
627 e->next = new ListElement;
634 char *mem = &e->data[role.blockOffset];
638 QString *ListElement::getStringProperty(const ListLayout::Role &role)
640 char *mem = getPropertyMemory(role);
641 QString *s = reinterpret_cast<QString *>(mem);
642 return s->data_ptr() ? s : 0;
645 QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
647 char *mem = getPropertyMemory(role);
648 QQmlGuard<QObject> *o = reinterpret_cast<QQmlGuard<QObject> *>(mem);
652 QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
654 QVariantMap *map = 0;
656 char *mem = getPropertyMemory(role);
657 if (isMemoryUsed<QVariantMap>(mem))
658 map = reinterpret_cast<QVariantMap *>(mem);
663 QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
667 char *mem = getPropertyMemory(role);
668 if (isMemoryUsed<QDateTime>(mem))
669 dt = reinterpret_cast<QDateTime *>(mem);
674 QQmlGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
676 char *mem = getPropertyMemory(role);
678 bool existingGuard = false;
679 for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) {
681 existingGuard = true;
686 QQmlGuard<QObject> *o = 0;
689 o = reinterpret_cast<QQmlGuard<QObject> *>(mem);
694 ListModel *ListElement::getListProperty(const ListLayout::Role &role)
696 char *mem = getPropertyMemory(role);
697 ListModel **value = reinterpret_cast<ListModel **>(mem);
701 QVariant ListElement::getProperty(const ListLayout::Role &role, const QQuickListModel *owner, QV8Engine *eng)
703 char *mem = getPropertyMemory(role);
708 case ListLayout::Role::Number:
710 double *value = reinterpret_cast<double *>(mem);
714 case ListLayout::Role::String:
716 QString *value = reinterpret_cast<QString *>(mem);
717 if (value->data_ptr() != 0)
721 case ListLayout::Role::Bool:
723 bool *value = reinterpret_cast<bool *>(mem);
727 case ListLayout::Role::List:
729 ListModel **value = reinterpret_cast<ListModel **>(mem);
730 ListModel *model = *value;
733 if (model->m_modelCache == 0) {
734 model->m_modelCache = new QQuickListModel(owner, model, eng);
735 QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
738 QObject *object = model->m_modelCache;
739 data = QVariant::fromValue(object);
743 case ListLayout::Role::QObject:
745 QQmlGuard<QObject> *guard = reinterpret_cast<QQmlGuard<QObject> *>(mem);
746 QObject *object = guard->data();
748 data = QVariant::fromValue(object);
751 case ListLayout::Role::VariantMap:
753 if (isMemoryUsed<QVariantMap>(mem)) {
754 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
759 case ListLayout::Role::DateTime:
761 if (isMemoryUsed<QDateTime>(mem)) {
762 QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
774 int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
778 if (role.type == ListLayout::Role::String) {
779 char *mem = getPropertyMemory(role);
780 QString *c = reinterpret_cast<QString *>(mem);
782 if (c->data_ptr() == 0) {
783 new (mem) QString(s);
786 changed = c->compare(s) != 0;
790 roleIndex = role.index;
796 int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
800 if (role.type == ListLayout::Role::Number) {
801 char *mem = getPropertyMemory(role);
802 double *value = new (mem) double;
803 bool changed = *value != d;
806 roleIndex = role.index;
812 int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
816 if (role.type == ListLayout::Role::Bool) {
817 char *mem = getPropertyMemory(role);
818 bool *value = new (mem) bool;
819 bool changed = *value != b;
822 roleIndex = role.index;
828 int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
832 if (role.type == ListLayout::Role::List) {
833 char *mem = getPropertyMemory(role);
834 ListModel **value = new (mem) ListModel *;
840 roleIndex = role.index;
846 int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
850 if (role.type == ListLayout::Role::QObject) {
851 char *mem = getPropertyMemory(role);
852 QQmlGuard<QObject> *g = reinterpret_cast<QQmlGuard<QObject> *>(mem);
853 bool existingGuard = false;
854 for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) {
856 existingGuard = true;
862 changed = g->data() != o;
867 new (mem) QQmlGuard<QObject>(o);
869 roleIndex = role.index;
875 int ListElement::setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
879 if (role.type == ListLayout::Role::VariantMap) {
880 char *mem = getPropertyMemory(role);
881 if (isMemoryUsed<QVariantMap>(mem)) {
882 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
885 new (mem) QVariantMap(eng->variantMapFromJS(o));
886 roleIndex = role.index;
892 int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
896 if (role.type == ListLayout::Role::VariantMap) {
897 char *mem = getPropertyMemory(role);
898 if (isMemoryUsed<QVariantMap>(mem)) {
899 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
903 new (mem) QVariantMap(*m);
905 new (mem) QVariantMap;
906 roleIndex = role.index;
912 int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt)
916 if (role.type == ListLayout::Role::DateTime) {
917 char *mem = getPropertyMemory(role);
918 if (isMemoryUsed<QDateTime>(mem)) {
919 QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
922 new (mem) QDateTime(dt);
923 roleIndex = role.index;
929 void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
931 char *mem = getPropertyMemory(role);
932 new (mem) QString(s);
935 void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
937 char *mem = getPropertyMemory(role);
938 double *value = new (mem) double;
942 void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
944 char *mem = getPropertyMemory(role);
945 bool *value = new (mem) bool;
949 void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
951 char *mem = getPropertyMemory(role);
952 new (mem) QQmlGuard<QObject>(o);
955 void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
957 char *mem = getPropertyMemory(role);
958 ListModel **value = new (mem) ListModel *;
962 void ListElement::setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
964 char *mem = getPropertyMemory(role);
965 QVariantMap *map = new (mem) QVariantMap;
966 *map = eng->variantMapFromJS(o);
969 void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt)
971 char *mem = getPropertyMemory(role);
972 new (mem) QDateTime(dt);
975 void ListElement::clearProperty(const ListLayout::Role &role)
978 case ListLayout::Role::String:
979 setStringProperty(role, QString());
981 case ListLayout::Role::Number:
982 setDoubleProperty(role, 0.0);
984 case ListLayout::Role::Bool:
985 setBoolProperty(role, false);
987 case ListLayout::Role::List:
988 setListProperty(role, 0);
990 case ListLayout::Role::QObject:
991 setQObjectProperty(role, 0);
993 case ListLayout::Role::DateTime:
994 setDateTimeProperty(role, QDateTime());
996 case ListLayout::Role::VariantMap:
997 setVariantMapProperty(role, 0);
1004 ListElement::ListElement()
1007 uid = uidCounter.fetchAndAddOrdered(1);
1009 memset(data, 0, sizeof(data));
1012 ListElement::ListElement(int existingUid)
1017 memset(data, 0, sizeof(data));
1020 ListElement::~ListElement()
1025 void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
1027 for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
1028 const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
1029 const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
1031 switch (srcRole.type) {
1032 case ListLayout::Role::List:
1034 ListModel *srcSubModel = src->getListProperty(srcRole);
1035 ListModel *targetSubModel = target->getListProperty(targetRole);
1038 if (targetSubModel == 0) {
1039 targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
1040 target->setListPropertyFast(targetRole, targetSubModel);
1042 ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
1046 case ListLayout::Role::QObject:
1048 QObject *object = src->getQObjectProperty(srcRole);
1049 target->setQObjectProperty(targetRole, object);
1052 case ListLayout::Role::String:
1053 case ListLayout::Role::Number:
1054 case ListLayout::Role::Bool:
1055 case ListLayout::Role::DateTime:
1057 QVariant v = src->getProperty(srcRole, 0, 0);
1058 target->setVariantProperty(targetRole, v);
1060 case ListLayout::Role::VariantMap:
1062 QVariantMap *map = src->getVariantMapProperty(srcRole);
1063 target->setVariantMapProperty(targetRole, map);
1073 void ListElement::destroy(ListLayout *layout)
1076 for (int i=0 ; i < layout->roleCount() ; ++i) {
1077 const ListLayout::Role &r = layout->getExistingRole(i);
1080 case ListLayout::Role::String:
1082 QString *string = getStringProperty(r);
1087 case ListLayout::Role::List:
1089 ListModel *model = getListProperty(r);
1096 case ListLayout::Role::QObject:
1098 QQmlGuard<QObject> *guard = getGuardProperty(r);
1100 guard->~QQmlGuard();
1103 case ListLayout::Role::VariantMap:
1105 QVariantMap *map = getVariantMapProperty(r);
1110 case ListLayout::Role::DateTime:
1112 QDateTime *dt = getDateTimeProperty(r);
1118 // other types don't need explicit cleanup.
1123 delete m_objectCache;
1131 int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
1135 switch (role.type) {
1136 case ListLayout::Role::Number:
1137 roleIndex = setDoubleProperty(role, d.toDouble());
1139 case ListLayout::Role::String:
1140 roleIndex = setStringProperty(role, d.toString());
1142 case ListLayout::Role::Bool:
1143 roleIndex = setBoolProperty(role, d.toBool());
1145 case ListLayout::Role::List:
1146 roleIndex = setListProperty(role, d.value<ListModel *>());
1148 case ListLayout::Role::VariantMap: {
1149 QVariantMap map = d.toMap();
1150 roleIndex = setVariantMapProperty(role, &map);
1153 case ListLayout::Role::DateTime:
1154 roleIndex = setDateTimeProperty(role, d.toDateTime());
1163 int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng)
1165 // Check if this key exists yet
1168 // Add the value now
1169 if (d->IsString()) {
1170 v8::Handle<v8::String> jsString = d->ToString();
1172 qstr.resize(jsString->Length());
1173 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
1174 roleIndex = setStringProperty(role, qstr);
1175 } else if (d->IsNumber()) {
1176 roleIndex = setDoubleProperty(role, d->NumberValue());
1177 } else if (d->IsArray()) {
1178 if (role.type == ListLayout::Role::List) {
1179 ListModel *subModel = new ListModel(role.subLayout, 0, -1);
1180 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
1181 int arrayLength = subArray->Length();
1182 for (int j=0 ; j < arrayLength ; ++j) {
1183 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
1184 subModel->append(subObject, eng);
1186 roleIndex = setListProperty(role, subModel);
1188 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));
1190 } else if (d->IsBoolean()) {
1191 roleIndex = setBoolProperty(role, d->BooleanValue());
1192 } else if (d->IsDate()) {
1193 QDateTime dt = QV8Engine::qtDateTimeFromJsDate(v8::Handle<v8::Date>::Cast(d)->NumberValue());
1194 roleIndex = setDateTimeProperty(role, dt);
1195 } else if (d->IsObject()) {
1196 QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
1197 if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
1198 QObject *o = QV8QObjectWrapper::toQObject(r);
1199 roleIndex = setQObjectProperty(role, o);
1200 } else if (role.type == ListLayout::Role::VariantMap) {
1201 roleIndex = setVariantMapProperty(role, d->ToObject(), eng);
1203 } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
1204 clearProperty(role);
1210 ModelObject::ModelObject(QQuickListModel *model, int elementIndex)
1211 : m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
1214 setNodeUpdatesEnabled(true);
1217 void ModelObject::updateValues()
1219 int roleCount = m_model->m_listModel->roleCount();
1220 for (int i=0 ; i < roleCount ; ++i) {
1221 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1222 QByteArray name = role.name.toUtf8();
1223 const QVariant &data = m_model->data(m_elementIndex, i);
1224 setValue(name, data, role.type == ListLayout::Role::List);
1228 void ModelObject::updateValues(const QVector<int> &roles)
1230 int roleCount = roles.count();
1231 for (int i=0 ; i < roleCount ; ++i) {
1232 int roleIndex = roles.at(i);
1233 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1234 QByteArray name = role.name.toUtf8();
1235 const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1236 setValue(name, data, role.type == ListLayout::Role::List);
1240 ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
1241 : QQmlOpenMetaObject(object), m_enabled(false), m_obj(object)
1245 ModelNodeMetaObject::~ModelNodeMetaObject()
1249 void ModelNodeMetaObject::propertyWritten(int index)
1254 QV8Engine *eng = m_obj->m_model->engine();
1256 QString propName = QString::fromUtf8(name(index));
1257 QVariant value = operator[](index);
1259 v8::HandleScope handle_scope;
1260 v8::Context::Scope scope(eng->context());
1262 v8::Handle<v8::Value> v = eng->fromVariant(value);
1264 int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng);
1265 if (roleIndex != -1) {
1268 m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
1272 DynamicRoleModelNode::DynamicRoleModelNode(QQuickListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1274 setNodeUpdatesEnabled(true);
1277 DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQuickListModel *owner)
1279 DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1281 object->updateValues(obj, roles);
1285 void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQuickListModel *> *targetModelHash)
1287 for (int i=0 ; i < src->m_meta->count() ; ++i) {
1288 const QByteArray &name = src->m_meta->name(i);
1289 QVariant value = src->m_meta->value(i);
1291 QQuickListModel *srcModel = qobject_cast<QQuickListModel *>(value.value<QObject *>());
1292 QQuickListModel *targetModel = qobject_cast<QQuickListModel *>(target->m_meta->value(i).value<QObject *>());
1295 if (targetModel == 0)
1296 targetModel = QQuickListModel::createWithOwner(target->m_owner);
1298 QQuickListModel::sync(srcModel, targetModel, targetModelHash);
1300 QObject *targetModelObject = targetModel;
1301 value = QVariant::fromValue(targetModelObject);
1302 } else if (targetModel) {
1306 target->setValue(name, value);
1310 void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int> &roles)
1312 const QList<QString> &keys = object.keys();
1314 QList<QString>::const_iterator it = keys.begin();
1315 QList<QString>::const_iterator end = keys.end();
1318 const QString &key = *it;
1320 int roleIndex = m_owner->m_roles.indexOf(key);
1321 if (roleIndex == -1) {
1322 roleIndex = m_owner->m_roles.count();
1323 m_owner->m_roles.append(key);
1326 QVariant value = object[key];
1328 if (value.type() == QVariant::List) {
1329 QQuickListModel *subModel = QQuickListModel::createWithOwner(m_owner);
1331 QVariantList subArray = value.toList();
1332 QVariantList::const_iterator subIt = subArray.begin();
1333 QVariantList::const_iterator subEnd = subArray.end();
1334 while (subIt != subEnd) {
1335 const QVariantMap &subObject = subIt->toMap();
1336 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1340 QObject *subModelObject = subModel;
1341 value = QVariant::fromValue(subModelObject);
1344 const QByteArray &keyUtf8 = key.toUtf8();
1346 QQuickListModel *existingModel = qobject_cast<QQuickListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1348 delete existingModel;
1350 if (m_meta->setValue(keyUtf8, value))
1357 DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1358 : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1362 DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1364 for (int i=0 ; i < count() ; ++i) {
1365 QQuickListModel *subModel = qobject_cast<QQuickListModel *>(value(i).value<QObject *>());
1371 void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1376 QVariant v = value(index);
1377 QQuickListModel *model = qobject_cast<QQuickListModel *>(v.value<QObject *>());
1382 void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1387 QQuickListModel *parentModel = m_owner->m_owner;
1389 QVariant v = value(index);
1390 if (v.type() == QVariant::List) {
1391 QQuickListModel *subModel = QQuickListModel::createWithOwner(parentModel);
1393 QVariantList subArray = v.toList();
1394 QVariantList::const_iterator subIt = subArray.begin();
1395 QVariantList::const_iterator subEnd = subArray.end();
1396 while (subIt != subEnd) {
1397 const QVariantMap &subObject = subIt->toMap();
1398 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1402 QObject *subModelObject = subModel;
1403 v = QVariant::fromValue(subModelObject);
1408 int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1409 int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1411 if (elementIndex != -1 && roleIndex != -1) {
1416 parentModel->emitItemsChanged(elementIndex, 1, roles);
1420 QQuickListModelParser::ListInstruction *QQuickListModelParser::ListModelData::instructions() const
1422 return (QQuickListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
1426 \qmlclass ListModel QQuickListModel
1427 \inqmlmodule QtQuick 2
1428 \brief Defines a free-form list data source
1429 \ingroup qtquick-models
1431 The ListModel is a simple container of ListElement definitions, each containing data roles.
1432 The contents can be defined dynamically, or explicitly in QML.
1434 The number of elements in the model can be obtained from its \l count property.
1435 A number of familiar methods are also provided to manipulate the contents of the
1436 model, including append(), insert(), move(), remove() and set(). These methods
1437 accept dictionaries as their arguments; these are translated to ListElement objects
1440 Elements can be manipulated via the model using the setProperty() method, which
1441 allows the roles of the specified element to be set and changed.
1443 \section1 Example Usage
1445 The following example shows a ListModel containing three elements, with the roles
1448 \div {class="float-right"}
1449 \inlineimage listmodel.png
1452 \snippet qml/listmodel/listmodel.qml 0
1454 Roles (properties) in each element must begin with a lower-case letter and
1455 should be common to all elements in a model. The ListElement documentation
1456 provides more guidelines for how elements should be defined.
1458 Since the example model contains an \c id property, it can be referenced
1459 by views, such as the ListView in this example:
1461 \snippet qml/listmodel/listmodel-simple.qml 0
1463 \snippet qml/listmodel/listmodel-simple.qml 1
1465 It is possible for roles to contain list data. In the following example we
1466 create a list of fruit attributes:
1468 \snippet qml/listmodel/listmodel-nested.qml model
1470 The delegate displays all the fruit attributes:
1472 \div {class="float-right"}
1473 \inlineimage listmodel-nested.png
1476 \snippet qml/listmodel/listmodel-nested.qml delegate
1478 \section1 Modifying List Models
1480 The content of a ListModel may be created and modified using the clear(),
1481 append(), set(), insert() and setProperty() methods. For example:
1483 \snippet qml/listmodel/listmodel-modify.qml delegate
1485 Note that when creating content dynamically the set of available properties
1486 cannot be changed once set. Whatever properties are first added to the model
1487 are the only permitted properties in the model.
1489 \section1 Using Threaded List Models with WorkerScript
1491 ListModel can be used together with WorkerScript access a list model
1492 from multiple threads. This is useful if list modifications are
1493 synchronous and take some time: the list operations can be moved to a
1494 different thread to avoid blocking of the main GUI thread.
1496 Here is an example that uses WorkerScript to periodically append the
1497 current time to a list model:
1499 \snippet examples/quick/threading/threadedlistmodel/timedisplay.qml 0
1501 The included file, \tt dataloader.js, looks like this:
1503 \snippet examples/quick/threading/threadedlistmodel/dataloader.js 0
1505 The timer in the main example sends messages to the worker script by calling
1506 \l WorkerScript::sendMessage(). When this message is received,
1507 \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
1508 which appends the current time to the list model.
1510 Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
1511 handler. You must call sync() or else the changes made to the list from the external
1512 thread will not be reflected in the list model in the main thread.
1514 \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml
1517 QQuickListModel::QQuickListModel(QObject *parent)
1518 : QAbstractListModel(parent)
1520 m_mainThread = true;
1523 m_uid = uidCounter.fetchAndAddOrdered(1);
1524 m_dynamicRoles = false;
1526 m_layout = new ListLayout;
1527 m_listModel = new ListModel(m_layout, this, -1);
1532 QQuickListModel::QQuickListModel(const QQuickListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
1533 : QAbstractListModel(parent)
1535 m_mainThread = owner->m_mainThread;
1537 m_agent = owner->m_agent;
1539 Q_ASSERT(owner->m_dynamicRoles == false);
1540 m_dynamicRoles = false;
1547 QQuickListModel::QQuickListModel(QQuickListModel *orig, QQuickListModelWorkerAgent *agent)
1548 : QAbstractListModel(agent)
1550 m_mainThread = false;
1553 m_dynamicRoles = orig->m_dynamicRoles;
1555 m_layout = new ListLayout(orig->m_layout);
1556 m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1559 sync(orig, this, 0);
1561 ListModel::sync(orig->m_listModel, m_listModel, 0);
1566 QQuickListModel::~QQuickListModel()
1568 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1569 delete m_modelObjects[i];
1572 m_listModel->destroy();
1575 if (m_mainThread && m_agent) {
1576 m_agent->modelDestroyed();
1587 QQuickListModel *QQuickListModel::createWithOwner(QQuickListModel *newOwner)
1589 QQuickListModel *model = new QQuickListModel;
1591 model->m_mainThread = newOwner->m_mainThread;
1592 model->m_engine = newOwner->m_engine;
1593 model->m_agent = newOwner->m_agent;
1594 model->m_dynamicRoles = newOwner->m_dynamicRoles;
1596 if (model->m_mainThread && model->m_agent)
1597 model->m_agent->addref();
1599 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1604 QV8Engine *QQuickListModel::engine() const
1606 if (m_engine == 0) {
1607 m_engine = QQmlEnginePrivate::getV8Engine(qmlEngine(this));
1613 void QQuickListModel::sync(QQuickListModel *src, QQuickListModel *target, QHash<int, QQuickListModel *> *targetModelHash)
1615 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1617 target->m_uid = src->m_uid;
1618 if (targetModelHash)
1619 targetModelHash->insert(target->m_uid, target);
1620 target->m_roles = src->m_roles;
1622 // Build hash of elements <-> uid for each of the lists
1623 QHash<int, ElementSync> elementHash;
1624 for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1625 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1626 int uid = e->getUid();
1629 elementHash.insert(uid, sync);
1631 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1632 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1633 int uid = e->getUid();
1635 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1636 if (it == elementHash.end()) {
1639 elementHash.insert(uid, sync);
1641 ElementSync &sync = it.value();
1646 // Get list of elements that are in the target but no longer in the source. These get deleted first.
1647 QHash<int, ElementSync>::iterator it = elementHash.begin();
1648 QHash<int, ElementSync>::iterator end = elementHash.end();
1650 const ElementSync &s = it.value();
1652 int targetIndex = target->m_modelObjects.indexOf(s.target);
1653 target->m_modelObjects.remove(targetIndex, 1);
1659 // Clear the target list, and append in correct order from the source
1660 target->m_modelObjects.clear();
1661 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1662 DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1663 it = elementHash.find(srcElement->getUid());
1664 const ElementSync &s = it.value();
1665 DynamicRoleModelNode *targetElement = s.target;
1666 if (targetElement == 0) {
1667 targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1669 DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1670 target->m_modelObjects.append(targetElement);
1674 void QQuickListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
1680 emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
1682 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1683 m_agent->data.changedChange(uid, index, count, roles);
1687 void QQuickListModel::emitItemsRemoved(int index, int count)
1693 beginRemoveRows(QModelIndex(), index, index + count - 1);
1695 emit countChanged();
1697 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1698 if (index == 0 && count == this->count())
1699 m_agent->data.clearChange(uid);
1700 m_agent->data.removeChange(uid, index, count);
1704 void QQuickListModel::emitItemsInserted(int index, int count)
1710 beginInsertRows(QModelIndex(), index, index + count - 1);
1712 emit countChanged();
1714 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1715 m_agent->data.insertChange(uid, index, count);
1719 void QQuickListModel::emitItemsMoved(int from, int to, int n)
1725 beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
1728 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1729 m_agent->data.moveChange(uid, from, n, to);
1733 QQuickListModelWorkerAgent *QQuickListModel::agent()
1738 m_agent = new QQuickListModelWorkerAgent(this);
1742 QModelIndex QQuickListModel::index(int row, int column, const QModelIndex &parent) const
1744 return row >= 0 && row < count() && column == 0 && !parent.isValid()
1745 ? createIndex(row, column)
1749 int QQuickListModel::rowCount(const QModelIndex &parent) const
1751 return !parent.isValid() ? count() : 0;
1754 QVariant QQuickListModel::data(const QModelIndex &index, int role) const
1756 return data(index.row(), role);
1759 QVariant QQuickListModel::data(int index, int role) const
1763 if (index >= count() || index < 0)
1767 v = m_modelObjects[index]->getValue(m_roles[role]);
1769 v = m_listModel->getProperty(index, role, this, engine());
1774 QHash<int, QByteArray> QQuickListModel::roleNames() const
1776 QHash<int, QByteArray> roleNames;
1778 if (m_dynamicRoles) {
1779 for (int i = 0 ; i < m_roles.count() ; ++i)
1780 roleNames.insert(i, m_roles.at(i).toUtf8());
1782 for (int i = 0 ; i < m_listModel->roleCount() ; ++i) {
1783 const ListLayout::Role &r = m_listModel->getExistingRole(i);
1784 roleNames.insert(i, r.name.toUtf8());
1792 \qmlproperty bool QtQuick2::ListModel::dynamicRoles
1794 By default, the type of a role is fixed the first time
1795 the role is used. For example, if you create a role called
1796 "data" and assign a number to it, you can no longer assign
1797 a string to the "data" role. However, when the dynamicRoles
1798 property is enabled, the type of a given role is not fixed
1799 and can be different between elements.
1801 The dynamicRoles property must be set before any data is
1802 added to the ListModel, and must be set from the main
1805 A ListModel that has data statically defined (via the
1806 ListElement QML syntax) cannot have the dynamicRoles
1809 There is a significant performance cost to using a
1810 ListModel with dynamic roles enabled. The cost varies
1811 from platform to platform but is typically somewhere
1812 between 4-6x slower than using static role types.
1814 Due to the performance cost of using dynamic roles,
1815 they are disabled by default.
1817 void QQuickListModel::setDynamicRoles(bool enableDynamicRoles)
1819 if (m_mainThread && m_agent == 0) {
1820 if (enableDynamicRoles) {
1821 if (m_layout->roleCount())
1822 qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
1824 m_dynamicRoles = true;
1826 if (m_roles.count()) {
1827 qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
1829 m_dynamicRoles = false;
1833 qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
1838 \qmlproperty int QtQuick2::ListModel::count
1839 The number of data entries in the model.
1841 int QQuickListModel::count() const
1846 count = m_modelObjects.count();
1848 count = m_listModel->elementCount();
1855 \qmlmethod QtQuick2::ListModel::clear()
1857 Deletes all content from the model.
1859 \sa append(), remove()
1861 void QQuickListModel::clear()
1863 int cleared = count();
1865 if (m_dynamicRoles) {
1866 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1867 delete m_modelObjects[i];
1868 m_modelObjects.clear();
1870 m_listModel->clear();
1873 emitItemsRemoved(0, cleared);
1877 \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
1879 Deletes the content at \a index from the model.
1883 void QQuickListModel::remove(QQmlV8Function *args)
1885 int argLength = args->Length();
1887 if (argLength == 1 || argLength == 2) {
1888 int index = (*args)[0]->Int32Value();
1889 int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1);
1891 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
1892 qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
1896 if (m_dynamicRoles) {
1897 for (int i=0 ; i < removeCount ; ++i)
1898 delete m_modelObjects[index+i];
1899 m_modelObjects.remove(index, removeCount);
1901 m_listModel->remove(index, removeCount);
1904 emitItemsRemoved(index, removeCount);
1906 qmlInfo(this) << tr("remove: incorrect number of arguments");
1911 \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
1913 Adds a new item to the list model at position \a index, with the
1917 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
1920 The \a index must be to an existing item in the list, or one past
1921 the end of the list (equivalent to append).
1926 void QQuickListModel::insert(QQmlV8Function *args)
1928 if (args->Length() == 2) {
1930 v8::Handle<v8::Value> arg0 = (*args)[0];
1931 int index = arg0->Int32Value();
1933 if (index < 0 || index > count()) {
1934 qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
1938 v8::Handle<v8::Value> arg1 = (*args)[1];
1940 if (arg1->IsArray()) {
1941 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
1942 int objectArrayLength = objectArray->Length();
1943 for (int i=0 ; i < objectArrayLength ; ++i) {
1944 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1946 if (m_dynamicRoles) {
1947 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1949 m_listModel->insert(index+i, argObject, args->engine());
1952 emitItemsInserted(index, objectArrayLength);
1953 } else if (arg1->IsObject()) {
1954 v8::Handle<v8::Object> argObject = arg1->ToObject();
1956 if (m_dynamicRoles) {
1957 m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1959 m_listModel->insert(index, argObject, args->engine());
1962 emitItemsInserted(index, 1);
1964 qmlInfo(this) << tr("insert: value is not an object");
1967 qmlInfo(this) << tr("insert: value is not an object");
1972 \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
1974 Moves \a n items \a from one position \a to another.
1976 The from and to ranges must exist; for example, to move the first 3 items
1977 to the end of the list:
1980 fruitModel.move(0, fruitModel.count - 3, 3)
1985 void QQuickListModel::move(int from, int to, int n)
1987 if (n==0 || from==to)
1989 if (!canMove(from, to, n)) {
1990 qmlInfo(this) << tr("move: out of range");
1994 if (m_dynamicRoles) {
1996 int realFrom = from;
2001 // Only move forwards - flip if backwards moving
2009 QPODVector<DynamicRoleModelNode *, 4> store;
2010 for (int i=0 ; i < (realTo-realFrom) ; ++i)
2011 store.append(m_modelObjects[realFrom+realN+i]);
2012 for (int i=0 ; i < realN ; ++i)
2013 store.append(m_modelObjects[realFrom+i]);
2014 for (int i=0 ; i < store.count() ; ++i)
2015 m_modelObjects[realFrom+i] = store[i];
2018 m_listModel->move(from, to, n);
2021 emitItemsMoved(from, to, n);
2025 \qmlmethod QtQuick2::ListModel::append(jsobject dict)
2027 Adds a new item to the end of the list model, with the
2031 fruitModel.append({"cost": 5.95, "name":"Pizza"})
2036 void QQuickListModel::append(QQmlV8Function *args)
2038 if (args->Length() == 1) {
2039 v8::Handle<v8::Value> arg = (*args)[0];
2041 if (arg->IsArray()) {
2042 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
2043 int objectArrayLength = objectArray->Length();
2045 int index = count();
2046 for (int i=0 ; i < objectArrayLength ; ++i) {
2047 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
2049 if (m_dynamicRoles) {
2050 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
2052 m_listModel->append(argObject, args->engine());
2056 emitItemsInserted(index, objectArrayLength);
2057 } else if (arg->IsObject()) {
2058 v8::Handle<v8::Object> argObject = arg->ToObject();
2062 if (m_dynamicRoles) {
2063 index = m_modelObjects.count();
2064 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
2066 index = m_listModel->append(argObject, args->engine());
2069 emitItemsInserted(index, 1);
2071 qmlInfo(this) << tr("append: value is not an object");
2074 qmlInfo(this) << tr("append: value is not an object");
2079 \qmlmethod object QtQuick2::ListModel::get(int index)
2081 Returns the item at \a index in the list model. This allows the item
2082 data to be accessed or modified from JavaScript:
2085 Component.onCompleted: {
2086 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2087 console.log(fruitModel.get(0).cost);
2088 fruitModel.get(0).cost = 10.95;
2092 The \a index must be an element in the list.
2094 Note that properties of the returned object that are themselves objects
2095 will also be models, and this get() method is used to access elements:
2098 fruitModel.append(..., "attributes":
2099 [{"name":"spikes","value":"7mm"},
2100 {"name":"color","value":"green"}]);
2101 fruitModel.get(0).attributes.get(1).value; // == "green"
2104 \warning The returned object is not guaranteed to remain valid. It
2105 should not be used in \l{Property Binding}{property bindings}.
2109 QQmlV8Handle QQuickListModel::get(int index) const
2111 v8::Handle<v8::Value> result = v8::Undefined();
2113 if (index >= 0 && index < count()) {
2114 QV8Engine *v8engine = engine();
2116 if (m_dynamicRoles) {
2117 DynamicRoleModelNode *object = m_modelObjects[index];
2118 result = v8engine->newQObject(object);
2120 ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQuickListModel *>(this), index);
2121 result = v8engine->newQObject(object);
2125 return QQmlV8Handle::fromHandle(result);
2129 \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
2131 Changes the item at \a index in the list model with the
2132 values in \a dict. Properties not appearing in \a dict
2136 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2139 If \a index is equal to count() then a new item is appended to the
2140 list. Otherwise, \a index must be an element in the list.
2144 void QQuickListModel::set(int index, const QQmlV8Handle &handle)
2146 v8::Handle<v8::Value> valuemap = handle.toHandle();
2148 if (!valuemap->IsObject() || valuemap->IsArray()) {
2149 qmlInfo(this) << tr("set: value is not an object");
2152 if (index > count() || index < 0) {
2153 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2157 v8::Handle<v8::Object> object = valuemap->ToObject();
2159 if (index == count()) {
2161 if (m_dynamicRoles) {
2162 m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
2164 m_listModel->insert(index, object, engine());
2167 emitItemsInserted(index, 1);
2172 if (m_dynamicRoles) {
2173 m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
2175 m_listModel->set(index, object, &roles, engine());
2179 emitItemsChanged(index, 1, roles);
2184 \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
2186 Changes the \a property of the item at \a index in the list model to \a value.
2189 fruitModel.setProperty(3, "cost", 5.95)
2192 The \a index must be an element in the list.
2196 void QQuickListModel::setProperty(int index, const QString& property, const QVariant& value)
2198 if (count() == 0 || index >= count() || index < 0) {
2199 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2203 if (m_dynamicRoles) {
2204 int roleIndex = m_roles.indexOf(property);
2205 if (roleIndex == -1) {
2206 roleIndex = m_roles.count();
2207 m_roles.append(property);
2209 if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
2212 emitItemsChanged(index, 1, roles);
2215 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2216 if (roleIndex != -1) {
2221 emitItemsChanged(index, 1, roles);
2227 \qmlmethod QtQuick2::ListModel::sync()
2229 Writes any unsaved changes to the list model after it has been modified
2230 from a worker script.
2232 void QQuickListModel::sync()
2234 // This is just a dummy method to make it look like sync() exists in
2235 // ListModel (and not just QQuickListModelWorkerAgent) and to let
2236 // us document sync().
2237 qmlInfo(this) << "List sync() can only be called from a WorkerScript";
2240 bool QQuickListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
2242 QList<QVariant> values = prop.assignedValues();
2243 for(int ii = 0; ii < values.count(); ++ii) {
2244 const QVariant &value = values.at(ii);
2246 if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
2247 QQmlCustomParserNode node =
2248 qvariant_cast<QQmlCustomParserNode>(value);
2250 if (node.name() != listElementTypeName) {
2251 const QMetaObject *mo = resolveType(node.name());
2252 if (mo != &QQuickListElement::staticMetaObject) {
2253 error(node, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2256 listElementTypeName = node.name(); // cache right name for next time
2261 li.type = ListInstruction::Push;
2266 QList<QQmlCustomParserProperty> props = node.properties();
2267 for(int jj = 0; jj < props.count(); ++jj) {
2268 const QQmlCustomParserProperty &nodeProp = props.at(jj);
2269 if (nodeProp.name().isEmpty()) {
2270 error(nodeProp, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2273 if (nodeProp.name() == QStringLiteral("id")) {
2274 error(nodeProp, QQuickListModel::tr("ListElement: cannot use reserved \"id\" property"));
2279 int ref = data.count();
2280 data.append(nodeProp.name().toUtf8());
2282 li.type = ListInstruction::Set;
2286 if(!compileProperty(nodeProp, instr, data))
2289 li.type = ListInstruction::Pop;
2296 li.type = ListInstruction::Pop;
2303 QQmlScript::Variant variant =
2304 qvariant_cast<QQmlScript::Variant>(value);
2306 int ref = data.count();
2309 d += char(variant.type()); // type tag
2310 if (variant.isString()) {
2311 d += variant.asString().toUtf8();
2312 } else if (variant.isNumber()) {
2313 d += QByteArray::number(variant.asNumber(),'g',20);
2314 } else if (variant.isBoolean()) {
2315 d += char(variant.asBoolean());
2316 } else if (variant.isScript()) {
2317 if (definesEmptyList(variant.asScript())) {
2318 d[0] = char(QQmlScript::Variant::Invalid); // marks empty list
2320 QByteArray script = variant.asScript().toUtf8();
2322 int v = evaluateEnum(script, &ok);
2324 using namespace QQmlJS;
2325 AST::Node *node = variant.asAST();
2326 AST::StringLiteral *literal = 0;
2327 if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
2328 if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
2329 if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
2330 if (callExpr->arguments && !callExpr->arguments->next)
2331 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
2333 error(prop, QQuickListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
2336 } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
2337 if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
2338 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
2340 error(prop, QQuickListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
2348 d[0] = char(QQmlScript::Variant::String);
2349 d += literal->value.toUtf8();
2351 error(prop, QQuickListModel::tr("ListElement: cannot use script for property value"));
2355 d[0] = char(QQmlScript::Variant::Number);
2356 d += QByteArray::number(v);
2364 li.type = ListInstruction::Value;
2373 QByteArray QQuickListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps)
2375 QList<ListInstruction> instr;
2377 listElementTypeName = QString(); // unknown
2379 for(int ii = 0; ii < customProps.count(); ++ii) {
2380 const QQmlCustomParserProperty &prop = customProps.at(ii);
2381 if(!prop.name().isEmpty()) { // isn't default property
2382 error(prop, QQuickListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
2383 return QByteArray();
2386 if(!compileProperty(prop, instr, data)) {
2387 return QByteArray();
2391 int size = sizeof(ListModelData) +
2392 instr.count() * sizeof(ListInstruction) +
2398 ListModelData *lmd = (ListModelData *)rv.data();
2399 lmd->dataOffset = sizeof(ListModelData) +
2400 instr.count() * sizeof(ListInstruction);
2401 lmd->instrCount = instr.count();
2402 for (int ii = 0; ii < instr.count(); ++ii)
2403 lmd->instructions()[ii] = instr.at(ii);
2404 ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
2409 void QQuickListModelParser::setCustomData(QObject *obj, const QByteArray &d)
2411 QQuickListModel *rv = static_cast<QQuickListModel *>(obj);
2413 QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv));
2414 rv->m_engine = engine;
2416 const ListModelData *lmd = (const ListModelData *)d.constData();
2417 const char *data = ((const char *)lmd) + lmd->dataOffset;
2419 bool setRoles = false;
2421 QStack<DataStackElement> stack;
2423 for (int ii = 0; ii < lmd->instrCount; ++ii) {
2424 const ListInstruction &instr = lmd->instructions()[ii];
2426 switch(instr.type) {
2427 case ListInstruction::Push:
2429 Q_ASSERT(!rv->m_dynamicRoles);
2431 ListModel *subModel = 0;
2433 if (stack.count() == 0) {
2434 subModel = rv->m_listModel;
2436 const DataStackElement &e0 = stack.at(stack.size() - 1);
2437 DataStackElement &e1 = stack[stack.size() - 2];
2439 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2440 if (role.type == ListLayout::Role::List) {
2441 subModel = e1.model->getListProperty(e1.elementIndex, role);
2443 if (subModel == 0) {
2444 subModel = new ListModel(role.subLayout, 0, -1);
2445 QVariant vModel = QVariant::fromValue(subModel);
2446 e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
2453 e.elementIndex = subModel ? subModel->appendElement() : -1;
2458 case ListInstruction::Pop:
2462 case ListInstruction::Value:
2464 const DataStackElement &e0 = stack.at(stack.size() - 1);
2465 DataStackElement &e1 = stack[stack.size() - 2];
2467 QString name = e0.name;
2470 switch (QQmlScript::Variant::Type(data[instr.dataIdx])) {
2471 case QQmlScript::Variant::Invalid:
2473 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2474 ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2475 value = QVariant::fromValue(emptyModel);
2478 case QQmlScript::Variant::Boolean:
2479 value = bool(data[1 + instr.dataIdx]);
2481 case QQmlScript::Variant::Number:
2482 value = QByteArray(data + 1 + instr.dataIdx).toDouble();
2484 case QQmlScript::Variant::String:
2485 value = QString::fromUtf8(data + 1 + instr.dataIdx);
2488 Q_ASSERT("Format error in ListInstruction");
2491 e1.model->setOrCreateProperty(e1.elementIndex, name, value);
2496 case ListInstruction::Set:
2499 e.name = QString::fromUtf8(data + instr.dataIdx);
2506 if (setRoles == false)
2507 qmlInfo(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
2510 bool QQuickListModelParser::definesEmptyList(const QString &s)
2512 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2513 for (int i=1; i<s.length()-1; i++) {
2514 if (!s[i].isSpace())
2524 \qmlclass ListElement QQuickListElement
2525 \inqmlmodule QtQuick 2
2526 \brief Defines a data item in a ListModel
2527 \ingroup qtquick-models
2529 List elements are defined inside ListModel definitions, and represent items in a
2530 list that will be displayed using ListView or \l Repeater items.
2532 List elements are defined like other QML elements except that they contain
2533 a collection of \e role definitions instead of properties. Using the same
2534 syntax as property definitions, roles both define how the data is accessed
2535 and include the data itself.
2537 The names used for roles must begin with a lower-case letter and should be
2538 common to all elements in a given model. Values must be simple constants; either
2539 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2540 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2542 \section1 Referencing Roles
2544 The role names are used by delegates to obtain data from list elements.
2545 Each role name is accessible in the delegate's scope, and refers to the
2546 corresponding role in the current element. Where a role name would be
2547 ambiguous to use, it can be accessed via the \l{ListView::}{model}
2548 property (e.g., \c{model.cost} instead of \c{cost}).
2550 \section1 Example Usage
2552 The following model defines a series of list elements, each of which
2553 contain "name" and "cost" roles and their associated values.
2555 \snippet qml/listmodel/listelements.qml model
2557 The delegate obtains the name and cost for each element by simply referring
2558 to \c name and \c cost:
2560 \snippet qml/listmodel/listelements.qml view