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::DataPointer dataPointer = s.data_ptr();
275 arrayData = dataPointer.d_ptr();
276 stringData = dataPointer.data();
277 stringSize = dataPointer.size;
278 if (arrayData)
279 arrayData->ref();
280}
281
282void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding)
283{
284 clear();
285 this->binding = binding;
286}
287
288QString StringOrTranslation::toString(const QQmlListModel *owner) const
289{
290 if (stringSize) {
291 if (arrayData)
292 arrayData->ref();
293 return QString(QStringPrivate(arrayData, stringData, stringSize));
294 }
295 if (!owner)
296 return QString();
297 return owner->m_compilationUnit->bindingValueAsString(binding);
298}
299
301{
302 if (!arrayData)
303 return QString();
304 arrayData->ref();
305 return QString(QStringPrivate(arrayData, stringData, stringSize));
306}
307
308void StringOrTranslation::clear()
309{
310 if (arrayData && !arrayData->deref())
311 QTypedArrayData<ushort>::deallocate(arrayData);
312 arrayData = nullptr;
313 stringData = nullptr;
314 stringSize = 0;
315 binding = nullptr;
316}
317
318QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex)
319{
320 ListElement *e = elements[elementIndex];
321 if (e->m_objectCache == nullptr) {
322 void *memory = operator new(sizeof(QObject) + sizeof(QQmlData));
323 void *ddataMemory = ((char *)memory) + sizeof(QObject);
324 e->m_objectCache = new (memory) QObject;
325
326 const QAbstractDeclarativeData *old = std::exchange(
327 QObjectPrivate::get(e->m_objectCache)->declarativeData,
328 new (ddataMemory) QQmlData(QQmlData::DoesNotOwnMemory));
329 Q_ASSERT(!old); // QObject should really not manipulate QQmlData
330
331 (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
332 }
333 return e->m_objectCache;
334}
335
336bool ListModel::sync(ListModel *src, ListModel *target)
337{
338 // Sanity check
339
340 bool hasChanges = false;
341
342 // Build hash of elements <-> uid for each of the lists
343 QHash<int, ElementSync> elementHash;
344 for (int i = 0; i < target->elements.count(); ++i) {
345 ListElement *e = target->elements.at(i);
346 int uid = e->getUid();
347 ElementSync sync;
348 sync.target = e;
349 sync.targetIndex = i;
350 elementHash.insert(uid, sync);
351 }
352 for (int i = 0; i < src->elements.count(); ++i) {
353 ListElement *e = src->elements.at(i);
354 int uid = e->getUid();
355
356 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
357 if (it == elementHash.end()) {
358 ElementSync sync;
359 sync.src = e;
360 sync.srcIndex = i;
361 elementHash.insert(uid, sync);
362 } else {
363 ElementSync &sync = it.value();
364 sync.src = e;
365 sync.srcIndex = i;
366 }
367 }
368
369 QQmlListModel *targetModel = target->m_modelCache;
370
371 // Get list of elements that are in the target but no longer in the source. These get deleted first.
372 int rowsRemoved = 0;
373 for (int i = 0 ; i < target->elements.count() ; ++i) {
374 ListElement *element = target->elements.at(i);
375 ElementSync &s = elementHash.find(element->getUid()).value();
376 Q_ASSERT(s.targetIndex >= 0);
377 // need to update the targetIndex, to keep it correct after removals
378 s.targetIndex -= rowsRemoved;
379 if (s.src == nullptr) {
380 Q_ASSERT(s.targetIndex == i);
381 hasChanges = true;
382 if (targetModel)
383 targetModel->beginRemoveRows(QModelIndex(), i, i);
384 s.target->destroy(target->m_layout);
385 target->elements.removeOne(s.target);
386 delete s.target;
387 if (targetModel)
388 targetModel->endRemoveRows();
389 ++rowsRemoved;
390 --i;
391 continue;
392 }
393 }
394
395 // Sync the layouts
396 ListLayout::sync(src->m_layout, target->m_layout);
397
398 // Clear the target list, and append in correct order from the source
399 target->elements.clear();
400 for (int i = 0; i < src->elements.count(); ++i) {
401 ListElement *srcElement = src->elements.at(i);
402 ElementSync &s = elementHash.find(srcElement->getUid()).value();
403 Q_ASSERT(s.srcIndex >= 0);
404 ListElement *targetElement = s.target;
405 if (targetElement == nullptr) {
406 targetElement = new ListElement(srcElement->getUid());
407 }
408 s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout);
409 target->elements.append(targetElement);
410 }
411
412 target->updateCacheIndices();
413
414 // Update values stored in target meta objects
415 for (int i=0 ; i < target->elements.count() ; ++i) {
416 ListElement *e = target->elements[i];
417 if (ModelNodeMetaObject *mo = e->objectCache())
419 }
420
421 // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
422 // so the model indices can't be out of bounds
423 //
424 // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
425 // model indices are updated correctly
426 int rowsInserted = 0;
427 const int targetElementCount = target->elements.count();
428 for (int i = 0 ; i < targetElementCount ; ++i) {
429 ListElement *element = target->elements.at(i);
430 ElementSync &s = elementHash.find(element->getUid()).value();
431 Q_ASSERT(s.srcIndex >= 0);
432 s.srcIndex += rowsInserted;
433 if (s.srcIndex != s.targetIndex) {
434 if (targetModel) {
435 if (s.targetIndex == -1) {
436 targetModel->beginInsertRows(QModelIndex(), i, i);
437 targetModel->endInsertRows();
438 ++rowsInserted;
439 } else {
440 bool validMove = targetModel->beginMoveRows(QModelIndex(), s.targetIndex, s.targetIndex, QModelIndex(), i);
441 Q_ASSERT(validMove);
442 targetModel->endMoveRows();
443 // fixup target indices of elements that still need to move
444 for (int j=i+1; j < targetElementCount; ++j) {
445 ListElement *eToFix = target->elements.at(j);
446 ElementSync &sToFix = elementHash.find(eToFix->getUid()).value();
447 if (i < s.targetIndex) {
448 // element was moved down
449 if (sToFix.targetIndex > s.targetIndex || sToFix.targetIndex < i)
450 continue; // unaffected by reordering
451 else
452 sToFix.targetIndex += 1;
453 } else {
454 // element was moved up
455 if (sToFix.targetIndex < s.targetIndex || sToFix.targetIndex > i)
456 continue; // unaffected by reordering
457 else
458 sToFix.targetIndex -= 1;
459 }
460 }
461 }
462 }
463 hasChanges = true;
464 }
465 if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
466 QModelIndex idx = targetModel->createIndex(i, 0);
467 if (targetModel)
468 targetModel->dataChanged(idx, idx, s.changedRoles);
469 hasChanges = true;
470 }
471 }
472 return hasChanges;
473}
474
475ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache)
476{
477}
478
480{
481 for (const auto &destroyer : remove(0, elements.count()))
482 destroyer();
483
484 m_layout = nullptr;
485 if (m_modelCache && m_modelCache->m_primary == false)
486 delete m_modelCache;
487 m_modelCache = nullptr;
488}
489
491{
492 int elementIndex = elements.count();
493 newElement(elementIndex);
494 return elementIndex;
495}
496
497void ListModel::insertElement(int index)
498{
499 newElement(index);
500 updateCacheIndices(index);
501}
502
503void ListModel::move(int from, int to, int n)
504{
505 if (from > to) {
506 // Only move forwards - flip if backwards moving
507 int tfrom = from;
508 int tto = to;
509 from = tto;
510 to = tto+n;
511 n = tfrom-tto;
512 }
513
514 QPODVector<ListElement *, 4> store;
515 for (int i=0 ; i < (to-from) ; ++i)
516 store.append(elements[from+n+i]);
517 for (int i=0 ; i < n ; ++i)
518 store.append(elements[from+i]);
519 for (int i=0 ; i < store.count() ; ++i)
520 elements[from+i] = store[i];
521
522 updateCacheIndices(from, to + n);
523}
524
525void ListModel::newElement(int index)
526{
527 ListElement *e = new ListElement;
528 elements.insert(index, e);
529}
530
531void ListModel::updateCacheIndices(int start, int end)
532{
533 int count = elements.count();
534
535 if (end < 0 || end > count)
536 end = count;
537
538 for (int i = start; i < end; ++i) {
539 ListElement *e = elements.at(i);
540 if (ModelNodeMetaObject *mo = e->objectCache())
541 mo->m_elementIndex = i;
542 }
543}
544
545QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
546{
547 if (roleIndex >= m_layout->roleCount())
548 return QVariant();
549 ListElement *e = elements[elementIndex];
550 const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
551 return e->getProperty(r, owner, eng);
552}
553
554ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
555{
556 ListElement *e = elements[elementIndex];
557 return e->getListProperty(role);
558}
559
561{
562 for (int index = 0; index != elements.count(); ++index) {
563 ListElement *e = elements[index];
564 if (ModelNodeMetaObject *cache = e->objectCache()) {
565 // TODO: more fine grained tracking?
566 cache->updateValues();
567 }
568 }
569}
570
571void ListModel::set(int elementIndex, QV4::Object *object, QList<int> *roles)
572{
573 ListElement *e = elements[elementIndex];
574
575 QV4::ExecutionEngine *v4 = object->engine();
576 QV4::Scope scope(v4);
577 QV4::ScopedObject o(scope);
578
579 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
580 QV4::ScopedString propertyName(scope);
581 QV4::ScopedValue propertyValue(scope);
582 while (1) {
583 propertyName = it.nextPropertyNameAsString(propertyValue);
584 if (!propertyName)
585 break;
586
587 // Check if this key exists yet
588 int roleIndex = -1;
589
590 // Add the value now
591 if (const QV4::String *s = propertyValue->as<QV4::String>()) {
592 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
593 roleIndex = e->setStringProperty(r, s->toQString());
594 } else if (propertyValue->isNumber()) {
595 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
596 roleIndex = e->setDoubleProperty(r, propertyValue->asDouble());
597 } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
598 roleIndex = setArrayLike(&o, propertyName, e, a);
599 } else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
600 roleIndex = setArrayLike(&o, propertyName, e, s);
601 } else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
602 roleIndex = setArrayLike(&o, propertyName, e, l);
603 } else if (propertyValue->isBoolean()) {
604 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
605 roleIndex = e->setBoolProperty(r, propertyValue->booleanValue());
606 } else if (QV4::DateObject *dd = propertyValue->as<QV4::DateObject>()) {
607 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
608 QDateTime dt = dd->toQDateTime();
609 roleIndex = e->setDateTimeProperty(r, dt);
610 } else if (QV4::UrlObject *url = propertyValue->as<QV4::UrlObject>()) {
611 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
612 QUrl qurl = QUrl(url->href());
613 roleIndex = e->setUrlProperty(r, qurl);
614 } else if (QV4::FunctionObject *f = propertyValue->as<QV4::FunctionObject>()) {
615 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function);
616 QV4::ScopedFunctionObject func(scope, f);
617 QJSValue jsv;
618 QJSValuePrivate::setValue(&jsv, func);
619 roleIndex = e->setFunctionProperty(r, jsv);
620 } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
621 if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
622 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
624 roleIndex = e->setQObjectProperty(role, wrapper);
625 } else if (QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
626 // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
627 QV4::Value::fromReturnedValue(o->asReturnedValue()),
628 QMetaType::fromType<QUrl>(), true);
629 maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
630 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
631 QUrl qurl = maybeUrl.toUrl();
632 roleIndex = e->setUrlProperty(r, qurl);
633 } else {
634 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
636 QV4::ScopedObject obj(scope, o);
637 roleIndex = e->setVariantMapProperty(role, obj);
638 }
639 }
640 } else if (propertyValue->isNullOrUndefined()) {
641 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
642 if (r)
643 e->clearProperty(*r);
644 }
645
646 if (roleIndex != -1)
647 roles->append(roleIndex);
648 }
649
650 if (ModelNodeMetaObject *mo = e->objectCache())
651 mo->updateValues(*roles);
652}
653
654void ListModel::set(int elementIndex, QV4::Object *object, ListModel::SetElement reason)
655{
656 if (!object)
657 return;
658
659 ListElement *e = elements[elementIndex];
660
661 QV4::ExecutionEngine *v4 = object->engine();
662 QV4::Scope scope(v4);
663
664 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
665 QV4::ScopedString propertyName(scope);
666 QV4::ScopedValue propertyValue(scope);
667 QV4::ScopedObject o(scope);
668 while (1) {
669 propertyName = it.nextPropertyNameAsString(propertyValue);
670 if (!propertyName)
671 break;
672
673 // Add the value now
674 if (QV4::String *s = propertyValue->stringValue()) {
675 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
677 e->setStringPropertyFast(r, s->toQString());
678 } else if (propertyValue->isNumber()) {
679 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
681 e->setDoublePropertyFast(r, propertyValue->asDouble());
682 }
683 } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
684 setArrayLikeFast(&o, propertyName, e, a);
685 } else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
686 setArrayLikeFast(&o, propertyName, e, s);
687 } else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
688 setArrayLikeFast(&o, propertyName, e, l);
689 } else if (propertyValue->isBoolean()) {
690 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
692 e->setBoolPropertyFast(r, propertyValue->booleanValue());
693 }
694 } else if (QV4::DateObject *date = propertyValue->as<QV4::DateObject>()) {
695 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
697 QDateTime dt = date->toQDateTime();
698 e->setDateTimePropertyFast(r, dt);
699 }
700 } else if (QV4::UrlObject *url = propertyValue->as<QV4::UrlObject>()){
701 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
703 QUrl qurl = QUrl(url->href()); // does what the private UrlObject->toQUrl would do
704 e->setUrlPropertyFast(r, qurl);
705 }
706 } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
707 if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
708 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
710 e->setQObjectPropertyFast(r, wrapper);
711 } else {
712 QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
713 // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
714 QV4::Value::fromReturnedValue(o->asReturnedValue()),
715 QMetaType::fromType<QUrl>(), true);
716 if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
717 const QUrl qurl = maybeUrl.toUrl();
718 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
720 e->setUrlPropertyFast(r, qurl);
721 } else {
722 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
724 e->setVariantMapFast(role, o);
725 }
726 }
727 } else if (propertyValue->isNullOrUndefined()) {
728 if (reason == SetElement::WasJustInserted) {
729 QQmlError err;
730 auto memberName = propertyName->toString(v4)->toQString();
731 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")));
732 qmlWarning(nullptr, err);
733 } else {
734 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
735 if (r)
736 e->clearProperty(*r);
737 }
738 }
739 }
740}
741
742QList<std::function<void()>> ListModel::remove(int index, int count)
743{
744 QList<std::function<void()>> toDestroy;
745 auto layout = m_layout;
746 for (int i=0 ; i < count ; ++i) {
747 auto element = elements[index+i];
748 toDestroy.append([element, layout](){
749 element->destroy(layout);
750 delete element;
751 });
752 }
753 elements.remove(index, count);
754 updateCacheIndices(index);
755 return toDestroy;
756}
757
758void ListModel::insert(int elementIndex, QV4::Object *object)
759{
760 insertElement(elementIndex);
761 set(elementIndex, object, SetElement::WasJustInserted);
762}
763
764int ListModel::append(QV4::Object *object)
765{
766 int elementIndex = appendElement();
767 set(elementIndex, object, SetElement::WasJustInserted);
768 return elementIndex;
769}
770
771int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
772{
773 int roleIndex = -1;
774
775 if (elementIndex >= 0 && elementIndex < elements.count()) {
776 ListElement *e = elements[elementIndex];
777
778 const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
779 if (r) {
780 roleIndex = e->setVariantProperty(*r, data);
781
782 ModelNodeMetaObject *cache = e->objectCache();
783
784 if (roleIndex != -1 && cache)
785 cache->updateValues(QList<int>(1, roleIndex));
786 }
787 }
788
789 return roleIndex;
790}
791
792int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng)
793{
794 int roleIndex = -1;
795
796 if (elementIndex >= 0 && elementIndex < elements.count()) {
797 ListElement *e = elements[elementIndex];
798 const ListLayout::Role *r = m_layout->getExistingRole(key);
799 if (r)
800 roleIndex = e->setJsProperty(*r, data, eng);
801 }
802
803 return roleIndex;
804}
805
806inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
807{
808 ListElement *e = this;
809 int blockIndex = 0;
810 while (blockIndex < role.blockIndex) {
811 if (e->next == nullptr) {
812 e->next = new ListElement;
813 e->next->uid = uid;
814 }
815 e = e->next;
816 ++blockIndex;
817 }
818
819 char *mem = &e->data[role.blockOffset];
820 return mem;
821}
822
823ModelNodeMetaObject *ListElement::objectCache()
824{
825 if (!m_objectCache)
826 return nullptr;
827 return ModelNodeMetaObject::get(m_objectCache);
828}
829
830StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role)
831{
832 char *mem = getPropertyMemory(role);
833 StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
834 return s;
835}
836
837QV4::QObjectWrapper *ListElement::getQObjectProperty(const ListLayout::Role &role)
838{
839 char *mem = getPropertyMemory(role);
840 QV4::PersistentValue *g = reinterpret_cast<QV4::PersistentValue *>(mem);
841 return g->as<QV4::QObjectWrapper>();
842}
843
844QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
845{
846 QVariantMap *map = nullptr;
847
848 char *mem = getPropertyMemory(role);
849 if (isMemoryUsed<QVariantMap>(mem))
850 map = reinterpret_cast<QVariantMap *>(mem);
851
852 return map;
853}
854
855QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
856{
857 QDateTime *dt = nullptr;
858
859 char *mem = getPropertyMemory(role);
860 if (isMemoryUsed<QDateTime>(mem))
861 dt = reinterpret_cast<QDateTime *>(mem);
862
863 return dt;
864}
865
866QUrl *ListElement::getUrlProperty(const ListLayout::Role &role)
867{
868 QUrl *url = nullptr;
869
870 char *mem = getPropertyMemory(role);
871 if (isMemoryUsed<QUrl>(mem))
872 url = reinterpret_cast<QUrl *>(mem);
873
874 return url;
875}
876
877QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role)
878{
879 QJSValue *f = nullptr;
880
881 char *mem = getPropertyMemory(role);
882 if (isMemoryUsed<QJSValue>(mem))
883 f = reinterpret_cast<QJSValue *>(mem);
884
885 return f;
886}
887
888QV4::PersistentValue *
889ListElement::getGuardProperty(const ListLayout::Role &role)
890{
891 char *mem = getPropertyMemory(role);
892
893 bool existingGuard = false;
894 for (size_t i = 0; i < sizeof(QV4::PersistentValue);
895 ++i) {
896 if (mem[i] != 0) {
897 existingGuard = true;
898 break;
899 }
900 }
901
902 QV4::PersistentValue *g = nullptr;
903
904 if (existingGuard)
905 g = reinterpret_cast<QV4::PersistentValue *>(mem);
906
907 return g;
908}
909
910ListModel *ListElement::getListProperty(const ListLayout::Role &role)
911{
912 char *mem = getPropertyMemory(role);
913 ListModel **value = reinterpret_cast<ListModel **>(mem);
914 return *value;
915}
916
917QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
918{
919 char *mem = getPropertyMemory(role);
920
921 QVariant data;
922
923 switch (role.type) {
925 {
926 double *value = reinterpret_cast<double *>(mem);
927 data = *value;
928 }
929 break;
931 {
932 StringOrTranslation *value = reinterpret_cast<StringOrTranslation *>(mem);
933 if (value->isSet())
934 data = value->toString(owner);
935 else
936 data = QString();
937 }
938 break;
940 {
941 bool *value = reinterpret_cast<bool *>(mem);
942 data = *value;
943 }
944 break;
946 {
947 ListModel **value = reinterpret_cast<ListModel **>(mem);
948 ListModel *model = *value;
949
950 if (model) {
951 if (model->m_modelCache == nullptr) {
952 model->m_modelCache = new QQmlListModel(owner, model, eng);
953 QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
954 }
955
956 QObject *object = model->m_modelCache;
957 data = QVariant::fromValue(object);
958 }
959 }
960 break;
962 {
963 QV4::PersistentValue *guard = reinterpret_cast<QV4::PersistentValue *>(mem);
964 data = QVariant::fromValue(guard->as<QV4::QObjectWrapper>()->object());
965 }
966 break;
968 {
969 if (isMemoryUsed<QVariantMap>(mem)) {
970 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
971 data = *map;
972 }
973 }
974 break;
976 {
977 if (isMemoryUsed<QDateTime>(mem)) {
978 QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
979 data = *dt;
980 }
981 }
982 break;
984 {
985 if (isMemoryUsed<QUrl>(mem)) {
986 QUrl *url = reinterpret_cast<QUrl *>(mem);
987 data = *url;
988 }
989 }
990 break;
992 {
993 if (isMemoryUsed<QJSValue>(mem)) {
994 QJSValue *func = reinterpret_cast<QJSValue *>(mem);
995 data = QVariant::fromValue(*func);
996 }
997 }
998 break;
999 default:
1000 break;
1001 }
1002
1003 return data;
1004}
1005
1006int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
1007{
1008 int roleIndex = -1;
1009
1010 if (role.type == ListLayout::Role::String) {
1011 char *mem = getPropertyMemory(role);
1012 StringOrTranslation *c = reinterpret_cast<StringOrTranslation *>(mem);
1013 bool changed;
1014 if (!c->isSet() || c->isTranslation())
1015 changed = true;
1016 else
1017 changed = c->asString().compare(s) != 0;
1018 c->setString(s);
1019 if (changed)
1020 roleIndex = role.index;
1021 }
1022
1023 return roleIndex;
1024}
1025
1026int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
1027{
1028 int roleIndex = -1;
1029
1030 if (role.type == ListLayout::Role::Number) {
1031 char *mem = getPropertyMemory(role);
1032 double *value = reinterpret_cast<double *>(mem);
1033 bool changed = *value != d;
1034 *value = d;
1035 if (changed)
1036 roleIndex = role.index;
1037 }
1038
1039 return roleIndex;
1040}
1041
1042int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
1043{
1044 int roleIndex = -1;
1045
1046 if (role.type == ListLayout::Role::Bool) {
1047 char *mem = getPropertyMemory(role);
1048 bool *value = reinterpret_cast<bool *>(mem);
1049 bool changed = *value != b;
1050 *value = b;
1051 if (changed)
1052 roleIndex = role.index;
1053 }
1054
1055 return roleIndex;
1056}
1057
1058int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
1059{
1060 int roleIndex = -1;
1061
1062 if (role.type == ListLayout::Role::List) {
1063 char *mem = getPropertyMemory(role);
1064 ListModel **value = reinterpret_cast<ListModel **>(mem);
1065 if (*value && *value != m) {
1066 (*value)->destroy();
1067 delete *value;
1068 }
1069 *value = m;
1070 roleIndex = role.index;
1071 }
1072
1073 return roleIndex;
1074}
1075
1076int ListElement::setQObjectProperty(const ListLayout::Role &role, QV4::QObjectWrapper *o)
1077{
1078 int roleIndex = -1;
1079
1080 if (role.type == ListLayout::Role::QObject) {
1081 char *mem = getPropertyMemory(role);
1082 if (isMemoryUsed<QVariantMap>(mem))
1083 reinterpret_cast<QV4::PersistentValue *>(mem)->set(o->engine(), *o);
1084 else
1085 new (mem) QV4::PersistentValue(o->engine(), o);
1086 roleIndex = role.index;
1087 }
1088
1089 return roleIndex;
1090}
1091
1092int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o)
1093{
1094 int roleIndex = -1;
1095
1097 char *mem = getPropertyMemory(role);
1098 if (isMemoryUsed<QVariantMap>(mem)) {
1099 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
1100 map->~QMap();
1101 }
1102 new (mem) QVariantMap(o->engine()->variantMapFromJS(o));
1103 roleIndex = role.index;
1104 }
1105
1106 return roleIndex;
1107}
1108
1109int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
1110{
1111 int roleIndex = -1;
1112
1114 char *mem = getPropertyMemory(role);
1115 if (isMemoryUsed<QVariantMap>(mem)) {
1116 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
1117 if (m && map->isSharedWith(*m))
1118 return roleIndex;
1119 map->~QMap();
1120 } else if (!m) {
1121 return roleIndex;
1122 }
1123 if (m)
1124 new (mem) QVariantMap(*m);
1125 else
1126 new (mem) QVariantMap;
1127 roleIndex = role.index;
1128 }
1129
1130 return roleIndex;
1131}
1132
1133int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt)
1134{
1135 int roleIndex = -1;
1136
1137 if (role.type == ListLayout::Role::DateTime) {
1138 char *mem = getPropertyMemory(role);
1139 if (isMemoryUsed<QDateTime>(mem)) {
1140 QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
1141 dt->~QDateTime();
1142 }
1143 new (mem) QDateTime(dt);
1144 roleIndex = role.index;
1145 }
1146
1147 return roleIndex;
1148}
1149
1150int ListElement::setUrlProperty(const ListLayout::Role &role, const QUrl &url)
1151{
1152 int roleIndex = -1;
1153
1154 if (role.type == ListLayout::Role::Url) {
1155 char *mem = getPropertyMemory(role);
1156 if (isMemoryUsed<QUrl>(mem)) {
1157 QUrl *qurl = reinterpret_cast<QUrl *>(mem);
1158 qurl->~QUrl();
1159 }
1160 new (mem) QUrl(url);
1161 roleIndex = role.index;
1162 }
1163
1164 return roleIndex;
1165}
1166
1167int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f)
1168{
1169 int roleIndex = -1;
1170
1171 if (role.type == ListLayout::Role::Function) {
1172 char *mem = getPropertyMemory(role);
1173 if (isMemoryUsed<QJSValue>(mem)) {
1174 QJSValue *f = reinterpret_cast<QJSValue *>(mem);
1175 f->~QJSValue();
1176 }
1177 new (mem) QJSValue(f);
1178 roleIndex = role.index;
1179 }
1180
1181 return roleIndex;
1182}
1183
1184int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b)
1185{
1186 int roleIndex = -1;
1187
1188 if (role.type == ListLayout::Role::String) {
1189 char *mem = getPropertyMemory(role);
1190 StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
1191 s->setTranslation(b);
1192 roleIndex = role.index;
1193 }
1194
1195 return roleIndex;
1196}
1197
1198
1199void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
1200{
1201 char *mem = getPropertyMemory(role);
1202 reinterpret_cast<StringOrTranslation *>(mem)->setString(s);
1203}
1204
1205void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
1206{
1207 char *mem = getPropertyMemory(role);
1208 double *value = new (mem) double;
1209 *value = d;
1210}
1211
1212void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
1213{
1214 char *mem = getPropertyMemory(role);
1215 bool *value = new (mem) bool;
1216 *value = b;
1217}
1218
1219void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QV4::QObjectWrapper *o)
1220{
1221 char *mem = getPropertyMemory(role);
1222 new (mem) QV4::PersistentValue(o->engine(), o);
1223}
1224
1225void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
1226{
1227 char *mem = getPropertyMemory(role);
1228 ListModel **value = new (mem) ListModel *;
1229 *value = m;
1230}
1231
1232void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o)
1233{
1234 char *mem = getPropertyMemory(role);
1235 QVariantMap *map = new (mem) QVariantMap;
1236 *map = o->engine()->variantMapFromJS(o);
1237}
1238
1239void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt)
1240{
1241 char *mem = getPropertyMemory(role);
1242 new (mem) QDateTime(dt);
1243}
1244
1245void ListElement::setUrlPropertyFast(const ListLayout::Role &role, const QUrl &url)
1246{
1247 char *mem = getPropertyMemory(role);
1248 new (mem) QUrl(url);
1249}
1250
1251void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f)
1252{
1253 char *mem = getPropertyMemory(role);
1254 new (mem) QJSValue(f);
1255}
1256
1257void ListElement::clearProperty(const ListLayout::Role &role)
1258{
1259 switch (role.type) {
1260 case ListLayout::Role::String:
1261 setStringProperty(role, QString());
1262 break;
1264 setDoubleProperty(role, 0.0);
1265 break;
1267 setBoolProperty(role, false);
1268 break;
1270 setListProperty(role, nullptr);
1271 break;
1272 case ListLayout::Role::QObject:
1273 setQObjectProperty(role, nullptr);
1274 break;
1275 case ListLayout::Role::DateTime:
1276 setDateTimeProperty(role, QDateTime());
1277 break;
1278 case ListLayout::Role::Url:
1279 setUrlProperty(role, QUrl());
1280 break;
1281 case ListLayout::Role::VariantMap:
1282 setVariantMapProperty(role, (QVariantMap *)nullptr);
1283 break;
1285 setFunctionProperty(role, QJSValue());
1286 break;
1287 default:
1288 break;
1289 }
1290}
1291
1293{
1294 m_objectCache = nullptr;
1295 uid = uidCounter.fetchAndAddOrdered(1);
1296 next = nullptr;
1297 memset(data, 0, sizeof(data));
1298}
1299
1300ListElement::ListElement(int existingUid)
1301{
1302 m_objectCache = nullptr;
1303 uid = existingUid;
1304 next = nullptr;
1305 memset(data, 0, sizeof(data));
1306}
1307
1309{
1310 delete next;
1311}
1312
1313QList<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
1314{
1315 QList<int> changedRoles;
1316 for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
1317 const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
1318 const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
1319
1320 int roleIndex = -1;
1321 switch (srcRole.type) {
1323 {
1324 ListModel *srcSubModel = src->getListProperty(srcRole);
1325 ListModel *targetSubModel = target->getListProperty(targetRole);
1326
1327 if (srcSubModel) {
1328 if (targetSubModel == nullptr) {
1329 targetSubModel = new ListModel(targetRole.subLayout, nullptr);
1330 target->setListPropertyFast(targetRole, targetSubModel);
1331 }
1332 if (ListModel::sync(srcSubModel, targetSubModel))
1333 roleIndex = targetRole.index;
1334 }
1335 }
1336 break;
1338 {
1339 QV4::QObjectWrapper *object = src->getQObjectProperty(srcRole);
1340 roleIndex = target->setQObjectProperty(targetRole, object);
1341 }
1342 break;
1348 {
1349 QVariant v = src->getProperty(srcRole, nullptr, nullptr);
1350 roleIndex = target->setVariantProperty(targetRole, v);
1351 }
1352 break;
1354 {
1355 QVariantMap *map = src->getVariantMapProperty(srcRole);
1356 roleIndex = target->setVariantMapProperty(targetRole, map);
1357 }
1358 break;
1359 default:
1360 break;
1361 }
1362 if (roleIndex >= 0)
1363 changedRoles << roleIndex;
1364 }
1365
1366 return changedRoles;
1367}
1368
1369void ListElement::destroy(ListLayout *layout)
1370{
1371 if (layout) {
1372 for (int i=0 ; i < layout->roleCount() ; ++i) {
1373 const ListLayout::Role &r = layout->getExistingRole(i);
1374
1375 switch (r.type) {
1377 {
1378 StringOrTranslation *string = getStringProperty(r);
1379 if (string)
1381 }
1382 break;
1384 {
1385 ListModel *model = getListProperty(r);
1386 if (model) {
1387 model->destroy();
1388 delete model;
1389 }
1390 }
1391 break;
1393 {
1394 if (QV4::PersistentValue *guard = getGuardProperty(r))
1395 guard->~PersistentValue();
1396 }
1397 break;
1399 {
1400 QVariantMap *map = getVariantMapProperty(r);
1401 if (map)
1402 map->~QMap();
1403 }
1404 break;
1406 {
1407 QDateTime *dt = getDateTimeProperty(r);
1408 if (dt)
1409 dt->~QDateTime();
1410 }
1411 break;
1413 {
1414 QUrl *url = getUrlProperty(r);
1415 if (url)
1416 url->~QUrl();
1417 break;
1418 }
1420 {
1421 QJSValue *f = getFunctionProperty(r);
1422 if (f)
1423 f->~QJSValue();
1424 }
1425 break;
1426 default:
1427 // other types don't need explicit cleanup.
1428 break;
1429 }
1430 }
1431
1432 if (m_objectCache) {
1433 m_objectCache->~QObject();
1434 operator delete(m_objectCache);
1435 }
1436 }
1437
1438 if (next)
1439 next->destroy(nullptr);
1440 uid = -1;
1441}
1442
1443int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
1444{
1445 int roleIndex = -1;
1446
1447 switch (role.type) {
1449 roleIndex = setDoubleProperty(role, d.toDouble());
1450 break;
1451 case ListLayout::Role::String:
1452 if (d.userType() == qMetaTypeId<const QV4::CompiledData::Binding *>())
1453 roleIndex = setTranslationProperty(role, d.value<const QV4::CompiledData::Binding*>());
1454 else
1455 roleIndex = setStringProperty(role, d.toString());
1456 break;
1458 roleIndex = setBoolProperty(role, d.toBool());
1459 break;
1461 roleIndex = setListProperty(role, d.value<ListModel *>());
1462 break;
1464 QVariantMap map = d.toMap();
1465 roleIndex = setVariantMapProperty(role, &map);
1466 }
1467 break;
1468 case ListLayout::Role::DateTime:
1469 roleIndex = setDateTimeProperty(role, d.toDateTime());
1470 break;
1471 case ListLayout::Role::Url:
1472 roleIndex = setUrlProperty(role, d.toUrl());
1473 break;
1475 roleIndex = setFunctionProperty(role, d.value<QJSValue>());
1476 break;
1477 default:
1478 break;
1479 }
1480
1481 return roleIndex;
1482}
1483
1484int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng)
1485{
1486 // Check if this key exists yet
1487 int roleIndex = -1;
1488
1489 QV4::Scope scope(eng);
1490
1491 // Add the value now
1492 if (d.isString()) {
1493 QString qstr = d.toQString();
1494 roleIndex = setStringProperty(role, qstr);
1495 } else if (d.isNumber()) {
1496 roleIndex = setDoubleProperty(role, d.asDouble());
1497 } else if (d.as<QV4::ArrayObject>()) {
1498 QV4::ScopedArrayObject a(scope, d);
1499 if (role.type == ListLayout::Role::List) {
1500 QV4::Scope scope(a->engine());
1501 QV4::ScopedObject o(scope);
1502
1503 ListModel *subModel = new ListModel(role.subLayout, nullptr);
1504 int arrayLength = a->getLength();
1505 for (int j=0 ; j < arrayLength ; ++j) {
1506 o = a->get(j);
1507 subModel->append(o);
1508 }
1509 roleIndex = setListProperty(role, subModel);
1510 } else {
1511 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));
1512 }
1513 } else if (d.isBoolean()) {
1514 roleIndex = setBoolProperty(role, d.booleanValue());
1515 } else if (d.as<QV4::DateObject>()) {
1516 QV4::Scoped<QV4::DateObject> dd(scope, d);
1517 QDateTime dt = dd->toQDateTime();
1518 roleIndex = setDateTimeProperty(role, dt);
1519 } else if (d.as<QV4::UrlObject>()) {
1520 QV4::Scoped<QV4::UrlObject> url(scope, d);
1521 QUrl qurl = QUrl(url->href());
1522 roleIndex = setUrlProperty(role, qurl);
1523 } else if (d.as<QV4::FunctionObject>()) {
1524 QV4::ScopedFunctionObject f(scope, d);
1525 QJSValue jsv;
1526 QJSValuePrivate::setValue(&jsv, f);
1527 roleIndex = setFunctionProperty(role, jsv);
1528 } else if (d.isObject()) {
1529 QV4::ScopedObject o(scope, d);
1530 QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>();
1531 if (role.type == ListLayout::Role::QObject && wrapper) {
1532 roleIndex = setQObjectProperty(role, wrapper);
1533 } else if (role.type == ListLayout::Role::VariantMap) {
1534 roleIndex = setVariantMapProperty(role, o);
1535 } else if (role.type == ListLayout::Role::Url) {
1536 QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
1537 // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
1538 QV4::Value::fromReturnedValue(o.asReturnedValue()),
1539 QMetaType::fromType<QUrl>(), true);
1540 if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
1541 roleIndex = setUrlProperty(role, maybeUrl.toUrl());
1542 }
1543 }
1544 } else if (d.isNullOrUndefined()) {
1545 clearProperty(role);
1546 }
1547
1548 return roleIndex;
1549}
1550
1551ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex)
1552: QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false)
1553{}
1554
1555void ModelNodeMetaObject::initialize()
1556{
1557 const int roleCount = m_model->m_listModel->roleCount();
1558 QList<QByteArray> properties;
1559 properties.reserve(roleCount);
1560 for (int i = 0 ; i < roleCount ; ++i) {
1561 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1562 QByteArray name = role.name.toUtf8();
1563 properties << name;
1564 }
1565 type()->createProperties(properties);
1567 m_enabled = true;
1568}
1569
1573
1574#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
1575const QMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object) const
1576#else
1577QMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object)
1578#endif
1579{
1580 if (!m_initialized) {
1581 m_initialized = true;
1582 const_cast<ModelNodeMetaObject *>(this)->initialize();
1583 }
1584 return QQmlOpenMetaObject::toDynamicMetaObject(object);
1585}
1586
1588{
1589 QObjectPrivate *op = QObjectPrivate::get(obj);
1590 return static_cast<ModelNodeMetaObject*>(op->metaObject);
1591}
1592
1594{
1595 Q_ALLOCA_INIT();
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_agent && m_ownAgent)
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 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
2116
2117 return model;
2118}
2119
2120QV4::ExecutionEngine *QQmlListModel::engine() const
2121{
2122 if (m_engine == nullptr) {
2123 m_engine = qmlEngine(this)->handle();
2124 }
2125
2126 return m_engine;
2127}
2128
2129bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target)
2130{
2131 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
2132
2133 bool hasChanges = false;
2134
2135 target->m_roles = src->m_roles;
2136
2137 // Build hash of elements <-> uid for each of the lists
2138 QHash<int, ElementSync> elementHash;
2139 for (int i = 0 ; i < target->m_modelObjects.size(); ++i) {
2140 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
2141 int uid = e->getUid();
2142 ElementSync sync;
2143 sync.target = e;
2144 sync.targetIndex = i;
2145 elementHash.insert(uid, sync);
2146 }
2147 for (int i = 0 ; i < src->m_modelObjects.size(); ++i) {
2148 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
2149 int uid = e->getUid();
2150
2151 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
2152 if (it == elementHash.end()) {
2153 ElementSync sync;
2154 sync.src = e;
2155 sync.srcIndex = i;
2156 elementHash.insert(uid, sync);
2157 } else {
2158 ElementSync &sync = it.value();
2159 sync.src = e;
2160 sync.srcIndex = i;
2161 }
2162 }
2163
2164 // Get list of elements that are in the target but no longer in the source. These get deleted first.
2165 int rowsRemoved = 0;
2166 for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
2167 DynamicRoleModelNode *element = target->m_modelObjects.at(i);
2168 ElementSync &s = elementHash.find(element->getUid()).value();
2169 Q_ASSERT(s.targetIndex >= 0);
2170 // need to update the targetIndex, to keep it correct after removals
2171 s.targetIndex -= rowsRemoved;
2172 if (s.src == nullptr) {
2173 Q_ASSERT(s.targetIndex == i);
2174 hasChanges = true;
2175 target->beginRemoveRows(QModelIndex(), i, i);
2176 target->m_modelObjects.remove(i, 1);
2177 target->endRemoveRows();
2178 delete s.target;
2179 ++rowsRemoved;
2180 --i;
2181 continue;
2182 }
2183 }
2184
2185 // Clear the target list, and append in correct order from the source
2186 target->m_modelObjects.clear();
2187 for (int i = 0 ; i < src->m_modelObjects.size() ; ++i) {
2188 DynamicRoleModelNode *element = src->m_modelObjects.at(i);
2189 ElementSync &s = elementHash.find(element->getUid()).value();
2190 Q_ASSERT(s.srcIndex >= 0);
2191 DynamicRoleModelNode *targetElement = s.target;
2192 if (targetElement == nullptr) {
2193 targetElement = new DynamicRoleModelNode(target, element->getUid());
2194 }
2195 s.changedRoles = DynamicRoleModelNode::sync(element, targetElement);
2196 target->m_modelObjects.append(targetElement);
2197 }
2198
2199 // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
2200 // so the model indices can't be out of bounds
2201 //
2202 // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
2203 // model indices are updated correctly
2204 int rowsInserted = 0;
2205 for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
2206 DynamicRoleModelNode *element = target->m_modelObjects.at(i);
2207 ElementSync &s = elementHash.find(element->getUid()).value();
2208 Q_ASSERT(s.srcIndex >= 0);
2209 s.srcIndex += rowsInserted;
2210 if (s.srcIndex != s.targetIndex) {
2211 if (s.targetIndex == -1) {
2212 target->beginInsertRows(QModelIndex(), i, i);
2213 target->endInsertRows();
2214 } else {
2215 target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
2216 target->endMoveRows();
2217 }
2218 hasChanges = true;
2219 ++rowsInserted;
2220 }
2221 if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
2222 QModelIndex idx = target->createIndex(i, 0);
2223 emit target->dataChanged(idx, idx, s.changedRoles);
2224 hasChanges = true;
2225 }
2226 }
2227 return hasChanges;
2228}
2229
2230void QQmlListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
2231{
2232 if (count <= 0)
2233 return;
2234
2235 if (m_mainThread)
2236 emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
2237}
2238
2239void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
2240{
2241 Q_ASSERT(index >= 0 && count >= 0);
2242 if (m_mainThread)
2243 beginInsertRows(QModelIndex(), index, index + count - 1);
2244}
2245
2246void QQmlListModel::emitItemsInserted()
2247{
2248 if (m_mainThread) {
2249 endInsertRows();
2250 emit countChanged();
2251 }
2252}
2253
2254QQmlListModelWorkerAgent *QQmlListModel::agent()
2255{
2256 if (m_agent)
2257 return m_agent;
2258
2259 m_agent = new QQmlListModelWorkerAgent(this);
2260 m_ownAgent = true;
2261 return m_agent;
2262}
2263
2264QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const
2265{
2266 return row >= 0 && row < count() && column == 0 && !parent.isValid()
2267 ? createIndex(row, column)
2268 : QModelIndex();
2269}
2270
2271int QQmlListModel::rowCount(const QModelIndex &parent) const
2272{
2273 return !parent.isValid() ? count() : 0;
2274}
2275
2276QVariant QQmlListModel::data(const QModelIndex &index, int role) const
2277{
2278 return data(index.row(), role);
2279}
2280
2281bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
2282{
2283 const int row = index.row();
2284 if (row >= count() || row < 0)
2285 return false;
2286
2287 if (m_dynamicRoles) {
2288 const QByteArray property = m_roles.at(role).toUtf8();
2289 if (m_modelObjects[row]->setValue(property, value)) {
2290 emitItemsChanged(row, 1, QList<int>(1, role));
2291 return true;
2292 }
2293 } else {
2294 const ListLayout::Role &r = m_listModel->getExistingRole(role);
2295 const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value);
2296 if (roleIndex != -1) {
2297 emitItemsChanged(row, 1, QList<int>(1, role));
2298 return true;
2299 }
2300 }
2301
2302 return false;
2303}
2304
2305QVariant QQmlListModel::data(int index, int role) const
2306{
2307 QVariant v;
2308
2309 if (index >= count() || index < 0)
2310 return v;
2311
2312 if (m_dynamicRoles)
2313 v = m_modelObjects[index]->getValue(m_roles[role]);
2314 else
2315 v = m_listModel->getProperty(index, role, this, engine());
2316
2317 return v;
2318}
2319
2320QHash<int, QByteArray> QQmlListModel::roleNames() const
2321{
2322 QHash<int, QByteArray> roleNames;
2323
2324 if (m_dynamicRoles) {
2325 const auto size = m_roles.size();
2326 roleNames.reserve(size);
2327 for (int i = 0 ; i < size ; ++i)
2328 roleNames.insert(i, m_roles.at(i).toUtf8());
2329 } else {
2330 const auto size = m_listModel->roleCount();
2331 roleNames.reserve(size);
2332 for (int i = 0 ; i < size; ++i) {
2333 const ListLayout::Role &r = m_listModel->getExistingRole(i);
2334 roleNames.insert(i, r.name.toUtf8());
2335 }
2336 }
2337
2338 return roleNames;
2339}
2340
2341/*!
2342 \qmlproperty bool ListModel::dynamicRoles
2343
2344 By default, the type of a role is fixed the first time
2345 the role is used. For example, if you create a role called
2346 "data" and assign a number to it, you can no longer assign
2347 a string to the "data" role. However, when the dynamicRoles
2348 property is enabled, the type of a given role is not fixed
2349 and can be different between elements.
2350
2351 The dynamicRoles property must be set before any data is
2352 added to the ListModel, and must be set from the main
2353 thread.
2354
2355 A ListModel that has data statically defined (via the
2356 ListElement QML syntax) cannot have the dynamicRoles
2357 property enabled.
2358
2359 There is a significant performance cost to using a
2360 ListModel with dynamic roles enabled. The cost varies
2361 from platform to platform but is typically somewhere
2362 between 4-6x slower than using static role types.
2363
2364 Due to the performance cost of using dynamic roles,
2365 they are disabled by default.
2366*/
2367void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
2368{
2369 if (m_mainThread && m_agent == nullptr) {
2370 if (enableDynamicRoles) {
2371 if (m_layout && m_layout->roleCount())
2372 qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
2373 else
2374 m_dynamicRoles = true;
2375 } else {
2376 if (m_roles.size()) {
2377 qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
2378 } else {
2379 m_dynamicRoles = false;
2380 }
2381 }
2382 } else {
2383 qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
2384 }
2385}
2386
2387/*!
2388 \qmlproperty int ListModel::count
2389 The number of data entries in the model.
2390*/
2391int QQmlListModel::count() const
2392{
2393 return m_dynamicRoles ? m_modelObjects.size() : m_listModel->elementCount();
2394}
2395
2396/*!
2397 \qmlmethod void ListModel::clear()
2398
2399 Deletes all content from the model. In particular this invalidates all objects you may have
2400 retrieved using \l get().
2401
2402 \sa append(), remove(), get()
2403*/
2404void QQmlListModel::clear()
2405{
2406 removeElements(0, count());
2407}
2408
2409/*!
2410 \qmlmethod void ListModel::remove(int index, int count = 1)
2411
2412 Deletes \a count number of items at \a index from the model.
2413
2414 \sa clear()
2415*/
2416void QQmlListModel::remove(QQmlV4FunctionPtr args)
2417{
2418 int argLength = args->length();
2419
2420 if (argLength == 1 || argLength == 2) {
2421 QV4::Scope scope(args->v4engine());
2422 int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
2423 int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);
2424
2425 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
2426 qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
2427 return;
2428 }
2429
2430 removeElements(index, removeCount);
2431 } else {
2432 qmlWarning(this) << tr("remove: incorrect number of arguments");
2433 }
2434}
2435
2436void QQmlListModel::removeElements(int index, int removeCount)
2437{
2438 Q_ASSERT(index >= 0 && removeCount >= 0);
2439
2440 if (!removeCount)
2441 return;
2442
2443 if (m_mainThread)
2444 beginRemoveRows(QModelIndex(), index, index + removeCount - 1);
2445
2446 QList<std::function<void()>> toDestroy;
2447 if (m_dynamicRoles) {
2448 for (int i=0 ; i < removeCount ; ++i) {
2449 auto modelObject = m_modelObjects[index+i];
2450 toDestroy.append([modelObject](){
2451 delete modelObject;
2452 });
2453 }
2454 m_modelObjects.remove(index, removeCount);
2455 } else {
2456 toDestroy = m_listModel->remove(index, removeCount);
2457 }
2458
2459 if (m_mainThread) {
2460 endRemoveRows();
2461 emit countChanged();
2462 }
2463 for (const auto &destroyer : std::as_const(toDestroy))
2464 destroyer();
2465}
2466
2467void QQmlListModel::updateTranslations()
2468{
2469 // assumption: it is impossible to have retranslatable strings in a
2470 // dynamic list model, as they would already have "decayed" to strings
2471 // when they were inserted
2472 if (m_dynamicRoles)
2473 return;
2474 Q_ASSERT(m_listModel);
2475
2476 QList<int> roles;
2477 for (int i = 0, end = m_listModel->roleCount(); i != end; ++i) {
2478 if (m_listModel->getExistingRole(i).type == ListLayout::Role::String)
2479 roles.append(i);
2480 }
2481
2482 if (!roles.isEmpty())
2483 emitItemsChanged(0, rowCount(QModelIndex()), roles);
2484
2485 m_listModel->updateTranslations();
2486}
2487
2488/*!
2489 \qmlmethod void ListModel::insert(int index, jsobject dict)
2490
2491 Adds a new item to the list model at position \a index, with the
2492 values in \a dict.
2493
2494 \code
2495 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
2496 \endcode
2497
2498 The \a index must be to an existing item in the list, or one past
2499 the end of the list (equivalent to append).
2500
2501 \sa set(), append()
2502*/
2503
2504void QQmlListModel::insert(QQmlV4FunctionPtr args)
2505{
2506 if (args->length() == 2) {
2507 QV4::Scope scope(args->v4engine());
2508 QV4::ScopedValue arg0(scope, (*args)[0]);
2509 int index = arg0->toInt32();
2510
2511 if (index < 0 || index > count()) {
2512 qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
2513 return;
2514 }
2515
2516 QV4::ScopedObject argObject(scope, (*args)[1]);
2517 QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
2518 if (objectArray) {
2519 QV4::ScopedObject argObject(scope);
2520
2521 int objectArrayLength = objectArray->getLength();
2522 emitItemsAboutToBeInserted(index, objectArrayLength);
2523 for (int i=0 ; i < objectArrayLength ; ++i) {
2524 argObject = objectArray->get(i);
2525
2526 if (m_dynamicRoles) {
2527 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2528 } else {
2529 m_listModel->insert(index+i, argObject);
2530 }
2531 }
2532 emitItemsInserted();
2533 } else if (argObject) {
2534 emitItemsAboutToBeInserted(index, 1);
2535
2536 if (m_dynamicRoles) {
2537 m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2538 } else {
2539 m_listModel->insert(index, argObject);
2540 }
2541
2542 emitItemsInserted();
2543 } else {
2544 qmlWarning(this) << tr("insert: value is not an object");
2545 }
2546 } else {
2547 qmlWarning(this) << tr("insert: value is not an object");
2548 }
2549}
2550
2551/*!
2552 \qmlmethod void ListModel::move(int from, int to, int n)
2553
2554 Moves \a n items \a from one position \a to another.
2555
2556 The from and to ranges must exist; for example, to move the first 3 items
2557 to the end of the list:
2558
2559 \code
2560 fruitModel.move(0, fruitModel.count - 3, 3)
2561 \endcode
2562
2563 \sa append()
2564*/
2565void QQmlListModel::move(int from, int to, int n)
2566{
2567 if (n == 0 || from == to)
2568 return;
2569 if (!canMove(from, to, n)) {
2570 qmlWarning(this) << tr("move: out of range");
2571 return;
2572 }
2573
2574 if (m_mainThread)
2575 beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
2576
2577 if (m_dynamicRoles) {
2578
2579 int realFrom = from;
2580 int realTo = to;
2581 int realN = n;
2582
2583 if (from > to) {
2584 // Only move forwards - flip if backwards moving
2585 int tfrom = from;
2586 int tto = to;
2587 realFrom = tto;
2588 realTo = tto+n;
2589 realN = tfrom-tto;
2590 }
2591
2592 QPODVector<DynamicRoleModelNode *, 4> store;
2593 for (int i=0 ; i < (realTo-realFrom) ; ++i)
2594 store.append(m_modelObjects[realFrom+realN+i]);
2595 for (int i=0 ; i < realN ; ++i)
2596 store.append(m_modelObjects[realFrom+i]);
2597 for (int i=0 ; i < store.count() ; ++i)
2598 m_modelObjects[realFrom+i] = store[i];
2599
2600 } else {
2601 m_listModel->move(from, to, n);
2602 }
2603
2604 if (m_mainThread)
2605 endMoveRows();
2606}
2607
2608/*!
2609 \qmlmethod void ListModel::append(jsobject dict)
2610
2611 Adds a new item to the end of the list model, with the
2612 values in \a dict.
2613
2614 \code
2615 fruitModel.append({"cost": 5.95, "name":"Pizza"})
2616 \endcode
2617
2618 \sa set(), remove()
2619*/
2620void QQmlListModel::append(QQmlV4FunctionPtr args)
2621{
2622 if (args->length() == 1) {
2623 QV4::Scope scope(args->v4engine());
2624 QV4::ScopedObject argObject(scope, (*args)[0]);
2625 QV4::ScopedArrayObject objectArray(scope, (*args)[0]);
2626
2627 if (objectArray) {
2628 QV4::ScopedObject argObject(scope);
2629
2630 int objectArrayLength = objectArray->getLength();
2631 if (objectArrayLength > 0) {
2632 int index = count();
2633 emitItemsAboutToBeInserted(index, objectArrayLength);
2634
2635 for (int i=0 ; i < objectArrayLength ; ++i) {
2636 argObject = objectArray->get(i);
2637
2638 if (m_dynamicRoles) {
2639 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2640 } else {
2641 m_listModel->append(argObject);
2642 }
2643 }
2644
2645 emitItemsInserted();
2646 }
2647 } else if (argObject) {
2648 int index;
2649
2650 if (m_dynamicRoles) {
2651 index = m_modelObjects.size();
2652 emitItemsAboutToBeInserted(index, 1);
2653 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2654 } else {
2655 index = m_listModel->elementCount();
2656 emitItemsAboutToBeInserted(index, 1);
2657 m_listModel->append(argObject);
2658 }
2659
2660 emitItemsInserted();
2661 } else {
2662 qmlWarning(this) << tr("append: value is not an object");
2663 }
2664 } else {
2665 qmlWarning(this) << tr("append: value is not an object");
2666 }
2667}
2668
2669/*!
2670 \qmlmethod var ListModel::get(int index)
2671
2672 Returns the item at \a index in the list model. This allows the item
2673 data to be accessed or modified from JavaScript:
2674
2675 \code
2676 Component.onCompleted: {
2677 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2678 console.log(fruitModel.get(0).cost);
2679 fruitModel.get(0).cost = 10.95;
2680 }
2681 \endcode
2682
2683 The \a index must be an element in the list.
2684
2685 Note that properties of the returned object that are themselves objects
2686 will also be models, and this get() method is used to access elements:
2687
2688 \code
2689 fruitModel.append(..., "attributes":
2690 [{"name":"spikes","value":"7mm"},
2691 {"name":"color","value":"green"}]);
2692 fruitModel.get(0).attributes.get(1).value; // == "green"
2693 \endcode
2694
2695 \warning The returned object is not guaranteed to remain valid. It
2696 should not be used in \l{Property Binding}{property bindings} or for
2697 storing data across modifications of its origin ListModel.
2698
2699 \sa append(), clear()
2700*/
2701QJSValue QQmlListModel::get(int index) const
2702{
2703 QV4::Scope scope(engine());
2704 QV4::ScopedValue result(scope, QV4::Value::undefinedValue());
2705
2706 if (index >= 0 && index < count()) {
2707
2708 if (m_dynamicRoles) {
2709 DynamicRoleModelNode *object = m_modelObjects[index];
2710 result = QV4::QObjectWrapper::wrap(scope.engine, object);
2711 } else {
2712 QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
2713 QQmlData *ddata = QQmlData::get(object);
2714 if (ddata->jsWrapper.isNullOrUndefined()) {
2715 result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
2716 // Keep track of the QObjectWrapper in persistent value storage
2717 ddata->jsWrapper.set(scope.engine, result);
2718 } else {
2719 result = ddata->jsWrapper.value();
2720 }
2721 }
2722 }
2723
2724 return QJSValuePrivate::fromReturnedValue(result->asReturnedValue());
2725}
2726
2727/*!
2728 \qmlmethod void ListModel::set(int index, jsobject dict)
2729
2730 Changes the item at \a index in the list model with the
2731 values in \a dict. Properties not appearing in \a dict
2732 are left unchanged.
2733
2734 \code
2735 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2736 \endcode
2737
2738 If \a index is equal to count() then a new item is appended to the
2739 list. Otherwise, \a index must be an element in the list.
2740
2741 \sa append()
2742*/
2743void QQmlListModel::set(int index, const QJSValue &value)
2744{
2745 QV4::Scope scope(engine());
2746 QV4::ScopedObject object(scope, QJSValuePrivate::asReturnedValue(&value));
2747
2748 if (!object) {
2749 qmlWarning(this) << tr("set: value is not an object");
2750 return;
2751 }
2752 if (index > count() || index < 0) {
2753 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2754 return;
2755 }
2756
2757
2758 if (index == count()) {
2759 emitItemsAboutToBeInserted(index, 1);
2760
2761 if (m_dynamicRoles) {
2762 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
2763 } else {
2764 m_listModel->insert(index, object);
2765 }
2766
2767 emitItemsInserted();
2768 } else {
2769
2770 QList<int> roles;
2771
2772 if (m_dynamicRoles) {
2773 m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
2774 } else {
2775 m_listModel->set(index, object, &roles);
2776 }
2777
2778 if (roles.size())
2779 emitItemsChanged(index, 1, roles);
2780 }
2781}
2782
2783/*!
2784 \qmlmethod void ListModel::setProperty(int index, string property, var value)
2785
2786 Changes the \a property of the item at \a index in the list model to \a value.
2787
2788 \code
2789 fruitModel.setProperty(3, "cost", 5.95)
2790 \endcode
2791
2792 The \a index must be an element in the list.
2793
2794 \sa append()
2795*/
2796void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
2797{
2798 if (count() == 0 || index >= count() || index < 0) {
2799 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2800 return;
2801 }
2802
2803 if (m_dynamicRoles) {
2804 int roleIndex = m_roles.indexOf(property);
2805 if (roleIndex == -1) {
2806 roleIndex = m_roles.size();
2807 m_roles.append(property);
2808 }
2809 if (m_modelObjects[index]->setValue(property.toUtf8(), value))
2810 emitItemsChanged(index, 1, QList<int>(1, roleIndex));
2811 } else {
2812 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2813 if (roleIndex != -1)
2814 emitItemsChanged(index, 1, QList<int>(1, roleIndex));
2815 }
2816}
2817
2818/*!
2819 \qmlmethod void ListModel::sync()
2820
2821 Writes any unsaved changes to the list model after it has been modified
2822 from a worker script.
2823*/
2824void QQmlListModel::sync()
2825{
2826 // This is just a dummy method to make it look like sync() exists in
2827 // ListModel (and not just QQmlListModelWorkerAgent) and to let
2828 // us document sync().
2829 qmlWarning(this) << "List sync() can only be called from a WorkerScript";
2830}
2831
2832bool QQmlListModelParser::verifyProperty(
2833 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
2834 const QV4::CompiledData::Binding *binding)
2835{
2836 if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
2837 const quint32 targetObjectIndex = binding->value.objectIndex;
2838 const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2839 QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
2840 if (objName != listElementTypeName) {
2841 const QMetaObject *mo = resolveType(objName);
2842 if (mo != &QQmlListElement::staticMetaObject) {
2843 error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2844 return false;
2845 }
2846 listElementTypeName = objName; // cache right name for next time
2847 }
2848
2849 if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
2850 error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
2851 return false;
2852 }
2853
2854 const QV4::CompiledData::Binding *binding = target->bindingTable();
2855 for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
2856 QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2857 if (propName.isEmpty()) {
2858 error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2859 return false;
2860 }
2861 if (!verifyProperty(compilationUnit, binding))
2862 return false;
2863 }
2864 } else if (binding->type() == QV4::CompiledData::Binding::Type_Script) {
2865 QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2866 if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
2867 bool ok;
2868 evaluateEnum(scriptStr, &ok);
2869 if (!ok) {
2870 error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
2871 return false;
2872 }
2873 }
2874 }
2875
2876 return true;
2877}
2878
2879bool QQmlListModelParser::applyProperty(
2880 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
2881 const QV4::CompiledData::Binding *binding, ListModel *model, QQmlListModel *owner,
2882 int outterElementIndex)
2883{
2884 const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
2885
2886 bool roleSet = false;
2887 const QV4::CompiledData::Binding::Type bindingType = binding->type();
2888 if (bindingType >= QV4::CompiledData::Binding::Type_Object) {
2889 const quint32 targetObjectIndex = binding->value.objectIndex;
2890 const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2891
2892 ListModel *subModel = nullptr;
2893 if (outterElementIndex == -1) {
2894 subModel = model;
2895 } else {
2896 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2897 if (role.type == ListLayout::Role::List) {
2898 subModel = model->getListProperty(outterElementIndex, role);
2899 if (subModel == nullptr) {
2900 subModel = new ListModel(role.subLayout, nullptr);
2901 model->setOrCreateProperty(
2902 outterElementIndex, elementName,
2903 QVariant::fromValue(subModel));
2904 }
2905 }
2906 }
2907
2908 int elementIndex = subModel ? subModel->appendElement() : -1;
2909
2910 const QV4::CompiledData::Binding *subBinding = target->bindingTable();
2911 for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
2912 roleSet |= applyProperty(compilationUnit, subBinding, subModel, owner, elementIndex);
2913 }
2914 } else if (!model) {
2915 return false;
2916 } else {
2917 QVariant value;
2918
2919 const bool isTranslationBinding = binding->isTranslationBinding();
2920 if (isTranslationBinding) {
2921 value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
2922 } else if (binding->evaluatesToString()) {
2923 value = compilationUnit->bindingValueAsString(binding);
2924 } else if (bindingType == QV4::CompiledData::Binding::Type_Number) {
2925 value = compilationUnit->bindingValueAsNumber(binding);
2926 } else if (bindingType == QV4::CompiledData::Binding::Type_Boolean) {
2927 value = binding->valueAsBoolean();
2928 } else if (bindingType == QV4::CompiledData::Binding::Type_Null) {
2929 value = QVariant::fromValue(nullptr);
2930 } else if (bindingType == QV4::CompiledData::Binding::Type_Script) {
2931 QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2932 if (definesEmptyList(scriptStr)) {
2933 value = QVariant::fromValue(
2934 new ListModel(model->getOrCreateListRole(elementName).subLayout, nullptr));
2935 } else if (binding->isFunctionExpression()) {
2936 QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
2937 Q_ASSERT(id != QQmlBinding::Invalid);
2938
2939 auto v4 = compilationUnit->engine;
2940 QV4::Scope scope(v4);
2941
2942 if (model->m_modelCache == nullptr) {
2943 model->m_modelCache = new QQmlListModel(owner, model, v4);
2944 QQmlEngine::setContextForObject(
2945 model->m_modelCache, QQmlEngine::contextForObject(owner));
2946 }
2947
2948 // for now we do not provide a context object; data from the ListElement must be passed to the function
2949 QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
2950 QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
2951
2952 QJSValue v;
2953 QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0));
2954 if (v4->hasException)
2955 v4->catchException();
2956 else
2957 QJSValuePrivate::setValue(&v, result);
2958 value.setValue(v);
2959 } else {
2960 bool ok;
2961 value = evaluateEnum(scriptStr, &ok);
2962 }
2963 } else {
2964 Q_UNREACHABLE();
2965 }
2966
2967 model->setOrCreateProperty(outterElementIndex, elementName, value);
2968 auto listModel = model->m_modelCache;
2969 if (isTranslationBinding && listModel) {
2970 if (!listModel->translationChangeHandler) {
2971 auto ep = QQmlEnginePrivate::get(compilationUnit->engine);
2972 model->m_modelCache->translationChangeHandler = std::make_unique<QPropertyNotifier>(
2973 ep->translationLanguage.addNotifier([listModel](){ listModel->updateTranslations(); }));
2974 }
2975 }
2976 roleSet = true;
2977 }
2978 return roleSet;
2979}
2980
2982 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
2983 const QList<const QV4::CompiledData::Binding *> &bindings)
2984{
2985 listElementTypeName = QString(); // unknown
2986
2987 for (const QV4::CompiledData::Binding *binding : bindings) {
2988 QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2989 if (!propName.isEmpty()) { // isn't default property
2990 error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
2991 return;
2992 }
2993 if (!verifyProperty(compilationUnit, binding))
2994 return;
2995 }
2996}
2997
2998void QQmlListModelParser::applyBindings(
2999 QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
3000 const QList<const QV4::CompiledData::Binding *> &bindings)
3001{
3002 QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
3003
3004 rv->m_engine = qmlEngine(rv)->handle();
3005 rv->m_compilationUnit = compilationUnit;
3006
3007 bool setRoles = false;
3008
3009 for (const QV4::CompiledData::Binding *binding : bindings) {
3010 if (binding->type() != QV4::CompiledData::Binding::Type_Object)
3011 continue;
3012 setRoles |= applyProperty(
3013 compilationUnit, binding, rv->m_listModel, rv, /*outter element index*/-1);
3014 }
3015
3016 if (setRoles == false)
3017 qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
3018}
3019
3020bool QQmlListModelParser::definesEmptyList(QStringView s)
3021{
3022 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
3023 for (auto c : s.sliced(1).chopped(1)) {
3024 if (!c.isSpace())
3025 return false;
3026 }
3027 return true;
3028 }
3029 return false;
3030}
3031
3032
3033/*!
3034 \qmltype ListElement
3035 \nativetype QQmlListElement
3036 \inqmlmodule QtQml.Models
3037 \brief Defines a data item in a ListModel.
3038 \ingroup qtquick-models
3039
3040 List elements are defined inside ListModel definitions, and represent items in a
3041 list that will be displayed using ListView or \l Repeater items.
3042
3043 List elements are defined like other QML elements except that they contain
3044 a collection of \e role definitions instead of properties. Using the same
3045 syntax as property definitions, roles both define how the data is accessed
3046 and include the data itself.
3047
3048 The names used for roles must begin with a lower-case letter and should be
3049 common to all elements in a given model. Values must be simple constants; either
3050 strings (quoted and optionally within a call to
3051 \l [QML] {Qt::} {QT_TR_NOOP()}, boolean values (true, false), numbers, or
3052 enumeration values (such as AlignText.AlignHCenter).
3053
3054 Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
3055 a role. This allows the definition of ListElements with callable actions.
3056
3057 \section1 Referencing Roles
3058
3059 The role names are used by delegates to obtain data from list elements.
3060 Each role name is accessible in the delegate's scope, and refers to the
3061 corresponding role in the current element. Where a role name would be
3062 ambiguous to use, it can be accessed via the \l{ListView::}{model}
3063 property (e.g., \c{model.cost} instead of \c{cost}).
3064
3065 \section1 Example Usage
3066
3067 The following model defines a series of list elements, each of which
3068 contain "name" and "cost" roles and their associated values.
3069
3070 \snippet qml/listmodel/listelements.qml model
3071
3072 The delegate obtains the name and cost for each element by simply referring
3073 to \c name and \c cost:
3074
3075 \snippet qml/listmodel/listelements.qml view
3076
3077 \sa ListModel
3078*/
3079
3080QT_END_NAMESPACE
3081
3082#include "moc_qqmllistmodel_p_p.cpp"
3083
3084#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)