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

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