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

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