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