Doc: Grouped Qt Quick types into several groups
[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     memset(data, 0, sizeof(data));
956 }
957
958 ListElement::ListElement(int existingUid)
959 {
960     m_objectCache = 0;
961     uid = existingUid;
962     next = 0;
963     memset(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         if (role.type == ListLayout::Role::List) {
1114             ListModel *subModel = new ListModel(role.subLayout, 0, -1);
1115             v8::Handle<v8::Array> subArray = v8::Handle<v8::Array>::Cast(d);
1116             int arrayLength = subArray->Length();
1117             for (int j=0 ; j < arrayLength ; ++j) {
1118                 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
1119                 subModel->append(subObject, eng);
1120             }
1121             roleIndex = setListProperty(role, subModel);
1122         } else {
1123             qmlInfo(0) << QString::fromLatin1("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
1124         }
1125     } else if (d->IsBoolean()) {
1126         roleIndex = setBoolProperty(role, d->BooleanValue());
1127     } else if (d->IsObject()) {
1128         QV8ObjectResource *r = (QV8ObjectResource *) d->ToObject()->GetExternalResource();
1129         if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
1130             QObject *o = QV8QObjectWrapper::toQObject(r);
1131             roleIndex = setQObjectProperty(role, o);
1132         } else if (role.type == ListLayout::Role::VariantMap) {
1133             roleIndex = setVariantMapProperty(role, d->ToObject(), eng);
1134         }
1135     } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
1136         clearProperty(role);
1137     }
1138
1139     return roleIndex;
1140 }
1141
1142 ModelObject::ModelObject(QQuickListModel *model, int elementIndex)
1143 : m_model(model), m_elementIndex(elementIndex), m_meta(new ModelNodeMetaObject(this))
1144 {
1145     updateValues();
1146     setNodeUpdatesEnabled(true);
1147 }
1148
1149 void ModelObject::updateValues()
1150 {
1151     int roleCount = m_model->m_listModel->roleCount();
1152     for (int i=0 ; i < roleCount ; ++i) {
1153         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1154         QByteArray name = role.name.toUtf8();
1155         const QVariant &data = m_model->data(m_elementIndex, i);
1156         setValue(name, data, role.type == ListLayout::Role::List);
1157     }
1158 }
1159
1160 void ModelObject::updateValues(const QList<int> &roles)
1161 {
1162     int roleCount = roles.count();
1163     for (int i=0 ; i < roleCount ; ++i) {
1164         int roleIndex = roles.at(i);
1165         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1166         QByteArray name = role.name.toUtf8();
1167         const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1168         setValue(name, data, role.type == ListLayout::Role::List);
1169     }
1170 }
1171
1172 ModelNodeMetaObject::ModelNodeMetaObject(ModelObject *object)
1173 : QQmlOpenMetaObject(object), m_enabled(false), m_obj(object)
1174 {
1175 }
1176
1177 ModelNodeMetaObject::~ModelNodeMetaObject()
1178 {
1179 }
1180
1181 void ModelNodeMetaObject::propertyWritten(int index)
1182 {
1183     if (!m_enabled)
1184         return;
1185
1186     QV8Engine *eng = m_obj->m_model->engine();
1187
1188     QString propName = QString::fromUtf8(name(index));
1189     QVariant value = operator[](index);
1190
1191     v8::HandleScope handle_scope;
1192     v8::Context::Scope scope(eng->context());
1193
1194     v8::Handle<v8::Value> v = eng->fromVariant(value);
1195
1196     int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng);
1197     if (roleIndex != -1) {
1198         QList<int> roles;
1199         roles << roleIndex;
1200         m_obj->m_model->emitItemsChanged(m_obj->m_elementIndex, 1, roles);
1201     }
1202 }
1203
1204 DynamicRoleModelNode::DynamicRoleModelNode(QQuickListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1205 {
1206     setNodeUpdatesEnabled(true);
1207 }
1208
1209 DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQuickListModel *owner)
1210 {
1211     DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1212     QList<int> roles;
1213     object->updateValues(obj, roles);
1214     return object;
1215 }
1216
1217 void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQuickListModel *> *targetModelHash)
1218 {
1219     for (int i=0 ; i < src->m_meta->count() ; ++i) {
1220         const QByteArray &name = src->m_meta->name(i);
1221         QVariant value = src->m_meta->value(i);
1222
1223         QQuickListModel *srcModel = qobject_cast<QQuickListModel *>(value.value<QObject *>());
1224         QQuickListModel *targetModel = qobject_cast<QQuickListModel *>(target->m_meta->value(i).value<QObject *>());
1225
1226         if (srcModel) {
1227             if (targetModel == 0)
1228                 targetModel = QQuickListModel::createWithOwner(target->m_owner);
1229
1230             QQuickListModel::sync(srcModel, targetModel, targetModelHash);
1231
1232             QObject *targetModelObject = targetModel;
1233             value = QVariant::fromValue(targetModelObject);
1234         } else if (targetModel) {
1235             delete targetModel;
1236         }
1237
1238         target->setValue(name, value);
1239     }
1240 }
1241
1242 void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
1243 {
1244     const QList<QString> &keys = object.keys();
1245
1246     QList<QString>::const_iterator it = keys.begin();
1247     QList<QString>::const_iterator end = keys.end();
1248
1249     while (it != end) {
1250         const QString &key = *it;
1251
1252         int roleIndex = m_owner->m_roles.indexOf(key);
1253         if (roleIndex == -1) {
1254             roleIndex = m_owner->m_roles.count();
1255             m_owner->m_roles.append(key);
1256         }
1257
1258         QVariant value = object[key];
1259
1260         if (value.type() == QVariant::List) {
1261             QQuickListModel *subModel = QQuickListModel::createWithOwner(m_owner);
1262
1263             QVariantList subArray = value.toList();
1264             QVariantList::const_iterator subIt = subArray.begin();
1265             QVariantList::const_iterator subEnd = subArray.end();
1266             while (subIt != subEnd) {
1267                 const QVariantMap &subObject = subIt->toMap();
1268                 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1269                 ++subIt;
1270             }
1271
1272             QObject *subModelObject = subModel;
1273             value = QVariant::fromValue(subModelObject);
1274         }
1275
1276         const QByteArray &keyUtf8 = key.toUtf8();
1277
1278         QQuickListModel *existingModel = qobject_cast<QQuickListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1279         if (existingModel)
1280             delete existingModel;
1281
1282         if (m_meta->setValue(keyUtf8, value))
1283             roles << roleIndex;
1284
1285         ++it;
1286     }
1287 }
1288
1289 DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1290     : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1291 {
1292 }
1293
1294 DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1295 {
1296     for (int i=0 ; i < count() ; ++i) {
1297         QQuickListModel *subModel = qobject_cast<QQuickListModel *>(value(i).value<QObject *>());
1298         if (subModel)
1299             delete subModel;
1300     }
1301 }
1302
1303 void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1304 {
1305     if (!m_enabled)
1306         return;
1307
1308     QVariant v = value(index);
1309     QQuickListModel *model = qobject_cast<QQuickListModel *>(v.value<QObject *>());
1310     if (model)
1311         delete model;
1312 }
1313
1314 void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1315 {
1316     if (!m_enabled)
1317         return;
1318
1319     QQuickListModel *parentModel = m_owner->m_owner;
1320
1321     QVariant v = value(index);
1322     if (v.type() == QVariant::List) {
1323         QQuickListModel *subModel = QQuickListModel::createWithOwner(parentModel);
1324
1325         QVariantList subArray = v.toList();
1326         QVariantList::const_iterator subIt = subArray.begin();
1327         QVariantList::const_iterator subEnd = subArray.end();
1328         while (subIt != subEnd) {
1329             const QVariantMap &subObject = subIt->toMap();
1330             subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1331             ++subIt;
1332         }
1333
1334         QObject *subModelObject = subModel;
1335         v = QVariant::fromValue(subModelObject);
1336
1337         setValue(index, v);
1338     }
1339
1340     int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1341     int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1342
1343     if (elementIndex != -1 && roleIndex != -1) {
1344         QList<int> roles;
1345         roles << roleIndex;
1346
1347         parentModel->emitItemsChanged(elementIndex, 1, roles);
1348     }
1349 }
1350
1351 QQuickListModelParser::ListInstruction *QQuickListModelParser::ListModelData::instructions() const
1352 {
1353     return (QQuickListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
1354 }
1355
1356 /*!
1357     \qmlclass ListModel QQuickListModel
1358     \inqmlmodule QtQuick 2
1359     \brief Defines a free-form list data source
1360     \ingroup qtquick-models
1361
1362     The ListModel is a simple container of ListElement definitions, each containing data roles.
1363     The contents can be defined dynamically, or explicitly in QML.
1364
1365     The number of elements in the model can be obtained from its \l count property.
1366     A number of familiar methods are also provided to manipulate the contents of the
1367     model, including append(), insert(), move(), remove() and set(). These methods
1368     accept dictionaries as their arguments; these are translated to ListElement objects
1369     by the model.
1370
1371     Elements can be manipulated via the model using the setProperty() method, which
1372     allows the roles of the specified element to be set and changed.
1373
1374     \section1 Example Usage
1375
1376     The following example shows a ListModel containing three elements, with the roles
1377     "name" and "cost".
1378
1379     \div {class="float-right"}
1380     \inlineimage listmodel.png
1381     \enddiv
1382
1383     \snippet qml/listmodel/listmodel.qml 0
1384
1385     Roles (properties) in each element must begin with a lower-case letter and
1386     should be common to all elements in a model. The ListElement documentation
1387     provides more guidelines for how elements should be defined.
1388
1389     Since the example model contains an \c id property, it can be referenced
1390     by views, such as the ListView in this example:
1391
1392     \snippet qml/listmodel/listmodel-simple.qml 0
1393     \dots 8
1394     \snippet qml/listmodel/listmodel-simple.qml 1
1395
1396     It is possible for roles to contain list data.  In the following example we
1397     create a list of fruit attributes:
1398
1399     \snippet qml/listmodel/listmodel-nested.qml model
1400
1401     The delegate displays all the fruit attributes:
1402
1403     \div {class="float-right"}
1404     \inlineimage listmodel-nested.png
1405     \enddiv
1406
1407     \snippet qml/listmodel/listmodel-nested.qml delegate
1408
1409     \section1 Modifying List Models
1410
1411     The content of a ListModel may be created and modified using the clear(),
1412     append(), set(), insert() and setProperty() methods.  For example:
1413
1414     \snippet qml/listmodel/listmodel-modify.qml delegate
1415
1416     Note that when creating content dynamically the set of available properties
1417     cannot be changed once set. Whatever properties are first added to the model
1418     are the only permitted properties in the model.
1419
1420     \section1 Using Threaded List Models with WorkerScript
1421
1422     ListModel can be used together with WorkerScript access a list model
1423     from multiple threads. This is useful if list modifications are
1424     synchronous and take some time: the list operations can be moved to a
1425     different thread to avoid blocking of the main GUI thread.
1426
1427     Here is an example that uses WorkerScript to periodically append the
1428     current time to a list model:
1429
1430     \snippet examples/quick/threading/threadedlistmodel/timedisplay.qml 0
1431
1432     The included file, \tt dataloader.js, looks like this:
1433
1434     \snippet examples/quick/threading/threadedlistmodel/dataloader.js 0
1435
1436     The timer in the main example sends messages to the worker script by calling
1437     \l WorkerScript::sendMessage(). When this message is received,
1438     \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
1439     which appends the current time to the list model.
1440
1441     Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
1442     handler. You must call sync() or else the changes made to the list from the external
1443     thread will not be reflected in the list model in the main thread.
1444
1445     \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtQml
1446 */
1447
1448 QQuickListModel::QQuickListModel(QObject *parent)
1449 : QListModelInterface(parent)
1450 {
1451     m_mainThread = true;
1452     m_primary = true;
1453     m_agent = 0;
1454     m_uid = uidCounter.fetchAndAddOrdered(1);
1455     m_dynamicRoles = false;
1456
1457     m_layout = new ListLayout;
1458     m_listModel = new ListModel(m_layout, this, -1);
1459
1460     m_engine = 0;
1461 }
1462
1463 QQuickListModel::QQuickListModel(const QQuickListModel *owner, ListModel *data, QV8Engine *eng, QObject *parent)
1464 : QListModelInterface(parent)
1465 {
1466     m_mainThread = owner->m_mainThread;
1467     m_primary = false;
1468     m_agent = owner->m_agent;
1469
1470     Q_ASSERT(owner->m_dynamicRoles == false);
1471     m_dynamicRoles = false;
1472     m_layout = 0;
1473     m_listModel = data;
1474
1475     m_engine = eng;
1476 }
1477
1478 QQuickListModel::QQuickListModel(QQuickListModel *orig, QQuickListModelWorkerAgent *agent)
1479 : QListModelInterface(agent)
1480 {
1481     m_mainThread = false;
1482     m_primary = true;
1483     m_agent = agent;
1484     m_dynamicRoles = orig->m_dynamicRoles;
1485
1486     m_layout = new ListLayout(orig->m_layout);
1487     m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1488
1489     if (m_dynamicRoles)
1490         sync(orig, this, 0);
1491     else
1492         ListModel::sync(orig->m_listModel, m_listModel, 0);
1493
1494     m_engine = 0;
1495 }
1496
1497 QQuickListModel::~QQuickListModel()
1498 {
1499     for (int i=0 ; i < m_modelObjects.count() ; ++i)
1500         delete m_modelObjects[i];
1501
1502     if (m_primary) {
1503         m_listModel->destroy();
1504         delete m_listModel;
1505
1506         if (m_mainThread && m_agent) {
1507             m_agent->modelDestroyed();
1508             m_agent->release();
1509         }
1510     }
1511
1512     m_listModel = 0;
1513
1514     delete m_layout;
1515     m_layout = 0;
1516 }
1517
1518 QQuickListModel *QQuickListModel::createWithOwner(QQuickListModel *newOwner)
1519 {
1520     QQuickListModel *model = new QQuickListModel;
1521
1522     model->m_mainThread = newOwner->m_mainThread;
1523     model->m_engine = newOwner->m_engine;
1524     model->m_agent = newOwner->m_agent;
1525     model->m_dynamicRoles = newOwner->m_dynamicRoles;
1526
1527     if (model->m_mainThread && model->m_agent)
1528         model->m_agent->addref();
1529
1530     QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1531
1532     return model;
1533 }
1534
1535 QV8Engine *QQuickListModel::engine() const
1536 {
1537     if (m_engine == 0) {
1538         m_engine  = QQmlEnginePrivate::getV8Engine(qmlEngine(this));
1539     }
1540
1541     return m_engine;
1542 }
1543
1544 void QQuickListModel::sync(QQuickListModel *src, QQuickListModel *target, QHash<int, QQuickListModel *> *targetModelHash)
1545 {
1546     Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1547
1548     target->m_uid = src->m_uid;
1549     if (targetModelHash)
1550         targetModelHash->insert(target->m_uid, target);
1551     target->m_roles = src->m_roles;
1552
1553     // Build hash of elements <-> uid for each of the lists
1554     QHash<int, ElementSync> elementHash;
1555     for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1556         DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1557         int uid = e->getUid();
1558         ElementSync sync;
1559         sync.target = e;
1560         elementHash.insert(uid, sync);
1561     }
1562     for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1563         DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1564         int uid = e->getUid();
1565
1566         QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1567         if (it == elementHash.end()) {
1568             ElementSync sync;
1569             sync.src = e;
1570             elementHash.insert(uid, sync);
1571         } else {
1572             ElementSync &sync = it.value();
1573             sync.src = e;
1574         }
1575     }
1576
1577     // Get list of elements that are in the target but no longer in the source. These get deleted first.
1578     QHash<int, ElementSync>::iterator it = elementHash.begin();
1579     QHash<int, ElementSync>::iterator end = elementHash.end();
1580     while (it != end) {
1581         const ElementSync &s = it.value();
1582         if (s.src == 0) {
1583             int targetIndex = target->m_modelObjects.indexOf(s.target);
1584             target->m_modelObjects.remove(targetIndex, 1);
1585             delete s.target;
1586         }
1587         ++it;
1588     }
1589
1590     // Clear the target list, and append in correct order from the source
1591     target->m_modelObjects.clear();
1592     for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1593         DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1594         it = elementHash.find(srcElement->getUid());
1595         const ElementSync &s = it.value();
1596         DynamicRoleModelNode *targetElement = s.target;
1597         if (targetElement == 0) {
1598             targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1599         }
1600         DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1601         target->m_modelObjects.append(targetElement);
1602     }
1603 }
1604
1605 void QQuickListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
1606 {
1607     if (m_mainThread) {
1608         emit itemsChanged(index, count, roles);
1609     } else {
1610         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1611         m_agent->data.changedChange(uid, index, count, roles);
1612     }
1613 }
1614
1615 void QQuickListModel::emitItemsRemoved(int index, int count)
1616 {
1617     if (m_mainThread) {
1618         emit itemsRemoved(index, count);
1619         emit countChanged();
1620     } else {
1621         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1622         if (index == 0 && count == this->count())
1623             m_agent->data.clearChange(uid);
1624         m_agent->data.removeChange(uid, index, count);
1625     }
1626 }
1627
1628 void QQuickListModel::emitItemsInserted(int index, int count)
1629 {
1630     if (m_mainThread) {
1631         emit itemsInserted(index, count);
1632         emit countChanged();
1633     } else {
1634         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1635         m_agent->data.insertChange(uid, index, count);
1636     }
1637 }
1638
1639 void QQuickListModel::emitItemsMoved(int from, int to, int n)
1640 {
1641     if (m_mainThread) {
1642         emit itemsMoved(from, to, n);
1643     } else {
1644         int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1645         m_agent->data.moveChange(uid, from, n, to);
1646     }
1647 }
1648
1649 QQuickListModelWorkerAgent *QQuickListModel::agent()
1650 {
1651     if (m_agent)
1652         return m_agent;
1653
1654     m_agent = new QQuickListModelWorkerAgent(this);
1655     return m_agent;
1656 }
1657
1658 QList<int> QQuickListModel::roles() const
1659 {
1660     QList<int> rolesArray;
1661
1662     if (m_dynamicRoles) {
1663         for (int i=0 ; i < m_roles.count() ; ++i)
1664             rolesArray << i;
1665     } else {
1666         for (int i=0 ; i < m_listModel->roleCount() ; ++i)
1667             rolesArray << i;
1668     }
1669
1670     return rolesArray;
1671 }
1672
1673 QString QQuickListModel::toString(int role) const
1674 {
1675     QString roleName;
1676
1677     if (m_dynamicRoles) {
1678         roleName = m_roles[role];
1679     } else {
1680         const ListLayout::Role &r = m_listModel->getExistingRole(role);
1681         roleName = r.name;
1682     }
1683
1684     return roleName;
1685 }
1686
1687 QVariant QQuickListModel::data(int index, int role) const
1688 {
1689     QVariant v;
1690
1691     if (index >= count() || index < 0)
1692         return v;
1693
1694     if (m_dynamicRoles)
1695         v = m_modelObjects[index]->getValue(m_roles[role]);
1696     else
1697         v = m_listModel->getProperty(index, role, this, engine());
1698
1699     return v;
1700 }
1701
1702 /*!
1703     \qmlproperty bool QtQuick2::ListModel::dynamicRoles
1704
1705     By default, the type of a role is fixed the first time
1706     the role is used. For example, if you create a role called
1707     "data" and assign a number to it, you can no longer assign
1708     a string to the "data" role. However, when the dynamicRoles
1709     property is enabled, the type of a given role is not fixed
1710     and can be different between elements.
1711
1712     The dynamicRoles property must be set before any data is
1713     added to the ListModel, and must be set from the main
1714     thread.
1715
1716     A ListModel that has data statically defined (via the
1717     ListElement QML syntax) cannot have the dynamicRoles
1718     property enabled.
1719
1720     There is a significant performance cost to using a
1721     ListModel with dynamic roles enabled. The cost varies
1722     from platform to platform but is typically somewhere
1723     between 4-6x slower than using static role types.
1724
1725     Due to the performance cost of using dynamic roles,
1726     they are disabled by default.
1727 */
1728 void QQuickListModel::setDynamicRoles(bool enableDynamicRoles)
1729 {
1730     if (m_mainThread && m_agent == 0) {
1731         if (enableDynamicRoles) {
1732             if (m_layout->roleCount())
1733                 qmlInfo(this) << tr("unable to enable dynamic roles as this model is not empty!");
1734             else
1735                 m_dynamicRoles = true;
1736         } else {
1737             if (m_roles.count()) {
1738                 qmlInfo(this) << tr("unable to enable static roles as this model is not empty!");
1739             } else {
1740                 m_dynamicRoles = false;
1741             }
1742         }
1743     } else {
1744         qmlInfo(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
1745     }
1746 }
1747
1748 /*!
1749     \qmlproperty int QtQuick2::ListModel::count
1750     The number of data entries in the model.
1751 */
1752 int QQuickListModel::count() const
1753 {
1754     int count;
1755
1756     if (m_dynamicRoles)
1757         count = m_modelObjects.count();
1758     else {
1759         count = m_listModel->elementCount();
1760     }
1761
1762     return count;
1763 }
1764
1765 /*!
1766     \qmlmethod QtQuick2::ListModel::clear()
1767
1768     Deletes all content from the model.
1769
1770     \sa append(), remove()
1771 */
1772 void QQuickListModel::clear()
1773 {
1774     int cleared = count();
1775
1776     if (m_dynamicRoles) {
1777         for (int i=0 ; i < m_modelObjects.count() ; ++i)
1778             delete m_modelObjects[i];
1779         m_modelObjects.clear();
1780     } else {
1781         m_listModel->clear();
1782     }
1783
1784     emitItemsRemoved(0, cleared);
1785 }
1786
1787 /*!
1788     \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
1789
1790     Deletes the content at \a index from the model.
1791
1792     \sa clear()
1793 */
1794 void QQuickListModel::remove(QQmlV8Function *args)
1795 {
1796     int argLength = args->Length();
1797
1798     if (argLength == 1 || argLength == 2) {
1799         int index = (*args)[0]->Int32Value();
1800         int removeCount = (argLength == 2 ? ((*args)[1]->Int32Value()) : 1);
1801
1802         if (index < 0 || index+removeCount > count() || removeCount <= 0) {
1803             qmlInfo(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
1804             return;
1805         }
1806
1807         if (m_dynamicRoles) {
1808             for (int i=0 ; i < removeCount ; ++i)
1809                 delete m_modelObjects[index+i];
1810             m_modelObjects.remove(index, removeCount);
1811         } else {
1812             m_listModel->remove(index, removeCount);
1813         }
1814
1815         emitItemsRemoved(index, removeCount);
1816     } else {
1817         qmlInfo(this) << tr("remove: incorrect number of arguments");
1818     }
1819 }
1820
1821 /*!
1822     \qmlmethod QtQuick2::ListModel::insert(int index, jsobject dict)
1823
1824     Adds a new item to the list model at position \a index, with the
1825     values in \a dict.
1826
1827     \code
1828         fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
1829     \endcode
1830
1831     The \a index must be to an existing item in the list, or one past
1832     the end of the list (equivalent to append).
1833
1834     \sa set(), append()
1835 */
1836
1837 void QQuickListModel::insert(QQmlV8Function *args)
1838 {
1839     if (args->Length() == 2) {
1840
1841         v8::Handle<v8::Value> arg0 = (*args)[0];
1842         int index = arg0->Int32Value();
1843
1844         if (index < 0 || index > count()) {
1845             qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
1846             return;
1847         }
1848
1849         v8::Handle<v8::Value> arg1 = (*args)[1];
1850
1851         if (arg1->IsArray()) {
1852             v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg1);
1853             int objectArrayLength = objectArray->Length();
1854             for (int i=0 ; i < objectArrayLength ; ++i) {
1855                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1856
1857                 if (m_dynamicRoles) {
1858                     m_modelObjects.insert(index+i, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1859                 } else {
1860                     m_listModel->insert(index+i, argObject, args->engine());
1861                 }
1862             }
1863             emitItemsInserted(index, objectArrayLength);
1864         } else if (arg1->IsObject()) {
1865             v8::Handle<v8::Object> argObject = arg1->ToObject();
1866
1867             if (m_dynamicRoles) {
1868                 m_modelObjects.insert(index, DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1869             } else {
1870                 m_listModel->insert(index, argObject, args->engine());
1871             }
1872
1873             emitItemsInserted(index, 1);
1874         } else {
1875             qmlInfo(this) << tr("insert: value is not an object");
1876         }
1877     } else {
1878         qmlInfo(this) << tr("insert: value is not an object");
1879     }
1880 }
1881
1882 /*!
1883     \qmlmethod QtQuick2::ListModel::move(int from, int to, int n)
1884
1885     Moves \a n items \a from one position \a to another.
1886
1887     The from and to ranges must exist; for example, to move the first 3 items
1888     to the end of the list:
1889
1890     \code
1891         fruitModel.move(0, fruitModel.count - 3, 3)
1892     \endcode
1893
1894     \sa append()
1895 */
1896 void QQuickListModel::move(int from, int to, int n)
1897 {
1898     if (n==0 || from==to)
1899         return;
1900     if (!canMove(from, to, n)) {
1901         qmlInfo(this) << tr("move: out of range");
1902         return;
1903     }
1904
1905     if (m_dynamicRoles) {
1906
1907         int realFrom = from;
1908         int realTo = to;
1909         int realN = n;
1910
1911         if (from > to) {
1912             // Only move forwards - flip if backwards moving
1913             int tfrom = from;
1914             int tto = to;
1915             realFrom = tto;
1916             realTo = tto+n;
1917             realN = tfrom-tto;
1918         }
1919
1920         QPODVector<DynamicRoleModelNode *, 4> store;
1921         for (int i=0 ; i < (realTo-realFrom) ; ++i)
1922             store.append(m_modelObjects[realFrom+realN+i]);
1923         for (int i=0 ; i < realN ; ++i)
1924             store.append(m_modelObjects[realFrom+i]);
1925         for (int i=0 ; i < store.count() ; ++i)
1926             m_modelObjects[realFrom+i] = store[i];
1927
1928     } else {
1929         m_listModel->move(from, to, n);
1930     }
1931
1932     emitItemsMoved(from, to, n);
1933 }
1934
1935 /*!
1936     \qmlmethod QtQuick2::ListModel::append(jsobject dict)
1937
1938     Adds a new item to the end of the list model, with the
1939     values in \a dict.
1940
1941     \code
1942         fruitModel.append({"cost": 5.95, "name":"Pizza"})
1943     \endcode
1944
1945     \sa set(), remove()
1946 */
1947 void QQuickListModel::append(QQmlV8Function *args)
1948 {
1949     if (args->Length() == 1) {
1950         v8::Handle<v8::Value> arg = (*args)[0];
1951
1952         if (arg->IsArray()) {
1953             v8::Handle<v8::Array> objectArray = v8::Handle<v8::Array>::Cast(arg);
1954             int objectArrayLength = objectArray->Length();
1955
1956             int index = count();
1957             for (int i=0 ; i < objectArrayLength ; ++i) {
1958                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
1959
1960                 if (m_dynamicRoles) {
1961                     m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1962                 } else {
1963                     m_listModel->append(argObject, args->engine());
1964                 }
1965             }
1966
1967             emitItemsInserted(index, objectArrayLength);
1968         } else if (arg->IsObject()) {
1969             v8::Handle<v8::Object> argObject = arg->ToObject();
1970
1971             int index;
1972
1973             if (m_dynamicRoles) {
1974                 index = m_modelObjects.count();
1975                 m_modelObjects.append(DynamicRoleModelNode::create(args->engine()->variantMapFromJS(argObject), this));
1976             } else {
1977                 index = m_listModel->append(argObject, args->engine());
1978             }
1979
1980             emitItemsInserted(index, 1);
1981         } else {
1982             qmlInfo(this) << tr("append: value is not an object");
1983         }
1984     } else {
1985         qmlInfo(this) << tr("append: value is not an object");
1986     }
1987 }
1988
1989 /*!
1990     \qmlmethod object QtQuick2::ListModel::get(int index)
1991
1992     Returns the item at \a index in the list model. This allows the item
1993     data to be accessed or modified from JavaScript:
1994
1995     \code
1996     Component.onCompleted: {
1997         fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
1998         console.log(fruitModel.get(0).cost);
1999         fruitModel.get(0).cost = 10.95;
2000     }
2001     \endcode
2002
2003     The \a index must be an element in the list.
2004
2005     Note that properties of the returned object that are themselves objects
2006     will also be models, and this get() method is used to access elements:
2007
2008     \code
2009         fruitModel.append(..., "attributes":
2010             [{"name":"spikes","value":"7mm"},
2011              {"name":"color","value":"green"}]);
2012         fruitModel.get(0).attributes.get(1).value; // == "green"
2013     \endcode
2014
2015     \warning The returned object is not guaranteed to remain valid. It
2016     should not be used in \l{Property Binding}{property bindings}.
2017
2018     \sa append()
2019 */
2020 QQmlV8Handle QQuickListModel::get(int index) const
2021 {
2022     v8::Handle<v8::Value> result = v8::Undefined();
2023
2024     if (index >= 0 && index < count()) {
2025         QV8Engine *v8engine = engine();
2026
2027         if (m_dynamicRoles) {
2028             DynamicRoleModelNode *object = m_modelObjects[index];
2029             result = v8engine->newQObject(object);
2030         } else {
2031             ModelObject *object = m_listModel->getOrCreateModelObject(const_cast<QQuickListModel *>(this), index);
2032             result = v8engine->newQObject(object);
2033         }
2034     }
2035
2036     return QQmlV8Handle::fromHandle(result);
2037 }
2038
2039 /*!
2040     \qmlmethod QtQuick2::ListModel::set(int index, jsobject dict)
2041
2042     Changes the item at \a index in the list model with the
2043     values in \a dict. Properties not appearing in \a dict
2044     are left unchanged.
2045
2046     \code
2047         fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2048     \endcode
2049
2050     If \a index is equal to count() then a new item is appended to the
2051     list. Otherwise, \a index must be an element in the list.
2052
2053     \sa append()
2054 */
2055 void QQuickListModel::set(int index, const QQmlV8Handle &handle)
2056 {
2057     v8::Handle<v8::Value> valuemap = handle.toHandle();
2058
2059     if (!valuemap->IsObject() || valuemap->IsArray()) {
2060         qmlInfo(this) << tr("set: value is not an object");
2061         return;
2062     }
2063     if (index > count() || index < 0) {
2064         qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2065         return;
2066     }
2067
2068     v8::Handle<v8::Object> object = valuemap->ToObject();
2069
2070     if (index == count()) {
2071
2072         if (m_dynamicRoles) {
2073             m_modelObjects.append(DynamicRoleModelNode::create(engine()->variantMapFromJS(object), this));
2074         } else {
2075             m_listModel->insert(index, object, engine());
2076         }
2077
2078         emitItemsInserted(index, 1);
2079     } else {
2080
2081         QList<int> roles;
2082
2083         if (m_dynamicRoles) {
2084             m_modelObjects[index]->updateValues(engine()->variantMapFromJS(object), roles);
2085         } else {
2086             m_listModel->set(index, object, &roles, engine());
2087         }
2088
2089         if (roles.count())
2090             emitItemsChanged(index, 1, roles);
2091     }
2092 }
2093
2094 /*!
2095     \qmlmethod QtQuick2::ListModel::setProperty(int index, string property, variant value)
2096
2097     Changes the \a property of the item at \a index in the list model to \a value.
2098
2099     \code
2100         fruitModel.setProperty(3, "cost", 5.95)
2101     \endcode
2102
2103     The \a index must be an element in the list.
2104
2105     \sa append()
2106 */
2107 void QQuickListModel::setProperty(int index, const QString& property, const QVariant& value)
2108 {
2109     if (count() == 0 || index >= count() || index < 0) {
2110         qmlInfo(this) << tr("set: index %1 out of range").arg(index);
2111         return;
2112     }
2113
2114     if (m_dynamicRoles) {
2115         int roleIndex = m_roles.indexOf(property);
2116         if (roleIndex == -1) {
2117             roleIndex = m_roles.count();
2118             m_roles.append(property);
2119         }
2120         if (m_modelObjects[index]->setValue(property.toUtf8(), value)) {
2121             QList<int> roles;
2122             roles << roleIndex;
2123             emitItemsChanged(index, 1, roles);
2124         }
2125     } else {
2126         int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2127         if (roleIndex != -1) {
2128
2129             QList<int> roles;
2130             roles << roleIndex;
2131
2132             emitItemsChanged(index, 1, roles);
2133         }
2134     }
2135 }
2136
2137 /*!
2138     \qmlmethod QtQuick2::ListModel::sync()
2139
2140     Writes any unsaved changes to the list model after it has been modified
2141     from a worker script.
2142 */
2143 void QQuickListModel::sync()
2144 {
2145     // This is just a dummy method to make it look like sync() exists in
2146     // ListModel (and not just QQuickListModelWorkerAgent) and to let
2147     // us document sync().
2148     qmlInfo(this) << "List sync() can only be called from a WorkerScript";
2149 }
2150
2151 bool QQuickListModelParser::compileProperty(const QQmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
2152 {
2153     QList<QVariant> values = prop.assignedValues();
2154     for(int ii = 0; ii < values.count(); ++ii) {
2155         const QVariant &value = values.at(ii);
2156
2157         if(value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
2158             QQmlCustomParserNode node =
2159                 qvariant_cast<QQmlCustomParserNode>(value);
2160
2161             if (node.name() != listElementTypeName) {
2162                 const QMetaObject *mo = resolveType(node.name());
2163                 if (mo != &QQuickListElement::staticMetaObject) {
2164                     error(node, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2165                     return false;
2166                 }
2167                 listElementTypeName = node.name(); // cache right name for next time
2168             }
2169
2170             {
2171             ListInstruction li;
2172             li.type = ListInstruction::Push;
2173             li.dataIdx = -1;
2174             instr << li;
2175             }
2176
2177             QList<QQmlCustomParserProperty> props = node.properties();
2178             for(int jj = 0; jj < props.count(); ++jj) {
2179                 const QQmlCustomParserProperty &nodeProp = props.at(jj);
2180                 if (nodeProp.name().isEmpty()) {
2181                     error(nodeProp, QQuickListModel::tr("ListElement: cannot contain nested elements"));
2182                     return false;
2183                 }
2184                 if (nodeProp.name() == QStringLiteral("id")) {
2185                     error(nodeProp, QQuickListModel::tr("ListElement: cannot use reserved \"id\" property"));
2186                     return false;
2187                 }
2188
2189                 ListInstruction li;
2190                 int ref = data.count();
2191                 data.append(nodeProp.name().toUtf8());
2192                 data.append('\0');
2193                 li.type = ListInstruction::Set;
2194                 li.dataIdx = ref;
2195                 instr << li;
2196
2197                 if(!compileProperty(nodeProp, instr, data))
2198                     return false;
2199
2200                 li.type = ListInstruction::Pop;
2201                 li.dataIdx = -1;
2202                 instr << li;
2203             }
2204
2205             {
2206             ListInstruction li;
2207             li.type = ListInstruction::Pop;
2208             li.dataIdx = -1;
2209             instr << li;
2210             }
2211
2212         } else {
2213
2214             QQmlScript::Variant variant =
2215                 qvariant_cast<QQmlScript::Variant>(value);
2216
2217             int ref = data.count();
2218
2219             QByteArray d;
2220             d += char(variant.type()); // type tag
2221             if (variant.isString()) {
2222                 d += variant.asString().toUtf8();
2223             } else if (variant.isNumber()) {
2224                 d += QByteArray::number(variant.asNumber(),'g',20);
2225             } else if (variant.isBoolean()) {
2226                 d += char(variant.asBoolean());
2227             } else if (variant.isScript()) {
2228                 if (definesEmptyList(variant.asScript())) {
2229                     d[0] = char(QQmlScript::Variant::Invalid); // marks empty list
2230                 } else {
2231                     QByteArray script = variant.asScript().toUtf8();
2232                     int v = evaluateEnum(script);
2233                     if (v<0) {
2234                         using namespace QQmlJS;
2235                         AST::Node *node = variant.asAST();
2236                         AST::StringLiteral *literal = 0;
2237                         if (AST::CallExpression *callExpr = AST::cast<AST::CallExpression *>(node)) {
2238                             if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(callExpr->base)) {
2239                                 if (idExpr->name == QLatin1String("QT_TR_NOOP") || idExpr->name == QLatin1String("QT_TRID_NOOP")) {
2240                                     if (callExpr->arguments && !callExpr->arguments->next)
2241                                         literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->expression);
2242                                     if (!literal) {
2243                                         error(prop, QQuickListModel::tr("ListElement: improperly specified %1").arg(idExpr->name.toString()));
2244                                         return false;
2245                                     }
2246                                 } else if (idExpr->name == QLatin1String("QT_TRANSLATE_NOOP")) {
2247                                     if (callExpr->arguments && callExpr->arguments->next && !callExpr->arguments->next->next)
2248                                         literal = AST::cast<AST::StringLiteral *>(callExpr->arguments->next->expression);
2249                                     if (!literal) {
2250                                         error(prop, QQuickListModel::tr("ListElement: improperly specified QT_TRANSLATE_NOOP"));
2251                                         return false;
2252                                     }
2253                                 }
2254                             }
2255                         }
2256
2257                         if (literal) {
2258                             d[0] = char(QQmlScript::Variant::String);
2259                             d += literal->value.toUtf8();
2260                         } else {
2261                             error(prop, QQuickListModel::tr("ListElement: cannot use script for property value"));
2262                             return false;
2263                         }
2264                     } else {
2265                         d[0] = char(QQmlScript::Variant::Number);
2266                         d += QByteArray::number(v);
2267                     }
2268                 }
2269             }
2270             d.append('\0');
2271             data.append(d);
2272
2273             ListInstruction li;
2274             li.type = ListInstruction::Value;
2275             li.dataIdx = ref;
2276             instr << li;
2277         }
2278     }
2279
2280     return true;
2281 }
2282
2283 QByteArray QQuickListModelParser::compile(const QList<QQmlCustomParserProperty> &customProps)
2284 {
2285     QList<ListInstruction> instr;
2286     QByteArray data;
2287     listElementTypeName = QString(); // unknown
2288
2289     for(int ii = 0; ii < customProps.count(); ++ii) {
2290         const QQmlCustomParserProperty &prop = customProps.at(ii);
2291         if(!prop.name().isEmpty()) { // isn't default property
2292             error(prop, QQuickListModel::tr("ListModel: undefined property '%1'").arg(prop.name()));
2293             return QByteArray();
2294         }
2295
2296         if(!compileProperty(prop, instr, data)) {
2297             return QByteArray();
2298         }
2299     }
2300
2301     int size = sizeof(ListModelData) +
2302                instr.count() * sizeof(ListInstruction) +
2303                data.count();
2304
2305     QByteArray rv;
2306     rv.resize(size);
2307
2308     ListModelData *lmd = (ListModelData *)rv.data();
2309     lmd->dataOffset = sizeof(ListModelData) +
2310                      instr.count() * sizeof(ListInstruction);
2311     lmd->instrCount = instr.count();
2312     for (int ii = 0; ii < instr.count(); ++ii)
2313         lmd->instructions()[ii] = instr.at(ii);
2314     ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
2315
2316     return rv;
2317 }
2318
2319 void QQuickListModelParser::setCustomData(QObject *obj, const QByteArray &d)
2320 {
2321     QQuickListModel *rv = static_cast<QQuickListModel *>(obj);
2322
2323     QV8Engine *engine = QQmlEnginePrivate::getV8Engine(qmlEngine(rv));
2324     rv->m_engine = engine;
2325
2326     const ListModelData *lmd = (const ListModelData *)d.constData();
2327     const char *data = ((const char *)lmd) + lmd->dataOffset;
2328
2329     QStack<DataStackElement> stack;
2330
2331     for (int ii = 0; ii < lmd->instrCount; ++ii) {
2332         const ListInstruction &instr = lmd->instructions()[ii];
2333
2334         switch(instr.type) {
2335         case ListInstruction::Push:
2336             {
2337                 Q_ASSERT(!rv->m_dynamicRoles);
2338
2339                 ListModel *subModel = 0;
2340
2341                 if (stack.count() == 0) {
2342                     subModel = rv->m_listModel;
2343                 } else {
2344                     const DataStackElement &e0 = stack.at(stack.size() - 1);
2345                     DataStackElement &e1 = stack[stack.size() - 2];
2346
2347                     const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2348                     if (role.type == ListLayout::Role::List) {
2349                         subModel = e1.model->getListProperty(e1.elementIndex, role);
2350
2351                         if (subModel == 0) {
2352                             subModel = new ListModel(role.subLayout, 0, -1);
2353                             QVariant vModel = QVariant::fromValue(subModel);
2354                             e1.model->setOrCreateProperty(e1.elementIndex, e0.name, vModel);
2355                         }
2356                     }
2357                 }
2358
2359                 DataStackElement e;
2360                 e.model = subModel;
2361                 e.elementIndex = subModel ? subModel->appendElement() : -1;
2362                 stack.push(e);
2363             }
2364             break;
2365
2366         case ListInstruction::Pop:
2367             stack.pop();
2368             break;
2369
2370         case ListInstruction::Value:
2371             {
2372                 const DataStackElement &e0 = stack.at(stack.size() - 1);
2373                 DataStackElement &e1 = stack[stack.size() - 2];
2374
2375                 QString name = e0.name;
2376                 QVariant value;
2377
2378                 switch (QQmlScript::Variant::Type(data[instr.dataIdx])) {
2379                     case QQmlScript::Variant::Invalid:
2380                         {
2381                             const ListLayout::Role &role = e1.model->getOrCreateListRole(e0.name);
2382                             ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2383                             value = QVariant::fromValue(emptyModel);
2384                         }
2385                         break;
2386                     case QQmlScript::Variant::Boolean:
2387                         value = bool(data[1 + instr.dataIdx]);
2388                         break;
2389                     case QQmlScript::Variant::Number:
2390                         value = QByteArray(data + 1 + instr.dataIdx).toDouble();
2391                         break;
2392                     case QQmlScript::Variant::String:
2393                         value = QString::fromUtf8(data + 1 + instr.dataIdx);
2394                         break;
2395                     default:
2396                         Q_ASSERT("Format error in ListInstruction");
2397                 }
2398
2399                 e1.model->setOrCreateProperty(e1.elementIndex, name, value);
2400             }
2401             break;
2402
2403         case ListInstruction::Set:
2404             {
2405                 DataStackElement e;
2406                 e.name = QString::fromUtf8(data + instr.dataIdx);
2407                 stack.push(e);
2408             }
2409             break;
2410         }
2411     }
2412 }
2413
2414 bool QQuickListModelParser::definesEmptyList(const QString &s)
2415 {
2416     if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2417         for (int i=1; i<s.length()-1; i++) {
2418             if (!s[i].isSpace())
2419                 return false;
2420         }
2421         return true;
2422     }
2423     return false;
2424 }
2425
2426
2427 /*!
2428     \qmlclass ListElement QQuickListElement
2429     \inqmlmodule QtQuick 2
2430     \brief Defines a data item in a ListModel
2431     \ingroup qtquick-models
2432
2433     List elements are defined inside ListModel definitions, and represent items in a
2434     list that will be displayed using ListView or \l Repeater items.
2435
2436     List elements are defined like other QML elements except that they contain
2437     a collection of \e role definitions instead of properties. Using the same
2438     syntax as property definitions, roles both define how the data is accessed
2439     and include the data itself.
2440
2441     The names used for roles must begin with a lower-case letter and should be
2442     common to all elements in a given model. Values must be simple constants; either
2443     strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2444     (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2445
2446     \section1 Referencing Roles
2447
2448     The role names are used by delegates to obtain data from list elements.
2449     Each role name is accessible in the delegate's scope, and refers to the
2450     corresponding role in the current element. Where a role name would be
2451     ambiguous to use, it can be accessed via the \l{ListView::}{model}
2452     property (e.g., \c{model.cost} instead of \c{cost}).
2453
2454     \section1 Example Usage
2455
2456     The following model defines a series of list elements, each of which
2457     contain "name" and "cost" roles and their associated values.
2458
2459     \snippet qml/listmodel/listelements.qml model
2460
2461     The delegate obtains the name and cost for each element by simply referring
2462     to \c name and \c cost:
2463
2464     \snippet qml/listmodel/listelements.qml view
2465
2466     \sa ListModel
2467 */
2468
2469 QT_END_NAMESPACE