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