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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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