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