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));
1427 \instantiates QQuickListModel
1428 \inqmlmodule QtQuick 2
1429 \brief Defines a free-form list data source
1430 \ingroup qtquick-models
1432 The ListModel is a simple container of ListElement definitions, each containing data roles.
1433 The contents can be defined dynamically, or explicitly in QML.
1435 The number of elements in the model can be obtained from its \l count property.
1436 A number of familiar methods are also provided to manipulate the contents of the
1437 model, including append(), insert(), move(), remove() and set(). These methods
1438 accept dictionaries as their arguments; these are translated to ListElement objects
1441 Elements can be manipulated via the model using the setProperty() method, which
1442 allows the roles of the specified element to be set and changed.
1444 \section1 Example Usage
1446 The following example shows a ListModel containing three elements, with the roles
1449 \div {class="float-right"}
1450 \inlineimage listmodel.png
1453 \snippet qml/listmodel/listmodel.qml 0
1455 Roles (properties) in each element must begin with a lower-case letter and
1456 should be common to all elements in a model. The ListElement documentation
1457 provides more guidelines for how elements should be defined.
1459 Since the example model contains an \c id property, it can be referenced
1460 by views, such as the ListView in this example:
1462 \snippet qml/listmodel/listmodel-simple.qml 0
1464 \snippet qml/listmodel/listmodel-simple.qml 1
1466 It is possible for roles to contain list data. In the following example we
1467 create a list of fruit attributes:
1469 \snippet qml/listmodel/listmodel-nested.qml model
1471 The delegate displays all the fruit attributes:
1473 \div {class="float-right"}
1474 \inlineimage listmodel-nested.png
1477 \snippet qml/listmodel/listmodel-nested.qml delegate
1479 \section1 Modifying List Models
1481 The content of a ListModel may be created and modified using the clear(),
1482 append(), set(), insert() and setProperty() methods. For example:
1484 \snippet qml/listmodel/listmodel-modify.qml delegate
1486 Note that when creating content dynamically the set of available properties
1487 cannot be changed once set. Whatever properties are first added to the model
1488 are the only permitted properties in the model.
1490 \section1 Using Threaded List Models with WorkerScript
1492 ListModel can be used together with WorkerScript access a list model
1493 from multiple threads. This is useful if list modifications are
1494 synchronous and take some time: the list operations can be moved to a
1495 different thread to avoid blocking of the main GUI thread.
1497 Here is an example that uses WorkerScript to periodically append the
1498 current time to a list model:
1500 \snippet examples/quick/threading/threadedlistmodel/timedisplay.qml 0
1502 The included file, \tt dataloader.js, looks like this:
1504 \snippet examples/quick/threading/threadedlistmodel/dataloader.js 0
1506 The timer in the main example sends messages to the worker script by calling
1507 \l WorkerScript::sendMessage(). When this message is received,
1508 \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
1509 which appends the current time to the list model.
1511 Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
1512 handler. You must call sync() or else the changes made to the list from the external
1513 thread will not be reflected in the list model in the main thread.
1515 \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml
1518 QQuickListModel::QQuickListModel(QObject *parent)
1519 : QAbstractListModel(parent)
1521 m_mainThread = true;
1524 m_uid = uidCounter.fetchAndAddOrdered(1);
1525 m_dynamicRoles = false;
1527 m_layout = new ListLayout;
1528 m_listModel = new ListModel(m_layout, this, -1);
1533 QQuickListModel::QQuickListModel(const QQuickListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
1534 : QAbstractListModel(parent)
1536 m_mainThread = owner->m_mainThread;
1538 m_agent = owner->m_agent;
1540 Q_ASSERT(owner->m_dynamicRoles == false);
1541 m_dynamicRoles = false;
1548 QQuickListModel::QQuickListModel(QQuickListModel *orig, QQuickListModelWorkerAgent *agent)
1549 : QAbstractListModel(agent)
1551 m_mainThread = false;
1554 m_dynamicRoles = orig->m_dynamicRoles;
1556 m_layout = new ListLayout(orig->m_layout);
1557 m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1560 sync(orig, this, 0);
1562 ListModel::sync(orig->m_listModel, m_listModel, 0);
1567 QQuickListModel::~QQuickListModel()
1569 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1570 delete m_modelObjects[i];
1573 m_listModel->destroy();
1576 if (m_mainThread && m_agent) {
1577 m_agent->modelDestroyed();
1588 QQuickListModel *QQuickListModel::createWithOwner(QQuickListModel *newOwner)
1590 QQuickListModel *model = new QQuickListModel;
1592 model->m_mainThread = newOwner->m_mainThread;
1593 model->m_engine = newOwner->m_engine;
1594 model->m_agent = newOwner->m_agent;
1595 model->m_dynamicRoles = newOwner->m_dynamicRoles;
1597 if (model->m_mainThread && model->m_agent)
1598 model->m_agent->addref();
1600 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1605 QV8Engine *QQuickListModel::engine() const
1607 if (m_engine == 0) {
1608 m_engine = QQmlEnginePrivate::getV8Engine(qmlEngine(this));
1614 void QQuickListModel::sync(QQuickListModel *src, QQuickListModel *target, QHash<int, QQuickListModel *> *targetModelHash)
1616 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1618 target->m_uid = src->m_uid;
1619 if (targetModelHash)
1620 targetModelHash->insert(target->m_uid, target);
1621 target->m_roles = src->m_roles;
1623 // Build hash of elements <-> uid for each of the lists
1624 QHash<int, ElementSync> elementHash;
1625 for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1626 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1627 int uid = e->getUid();
1630 elementHash.insert(uid, sync);
1632 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1633 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1634 int uid = e->getUid();
1636 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1637 if (it == elementHash.end()) {
1640 elementHash.insert(uid, sync);
1642 ElementSync &sync = it.value();
1647 // Get list of elements that are in the target but no longer in the source. These get deleted first.
1648 QHash<int, ElementSync>::iterator it = elementHash.begin();
1649 QHash<int, ElementSync>::iterator end = elementHash.end();
1651 const ElementSync &s = it.value();
1653 int targetIndex = target->m_modelObjects.indexOf(s.target);
1654 target->m_modelObjects.remove(targetIndex, 1);
1660 // Clear the target list, and append in correct order from the source
1661 target->m_modelObjects.clear();
1662 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1663 DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1664 it = elementHash.find(srcElement->getUid());
1665 const ElementSync &s = it.value();
1666 DynamicRoleModelNode *targetElement = s.target;
1667 if (targetElement == 0) {
1668 targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1670 DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1671 target->m_modelObjects.append(targetElement);
1675 void QQuickListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
1681 emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
1683 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1684 m_agent->data.changedChange(uid, index, count, roles);
1688 void QQuickListModel::emitItemsRemoved(int index, int count)
1694 beginRemoveRows(QModelIndex(), index, index + count - 1);
1696 emit countChanged();
1698 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1699 if (index == 0 && count == this->count())
1700 m_agent->data.clearChange(uid);
1701 m_agent->data.removeChange(uid, index, count);
1705 void QQuickListModel::emitItemsInserted(int index, int count)
1711 beginInsertRows(QModelIndex(), index, index + count - 1);
1713 emit countChanged();
1715 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1716 m_agent->data.insertChange(uid, index, count);
1720 void QQuickListModel::emitItemsMoved(int from, int to, int n)
1726 beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
1729 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1730 m_agent->data.moveChange(uid, from, n, to);
1734 QQuickListModelWorkerAgent *QQuickListModel::agent()
1739 m_agent = new QQuickListModelWorkerAgent(this);
1743 QModelIndex QQuickListModel::index(int row, int column, const QModelIndex &parent) const
1745 return row >= 0 && row < count() && column == 0 && !parent.isValid()
1746 ? createIndex(row, column)
1750 int QQuickListModel::rowCount(const QModelIndex &parent) const
1752 return !parent.isValid() ? count() : 0;
1755 QVariant QQuickListModel::data(const QModelIndex &index, int role) const
1757 return data(index.row(), role);
1760 QVariant QQuickListModel::data(int index, int role) const
1764 if (index >= count() || index < 0)
1768 v = m_modelObjects[index]->getValue(m_roles[role]);
1770 v = m_listModel->getProperty(index, role, this, engine());
1775 QHash<int, QByteArray> QQuickListModel::roleNames() const
1777 QHash<int, QByteArray> roleNames;
1779 if (m_dynamicRoles) {
1780 for (int i = 0 ; i < m_roles.count() ; ++i)
1781 roleNames.insert(i, m_roles.at(i).toUtf8());
1783 for (int i = 0 ; i < m_listModel->roleCount() ; ++i) {
1784 const ListLayout::Role &r = m_listModel->getExistingRole(i);
1785 roleNames.insert(i, r.name.toUtf8());
1793 \qmlproperty bool QtQuick2::ListModel::dynamicRoles
1795 By default, the type of a role is fixed the first time
1796 the role is used. For example, if you create a role called
1797 "data" and assign a number to it, you can no longer assign
1798 a string to the "data" role. However, when the dynamicRoles
1799 property is enabled, the type of a given role is not fixed
1800 and can be different between elements.
1802 The dynamicRoles property must be set before any data is
1803 added to the ListModel, and must be set from the main
1806 A ListModel that has data statically defined (via the
1807 ListElement QML syntax) cannot have the dynamicRoles
1810 There is a significant performance cost to using a
1811 ListModel with dynamic roles enabled. The cost varies
1812 from platform to platform but is typically somewhere
1813 between 4-6x slower than using static role types.
1815 Due to the performance cost of using dynamic roles,
1816 they are disabled by default.
1818 void QQuickListModel::setDynamicRoles(bool enableDynamicRoles)
1820 if (m_mainThread && m_agent == 0) {
1821 if (enableDynamicRoles) {
1822 if (m_layout->roleCount())
1823 qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
1825 m_dynamicRoles = true;
1827 if (m_roles.count()) {
1828 qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
1830 m_dynamicRoles = false;
1834 qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
1839 \qmlproperty int QtQuick2::ListModel::count
1840 The number of data entries in the model.
1842 int QQuickListModel::count() const
1847 count = m_modelObjects.count();
1849 count = m_listModel->elementCount();
1856 \qmlmethod QtQuick2::ListModel::clear()
1858 Deletes all content from the model.
1860 \sa append(), remove()
1862 void QQuickListModel::clear()
1864 int cleared = count();
1866 if (m_dynamicRoles) {
1867 for (int i=0 ; i < m_modelObjects.count() ; ++i)
1868 delete m_modelObjects[i];
1869 m_modelObjects.clear();
1871 m_listModel->clear();
1874 emitItemsRemoved(0, cleared);
1878 \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
1880 Deletes the content at \a index from the model.
1884 void QQuickListModel::remove(QQmlV8Function *args)
1886 int argLength = args->Length();
1888 if (argLength == 1 || argLength == 2) {
1889 int index = (*args)[0]->Int32Value();
1890 int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1);
1892 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
1893 qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
1897 if (m_dynamicRoles) {
1898 for (int i=0 ; i < removeCount ; ++i)
1899 delete m_modelObjects[index+i];
1900 m_modelObjects.remove(index, removeCount);
1902 m_listModel->remove(index, removeCount);
1905 emitItemsRemoved(index, removeCount);
1907 qmlInfo(this) << tr("remove: incorrect number of arguments");
1912 \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
1914 Adds a new item to the list model at position \a index, with the
1918 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
1921 The \a index must be to an existing item in the list, or one past
1922 the end of the list (equivalent to append).
1927 void QQuickListModel::insert(QQmlV8Function *args)
1929 if (args->Length() == 2) {
1931 v8::Handle<v8::Value> arg0 = (*args)[0];
1932 int index = arg0->Int32Value();
1934 if (index < 0 || index > count()) {
1935 qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
1939 v8::Handle<v8::Value> arg1 = (*args)[1];
1941 if (arg1->IsArray()) {
1942 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
1943 int objectArrayLength = objectArray->Length();
1944 for (int i=0 ; i < objectArrayLength ; ++i) {
1945 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1947 if (m_dynamicRoles) {
1948 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1950 m_listModel->insert(index+i, argObject, args->engine());
1953 emitItemsInserted(index, objectArrayLength);
1954 } else if (arg1->IsObject()) {
1955 v8::Handle<v8::Object> argObject = arg1->ToObject();
1957 if (m_dynamicRoles) {
1958 m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1960 m_listModel->insert(index, argObject, args->engine());
1963 emitItemsInserted(index, 1);
1965 qmlInfo(this) << tr("insert: value is not an object");
1968 qmlInfo(this) << tr("insert: value is not an object");
1973 \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
1975 Moves \a n items \a from one position \a to another.
1977 The from and to ranges must exist; for example, to move the first 3 items
1978 to the end of the list:
1981 fruitModel.move(0, fruitModel.count - 3, 3)
1986 void QQuickListModel::move(int from, int to, int n)
1988 if (n==0 || from==to)
1990 if (!canMove(from, to, n)) {
1991 qmlInfo(this) << tr("move: out of range");
1995 if (m_dynamicRoles) {
1997 int realFrom = from;
2002 // Only move forwards - flip if backwards moving
2010 QPODVector<DynamicRoleModelNode *, 4> store;
2011 for (int i=0 ; i < (realTo-realFrom) ; ++i)
2012 store.append(m_modelObjects[realFrom+realN+i]);
2013 for (int i=0 ; i < realN ; ++i)
2014 store.append(m_modelObjects[realFrom+i]);
2015 for (int i=0 ; i < store.count() ; ++i)
2016 m_modelObjects[realFrom+i] = store[i];
2019 m_listModel->move(from, to, n);
2022 emitItemsMoved(from, to, n);
2026 \qmlmethod QtQuick2::ListModel::append(jsobject dict)
2028 Adds a new item to the end of the list model, with the
2032 fruitModel.append({"cost": 5.95, "name":"Pizza"})
2037 void QQuickListModel::append(QQmlV8Function *args)
2039 if (args->Length() == 1) {
2040 v8::Handle<v8::Value> arg = (*args)[0];
2042 if (arg->IsArray()) {
2043 v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
2044 int objectArrayLength = objectArray->Length();
2046 int index = count();
2047 for (int i=0 ; i < objectArrayLength ; ++i) {
2048 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
2050 if (m_dynamicRoles) {
2051 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
2053 m_listModel->append(argObject, args->engine());
2057 emitItemsInserted(index, objectArrayLength);
2058 } else if (arg->IsObject()) {
2059 v8::Handle<v8::Object> argObject = arg->ToObject();
2063 if (m_dynamicRoles) {
2064 index = m_modelObjects.count();
2065 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
2067 index = m_listModel->append(argObject, args->engine());
2070 emitItemsInserted(index, 1);
2072 qmlInfo(this) << tr("append: value is not an object");
2075 qmlInfo(this) << tr("append: value is not an object");
2080 \qmlmethod object QtQuick2::ListModel::get(int index)
2082 Returns the item at \a index in the list model. This allows the item
2083 data to be accessed or modified from JavaScript:
2086 Component.onCompleted: {
2087 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2088 console.log(fruitModel.get(0).cost);
2089 fruitModel.get(0).cost = 10.95;
2093 The \a index must be an element in the list.
2095 Note that properties of the returned object that are themselves objects
2096 will also be models, and this get() method is used to access elements:
2099 fruitModel.append(..., "attributes":
2100 [{"name":"spikes","value":"7mm"},
2101 {"name":"color","value":"green"}]);
2102 fruitModel.get(0).attributes.get(1).value; // == "green"
2105 \warning The returned object is not guaranteed to remain valid. It
2106 should not be used in \l{Property Binding}{property bindings}.
2110 QQmlV8Handle QQuickListModel::get(int index) const
2112 v8::Handle<v8::Value> result = v8::Undefined();
2114 if (index >= 0 && index < count()) {
2115 QV8Engine *v8engine = engine();
2117 if (m_dynamicRoles) {
2118 DynamicRoleModelNode *object = m_modelObjects[index];
2119 result = v8engine->newQObject(object);
2121 ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQuickListModel *>(this), index);
2122 result = v8engine->newQObject(object);
2126 return QQmlV8Handle::fromHandle(result);
2130 \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
2132 Changes the item at \a index in the list model with the
2133 values in \a dict. Properties not appearing in \a dict
2137 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2140 If \a index is equal to count() then a new item is appended to the
2141 list. Otherwise, \a index must be an element in the list.
2145 void QQuickListModel::set(int index, const QQmlV8Handle &handle)
2147 v8::Handle<v8::Value> valuemap = handle.toHandle();
2149 if (!valuemap->IsObject() || valuemap->IsArray()) {
2150 qmlInfo(this) << tr("set: value is not an object");
2153 if (index > count() || index < 0) {
2154 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2158 v8::Handle<v8::Object> object = valuemap->ToObject();
2160 if (index == count()) {
2162 if (m_dynamicRoles) {
2163 m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
2165 m_listModel->insert(index, object, engine());
2168 emitItemsInserted(index, 1);
2173 if (m_dynamicRoles) {
2174 m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
2176 m_listModel->set(index, object, &roles, engine());
2180 emitItemsChanged(index, 1, roles);
2185 \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
2187 Changes the \a property of the item at \a index in the list model to \a value.
2190 fruitModel.setProperty(3, "cost", 5.95)
2193 The \a index must be an element in the list.
2197 void QQuickListModel::setProperty(int index, const QString& property, const QVariant& value)
2199 if (count() == 0 || index >= count() || index < 0) {
2200 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2204 if (m_dynamicRoles) {
2205 int roleIndex = m_roles.indexOf(property);
2206 if (roleIndex == -1) {
2207 roleIndex = m_roles.count();
2208 m_roles.append(property);
2210 if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
2213 emitItemsChanged(index, 1, roles);
2216 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2217 if (roleIndex != -1) {
2222 emitItemsChanged(index, 1, roles);
2228 \qmlmethod QtQuick2::ListModel::sync()
2230 Writes any unsaved changes to the list model after it has been modified
2231 from a worker script.
2233 void QQuickListModel::sync()
2235 // This is just a dummy method to make it look like sync() exists in
2236 // ListModel (and not just QQuickListModelWorkerAgent) and to let
2237 // us document sync().
2238 qmlInfo(this) << "List sync() can only be called from a WorkerScript";
2241 bool QQuickListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
2243 QList<QVariant> values = prop.assignedValues();
2244 for(int ii = 0; ii < values.count(); ++ii) {
2245 const QVariant &value = values.at(ii);
2247 if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
2248 QQmlCustomParserNode node =
2249 qvariant_cast<QQmlCustomParserNode>(value);
2251 if (node.name() != listElementTypeName) {
2252 const QMetaObject *mo = resolveType(node.name());
2253 if (mo != &QQuickListElement::staticMetaObject) {
2254 error(node, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2257 listElementTypeName = node.name(); // cache right name for next time
2262 li.type = ListInstruction::Push;
2267 QList<QQmlCustomParserProperty> props = node.properties();
2268 for(int jj = 0; jj < props.count(); ++jj) {
2269 const QQmlCustomParserProperty &nodeProp = props.at(jj);
2270 if (nodeProp.name().isEmpty()) {
2271 error(nodeProp, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2274 if (nodeProp.name() == QStringLiteral("id")) {
2275 error(nodeProp, QQuickListModel::tr("ListElement: cannot use reserved \"id\" property"));
2280 int ref = data.count();
2281 data.append(nodeProp.name().toUtf8());
2283 li.type = ListInstruction::Set;
2287 if(!compileProperty(nodeProp, instr, data))
2290 li.type = ListInstruction::Pop;
2297 li.type = ListInstruction::Pop;
2304 QQmlScript::Variant variant =
2305 qvariant_cast<QQmlScript::Variant>(value);
2307 int ref = data.count();
2310 d += char(variant.type()); // type tag
2311 if (variant.isString()) {
2312 d += variant.asString().toUtf8();
2313 } else if (variant.isNumber()) {
2314 d += QByteArray::number(variant.asNumber(),'g',20);
2315 } else if (variant.isBoolean()) {
2316 d += char(variant.asBoolean());
2317 } else if (variant.isScript()) {
2318 if (definesEmptyList(variant.asScript())) {
2319 d[0] = char(QQmlScript::Variant::Invalid); // marks empty list
2321 QByteArray script = variant.asScript().toUtf8();
2323 int v = evaluateEnum(script, &ok);
2325 using namespace QQmlJS;
2326 AST::Node *node = variant.asAST();
2327 AST::StringLiteral *literal = 0;
2328 if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
2329 if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
2330 if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
2331 if (callExpr->arguments && !callExpr->arguments->next)
2332 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
2334 error(prop, QQuickListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
2337 } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
2338 if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
2339 literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
2341 error(prop, QQuickListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
2349 d[0] = char(QQmlScript::Variant::String);
2350 d += literal->value.toUtf8();
2352 error(prop, QQuickListModel::tr("ListElement: cannot use script for property value"));
2356 d[0] = char(QQmlScript::Variant::Number);
2357 d += QByteArray::number(v);
2365 li.type = ListInstruction::Value;
2374 QByteArray QQuickListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps)
2376 QList<ListInstruction> instr;
2378 listElementTypeName = QString(); // unknown
2380 for(int ii = 0; ii < customProps.count(); ++ii) {
2381 const QQmlCustomParserProperty &prop = customProps.at(ii);
2382 if(!prop.name().isEmpty()) { // isn't default property
2383 error(prop, QQuickListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
2384 return QByteArray();
2387 if(!compileProperty(prop, instr, data)) {
2388 return QByteArray();
2392 int size = sizeof(ListModelData) +
2393 instr.count() * sizeof(ListInstruction) +
2399 ListModelData *lmd = (ListModelData *)rv.data();
2400 lmd->dataOffset = sizeof(ListModelData) +
2401 instr.count() * sizeof(ListInstruction);
2402 lmd->instrCount = instr.count();
2403 for (int ii = 0; ii < instr.count(); ++ii)
2404 lmd->instructions()[ii] = instr.at(ii);
2405 ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
2410 void QQuickListModelParser::setCustomData(QObject *obj, const QByteArray &d)
2412 QQuickListModel *rv = static_cast<QQuickListModel *>(obj);
2414 QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv));
2415 rv->m_engine = engine;
2417 const ListModelData *lmd = (const ListModelData *)d.constData();
2418 const char *data = ((const char *)lmd) + lmd->dataOffset;
2420 bool setRoles = false;
2422 QStack<DataStackElement> stack;
2424 for (int ii = 0; ii < lmd->instrCount; ++ii) {
2425 const ListInstruction &instr = lmd->instructions()[ii];
2427 switch(instr.type) {
2428 case ListInstruction::Push:
2430 Q_ASSERT(!rv->m_dynamicRoles);
2432 ListModel *subModel = 0;
2434 if (stack.count() == 0) {
2435 subModel = rv->m_listModel;
2437 const DataStackElement &e0 = stack.at(stack.size() - 1);
2438 DataStackElement &e1 = stack[stack.size() - 2];
2440 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2441 if (role.type == ListLayout::Role::List) {
2442 subModel = e1.model->getListProperty(e1.elementIndex, role);
2444 if (subModel == 0) {
2445 subModel = new ListModel(role.subLayout, 0, -1);
2446 QVariant vModel = QVariant::fromValue(subModel);
2447 e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
2454 e.elementIndex = subModel ? subModel->appendElement() : -1;
2459 case ListInstruction::Pop:
2463 case ListInstruction::Value:
2465 const DataStackElement &e0 = stack.at(stack.size() - 1);
2466 DataStackElement &e1 = stack[stack.size() - 2];
2468 QString name = e0.name;
2471 switch (QQmlScript::Variant::Type(data[instr.dataIdx])) {
2472 case QQmlScript::Variant::Invalid:
2474 const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2475 ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2476 value = QVariant::fromValue(emptyModel);
2479 case QQmlScript::Variant::Boolean:
2480 value = bool(data[1 + instr.dataIdx]);
2482 case QQmlScript::Variant::Number:
2483 value = QByteArray(data + 1 + instr.dataIdx).toDouble();
2485 case QQmlScript::Variant::String:
2486 value = QString::fromUtf8(data + 1 + instr.dataIdx);
2489 Q_ASSERT("Format error in ListInstruction");
2492 e1.model->setOrCreateProperty(e1.elementIndex, name, value);
2497 case ListInstruction::Set:
2500 e.name = QString::fromUtf8(data + instr.dataIdx);
2507 if (setRoles == false)
2508 qmlInfo(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
2511 bool QQuickListModelParser::definesEmptyList(const QString &s)
2513 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2514 for (int i=1; i<s.length()-1; i++) {
2515 if (!s[i].isSpace())
2525 \qmltype ListElement
2526 \instantiates QQuickListElement
2527 \inqmlmodule QtQuick 2
2528 \brief Defines a data item in a ListModel
2529 \ingroup qtquick-models
2531 List elements are defined inside ListModel definitions, and represent items in a
2532 list that will be displayed using ListView or \l Repeater items.
2534 List elements are defined like other QML elements except that they contain
2535 a collection of \e role definitions instead of properties. Using the same
2536 syntax as property definitions, roles both define how the data is accessed
2537 and include the data itself.
2539 The names used for roles must begin with a lower-case letter and should be
2540 common to all elements in a given model. Values must be simple constants; either
2541 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2542 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2544 \section1 Referencing Roles
2546 The role names are used by delegates to obtain data from list elements.
2547 Each role name is accessible in the delegate's scope, and refers to the
2548 corresponding role in the current element. Where a role name would be
2549 ambiguous to use, it can be accessed via the \l{ListView::}{model}
2550 property (e.g., \c{model.cost} instead of \c{cost}).
2552 \section1 Example Usage
2554 The following model defines a series of list elements, each of which
2555 contain "name" and "cost" roles and their associated values.
2557 \snippet qml/listmodel/listelements.qml model
2559 The delegate obtains the name and cost for each element by simply referring
2560 to \c name and \c cost:
2562 \snippet qml/listmodel/listelements.qml view