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