Rename QDeclarative symbols to QQuick and QQml
[profile/ivi/qtdeclarative.git] / src / qml / qml / qquicklistmodel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
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>
47
48 #include <private/qqmlcustomparser_p.h>
49 #include <private/qqmlscript_p.h>
50 #include <private/qqmlengine_p.h>
51 #include <qqmlcontext.h>
52 #include <qqmlinfo.h>
53
54 #include <QtCore/qdebug.h>
55 #include <QtCore/qstack.h>
56 #include <QXmlStreamReader>
57
58 Q_DECLARE_METATYPE(QListModelInterface *)
59
60 QT_BEGIN_NAMESPACE
61
62 // Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
63 enum { MIN_LISTMODEL_UID = 1024 };
64
65 static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
66
67 template <typename T>
68 static bool isMemoryUsed(const char *mem)
69 {
70     for (size_t i=0 ; i < sizeof(T) ; ++i) {
71         if (mem[i] != 0)
72             return true;
73     }
74
75     return false;
76 }
77
78 static QString roleTypeName(ListLayout::Role::DataType t)
79 {
80     QString result;
81     const char *roleTypeNames[] = { "String", "Number", "Bool", "List", "QObject", "VariantMap" };
82
83     if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
84         result = QString::fromLatin1(roleTypeNames[t]);
85
86     return result;
87 }
88
89 const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
90 {
91     QStringHash<Role *>::Node *node = roleHash.findNode(key);
92     if (node) {
93         const Role &r = *node->value;
94         if (type != r.type)
95             qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
96         return r;
97     }
98
99     return createRole(key, type);
100 }
101
102 const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key, Role::DataType type)
103 {
104     QHashedV8String hashedKey(key);
105     QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
106     if (node) {
107         const Role &r = *node->value;
108         if (type != r.type)
109             qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
110         return r;
111     }
112
113     QString qkey;
114     qkey.resize(key->Length());
115     key->Write(reinterpret_cast<uint16_t*>(qkey.data()));
116
117     return createRole(qkey, type);
118 }
119
120 const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
121 {
122     const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QQmlGuard<QObject>), sizeof(QVariantMap) };
123     const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap) };
124
125     Role *r = new Role;
126     r->name = key;
127     r->type = type;
128
129     if (type == Role::List) {
130         r->subLayout = new ListLayout;
131     } else {
132         r->subLayout = 0;
133     }
134
135     int dataSize = dataSizes[type];
136     int dataAlignment = dataAlignments[type];
137
138     int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
139     if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
140         r->blockIndex = ++currentBlock;
141         r->blockOffset = 0;
142         currentBlockOffset = dataSize;
143     } else {
144         r->blockIndex = currentBlock;
145         r->blockOffset = dataOffset;
146         currentBlockOffset = dataOffset + dataSize;
147     }
148
149     int roleIndex = roles.count();
150     r->index = roleIndex;
151
152     roles.append(r);
153     roleHash.insert(key, r);
154
155     return *r;
156 }
157
158 ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
159 {
160     for (int i=0 ; i < other->roles.count() ; ++i) {
161         Role *role = new Role(other->roles[i]);
162         roles.append(role);
163         roleHash.insert(role->name, role);
164     }
165     currentBlockOffset = other->currentBlockOffset;
166     currentBlock = other->currentBlock;
167 }
168
169 ListLayout::~ListLayout()
170 {
171     for (int i=0 ; i < roles.count() ; ++i) {
172         delete roles[i];
173     }
174 }
175
176 void ListLayout::sync(ListLayout *src, ListLayout *target)
177 {
178     int roleOffset = target->roles.count();
179     int newRoleCount = src->roles.count() - roleOffset;
180
181     for (int i=0 ; i < newRoleCount ; ++i) {
182         Role *role = new Role(src->roles[roleOffset + i]);
183         target->roles.append(role);
184         target->roleHash.insert(role->name, role);
185     }
186
187     target->currentBlockOffset = src->currentBlockOffset;
188     target->currentBlock = src->currentBlock;
189 }
190
191 ListLayout::Role::Role(const Role *other)
192 {
193     name = other->name;
194     type = other->type;
195     blockIndex = other->blockIndex;
196     blockOffset = other->blockOffset;
197     index = other->index;
198     if (other->subLayout)
199         subLayout = new ListLayout(other->subLayout);
200     else
201         subLayout = 0;
202 }
203
204 ListLayout::Role::~Role()
205 {
206     delete subLayout;
207 }
208
209 const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
210 {
211     Role::DataType type;
212
213     switch (data.type()) {
214         case QVariant::Double:      type = Role::Number;      break;
215         case QVariant::Int:         type = Role::Number;      break;
216         case QVariant::UserType:    type = Role::List;        break;
217         case QVariant::Bool:        type = Role::Bool;        break;
218         case QVariant::String:      type = Role::String;      break;
219         case QVariant::Map:         type = Role::VariantMap;  break;
220         default:                    type = Role::Invalid;     break;
221     }
222
223     if (type == Role::Invalid) {
224         qmlInfo(0) << "Can't create role for unsupported data type";
225         return 0;
226     }
227
228     return &getRoleOrCreate(key, type);
229 }
230
231 const ListLayout::Role *ListLayout::getExistingRole(const QString &key)
232 {
233     Role *r = 0;
234     QStringHash<Role *>::Node *node = roleHash.findNode(key);
235     if (node)
236         r = node->value;
237     return r;
238 }
239
240 const ListLayout::Role *ListLayout::getExistingRole(v8::Handle<v8::String> key)
241 {
242     Role *r = 0;
243     QHashedV8String hashedKey(key);
244     QStringHash<Role *>::Node *node = roleHash.findNode(hashedKey);
245     if (node)
246         r = node->value;
247     return r;
248 }
249
250 ModelObject *ListModel::getOrCreateModelObject(QQuickListModel *model, int elementIndex)
251 {
252     ListElement *e = elements[elementIndex];
253     if (e->m_objectCache == 0) {
254         e->m_objectCache = new ModelObject(model, elementIndex);
255     }
256     return e->m_objectCache;
257 }
258
259 void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
260 {
261     // Sanity check
262     target->m_uid = src->m_uid;
263     if (targetModelHash)
264         targetModelHash->insert(target->m_uid, target);
265
266     // Build hash of elements <-> uid for each of the lists
267     QHash<int, ElementSync> elementHash;
268     for (int i=0 ; i < target->elements.count() ; ++i) {
269         ListElement *e = target->elements.at(i);
270         int uid = e->getUid();
271         ElementSync sync;
272         sync.target = e;
273         elementHash.insert(uid, sync);
274     }
275     for (int i=0 ; i < src->elements.count() ; ++i) {
276         ListElement *e = src->elements.at(i);
277         int uid = e->getUid();
278
279         QHash<int, ElementSync>::iterator it = elementHash.find(uid);
280         if (it == elementHash.end()) {
281             ElementSync sync;
282             sync.src = e;
283             elementHash.insert(uid, sync);
284         } else {
285             ElementSync &sync = it.value();
286             sync.src = e;
287         }
288     }
289
290     // Get list of elements that are in the target but no longer in the source. These get deleted first.
291     QHash<int, ElementSync>::iterator it = elementHash.begin();
292     QHash<int, ElementSync>::iterator end = elementHash.end();
293     while (it != end) {
294         const ElementSync &s = it.value();
295         if (s.src == 0) {
296             s.target->destroy(target->m_layout);
297             target->elements.removeOne(s.target);
298             delete s.target;
299         }
300         ++it;
301     }
302
303     // Sync the layouts
304     ListLayout::sync(src->m_layout, target->m_layout);
305
306     // Clear the target list, and append in correct order from the source
307     target->elements.clear();
308     for (int i=0 ; i < src->elements.count() ; ++i) {
309         ListElement *srcElement = src->elements.at(i);
310         it = elementHash.find(srcElement->getUid());
311         const ElementSync &s = it.value();
312         ListElement *targetElement = s.target;
313         if (targetElement == 0) {
314             targetElement = new ListElement(srcElement->getUid());
315         }
316         ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
317         target->elements.append(targetElement);
318     }
319
320     target->updateCacheIndices();
321
322     // Update values stored in target meta objects
323     for (int i=0 ; i < target->elements.count() ; ++i) {
324         ListElement *e = target->elements[i];
325         if (e->m_objectCache)
326             e->m_objectCache->updateValues();
327     }
328 }
329
330 ListModel::ListModel(ListLayout *layout, QQuickListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
331 {
332     if (uid == -1)
333         uid = uidCounter.fetchAndAddOrdered(1);
334     m_uid = uid;
335 }
336
337 void ListModel::destroy()
338 {
339     clear();
340     m_uid = -1;
341     m_layout = 0;
342     if (m_modelCache && m_modelCache->m_primary == false)
343         delete m_modelCache;
344     m_modelCache = 0;
345 }
346
347 int ListModel::appendElement()
348 {
349     int elementIndex = elements.count();
350     newElement(elementIndex);
351     return elementIndex;
352 }
353
354 void ListModel::insertElement(int index)
355 {
356     newElement(index);
357     updateCacheIndices();
358 }
359
360 void ListModel::move(int from, int to, int n)
361 {
362     if (from > to) {
363         // Only move forwards - flip if backwards moving
364         int tfrom = from;
365         int tto = to;
366         from = tto;
367         to = tto+n;
368         n = tfrom-tto;
369     }
370
371     QPODVector<ListElement *, 4> store;
372     for (int i=0 ; i < (to-from) ; ++i)
373         store.append(elements[from+n+i]);
374     for (int i=0 ; i < n ; ++i)
375         store.append(elements[from+i]);
376     for (int i=0 ; i < store.count() ; ++i)
377         elements[from+i] = store[i];
378
379     updateCacheIndices();
380 }
381
382 void ListModel::newElement(int index)
383 {
384     ListElement *e = new ListElement;
385     elements.insert(index, e);
386 }
387
388 void ListModel::updateCacheIndices()
389 {
390     for (int i=0 ; i < elements.count() ; ++i) {
391         ListElement *e = elements.at(i);
392         if (e->m_objectCache) {
393             e->m_objectCache->m_elementIndex = i;
394         }
395     }
396 }
397
398 QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQuickListModel *owner, QV8Engine *eng)
399 {
400     ListElement *e = elements[elementIndex];
401     const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
402     return e->getProperty(r, owner, eng);
403 }
404
405 ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
406 {
407     ListElement *e = elements[elementIndex];
408     return e->getListProperty(role);
409 }
410
411 void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles, QV8Engine *eng)
412 {
413     ListElement *e = elements[elementIndex];
414
415     v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
416     int propertyCount = propertyNames->Length();
417
418     for (int i=0 ; i < propertyCount ; ++i) {
419         v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
420         v8::Local<v8::Value> propertyValue = object->Get(propertyName);
421
422         // Check if this key exists yet
423         int roleIndex = -1;
424
425         // Add the value now
426         if (propertyValue->IsString()) {
427             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
428             v8::Handle<v8::String> jsString = propertyValue->ToString();
429             QString qstr;
430             qstr.resize(jsString->Length());
431             jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
432             roleIndex = e->setStringProperty(r, qstr);
433         } else if (propertyValue->IsNumber()) {
434             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
435             roleIndex = e->setDoubleProperty(r, propertyValue->NumberValue());
436         } else if (propertyValue->IsArray()) {
437             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
438             ListModel *subModel = new ListModel(r.subLayout, 0, -1);
439
440             v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
441             int arrayLength = subArray->Length();
442             for (int j=0 ; j < arrayLength ; ++j) {
443                 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
444                 subModel->append(subObject, eng);
445             }
446
447             roleIndex = e->setListProperty(r, subModel);
448         } else if (propertyValue->IsBoolean()) {
449             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
450             roleIndex = e->setBoolProperty(r, propertyValue->BooleanValue());
451         } else if (propertyValue->IsObject()) {
452             QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
453             if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
454                 QObject *o = QV8QObjectWrapper::toQObject(r);
455                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
456                 if (role.type == ListLayout::Role::QObject)
457                     roleIndex = e->setQObjectProperty(role, o);
458             } else {
459                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
460                 if (role.type == ListLayout::Role::VariantMap)
461                     roleIndex = e->setVariantMapProperty(role, propertyValue->ToObject(), eng);
462             }
463         } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
464             const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
465             if (r)
466                 e->clearProperty(*r);
467         }
468
469         if (roleIndex != -1)
470             roles->append(roleIndex);
471     }
472
473     if (e->m_objectCache) {
474         e->m_objectCache->updateValues(*roles);
475     }
476 }
477
478 void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
479 {
480     ListElement *e = elements[elementIndex];
481
482     v8::Local<v8::Array> propertyNames = object->GetPropertyNames();
483     int propertyCount = propertyNames->Length();
484
485     for (int i=0 ; i < propertyCount ; ++i) {
486         v8::Local<v8::String> propertyName = propertyNames->Get(i)->ToString();
487         v8::Local<v8::Value> propertyValue = object->Get(propertyName);
488
489         // Add the value now
490         if (propertyValue->IsString()) {
491             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
492             if (r.type == ListLayout::Role::String) {
493                 v8::Handle<v8::String> jsString = propertyValue->ToString();
494                 QString qstr;
495                 qstr.resize(jsString->Length());
496                 jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
497                 e->setStringPropertyFast(r, qstr);
498             }
499         } else if (propertyValue->IsNumber()) {
500             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
501             if (r.type == ListLayout::Role::Number) {
502                 e->setDoublePropertyFast(r, propertyValue->NumberValue());
503             }
504         } else if (propertyValue->IsArray()) {
505             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
506             if (r.type == ListLayout::Role::List) {
507                 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
508
509                 v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(propertyValue);
510                 int arrayLength = subArray->Length();
511                 for (int j=0 ; j < arrayLength ; ++j) {
512                     v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
513                     subModel->append(subObject, eng);
514                 }
515
516                 e->setListPropertyFast(r, subModel);
517             }
518         } else if (propertyValue->IsBoolean()) {
519             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
520             if (r.type == ListLayout::Role::Bool) {
521                 e->setBoolPropertyFast(r, propertyValue->BooleanValue());
522             }
523         } else if (propertyValue->IsObject()) {
524             QV8ObjectResource *r = (QV8ObjectResource *) propertyValue->ToObject()->GetExternalResource();
525             if (r && r->resourceType() == QV8ObjectResource::QObjectType) {
526                 QObject *o = QV8QObjectWrapper::toQObject(r);
527                 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
528                 if (r.type == ListLayout::Role::QObject)
529                     e->setQObjectPropertyFast(r, o);
530             } else {
531                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
532                 if (role.type == ListLayout::Role::VariantMap)
533                     e->setVariantMapFast(role, propertyValue->ToObject(), eng);
534             }
535         } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
536             const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
537             if (r)
538                 e->clearProperty(*r);
539         }
540     }
541 }
542
543 void ListModel::clear()
544 {
545     int elementCount = elements.count();
546     for (int i=0 ; i < elementCount ; ++i) {
547         elements[i]->destroy(m_layout);
548         delete elements[i];
549     }
550     elements.clear();
551 }
552
553 void ListModel::remove(int index, int count)
554 {
555     for (int i=0 ; i < count ; ++i) {
556         elements[index+i]->destroy(m_layout);
557         delete elements[index+i];
558     }
559     elements.remove(index, count);
560     updateCacheIndices();
561 }
562
563 void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
564 {
565     insertElement(elementIndex);
566     set(elementIndex, object, eng);
567 }
568
569 int ListModel::append(v8::Handle<v8::Object> object, QV8Engine *eng)
570 {
571     int elementIndex = appendElement();
572     set(elementIndex, object, eng);
573     return elementIndex;
574 }
575
576 int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
577 {
578     int roleIndex = -1;
579
580     if (elementIndex >= 0 && elementIndex < elements.count()) {
581         ListElement *e = elements[elementIndex];
582
583         const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
584         if (r) {
585             roleIndex = e->setVariantProperty(*r, data);
586
587             if (roleIndex != -1 && e->m_objectCache) {
588                 QList<int> roles;
589                 roles << roleIndex;
590                 e->m_objectCache->updateValues(roles);
591             }
592         }
593     }
594
595     return roleIndex;
596 }
597
598 int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng)
599 {
600     int roleIndex = -1;
601
602     if (elementIndex >= 0 && elementIndex < elements.count()) {
603         ListElement *e = elements[elementIndex];
604         const ListLayout::Role *r = m_layout->getExistingRole(key);
605         if (r)
606             roleIndex = e->setJsProperty(*r, data, eng);
607     }
608
609     return roleIndex;
610 }
611
612 inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
613 {
614     ListElement *e = this;
615     int blockIndex = 0;
616     while (blockIndex < role.blockIndex) {
617         if (e->next == 0) {
618             e->next = new ListElement;
619             e->next->uid = uid;
620         }
621         e = e->next;
622         ++blockIndex;
623     }
624
625     char *mem = &e->data[role.blockOffset];
626     return mem;
627 }
628
629 QString *ListElement::getStringProperty(const ListLayout::Role &role)
630 {
631     char *mem = getPropertyMemory(role);
632     QString *s = reinterpret_cast<QString *>(mem);
633     return s->data_ptr() ? s : 0;
634 }
635
636 QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
637 {
638     char *mem = getPropertyMemory(role);
639     QQmlGuard<QObject> *o = reinterpret_cast<QQmlGuard<QObject> *>(mem);
640     return o->data();
641 }
642
643 QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
644 {
645     QVariantMap *map = 0;
646
647     char *mem = getPropertyMemory(role);
648     if (isMemoryUsed<QVariantMap>(mem))
649         map = reinterpret_cast<QVariantMap *>(mem);
650
651     return map;
652 }
653
654 QQmlGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
655 {
656     char *mem = getPropertyMemory(role);
657
658     bool existingGuard = false;
659     for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) {
660         if (mem[i] != 0) {
661             existingGuard = true;
662             break;
663         }
664     }
665
666     QQmlGuard<QObject> *o = 0;
667
668     if (existingGuard)
669         o = reinterpret_cast<QQmlGuard<QObject> *>(mem);
670
671     return o;
672 }
673
674 ListModel *ListElement::getListProperty(const ListLayout::Role &role)
675 {
676     char *mem = getPropertyMemory(role);
677     ListModel **value = reinterpret_cast<ListModel **>(mem);
678     return *value;
679 }
680
681 QVariant ListElement::getProperty(const ListLayout::Role &role, const QQuickListModel *owner, QV8Engine *eng)
682 {
683     char *mem = getPropertyMemory(role);
684
685     QVariant data;
686
687     switch (role.type) {
688         case ListLayout::Role::Number:
689             {
690                 double *value = reinterpret_cast<double *>(mem);
691                 data = *value;
692             }
693             break;
694         case ListLayout::Role::String:
695             {
696                 QString *value = reinterpret_cast<QString *>(mem);
697                 if (value->data_ptr() != 0)
698                     data = *value;
699             }
700             break;
701         case ListLayout::Role::Bool:
702             {
703                 bool *value = reinterpret_cast<bool *>(mem);
704                 data = *value;
705             }
706             break;
707         case ListLayout::Role::List:
708             {
709                 ListModel **value = reinterpret_cast<ListModel **>(mem);
710                 ListModel *model = *value;
711
712                 if (model) {
713                     if (model->m_modelCache == 0) {
714                         model->m_modelCache = new QQuickListModel(owner, model, eng);
715                         QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
716                     }
717
718                     QObject *object = model->m_modelCache;
719                     data = QVariant::fromValue(object);
720                 }
721             }
722             break;
723         case ListLayout::Role::QObject:
724             {
725                 QQmlGuard<QObject> *guard = reinterpret_cast<QQmlGuard<QObject> *>(mem);
726                 QObject *object = guard->data();
727                 if (object)
728                     data = QVariant::fromValue(object);
729             }
730             break;
731         case ListLayout::Role::VariantMap:
732             {
733                 if (isMemoryUsed<QVariantMap>(mem)) {
734                     QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
735                     data = *map;
736                 }
737             }
738             break;
739         default:
740             break;
741     }
742
743     return data;
744 }
745
746 int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
747 {
748     int roleIndex = -1;
749
750     if (role.type == ListLayout::Role::String) {
751         char *mem = getPropertyMemory(role);
752         QString *c = reinterpret_cast<QString *>(mem);
753         bool changed;
754         if (c->data_ptr() == 0) {
755             new (mem) QString(s);
756             changed = true;
757         } else {
758             changed = c->compare(s) != 0;
759             *c = s;
760         }
761         if (changed)
762             roleIndex = role.index;
763     }
764
765     return roleIndex;
766 }
767
768 int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
769 {
770     int roleIndex = -1;
771
772     if (role.type == ListLayout::Role::Number) {
773         char *mem = getPropertyMemory(role);
774         double *value = new (mem) double;
775         bool changed = *value != d;
776         *value = d;
777         if (changed)
778             roleIndex = role.index;
779     }
780
781     return roleIndex;
782 }
783
784 int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
785 {
786     int roleIndex = -1;
787
788     if (role.type == ListLayout::Role::Bool) {
789         char *mem = getPropertyMemory(role);
790         bool *value = new (mem) bool;
791         bool changed = *value != b;
792         *value = b;
793         if (changed)
794             roleIndex = role.index;
795     }
796
797     return roleIndex;
798 }
799
800 int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
801 {
802     int roleIndex = -1;
803
804     if (role.type == ListLayout::Role::List) {
805         char *mem = getPropertyMemory(role);
806         ListModel **value = new (mem) ListModel *;
807         if (*value) {
808             (*value)->destroy();
809             delete *value;
810         }
811         *value = m;
812         roleIndex = role.index;
813     }
814
815     return roleIndex;
816 }
817
818 int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
819 {
820     int roleIndex = -1;
821
822     if (role.type == ListLayout::Role::QObject) {
823         char *mem = getPropertyMemory(role);
824         QQmlGuard<QObject> *g = reinterpret_cast<QQmlGuard<QObject> *>(mem);
825         bool existingGuard = false;
826         for (size_t i=0 ; i < sizeof(QQmlGuard<QObject>) ; ++i) {
827             if (mem[i] != 0) {
828                 existingGuard = true;
829                 break;
830             }
831         }
832         bool changed;
833         if (existingGuard) {
834             changed = g->data() != o;
835             g->~QQmlGuard();
836         } else {
837             changed = true;
838         }
839         new (mem) QQmlGuard<QObject>(o);
840         if (changed)
841             roleIndex = role.index;
842     }
843
844     return roleIndex;
845 }
846
847 int ListElement::setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
848 {
849     int roleIndex = -1;
850
851     if (role.type == ListLayout::Role::VariantMap) {
852         char *mem = getPropertyMemory(role);
853         if (isMemoryUsed<QVariantMap>(mem)) {
854             QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
855             map->~QMap();
856         }
857         new (mem) QVariantMap(eng->variantMapFromJS(o));
858         roleIndex = role.index;
859     }
860
861     return roleIndex;
862 }
863
864 int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
865 {
866     int roleIndex = -1;
867
868     if (role.type == ListLayout::Role::VariantMap) {
869         char *mem = getPropertyMemory(role);
870         if (isMemoryUsed<QVariantMap>(mem)) {
871             QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
872             map->~QMap();
873         }
874         if (m)
875             new (mem) QVariantMap(*m);
876         else
877             new (mem) QVariantMap;
878         roleIndex = role.index;
879     }
880
881     return roleIndex;
882 }
883
884 void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
885 {
886     char *mem = getPropertyMemory(role);
887     new (mem) QString(s);
888 }
889
890 void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
891 {
892     char *mem = getPropertyMemory(role);
893     double *value = new (mem) double;
894     *value = d;
895 }
896
897 void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
898 {
899     char *mem = getPropertyMemory(role);
900     bool *value = new (mem) bool;
901     *value = b;
902 }
903
904 void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
905 {
906     char *mem = getPropertyMemory(role);
907     new (mem) QQmlGuard<QObject>(o);
908 }
909
910 void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
911 {
912     char *mem = getPropertyMemory(role);
913     ListModel **value = new (mem) ListModel *;
914     *value = m;
915 }
916
917 void ListElement::setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
918 {
919     char *mem = getPropertyMemory(role);
920     QVariantMap *map = new (mem) QVariantMap;
921     *map = eng->variantMapFromJS(o);
922 }
923
924 void ListElement::clearProperty(const ListLayout::Role &role)
925 {
926     switch (role.type) {
927     case ListLayout::Role::String:
928         setStringProperty(role, QString());
929         break;
930     case ListLayout::Role::Number:
931         setDoubleProperty(role, 0.0);
932         break;
933     case ListLayout::Role::Bool:
934         setBoolProperty(role, false);
935         break;
936     case ListLayout::Role::List:
937         setListProperty(role, 0);
938         break;
939     case ListLayout::Role::QObject:
940         setQObjectProperty(role, 0);
941         break;
942     case ListLayout::Role::VariantMap:
943         setVariantMapProperty(role, 0);
944         break;
945     default:
946         break;
947     }
948 }
949
950 ListElement::ListElement()
951 {
952     m_objectCache = 0;
953     uid = uidCounter.fetchAndAddOrdered(1);
954     next = 0;
955     qMemSet(data, 0, sizeof(data));
956 }
957
958 ListElement::ListElement(int existingUid)
959 {
960     m_objectCache = 0;
961     uid = existingUid;
962     next = 0;
963     qMemSet(data, 0, sizeof(data));
964 }
965
966 ListElement::~ListElement()
967 {
968     delete next;
969 }
970
971 void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
972 {
973     for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
974         const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
975         const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
976
977         switch (srcRole.type) {
978             case ListLayout::Role::List:
979                 {
980                     ListModel *srcSubModel = src->getListProperty(srcRole);
981                     ListModel *targetSubModel = target->getListProperty(targetRole);
982
983                     if (srcSubModel) {
984                         if (targetSubModel == 0) {
985                             targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
986                             target->setListPropertyFast(targetRole, targetSubModel);
987                         }
988                         ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
989                     }
990                 }
991                 break;
992             case ListLayout::Role::QObject:
993                 {
994                     QObject *object = src->getQObjectProperty(srcRole);
995                     target->setQObjectProperty(targetRole, object);
996                 }
997                 break;
998             case ListLayout::Role::String:
999             case ListLayout::Role::Number:
1000             case ListLayout::Role::Bool:
1001                 {
1002                     QVariant v = src->getProperty(srcRole, 0, 0);
1003                     target->setVariantProperty(targetRole, v);
1004                 }
1005             case ListLayout::Role::VariantMap:
1006                 {
1007                     QVariantMap *map = src->getVariantMapProperty(srcRole);
1008                     target->setVariantMapProperty(targetRole, map);
1009                 }
1010                 break;
1011             default:
1012                 break;
1013         }
1014     }
1015
1016 }
1017
1018 void ListElement::destroy(ListLayout *layout)
1019 {
1020     if (layout) {
1021         for (int i=0 ; i < layout->roleCount() ; ++i) {
1022             const ListLayout::Role &r = layout->getExistingRole(i);
1023
1024             switch (r.type) {
1025                 case ListLayout::Role::String:
1026                     {
1027                         QString *string = getStringProperty(r);
1028                         if (string)
1029                             string->~QString();
1030                     }
1031                     break;
1032                 case ListLayout::Role::List:
1033                     {
1034                         ListModel *model = getListProperty(r);
1035                         if (model) {
1036                             model->destroy();
1037                             delete model;
1038                         }
1039                     }
1040                     break;
1041                 case ListLayout::Role::QObject:
1042                     {
1043                         QQmlGuard<QObject> *guard = getGuardProperty(r);
1044                         if (guard)
1045                             guard->~QQmlGuard();
1046                     }
1047                     break;
1048                 case ListLayout::Role::VariantMap:
1049                     {
1050                         QVariantMap *map = getVariantMapProperty(r);
1051                         if (map)
1052                             map->~QMap();
1053                     }
1054                     break;
1055                 default:
1056                     // other types don't need explicit cleanup.
1057                     break;
1058             }
1059         }
1060
1061         delete m_objectCache;
1062     }
1063
1064     if (next)
1065         next->destroy(0);
1066     uid = -1;
1067 }
1068
1069 int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
1070 {
1071     int roleIndex = -1;
1072
1073     switch (role.type) {
1074         case ListLayout::Role::Number:
1075             roleIndex = setDoubleProperty(role, d.toDouble());
1076             break;
1077         case ListLayout::Role::String:
1078             roleIndex = setStringProperty(role, d.toString());
1079             break;
1080         case ListLayout::Role::Bool:
1081             roleIndex = setBoolProperty(role, d.toBool());
1082             break;
1083         case ListLayout::Role::List:
1084             roleIndex = setListProperty(role, d.value<ListModel *>());
1085             break;
1086         case ListLayout::Role::VariantMap: {
1087                 QVariantMap map = d.toMap();
1088                 roleIndex = setVariantMapProperty(role, &map);
1089             }
1090             break;
1091         default:
1092             break;
1093     }
1094
1095     return roleIndex;
1096 }
1097
1098 int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng)
1099 {
1100     // Check if this key exists yet
1101     int roleIndex = -1;
1102
1103     // Add the value now
1104     if (d->IsString()) {
1105         v8::Handle<v8::String> jsString = d->ToString();
1106         QString qstr;
1107         qstr.resize(jsString->Length());
1108         jsString->Write(reinterpret_cast<uint16_t*>(qstr.data()));
1109         roleIndex = setStringProperty(role, qstr);
1110     } else if (d->IsNumber()) {
1111         roleIndex = setDoubleProperty(role, d->NumberValue());
1112     } else if (d->IsArray()) {
1113         ListModel *subModel = new ListModel(role.subLayout, 0, -1);
1114         v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
1115         int arrayLength = subArray->Length();
1116         for (int j=0 ; j < arrayLength ; ++j) {
1117             v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
1118             subModel->append(subObject, eng);
1119         }
1120         roleIndex = setListProperty(role, subModel);
1121     } else if (d->IsBoolean()) {
1122         roleIndex = setBoolProperty(role, d->BooleanValue());
1123     } else if (d->IsObject()) {
1124         QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
1125         if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
1126             QObject *o = QV8QObjectWrapper::toQObject(r);
1127             roleIndex = setQObjectProperty(role, o);
1128         } else if (role.type == ListLayout::Role::VariantMap) {
1129             roleIndex = setVariantMapProperty(role, d->ToObject(), eng);
1130         }
1131     } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
1132         clearProperty(role);
1133     }
1134
1135     return roleIndex;
1136 }
1137
1138 ModelObject::ModelObject(QQuickListModel *model, int elementIndex)
1139 : m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
1140 {
1141     updateValues();
1142     setNodeUpdatesEnabled(true);
1143 }
1144
1145 void ModelObject::updateValues()
1146 {
1147     int roleCount = m_model->m_listModel->roleCount();
1148     for (int i=0 ; i < roleCount ; ++i) {
1149         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1150         QByteArray name = role.name.toUtf8();
1151         const QVariant &data = m_model->data(m_elementIndex, i);
1152         setValue(name, data, role.type == ListLayout::Role::List);
1153     }
1154 }
1155
1156 void ModelObject::updateValues(const QList<int> &roles)
1157 {
1158     int roleCount = roles.count();
1159     for (int i=0 ; i < roleCount ; ++i) {
1160         int roleIndex = roles.at(i);
1161         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1162         QByteArray name = role.name.toUtf8();
1163         const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1164         setValue(name, data, role.type == ListLayout::Role::List);
1165     }
1166 }
1167
1168 ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
1169 : QQmlOpenMetaObject(object), m_enabled(false), m_obj(object)
1170 {
1171 }
1172
1173 ModelNodeMetaObject::~ModelNodeMetaObject()
1174 {
1175 }
1176
1177 void ModelNodeMetaObject::propertyWritten(int index)
1178 {
1179     if (!m_enabled)
1180         return;
1181
1182     QV8Engine *eng = m_obj->m_model->engine();
1183
1184     QString propName = QString::fromUtf8(name(index));
1185     QVariant value = operator[](index);
1186
1187     v8::HandleScope handle_scope;
1188     v8::Context::Scope scope(eng->context());
1189
1190     v8::Handle<v8::Value> v = eng->fromVariant(value);
1191
1192     int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng);
1193     if (roleIndex != -1) {
1194         QList<int> roles;
1195         roles << roleIndex;
1196         m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
1197     }
1198 }
1199
1200 DynamicRoleModelNode::DynamicRoleModelNode(QQuickListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1201 {
1202     setNodeUpdatesEnabled(true);
1203 }
1204
1205 DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQuickListModel *owner)
1206 {
1207     DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1208     QList<int> roles;
1209     object->updateValues(obj, roles);
1210     return object;
1211 }
1212
1213 void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQuickListModel *> *targetModelHash)
1214 {
1215     for (int i=0 ; i < src->m_meta->count() ; ++i) {
1216         const QByteArray &name = src->m_meta->name(i);
1217         QVariant value = src->m_meta->value(i);
1218
1219         QQuickListModel *srcModel = qobject_cast<QQuickListModel *>(value.value<QObject *>());
1220         QQuickListModel *targetModel = qobject_cast<QQuickListModel *>(target->m_meta->value(i).value<QObject *>());
1221
1222         if (srcModel) {
1223             if (targetModel == 0)
1224                 targetModel = QQuickListModel::createWithOwner(target->m_owner);
1225
1226             QQuickListModel::sync(srcModel, targetModel, targetModelHash);
1227
1228             QObject *targetModelObject = targetModel;
1229             value = QVariant::fromValue(targetModelObject);
1230         } else if (targetModel) {
1231             delete targetModel;
1232         }
1233
1234         target->setValue(name, value);
1235     }
1236 }
1237
1238 void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
1239 {
1240     const QList<QString> &keys = object.keys();
1241
1242     QList<QString>::const_iterator it = keys.begin();
1243     QList<QString>::const_iterator end = keys.end();
1244
1245     while (it != end) {
1246         const QString &key = *it;
1247
1248         int roleIndex = m_owner->m_roles.indexOf(key);
1249         if (roleIndex == -1) {
1250             roleIndex = m_owner->m_roles.count();
1251             m_owner->m_roles.append(key);
1252         }
1253
1254         QVariant value = object[key];
1255
1256         if (value.type() == QVariant::List) {
1257             QQuickListModel *subModel = QQuickListModel::createWithOwner(m_owner);
1258
1259             QVariantList subArray = value.toList();
1260             QVariantList::const_iterator subIt = subArray.begin();
1261             QVariantList::const_iterator subEnd = subArray.end();
1262             while (subIt != subEnd) {
1263                 const QVariantMap &subObject = subIt->toMap();
1264                 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1265                 ++subIt;
1266             }
1267
1268             QObject *subModelObject = subModel;
1269             value = QVariant::fromValue(subModelObject);
1270         }
1271
1272         const QByteArray &keyUtf8 = key.toUtf8();
1273
1274         QQuickListModel *existingModel = qobject_cast<QQuickListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1275         if (existingModel)
1276             delete existingModel;
1277
1278         if (m_meta->setValue(keyUtf8, value))
1279             roles << roleIndex;
1280
1281         ++it;
1282     }
1283 }
1284
1285 DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1286     : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1287 {
1288 }
1289
1290 DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1291 {
1292     for (int i=0 ; i < count() ; ++i) {
1293         QQuickListModel *subModel = qobject_cast<QQuickListModel *>(value(i).value<QObject *>());
1294         if (subModel)
1295             delete subModel;
1296     }
1297 }
1298
1299 void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1300 {
1301     if (!m_enabled)
1302         return;
1303
1304     QVariant v = value(index);
1305     QQuickListModel *model = qobject_cast<QQuickListModel *>(v.value<QObject *>());
1306     if (model)
1307         delete model;
1308 }
1309
1310 void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1311 {
1312     if (!m_enabled)
1313         return;
1314
1315     QQuickListModel *parentModel = m_owner->m_owner;
1316
1317     QVariant v = value(index);
1318     if (v.type() == QVariant::List) {
1319         QQuickListModel *subModel = QQuickListModel::createWithOwner(parentModel);
1320
1321         QVariantList subArray = v.toList();
1322         QVariantList::const_iterator subIt = subArray.begin();
1323         QVariantList::const_iterator subEnd = subArray.end();
1324         while (subIt != subEnd) {
1325             const QVariantMap &subObject = subIt->toMap();
1326             subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1327             ++subIt;
1328         }
1329
1330         QObject *subModelObject = subModel;
1331         v = QVariant::fromValue(subModelObject);
1332
1333         setValue(index, v);
1334     }
1335
1336     int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1337     int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1338
1339     if (elementIndex != -1 && roleIndex != -1) {
1340         QList<int> roles;
1341         roles << roleIndex;
1342
1343         parentModel->emitItemsChanged(elementIndex, 1, roles);
1344     }
1345 }
1346
1347 QQuickListModelParser::ListInstruction *QQuickListModelParser::ListModelData::instructions() const
1348 {
1349     return (QQuickListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
1350 }
1351
1352 /*!
1353     \qmlclass ListModel QQuickListModel
1354     \inqmlmodule QtQuick 2
1355     \ingroup qml-working-with-data
1356     \brief The ListModel element defines a free-form list data source.
1357
1358     The ListModel is a simple container of ListElement definitions, each containing data roles.
1359     The contents can be defined dynamically, or explicitly in QML.
1360
1361     The number of elements in the model can be obtained from its \l count property.
1362     A number of familiar methods are also provided to manipulate the contents of the
1363     model, including append(), insert(), move(), remove() and set(). These methods
1364     accept dictionaries as their arguments; these are translated to ListElement objects
1365     by the model.
1366
1367     Elements can be manipulated via the model using the setProperty() method, which
1368     allows the roles of the specified element to be set and changed.
1369
1370     \section1 Example Usage
1371
1372     The following example shows a ListModel containing three elements, with the roles
1373     "name" and "cost".
1374
1375     \div {class="float-right"}
1376     \inlineimage listmodel.png
1377     \enddiv
1378
1379     \snippet doc/src/snippets/qml/listmodel.qml 0
1380
1381     \clearfloat
1382     Roles (properties) in each element must begin with a lower-case letter and
1383     should be common to all elements in a model. The ListElement documentation
1384     provides more guidelines for how elements should be defined.
1385
1386     Since the example model contains an \c id property, it can be referenced
1387     by views, such as the ListView in this example:
1388
1389     \snippet doc/src/snippets/qml/listmodel-simple.qml 0
1390     \dots 8
1391     \snippet doc/src/snippets/qml/listmodel-simple.qml 1
1392
1393     It is possible for roles to contain list data.  In the following example we
1394     create a list of fruit attributes:
1395
1396     \snippet doc/src/snippets/qml/listmodel-nested.qml model
1397
1398     The delegate displays all the fruit attributes:
1399
1400     \div {class="float-right"}
1401     \inlineimage listmodel-nested.png
1402     \enddiv
1403
1404     \snippet doc/src/snippets/qml/listmodel-nested.qml delegate
1405
1406     \clearfloat
1407     \section1 Modifying List Models
1408
1409     The content of a ListModel may be created and modified using the clear(),
1410     append(), set(), insert() and setProperty() methods.  For example:
1411
1412     \snippet doc/src/snippets/qml/listmodel-modify.qml delegate
1413
1414     Note that when creating content dynamically the set of available properties
1415     cannot be changed once set. Whatever properties are first added to the model
1416     are the only permitted properties in the model.
1417
1418     \section1 Using Threaded List Models with WorkerScript
1419
1420     ListModel can be used together with WorkerScript access a list model
1421     from multiple threads. This is useful if list modifications are
1422     synchronous and take some time: the list operations can be moved to a
1423     different thread to avoid blocking of the main GUI thread.
1424
1425     Here is an example that uses WorkerScript to periodically append the
1426     current time to a list model:
1427
1428     \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
1429
1430     The included file, \tt dataloader.js, looks like this:
1431
1432     \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
1433
1434     The timer in the main example sends messages to the worker script by calling
1435     \l WorkerScript::sendMessage(). When this message is received,
1436     \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
1437     which appends the current time to the list model.
1438
1439     Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
1440     handler. You must call sync() or else the changes made to the list from the external
1441     thread will not be reflected in the list model in the main thread.
1442
1443     \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml
1444 */
1445
1446 QQuickListModel::QQuickListModel(QObject *parent)
1447 : QListModelInterface(parent)
1448 {
1449     m_mainThread = true;
1450     m_primary = true;
1451     m_agent = 0;
1452     m_uid = uidCounter.fetchAndAddOrdered(1);
1453     m_dynamicRoles = false;
1454
1455     m_layout = new ListLayout;
1456     m_listModel = new ListModel(m_layout, this, -1);
1457
1458     m_engine = 0;
1459 }
1460
1461 QQuickListModel::QQuickListModel(const QQuickListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
1462 : QListModelInterface(parent)
1463 {
1464     m_mainThread = owner->m_mainThread;
1465     m_primary = false;
1466     m_agent = owner->m_agent;
1467
1468     Q_ASSERT(owner->m_dynamicRoles == false);
1469     m_dynamicRoles = false;
1470     m_layout = 0;
1471     m_listModel = data;
1472
1473     m_engine = eng;
1474 }
1475
1476 QQuickListModel::QQuickListModel(QQuickListModel *orig, QQuickListModelWorkerAgent *agent)
1477 : QListModelInterface(agent)
1478 {
1479     m_mainThread = false;
1480     m_primary = true;
1481     m_agent = agent;
1482     m_dynamicRoles = orig->m_dynamicRoles;
1483
1484     m_layout = new ListLayout(orig->m_layout);
1485     m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1486
1487     if (m_dynamicRoles)
1488         sync(orig, this, 0);
1489     else
1490         ListModel::sync(orig->m_listModel, m_listModel, 0);
1491
1492     m_engine = 0;
1493 }
1494
1495 QQuickListModel::~QQuickListModel()
1496 {
1497     for (int i=0 ; i < m_modelObjects.count() ; ++i)
1498         delete m_modelObjects[i];
1499
1500     if (m_primary) {
1501         m_listModel->destroy();
1502         delete m_listModel;
1503
1504         if (m_mainThread && m_agent) {
1505             m_agent->modelDestroyed();
1506             m_agent->release();
1507         }
1508     }
1509
1510     m_listModel = 0;
1511
1512     delete m_layout;
1513     m_layout = 0;
1514 }
1515
1516 QQuickListModel *QQuickListModel::createWithOwner(QQuickListModel *newOwner)
1517 {
1518     QQuickListModel *model = new QQuickListModel;
1519
1520     model->m_mainThread = newOwner->m_mainThread;
1521     model->m_engine = newOwner->m_engine;
1522     model->m_agent = newOwner->m_agent;
1523     model->m_dynamicRoles = newOwner->m_dynamicRoles;
1524
1525     if (model->m_mainThread && model->m_agent)
1526         model->m_agent->addref();
1527
1528     QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1529
1530     return model;
1531 }
1532
1533 QV8Engine *QQuickListModel::engine() const
1534 {
1535     if (m_engine == 0) {
1536         m_engine  = QQmlEnginePrivate::getV8Engine(qmlEngine(this));
1537     }
1538
1539     return m_engine;
1540 }
1541
1542 void QQuickListModel::sync(QQuickListModel *src, QQuickListModel *target, QHash<int, QQuickListModel *> *targetModelHash)
1543 {
1544     Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1545
1546     target->m_uid = src->m_uid;
1547     if (targetModelHash)
1548         targetModelHash->insert(target->m_uid, target);
1549     target->m_roles = src->m_roles;
1550
1551     // Build hash of elements <-> uid for each of the lists
1552     QHash<int, ElementSync> elementHash;
1553     for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1554         DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1555         int uid = e->getUid();
1556         ElementSync sync;
1557         sync.target = e;
1558         elementHash.insert(uid, sync);
1559     }
1560     for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1561         DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1562         int uid = e->getUid();
1563
1564         QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1565         if (it == elementHash.end()) {
1566             ElementSync sync;
1567             sync.src = e;
1568             elementHash.insert(uid, sync);
1569         } else {
1570             ElementSync &sync = it.value();
1571             sync.src = e;
1572         }
1573     }
1574
1575     // Get list of elements that are in the target but no longer in the source. These get deleted first.
1576     QHash<int, ElementSync>::iterator it = elementHash.begin();
1577     QHash<int, ElementSync>::iterator end = elementHash.end();
1578     while (it != end) {
1579         const ElementSync &s = it.value();
1580         if (s.src == 0) {
1581             int targetIndex = target->m_modelObjects.indexOf(s.target);
1582             target->m_modelObjects.remove(targetIndex, 1);
1583             delete s.target;
1584         }
1585         ++it;
1586     }
1587
1588     // Clear the target list, and append in correct order from the source
1589     target->m_modelObjects.clear();
1590     for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1591         DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1592         it = elementHash.find(srcElement->getUid());
1593         const ElementSync &s = it.value();
1594         DynamicRoleModelNode *targetElement = s.target;
1595         if (targetElement == 0) {
1596             targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1597         }
1598         DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1599         target->m_modelObjects.append(targetElement);
1600     }
1601 }
1602
1603 void QQuickListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
1604 {
1605     if (m_mainThread) {
1606         emit itemsChanged(index, count, roles);
1607     } else {
1608         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1609         m_agent->data.changedChange(uid, index, count, roles);
1610     }
1611 }
1612
1613 void QQuickListModel::emitItemsRemoved(int index, int count)
1614 {
1615     if (m_mainThread) {
1616         emit itemsRemoved(index, count);
1617         emit countChanged();
1618     } else {
1619         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1620         if (index == 0 && count == this->count())
1621             m_agent->data.clearChange(uid);
1622         m_agent->data.removeChange(uid, index, count);
1623     }
1624 }
1625
1626 void QQuickListModel::emitItemsInserted(int index, int count)
1627 {
1628     if (m_mainThread) {
1629         emit itemsInserted(index, count);
1630         emit countChanged();
1631     } else {
1632         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1633         m_agent->data.insertChange(uid, index, count);
1634     }
1635 }
1636
1637 void QQuickListModel::emitItemsMoved(int from, int to, int n)
1638 {
1639     if (m_mainThread) {
1640         emit itemsMoved(from, to, n);
1641     } else {
1642         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1643         m_agent->data.moveChange(uid, from, n, to);
1644     }
1645 }
1646
1647 QQuickListModelWorkerAgent *QQuickListModel::agent()
1648 {
1649     if (m_agent)
1650         return m_agent;
1651
1652     m_agent = new QQuickListModelWorkerAgent(this);
1653     return m_agent;
1654 }
1655
1656 QList<int> QQuickListModel::roles() const
1657 {
1658     QList<int> rolesArray;
1659
1660     if (m_dynamicRoles) {
1661         for (int i=0 ; i < m_roles.count() ; ++i)
1662             rolesArray << i;
1663     } else {
1664         for (int i=0 ; i < m_listModel->roleCount() ; ++i)
1665             rolesArray << i;
1666     }
1667
1668     return rolesArray;
1669 }
1670
1671 QString QQuickListModel::toString(int role) const
1672 {
1673     QString roleName;
1674
1675     if (m_dynamicRoles) {
1676         roleName = m_roles[role];
1677     } else {
1678         const ListLayout::Role &r = m_listModel->getExistingRole(role);
1679         roleName = r.name;
1680     }
1681
1682     return roleName;
1683 }
1684
1685 QVariant QQuickListModel::data(int index, int role) const
1686 {
1687     QVariant v;
1688
1689     if (index >= count() || index < 0)
1690         return v;
1691
1692     if (m_dynamicRoles)
1693         v = m_modelObjects[index]->getValue(m_roles[role]);
1694     else
1695         v = m_listModel->getProperty(index, role, this, engine());
1696
1697     return v;
1698 }
1699
1700 /*!
1701     \qmlproperty bool QtQuick2::ListModel::dynamicRoles
1702
1703     By default, the type of a role is fixed the first time
1704     the role is used. For example, if you create a role called
1705     "data" and assign a number to it, you can no longer assign
1706     a string to the "data" role. However, when the dynamicRoles
1707     property is enabled, the type of a given role is not fixed
1708     and can be different between elements.
1709
1710     The dynamicRoles property must be set before any data is
1711     added to the ListModel, and must be set from the main
1712     thread.
1713
1714     A ListModel that has data statically defined (via the
1715     ListElement QML syntax) cannot have the dynamicRoles
1716     property enabled.
1717
1718     There is a significant performance cost to using a
1719     ListModel with dynamic roles enabled. The cost varies
1720     from platform to platform but is typically somewhere
1721     between 4-6x slower than using static role types.
1722
1723     Due to the performance cost of using dynamic roles,
1724     they are disabled by default.
1725 */
1726 void QQuickListModel::setDynamicRoles(bool enableDynamicRoles)
1727 {
1728     if (m_mainThread && m_agent == 0) {
1729         if (enableDynamicRoles) {
1730             if (m_layout->roleCount())
1731                 qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
1732             else
1733                 m_dynamicRoles = true;
1734         } else {
1735             if (m_roles.count()) {
1736                 qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
1737             } else {
1738                 m_dynamicRoles = false;
1739             }
1740         }
1741     } else {
1742         qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
1743     }
1744 }
1745
1746 /*!
1747     \qmlproperty int QtQuick2::ListModel::count
1748     The number of data entries in the model.
1749 */
1750 int QQuickListModel::count() const
1751 {
1752     int count;
1753
1754     if (m_dynamicRoles)
1755         count = m_modelObjects.count();
1756     else {
1757         count = m_listModel->elementCount();
1758     }
1759
1760     return count;
1761 }
1762
1763 /*!
1764     \qmlmethod QtQuick2::ListModel::clear()
1765
1766     Deletes all content from the model.
1767
1768     \sa append() remove()
1769 */
1770 void QQuickListModel::clear()
1771 {
1772     int cleared = count();
1773
1774     if (m_dynamicRoles) {
1775         for (int i=0 ; i < m_modelObjects.count() ; ++i)
1776             delete m_modelObjects[i];
1777         m_modelObjects.clear();
1778     } else {
1779         m_listModel->clear();
1780     }
1781
1782     emitItemsRemoved(0, cleared);
1783 }
1784
1785 /*!
1786     \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
1787
1788     Deletes the content at \a index from the model.
1789
1790     \sa clear()
1791 */
1792 void QQuickListModel::remove(QQmlV8Function *args)
1793 {
1794     int argLength = args->Length();
1795
1796     if (argLength == 1 || argLength == 2) {
1797         int index = (*args)[0]->Int32Value();
1798         int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1);
1799
1800         if (index < 0 || index+removeCount > count() || removeCount <= 0) {
1801             qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
1802             return;
1803         }
1804
1805         if (m_dynamicRoles) {
1806             for (int i=0 ; i < removeCount ; ++i)
1807                 delete m_modelObjects[index+i];
1808             m_modelObjects.remove(index, removeCount);
1809         } else {
1810             m_listModel->remove(index, removeCount);
1811         }
1812
1813         emitItemsRemoved(index, removeCount);
1814     } else {
1815         qmlInfo(this) << tr("remove: incorrect number of arguments");
1816     }
1817 }
1818
1819 /*!
1820     \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
1821
1822     Adds a new item to the list model at position \a index, with the
1823     values in \a dict.
1824
1825     \code
1826         fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
1827     \endcode
1828
1829     The \a index must be to an existing item in the list, or one past
1830     the end of the list (equivalent to append).
1831
1832     \sa set() append()
1833 */
1834
1835 void QQuickListModel::insert(QQmlV8Function *args)
1836 {
1837     if (args->Length() == 2) {
1838
1839         v8::Handle<v8::Value> arg0 = (*args)[0];
1840         int index = arg0->Int32Value();
1841
1842         if (index < 0 || index > count()) {
1843             qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
1844             return;
1845         }
1846
1847         v8::Handle<v8::Value> arg1 = (*args)[1];
1848
1849         if (arg1->IsArray()) {
1850             v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
1851             int objectArrayLength = objectArray->Length();
1852             for (int i=0 ; i < objectArrayLength ; ++i) {
1853                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1854
1855                 if (m_dynamicRoles) {
1856                     m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1857                 } else {
1858                     m_listModel->insert(index+i, argObject, args->engine());
1859                 }
1860             }
1861             emitItemsInserted(index, objectArrayLength);
1862         } else if (arg1->IsObject()) {
1863             v8::Handle<v8::Object> argObject = arg1->ToObject();
1864
1865             if (m_dynamicRoles) {
1866                 m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1867             } else {
1868                 m_listModel->insert(index, argObject, args->engine());
1869             }
1870
1871             emitItemsInserted(index, 1);
1872         } else {
1873             qmlInfo(this) << tr("insert: value is not an object");
1874         }
1875     } else {
1876         qmlInfo(this) << tr("insert: value is not an object");
1877     }
1878 }
1879
1880 /*!
1881     \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
1882
1883     Moves \a n items \a from one position \a to another.
1884
1885     The from and to ranges must exist; for example, to move the first 3 items
1886     to the end of the list:
1887
1888     \code
1889         fruitModel.move(0, fruitModel.count - 3, 3)
1890     \endcode
1891
1892     \sa append()
1893 */
1894 void QQuickListModel::move(int from, int to, int n)
1895 {
1896     if (n==0 || from==to)
1897         return;
1898     if (!canMove(from, to, n)) {
1899         qmlInfo(this) << tr("move: out of range");
1900         return;
1901     }
1902
1903     if (m_dynamicRoles) {
1904
1905         int realFrom = from;
1906         int realTo = to;
1907         int realN = n;
1908
1909         if (from > to) {
1910             // Only move forwards - flip if backwards moving
1911             int tfrom = from;
1912             int tto = to;
1913             realFrom = tto;
1914             realTo = tto+n;
1915             realN = tfrom-tto;
1916         }
1917
1918         QPODVector<DynamicRoleModelNode *, 4> store;
1919         for (int i=0 ; i < (realTo-realFrom) ; ++i)
1920             store.append(m_modelObjects[realFrom+realN+i]);
1921         for (int i=0 ; i < realN ; ++i)
1922             store.append(m_modelObjects[realFrom+i]);
1923         for (int i=0 ; i < store.count() ; ++i)
1924             m_modelObjects[realFrom+i] = store[i];
1925
1926     } else {
1927         m_listModel->move(from, to, n);
1928     }
1929
1930     emitItemsMoved(from, to, n);
1931 }
1932
1933 /*!
1934     \qmlmethod QtQuick2::ListModel::append(jsobject dict)
1935
1936     Adds a new item to the end of the list model, with the
1937     values in \a dict.
1938
1939     \code
1940         fruitModel.append({"cost": 5.95, "name":"Pizza"})
1941     \endcode
1942
1943     \sa set() remove()
1944 */
1945 void QQuickListModel::append(QQmlV8Function *args)
1946 {
1947     if (args->Length() == 1) {
1948         v8::Handle<v8::Value> arg = (*args)[0];
1949
1950         if (arg->IsArray()) {
1951             v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
1952             int objectArrayLength = objectArray->Length();
1953
1954             int index = count();
1955             for (int i=0 ; i < objectArrayLength ; ++i) {
1956                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1957
1958                 if (m_dynamicRoles) {
1959                     m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1960                 } else {
1961                     m_listModel->append(argObject, args->engine());
1962                 }
1963             }
1964
1965             emitItemsInserted(index, objectArrayLength);
1966         } else if (arg->IsObject()) {
1967             v8::Handle<v8::Object> argObject = arg->ToObject();
1968
1969             int index;
1970
1971             if (m_dynamicRoles) {
1972                 index = m_modelObjects.count();
1973                 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1974             } else {
1975                 index = m_listModel->append(argObject, args->engine());
1976             }
1977
1978             emitItemsInserted(index, 1);
1979         } else {
1980             qmlInfo(this) << tr("append: value is not an object");
1981         }
1982     } else {
1983         qmlInfo(this) << tr("append: value is not an object");
1984     }
1985 }
1986
1987 /*!
1988     \qmlmethod object QtQuick2::ListModel::get(int index)
1989
1990     Returns the item at \a index in the list model. This allows the item
1991     data to be accessed or modified from JavaScript:
1992
1993     \code
1994     Component.onCompleted: {
1995         fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
1996         console.log(fruitModel.get(0).cost);
1997         fruitModel.get(0).cost = 10.95;
1998     }
1999     \endcode
2000
2001     The \a index must be an element in the list.
2002
2003     Note that properties of the returned object that are themselves objects
2004     will also be models, and this get() method is used to access elements:
2005
2006     \code
2007         fruitModel.append(..., "attributes":
2008             [{"name":"spikes","value":"7mm"},
2009              {"name":"color","value":"green"}]);
2010         fruitModel.get(0).attributes.get(1).value; // == "green"
2011     \endcode
2012
2013     \warning The returned object is not guaranteed to remain valid. It
2014     should not be used in \l{Property Binding}{property bindings}.
2015
2016     \sa append()
2017 */
2018 QQmlV8Handle QQuickListModel::get(int index) const
2019 {
2020     v8::Handle<v8::Value> result = v8::Undefined();
2021
2022     if (index >= 0 && index < count()) {
2023         QV8Engine *v8engine = engine();
2024
2025         if (m_dynamicRoles) {
2026             DynamicRoleModelNode *object = m_modelObjects[index];
2027             result = v8engine->newQObject(object);
2028         } else {
2029             ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQuickListModel *>(this), index);
2030             result = v8engine->newQObject(object);
2031         }
2032     }
2033
2034     return QQmlV8Handle::fromHandle(result);
2035 }
2036
2037 /*!
2038     \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
2039
2040     Changes the item at \a index in the list model with the
2041     values in \a dict. Properties not appearing in \a dict
2042     are left unchanged.
2043
2044     \code
2045         fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2046     \endcode
2047
2048     If \a index is equal to count() then a new item is appended to the
2049     list. Otherwise, \a index must be an element in the list.
2050
2051     \sa append()
2052 */
2053 void QQuickListModel::set(int index, const QQmlV8Handle &handle)
2054 {
2055     v8::Handle<v8::Value> valuemap = handle.toHandle();
2056
2057     if (!valuemap->IsObject() || valuemap->IsArray()) {
2058         qmlInfo(this) << tr("set: value is not an object");
2059         return;
2060     }
2061     if (index > count() || index < 0) {
2062         qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2063         return;
2064     }
2065
2066     v8::Handle<v8::Object> object = valuemap->ToObject();
2067
2068     if (index == count()) {
2069
2070         if (m_dynamicRoles) {
2071             m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
2072         } else {
2073             m_listModel->insert(index, object, engine());
2074         }
2075
2076         emitItemsInserted(index, 1);
2077     } else {
2078
2079         QList<int> roles;
2080
2081         if (m_dynamicRoles) {
2082             m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
2083         } else {
2084             m_listModel->set(index, object, &roles, engine());
2085         }
2086
2087         if (roles.count())
2088             emitItemsChanged(index, 1, roles);
2089     }
2090 }
2091
2092 /*!
2093     \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
2094
2095     Changes the \a property of the item at \a index in the list model to \a value.
2096
2097     \code
2098         fruitModel.setProperty(3, "cost", 5.95)
2099     \endcode
2100
2101     The \a index must be an element in the list.
2102
2103     \sa append()
2104 */
2105 void QQuickListModel::setProperty(int index, const QString& property, const QVariant& value)
2106 {
2107     if (count() == 0 || index >= count() || index < 0) {
2108         qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2109         return;
2110     }
2111
2112     if (m_dynamicRoles) {
2113         int roleIndex = m_roles.indexOf(property);
2114         if (roleIndex == -1) {
2115             roleIndex = m_roles.count();
2116             m_roles.append(property);
2117         }
2118         if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
2119             QList<int> roles;
2120             roles << roleIndex;
2121             emitItemsChanged(index, 1, roles);
2122         }
2123     } else {
2124         int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2125         if (roleIndex != -1) {
2126
2127             QList<int> roles;
2128             roles << roleIndex;
2129
2130             emitItemsChanged(index, 1, roles);
2131         }
2132     }
2133 }
2134
2135 /*!
2136     \qmlmethod QtQuick2::ListModel::sync()
2137
2138     Writes any unsaved changes to the list model after it has been modified
2139     from a worker script.
2140 */
2141 void QQuickListModel::sync()
2142 {
2143     // This is just a dummy method to make it look like sync() exists in
2144     // ListModel (and not just QQuickListModelWorkerAgent) and to let
2145     // us document sync().
2146     qmlInfo(this) << "List sync() can only be called from a WorkerScript";
2147 }
2148
2149 bool QQuickListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
2150 {
2151     QList<QVariant> values = prop.assignedValues();
2152     for(int ii = 0; ii < values.count(); ++ii) {
2153         const QVariant &value = values.at(ii);
2154
2155         if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
2156             QQmlCustomParserNode node =
2157                 qvariant_cast<QQmlCustomParserNode>(value);
2158
2159             if (node.name() != listElementTypeName) {
2160                 const QMetaObject *mo = resolveType(node.name());
2161                 if (mo != &QQuickListElement::staticMetaObject) {
2162                     error(node, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2163                     return false;
2164                 }
2165                 listElementTypeName = node.name(); // cache right name for next time
2166             }
2167
2168             {
2169             ListInstruction li;
2170             li.type = ListInstruction::Push;
2171             li.dataIdx = -1;
2172             instr << li;
2173             }
2174
2175             QList<QQmlCustomParserProperty> props = node.properties();
2176             for(int jj = 0; jj < props.count(); ++jj) {
2177                 const QQmlCustomParserProperty &nodeProp = props.at(jj);
2178                 if (nodeProp.name().isEmpty()) {
2179                     error(nodeProp, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2180                     return false;
2181                 }
2182                 if (nodeProp.name() == QStringLiteral("id")) {
2183                     error(nodeProp, QQuickListModel::tr("ListElement: cannot use reserved \"id\" property"));
2184                     return false;
2185                 }
2186
2187                 ListInstruction li;
2188                 int ref = data.count();
2189                 data.append(nodeProp.name().toUtf8());
2190                 data.append('\0');
2191                 li.type = ListInstruction::Set;
2192                 li.dataIdx = ref;
2193                 instr << li;
2194
2195                 if(!compileProperty(nodeProp, instr, data))
2196                     return false;
2197
2198                 li.type = ListInstruction::Pop;
2199                 li.dataIdx = -1;
2200                 instr << li;
2201             }
2202
2203             {
2204             ListInstruction li;
2205             li.type = ListInstruction::Pop;
2206             li.dataIdx = -1;
2207             instr << li;
2208             }
2209
2210         } else {
2211
2212             QQmlScript::Variant variant =
2213                 qvariant_cast<QQmlScript::Variant>(value);
2214
2215             int ref = data.count();
2216
2217             QByteArray d;
2218             d += char(variant.type()); // type tag
2219             if (variant.isString()) {
2220                 d += variant.asString().toUtf8();
2221             } else if (variant.isNumber()) {
2222                 d += QByteArray::number(variant.asNumber(),'g',20);
2223             } else if (variant.isBoolean()) {
2224                 d += char(variant.asBoolean());
2225             } else if (variant.isScript()) {
2226                 if (definesEmptyList(variant.asScript())) {
2227                     d[0] = char(QQmlScript::Variant::Invalid); // marks empty list
2228                 } else {
2229                     QByteArray script = variant.asScript().toUtf8();
2230                     int v = evaluateEnum(script);
2231                     if (v<0) {
2232                         using namespace QQmlJS;
2233                         AST::Node *node = variant.asAST();
2234                         AST::StringLiteral *literal = 0;
2235                         if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
2236                             if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
2237                                 if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
2238                                     if (callExpr->arguments && !callExpr->arguments->next)
2239                                         literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
2240                                     if (!literal) {
2241                                         error(prop, QQuickListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
2242                                         return false;
2243                                     }
2244                                 } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
2245                                     if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
2246                                         literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
2247                                     if (!literal) {
2248                                         error(prop, QQuickListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
2249                                         return false;
2250                                     }
2251                                 }
2252                             }
2253                         }
2254
2255                         if (literal) {
2256                             d[0] = char(QQmlScript::Variant::String);
2257                             d += literal->value.toUtf8();
2258                         } else {
2259                             error(prop, QQuickListModel::tr("ListElement: cannot use script for property value"));
2260                             return false;
2261                         }
2262                     } else {
2263                         d[0] = char(QQmlScript::Variant::Number);
2264                         d += QByteArray::number(v);
2265                     }
2266                 }
2267             }
2268             d.append('\0');
2269             data.append(d);
2270
2271             ListInstruction li;
2272             li.type = ListInstruction::Value;
2273             li.dataIdx = ref;
2274             instr << li;
2275         }
2276     }
2277
2278     return true;
2279 }
2280
2281 QByteArray QQuickListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps)
2282 {
2283     QList<ListInstruction> instr;
2284     QByteArray data;
2285     listElementTypeName = QString(); // unknown
2286
2287     for(int ii = 0; ii < customProps.count(); ++ii) {
2288         const QQmlCustomParserProperty &prop = customProps.at(ii);
2289         if(!prop.name().isEmpty()) { // isn't default property
2290             error(prop, QQuickListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
2291             return QByteArray();
2292         }
2293
2294         if(!compileProperty(prop, instr, data)) {
2295             return QByteArray();
2296         }
2297     }
2298
2299     int size = sizeof(ListModelData) +
2300                instr.count() * sizeof(ListInstruction) +
2301                data.count();
2302
2303     QByteArray rv;
2304     rv.resize(size);
2305
2306     ListModelData *lmd = (ListModelData *)rv.data();
2307     lmd->dataOffset = sizeof(ListModelData) +
2308                      instr.count() * sizeof(ListInstruction);
2309     lmd->instrCount = instr.count();
2310     for (int ii = 0; ii < instr.count(); ++ii)
2311         lmd->instructions()[ii] = instr.at(ii);
2312     ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
2313
2314     return rv;
2315 }
2316
2317 void QQuickListModelParser::setCustomData(QObject *obj, const QByteArray &d)
2318 {
2319     QQuickListModel *rv = static_cast<QQuickListModel *>(obj);
2320
2321     QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv));
2322     rv->m_engine = engine;
2323
2324     const ListModelData *lmd = (const ListModelData *)d.constData();
2325     const char *data = ((const char *)lmd) + lmd->dataOffset;
2326
2327     QStack<DataStackElement> stack;
2328
2329     for (int ii = 0; ii < lmd->instrCount; ++ii) {
2330         const ListInstruction &instr = lmd->instructions()[ii];
2331
2332         switch(instr.type) {
2333         case ListInstruction::Push:
2334             {
2335                 Q_ASSERT(!rv->m_dynamicRoles);
2336
2337                 ListModel *subModel = 0;
2338
2339                 if (stack.count() == 0) {
2340                     subModel = rv->m_listModel;
2341                 } else {
2342                     const DataStackElement &e0 = stack.at(stack.size() - 1);
2343                     DataStackElement &e1 = stack[stack.size() - 2];
2344
2345                     const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2346                     if (role.type == ListLayout::Role::List) {
2347                         subModel = e1.model->getListProperty(e1.elementIndex, role);
2348
2349                         if (subModel == 0) {
2350                             subModel = new ListModel(role.subLayout, 0, -1);
2351                             QVariant vModel = QVariant::fromValue(subModel);
2352                             e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
2353                         }
2354                     }
2355                 }
2356
2357                 DataStackElement e;
2358                 e.model = subModel;
2359                 e.elementIndex = subModel ? subModel->appendElement() : -1;
2360                 stack.push(e);
2361             }
2362             break;
2363
2364         case ListInstruction::Pop:
2365             stack.pop();
2366             break;
2367
2368         case ListInstruction::Value:
2369             {
2370                 const DataStackElement &e0 = stack.at(stack.size() - 1);
2371                 DataStackElement &e1 = stack[stack.size() - 2];
2372
2373                 QString name = e0.name;
2374                 QVariant value;
2375
2376                 switch (QQmlScript::Variant::Type(data[instr.dataIdx])) {
2377                     case QQmlScript::Variant::Invalid:
2378                         {
2379                             const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2380                             ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2381                             value = QVariant::fromValue(emptyModel);
2382                         }
2383                         break;
2384                     case QQmlScript::Variant::Boolean:
2385                         value = bool(data[1 + instr.dataIdx]);
2386                         break;
2387                     case QQmlScript::Variant::Number:
2388                         value = QByteArray(data + 1 + instr.dataIdx).toDouble();
2389                         break;
2390                     case QQmlScript::Variant::String:
2391                         value = QString::fromUtf8(data + 1 + instr.dataIdx);
2392                         break;
2393                     default:
2394                         Q_ASSERT("Format error in ListInstruction");
2395                 }
2396
2397                 e1.model->setOrCreateProperty(e1.elementIndex, name, value);
2398             }
2399             break;
2400
2401         case ListInstruction::Set:
2402             {
2403                 DataStackElement e;
2404                 e.name = QString::fromUtf8(data + instr.dataIdx);
2405                 stack.push(e);
2406             }
2407             break;
2408         }
2409     }
2410 }
2411
2412 bool QQuickListModelParser::definesEmptyList(const QString &s)
2413 {
2414     if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2415         for (int i=1; i<s.length()-1; i++) {
2416             if (!s[i].isSpace())
2417                 return false;
2418         }
2419         return true;
2420     }
2421     return false;
2422 }
2423
2424
2425 /*!
2426     \qmlclass ListElement QQuickListElement
2427     \inqmlmodule QtQuick 2
2428     \ingroup qml-working-with-data
2429     \brief The ListElement element defines a data item in a ListModel.
2430
2431     List elements are defined inside ListModel definitions, and represent items in a
2432     list that will be displayed using ListView or \l Repeater items.
2433
2434     List elements are defined like other QML elements except that they contain
2435     a collection of \e role definitions instead of properties. Using the same
2436     syntax as property definitions, roles both define how the data is accessed
2437     and include the data itself.
2438
2439     The names used for roles must begin with a lower-case letter and should be
2440     common to all elements in a given model. Values must be simple constants; either
2441     strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2442     (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2443
2444     \section1 Referencing Roles
2445
2446     The role names are used by delegates to obtain data from list elements.
2447     Each role name is accessible in the delegate's scope, and refers to the
2448     corresponding role in the current element. Where a role name would be
2449     ambiguous to use, it can be accessed via the \l{ListView::}{model}
2450     property (e.g., \c{model.cost} instead of \c{cost}).
2451
2452     \section1 Example Usage
2453
2454     The following model defines a series of list elements, each of which
2455     contain "name" and "cost" roles and their associated values.
2456
2457     \snippet doc/src/snippets/qml/qml-data-models/listelements.qml model
2458
2459     The delegate obtains the name and cost for each element by simply referring
2460     to \c name and \c cost:
2461
2462     \snippet doc/src/snippets/qml/qml-data-models/listelements.qml view
2463
2464     \sa ListModel
2465 */
2466
2467 QT_END_NAMESPACE