1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qqmllistmodel_p_p.h"
41 #include "qqmllistmodelworkeragent_p.h"
42 #include <private/qqmlopenmetaobject_p.h>
43 #include <private/qqmljsast_p.h>
44 #include <private/qqmljsengine_p.h>
45 #include <private/qjsvalue_p.h>
46 
47 #include <private/qqmlcustomparser_p.h>
48 #include <private/qqmlengine_p.h>
49 #include <private/qqmlnotifier_p.h>
50 
51 #include <private/qv4object_p.h>
52 #include <private/qv4dateobject_p.h>
53 #include <private/qv4objectiterator_p.h>
54 #include <private/qv4alloca_p.h>
55 #include <private/qv4lookup_p.h>
56 #include <private/qv4qmlcontext_p.h>
57 
58 #include <qqmlcontext.h>
59 #include <qqmlinfo.h>
60 
61 #include <QtCore/qdebug.h>
62 #include <QtCore/qstack.h>
63 #include <QXmlStreamReader>
64 #include <QtCore/qdatetime.h>
65 #include <QScopedValueRollback>
66 
67 Q_DECLARE_METATYPE(const QV4::CompiledData::Binding*);
68 
69 QT_BEGIN_NAMESPACE
70 
71 // Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
72 enum { MIN_LISTMODEL_UID = 1024 };
73 
74 static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
75 
76 template <typename T>
isMemoryUsed(const char * mem)77 static bool isMemoryUsed(const char *mem)
78 {
79     for (size_t i=0 ; i < sizeof(T) ; ++i) {
80         if (mem[i] != 0)
81             return true;
82     }
83 
84     return false;
85 }
86 
roleTypeName(ListLayout::Role::DataType t)87 static QString roleTypeName(ListLayout::Role::DataType t)
88 {
89     static const QString roleTypeNames[] = {
90         QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"),
91         QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"),
92         QStringLiteral("DateTime"), QStringLiteral("Function")
93     };
94 
95     if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
96         return roleTypeNames[t];
97 
98     return QString();
99 }
100 
getRoleOrCreate(const QString & key,Role::DataType type)101 const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
102 {
103     QStringHash<Role *>::Node *node = roleHash.findNode(key);
104     if (node) {
105         const Role &r = *node->value;
106         if (type != r.type)
107             qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
108         return r;
109     }
110 
111     return createRole(key, type);
112 }
113 
getRoleOrCreate(QV4::String * key,Role::DataType type)114 const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::DataType type)
115 {
116     QStringHash<Role *>::Node *node = roleHash.findNode(key);
117     if (node) {
118         const Role &r = *node->value;
119         if (type != r.type)
120             qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
121         return r;
122     }
123 
124     QString qkey = key->toQString();
125 
126     return createRole(qkey, type);
127 }
128 
createRole(const QString & key,ListLayout::Role::DataType type)129 const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
130 {
131     const int dataSizes[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QPointer<QObject>), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
132     const int dataAlignments[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
133 
134     Role *r = new Role;
135     r->name = key;
136     r->type = type;
137 
138     if (type == Role::List) {
139         r->subLayout = new ListLayout;
140     } else {
141         r->subLayout = nullptr;
142     }
143 
144     int dataSize = dataSizes[type];
145     int dataAlignment = dataAlignments[type];
146 
147     int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
148     if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
149         r->blockIndex = ++currentBlock;
150         r->blockOffset = 0;
151         currentBlockOffset = dataSize;
152     } else {
153         r->blockIndex = currentBlock;
154         r->blockOffset = dataOffset;
155         currentBlockOffset = dataOffset + dataSize;
156     }
157 
158     int roleIndex = roles.count();
159     r->index = roleIndex;
160 
161     roles.append(r);
162     roleHash.insert(key, r);
163 
164     return *r;
165 }
166 
ListLayout(const ListLayout * other)167 ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
168 {
169     const int otherRolesCount = other->roles.count();
170     roles.reserve(otherRolesCount);
171     for (int i=0 ; i < otherRolesCount; ++i) {
172         Role *role = new Role(other->roles[i]);
173         roles.append(role);
174         roleHash.insert(role->name, role);
175     }
176     currentBlockOffset = other->currentBlockOffset;
177     currentBlock = other->currentBlock;
178 }
179 
~ListLayout()180 ListLayout::~ListLayout()
181 {
182     qDeleteAll(roles);
183 }
184 
sync(ListLayout * src,ListLayout * target)185 void ListLayout::sync(ListLayout *src, ListLayout *target)
186 {
187     int roleOffset = target->roles.count();
188     int newRoleCount = src->roles.count() - roleOffset;
189 
190     for (int i=0 ; i < newRoleCount ; ++i) {
191         Role *role = new Role(src->roles[roleOffset + i]);
192         target->roles.append(role);
193         target->roleHash.insert(role->name, role);
194     }
195 
196     target->currentBlockOffset = src->currentBlockOffset;
197     target->currentBlock = src->currentBlock;
198 }
199 
Role(const Role * other)200 ListLayout::Role::Role(const Role *other)
201 {
202     name = other->name;
203     type = other->type;
204     blockIndex = other->blockIndex;
205     blockOffset = other->blockOffset;
206     index = other->index;
207     if (other->subLayout)
208         subLayout = new ListLayout(other->subLayout);
209     else
210         subLayout = nullptr;
211 }
212 
~Role()213 ListLayout::Role::~Role()
214 {
215     delete subLayout;
216 }
217 
getRoleOrCreate(const QString & key,const QVariant & data)218 const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
219 {
220     Role::DataType type;
221 
222     switch (data.userType()) {
223         case QMetaType::Double:      type = Role::Number;      break;
224         case QMetaType::Int:         type = Role::Number;      break;
225         case QMetaType::Bool:        type = Role::Bool;        break;
226         case QMetaType::QString:      type = Role::String;      break;
227         case QMetaType::QVariantMap:         type = Role::VariantMap;  break;
228         case QMetaType::QDateTime:    type = Role::DateTime;    break;
229         default:    {
230             if (data.userType() == qMetaTypeId<QJSValue>() &&
231                 data.value<QJSValue>().isCallable()) {
232                 type = Role::Function;
233                 break;
234             } else if (data.userType() == qMetaTypeId<const QV4::CompiledData::Binding*>()
235                        && data.value<const QV4::CompiledData::Binding*>()->isTranslationBinding()) {
236                 type = Role::String;
237                 break;
238             } else if (data.userType() >= QMetaType::User) {
239                 type = Role::List;
240                 break;
241             } else {
242                 type = Role::Invalid;
243                 break;
244             }
245         }
246     }
247 
248     if (type == Role::Invalid) {
249         qmlWarning(nullptr) << "Can't create role for unsupported data type";
250         return nullptr;
251     }
252 
253     return &getRoleOrCreate(key, type);
254 }
255 
getExistingRole(const QString & key) const256 const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const
257 {
258     Role *r = nullptr;
259     QStringHash<Role *>::Node *node = roleHash.findNode(key);
260     if (node)
261         r = node->value;
262     return r;
263 }
264 
getExistingRole(QV4::String * key) const265 const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const
266 {
267     Role *r = nullptr;
268     QStringHash<Role *>::Node *node = roleHash.findNode(key);
269     if (node)
270         r = node->value;
271     return r;
272 }
273 
StringOrTranslation(const QString & s)274 StringOrTranslation::StringOrTranslation(const QString &s)
275 {
276     d.setFlag();
277     setString(s);
278 }
279 
StringOrTranslation(const QV4::CompiledData::Binding * binding)280 StringOrTranslation::StringOrTranslation(const QV4::CompiledData::Binding *binding)
281 {
282     d.setFlag();
283     clear();
284     d = binding;
285 }
286 
~StringOrTranslation()287 StringOrTranslation::~StringOrTranslation()
288 {
289     clear();
290 }
291 
setString(const QString & s)292 void StringOrTranslation::setString(const QString &s)
293 {
294     d.setFlag();
295     clear();
296     QStringData *stringData = const_cast<QString &>(s).data_ptr();
297     d = stringData;
298     if (stringData)
299         stringData->ref.ref();
300 }
301 
setTranslation(const QV4::CompiledData::Binding * binding)302 void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding)
303 {
304     d.setFlag();
305     clear();
306     d = binding;
307 }
308 
toString(const QQmlListModel * owner) const309 QString StringOrTranslation::toString(const QQmlListModel *owner) const
310 {
311     if (d.isNull())
312         return QString();
313     if (d.isT1()) {
314         QStringDataPtr holder = { d.asT1() };
315         holder.ptr->ref.ref();
316         return QString(holder);
317     }
318     if (!owner)
319         return QString();
320     return owner->m_compilationUnit->bindingValueAsString(d.asT2());
321 }
322 
asString() const323 QString StringOrTranslation::asString() const
324 {
325     if (d.isNull())
326         return QString();
327     if (!d.isT1())
328         return QString();
329     QStringDataPtr holder = { d.asT1() };
330     holder.ptr->ref.ref();
331     return QString(holder);
332 }
333 
clear()334 void StringOrTranslation::clear()
335 {
336     if (QStringData *strData = d.isT1() ? d.asT1() : nullptr) {
337         if (!strData->ref.deref())
338             QStringData::deallocate(strData);
339     }
340     d = static_cast<QStringData *>(nullptr);
341 }
342 
getOrCreateModelObject(QQmlListModel * model,int elementIndex)343 QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex)
344 {
345     ListElement *e = elements[elementIndex];
346     if (e->m_objectCache == nullptr) {
347         void *memory = operator new(sizeof(QObject) + sizeof(QQmlData));
348         void *ddataMemory = ((char *)memory) + sizeof(QObject);
349         e->m_objectCache = new (memory) QObject;
350         QQmlData *ddata = new (ddataMemory) QQmlData;
351         ddata->ownMemory = false;
352         QObjectPrivate::get(e->m_objectCache)->declarativeData = ddata;
353         (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
354     }
355     return e->m_objectCache;
356 }
357 
sync(ListModel * src,ListModel * target)358 bool ListModel::sync(ListModel *src, ListModel *target)
359 {
360     // Sanity check
361 
362     bool hasChanges = false;
363 
364     // Build hash of elements <-> uid for each of the lists
365     QHash<int, ElementSync> elementHash;
366     for (int i = 0; i < target->elements.count(); ++i) {
367         ListElement *e = target->elements.at(i);
368         int uid = e->getUid();
369         ElementSync sync;
370         sync.target = e;
371         sync.targetIndex = i;
372         elementHash.insert(uid, sync);
373     }
374     for (int i = 0; i < src->elements.count(); ++i) {
375         ListElement *e = src->elements.at(i);
376         int uid = e->getUid();
377 
378         QHash<int, ElementSync>::iterator it = elementHash.find(uid);
379         if (it == elementHash.end()) {
380             ElementSync sync;
381             sync.src = e;
382             sync.srcIndex = i;
383             elementHash.insert(uid, sync);
384         } else {
385             ElementSync &sync = it.value();
386             sync.src = e;
387             sync.srcIndex = i;
388         }
389     }
390 
391     QQmlListModel *targetModel = target->m_modelCache;
392 
393     // Get list of elements that are in the target but no longer in the source. These get deleted first.
394     int rowsRemoved = 0;
395     for (int i = 0 ; i < target->elements.count() ; ++i) {
396         ListElement *element = target->elements.at(i);
397         ElementSync &s = elementHash.find(element->getUid()).value();
398         Q_ASSERT(s.targetIndex >= 0);
399         // need to update the targetIndex, to keep it correct after removals
400         s.targetIndex -= rowsRemoved;
401         if (s.src == nullptr) {
402             Q_ASSERT(s.targetIndex == i);
403             hasChanges = true;
404             if (targetModel)
405                 targetModel->beginRemoveRows(QModelIndex(), i, i);
406             s.target->destroy(target->m_layout);
407             target->elements.removeOne(s.target);
408             delete s.target;
409             if (targetModel)
410                 targetModel->endRemoveRows();
411             ++rowsRemoved;
412             --i;
413             continue;
414         }
415     }
416 
417     // Sync the layouts
418     ListLayout::sync(src->m_layout, target->m_layout);
419 
420     // Clear the target list, and append in correct order from the source
421     target->elements.clear();
422     for (int i = 0; i < src->elements.count(); ++i) {
423         ListElement *srcElement = src->elements.at(i);
424         ElementSync &s = elementHash.find(srcElement->getUid()).value();
425         Q_ASSERT(s.srcIndex >= 0);
426         ListElement *targetElement = s.target;
427         if (targetElement == nullptr) {
428             targetElement = new ListElement(srcElement->getUid());
429         }
430         s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout);
431         target->elements.append(targetElement);
432     }
433 
434     target->updateCacheIndices();
435 
436     // Update values stored in target meta objects
437     for (int i=0 ; i < target->elements.count() ; ++i) {
438         ListElement *e = target->elements[i];
439         if (ModelNodeMetaObject *mo = e->objectCache())
440             mo->updateValues();
441     }
442 
443     // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
444     // so the model indices can't be out of bounds
445     //
446     // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
447     // model indices are updated correctly
448     int rowsInserted = 0;
449     for (int i = 0 ; i < target->elements.count() ; ++i) {
450         ListElement *element = target->elements.at(i);
451         ElementSync &s = elementHash.find(element->getUid()).value();
452         Q_ASSERT(s.srcIndex >= 0);
453         s.srcIndex += rowsInserted;
454         if (s.srcIndex != s.targetIndex) {
455             if (targetModel) {
456                 if (s.targetIndex == -1) {
457                     targetModel->beginInsertRows(QModelIndex(), i, i);
458                     targetModel->endInsertRows();
459                 } else {
460                     targetModel->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
461                     targetModel->endMoveRows();
462                 }
463             }
464             hasChanges = true;
465             ++rowsInserted;
466         }
467         if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
468             QModelIndex idx = targetModel->createIndex(i, 0);
469             if (targetModel)
470                 targetModel->dataChanged(idx, idx, s.changedRoles);
471             hasChanges = true;
472         }
473     }
474     return hasChanges;
475 }
476 
ListModel(ListLayout * layout,QQmlListModel * modelCache)477 ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache)
478 {
479 }
480 
destroy()481 void ListModel::destroy()
482 {
483     for (const auto &destroyer : remove(0, elements.count()))
484         destroyer();
485 
486     m_layout = nullptr;
487     if (m_modelCache && m_modelCache->m_primary == false)
488         delete m_modelCache;
489     m_modelCache = nullptr;
490 }
491 
appendElement()492 int ListModel::appendElement()
493 {
494     int elementIndex = elements.count();
495     newElement(elementIndex);
496     return elementIndex;
497 }
498 
insertElement(int index)499 void ListModel::insertElement(int index)
500 {
501     newElement(index);
502     updateCacheIndices(index);
503 }
504 
move(int from,int to,int n)505 void ListModel::move(int from, int to, int n)
506 {
507     if (from > to) {
508         // Only move forwards - flip if backwards moving
509         int tfrom = from;
510         int tto = to;
511         from = tto;
512         to = tto+n;
513         n = tfrom-tto;
514     }
515 
516     QPODVector<ListElement *, 4> store;
517     for (int i=0 ; i < (to-from) ; ++i)
518         store.append(elements[from+n+i]);
519     for (int i=0 ; i < n ; ++i)
520         store.append(elements[from+i]);
521     for (int i=0 ; i < store.count() ; ++i)
522         elements[from+i] = store[i];
523 
524     updateCacheIndices(from, to + n);
525 }
526 
newElement(int index)527 void ListModel::newElement(int index)
528 {
529     ListElement *e = new ListElement;
530     elements.insert(index, e);
531 }
532 
updateCacheIndices(int start,int end)533 void ListModel::updateCacheIndices(int start, int end)
534 {
535     int count = elements.count();
536 
537     if (end < 0 || end > count)
538         end = count;
539 
540     for (int i = start; i < end; ++i) {
541         ListElement *e = elements.at(i);
542         if (ModelNodeMetaObject *mo = e->objectCache())
543             mo->m_elementIndex = i;
544     }
545 }
546 
getProperty(int elementIndex,int roleIndex,const QQmlListModel * owner,QV4::ExecutionEngine * eng)547 QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
548 {
549     if (roleIndex >= m_layout->roleCount())
550         return QVariant();
551     ListElement *e = elements[elementIndex];
552     const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
553     return e->getProperty(r, owner, eng);
554 }
555 
getListProperty(int elementIndex,const ListLayout::Role & role)556 ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
557 {
558     ListElement *e = elements[elementIndex];
559     return e->getListProperty(role);
560 }
561 
set(int elementIndex,QV4::Object * object,QVector<int> * roles)562 void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
563 {
564     ListElement *e = elements[elementIndex];
565 
566     QV4::ExecutionEngine *v4 = object->engine();
567     QV4::Scope scope(v4);
568     QV4::ScopedObject o(scope);
569 
570     QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
571     QV4::ScopedString propertyName(scope);
572     QV4::ScopedValue propertyValue(scope);
573     while (1) {
574         propertyName = it.nextPropertyNameAsString(propertyValue);
575         if (!propertyName)
576             break;
577 
578         // Check if this key exists yet
579         int roleIndex = -1;
580 
581         // Add the value now
582         if (const QV4::String *s = propertyValue->as<QV4::String>()) {
583             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
584             roleIndex = e->setStringProperty(r, s->toQString());
585         } else if (propertyValue->isNumber()) {
586             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
587             roleIndex = e->setDoubleProperty(r, propertyValue->asDouble());
588         } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
589             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
590             ListModel *subModel = new ListModel(r.subLayout, nullptr);
591 
592             int arrayLength = a->getLength();
593             for (int j=0 ; j < arrayLength ; ++j) {
594                 o = a->get(j);
595                 subModel->append(o);
596             }
597 
598             roleIndex = e->setListProperty(r, subModel);
599         } else if (propertyValue->isBoolean()) {
600             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
601             roleIndex = e->setBoolProperty(r, propertyValue->booleanValue());
602         } else if (QV4::DateObject *dd = propertyValue->as<QV4::DateObject>()) {
603             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
604             QDateTime dt = dd->toQDateTime();
605             roleIndex = e->setDateTimeProperty(r, dt);
606         } else if (QV4::FunctionObject *f = propertyValue->as<QV4::FunctionObject>()) {
607             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function);
608             QV4::ScopedFunctionObject func(scope, f);
609             QJSValue jsv;
610             QJSValuePrivate::setValue(&jsv, v4, func);
611             roleIndex = e->setFunctionProperty(r, jsv);
612         } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
613             if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
614                 QObject *o = wrapper->object();
615                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
616                 if (role.type == ListLayout::Role::QObject)
617                     roleIndex = e->setQObjectProperty(role, o);
618             } else {
619                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
620                 if (role.type == ListLayout::Role::VariantMap) {
621                     QV4::ScopedObject obj(scope, o);
622                     roleIndex = e->setVariantMapProperty(role, obj);
623                 }
624             }
625         } else if (propertyValue->isNullOrUndefined()) {
626             const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
627             if (r)
628                 e->clearProperty(*r);
629         }
630 
631         if (roleIndex != -1)
632             roles->append(roleIndex);
633     }
634 
635     if (ModelNodeMetaObject *mo = e->objectCache())
636         mo->updateValues(*roles);
637 }
638 
set(int elementIndex,QV4::Object * object,ListModel::SetElement reason)639 void ListModel::set(int elementIndex, QV4::Object *object, ListModel::SetElement reason)
640 {
641     if (!object)
642         return;
643 
644     ListElement *e = elements[elementIndex];
645 
646     QV4::ExecutionEngine *v4 = object->engine();
647     QV4::Scope scope(v4);
648 
649     QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
650     QV4::ScopedString propertyName(scope);
651     QV4::ScopedValue propertyValue(scope);
652     QV4::ScopedObject o(scope);
653     while (1) {
654         propertyName = it.nextPropertyNameAsString(propertyValue);
655         if (!propertyName)
656             break;
657 
658         // Add the value now
659         if (QV4::String *s = propertyValue->stringValue()) {
660             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
661             if (r.type == ListLayout::Role::String)
662                 e->setStringPropertyFast(r, s->toQString());
663         } else if (propertyValue->isNumber()) {
664             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
665             if (r.type == ListLayout::Role::Number) {
666                 e->setDoublePropertyFast(r, propertyValue->asDouble());
667             }
668         } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
669             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
670             if (r.type == ListLayout::Role::List) {
671                 ListModel *subModel = new ListModel(r.subLayout, nullptr);
672 
673                 int arrayLength = a->getLength();
674                 for (int j=0 ; j < arrayLength ; ++j) {
675                     o = a->get(j);
676                     subModel->append(o);
677                 }
678 
679                 e->setListPropertyFast(r, subModel);
680             }
681         } else if (propertyValue->isBoolean()) {
682             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
683             if (r.type == ListLayout::Role::Bool) {
684                 e->setBoolPropertyFast(r, propertyValue->booleanValue());
685             }
686         } else if (QV4::DateObject *date = propertyValue->as<QV4::DateObject>()) {
687             const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
688             if (r.type == ListLayout::Role::DateTime) {
689                 QDateTime dt = date->toQDateTime();
690                 e->setDateTimePropertyFast(r, dt);
691             }
692         } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
693             if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
694                 QObject *o = wrapper->object();
695                 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
696                 if (r.type == ListLayout::Role::QObject)
697                     e->setQObjectPropertyFast(r, o);
698             } else {
699                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
700                 if (role.type == ListLayout::Role::VariantMap)
701                     e->setVariantMapFast(role, o);
702             }
703         } else if (propertyValue->isNullOrUndefined()) {
704             if (reason == SetElement::WasJustInserted) {
705                 QQmlError err;
706                 auto memberName = propertyName->toString(v4)->toQString();
707                 err.setDescription(QString::fromLatin1("%1 is %2. Adding an object with a %2 member does not create a role for it.").arg(memberName, propertyValue->isNull() ? QLatin1String("null") : QLatin1String("undefined")));
708                 qmlWarning(nullptr, err);
709             } else {
710                 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
711                 if (r)
712                     e->clearProperty(*r);
713             }
714         }
715     }
716 }
717 
remove(int index,int count)718 QVector<std::function<void()>> ListModel::remove(int index, int count)
719 {
720     QVector<std::function<void()>> toDestroy;
721     auto layout = m_layout;
722     for (int i=0 ; i < count ; ++i) {
723         auto element = elements[index+i];
724         toDestroy.append([element, layout](){
725             element->destroy(layout);
726             delete element;
727         });
728     }
729     elements.remove(index, count);
730     updateCacheIndices(index);
731     return toDestroy;
732 }
733 
insert(int elementIndex,QV4::Object * object)734 void ListModel::insert(int elementIndex, QV4::Object *object)
735 {
736     insertElement(elementIndex);
737     set(elementIndex, object, SetElement::WasJustInserted);
738 }
739 
append(QV4::Object * object)740 int ListModel::append(QV4::Object *object)
741 {
742     int elementIndex = appendElement();
743     set(elementIndex, object, SetElement::WasJustInserted);
744     return elementIndex;
745 }
746 
setOrCreateProperty(int elementIndex,const QString & key,const QVariant & data)747 int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
748 {
749     int roleIndex = -1;
750 
751     if (elementIndex >= 0 && elementIndex < elements.count()) {
752         ListElement *e = elements[elementIndex];
753 
754         const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
755         if (r) {
756             roleIndex = e->setVariantProperty(*r, data);
757 
758             ModelNodeMetaObject *cache = e->objectCache();
759 
760             if (roleIndex != -1 && cache)
761                 cache->updateValues(QVector<int>(1, roleIndex));
762         }
763     }
764 
765     return roleIndex;
766 }
767 
setExistingProperty(int elementIndex,const QString & key,const QV4::Value & data,QV4::ExecutionEngine * eng)768 int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng)
769 {
770     int roleIndex = -1;
771 
772     if (elementIndex >= 0 && elementIndex < elements.count()) {
773         ListElement *e = elements[elementIndex];
774         const ListLayout::Role *r = m_layout->getExistingRole(key);
775         if (r)
776             roleIndex = e->setJsProperty(*r, data, eng);
777     }
778 
779     return roleIndex;
780 }
781 
getPropertyMemory(const ListLayout::Role & role)782 inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
783 {
784     ListElement *e = this;
785     int blockIndex = 0;
786     while (blockIndex < role.blockIndex) {
787         if (e->next == nullptr) {
788             e->next = new ListElement;
789             e->next->uid = uid;
790         }
791         e = e->next;
792         ++blockIndex;
793     }
794 
795     char *mem = &e->data[role.blockOffset];
796     return mem;
797 }
798 
objectCache()799 ModelNodeMetaObject *ListElement::objectCache()
800 {
801     if (!m_objectCache)
802         return nullptr;
803     return ModelNodeMetaObject::get(m_objectCache);
804 }
805 
getStringProperty(const ListLayout::Role & role)806 StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role)
807 {
808     char *mem = getPropertyMemory(role);
809     StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
810     return s;
811 }
812 
getQObjectProperty(const ListLayout::Role & role)813 QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
814 {
815     char *mem = getPropertyMemory(role);
816     QPointer<QObject> *o = reinterpret_cast<QPointer<QObject> *>(mem);
817     return o->data();
818 }
819 
getVariantMapProperty(const ListLayout::Role & role)820 QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
821 {
822     QVariantMap *map = nullptr;
823 
824     char *mem = getPropertyMemory(role);
825     if (isMemoryUsed<QVariantMap>(mem))
826         map = reinterpret_cast<QVariantMap *>(mem);
827 
828     return map;
829 }
830 
getDateTimeProperty(const ListLayout::Role & role)831 QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
832 {
833     QDateTime *dt = nullptr;
834 
835     char *mem = getPropertyMemory(role);
836     if (isMemoryUsed<QDateTime>(mem))
837         dt = reinterpret_cast<QDateTime *>(mem);
838 
839     return dt;
840 }
841 
getFunctionProperty(const ListLayout::Role & role)842 QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role)
843 {
844     QJSValue *f = nullptr;
845 
846     char *mem = getPropertyMemory(role);
847     if (isMemoryUsed<QJSValue>(mem))
848         f = reinterpret_cast<QJSValue *>(mem);
849 
850     return f;
851 }
852 
getGuardProperty(const ListLayout::Role & role)853 QPointer<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
854 {
855     char *mem = getPropertyMemory(role);
856 
857     bool existingGuard = false;
858     for (size_t i=0 ; i < sizeof(QPointer<QObject>) ; ++i) {
859         if (mem[i] != 0) {
860             existingGuard = true;
861             break;
862         }
863     }
864 
865     QPointer<QObject> *o = nullptr;
866 
867     if (existingGuard)
868         o = reinterpret_cast<QPointer<QObject> *>(mem);
869 
870     return o;
871 }
872 
getListProperty(const ListLayout::Role & role)873 ListModel *ListElement::getListProperty(const ListLayout::Role &role)
874 {
875     char *mem = getPropertyMemory(role);
876     ListModel **value = reinterpret_cast<ListModel **>(mem);
877     return *value;
878 }
879 
getProperty(const ListLayout::Role & role,const QQmlListModel * owner,QV4::ExecutionEngine * eng)880 QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
881 {
882     char *mem = getPropertyMemory(role);
883 
884     QVariant data;
885 
886     switch (role.type) {
887         case ListLayout::Role::Number:
888             {
889                 double *value = reinterpret_cast<double *>(mem);
890                 data = *value;
891             }
892             break;
893         case ListLayout::Role::String:
894             {
895                 StringOrTranslation *value = reinterpret_cast<StringOrTranslation *>(mem);
896                 if (value->isSet())
897                     data = value->toString(owner);
898             }
899             break;
900         case ListLayout::Role::Bool:
901             {
902                 bool *value = reinterpret_cast<bool *>(mem);
903                 data = *value;
904             }
905             break;
906         case ListLayout::Role::List:
907             {
908                 ListModel **value = reinterpret_cast<ListModel **>(mem);
909                 ListModel *model = *value;
910 
911                 if (model) {
912                     if (model->m_modelCache == nullptr) {
913                         model->m_modelCache = new QQmlListModel(owner, model, eng);
914                         QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
915                     }
916 
917                     QObject *object = model->m_modelCache;
918                     data = QVariant::fromValue(object);
919                 }
920             }
921             break;
922         case ListLayout::Role::QObject:
923             {
924                 QPointer<QObject> *guard = reinterpret_cast<QPointer<QObject> *>(mem);
925                 QObject *object = guard->data();
926                 if (object)
927                     data = QVariant::fromValue(object);
928             }
929             break;
930         case ListLayout::Role::VariantMap:
931             {
932                 if (isMemoryUsed<QVariantMap>(mem)) {
933                     QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
934                     data = *map;
935                 }
936             }
937             break;
938         case ListLayout::Role::DateTime:
939             {
940                 if (isMemoryUsed<QDateTime>(mem)) {
941                     QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
942                     data = *dt;
943                 }
944             }
945             break;
946         case ListLayout::Role::Function:
947             {
948                 if (isMemoryUsed<QJSValue>(mem)) {
949                     QJSValue *func = reinterpret_cast<QJSValue *>(mem);
950                     data = QVariant::fromValue(*func);
951                 }
952             }
953             break;
954         default:
955             break;
956     }
957 
958     return data;
959 }
960 
setStringProperty(const ListLayout::Role & role,const QString & s)961 int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
962 {
963     int roleIndex = -1;
964 
965     if (role.type == ListLayout::Role::String) {
966         char *mem = getPropertyMemory(role);
967         StringOrTranslation *c = reinterpret_cast<StringOrTranslation *>(mem);
968         bool changed;
969         if (!c->isSet() || c->isTranslation())
970             changed = true;
971         else
972             changed = c->asString().compare(s) != 0;
973         c->setString(s);
974         if (changed)
975             roleIndex = role.index;
976     }
977 
978     return roleIndex;
979 }
980 
setDoubleProperty(const ListLayout::Role & role,double d)981 int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
982 {
983     int roleIndex = -1;
984 
985     if (role.type == ListLayout::Role::Number) {
986         char *mem = getPropertyMemory(role);
987         double *value = reinterpret_cast<double *>(mem);
988         bool changed = *value != d;
989         *value = d;
990         if (changed)
991             roleIndex = role.index;
992     }
993 
994     return roleIndex;
995 }
996 
setBoolProperty(const ListLayout::Role & role,bool b)997 int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
998 {
999     int roleIndex = -1;
1000 
1001     if (role.type == ListLayout::Role::Bool) {
1002         char *mem = getPropertyMemory(role);
1003         bool *value = reinterpret_cast<bool *>(mem);
1004         bool changed = *value != b;
1005         *value = b;
1006         if (changed)
1007             roleIndex = role.index;
1008     }
1009 
1010     return roleIndex;
1011 }
1012 
setListProperty(const ListLayout::Role & role,ListModel * m)1013 int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
1014 {
1015     int roleIndex = -1;
1016 
1017     if (role.type == ListLayout::Role::List) {
1018         char *mem = getPropertyMemory(role);
1019         ListModel **value = reinterpret_cast<ListModel **>(mem);
1020         if (*value && *value != m) {
1021             (*value)->destroy();
1022             delete *value;
1023         }
1024         *value = m;
1025         roleIndex = role.index;
1026     }
1027 
1028     return roleIndex;
1029 }
1030 
setQObjectProperty(const ListLayout::Role & role,QObject * o)1031 int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
1032 {
1033     int roleIndex = -1;
1034 
1035     if (role.type == ListLayout::Role::QObject) {
1036         char *mem = getPropertyMemory(role);
1037         QPointer<QObject> *g = reinterpret_cast<QPointer<QObject> *>(mem);
1038         bool existingGuard = false;
1039         for (size_t i=0 ; i < sizeof(QPointer<QObject>) ; ++i) {
1040             if (mem[i] != 0) {
1041                 existingGuard = true;
1042                 break;
1043             }
1044         }
1045         bool changed;
1046         if (existingGuard) {
1047             changed = g->data() != o;
1048             g->~QPointer();
1049         } else {
1050             changed = true;
1051         }
1052         new (mem) QPointer<QObject>(o);
1053         if (changed)
1054             roleIndex = role.index;
1055     }
1056 
1057     return roleIndex;
1058 }
1059 
setVariantMapProperty(const ListLayout::Role & role,QV4::Object * o)1060 int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o)
1061 {
1062     int roleIndex = -1;
1063 
1064     if (role.type == ListLayout::Role::VariantMap) {
1065         char *mem = getPropertyMemory(role);
1066         if (isMemoryUsed<QVariantMap>(mem)) {
1067             QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
1068             map->~QMap();
1069         }
1070         new (mem) QVariantMap(o->engine()->variantMapFromJS(o));
1071         roleIndex = role.index;
1072     }
1073 
1074     return roleIndex;
1075 }
1076 
setVariantMapProperty(const ListLayout::Role & role,QVariantMap * m)1077 int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
1078 {
1079     int roleIndex = -1;
1080 
1081     if (role.type == ListLayout::Role::VariantMap) {
1082         char *mem = getPropertyMemory(role);
1083         if (isMemoryUsed<QVariantMap>(mem)) {
1084             QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
1085             if (m && map->isSharedWith(*m))
1086                 return roleIndex;
1087             map->~QMap();
1088         } else if (!m) {
1089             return roleIndex;
1090         }
1091         if (m)
1092             new (mem) QVariantMap(*m);
1093         else
1094             new (mem) QVariantMap;
1095         roleIndex = role.index;
1096     }
1097 
1098     return roleIndex;
1099 }
1100 
setDateTimeProperty(const ListLayout::Role & role,const QDateTime & dt)1101 int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt)
1102 {
1103     int roleIndex = -1;
1104 
1105     if (role.type == ListLayout::Role::DateTime) {
1106         char *mem = getPropertyMemory(role);
1107         if (isMemoryUsed<QDateTime>(mem)) {
1108             QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
1109             dt->~QDateTime();
1110         }
1111         new (mem) QDateTime(dt);
1112         roleIndex = role.index;
1113     }
1114 
1115     return roleIndex;
1116 }
1117 
setFunctionProperty(const ListLayout::Role & role,const QJSValue & f)1118 int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f)
1119 {
1120     int roleIndex = -1;
1121 
1122     if (role.type == ListLayout::Role::Function) {
1123         char *mem = getPropertyMemory(role);
1124         if (isMemoryUsed<QJSValue>(mem)) {
1125             QJSValue *f = reinterpret_cast<QJSValue *>(mem);
1126             f->~QJSValue();
1127         }
1128         new (mem) QJSValue(f);
1129         roleIndex = role.index;
1130     }
1131 
1132     return roleIndex;
1133 }
1134 
setTranslationProperty(const ListLayout::Role & role,const QV4::CompiledData::Binding * b)1135 int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b)
1136 {
1137     int roleIndex = -1;
1138 
1139     if (role.type == ListLayout::Role::String) {
1140         char *mem = getPropertyMemory(role);
1141         StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
1142         s->setTranslation(b);
1143         roleIndex = role.index;
1144     }
1145 
1146     return roleIndex;
1147 }
1148 
1149 
setStringPropertyFast(const ListLayout::Role & role,const QString & s)1150 void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
1151 {
1152     char *mem = getPropertyMemory(role);
1153     new (mem) StringOrTranslation(s);
1154 }
1155 
setDoublePropertyFast(const ListLayout::Role & role,double d)1156 void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
1157 {
1158     char *mem = getPropertyMemory(role);
1159     double *value = new (mem) double;
1160     *value = d;
1161 }
1162 
setBoolPropertyFast(const ListLayout::Role & role,bool b)1163 void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
1164 {
1165     char *mem = getPropertyMemory(role);
1166     bool *value = new (mem) bool;
1167     *value = b;
1168 }
1169 
setQObjectPropertyFast(const ListLayout::Role & role,QObject * o)1170 void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
1171 {
1172     char *mem = getPropertyMemory(role);
1173     new (mem) QPointer<QObject>(o);
1174 }
1175 
setListPropertyFast(const ListLayout::Role & role,ListModel * m)1176 void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
1177 {
1178     char *mem = getPropertyMemory(role);
1179     ListModel **value = new (mem) ListModel *;
1180     *value = m;
1181 }
1182 
setVariantMapFast(const ListLayout::Role & role,QV4::Object * o)1183 void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o)
1184 {
1185     char *mem = getPropertyMemory(role);
1186     QVariantMap *map = new (mem) QVariantMap;
1187     *map = o->engine()->variantMapFromJS(o);
1188 }
1189 
setDateTimePropertyFast(const ListLayout::Role & role,const QDateTime & dt)1190 void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt)
1191 {
1192     char *mem = getPropertyMemory(role);
1193     new (mem) QDateTime(dt);
1194 }
1195 
setFunctionPropertyFast(const ListLayout::Role & role,const QJSValue & f)1196 void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f)
1197 {
1198     char *mem = getPropertyMemory(role);
1199     new (mem) QJSValue(f);
1200 }
1201 
clearProperty(const ListLayout::Role & role)1202 void ListElement::clearProperty(const ListLayout::Role &role)
1203 {
1204     switch (role.type) {
1205     case ListLayout::Role::String:
1206         setStringProperty(role, QString());
1207         break;
1208     case ListLayout::Role::Number:
1209         setDoubleProperty(role, 0.0);
1210         break;
1211     case ListLayout::Role::Bool:
1212         setBoolProperty(role, false);
1213         break;
1214     case ListLayout::Role::List:
1215         setListProperty(role, nullptr);
1216         break;
1217     case ListLayout::Role::QObject:
1218         setQObjectProperty(role, nullptr);
1219         break;
1220     case ListLayout::Role::DateTime:
1221         setDateTimeProperty(role, QDateTime());
1222         break;
1223     case ListLayout::Role::VariantMap:
1224         setVariantMapProperty(role, (QVariantMap *)nullptr);
1225         break;
1226     case ListLayout::Role::Function:
1227         setFunctionProperty(role, QJSValue());
1228         break;
1229     default:
1230         break;
1231     }
1232 }
1233 
ListElement()1234 ListElement::ListElement()
1235 {
1236     m_objectCache = nullptr;
1237     uid = uidCounter.fetchAndAddOrdered(1);
1238     next = nullptr;
1239     memset(data, 0, sizeof(data));
1240 }
1241 
ListElement(int existingUid)1242 ListElement::ListElement(int existingUid)
1243 {
1244     m_objectCache = nullptr;
1245     uid = existingUid;
1246     next = nullptr;
1247     memset(data, 0, sizeof(data));
1248 }
1249 
~ListElement()1250 ListElement::~ListElement()
1251 {
1252     delete next;
1253 }
1254 
sync(ListElement * src,ListLayout * srcLayout,ListElement * target,ListLayout * targetLayout)1255 QVector<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
1256 {
1257     QVector<int> changedRoles;
1258     for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
1259         const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
1260         const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
1261 
1262         int roleIndex = -1;
1263         switch (srcRole.type) {
1264             case ListLayout::Role::List:
1265                 {
1266                     ListModel *srcSubModel = src->getListProperty(srcRole);
1267                     ListModel *targetSubModel = target->getListProperty(targetRole);
1268 
1269                     if (srcSubModel) {
1270                         if (targetSubModel == nullptr) {
1271                             targetSubModel = new ListModel(targetRole.subLayout, nullptr);
1272                             target->setListPropertyFast(targetRole, targetSubModel);
1273                         }
1274                         if (ListModel::sync(srcSubModel, targetSubModel))
1275                             roleIndex = targetRole.index;
1276                     }
1277                 }
1278                 break;
1279             case ListLayout::Role::QObject:
1280                 {
1281                     QObject *object = src->getQObjectProperty(srcRole);
1282                     roleIndex = target->setQObjectProperty(targetRole, object);
1283                 }
1284                 break;
1285             case ListLayout::Role::String:
1286             case ListLayout::Role::Number:
1287             case ListLayout::Role::Bool:
1288             case ListLayout::Role::DateTime:
1289             case ListLayout::Role::Function:
1290                 {
1291                     QVariant v = src->getProperty(srcRole, nullptr, nullptr);
1292                     roleIndex = target->setVariantProperty(targetRole, v);
1293                 }
1294                 break;
1295             case ListLayout::Role::VariantMap:
1296                 {
1297                     QVariantMap *map = src->getVariantMapProperty(srcRole);
1298                     roleIndex = target->setVariantMapProperty(targetRole, map);
1299                 }
1300                 break;
1301             default:
1302                 break;
1303         }
1304         if (roleIndex >= 0)
1305             changedRoles << roleIndex;
1306     }
1307 
1308     return changedRoles;
1309 }
1310 
destroy(ListLayout * layout)1311 void ListElement::destroy(ListLayout *layout)
1312 {
1313     if (layout) {
1314         for (int i=0 ; i < layout->roleCount() ; ++i) {
1315             const ListLayout::Role &r = layout->getExistingRole(i);
1316 
1317             switch (r.type) {
1318                 case ListLayout::Role::String:
1319                     {
1320                         StringOrTranslation *string = getStringProperty(r);
1321                         if (string)
1322                             string->~StringOrTranslation();
1323                     }
1324                     break;
1325                 case ListLayout::Role::List:
1326                     {
1327                         ListModel *model = getListProperty(r);
1328                         if (model) {
1329                             model->destroy();
1330                             delete model;
1331                         }
1332                     }
1333                     break;
1334                 case ListLayout::Role::QObject:
1335                     {
1336                         QPointer<QObject> *guard = getGuardProperty(r);
1337                         if (guard)
1338                             guard->~QPointer();
1339                     }
1340                     break;
1341                 case ListLayout::Role::VariantMap:
1342                     {
1343                         QVariantMap *map = getVariantMapProperty(r);
1344                         if (map)
1345                             map->~QMap();
1346                     }
1347                     break;
1348                 case ListLayout::Role::DateTime:
1349                     {
1350                         QDateTime *dt = getDateTimeProperty(r);
1351                         if (dt)
1352                             dt->~QDateTime();
1353                     }
1354                     break;
1355                 case ListLayout::Role::Function:
1356                     {
1357                         QJSValue *f = getFunctionProperty(r);
1358                         if (f)
1359                             f->~QJSValue();
1360                     }
1361                     break;
1362                 default:
1363                     // other types don't need explicit cleanup.
1364                     break;
1365             }
1366         }
1367 
1368         if (m_objectCache) {
1369             m_objectCache->~QObject();
1370             operator delete(m_objectCache);
1371         }
1372     }
1373 
1374     if (next)
1375         next->destroy(nullptr);
1376     uid = -1;
1377 }
1378 
setVariantProperty(const ListLayout::Role & role,const QVariant & d)1379 int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
1380 {
1381     int roleIndex = -1;
1382 
1383     switch (role.type) {
1384         case ListLayout::Role::Number:
1385             roleIndex = setDoubleProperty(role, d.toDouble());
1386             break;
1387         case ListLayout::Role::String:
1388             if (d.userType() == qMetaTypeId<const QV4::CompiledData::Binding *>())
1389                 roleIndex = setTranslationProperty(role, d.value<const QV4::CompiledData::Binding*>());
1390             else
1391                 roleIndex = setStringProperty(role, d.toString());
1392             break;
1393         case ListLayout::Role::Bool:
1394             roleIndex = setBoolProperty(role, d.toBool());
1395             break;
1396         case ListLayout::Role::List:
1397             roleIndex = setListProperty(role, d.value<ListModel *>());
1398             break;
1399         case ListLayout::Role::VariantMap: {
1400                 QVariantMap map = d.toMap();
1401                 roleIndex = setVariantMapProperty(role, &map);
1402             }
1403             break;
1404         case ListLayout::Role::DateTime:
1405             roleIndex = setDateTimeProperty(role, d.toDateTime());
1406             break;
1407         case ListLayout::Role::Function:
1408             roleIndex = setFunctionProperty(role, d.value<QJSValue>());
1409             break;
1410         default:
1411             break;
1412     }
1413 
1414     return roleIndex;
1415 }
1416 
setJsProperty(const ListLayout::Role & role,const QV4::Value & d,QV4::ExecutionEngine * eng)1417 int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng)
1418 {
1419     // Check if this key exists yet
1420     int roleIndex = -1;
1421 
1422     QV4::Scope scope(eng);
1423 
1424     // Add the value now
1425     if (d.isString()) {
1426         QString qstr = d.toQString();
1427         roleIndex = setStringProperty(role, qstr);
1428     } else if (d.isNumber()) {
1429         roleIndex = setDoubleProperty(role, d.asDouble());
1430     } else if (d.as<QV4::ArrayObject>()) {
1431         QV4::ScopedArrayObject a(scope, d);
1432         if (role.type == ListLayout::Role::List) {
1433             QV4::Scope scope(a->engine());
1434             QV4::ScopedObject o(scope);
1435 
1436             ListModel *subModel = new ListModel(role.subLayout, nullptr);
1437             int arrayLength = a->getLength();
1438             for (int j=0 ; j < arrayLength ; ++j) {
1439                 o = a->get(j);
1440                 subModel->append(o);
1441             }
1442             roleIndex = setListProperty(role, subModel);
1443         } else {
1444             qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
1445         }
1446     } else if (d.isBoolean()) {
1447         roleIndex = setBoolProperty(role, d.booleanValue());
1448     } else if (d.as<QV4::DateObject>()) {
1449         QV4::Scoped<QV4::DateObject> dd(scope, d);
1450         QDateTime dt = dd->toQDateTime();
1451         roleIndex = setDateTimeProperty(role, dt);
1452     } else if (d.as<QV4::FunctionObject>()) {
1453         QV4::ScopedFunctionObject f(scope, d);
1454         QJSValue jsv;
1455         QJSValuePrivate::setValue(&jsv, eng, f);
1456         roleIndex = setFunctionProperty(role, jsv);
1457     } else if (d.isObject()) {
1458         QV4::ScopedObject o(scope, d);
1459         QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>();
1460         if (role.type == ListLayout::Role::QObject && wrapper) {
1461             QObject *o = wrapper->object();
1462             roleIndex = setQObjectProperty(role, o);
1463         } else if (role.type == ListLayout::Role::VariantMap) {
1464             roleIndex = setVariantMapProperty(role, o);
1465         }
1466     } else if (d.isNullOrUndefined()) {
1467         clearProperty(role);
1468     }
1469 
1470     return roleIndex;
1471 }
1472 
ModelNodeMetaObject(QObject * object,QQmlListModel * model,int elementIndex)1473 ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex)
1474 : QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false)
1475 {}
1476 
initialize()1477 void ModelNodeMetaObject::initialize()
1478 {
1479     const int roleCount = m_model->m_listModel->roleCount();
1480     QVector<QByteArray> properties;
1481     properties.reserve(roleCount);
1482     for (int i = 0 ; i < roleCount ; ++i) {
1483         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1484         QByteArray name = role.name.toUtf8();
1485         properties << name;
1486     }
1487     type()->createProperties(properties);
1488     updateValues();
1489     m_enabled = true;
1490 }
1491 
~ModelNodeMetaObject()1492 ModelNodeMetaObject::~ModelNodeMetaObject()
1493 {
1494 }
1495 
toDynamicMetaObject(QObject * object)1496 QAbstractDynamicMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object)
1497 {
1498     if (!m_initialized) {
1499         m_initialized = true;
1500         initialize();
1501     }
1502     return QQmlOpenMetaObject::toDynamicMetaObject(object);
1503 }
1504 
get(QObject * obj)1505 ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj)
1506 {
1507     QObjectPrivate *op = QObjectPrivate::get(obj);
1508     return static_cast<ModelNodeMetaObject*>(op->metaObject);
1509 }
1510 
updateValues()1511 void ModelNodeMetaObject::updateValues()
1512 {
1513     const int roleCount = m_model->m_listModel->roleCount();
1514     if (!m_initialized) {
1515         if (roleCount) {
1516             Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int));
1517             for (int i = 0; i < roleCount; ++i)
1518                 changedRoles[i] = i;
1519             emitDirectNotifies(changedRoles, roleCount);
1520         }
1521         return;
1522     }
1523     for (int i=0 ; i < roleCount ; ++i) {
1524         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1525         QByteArray name = role.name.toUtf8();
1526         const QVariant &data = m_model->data(m_elementIndex, i);
1527         setValue(name, data, role.type == ListLayout::Role::List);
1528     }
1529 }
1530 
updateValues(const QVector<int> & roles)1531 void ModelNodeMetaObject::updateValues(const QVector<int> &roles)
1532 {
1533     if (!m_initialized) {
1534         emitDirectNotifies(roles.constData(), roles.count());
1535         return;
1536     }
1537     int roleCount = roles.count();
1538     for (int i=0 ; i < roleCount ; ++i) {
1539         int roleIndex = roles.at(i);
1540         const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1541         QByteArray name = role.name.toUtf8();
1542         const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1543         setValue(name, data, role.type == ListLayout::Role::List);
1544     }
1545 }
1546 
propertyWritten(int index)1547 void ModelNodeMetaObject::propertyWritten(int index)
1548 {
1549     if (!m_enabled)
1550         return;
1551 
1552     QString propName = QString::fromUtf8(name(index));
1553     const QVariant value = this->value(index);
1554 
1555     QV4::Scope scope(m_model->engine());
1556     QV4::ScopedValue v(scope, scope.engine->fromVariant(value));
1557 
1558     int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine);
1559     if (roleIndex != -1)
1560         m_model->emitItemsChanged(m_elementIndex, 1, QVector<int>(1, roleIndex));
1561 }
1562 
1563 // Does the emission of the notifiers when we haven't created the meta-object yet
emitDirectNotifies(const int * changedRoles,int roleCount)1564 void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount)
1565 {
1566     Q_ASSERT(!m_initialized);
1567     QQmlData *ddata = QQmlData::get(object(), /*create*/false);
1568     if (!ddata)
1569         return;
1570     // There's nothing to emit if we're a list model in a worker thread.
1571     if (!qmlEngine(m_model))
1572         return;
1573     for (int i = 0; i < roleCount; ++i) {
1574         const int changedRole = changedRoles[i];
1575         QQmlNotifier::notify(ddata, changedRole);
1576     }
1577 }
1578 
1579 namespace QV4 {
1580 
virtualPut(Managed * m,PropertyKey id,const Value & value,Value * receiver)1581 bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
1582 {
1583     if (!id.isString())
1584         return Object::virtualPut(m, id, value, receiver);
1585     QString propName = id.toQString();
1586 
1587     ModelObject *that = static_cast<ModelObject*>(m);
1588 
1589     ExecutionEngine *eng = that->engine();
1590     const int elementIndex = that->d()->elementIndex();
1591     int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng);
1592     if (roleIndex != -1)
1593         that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
1594 
1595     ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object());
1596     if (mo->initialized())
1597         mo->emitPropertyNotification(propName.toUtf8());
1598     return true;
1599 }
1600 
virtualGet(const Managed * m,PropertyKey id,const Value * receiver,bool * hasProperty)1601 ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
1602 {
1603     if (!id.isString())
1604         return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
1605 
1606     const ModelObject *that = static_cast<const ModelObject*>(m);
1607     Scope scope(that);
1608     ScopedString name(scope, id.asStringOrSymbol());
1609     const ListLayout::Role *role = that->d()->m_model->m_listModel->getExistingRole(name);
1610     if (!role)
1611         return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
1612     if (hasProperty)
1613         *hasProperty = true;
1614 
1615     if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) {
1616         QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine);
1617         if (ep && ep->propertyCapture)
1618             ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false);
1619     }
1620 
1621     const int elementIndex = that->d()->elementIndex();
1622     QVariant value = that->d()->m_model->data(elementIndex, role->index);
1623     return that->engine()->fromVariant(value);
1624 }
1625 
virtualResolveLookupGetter(const Object * object,ExecutionEngine * engine,Lookup * lookup)1626 ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
1627 {
1628     lookup->getter = Lookup::getterFallback;
1629     return lookup->getter(lookup, engine, *object);
1630 }
1631 
1632 struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
1633 {
1634     int roleNameIndex = 0;
1635     ~ModelObjectOwnPropertyKeyIterator() override = default;
1636     PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
1637 
1638 };
1639 
next(const Object * o,Property * pd,PropertyAttributes * attrs)1640 PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
1641 {
1642     const ModelObject *that = static_cast<const ModelObject *>(o);
1643 
1644     ExecutionEngine *v4 = that->engine();
1645     if (roleNameIndex < that->listModel()->roleCount()) {
1646         Scope scope(that->engine());
1647         const ListLayout::Role &role = that->listModel()->getExistingRole(roleNameIndex);
1648         ++roleNameIndex;
1649         ScopedString roleName(scope, v4->newString(role.name));
1650         if (attrs)
1651             *attrs = QV4::Attr_Data;
1652         if (pd) {
1653 
1654             QVariant value = that->d()->m_model->data(that->d()->elementIndex(), role.index);
1655             if (auto recursiveListModel = qvariant_cast<QQmlListModel*>(value)) {
1656                 auto size = recursiveListModel->count();
1657                 auto array = ScopedArrayObject{scope, v4->newArrayObject(size)};
1658                 for (auto i = 0; i < size; i++) {
1659                     array->arrayPut(i, QJSValuePrivate::convertedToValue(v4, recursiveListModel->get(i)));
1660                 }
1661                 pd->value = array;
1662             } else {
1663                 pd->value = v4->fromVariant(value);
1664             }
1665         }
1666         return roleName->toPropertyKey();
1667     }
1668 
1669     // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add
1670     // unnecessary entries that relate to the roles used. These just create extra work
1671     // later on as they will just be ignored.
1672     return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
1673 }
1674 
virtualOwnPropertyKeys(const Object * m,Value * target)1675 OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target)
1676 {
1677     *target = *m;
1678     return new ModelObjectOwnPropertyKeyIterator;
1679 }
1680 
1681 DEFINE_OBJECT_VTABLE(ModelObject);
1682 
1683 } // namespace QV4
1684 
DynamicRoleModelNode(QQmlListModel * owner,int uid)1685 DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1686 {
1687     setNodeUpdatesEnabled(true);
1688 }
1689 
create(const QVariantMap & obj,QQmlListModel * owner)1690 DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner)
1691 {
1692     DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1693     QVector<int> roles;
1694     object->updateValues(obj, roles);
1695     return object;
1696 }
1697 
sync(DynamicRoleModelNode * src,DynamicRoleModelNode * target)1698 QVector<int> DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target)
1699 {
1700     QVector<int> changedRoles;
1701     for (int i = 0; i < src->m_meta->count(); ++i) {
1702         const QByteArray &name = src->m_meta->name(i);
1703         QVariant value = src->m_meta->value(i);
1704 
1705         QQmlListModel *srcModel = qobject_cast<QQmlListModel *>(value.value<QObject *>());
1706         QQmlListModel *targetModel = qobject_cast<QQmlListModel *>(target->m_meta->value(i).value<QObject *>());
1707 
1708         bool modelHasChanges = false;
1709         if (srcModel) {
1710             if (targetModel == nullptr)
1711                 targetModel = QQmlListModel::createWithOwner(target->m_owner);
1712 
1713             modelHasChanges = QQmlListModel::sync(srcModel, targetModel);
1714 
1715             QObject *targetModelObject = targetModel;
1716             value = QVariant::fromValue(targetModelObject);
1717         } else if (targetModel) {
1718             delete targetModel;
1719         }
1720 
1721         if (target->setValue(name, value) || modelHasChanges)
1722             changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name));
1723     }
1724     return changedRoles;
1725 }
1726 
updateValues(const QVariantMap & object,QVector<int> & roles)1727 void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int> &roles)
1728 {
1729     for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) {
1730         const QString &key = it.key();
1731 
1732         int roleIndex = m_owner->m_roles.indexOf(key);
1733         if (roleIndex == -1) {
1734             roleIndex = m_owner->m_roles.count();
1735             m_owner->m_roles.append(key);
1736         }
1737 
1738         QVariant value = it.value();
1739 
1740         // A JS array/object is translated into a (hierarchical) QQmlListModel,
1741         // so translate to a variant map/list first with toVariant().
1742         if (value.userType() == qMetaTypeId<QJSValue>())
1743             value = value.value<QJSValue>().toVariant();
1744 
1745         if (value.userType() == QMetaType::QVariantList) {
1746             QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);
1747 
1748             QVariantList subArray = value.toList();
1749             QVariantList::const_iterator subIt = subArray.cbegin();
1750             QVariantList::const_iterator subEnd = subArray.cend();
1751             while (subIt != subEnd) {
1752                 const QVariantMap &subObject = subIt->toMap();
1753                 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1754                 ++subIt;
1755             }
1756 
1757             QObject *subModelObject = subModel;
1758             value = QVariant::fromValue(subModelObject);
1759         }
1760 
1761         const QByteArray &keyUtf8 = key.toUtf8();
1762 
1763         QQmlListModel *existingModel = qobject_cast<QQmlListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1764         delete existingModel;
1765 
1766         if (m_meta->setValue(keyUtf8, value))
1767             roles << roleIndex;
1768     }
1769 }
1770 
DynamicRoleModelNodeMetaObject(DynamicRoleModelNode * object)1771 DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1772     : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1773 {
1774 }
1775 
~DynamicRoleModelNodeMetaObject()1776 DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1777 {
1778     for (int i=0 ; i < count() ; ++i) {
1779         QQmlListModel *subModel = qobject_cast<QQmlListModel *>(value(i).value<QObject *>());
1780         delete subModel;
1781     }
1782 }
1783 
propertyWrite(int index)1784 void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1785 {
1786     if (!m_enabled)
1787         return;
1788 
1789     QVariant v = value(index);
1790     QQmlListModel *model = qobject_cast<QQmlListModel *>(v.value<QObject *>());
1791     delete model;
1792 }
1793 
propertyWritten(int index)1794 void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1795 {
1796     if (!m_enabled)
1797         return;
1798 
1799     QQmlListModel *parentModel = m_owner->m_owner;
1800 
1801     QVariant v = value(index);
1802 
1803     // A JS array/object is translated into a (hierarchical) QQmlListModel,
1804     // so translate to a variant map/list first with toVariant().
1805     if (v.userType() == qMetaTypeId<QJSValue>())
1806         v= v.value<QJSValue>().toVariant();
1807 
1808     if (v.userType() == QMetaType::QVariantList) {
1809         QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);
1810 
1811         QVariantList subArray = v.toList();
1812         QVariantList::const_iterator subIt = subArray.cbegin();
1813         QVariantList::const_iterator subEnd = subArray.cend();
1814         while (subIt != subEnd) {
1815             const QVariantMap &subObject = subIt->toMap();
1816             subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1817             ++subIt;
1818         }
1819 
1820         QObject *subModelObject = subModel;
1821         v = QVariant::fromValue(subModelObject);
1822 
1823         setValue(index, v);
1824     }
1825 
1826     int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1827     if (elementIndex != -1) {
1828         int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1829         if (roleIndex != -1)
1830             parentModel->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
1831     }
1832 }
1833 
1834 /*!
1835     \qmltype ListModel
1836     \instantiates QQmlListModel
1837     \inqmlmodule QtQml.Models
1838     \ingroup qtquick-models
1839     \brief Defines a free-form list data source.
1840 
1841     The ListModel is a simple container of ListElement definitions, each
1842     containing data roles. The contents can be defined dynamically, or
1843     explicitly in QML.
1844 
1845     The number of elements in the model can be obtained from its \l count property.
1846     A number of familiar methods are also provided to manipulate the contents of the
1847     model, including append(), insert(), move(), remove() and set(). These methods
1848     accept dictionaries as their arguments; these are translated to ListElement objects
1849     by the model.
1850 
1851     Elements can be manipulated via the model using the setProperty() method, which
1852     allows the roles of the specified element to be set and changed.
1853 
1854     \section1 Example Usage
1855 
1856     The following example shows a ListModel containing three elements, with the roles
1857     "name" and "cost".
1858 
1859     \div {class="float-right"}
1860     \inlineimage listmodel.png
1861     \enddiv
1862 
1863     \snippet qml/listmodel/listmodel.qml 0
1864 
1865     Roles (properties) in each element must begin with a lower-case letter and
1866     should be common to all elements in a model. The ListElement documentation
1867     provides more guidelines for how elements should be defined.
1868 
1869     Since the example model contains an \c id property, it can be referenced
1870     by views, such as the ListView in this example:
1871 
1872     \snippet qml/listmodel/listmodel-simple.qml 0
1873     \dots 8
1874     \snippet qml/listmodel/listmodel-simple.qml 1
1875 
1876     It is possible for roles to contain list data.  In the following example we
1877     create a list of fruit attributes:
1878 
1879     \snippet qml/listmodel/listmodel-nested.qml model
1880 
1881     The delegate displays all the fruit attributes:
1882 
1883     \div {class="float-right"}
1884     \inlineimage listmodel-nested.png
1885     \enddiv
1886 
1887     \snippet qml/listmodel/listmodel-nested.qml delegate
1888 
1889     \section1 Modifying List Models
1890 
1891     The content of a ListModel may be created and modified using the clear(),
1892     append(), set(), insert() and setProperty() methods.  For example:
1893 
1894     \snippet qml/listmodel/listmodel-modify.qml delegate
1895 
1896     Note that when creating content dynamically the set of available properties
1897     cannot be changed once set. Whatever properties are first added to the model
1898     are the only permitted properties in the model.
1899 
1900     \section1 Using Threaded List Models with WorkerScript
1901 
1902     ListModel can be used together with WorkerScript access a list model
1903     from multiple threads. This is useful if list modifications are
1904     synchronous and take some time: the list operations can be moved to a
1905     different thread to avoid blocking of the main GUI thread.
1906 
1907     Here is an example that uses WorkerScript to periodically append the
1908     current time to a list model:
1909 
1910     \snippet ../../examples/quick/threading/threadedlistmodel/timedisplay.qml 0
1911 
1912     The included file, \tt dataloader.mjs, looks like this:
1913 
1914     \snippet ../../examples/quick/threading/threadedlistmodel/dataloader.mjs 0
1915 
1916     The timer in the main example sends messages to the worker script by calling
1917     \l WorkerScript::sendMessage(). When this message is received,
1918     \c WorkerScript.onMessage() is invoked in \c dataloader.mjs,
1919     which appends the current time to the list model.
1920 
1921     Note the call to sync() from the external thread.
1922     You must call sync() or else the changes made to the list from that
1923     thread will not be reflected in the list model in the main thread.
1924 
1925     \sa {qml-data-models}{Data Models}, {Qt Quick Examples - Threading}, {Qt QML}
1926 */
1927 
QQmlListModel(QObject * parent)1928 QQmlListModel::QQmlListModel(QObject *parent)
1929 : QAbstractListModel(parent)
1930 {
1931     m_mainThread = true;
1932     m_primary = true;
1933     m_agent = nullptr;
1934     m_dynamicRoles = false;
1935 
1936     m_layout = new ListLayout;
1937     m_listModel = new ListModel(m_layout, this);
1938 
1939     m_engine = nullptr;
1940 }
1941 
QQmlListModel(const QQmlListModel * owner,ListModel * data,QV4::ExecutionEngine * engine,QObject * parent)1942 QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent)
1943 : QAbstractListModel(parent)
1944 {
1945     m_mainThread = owner->m_mainThread;
1946     m_primary = false;
1947     m_agent = owner->m_agent;
1948 
1949     Q_ASSERT(owner->m_dynamicRoles == false);
1950     m_dynamicRoles = false;
1951     m_layout = nullptr;
1952     m_listModel = data;
1953 
1954     m_engine = engine;
1955     m_compilationUnit = owner->m_compilationUnit;
1956 }
1957 
QQmlListModel(QQmlListModel * orig,QQmlListModelWorkerAgent * agent)1958 QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent)
1959 : QAbstractListModel(agent)
1960 {
1961     m_mainThread = false;
1962     m_primary = true;
1963     m_agent = agent;
1964     m_dynamicRoles = orig->m_dynamicRoles;
1965 
1966     m_layout = new ListLayout(orig->m_layout);
1967     m_listModel = new ListModel(m_layout, this);
1968 
1969     if (m_dynamicRoles)
1970         sync(orig, this);
1971     else
1972         ListModel::sync(orig->m_listModel, m_listModel);
1973 
1974     m_engine = nullptr;
1975     m_compilationUnit = orig->m_compilationUnit;
1976 }
1977 
~QQmlListModel()1978 QQmlListModel::~QQmlListModel()
1979 {
1980     qDeleteAll(m_modelObjects);
1981 
1982     if (m_primary) {
1983         m_listModel->destroy();
1984         delete m_listModel;
1985 
1986         if (m_mainThread && m_agent) {
1987             m_agent->modelDestroyed();
1988             m_agent->release();
1989         }
1990     }
1991 
1992     m_listModel = nullptr;
1993 
1994     delete m_layout;
1995     m_layout = nullptr;
1996 }
1997 
createWithOwner(QQmlListModel * newOwner)1998 QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
1999 {
2000     QQmlListModel *model = new QQmlListModel;
2001 
2002     model->m_mainThread = newOwner->m_mainThread;
2003     model->m_engine = newOwner->m_engine;
2004     model->m_agent = newOwner->m_agent;
2005     model->m_dynamicRoles = newOwner->m_dynamicRoles;
2006 
2007     if (model->m_mainThread && model->m_agent)
2008         model->m_agent->addref();
2009 
2010     QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
2011 
2012     return model;
2013 }
2014 
engine() const2015 QV4::ExecutionEngine *QQmlListModel::engine() const
2016 {
2017     if (m_engine == nullptr) {
2018         m_engine  = qmlEngine(this)->handle();
2019     }
2020 
2021     return m_engine;
2022 }
2023 
sync(QQmlListModel * src,QQmlListModel * target)2024 bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target)
2025 {
2026     Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
2027 
2028     bool hasChanges = false;
2029 
2030     target->m_roles = src->m_roles;
2031 
2032     // Build hash of elements <-> uid for each of the lists
2033     QHash<int, ElementSync> elementHash;
2034     for (int i = 0 ; i < target->m_modelObjects.count(); ++i) {
2035         DynamicRoleModelNode *e = target->m_modelObjects.at(i);
2036         int uid = e->getUid();
2037         ElementSync sync;
2038         sync.target = e;
2039         sync.targetIndex = i;
2040         elementHash.insert(uid, sync);
2041     }
2042     for (int i = 0 ; i < src->m_modelObjects.count(); ++i) {
2043         DynamicRoleModelNode *e = src->m_modelObjects.at(i);
2044         int uid = e->getUid();
2045 
2046         QHash<int, ElementSync>::iterator it = elementHash.find(uid);
2047         if (it == elementHash.end()) {
2048             ElementSync sync;
2049             sync.src = e;
2050             sync.srcIndex = i;
2051             elementHash.insert(uid, sync);
2052         } else {
2053             ElementSync &sync = it.value();
2054             sync.src = e;
2055             sync.srcIndex = i;
2056         }
2057     }
2058 
2059     // Get list of elements that are in the target but no longer in the source. These get deleted first.
2060     int rowsRemoved = 0;
2061     for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) {
2062         DynamicRoleModelNode *element = target->m_modelObjects.at(i);
2063         ElementSync &s = elementHash.find(element->getUid()).value();
2064         Q_ASSERT(s.targetIndex >= 0);
2065         // need to update the targetIndex, to keep it correct after removals
2066         s.targetIndex -= rowsRemoved;
2067         if (s.src == nullptr) {
2068             Q_ASSERT(s.targetIndex == i);
2069             hasChanges = true;
2070             target->beginRemoveRows(QModelIndex(), i, i);
2071             target->m_modelObjects.remove(i, 1);
2072             target->endRemoveRows();
2073             delete s.target;
2074             ++rowsRemoved;
2075             --i;
2076             continue;
2077         }
2078     }
2079 
2080     // Clear the target list, and append in correct order from the source
2081     target->m_modelObjects.clear();
2082     for (int i = 0 ; i < src->m_modelObjects.count() ; ++i) {
2083         DynamicRoleModelNode *element = src->m_modelObjects.at(i);
2084         ElementSync &s = elementHash.find(element->getUid()).value();
2085         Q_ASSERT(s.srcIndex >= 0);
2086         DynamicRoleModelNode *targetElement = s.target;
2087         if (targetElement == nullptr) {
2088             targetElement = new DynamicRoleModelNode(target, element->getUid());
2089         }
2090         s.changedRoles = DynamicRoleModelNode::sync(element, targetElement);
2091         target->m_modelObjects.append(targetElement);
2092     }
2093 
2094     // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
2095     // so the model indices can't be out of bounds
2096     //
2097     // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
2098     // model indices are updated correctly
2099     int rowsInserted = 0;
2100     for (int i = 0 ; i < target->m_modelObjects.count() ; ++i) {
2101         DynamicRoleModelNode *element = target->m_modelObjects.at(i);
2102         ElementSync &s = elementHash.find(element->getUid()).value();
2103         Q_ASSERT(s.srcIndex >= 0);
2104         s.srcIndex += rowsInserted;
2105         if (s.srcIndex != s.targetIndex) {
2106             if (s.targetIndex == -1) {
2107                 target->beginInsertRows(QModelIndex(), i, i);
2108                 target->endInsertRows();
2109             } else {
2110                 target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
2111                 target->endMoveRows();
2112             }
2113             hasChanges = true;
2114             ++rowsInserted;
2115         }
2116         if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
2117             QModelIndex idx = target->createIndex(i, 0);
2118             emit target->dataChanged(idx, idx, s.changedRoles);
2119             hasChanges = true;
2120         }
2121     }
2122     return hasChanges;
2123 }
2124 
emitItemsChanged(int index,int count,const QVector<int> & roles)2125 void QQmlListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
2126 {
2127     if (count <= 0)
2128         return;
2129 
2130     if (m_mainThread)
2131         emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
2132 }
2133 
emitItemsAboutToBeInserted(int index,int count)2134 void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
2135 {
2136     Q_ASSERT(index >= 0 && count >= 0);
2137     if (m_mainThread)
2138         beginInsertRows(QModelIndex(), index, index + count - 1);
2139 }
2140 
emitItemsInserted()2141 void QQmlListModel::emitItemsInserted()
2142 {
2143     if (m_mainThread) {
2144         endInsertRows();
2145         emit countChanged();
2146     }
2147 }
2148 
agent()2149 QQmlListModelWorkerAgent *QQmlListModel::agent()
2150 {
2151     if (m_agent)
2152         return m_agent;
2153 
2154     m_agent = new QQmlListModelWorkerAgent(this);
2155     return m_agent;
2156 }
2157 
index(int row,int column,const QModelIndex & parent) const2158 QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const
2159 {
2160     return row >= 0 && row < count() && column == 0 && !parent.isValid()
2161             ? createIndex(row, column)
2162             : QModelIndex();
2163 }
2164 
rowCount(const QModelIndex & parent) const2165 int QQmlListModel::rowCount(const QModelIndex &parent) const
2166 {
2167     return !parent.isValid() ? count() : 0;
2168 }
2169 
data(const QModelIndex & index,int role) const2170 QVariant QQmlListModel::data(const QModelIndex &index, int role) const
2171 {
2172     return data(index.row(), role);
2173 }
2174 
setData(const QModelIndex & index,const QVariant & value,int role)2175 bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
2176 {
2177     const int row = index.row();
2178     if (row >= count() || row < 0)
2179         return false;
2180 
2181     if (m_dynamicRoles) {
2182         const QByteArray property = m_roles.at(role).toUtf8();
2183         if (m_modelObjects[row]->setValue(property, value)) {
2184             emitItemsChanged(row, 1, QVector<int>(1, role));
2185             return true;
2186         }
2187     } else {
2188         const ListLayout::Role &r = m_listModel->getExistingRole(role);
2189         const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value);
2190         if (roleIndex != -1) {
2191             emitItemsChanged(row, 1, QVector<int>(1, role));
2192             return true;
2193         }
2194     }
2195 
2196     return false;
2197 }
2198 
data(int index,int role) const2199 QVariant QQmlListModel::data(int index, int role) const
2200 {
2201     QVariant v;
2202 
2203     if (index >= count() || index < 0)
2204         return v;
2205 
2206     if (m_dynamicRoles)
2207         v = m_modelObjects[index]->getValue(m_roles[role]);
2208     else
2209         v = m_listModel->getProperty(index, role, this, engine());
2210 
2211     return v;
2212 }
2213 
roleNames() const2214 QHash<int, QByteArray> QQmlListModel::roleNames() const
2215 {
2216     QHash<int, QByteArray> roleNames;
2217 
2218     if (m_dynamicRoles) {
2219         for (int i = 0 ; i < m_roles.count() ; ++i)
2220             roleNames.insert(i, m_roles.at(i).toUtf8());
2221     } else {
2222         for (int i = 0 ; i < m_listModel->roleCount() ; ++i) {
2223             const ListLayout::Role &r = m_listModel->getExistingRole(i);
2224             roleNames.insert(i, r.name.toUtf8());
2225         }
2226     }
2227 
2228     return roleNames;
2229 }
2230 
2231 /*!
2232     \qmlproperty bool ListModel::dynamicRoles
2233 
2234     By default, the type of a role is fixed the first time
2235     the role is used. For example, if you create a role called
2236     "data" and assign a number to it, you can no longer assign
2237     a string to the "data" role. However, when the dynamicRoles
2238     property is enabled, the type of a given role is not fixed
2239     and can be different between elements.
2240 
2241     The dynamicRoles property must be set before any data is
2242     added to the ListModel, and must be set from the main
2243     thread.
2244 
2245     A ListModel that has data statically defined (via the
2246     ListElement QML syntax) cannot have the dynamicRoles
2247     property enabled.
2248 
2249     There is a significant performance cost to using a
2250     ListModel with dynamic roles enabled. The cost varies
2251     from platform to platform but is typically somewhere
2252     between 4-6x slower than using static role types.
2253 
2254     Due to the performance cost of using dynamic roles,
2255     they are disabled by default.
2256 */
setDynamicRoles(bool enableDynamicRoles)2257 void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
2258 {
2259     if (m_mainThread && m_agent == nullptr) {
2260         if (enableDynamicRoles) {
2261             if (m_layout->roleCount())
2262                 qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
2263             else
2264                 m_dynamicRoles = true;
2265         } else {
2266             if (m_roles.count()) {
2267                 qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
2268             } else {
2269                 m_dynamicRoles = false;
2270             }
2271         }
2272     } else {
2273         qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
2274     }
2275 }
2276 
2277 /*!
2278     \qmlproperty int ListModel::count
2279     The number of data entries in the model.
2280 */
count() const2281 int QQmlListModel::count() const
2282 {
2283     return m_dynamicRoles ? m_modelObjects.count() : m_listModel->elementCount();
2284 }
2285 
2286 /*!
2287     \qmlmethod ListModel::clear()
2288 
2289     Deletes all content from the model.
2290 
2291     \sa append(), remove()
2292 */
clear()2293 void QQmlListModel::clear()
2294 {
2295     removeElements(0, count());
2296 }
2297 
2298 /*!
2299     \qmlmethod ListModel::remove(int index, int count = 1)
2300 
2301     Deletes the content at \a index from the model.
2302 
2303     \sa clear()
2304 */
remove(QQmlV4Function * args)2305 void QQmlListModel::remove(QQmlV4Function *args)
2306 {
2307     int argLength = args->length();
2308 
2309     if (argLength == 1 || argLength == 2) {
2310         QV4::Scope scope(args->v4engine());
2311         int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
2312         int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);
2313 
2314         if (index < 0 || index+removeCount > count() || removeCount <= 0) {
2315             qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
2316             return;
2317         }
2318 
2319         removeElements(index, removeCount);
2320     } else {
2321         qmlWarning(this) << tr("remove: incorrect number of arguments");
2322     }
2323 }
2324 
removeElements(int index,int removeCount)2325 void QQmlListModel::removeElements(int index, int removeCount)
2326 {
2327     Q_ASSERT(index >= 0 && removeCount >= 0);
2328 
2329     if (!removeCount)
2330         return;
2331 
2332     if (m_mainThread)
2333         beginRemoveRows(QModelIndex(), index, index + removeCount - 1);
2334 
2335     QVector<std::function<void()>> toDestroy;
2336     if (m_dynamicRoles) {
2337         for (int i=0 ; i < removeCount ; ++i) {
2338             auto modelObject = m_modelObjects[index+i];
2339             toDestroy.append([modelObject](){
2340                 delete modelObject;
2341             });
2342         }
2343         m_modelObjects.remove(index, removeCount);
2344     } else {
2345         toDestroy = m_listModel->remove(index, removeCount);
2346     }
2347 
2348     if (m_mainThread) {
2349         endRemoveRows();
2350         emit countChanged();
2351     }
2352     for (const auto &destroyer : toDestroy)
2353         destroyer();
2354 }
2355 
2356 /*!
2357     \qmlmethod ListModel::insert(int index, jsobject dict)
2358 
2359     Adds a new item to the list model at position \a index, with the
2360     values in \a dict.
2361 
2362     \code
2363         fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
2364     \endcode
2365 
2366     The \a index must be to an existing item in the list, or one past
2367     the end of the list (equivalent to append).
2368 
2369     \sa set(), append()
2370 */
2371 
insert(QQmlV4Function * args)2372 void QQmlListModel::insert(QQmlV4Function *args)
2373 {
2374     if (args->length() == 2) {
2375         QV4::Scope scope(args->v4engine());
2376         QV4::ScopedValue arg0(scope, (*args)[0]);
2377         int index = arg0->toInt32();
2378 
2379         if (index < 0 || index > count()) {
2380             qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
2381             return;
2382         }
2383 
2384         QV4::ScopedObject argObject(scope, (*args)[1]);
2385         QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
2386         if (objectArray) {
2387             QV4::ScopedObject argObject(scope);
2388 
2389             int objectArrayLength = objectArray->getLength();
2390             emitItemsAboutToBeInserted(index, objectArrayLength);
2391             for (int i=0 ; i < objectArrayLength ; ++i) {
2392                 argObject = objectArray->get(i);
2393 
2394                 if (m_dynamicRoles) {
2395                     m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2396                 } else {
2397                     m_listModel->insert(index+i, argObject);
2398                 }
2399             }
2400             emitItemsInserted();
2401         } else if (argObject) {
2402             emitItemsAboutToBeInserted(index, 1);
2403 
2404             if (m_dynamicRoles) {
2405                 m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2406             } else {
2407                 m_listModel->insert(index, argObject);
2408             }
2409 
2410             emitItemsInserted();
2411         } else {
2412             qmlWarning(this) << tr("insert: value is not an object");
2413         }
2414     } else {
2415         qmlWarning(this) << tr("insert: value is not an object");
2416     }
2417 }
2418 
2419 /*!
2420     \qmlmethod ListModel::move(int from, int to, int n)
2421 
2422     Moves \a n items \a from one position \a to another.
2423 
2424     The from and to ranges must exist; for example, to move the first 3 items
2425     to the end of the list:
2426 
2427     \code
2428         fruitModel.move(0, fruitModel.count - 3, 3)
2429     \endcode
2430 
2431     \sa append()
2432 */
move(int from,int to,int n)2433 void QQmlListModel::move(int from, int to, int n)
2434 {
2435     if (n == 0 || from == to)
2436         return;
2437     if (!canMove(from, to, n)) {
2438         qmlWarning(this) << tr("move: out of range");
2439         return;
2440     }
2441 
2442     if (m_mainThread)
2443         beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
2444 
2445     if (m_dynamicRoles) {
2446 
2447         int realFrom = from;
2448         int realTo = to;
2449         int realN = n;
2450 
2451         if (from > to) {
2452             // Only move forwards - flip if backwards moving
2453             int tfrom = from;
2454             int tto = to;
2455             realFrom = tto;
2456             realTo = tto+n;
2457             realN = tfrom-tto;
2458         }
2459 
2460         QPODVector<DynamicRoleModelNode *, 4> store;
2461         for (int i=0 ; i < (realTo-realFrom) ; ++i)
2462             store.append(m_modelObjects[realFrom+realN+i]);
2463         for (int i=0 ; i < realN ; ++i)
2464             store.append(m_modelObjects[realFrom+i]);
2465         for (int i=0 ; i < store.count() ; ++i)
2466             m_modelObjects[realFrom+i] = store[i];
2467 
2468     } else {
2469         m_listModel->move(from, to, n);
2470     }
2471 
2472     if (m_mainThread)
2473         endMoveRows();
2474 }
2475 
2476 /*!
2477     \qmlmethod ListModel::append(jsobject dict)
2478 
2479     Adds a new item to the end of the list model, with the
2480     values in \a dict.
2481 
2482     \code
2483         fruitModel.append({"cost": 5.95, "name":"Pizza"})
2484     \endcode
2485 
2486     \sa set(), remove()
2487 */
append(QQmlV4Function * args)2488 void QQmlListModel::append(QQmlV4Function *args)
2489 {
2490     if (args->length() == 1) {
2491         QV4::Scope scope(args->v4engine());
2492         QV4::ScopedObject argObject(scope, (*args)[0]);
2493         QV4::ScopedArrayObject objectArray(scope, (*args)[0]);
2494 
2495         if (objectArray) {
2496             QV4::ScopedObject argObject(scope);
2497 
2498             int objectArrayLength = objectArray->getLength();
2499             if (objectArrayLength > 0) {
2500                 int index = count();
2501                 emitItemsAboutToBeInserted(index, objectArrayLength);
2502 
2503                 for (int i=0 ; i < objectArrayLength ; ++i) {
2504                     argObject = objectArray->get(i);
2505 
2506                     if (m_dynamicRoles) {
2507                         m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2508                     } else {
2509                         m_listModel->append(argObject);
2510                     }
2511                 }
2512 
2513                 emitItemsInserted();
2514             }
2515         } else if (argObject) {
2516             int index;
2517 
2518             if (m_dynamicRoles) {
2519                 index = m_modelObjects.count();
2520                 emitItemsAboutToBeInserted(index, 1);
2521                 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2522             } else {
2523                 index = m_listModel->elementCount();
2524                 emitItemsAboutToBeInserted(index, 1);
2525                 m_listModel->append(argObject);
2526             }
2527 
2528             emitItemsInserted();
2529         } else {
2530             qmlWarning(this) << tr("append: value is not an object");
2531         }
2532     } else {
2533         qmlWarning(this) << tr("append: value is not an object");
2534     }
2535 }
2536 
2537 /*!
2538     \qmlmethod object ListModel::get(int index)
2539 
2540     Returns the item at \a index in the list model. This allows the item
2541     data to be accessed or modified from JavaScript:
2542 
2543     \code
2544     Component.onCompleted: {
2545         fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2546         console.log(fruitModel.get(0).cost);
2547         fruitModel.get(0).cost = 10.95;
2548     }
2549     \endcode
2550 
2551     The \a index must be an element in the list.
2552 
2553     Note that properties of the returned object that are themselves objects
2554     will also be models, and this get() method is used to access elements:
2555 
2556     \code
2557         fruitModel.append(..., "attributes":
2558             [{"name":"spikes","value":"7mm"},
2559              {"name":"color","value":"green"}]);
2560         fruitModel.get(0).attributes.get(1).value; // == "green"
2561     \endcode
2562 
2563     \warning The returned object is not guaranteed to remain valid. It
2564     should not be used in \l{Property Binding}{property bindings}.
2565 
2566     \sa append()
2567 */
get(int index) const2568 QJSValue QQmlListModel::get(int index) const
2569 {
2570     QV4::Scope scope(engine());
2571     QV4::ScopedValue result(scope, QV4::Value::undefinedValue());
2572 
2573     if (index >= 0 && index < count()) {
2574 
2575         if (m_dynamicRoles) {
2576             DynamicRoleModelNode *object = m_modelObjects[index];
2577             result = QV4::QObjectWrapper::wrap(scope.engine, object);
2578         } else {
2579             QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
2580             QQmlData *ddata = QQmlData::get(object);
2581             if (ddata->jsWrapper.isNullOrUndefined()) {
2582                 result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
2583                 // Keep track of the QObjectWrapper in persistent value storage
2584                 ddata->jsWrapper.set(scope.engine, result);
2585             } else {
2586                 result = ddata->jsWrapper.value();
2587             }
2588         }
2589     }
2590 
2591     return QJSValue(engine(), result->asReturnedValue());
2592 }
2593 
2594 /*!
2595     \qmlmethod ListModel::set(int index, jsobject dict)
2596 
2597     Changes the item at \a index in the list model with the
2598     values in \a dict. Properties not appearing in \a dict
2599     are left unchanged.
2600 
2601     \code
2602         fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2603     \endcode
2604 
2605     If \a index is equal to count() then a new item is appended to the
2606     list. Otherwise, \a index must be an element in the list.
2607 
2608     \sa append()
2609 */
set(int index,const QJSValue & value)2610 void QQmlListModel::set(int index, const QJSValue &value)
2611 {
2612     QV4::Scope scope(engine());
2613     QV4::ScopedObject object(scope, QJSValuePrivate::getValue(&value));
2614 
2615     if (!object) {
2616         qmlWarning(this) << tr("set: value is not an object");
2617         return;
2618     }
2619     if (index > count() || index < 0) {
2620         qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2621         return;
2622     }
2623 
2624 
2625     if (index == count()) {
2626         emitItemsAboutToBeInserted(index, 1);
2627 
2628         if (m_dynamicRoles) {
2629             m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
2630         } else {
2631             m_listModel->insert(index, object);
2632         }
2633 
2634         emitItemsInserted();
2635     } else {
2636 
2637         QVector<int> roles;
2638 
2639         if (m_dynamicRoles) {
2640             m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
2641         } else {
2642             m_listModel->set(index, object, &roles);
2643         }
2644 
2645         if (roles.count())
2646             emitItemsChanged(index, 1, roles);
2647     }
2648 }
2649 
2650 /*!
2651     \qmlmethod ListModel::setProperty(int index, string property, variant value)
2652 
2653     Changes the \a property of the item at \a index in the list model to \a value.
2654 
2655     \code
2656         fruitModel.setProperty(3, "cost", 5.95)
2657     \endcode
2658 
2659     The \a index must be an element in the list.
2660 
2661     \sa append()
2662 */
setProperty(int index,const QString & property,const QVariant & value)2663 void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
2664 {
2665     if (count() == 0 || index >= count() || index < 0) {
2666         qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2667         return;
2668     }
2669 
2670     if (m_dynamicRoles) {
2671         int roleIndex = m_roles.indexOf(property);
2672         if (roleIndex == -1) {
2673             roleIndex = m_roles.count();
2674             m_roles.append(property);
2675         }
2676         if (m_modelObjects[index]->setValue(property.toUtf8(), value))
2677             emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
2678     } else {
2679         int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2680         if (roleIndex != -1)
2681             emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
2682     }
2683 }
2684 
2685 /*!
2686     \qmlmethod ListModel::sync()
2687 
2688     Writes any unsaved changes to the list model after it has been modified
2689     from a worker script.
2690 */
sync()2691 void QQmlListModel::sync()
2692 {
2693     // This is just a dummy method to make it look like sync() exists in
2694     // ListModel (and not just QQmlListModelWorkerAgent) and to let
2695     // us document sync().
2696     qmlWarning(this) << "List sync() can only be called from a WorkerScript";
2697 }
2698 
verifyProperty(const QQmlRefPointer<QV4::ExecutableCompilationUnit> & compilationUnit,const QV4::CompiledData::Binding * binding)2699 bool QQmlListModelParser::verifyProperty(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding)
2700 {
2701     if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
2702         const quint32 targetObjectIndex = binding->value.objectIndex;
2703         const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2704         QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
2705         if (objName != listElementTypeName) {
2706             const QMetaObject *mo = resolveType(objName);
2707             if (mo != &QQmlListElement::staticMetaObject) {
2708                 error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2709                 return false;
2710             }
2711             listElementTypeName = objName; // cache right name for next time
2712         }
2713 
2714         if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
2715             error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
2716             return false;
2717         }
2718 
2719         const QV4::CompiledData::Binding *binding = target->bindingTable();
2720         for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
2721             QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2722             if (propName.isEmpty()) {
2723                 error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2724                 return false;
2725             }
2726             if (!verifyProperty(compilationUnit, binding))
2727                 return false;
2728         }
2729     } else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
2730         QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2731         if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
2732             QByteArray script = scriptStr.toUtf8();
2733             bool ok;
2734             evaluateEnum(script, &ok);
2735             if (!ok) {
2736                 error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
2737                 return false;
2738             }
2739         }
2740     }
2741 
2742     return true;
2743 }
2744 
applyProperty(const QQmlRefPointer<QV4::ExecutableCompilationUnit> & compilationUnit,const QV4::CompiledData::Binding * binding,ListModel * model,int outterElementIndex)2745 bool QQmlListModelParser::applyProperty(
2746         const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
2747         const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex)
2748 {
2749     const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
2750 
2751     bool roleSet = false;
2752     if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
2753         const quint32 targetObjectIndex = binding->value.objectIndex;
2754         const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2755 
2756         ListModel *subModel = nullptr;
2757         if (outterElementIndex == -1) {
2758             subModel = model;
2759         } else {
2760             const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2761             if (role.type == ListLayout::Role::List) {
2762                 subModel = model->getListProperty(outterElementIndex, role);
2763                 if (subModel == nullptr) {
2764                     subModel = new ListModel(role.subLayout, nullptr);
2765                     QVariant vModel = QVariant::fromValue(subModel);
2766                     model->setOrCreateProperty(outterElementIndex, elementName, vModel);
2767                 }
2768             }
2769         }
2770 
2771         int elementIndex = subModel ? subModel->appendElement() : -1;
2772 
2773         const QV4::CompiledData::Binding *subBinding = target->bindingTable();
2774         for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
2775             roleSet |= applyProperty(compilationUnit, subBinding, subModel, elementIndex);
2776         }
2777 
2778     } else {
2779         QVariant value;
2780 
2781         if (binding->isTranslationBinding()) {
2782             value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
2783         } else if (binding->evaluatesToString()) {
2784             value = compilationUnit->bindingValueAsString(binding);
2785         } else if (binding->type == QV4::CompiledData::Binding::Type_Number) {
2786             value = compilationUnit->bindingValueAsNumber(binding);
2787         } else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) {
2788             value = binding->valueAsBoolean();
2789         } else if (binding->type == QV4::CompiledData::Binding::Type_Null) {
2790             value = QVariant::fromValue(nullptr);
2791         } else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
2792             QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2793             if (definesEmptyList(scriptStr)) {
2794                 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2795                 ListModel *emptyModel = new ListModel(role.subLayout, nullptr);
2796                 value = QVariant::fromValue(emptyModel);
2797             } else if (binding->isFunctionExpression()) {
2798                 QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
2799                 Q_ASSERT(id != QQmlBinding::Invalid);
2800 
2801                 auto v4 = compilationUnit->engine;
2802                 QV4::Scope scope(v4);
2803                 // for now we do not provide a context object; data from the ListElement must be passed to the function
2804                 QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
2805                 QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
2806 
2807                 QJSValue v;
2808                 QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0));
2809                 if (v4->hasException)
2810                     v4->catchException();
2811                 else
2812                     QJSValuePrivate::setValue(&v, v4, result->asReturnedValue());
2813                 value.setValue<QJSValue>(v);
2814             } else {
2815                 QByteArray script = scriptStr.toUtf8();
2816                 bool ok;
2817                 value = evaluateEnum(script, &ok);
2818             }
2819         } else {
2820             Q_UNREACHABLE();
2821         }
2822 
2823         model->setOrCreateProperty(outterElementIndex, elementName, value);
2824         roleSet = true;
2825     }
2826     return roleSet;
2827 }
2828 
verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> & compilationUnit,const QList<const QV4::CompiledData::Binding * > & bindings)2829 void QQmlListModelParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
2830 {
2831     listElementTypeName = QString(); // unknown
2832 
2833     for (const QV4::CompiledData::Binding *binding : bindings) {
2834         QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2835         if (!propName.isEmpty()) { // isn't default property
2836             error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
2837             return;
2838         }
2839         if (!verifyProperty(compilationUnit, binding))
2840             return;
2841     }
2842 }
2843 
applyBindings(QObject * obj,const QQmlRefPointer<QV4::ExecutableCompilationUnit> & compilationUnit,const QList<const QV4::CompiledData::Binding * > & bindings)2844 void QQmlListModelParser::applyBindings(QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
2845 {
2846     QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
2847 
2848     rv->m_engine = qmlEngine(rv)->handle();
2849     rv->m_compilationUnit = compilationUnit;
2850 
2851     bool setRoles = false;
2852 
2853     for (const QV4::CompiledData::Binding *binding : bindings) {
2854         if (binding->type != QV4::CompiledData::Binding::Type_Object)
2855             continue;
2856         setRoles |= applyProperty(compilationUnit, binding, rv->m_listModel, /*outter element index*/-1);
2857     }
2858 
2859     if (setRoles == false)
2860         qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
2861 }
2862 
definesEmptyList(const QString & s)2863 bool QQmlListModelParser::definesEmptyList(const QString &s)
2864 {
2865     if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2866         for (int i=1; i<s.length()-1; i++) {
2867             if (!s[i].isSpace())
2868                 return false;
2869         }
2870         return true;
2871     }
2872     return false;
2873 }
2874 
2875 
2876 /*!
2877     \qmltype ListElement
2878     \instantiates QQmlListElement
2879     \inqmlmodule QtQml.Models
2880     \brief Defines a data item in a ListModel.
2881     \ingroup qtquick-models
2882 
2883     List elements are defined inside ListModel definitions, and represent items in a
2884     list that will be displayed using ListView or \l Repeater items.
2885 
2886     List elements are defined like other QML elements except that they contain
2887     a collection of \e role definitions instead of properties. Using the same
2888     syntax as property definitions, roles both define how the data is accessed
2889     and include the data itself.
2890 
2891     The names used for roles must begin with a lower-case letter and should be
2892     common to all elements in a given model. Values must be simple constants; either
2893     strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2894     (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2895 
2896     Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
2897     a role. This allows the definition of ListElements with callable actions.
2898 
2899     \section1 Referencing Roles
2900 
2901     The role names are used by delegates to obtain data from list elements.
2902     Each role name is accessible in the delegate's scope, and refers to the
2903     corresponding role in the current element. Where a role name would be
2904     ambiguous to use, it can be accessed via the \l{ListView::}{model}
2905     property (e.g., \c{model.cost} instead of \c{cost}).
2906 
2907     \section1 Example Usage
2908 
2909     The following model defines a series of list elements, each of which
2910     contain "name" and "cost" roles and their associated values.
2911 
2912     \snippet qml/listmodel/listelements.qml model
2913 
2914     The delegate obtains the name and cost for each element by simply referring
2915     to \c name and \c cost:
2916 
2917     \snippet qml/listmodel/listelements.qml view
2918 
2919     \sa ListModel
2920 */
2921 
2922 QT_END_NAMESPACE
2923 
2924 #include "moc_qqmllistmodel_p.cpp"
2925