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