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

source code of qtdeclarative/src/qmlmodels/qqmllistmodel.cpp