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, QVector<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);
624 if (role.type == 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);
677 if (r.type == 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);
710 if (r.type == 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);
724 if (role.type == 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
743QVector<std::function<void()>> ListModel::remove(int index, int count)
744{
745 QVector<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(QVector<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
1314QVector<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
1315{
1316 QVector<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 QVector<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 QVector<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, QVector<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, QVector<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 QVector<int> roles;
1790 object->updateValues(obj, roles);
1791 return object;
1792}
1793
1795{
1796 QVector<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, QVector<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, QVector<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 QVector<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, QVector<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, QVector<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 for (int i = 0 ; i < m_roles.size() ; ++i)
2326 roleNames.insert(i, m_roles.at(i).toUtf8());
2327 } else {
2328 for (int i = 0 ; i < m_listModel->roleCount() ; ++i) {
2329 const ListLayout::Role &r = m_listModel->getExistingRole(i);
2330 roleNames.insert(i, r.name.toUtf8());
2331 }
2332 }
2333
2334 return roleNames;
2335}
2336
2337/*!
2338 \qmlproperty bool ListModel::dynamicRoles
2339
2340 By default, the type of a role is fixed the first time
2341 the role is used. For example, if you create a role called
2342 "data" and assign a number to it, you can no longer assign
2343 a string to the "data" role. However, when the dynamicRoles
2344 property is enabled, the type of a given role is not fixed
2345 and can be different between elements.
2346
2347 The dynamicRoles property must be set before any data is
2348 added to the ListModel, and must be set from the main
2349 thread.
2350
2351 A ListModel that has data statically defined (via the
2352 ListElement QML syntax) cannot have the dynamicRoles
2353 property enabled.
2354
2355 There is a significant performance cost to using a
2356 ListModel with dynamic roles enabled. The cost varies
2357 from platform to platform but is typically somewhere
2358 between 4-6x slower than using static role types.
2359
2360 Due to the performance cost of using dynamic roles,
2361 they are disabled by default.
2362*/
2363void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
2364{
2365 if (m_mainThread && m_agent == nullptr) {
2366 if (enableDynamicRoles) {
2367 if (m_layout && m_layout->roleCount())
2368 qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
2369 else
2370 m_dynamicRoles = true;
2371 } else {
2372 if (m_roles.size()) {
2373 qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
2374 } else {
2375 m_dynamicRoles = false;
2376 }
2377 }
2378 } else {
2379 qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
2380 }
2381}
2382
2383/*!
2384 \qmlproperty int ListModel::count
2385 The number of data entries in the model.
2386*/
2387int QQmlListModel::count() const
2388{
2389 return m_dynamicRoles ? m_modelObjects.size() : m_listModel->elementCount();
2390}
2391
2392/*!
2393 \qmlmethod ListModel::clear()
2394
2395 Deletes all content from the model. In particular this invalidates all objects you may have
2396 retrieved using \l get().
2397
2398 \sa append(), remove(), get()
2399*/
2400void QQmlListModel::clear()
2401{
2402 removeElements(0, count());
2403}
2404
2405/*!
2406 \qmlmethod ListModel::remove(int index, int count = 1)
2407
2408 Deletes \a count number of items at \a index from the model.
2409
2410 \sa clear()
2411*/
2412void QQmlListModel::remove(QQmlV4FunctionPtr args)
2413{
2414 int argLength = args->length();
2415
2416 if (argLength == 1 || argLength == 2) {
2417 QV4::Scope scope(args->v4engine());
2418 int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
2419 int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);
2420
2421 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
2422 qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
2423 return;
2424 }
2425
2426 removeElements(index, removeCount);
2427 } else {
2428 qmlWarning(this) << tr("remove: incorrect number of arguments");
2429 }
2430}
2431
2432void QQmlListModel::removeElements(int index, int removeCount)
2433{
2434 Q_ASSERT(index >= 0 && removeCount >= 0);
2435
2436 if (!removeCount)
2437 return;
2438
2439 if (m_mainThread)
2440 beginRemoveRows(QModelIndex(), index, index + removeCount - 1);
2441
2442 QVector<std::function<void()>> toDestroy;
2443 if (m_dynamicRoles) {
2444 for (int i=0 ; i < removeCount ; ++i) {
2445 auto modelObject = m_modelObjects[index+i];
2446 toDestroy.append([modelObject](){
2447 delete modelObject;
2448 });
2449 }
2450 m_modelObjects.remove(index, removeCount);
2451 } else {
2452 toDestroy = m_listModel->remove(index, removeCount);
2453 }
2454
2455 if (m_mainThread) {
2456 endRemoveRows();
2457 emit countChanged();
2458 }
2459 for (const auto &destroyer : toDestroy)
2460 destroyer();
2461}
2462
2463void QQmlListModel::updateTranslations()
2464{
2465 // assumption: it is impossible to have retranslatable strings in a
2466 // dynamic list model, as they would already have "decayed" to strings
2467 // when they were inserted
2468 if (m_dynamicRoles)
2469 return;
2470 Q_ASSERT(m_listModel);
2471
2472 QList<int> roles;
2473 for (int i = 0, end = m_listModel->roleCount(); i != end; ++i) {
2474 if (m_listModel->getExistingRole(i).type == ListLayout::Role::String)
2475 roles.append(i);
2476 }
2477
2478 if (!roles.isEmpty())
2479 emitItemsChanged(0, rowCount(QModelIndex()), roles);
2480
2481 m_listModel->updateTranslations();
2482}
2483
2484/*!
2485 \qmlmethod ListModel::insert(int index, jsobject dict)
2486
2487 Adds a new item to the list model at position \a index, with the
2488 values in \a dict.
2489
2490 \code
2491 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
2492 \endcode
2493
2494 The \a index must be to an existing item in the list, or one past
2495 the end of the list (equivalent to append).
2496
2497 \sa set(), append()
2498*/
2499
2500void QQmlListModel::insert(QQmlV4FunctionPtr args)
2501{
2502 if (args->length() == 2) {
2503 QV4::Scope scope(args->v4engine());
2504 QV4::ScopedValue arg0(scope, (*args)[0]);
2505 int index = arg0->toInt32();
2506
2507 if (index < 0 || index > count()) {
2508 qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
2509 return;
2510 }
2511
2512 QV4::ScopedObject argObject(scope, (*args)[1]);
2513 QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
2514 if (objectArray) {
2515 QV4::ScopedObject argObject(scope);
2516
2517 int objectArrayLength = objectArray->getLength();
2518 emitItemsAboutToBeInserted(index, objectArrayLength);
2519 for (int i=0 ; i < objectArrayLength ; ++i) {
2520 argObject = objectArray->get(i);
2521
2522 if (m_dynamicRoles) {
2523 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2524 } else {
2525 m_listModel->insert(index+i, argObject);
2526 }
2527 }
2528 emitItemsInserted();
2529 } else if (argObject) {
2530 emitItemsAboutToBeInserted(index, 1);
2531
2532 if (m_dynamicRoles) {
2533 m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2534 } else {
2535 m_listModel->insert(index, argObject);
2536 }
2537
2538 emitItemsInserted();
2539 } else {
2540 qmlWarning(this) << tr("insert: value is not an object");
2541 }
2542 } else {
2543 qmlWarning(this) << tr("insert: value is not an object");
2544 }
2545}
2546
2547/*!
2548 \qmlmethod ListModel::move(int from, int to, int n)
2549
2550 Moves \a n items \a from one position \a to another.
2551
2552 The from and to ranges must exist; for example, to move the first 3 items
2553 to the end of the list:
2554
2555 \code
2556 fruitModel.move(0, fruitModel.count - 3, 3)
2557 \endcode
2558
2559 \sa append()
2560*/
2561void QQmlListModel::move(int from, int to, int n)
2562{
2563 if (n == 0 || from == to)
2564 return;
2565 if (!canMove(from, to, n)) {
2566 qmlWarning(this) << tr("move: out of range");
2567 return;
2568 }
2569
2570 if (m_mainThread)
2571 beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
2572
2573 if (m_dynamicRoles) {
2574
2575 int realFrom = from;
2576 int realTo = to;
2577 int realN = n;
2578
2579 if (from > to) {
2580 // Only move forwards - flip if backwards moving
2581 int tfrom = from;
2582 int tto = to;
2583 realFrom = tto;
2584 realTo = tto+n;
2585 realN = tfrom-tto;
2586 }
2587
2588 QPODVector<DynamicRoleModelNode *, 4> store;
2589 for (int i=0 ; i < (realTo-realFrom) ; ++i)
2590 store.append(m_modelObjects[realFrom+realN+i]);
2591 for (int i=0 ; i < realN ; ++i)
2592 store.append(m_modelObjects[realFrom+i]);
2593 for (int i=0 ; i < store.count() ; ++i)
2594 m_modelObjects[realFrom+i] = store[i];
2595
2596 } else {
2597 m_listModel->move(from, to, n);
2598 }
2599
2600 if (m_mainThread)
2601 endMoveRows();
2602}
2603
2604/*!
2605 \qmlmethod ListModel::append(jsobject dict)
2606
2607 Adds a new item to the end of the list model, with the
2608 values in \a dict.
2609
2610 \code
2611 fruitModel.append({"cost": 5.95, "name":"Pizza"})
2612 \endcode
2613
2614 \sa set(), remove()
2615*/
2616void QQmlListModel::append(QQmlV4FunctionPtr args)
2617{
2618 if (args->length() == 1) {
2619 QV4::Scope scope(args->v4engine());
2620 QV4::ScopedObject argObject(scope, (*args)[0]);
2621 QV4::ScopedArrayObject objectArray(scope, (*args)[0]);
2622
2623 if (objectArray) {
2624 QV4::ScopedObject argObject(scope);
2625
2626 int objectArrayLength = objectArray->getLength();
2627 if (objectArrayLength > 0) {
2628 int index = count();
2629 emitItemsAboutToBeInserted(index, objectArrayLength);
2630
2631 for (int i=0 ; i < objectArrayLength ; ++i) {
2632 argObject = objectArray->get(i);
2633
2634 if (m_dynamicRoles) {
2635 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2636 } else {
2637 m_listModel->append(argObject);
2638 }
2639 }
2640
2641 emitItemsInserted();
2642 }
2643 } else if (argObject) {
2644 int index;
2645
2646 if (m_dynamicRoles) {
2647 index = m_modelObjects.size();
2648 emitItemsAboutToBeInserted(index, 1);
2649 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2650 } else {
2651 index = m_listModel->elementCount();
2652 emitItemsAboutToBeInserted(index, 1);
2653 m_listModel->append(argObject);
2654 }
2655
2656 emitItemsInserted();
2657 } else {
2658 qmlWarning(this) << tr("append: value is not an object");
2659 }
2660 } else {
2661 qmlWarning(this) << tr("append: value is not an object");
2662 }
2663}
2664
2665/*!
2666 \qmlmethod object ListModel::get(int index)
2667
2668 Returns the item at \a index in the list model. This allows the item
2669 data to be accessed or modified from JavaScript:
2670
2671 \code
2672 Component.onCompleted: {
2673 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2674 console.log(fruitModel.get(0).cost);
2675 fruitModel.get(0).cost = 10.95;
2676 }
2677 \endcode
2678
2679 The \a index must be an element in the list.
2680
2681 Note that properties of the returned object that are themselves objects
2682 will also be models, and this get() method is used to access elements:
2683
2684 \code
2685 fruitModel.append(..., "attributes":
2686 [{"name":"spikes","value":"7mm"},
2687 {"name":"color","value":"green"}]);
2688 fruitModel.get(0).attributes.get(1).value; // == "green"
2689 \endcode
2690
2691 \warning The returned object is not guaranteed to remain valid. It
2692 should not be used in \l{Property Binding}{property bindings} or for
2693 storing data across modifications of its origin ListModel.
2694
2695 \sa append(), clear()
2696*/
2697QJSValue QQmlListModel::get(int index) const
2698{
2699 QV4::Scope scope(engine());
2700 QV4::ScopedValue result(scope, QV4::Value::undefinedValue());
2701
2702 if (index >= 0 && index < count()) {
2703
2704 if (m_dynamicRoles) {
2705 DynamicRoleModelNode *object = m_modelObjects[index];
2706 result = QV4::QObjectWrapper::wrap(scope.engine, object);
2707 } else {
2708 QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
2709 QQmlData *ddata = QQmlData::get(object);
2710 if (ddata->jsWrapper.isNullOrUndefined()) {
2711 result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
2712 // Keep track of the QObjectWrapper in persistent value storage
2713 ddata->jsWrapper.set(scope.engine, result);
2714 } else {
2715 result = ddata->jsWrapper.value();
2716 }
2717 }
2718 }
2719
2720 return QJSValuePrivate::fromReturnedValue(result->asReturnedValue());
2721}
2722
2723/*!
2724 \qmlmethod ListModel::set(int index, jsobject dict)
2725
2726 Changes the item at \a index in the list model with the
2727 values in \a dict. Properties not appearing in \a dict
2728 are left unchanged.
2729
2730 \code
2731 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2732 \endcode
2733
2734 If \a index is equal to count() then a new item is appended to the
2735 list. Otherwise, \a index must be an element in the list.
2736
2737 \sa append()
2738*/
2739void QQmlListModel::set(int index, const QJSValue &value)
2740{
2741 QV4::Scope scope(engine());
2742 QV4::ScopedObject object(scope, QJSValuePrivate::asReturnedValue(&value));
2743
2744 if (!object) {
2745 qmlWarning(this) << tr("set: value is not an object");
2746 return;
2747 }
2748 if (index > count() || index < 0) {
2749 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2750 return;
2751 }
2752
2753
2754 if (index == count()) {
2755 emitItemsAboutToBeInserted(index, 1);
2756
2757 if (m_dynamicRoles) {
2758 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
2759 } else {
2760 m_listModel->insert(index, object);
2761 }
2762
2763 emitItemsInserted();
2764 } else {
2765
2766 QVector<int> roles;
2767
2768 if (m_dynamicRoles) {
2769 m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
2770 } else {
2771 m_listModel->set(index, object, &roles);
2772 }
2773
2774 if (roles.size())
2775 emitItemsChanged(index, 1, roles);
2776 }
2777}
2778
2779/*!
2780 \qmlmethod ListModel::setProperty(int index, string property, variant value)
2781
2782 Changes the \a property of the item at \a index in the list model to \a value.
2783
2784 \code
2785 fruitModel.setProperty(3, "cost", 5.95)
2786 \endcode
2787
2788 The \a index must be an element in the list.
2789
2790 \sa append()
2791*/
2792void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
2793{
2794 if (count() == 0 || index >= count() || index < 0) {
2795 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2796 return;
2797 }
2798
2799 if (m_dynamicRoles) {
2800 int roleIndex = m_roles.indexOf(property);
2801 if (roleIndex == -1) {
2802 roleIndex = m_roles.size();
2803 m_roles.append(property);
2804 }
2805 if (m_modelObjects[index]->setValue(property.toUtf8(), value))
2806 emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
2807 } else {
2808 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2809 if (roleIndex != -1)
2810 emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
2811 }
2812}
2813
2814/*!
2815 \qmlmethod ListModel::sync()
2816
2817 Writes any unsaved changes to the list model after it has been modified
2818 from a worker script.
2819*/
2820void QQmlListModel::sync()
2821{
2822 // This is just a dummy method to make it look like sync() exists in
2823 // ListModel (and not just QQmlListModelWorkerAgent) and to let
2824 // us document sync().
2825 qmlWarning(this) << "List sync() can only be called from a WorkerScript";
2826}
2827
2828bool QQmlListModelParser::verifyProperty(
2829 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
2830 const QV4::CompiledData::Binding *binding)
2831{
2832 if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
2833 const quint32 targetObjectIndex = binding->value.objectIndex;
2834 const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2835 QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
2836 if (objName != listElementTypeName) {
2837 const QMetaObject *mo = resolveType(objName);
2838 if (mo != &QQmlListElement::staticMetaObject) {
2839 error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2840 return false;
2841 }
2842 listElementTypeName = objName; // cache right name for next time
2843 }
2844
2845 if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
2846 error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
2847 return false;
2848 }
2849
2850 const QV4::CompiledData::Binding *binding = target->bindingTable();
2851 for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
2852 QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2853 if (propName.isEmpty()) {
2854 error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2855 return false;
2856 }
2857 if (!verifyProperty(compilationUnit, binding))
2858 return false;
2859 }
2860 } else if (binding->type() == QV4::CompiledData::Binding::Type_Script) {
2861 QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2862 if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
2863 bool ok;
2864 evaluateEnum(scriptStr, &ok);
2865 if (!ok) {
2866 error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
2867 return false;
2868 }
2869 }
2870 }
2871
2872 return true;
2873}
2874
2875bool QQmlListModelParser::applyProperty(
2876 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
2877 const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex)
2878{
2879 const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
2880
2881 bool roleSet = false;
2882 const QV4::CompiledData::Binding::Type bindingType = binding->type();
2883 if (bindingType >= QV4::CompiledData::Binding::Type_Object) {
2884 const quint32 targetObjectIndex = binding->value.objectIndex;
2885 const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
2886
2887 ListModel *subModel = nullptr;
2888 if (outterElementIndex == -1) {
2889 subModel = model;
2890 } else {
2891 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2892 if (role.type == ListLayout::Role::List) {
2893 subModel = model->getListProperty(outterElementIndex, role);
2894 if (subModel == nullptr) {
2895 subModel = new ListModel(role.subLayout, nullptr);
2896 QVariant vModel = QVariant::fromValue(subModel);
2897 model->setOrCreateProperty(outterElementIndex, elementName, vModel);
2898 }
2899 }
2900 }
2901
2902 int elementIndex = subModel ? subModel->appendElement() : -1;
2903
2904 const QV4::CompiledData::Binding *subBinding = target->bindingTable();
2905 for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
2906 roleSet |= applyProperty(compilationUnit, subBinding, subModel, elementIndex);
2907 }
2908
2909 } else {
2910 QVariant value;
2911
2912 const bool isTranslationBinding = binding->isTranslationBinding();
2913 if (isTranslationBinding) {
2914 value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
2915 } else if (binding->evaluatesToString()) {
2916 value = compilationUnit->bindingValueAsString(binding);
2917 } else if (bindingType == QV4::CompiledData::Binding::Type_Number) {
2918 value = compilationUnit->bindingValueAsNumber(binding);
2919 } else if (bindingType == QV4::CompiledData::Binding::Type_Boolean) {
2920 value = binding->valueAsBoolean();
2921 } else if (bindingType == QV4::CompiledData::Binding::Type_Null) {
2922 value = QVariant::fromValue(nullptr);
2923 } else if (bindingType == QV4::CompiledData::Binding::Type_Script) {
2924 QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
2925 if (definesEmptyList(scriptStr)) {
2926 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2927 ListModel *emptyModel = new ListModel(role.subLayout, nullptr);
2928 value = QVariant::fromValue(emptyModel);
2929 } else if (binding->isFunctionExpression()) {
2930 QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
2931 Q_ASSERT(id != QQmlBinding::Invalid);
2932
2933 auto v4 = compilationUnit->engine;
2934 QV4::Scope scope(v4);
2935 // for now we do not provide a context object; data from the ListElement must be passed to the function
2936 QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
2937 QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
2938
2939 QJSValue v;
2940 QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0));
2941 if (v4->hasException)
2942 v4->catchException();
2943 else
2944 QJSValuePrivate::setValue(&v, result);
2945 value.setValue(v);
2946 } else {
2947 bool ok;
2948 value = evaluateEnum(scriptStr, &ok);
2949 }
2950 } else {
2951 Q_UNREACHABLE();
2952 }
2953
2954 if (!model)
2955 return roleSet;
2956 model->setOrCreateProperty(outterElementIndex, elementName, value);
2957 auto listModel = model->m_modelCache;
2958 if (isTranslationBinding && listModel) {
2959 if (!listModel->translationChangeHandler) {
2960 auto ep = QQmlEnginePrivate::get(compilationUnit->engine);
2961 model->m_modelCache->translationChangeHandler = std::make_unique<QPropertyNotifier>(
2962 ep->translationLanguage.addNotifier([listModel](){ listModel->updateTranslations(); }));
2963 }
2964 }
2965 roleSet = true;
2966 }
2967 return roleSet;
2968}
2969
2971 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
2972 const QList<const QV4::CompiledData::Binding *> &bindings)
2973{
2974 listElementTypeName = QString(); // unknown
2975
2976 for (const QV4::CompiledData::Binding *binding : bindings) {
2977 QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
2978 if (!propName.isEmpty()) { // isn't default property
2979 error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
2980 return;
2981 }
2982 if (!verifyProperty(compilationUnit, binding))
2983 return;
2984 }
2985}
2986
2987void QQmlListModelParser::applyBindings(
2988 QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
2989 const QList<const QV4::CompiledData::Binding *> &bindings)
2990{
2991 QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
2992
2993 rv->m_engine = qmlEngine(rv)->handle();
2994 rv->m_compilationUnit = compilationUnit;
2995
2996 bool setRoles = false;
2997
2998 for (const QV4::CompiledData::Binding *binding : bindings) {
2999 if (binding->type() != QV4::CompiledData::Binding::Type_Object)
3000 continue;
3001 setRoles |= applyProperty(compilationUnit, binding, rv->m_listModel, /*outter element index*/-1);
3002 }
3003
3004 if (setRoles == false)
3005 qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
3006}
3007
3008bool QQmlListModelParser::definesEmptyList(const QString &s)
3009{
3010 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
3011 for (int i=1; i<s.size()-1; i++) {
3012 if (!s[i].isSpace())
3013 return false;
3014 }
3015 return true;
3016 }
3017 return false;
3018}
3019
3020
3021/*!
3022 \qmltype ListElement
3023 \nativetype QQmlListElement
3024 \inqmlmodule QtQml.Models
3025 \brief Defines a data item in a ListModel.
3026 \ingroup qtquick-models
3027
3028 List elements are defined inside ListModel definitions, and represent items in a
3029 list that will be displayed using ListView or \l Repeater items.
3030
3031 List elements are defined like other QML elements except that they contain
3032 a collection of \e role definitions instead of properties. Using the same
3033 syntax as property definitions, roles both define how the data is accessed
3034 and include the data itself.
3035
3036 The names used for roles must begin with a lower-case letter and should be
3037 common to all elements in a given model. Values must be simple constants; either
3038 strings (quoted and optionally within a call to
3039 \l [QML] {Qt::} {QT_TR_NOOP()}, boolean values (true, false), numbers, or
3040 enumeration values (such as AlignText.AlignHCenter).
3041
3042 Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
3043 a role. This allows the definition of ListElements with callable actions.
3044
3045 \section1 Referencing Roles
3046
3047 The role names are used by delegates to obtain data from list elements.
3048 Each role name is accessible in the delegate's scope, and refers to the
3049 corresponding role in the current element. Where a role name would be
3050 ambiguous to use, it can be accessed via the \l{ListView::}{model}
3051 property (e.g., \c{model.cost} instead of \c{cost}).
3052
3053 \section1 Example Usage
3054
3055 The following model defines a series of list elements, each of which
3056 contain "name" and "cost" roles and their associated values.
3057
3058 \snippet qml/listmodel/listelements.qml model
3059
3060 The delegate obtains the name and cost for each element by simply referring
3061 to \c name and \c cost:
3062
3063 \snippet qml/listmodel/listelements.qml view
3064
3065 \sa ListModel
3066*/
3067
3068QT_END_NAMESPACE
3069
3070#include "moc_qqmllistmodel_p_p.cpp"
3071
3072#include "moc_qqmllistmodel_p.cpp"
DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
void propertyWritten(int index) override
void propertyWrite(int index) override
void setNodeUpdatesEnabled(bool enable)
void updateValues(const QVariantMap &object, QVector< int > &roles)
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)
void set(int elementIndex, QV4::Object *object, QVector< int > *roles)
ListModel * getListProperty(int elementIndex, const ListLayout::Role &role)
int roleCount() const
const ListLayout::Role & getExistingRole(int index) const
void propertyWritten(int index) override
ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex)
void updateValues(const QVector< int > &roles)
\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:23
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)