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 const int roleCount = m_model->m_listModel->roleCount();
1597 if (!m_initialized) {
1598 if (roleCount) {
1599 Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int));
1600 for (int i = 0; i < roleCount; ++i)
1601 changedRoles[i] = i;
1602 emitDirectNotifies(changedRoles, roleCount);
1603 }
1604 return;
1605 }
1606 for (int i=0 ; i < roleCount ; ++i) {
1607 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1608 QByteArray name = role.name.toUtf8();
1609 const QVariant &data = m_model->data(m_elementIndex, i);
1610 setValue(name, data, role.type == ListLayout::Role::List);
1611 }
1612}
1613
1614void ModelNodeMetaObject::updateValues(const QList<int> &roles)
1615{
1616 if (!m_initialized) {
1617 emitDirectNotifies(roles.constData(), roles.size());
1618 return;
1619 }
1620 int roleCount = roles.size();
1621 for (int i=0 ; i < roleCount ; ++i) {
1622 int roleIndex = roles.at(i);
1623 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1624 QByteArray name = role.name.toUtf8();
1625 const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1626 setValue(name, data, role.type == ListLayout::Role::List);
1627 }
1628}
1629
1631{
1632 if (!m_enabled)
1633 return;
1634
1635 QString propName = QString::fromUtf8(name(index));
1636 const QVariant value = this->value(index);
1637
1638 QV4::Scope scope(m_model->engine());
1639 QV4::ScopedValue v(scope, scope.engine->fromVariant(value));
1640
1641 int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine);
1642 if (roleIndex != -1)
1643 m_model->emitItemsChanged(m_elementIndex, 1, QList<int>(1, roleIndex));
1644}
1645
1646// Does the emission of the notifiers when we haven't created the meta-object yet
1647void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount)
1648{
1649 Q_ASSERT(!m_initialized);
1650 QQmlData *ddata = QQmlData::get(object(), /*create*/false);
1651 if (!ddata)
1652 return;
1653 // There's nothing to emit if we're a list model in a worker thread.
1654 if (!qmlEngine(m_model))
1655 return;
1656 for (int i = 0; i < roleCount; ++i) {
1657 const int changedRole = changedRoles[i];
1658 QQmlNotifier::notify(ddata, changedRole);
1659 }
1660}
1661
1662namespace QV4 {
1663
1664bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
1665{
1666 if (!id.isString())
1667 return Object::virtualPut(m, id, value, receiver);
1668 QString propName = id.toQString();
1669
1670 ModelObject *that = static_cast<ModelObject*>(m);
1671
1672 ExecutionEngine *eng = that->engine();
1673 const int elementIndex = that->d()->elementIndex();
1674 if (QQmlListModel *model = that->d()->m_model) {
1675 const int roleIndex
1676 = model->listModel()->setExistingProperty(elementIndex, propName, value, eng);
1677 if (roleIndex != -1)
1678 model->emitItemsChanged(elementIndex, 1, QList<int>(1, roleIndex));
1679 }
1680
1681 ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object());
1682 if (mo->initialized())
1683 mo->emitPropertyNotification(propName.toUtf8());
1684 return true;
1685}
1686
1687ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
1688{
1689 if (!id.isString())
1690 return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
1691
1692 const ModelObject *that = static_cast<const ModelObject*>(m);
1693 Scope scope(that);
1694 ScopedString name(scope, id.asStringOrSymbol());
1695 QQmlListModel *model = that->d()->m_model;
1696 if (!model)
1697 return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
1698
1699 const ListLayout::Role *role = model->listModel()->getExistingRole(name);
1700 if (!role)
1701 return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
1702 if (hasProperty)
1703 *hasProperty = true;
1704
1705 if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) {
1706 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine);
1707 if (ep && ep->propertyCapture)
1708 ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false);
1709 }
1710
1711 const int elementIndex = that->d()->elementIndex();
1712 QVariant value = model->data(elementIndex, role->index);
1713 return that->engine()->fromVariant(value);
1714}
1715
1716ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
1717{
1718 lookup->call = Lookup::Call::GetterQObjectPropertyFallback;
1719 return lookup->getter(engine, *object);
1720}
1721
1723{
1726 PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
1727
1728};
1729
1730PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
1731{
1732 const ModelObject *that = static_cast<const ModelObject *>(o);
1733
1734 ExecutionEngine *v4 = that->engine();
1735
1736 QQmlListModel *model = that->d()->m_model;
1737 ListModel *listModel = model ? model->listModel() : nullptr;
1738 if (listModel && roleNameIndex < listModel->roleCount()) {
1739 Scope scope(that->engine());
1740 const ListLayout::Role &role = listModel->getExistingRole(roleNameIndex);
1741 ++roleNameIndex;
1742 ScopedString roleName(scope, v4->newString(role.name));
1743 if (attrs)
1744 *attrs = QV4::Attr_Data;
1745 if (pd) {
1746
1747 QVariant value = model->data(that->d()->elementIndex(), role.index);
1748 if (auto recursiveListModel = qvariant_cast<QQmlListModel*>(value)) {
1749 auto size = recursiveListModel->count();
1750 auto array = ScopedArrayObject{scope, v4->newArrayObject(size)};
1751 QV4::ScopedValue val(scope);
1752 for (auto i = 0; i < size; i++) {
1753 val = QJSValuePrivate::convertToReturnedValue(
1754 v4, recursiveListModel->get(i));
1755 array->arrayPut(i, val);
1756 }
1757 pd->value = array;
1758 } else {
1759 pd->value = v4->fromVariant(value);
1760 }
1761 }
1762 return roleName->toPropertyKey();
1763 }
1764
1765 // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add
1766 // unnecessary entries that relate to the roles used. These just create extra work
1767 // later on as they will just be ignored.
1768 return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
1769}
1770
1771OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target)
1772{
1773 *target = *m;
1775}
1776
1778
1779} // namespace QV4
1780
1781DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1782{
1784}
1785
1786DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner)
1787{
1788 DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1789 QList<int> roles;
1790 object->updateValues(obj, roles);
1791 return object;
1792}
1793
1795{
1796 QList<int> changedRoles;
1797 for (int i = 0; i < src->m_meta->count(); ++i) {
1798 const QByteArray &name = src->m_meta->name(i);
1799 QVariant value = src->m_meta->value(i);
1800
1801 QQmlListModel *srcModel = qobject_cast<QQmlListModel *>(value.value<QObject *>());
1802 QQmlListModel *targetModel = qobject_cast<QQmlListModel *>(target->m_meta->value(i).value<QObject *>());
1803
1804 bool modelHasChanges = false;
1805 if (srcModel) {
1806 if (targetModel == nullptr)
1807 targetModel = QQmlListModel::createWithOwner(target->m_owner);
1808
1809 modelHasChanges = QQmlListModel::sync(srcModel, targetModel);
1810
1811 QObject *targetModelObject = targetModel;
1812 value = QVariant::fromValue(targetModelObject);
1813 } else if (targetModel) {
1814 delete targetModel;
1815 }
1816
1817 if (target->setValue(name, value) || modelHasChanges)
1818 changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name));
1819 }
1820 return changedRoles;
1821}
1822
1823void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
1824{
1825 for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) {
1826 const QString &key = it.key();
1827
1828 int roleIndex = m_owner->m_roles.indexOf(key);
1829 if (roleIndex == -1) {
1830 roleIndex = m_owner->m_roles.size();
1831 m_owner->m_roles.append(key);
1832 }
1833
1834 QVariant value = it.value();
1835
1836 // A JS array/object is translated into a (hierarchical) QQmlListModel,
1837 // so translate to a variant map/list first with toVariant().
1838 if (value.userType() == qMetaTypeId<QJSValue>())
1839 value = value.value<QJSValue>().toVariant();
1840
1841 if (value.userType() == QMetaType::QVariantList) {
1842 QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);
1843
1844 QVariantList subArray = value.toList();
1845 QVariantList::const_iterator subIt = subArray.cbegin();
1846 QVariantList::const_iterator subEnd = subArray.cend();
1847 while (subIt != subEnd) {
1848 const QVariantMap &subObject = subIt->toMap();
1849 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1850 ++subIt;
1851 }
1852
1853 QObject *subModelObject = subModel;
1854 value = QVariant::fromValue(subModelObject);
1855 }
1856
1857 const QByteArray &keyUtf8 = key.toUtf8();
1858
1859 QQmlListModel *existingModel = qobject_cast<QQmlListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1860 delete existingModel;
1861
1862 if (m_meta->setValue(keyUtf8, value))
1863 roles << roleIndex;
1864 }
1865}
1866
1871
1873{
1874 for (int i=0 ; i < count() ; ++i) {
1875 QQmlListModel *subModel = qobject_cast<QQmlListModel *>(value(i).value<QObject *>());
1876 delete subModel;
1877 }
1878}
1879
1881{
1882 if (!m_enabled)
1883 return;
1884
1885 QVariant v = value(index);
1886 QQmlListModel *model = qobject_cast<QQmlListModel *>(v.value<QObject *>());
1887 delete model;
1888}
1889
1891{
1892 if (!m_enabled)
1893 return;
1894
1895 QQmlListModel *parentModel = m_owner->m_owner;
1896
1897 QVariant v = value(index);
1898
1899 // A JS array/object is translated into a (hierarchical) QQmlListModel,
1900 // so translate to a variant map/list first with toVariant().
1901 if (v.userType() == qMetaTypeId<QJSValue>())
1902 v= v.value<QJSValue>().toVariant();
1903
1904 if (v.userType() == QMetaType::QVariantList) {
1905 QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);
1906
1907 QVariantList subArray = v.toList();
1908 QVariantList::const_iterator subIt = subArray.cbegin();
1909 QVariantList::const_iterator subEnd = subArray.cend();
1910 while (subIt != subEnd) {
1911 const QVariantMap &subObject = subIt->toMap();
1912 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1913 ++subIt;
1914 }
1915
1916 QObject *subModelObject = subModel;
1917 v = QVariant::fromValue(subModelObject);
1918
1919 setValue(index, v);
1920 }
1921
1922 int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1923 if (elementIndex != -1) {
1924 int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1925 if (roleIndex != -1)
1926 parentModel->emitItemsChanged(elementIndex, 1, QList<int>(1, roleIndex));
1927 }
1928}
1929
1930/*!
1931 \qmltype ListModel
1932 \nativetype QQmlListModel
1933 //! \inherits AbstractListModel
1934 \inqmlmodule QtQml.Models
1935 \ingroup qtquick-models
1936 \brief Defines a free-form list data source.
1937
1938 The ListModel is a simple container of ListElement definitions, each
1939 containing data roles. The contents can be defined dynamically, or
1940 explicitly in QML.
1941
1942 The number of elements in the model can be obtained from its \l count property.
1943 A number of familiar methods are also provided to manipulate the contents of the
1944 model, including append(), insert(), move(), remove() and set(). These methods
1945 accept dictionaries as their arguments; these are translated to ListElement objects
1946 by the model.
1947
1948 Elements can be manipulated via the model using the setProperty() method, which
1949 allows the roles of the specified element to be set and changed.
1950
1951 ListModel inherits from \l{QAbstractListModel} and provides its \l{Q_INVOKABLE}
1952 methods. You can, for example use \l{QAbstractItemModel::index} to retrieve a
1953 \l{QModelIndex} for a row and column.
1954
1955 \section1 Example Usage
1956
1957 The following example shows a ListModel containing three elements, with the roles
1958 "name" and "cost".
1959
1960 \div {class="float-right"}
1961 \inlineimage listmodel.png
1962 {List showing Apple, Orange, and Banana with prices}
1963 \enddiv
1964
1965 \snippet qml/listmodel/listmodel.qml 0
1966
1967 Roles (properties) in each element must begin with a lower-case letter and
1968 should be common to all elements in a model. The ListElement documentation
1969 provides more guidelines for how elements should be defined.
1970
1971 Since the example model contains an \c id property, it can be referenced
1972 by views, such as the ListView in this example:
1973
1974 \snippet qml/listmodel/listmodel-simple.qml 0
1975 \dots 8
1976 \snippet qml/listmodel/listmodel-simple.qml 1
1977
1978 It is possible for roles to contain list data. In the following example we
1979 create a list of fruit attributes:
1980
1981 \snippet qml/listmodel/listmodel-nested.qml model
1982
1983 The delegate displays all the fruit attributes:
1984
1985 \div {class="float-right"}
1986 \inlineimage listmodel-nested.png
1987 {List showing fruits with nested attribute data}
1988 \enddiv
1989
1990 \snippet qml/listmodel/listmodel-nested.qml delegate
1991
1992 \section1 Modifying List Models
1993
1994 The content of a ListModel may be created and modified using the clear(),
1995 append(), set(), insert() and setProperty() methods. For example:
1996
1997 \snippet qml/listmodel/listmodel-modify.qml delegate
1998
1999 Note that when creating content dynamically the set of available properties
2000 cannot be changed once set. Whatever properties are first added to the model
2001 are the only permitted properties in the model.
2002
2003 \section1 Using Threaded List Models with WorkerScript
2004
2005 ListModel can be used together with WorkerScript to access a list model
2006 from multiple threads. This is useful if list modifications are
2007 synchronous and take some time: the list operations can be moved to a
2008 different thread to avoid blocking of the main GUI thread.
2009
2010 Here is an example that uses WorkerScript to periodically append the
2011 current time to a list model:
2012
2013 \snippet qml/listmodel/WorkerScript.qml 0
2014
2015 The included file, \tt dataloader.mjs, looks like this:
2016
2017 \snippet qml/listmodel/dataloader.mjs 0
2018
2019 The timer in the main example sends messages to the worker script by calling
2020 \l WorkerScript::sendMessage(). When this message is received,
2021 \c WorkerScript.onMessage() is invoked in \c dataloader.mjs,
2022 which appends the current time to the list model.
2023
2024 Note the call to sync() from the external thread.
2025 You must call sync() or else the changes made to the list from that
2026 thread will not be reflected in the list model in the main thread.
2027
2028 \sa {qml-data-models}{Data Models}, {Qt Qml}
2029*/
2030
2031QQmlListModel::QQmlListModel(QObject *parent)
2032: QAbstractListModel(parent)
2033{
2034 m_mainThread = true;
2035 m_primary = true;
2036 m_agent = nullptr;
2037 m_dynamicRoles = false;
2038
2039 m_layout = new ListLayout;
2040 m_listModel = new ListModel(m_layout, this);
2041
2042 m_engine = nullptr;
2043}
2044
2045QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent)
2046: QAbstractListModel(parent)
2047{
2048 m_mainThread = owner->m_mainThread;
2049 m_primary = false;
2050 m_agent = owner->m_agent;
2051
2052 Q_ASSERT(owner->m_dynamicRoles == false);
2053 m_dynamicRoles = false;
2054 m_layout = nullptr;
2055 m_listModel = data;
2056
2057 m_engine = engine;
2058 m_compilationUnit = owner->m_compilationUnit;
2059}
2060
2061QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent)
2062: QAbstractListModel(agent)
2063{
2064 m_mainThread = false;
2065 m_primary = true;
2066 m_agent = agent;
2067 m_dynamicRoles = orig->m_dynamicRoles;
2068
2069 if (ListLayout *layout = orig->m_layout)
2070 m_layout = new ListLayout(layout);
2071 else
2072 m_layout = new ListLayout;
2073
2074 m_listModel = new ListModel(m_layout, this);
2075
2076 if (m_dynamicRoles)
2077 sync(orig, this);
2078 else
2079 ListModel::sync(orig->m_listModel, m_listModel);
2080
2081 m_engine = nullptr;
2082 m_compilationUnit = orig->m_compilationUnit;
2083}
2084
2085QQmlListModel::~QQmlListModel()
2086{
2087 qDeleteAll(m_modelObjects);
2088
2089 if (m_primary) {
2090 m_listModel->destroy();
2091 delete m_listModel;
2092
2093 if (m_mainThread && m_agent)
2094 m_agent->modelDestroyed();
2095 }
2096
2097 if (m_mainThread && m_agent)
2098 m_agent->release();
2099
2100 m_listModel = nullptr;
2101
2102 delete m_layout;
2103 m_layout = nullptr;
2104}
2105
2106QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
2107{
2108 QQmlListModel *model = new QQmlListModel;
2109
2110 model->m_mainThread = newOwner->m_mainThread;
2111 model->m_engine = newOwner->m_engine;
2112 model->m_agent = newOwner->m_agent;
2113 model->m_dynamicRoles = newOwner->m_dynamicRoles;
2114
2115 if (model->m_mainThread && model->m_agent)
2116 model->m_agent->addref();
2117
2118 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
2119
2120 return model;
2121}
2122
2123QV4::ExecutionEngine *QQmlListModel::engine() const
2124{
2125 if (m_engine == nullptr) {
2126 m_engine = qmlEngine(this)->handle();
2127 }
2128
2129 return m_engine;
2130}
2131
2132bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target)
2133{
2134 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
2135
2136 bool hasChanges = false;
2137
2138 target->m_roles = src->m_roles;
2139
2140 // Build hash of elements <-> uid for each of the lists
2141 QHash<int, ElementSync> elementHash;
2142 for (int i = 0 ; i < target->m_modelObjects.size(); ++i) {
2143 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
2144 int uid = e->getUid();
2145 ElementSync sync;
2146 sync.target = e;
2147 sync.targetIndex = i;
2148 elementHash.insert(uid, sync);
2149 }
2150 for (int i = 0 ; i < src->m_modelObjects.size(); ++i) {
2151 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
2152 int uid = e->getUid();
2153
2154 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
2155 if (it == elementHash.end()) {
2156 ElementSync sync;
2157 sync.src = e;
2158 sync.srcIndex = i;
2159 elementHash.insert(uid, sync);
2160 } else {
2161 ElementSync &sync = it.value();
2162 sync.src = e;
2163 sync.srcIndex = i;
2164 }
2165 }
2166
2167 // Get list of elements that are in the target but no longer in the source. These get deleted first.
2168 int rowsRemoved = 0;
2169 for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
2170 DynamicRoleModelNode *element = target->m_modelObjects.at(i);
2171 ElementSync &s = elementHash.find(element->getUid()).value();
2172 Q_ASSERT(s.targetIndex >= 0);
2173 // need to update the targetIndex, to keep it correct after removals
2174 s.targetIndex -= rowsRemoved;
2175 if (s.src == nullptr) {
2176 Q_ASSERT(s.targetIndex == i);
2177 hasChanges = true;
2178 target->beginRemoveRows(QModelIndex(), i, i);
2179 target->m_modelObjects.remove(i, 1);
2180 target->endRemoveRows();
2181 delete s.target;
2182 ++rowsRemoved;
2183 --i;
2184 continue;
2185 }
2186 }
2187
2188 // Clear the target list, and append in correct order from the source
2189 target->m_modelObjects.clear();
2190 for (int i = 0 ; i < src->m_modelObjects.size() ; ++i) {
2191 DynamicRoleModelNode *element = src->m_modelObjects.at(i);
2192 ElementSync &s = elementHash.find(element->getUid()).value();
2193 Q_ASSERT(s.srcIndex >= 0);
2194 DynamicRoleModelNode *targetElement = s.target;
2195 if (targetElement == nullptr) {
2196 targetElement = new DynamicRoleModelNode(target, element->getUid());
2197 }
2198 s.changedRoles = DynamicRoleModelNode::sync(element, targetElement);
2199 target->m_modelObjects.append(targetElement);
2200 }
2201
2202 // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
2203 // so the model indices can't be out of bounds
2204 //
2205 // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
2206 // model indices are updated correctly
2207 int rowsInserted = 0;
2208 for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
2209 DynamicRoleModelNode *element = target->m_modelObjects.at(i);
2210 ElementSync &s = elementHash.find(element->getUid()).value();
2211 Q_ASSERT(s.srcIndex >= 0);
2212 s.srcIndex += rowsInserted;
2213 if (s.srcIndex != s.targetIndex) {
2214 if (s.targetIndex == -1) {
2215 target->beginInsertRows(QModelIndex(), i, i);
2216 target->endInsertRows();
2217 } else {
2218 target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
2219 target->endMoveRows();
2220 }
2221 hasChanges = true;
2222 ++rowsInserted;
2223 }
2224 if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
2225 QModelIndex idx = target->createIndex(i, 0);
2226 emit target->dataChanged(idx, idx, s.changedRoles);
2227 hasChanges = true;
2228 }
2229 }
2230 return hasChanges;
2231}
2232
2233void QQmlListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
2234{
2235 if (count <= 0)
2236 return;
2237
2238 if (m_mainThread)
2239 emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
2240}
2241
2242void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
2243{
2244 Q_ASSERT(index >= 0 && count >= 0);
2245 if (m_mainThread)
2246 beginInsertRows(QModelIndex(), index, index + count - 1);
2247}
2248
2249void QQmlListModel::emitItemsInserted()
2250{
2251 if (m_mainThread) {
2252 endInsertRows();
2253 emit countChanged();
2254 }
2255}
2256
2257QQmlListModelWorkerAgent *QQmlListModel::agent()
2258{
2259 if (m_agent)
2260 return m_agent;
2261
2262 m_agent = new QQmlListModelWorkerAgent(this);
2263 return m_agent;
2264}
2265
2266QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const
2267{
2268 return row >= 0 && row < count() && column == 0 && !parent.isValid()
2269 ? createIndex(row, column)
2270 : QModelIndex();
2271}
2272
2273int QQmlListModel::rowCount(const QModelIndex &parent) const
2274{
2275 return !parent.isValid() ? count() : 0;
2276}
2277
2278QVariant QQmlListModel::data(const QModelIndex &index, int role) const
2279{
2280 return data(index.row(), role);
2281}
2282
2283bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
2284{
2285 const int row = index.row();
2286 if (row >= count() || row < 0)
2287 return false;
2288
2289 if (m_dynamicRoles) {
2290 const QByteArray property = m_roles.at(role).toUtf8();
2291 if (m_modelObjects[row]->setValue(property, value)) {
2292 emitItemsChanged(row, 1, QList<int>(1, role));
2293 return true;
2294 }
2295 } else {
2296 const ListLayout::Role &r = m_listModel->getExistingRole(role);
2297 const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value);
2298 if (roleIndex != -1) {
2299 emitItemsChanged(row, 1, QList<int>(1, role));
2300 return true;
2301 }
2302 }
2303
2304 return false;
2305}
2306
2307QVariant QQmlListModel::data(int index, int role) const
2308{
2309 QVariant v;
2310
2311 if (index >= count() || index < 0)
2312 return v;
2313
2314 if (m_dynamicRoles)
2315 v = m_modelObjects[index]->getValue(m_roles[role]);
2316 else
2317 v = m_listModel->getProperty(index, role, this, engine());
2318
2319 return v;
2320}
2321
2322QHash<int, QByteArray> QQmlListModel::roleNames() const
2323{
2324 QHash<int, QByteArray> roleNames;
2325
2326 if (m_dynamicRoles) {
2327 const auto size = m_roles.size();
2328 roleNames.reserve(size);
2329 for (int i = 0 ; i < size ; ++i)
2330 roleNames.insert(i, m_roles.at(i).toUtf8());
2331 } else {
2332 const auto size = m_listModel->roleCount();
2333 roleNames.reserve(size);
2334 for (int i = 0 ; i < size; ++i) {
2335 const ListLayout::Role &r = m_listModel->getExistingRole(i);
2336 roleNames.insert(i, r.name.toUtf8());
2337 }
2338 }
2339
2340 return roleNames;
2341}
2342
2343/*!
2344 \qmlproperty bool ListModel::dynamicRoles
2345
2346 By default, the type of a role is fixed the first time
2347 the role is used. For example, if you create a role called
2348 "data" and assign a number to it, you can no longer assign
2349 a string to the "data" role. However, when the dynamicRoles
2350 property is enabled, the type of a given role is not fixed
2351 and can be different between elements.
2352
2353 The dynamicRoles property must be set before any data is
2354 added to the ListModel, and must be set from the main
2355 thread.
2356
2357 A ListModel that has data statically defined (via the
2358 ListElement QML syntax) cannot have the dynamicRoles
2359 property enabled.
2360
2361 There is a significant performance cost to using a
2362 ListModel with dynamic roles enabled. The cost varies
2363 from platform to platform but is typically somewhere
2364 between 4-6x slower than using static role types.
2365
2366 Due to the performance cost of using dynamic roles,
2367 they are disabled by default.
2368*/
2369void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
2370{
2371 if (m_mainThread && m_agent == nullptr) {
2372 if (enableDynamicRoles) {
2373 if (m_layout && m_layout->roleCount())
2374 qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
2375 else
2376 m_dynamicRoles = true;
2377 } else {
2378 if (m_roles.size()) {
2379 qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
2380 } else {
2381 m_dynamicRoles = false;
2382 }
2383 }
2384 } else {
2385 qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
2386 }
2387}
2388
2389/*!
2390 \qmlproperty int ListModel::count
2391 The number of data entries in the model.
2392*/
2393int QQmlListModel::count() const
2394{
2395 return m_dynamicRoles ? m_modelObjects.size() : m_listModel->elementCount();
2396}
2397
2398/*!
2399 \qmlmethod void ListModel::clear()
2400
2401 Deletes all content from the model. In particular this invalidates all objects you may have
2402 retrieved using \l get().
2403
2404 \sa append(), remove(), get()
2405*/
2406void QQmlListModel::clear()
2407{
2408 removeElements(0, count());
2409}
2410
2411/*!
2412 \qmlmethod void ListModel::remove(int index, int count = 1)
2413
2414 Deletes \a count number of items at \a index from the model.
2415
2416 \sa clear()
2417*/
2418void QQmlListModel::remove(QQmlV4FunctionPtr args)
2419{
2420 int argLength = args->length();
2421
2422 if (argLength == 1 || argLength == 2) {
2423 QV4::Scope scope(args->v4engine());
2424 int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
2425 int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);
2426
2427 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
2428 qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
2429 return;
2430 }
2431
2432 removeElements(index, removeCount);
2433 } else {
2434 qmlWarning(this) << tr("remove: incorrect number of arguments");
2435 }
2436}
2437
2438void QQmlListModel::removeElements(int index, int removeCount)
2439{
2440 Q_ASSERT(index >= 0 && removeCount >= 0);
2441
2442 if (!removeCount)
2443 return;
2444
2445 if (m_mainThread)
2446 beginRemoveRows(QModelIndex(), index, index + removeCount - 1);
2447
2448 QList<std::function<void()>> toDestroy;
2449 if (m_dynamicRoles) {
2450 for (int i=0 ; i < removeCount ; ++i) {
2451 auto modelObject = m_modelObjects[index+i];
2452 toDestroy.append([modelObject](){
2453 delete modelObject;
2454 });
2455 }
2456 m_modelObjects.remove(index, removeCount);
2457 } else {
2458 toDestroy = m_listModel->remove(index, removeCount);
2459 }
2460
2461 if (m_mainThread) {
2462 endRemoveRows();
2463 emit countChanged();
2464 }
2465 for (const auto &destroyer : std::as_const(toDestroy))
2466 destroyer();
2467}
2468
2469void QQmlListModel::updateTranslations()
2470{
2471 // assumption: it is impossible to have retranslatable strings in a
2472 // dynamic list model, as they would already have "decayed" to strings
2473 // when they were inserted
2474 if (m_dynamicRoles)
2475 return;
2476 Q_ASSERT(m_listModel);
2477
2478 QList<int> roles;
2479 for (int i = 0, end = m_listModel->roleCount(); i != end; ++i) {
2480 if (m_listModel->getExistingRole(i).type == ListLayout::Role::String)
2481 roles.append(i);
2482 }
2483
2484 if (!roles.isEmpty())
2485 emitItemsChanged(0, rowCount(QModelIndex()), roles);
2486
2487 m_listModel->updateTranslations();
2488}
2489
2490/*!
2491 \qmlmethod void ListModel::insert(int index, jsobject dict)
2492
2493 Adds a new item to the list model at position \a index, with the
2494 values in \a dict.
2495
2496 \code
2497 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
2498 \endcode
2499
2500 The \a index must be to an existing item in the list, or one past
2501 the end of the list (equivalent to append).
2502
2503 \sa set(), append()
2504*/
2505
2506void QQmlListModel::insert(QQmlV4FunctionPtr args)
2507{
2508 if (args->length() == 2) {
2509 QV4::Scope scope(args->v4engine());
2510 QV4::ScopedValue arg0(scope, (*args)[0]);
2511 int index = arg0->toInt32();
2512
2513 if (index < 0 || index > count()) {
2514 qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
2515 return;
2516 }
2517
2518 QV4::ScopedObject argObject(scope, (*args)[1]);
2519 QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
2520 if (objectArray) {
2521 QV4::ScopedObject argObject(scope);
2522
2523 int objectArrayLength = objectArray->getLength();
2524 emitItemsAboutToBeInserted(index, objectArrayLength);
2525 for (int i=0 ; i < objectArrayLength ; ++i) {
2526 argObject = objectArray->get(i);
2527
2528 if (m_dynamicRoles) {
2529 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2530 } else {
2531 m_listModel->insert(index+i, argObject);
2532 }
2533 }
2534 emitItemsInserted();
2535 } else if (argObject) {
2536 emitItemsAboutToBeInserted(index, 1);
2537
2538 if (m_dynamicRoles) {
2539 m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2540 } else {
2541 m_listModel->insert(index, argObject);
2542 }
2543
2544 emitItemsInserted();
2545 } else {
2546 qmlWarning(this) << tr("insert: value is not an object");
2547 }
2548 } else {
2549 qmlWarning(this) << tr("insert: value is not an object");
2550 }
2551}
2552
2553/*!
2554 \qmlmethod void ListModel::move(int from, int to, int n)
2555
2556 Moves \a n items \a from one position \a to another.
2557
2558 The from and to ranges must exist; for example, to move the first 3 items
2559 to the end of the list:
2560
2561 \code
2562 fruitModel.move(0, fruitModel.count - 3, 3)
2563 \endcode
2564
2565 \sa append()
2566*/
2567void QQmlListModel::move(int from, int to, int n)
2568{
2569 if (n == 0 || from == to)
2570 return;
2571 if (!canMove(from, to, n)) {
2572 qmlWarning(this) << tr("move: out of range");
2573 return;
2574 }
2575
2576 if (m_mainThread)
2577 beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
2578
2579 if (m_dynamicRoles) {
2580
2581 int realFrom = from;
2582 int realTo = to;
2583 int realN = n;
2584
2585 if (from > to) {
2586 // Only move forwards - flip if backwards moving
2587 int tfrom = from;
2588 int tto = to;
2589 realFrom = tto;
2590 realTo = tto+n;
2591 realN = tfrom-tto;
2592 }
2593
2594 QPODVector<DynamicRoleModelNode *, 4> store;
2595 for (int i=0 ; i < (realTo-realFrom) ; ++i)
2596 store.append(m_modelObjects[realFrom+realN+i]);
2597 for (int i=0 ; i < realN ; ++i)
2598 store.append(m_modelObjects[realFrom+i]);
2599 for (int i=0 ; i < store.count() ; ++i)
2600 m_modelObjects[realFrom+i] = store[i];
2601
2602 } else {
2603 m_listModel->move(from, to, n);
2604 }
2605
2606 if (m_mainThread)
2607 endMoveRows();
2608}
2609
2610/*!
2611 \qmlmethod void ListModel::append(jsobject dict)
2612
2613 Adds a new item to the end of the list model, with the
2614 values in \a dict.
2615
2616 \code
2617 fruitModel.append({"cost": 5.95, "name":"Pizza"})
2618 \endcode
2619
2620 \sa set(), remove()
2621*/
2622void QQmlListModel::append(QQmlV4FunctionPtr args)
2623{
2624 if (args->length() == 1) {
2625 QV4::Scope scope(args->v4engine());
2626 QV4::ScopedObject argObject(scope, (*args)[0]);
2627 QV4::ScopedArrayObject objectArray(scope, (*args)[0]);
2628
2629 if (objectArray) {
2630 QV4::ScopedObject argObject(scope);
2631
2632 int objectArrayLength = objectArray->getLength();
2633 if (objectArrayLength > 0) {
2634 int index = count();
2635 emitItemsAboutToBeInserted(index, objectArrayLength);
2636
2637 for (int i=0 ; i < objectArrayLength ; ++i) {
2638 argObject = objectArray->get(i);
2639
2640 if (m_dynamicRoles) {
2641 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2642 } else {
2643 m_listModel->append(argObject);
2644 }
2645 }
2646
2647 emitItemsInserted();
2648 }
2649 } else if (argObject) {
2650 int index;
2651
2652 if (m_dynamicRoles) {
2653 index = m_modelObjects.size();
2654 emitItemsAboutToBeInserted(index, 1);
2655 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2656 } else {
2657 index = m_listModel->elementCount();
2658 emitItemsAboutToBeInserted(index, 1);
2659 m_listModel->append(argObject);
2660 }
2661
2662 emitItemsInserted();
2663 } else {
2664 qmlWarning(this) << tr("append: value is not an object");
2665 }
2666 } else {
2667 qmlWarning(this) << tr("append: value is not an object");
2668 }
2669}
2670
2671/*!
2672 \qmlmethod object ListModel::get(int index)
2673
2674 Returns the item at \a index in the list model. This allows the item
2675 data to be accessed or modified from JavaScript:
2676
2677 \code
2678 Component.onCompleted: {
2679 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2680 console.log(fruitModel.get(0).cost);
2681 fruitModel.get(0).cost = 10.95;
2682 }
2683 \endcode
2684
2685 The \a index must be an element in the list.
2686
2687 Note that properties of the returned object that are themselves objects
2688 will also be models, and this get() method is used to access elements:
2689
2690 \code
2691 fruitModel.append(..., "attributes":
2692 [{"name":"spikes","value":"7mm"},
2693 {"name":"color","value":"green"}]);
2694 fruitModel.get(0).attributes.get(1).value; // == "green"
2695 \endcode
2696
2697 \warning The returned object is not guaranteed to remain valid. It
2698 should not be used in \l{Property Binding}{property bindings} or for
2699 storing data across modifications of its origin ListModel.
2700
2701 \sa append(), clear()
2702*/
2703QJSValue QQmlListModel::get(int index) const
2704{
2705 QV4::Scope scope(engine());
2706 QV4::ScopedValue result(scope, QV4::Value::undefinedValue());
2707
2708 if (index >= 0 && index < count()) {
2709
2710 if (m_dynamicRoles) {
2711 DynamicRoleModelNode *object = m_modelObjects[index];
2712 result = QV4::QObjectWrapper::wrap(scope.engine, object);
2713 } else {
2714 QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
2715 QQmlData *ddata = QQmlData::get(object);
2716 if (ddata->jsWrapper.isNullOrUndefined()) {
2717 result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
2718 // Keep track of the QObjectWrapper in persistent value storage
2719 ddata->jsWrapper.set(scope.engine, result);
2720 } else {
2721 result = ddata->jsWrapper.value();
2722 }
2723 }
2724 }
2725
2726 return QJSValuePrivate::fromReturnedValue(result->asReturnedValue());
2727}
2728
2729/*!
2730 \qmlmethod void ListModel::set(int index, jsobject dict)
2731
2732 Changes the item at \a index in the list model with the
2733 values in \a dict. Properties not appearing in \a dict
2734 are left unchanged.
2735
2736 \code
2737 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2738 \endcode
2739
2740 If \a index is equal to count() then a new item is appended to the
2741 list. Otherwise, \a index must be an element in the list.
2742
2743 \sa append()
2744*/
2745void QQmlListModel::set(int index, const QJSValue &value)
2746{
2747 QV4::Scope scope(engine());
2748 QV4::ScopedObject object(scope, QJSValuePrivate::asReturnedValue(&value));
2749
2750 if (!object) {
2751 qmlWarning(this) << tr("set: value is not an object");
2752 return;
2753 }
2754 if (index > count() || index < 0) {
2755 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2756 return;
2757 }
2758
2759
2760 if (index == count()) {
2761 emitItemsAboutToBeInserted(index, 1);
2762
2763 if (m_dynamicRoles) {
2764 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
2765 } else {
2766 m_listModel->insert(index, object);
2767 }
2768
2769 emitItemsInserted();
2770 } else {
2771
2772 QList<int> roles;
2773
2774 if (m_dynamicRoles) {
2775 m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
2776 } else {
2777 m_listModel->set(index, object, &roles);
2778 }
2779
2780 if (roles.size())
2781 emitItemsChanged(index, 1, roles);
2782 }
2783}
2784
2785/*!
2786 \qmlmethod void ListModel::setProperty(int index, string property, var value)
2787
2788 Changes the \a property of the item at \a index in the list model to \a value.
2789
2790 \code
2791 fruitModel.setProperty(3, "cost", 5.95)
2792 \endcode
2793
2794 The \a index must be an element in the list.
2795
2796 \sa append()
2797*/
2798void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
2799{
2800 if (count() == 0 || index >= count() || index < 0) {
2801 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2802 return;
2803 }
2804
2805 if (m_dynamicRoles) {
2806 int roleIndex = m_roles.indexOf(property);
2807 if (roleIndex == -1) {
2808 roleIndex = m_roles.size();
2809 m_roles.append(property);
2810 }
2811 if (m_modelObjects[index]->setValue(property.toUtf8(), value))
2812 emitItemsChanged(index, 1, QList<int>(1, roleIndex));
2813 } else {
2814 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2815 if (roleIndex != -1)
2816 emitItemsChanged(index, 1, QList<int>(1, roleIndex));
2817 }
2818}
2819
2820/*!
2821 \qmlmethod void ListModel::sync()
2822
2823 Writes any unsaved changes to the list model after it has been modified
2824 from a worker script.
2825*/
2826void QQmlListModel::sync()
2827{
2828 // This is just a dummy method to make it look like sync() exists in
2829 // ListModel (and not just QQmlListModelWorkerAgent) and to let
2830 // us document sync().
2831 qmlWarning(this) << "List sync() can only be called from a WorkerScript";
2832}
2833
2834bool QQmlListModelParser::verifyProperty(
2835 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
2836 const QV4::CompiledData::Binding *binding)
2837{
2838 if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
2839 const quint32 targetObjectIndex = binding->value.objectIndex;
2840 const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2841 QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
2842 if (objName != listElementTypeName) {
2843 const QMetaObject *mo = resolveType(objName);
2844 if (mo != &QQmlListElement::staticMetaObject) {
2845 error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2846 return false;
2847 }
2848 listElementTypeName = objName; // cache right name for next time
2849 }
2850
2851 if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
2852 error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
2853 return false;
2854 }
2855
2856 const QV4::CompiledData::Binding *binding = target->bindingTable();
2857 for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
2858 QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2859 if (propName.isEmpty()) {
2860 error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2861 return false;
2862 }
2863 if (!verifyProperty(compilationUnit, binding))
2864 return false;
2865 }
2866 } else if (binding->type() == QV4::CompiledData::Binding::Type_Script) {
2867 QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2868 if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
2869 bool ok;
2870 evaluateEnum(scriptStr, &ok);
2871 if (!ok) {
2872 error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
2873 return false;
2874 }
2875 }
2876 }
2877
2878 return true;
2879}
2880
2881bool QQmlListModelParser::applyProperty(
2882 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
2883 const QV4::CompiledData::Binding *binding, ListModel *model, QQmlListModel *owner,
2884 int outterElementIndex)
2885{
2886 const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
2887
2888 bool roleSet = false;
2889 const QV4::CompiledData::Binding::Type bindingType = binding->type();
2890 if (bindingType >= QV4::CompiledData::Binding::Type_Object) {
2891 const quint32 targetObjectIndex = binding->value.objectIndex;
2892 const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2893
2894 ListModel *subModel = nullptr;
2895 if (outterElementIndex == -1) {
2896 subModel = model;
2897 } else {
2898 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2899 if (role.type == ListLayout::Role::List) {
2900 subModel = model->getListProperty(outterElementIndex, role);
2901 if (subModel == nullptr) {
2902 subModel = new ListModel(role.subLayout, nullptr);
2903 model->setOrCreateProperty(
2904 outterElementIndex, elementName,
2905 QVariant::fromValue(subModel));
2906 }
2907 }
2908 }
2909
2910 int elementIndex = subModel ? subModel->appendElement() : -1;
2911
2912 const QV4::CompiledData::Binding *subBinding = target->bindingTable();
2913 for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
2914 roleSet |= applyProperty(compilationUnit, subBinding, subModel, owner, elementIndex);
2915 }
2916 } else if (!model) {
2917 return false;
2918 } else {
2919 QVariant value;
2920
2921 const bool isTranslationBinding = binding->isTranslationBinding();
2922 if (isTranslationBinding) {
2923 value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
2924 } else if (binding->evaluatesToString()) {
2925 value = compilationUnit->bindingValueAsString(binding);
2926 } else if (bindingType == QV4::CompiledData::Binding::Type_Number) {
2927 value = compilationUnit->bindingValueAsNumber(binding);
2928 } else if (bindingType == QV4::CompiledData::Binding::Type_Boolean) {
2929 value = binding->valueAsBoolean();
2930 } else if (bindingType == QV4::CompiledData::Binding::Type_Null) {
2931 value = QVariant::fromValue(nullptr);
2932 } else if (bindingType == QV4::CompiledData::Binding::Type_Script) {
2933 QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2934 if (definesEmptyList(scriptStr)) {
2935 value = QVariant::fromValue(
2936 new ListModel(model->getOrCreateListRole(elementName).subLayout, nullptr));
2937 } else if (binding->isFunctionExpression()) {
2938 QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
2939 Q_ASSERT(id != QQmlBinding::Invalid);
2940
2941 auto v4 = compilationUnit->engine;
2942 QV4::Scope scope(v4);
2943
2944 if (model->m_modelCache == nullptr) {
2945 model->m_modelCache = new QQmlListModel(owner, model, v4);
2946 QQmlEngine::setContextForObject(
2947 model->m_modelCache, QQmlEngine::contextForObject(owner));
2948 }
2949
2950 // for now we do not provide a context object; data from the ListElement must be passed to the function
2951 QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
2952 QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
2953
2954 QJSValue v;
2955 QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0));
2956 if (v4->hasException)
2957 v4->catchException();
2958 else
2959 QJSValuePrivate::setValue(&v, result);
2960 value.setValue(v);
2961 } else {
2962 bool ok;
2963 value = evaluateEnum(scriptStr, &ok);
2964 }
2965 } else {
2966 Q_UNREACHABLE();
2967 }
2968
2969 model->setOrCreateProperty(outterElementIndex, elementName, value);
2970 auto listModel = model->m_modelCache;
2971 if (isTranslationBinding && listModel) {
2972 if (!listModel->translationChangeHandler) {
2973 auto ep = QQmlEnginePrivate::get(compilationUnit->engine);
2974 model->m_modelCache->translationChangeHandler = std::make_unique<QPropertyNotifier>(
2975 ep->translationLanguage.addNotifier([listModel](){ listModel->updateTranslations(); }));
2976 }
2977 }
2978 roleSet = true;
2979 }
2980 return roleSet;
2981}
2982
2984 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
2985 const QList<const QV4::CompiledData::Binding *> &bindings)
2986{
2987 listElementTypeName = QString(); // unknown
2988
2989 for (const QV4::CompiledData::Binding *binding : bindings) {
2990 QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2991 if (!propName.isEmpty()) { // isn't default property
2992 error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
2993 return;
2994 }
2995 if (!verifyProperty(compilationUnit, binding))
2996 return;
2997 }
2998}
2999
3000void QQmlListModelParser::applyBindings(
3001 QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
3002 const QList<const QV4::CompiledData::Binding *> &bindings)
3003{
3004 QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
3005
3006 rv->m_engine = qmlEngine(rv)->handle();
3007 rv->m_compilationUnit = compilationUnit;
3008
3009 bool setRoles = false;
3010
3011 for (const QV4::CompiledData::Binding *binding : bindings) {
3012 if (binding->type() != QV4::CompiledData::Binding::Type_Object)
3013 continue;
3014 setRoles |= applyProperty(
3015 compilationUnit, binding, rv->m_listModel, rv, /*outter element index*/-1);
3016 }
3017
3018 if (setRoles == false)
3019 qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
3020}
3021
3022bool QQmlListModelParser::definesEmptyList(QStringView s)
3023{
3024 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
3025 for (auto c : s.sliced(1).chopped(1)) {
3026 if (!c.isSpace())
3027 return false;
3028 }
3029 return true;
3030 }
3031 return false;
3032}
3033
3034
3035/*!
3036 \qmltype ListElement
3037 \nativetype QQmlListElement
3038 \inqmlmodule QtQml.Models
3039 \brief Defines a data item in a ListModel.
3040 \ingroup qtquick-models
3041
3042 List elements are defined inside ListModel definitions, and represent items in a
3043 list that will be displayed using ListView or \l Repeater items.
3044
3045 List elements are defined like other QML elements except that they contain
3046 a collection of \e role definitions instead of properties. Using the same
3047 syntax as property definitions, roles both define how the data is accessed
3048 and include the data itself.
3049
3050 The names used for roles must begin with a lower-case letter and should be
3051 common to all elements in a given model. Values must be simple constants; either
3052 strings (quoted and optionally within a call to
3053 \l [QML] {Qt::} {QT_TR_NOOP()}, boolean values (true, false), numbers, or
3054 enumeration values (such as AlignText.AlignHCenter).
3055
3056 Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
3057 a role. This allows the definition of ListElements with callable actions.
3058
3059 \section1 Referencing Roles
3060
3061 The role names are used by delegates to obtain data from list elements.
3062 Each role name is accessible in the delegate's scope, and refers to the
3063 corresponding role in the current element. Where a role name would be
3064 ambiguous to use, it can be accessed via the \l{ListView::}{model}
3065 property (e.g., \c{model.cost} instead of \c{cost}).
3066
3067 \section1 Example Usage
3068
3069 The following model defines a series of list elements, each of which
3070 contain "name" and "cost" roles and their associated values.
3071
3072 \snippet qml/listmodel/listelements.qml model
3073
3074 The delegate obtains the name and cost for each element by simply referring
3075 to \c name and \c cost:
3076
3077 \snippet qml/listmodel/listelements.qml view
3078
3079 \sa ListModel
3080*/
3081
3082QT_END_NAMESPACE
3083
3084#include "moc_qqmllistmodel_p_p.cpp"
3085
3086#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)