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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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