Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmllistmodel_p_p.h"
++ #include "qqmllistmodelworkeragent_p.h"
++
++ #include <private/qjsvalue_p.h>
++
++ #include <private/qqmlcustomparser_p.h>
++ #include <private/qqmlengine_p.h>
++ #include <private/qqmljsast_p.h>
++ #include <private/qqmljsengine_p.h>
++ #include <private/qqmllistwrapper_p.h>
++ #include <private/qqmlnotifier_p.h>
++ #include <private/qqmlopenmetaobject_p.h>
++
++ #include <private/qv4alloca_p.h>
++ #include <private/qv4dateobject_p.h>
++ #include <private/qv4lookup_p.h>
++ #include <private/qv4object_p.h>
++ #include <private/qv4objectiterator_p.h>
++ #include <private/qv4qmlcontext_p.h>
++ #include <private/qv4sequenceobject_p.h>
++ #include <private/qv4urlobject_p.h>
++
++ #include <qqmlcontext.h>
++ #include <qqmlinfo.h>
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qstack.h>
++ #include <QXmlStreamReader>
++ #include <QtCore/qdatetime.h>
++ #include <QScopedValueRollback>
++
++ Q_DECLARE_METATYPE(const QV4::CompiledData::Binding*);
++
++ #include <algorithm>
++ #include <vector>
++
++ QT_BEGIN_NAMESPACE
++
++ // Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
++ enum { MIN_LISTMODEL_UID = 1024 };
++
++ static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
++
++ template <typename T>
++ static bool isMemoryUsed(const char *mem)
++ {
++ for (size_t i=0 ; i < sizeof(T) ; ++i) {
++ if (mem[i] != 0)
++ return true;
++ }
++
++ return false;
++ }
++
++ static QString roleTypeName(ListLayout::Role::DataType t)
++ {
++ static const QString roleTypeNames[] = {
++ QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"),
++ QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"),
++ QStringLiteral("DateTime"), QStringLiteral("Url"), QStringLiteral("Function")
++ };
++
++ if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
++ return roleTypeNames[t];
++
++ return QString();
++ }
++
++ const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
++ {
++ QStringHash<Role *>::Node *node = roleHash.findNode(key);
++ if (node) {
++ const Role &r = *node->value;
++ if (type != r.type)
++ 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));
++ return r;
++ }
++
++ return createRole(key, type);
++ }
++
++ const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::DataType type)
++ {
++ QStringHash<Role *>::Node *node = roleHash.findNode(key);
++ if (node) {
++ const Role &r = *node->value;
++ if (type != r.type)
++ 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));
++ return r;
++ }
++
++ QString qkey = key->toQString();
++
++ return createRole(qkey, type);
++ }
++
++ const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
++ {
++ const int dataSizes[] = {
++ sizeof(StringOrTranslation),
++ sizeof(double),
++ sizeof(bool),
++ sizeof(ListModel *),
++ sizeof(QV4::PersistentValue),
++ sizeof(QVariantMap),
++ sizeof(QDateTime),
++ sizeof(QUrl),
++ sizeof(QJSValue)
++ };
++ const int dataAlignments[] = {
++ alignof(StringOrTranslation),
++ alignof(double),
++ alignof(bool),
++ alignof(ListModel *),
++ alignof(QV4::PersistentValue),
++ alignof(QVariantMap),
++ alignof(QDateTime),
++ alignof(QUrl),
++ alignof(QJSValue)
++ };
++
++ Role *r = new Role;
++ r->name = key;
++ r->type = type;
++
++ if (type == Role::List) {
++ r->subLayout = new ListLayout;
++ } else {
++ r->subLayout = nullptr;
++ }
++
++ int dataSize = dataSizes[type];
++ int dataAlignment = dataAlignments[type];
++
++ int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
++ if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
++ r->blockIndex = ++currentBlock;
++ r->blockOffset = 0;
++ currentBlockOffset = dataSize;
++ } else {
++ r->blockIndex = currentBlock;
++ r->blockOffset = dataOffset;
++ currentBlockOffset = dataOffset + dataSize;
++ }
++
++ int roleIndex = roles.size();
++ r->index = roleIndex;
++
++ roles.append(r);
++ roleHash.insert(key, r);
++
++ return *r;
++ }
++
++ ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
++ {
++ const int otherRolesCount = other->roles.size();
++ roles.reserve(otherRolesCount);
++ for (int i=0 ; i < otherRolesCount; ++i) {
++ Role *role = new Role(other->roles[i]);
++ roles.append(role);
++ roleHash.insert(role->name, role);
++ }
++ currentBlockOffset = other->currentBlockOffset;
++ currentBlock = other->currentBlock;
++ }
++
++ ListLayout::~ListLayout()
++ {
++ qDeleteAll(roles);
++ }
++
++ void ListLayout::sync(ListLayout *src, ListLayout *target)
++ {
++ int roleOffset = target->roles.size();
++ int newRoleCount = src->roles.size() - roleOffset;
++
++ for (int i=0 ; i < newRoleCount ; ++i) {
++ Role *role = new Role(src->roles[roleOffset + i]);
++ target->roles.append(role);
++ target->roleHash.insert(role->name, role);
++ }
++
++ target->currentBlockOffset = src->currentBlockOffset;
++ target->currentBlock = src->currentBlock;
++ }
++
++ ListLayout::Role::Role(const Role *other)
++ {
++ name = other->name;
++ type = other->type;
++ blockIndex = other->blockIndex;
++ blockOffset = other->blockOffset;
++ index = other->index;
++ if (other->subLayout)
++ subLayout = new ListLayout(other->subLayout);
++ else
++ subLayout = nullptr;
++ }
++
++ ListLayout::Role::~Role()
++ {
++ delete subLayout;
++ }
++
++ const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
++ {
++ Role::DataType type;
++
++ switch (data.userType()) {
++ case QMetaType::Double: type = Role::Number; break;
++ case QMetaType::Int: type = Role::Number; break;
++ case QMetaType::Bool: type = Role::Bool; break;
++ case QMetaType::QString: type = Role::String; break;
++ case QMetaType::QVariantMap: type = Role::VariantMap; break;
++ case QMetaType::QDateTime: type = Role::DateTime; break;
++ case QMetaType::QUrl: type = Role::Url; break;
++ default: {
++ if (data.userType() == qMetaTypeId<QJSValue>() &&
++ data.value<QJSValue>().isCallable()) {
++ type = Role::Function;
++ break;
++ } else if (data.userType() == qMetaTypeId<const QV4::CompiledData::Binding*>()
++ && data.value<const QV4::CompiledData::Binding*>()->isTranslationBinding()) {
++ type = Role::String;
++ break;
++ } else if (data.userType() >= QMetaType::User) {
++ type = Role::List;
++ break;
++ } else {
++ type = Role::Invalid;
++ break;
++ }
++ }
++ }
++
++ if (type == Role::Invalid) {
++ qmlWarning(nullptr) << "Can't create role for unsupported data type";
++ return nullptr;
++ }
++
++ return &getRoleOrCreate(key, type);
++ }
++
++ const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const
++ {
++ Role *r = nullptr;
++ QStringHash<Role *>::Node *node = roleHash.findNode(key);
++ if (node)
++ r = node->value;
++ return r;
++ }
++
++ const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const
++ {
++ Role *r = nullptr;
++ QStringHash<Role *>::Node *node = roleHash.findNode(key);
++ if (node)
++ r = node->value;
++ return r;
++ }
++
++ StringOrTranslation::~StringOrTranslation()
++ {
++ clear();
++ }
++
++ void StringOrTranslation::setString(const QString &s)
++ {
++ clear();
++ if (s.isEmpty())
++ return;
++ QString::DataPointer dataPointer = s.data_ptr();
++ arrayData = dataPointer.d_ptr();
++ stringData = dataPointer.data();
++ stringSize = dataPointer.size;
++ if (arrayData)
++ arrayData->ref();
++ }
++
++ void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding)
++ {
++ clear();
++ this->binding = binding;
++ }
++
++ QString StringOrTranslation::toString(const QQmlListModel *owner) const
++ {
++ if (stringSize) {
++ if (arrayData)
++ arrayData->ref();
++ return QString(QStringPrivate(arrayData, stringData, stringSize));
++ }
++ if (!owner)
++ return QString();
++ return owner->m_compilationUnit->bindingValueAsString(binding);
++ }
++
++ QString StringOrTranslation::asString() const
++ {
++ if (!arrayData)
++ return QString();
++ arrayData->ref();
++ return QString(QStringPrivate(arrayData, stringData, stringSize));
++ }
++
++ void StringOrTranslation::clear()
++ {
++ if (arrayData && !arrayData->deref())
++ QTypedArrayData<ushort>::deallocate(arrayData);
++ arrayData = nullptr;
++ stringData = nullptr;
++ stringSize = 0;
++ binding = nullptr;
++ }
++
++ QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex)
++ {
++ ListElement *e = elements[elementIndex];
++ if (e->m_objectCache == nullptr) {
++ void *memory = operator new(sizeof(QObject) + sizeof(QQmlData));
++ void *ddataMemory = ((char *)memory) + sizeof(QObject);
++ e->m_objectCache = new (memory) QObject;
++
++ const QAbstractDeclarativeData *old = std::exchange(
++ QObjectPrivate::get(e->m_objectCache)->declarativeData,
++ new (ddataMemory) QQmlData(QQmlData::DoesNotOwnMemory));
++ Q_ASSERT(!old); // QObject should really not manipulate QQmlData
++
++ (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
++ }
++ return e->m_objectCache;
++ }
++
++ bool ListModel::sync(ListModel *src, ListModel *target)
++ {
++ // Sanity check
++
++ bool hasChanges = false;
++
++ // Build hash of elements <-> uid for each of the lists
++ QHash<int, ElementSync> elementHash;
++ for (int i = 0; i < target->elementCount(); ++i) {
++ ListElement *e = target->elements[i];
++ int uid = e->getUid();
++ ElementSync sync;
++ sync.target = e;
++ sync.targetIndex = i;
++ elementHash.insert(uid, sync);
++ }
++ for (int i = 0; i < src->elementCount(); ++i) {
++ ListElement *e = src->elements[i];
++ int uid = e->getUid();
++
++ QHash<int, ElementSync>::iterator it = elementHash.find(uid);
++ if (it == elementHash.end()) {
++ ElementSync sync;
++ sync.src = e;
++ sync.srcIndex = i;
++ elementHash.insert(uid, sync);
++ } else {
++ ElementSync &sync = it.value();
++ sync.src = e;
++ sync.srcIndex = i;
++ }
++ }
++
++ QQmlListModel *targetModel = target->m_modelCache;
++
++ // Get list of elements that are in the target but no longer in the source. These get deleted first.
++ int rowsRemoved = 0;
++ for (int i = 0 ; i < target->elementCount() ; ++i) {
++ ListElement *element = target->elements[i];
++ ElementSync &s = elementHash.find(element->getUid()).value();
++ Q_ASSERT(s.targetIndex >= 0);
++ // need to update the targetIndex, to keep it correct after removals
++ s.targetIndex -= rowsRemoved;
++ if (s.src == nullptr) {
++ Q_ASSERT(s.targetIndex == i);
++ hasChanges = true;
++ if (targetModel)
++ targetModel->beginRemoveRows(QModelIndex(), i, i);
++ s.target->destroy(target->m_layout);
++ if (auto it = std::find(target->elements.begin(), target->elements.end(), s.target);
++ it != target->elements.end()) {
++ target->elements.erase(it);
++ }
++ delete s.target;
++ if (targetModel)
++ targetModel->endRemoveRows();
++ ++rowsRemoved;
++ --i;
++ continue;
++ }
++ }
++
++ // Sync the layouts
++ ListLayout::sync(src->m_layout, target->m_layout);
++
++ // Clear the target list, and append in correct order from the source
++ target->elements.clear();
++ for (int i = 0; i < src->elementCount(); ++i) {
++ ListElement *srcElement = src->elements[i];
++ ElementSync &s = elementHash.find(srcElement->getUid()).value();
++ Q_ASSERT(s.srcIndex >= 0);
++ ListElement *targetElement = s.target;
++ if (targetElement == nullptr) {
++ targetElement = new ListElement(srcElement->getUid());
++ }
++ s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout);
++ target->elements.push_back(targetElement);
++ }
++
++ target->updateCacheIndices();
++
++ // Update values stored in target meta objects
++ for (ListElement *e : target->elements) {
++ if (ModelNodeMetaObject *mo = e->objectCache())
++ mo->updateValues();
++ }
++
++ // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
++ // so the model indices can't be out of bounds
++ //
++ // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
++ // model indices are updated correctly
++ int rowsInserted = 0;
++ const int targetElementCount = target->elementCount();
++ for (int i = 0 ; i < targetElementCount ; ++i) {
++ ListElement *element = target->elements[i];
++ ElementSync &s = elementHash.find(element->getUid()).value();
++ Q_ASSERT(s.srcIndex >= 0);
++ s.srcIndex += rowsInserted;
++ if (s.srcIndex != s.targetIndex) {
++ if (targetModel) {
++ if (s.targetIndex == -1) {
++ targetModel->beginInsertRows(QModelIndex(), i, i);
++ targetModel->endInsertRows();
++ ++rowsInserted;
++ } else {
++ bool validMove = targetModel->beginMoveRows(QModelIndex(), s.targetIndex, s.targetIndex, QModelIndex(), i);
++ Q_ASSERT(validMove);
++ targetModel->endMoveRows();
++ // fixup target indices of elements that still need to move
++ for (int j=i+1; j < targetElementCount; ++j) {
++ ListElement *eToFix = target->elements[j];
++ ElementSync &sToFix = elementHash.find(eToFix->getUid()).value();
++ if (i < s.targetIndex) {
++ // element was moved down
++ if (sToFix.targetIndex > s.targetIndex || sToFix.targetIndex < i)
++ continue; // unaffected by reordering
++ else
++ sToFix.targetIndex += 1;
++ } else {
++ // element was moved up
++ if (sToFix.targetIndex < s.targetIndex || sToFix.targetIndex > i)
++ continue; // unaffected by reordering
++ else
++ sToFix.targetIndex -= 1;
++ }
++ }
++ }
++ }
++ hasChanges = true;
++ }
++ if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
++ QModelIndex idx = targetModel->createIndex(i, 0);
++ if (targetModel)
++ targetModel->dataChanged(idx, idx, s.changedRoles);
++ hasChanges = true;
++ }
++ }
++ return hasChanges;
++ }
++
++ ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache)
++ {
++ }
++
++ void ListModel::destroy()
++ {
++ for (const auto &destroyer : remove(0, elementCount()))
++ destroyer();
++
++ m_layout = nullptr;
++ if (m_modelCache && m_modelCache->m_primary == false)
++ delete m_modelCache;
++ m_modelCache = nullptr;
++ }
++
++ int ListModel::appendElement()
++ {
++ int elementIndex = elementCount();
++ newElement(elementIndex);
++ return elementIndex;
++ }
++
++ void ListModel::insertElement(int index)
++ {
++ newElement(index);
++ updateCacheIndices(index);
++ }
++
++ void ListModel::move(int from, int to, int n)
++ {
++ if (from > to) {
++ // Only move forwards - flip if backwards moving
++ int tfrom = from;
++ int tto = to;
++ from = tto;
++ to = tto+n;
++ n = tfrom-tto;
++ }
++
++ // Rotate the [from, to + n) sub-range so the n-element block starting at
++ // 'from' moves to the end of the range; no temporary storage required.
++ std::rotate(elements.begin() + from,
++ elements.begin() + from + n,
++ elements.begin() + to + n);
++
++ updateCacheIndices(from, to + n);
++ }
++
++ void ListModel::newElement(int index)
++ {
++ ListElement *e = new ListElement;
++ elements.insert(elements.begin() + index, e);
++ }
++
++ void ListModel::updateCacheIndices(int start, int end)
++ {
++ int count = elementCount();
++
++ if (end < 0 || end > count)
++ end = count;
++
++ for (int i = start; i < end; ++i) {
++ ListElement *e = elements[i];
++ if (ModelNodeMetaObject *mo = e->objectCache())
++ mo->m_elementIndex = i;
++ }
++ }
++
++ QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
++ {
++ if (roleIndex >= m_layout->roleCount())
++ return QVariant();
++ ListElement *e = elements[elementIndex];
++ const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
++ return e->getProperty(r, owner, eng);
++ }
++
++ ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
++ {
++ ListElement *e = elements[elementIndex];
++ return e->getListProperty(role);
++ }
++
++ void ListModel::updateTranslations()
++ {
++ for (ListElement *e : elements) {
++ if (ModelNodeMetaObject *cache = e->objectCache()) {
++ // TODO: more fine grained tracking?
++ cache->updateValues();
++ }
++ }
++ }
++
++ void ListModel::set(int elementIndex, QV4::Object *object, QList<int> *roles)
++ {
++ ListElement *e = elements[elementIndex];
++
++ QV4::ExecutionEngine *v4 = object->engine();
++ QV4::Scope scope(v4);
++ QV4::ScopedObject o(scope);
++
++ QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
++ QV4::ScopedString propertyName(scope);
++ QV4::ScopedValue propertyValue(scope);
++ while (1) {
++ propertyName = it.nextPropertyNameAsString(propertyValue);
++ if (!propertyName)
++ break;
++
++ // Check if this key exists yet
++ int roleIndex = -1;
++
++ // Add the value now
++ if (const QV4::String *s = propertyValue->as<QV4::String>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
++ roleIndex = e->setStringProperty(r, s->toQString());
++ } else if (propertyValue->isNumber()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
++ roleIndex = e->setDoubleProperty(r, propertyValue->asDouble());
++ } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
++ roleIndex = setArrayLike(&o, propertyName, e, a);
++ } else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
++ roleIndex = setArrayLike(&o, propertyName, e, s);
++ } else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
++ roleIndex = setArrayLike(&o, propertyName, e, l);
++ } else if (propertyValue->isBoolean()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
++ roleIndex = e->setBoolProperty(r, propertyValue->booleanValue());
++ } else if (QV4::DateObject *dd = propertyValue->as<QV4::DateObject>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
++ QDateTime dt = dd->toQDateTime();
++ roleIndex = e->setDateTimeProperty(r, dt);
++ } else if (QV4::UrlObject *url = propertyValue->as<QV4::UrlObject>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
++ QUrl qurl = QUrl(url->href());
++ roleIndex = e->setUrlProperty(r, qurl);
++ } else if (QV4::FunctionObject *f = propertyValue->as<QV4::FunctionObject>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function);
++ QV4::ScopedFunctionObject func(scope, f);
++ QJSValue jsv;
++ QJSValuePrivate::setValue(&jsv, func);
++ roleIndex = e->setFunctionProperty(r, jsv);
++ } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
++ if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
++ const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
++ if (role.type == ListLayout::Role::QObject)
++ roleIndex = e->setQObjectProperty(role, wrapper);
++ } else if (QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
++ // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
++ QV4::Value::fromReturnedValue(o->asReturnedValue()),
++ QMetaType::fromType<QUrl>(), true);
++ maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
++ QUrl qurl = maybeUrl.toUrl();
++ roleIndex = e->setUrlProperty(r, qurl);
++ } else {
++ const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
++ if (role.type == ListLayout::Role::VariantMap) {
++ QV4::ScopedObject obj(scope, o);
++ roleIndex = e->setVariantMapProperty(role, obj);
++ }
++ }
++ } else if (propertyValue->isNullOrUndefined()) {
++ const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
++ if (r)
++ e->clearProperty(*r);
++ }
++
++ if (roleIndex != -1)
++ roles->append(roleIndex);
++ }
++
++ if (ModelNodeMetaObject *mo = e->objectCache())
++ mo->updateValues(*roles);
++ }
++
++ void ListModel::set(int elementIndex, QV4::Object *object, ListModel::SetElement reason)
++ {
++ if (!object)
++ return;
++
++ ListElement *e = elements[elementIndex];
++
++ QV4::ExecutionEngine *v4 = object->engine();
++ QV4::Scope scope(v4);
++
++ QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
++ QV4::ScopedString propertyName(scope);
++ QV4::ScopedValue propertyValue(scope);
++ QV4::ScopedObject o(scope);
++ while (1) {
++ propertyName = it.nextPropertyNameAsString(propertyValue);
++ if (!propertyName)
++ break;
++
++ // Add the value now
++ if (QV4::String *s = propertyValue->stringValue()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
++ if (r.type == ListLayout::Role::String)
++ e->setStringPropertyFast(r, s->toQString());
++ } else if (propertyValue->isNumber()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
++ if (r.type == ListLayout::Role::Number) {
++ e->setDoublePropertyFast(r, propertyValue->asDouble());
++ }
++ } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
++ setArrayLikeFast(&o, propertyName, e, a);
++ } else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
++ setArrayLikeFast(&o, propertyName, e, s);
++ } else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
++ setArrayLikeFast(&o, propertyName, e, l);
++ } else if (propertyValue->isBoolean()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
++ if (r.type == ListLayout::Role::Bool) {
++ e->setBoolPropertyFast(r, propertyValue->booleanValue());
++ }
++ } else if (QV4::DateObject *date = propertyValue->as<QV4::DateObject>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
++ if (r.type == ListLayout::Role::DateTime) {
++ QDateTime dt = date->toQDateTime();
++ e->setDateTimePropertyFast(r, dt);
++ }
++ } else if (QV4::UrlObject *url = propertyValue->as<QV4::UrlObject>()){
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
++ if (r.type == ListLayout::Role::Url) {
++ QUrl qurl = QUrl(url->href()); // does what the private UrlObject->toQUrl would do
++ e->setUrlPropertyFast(r, qurl);
++ }
++ } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
++ if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
++ if (r.type == ListLayout::Role::QObject)
++ e->setQObjectPropertyFast(r, wrapper);
++ } else {
++ QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
++ // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
++ QV4::Value::fromReturnedValue(o->asReturnedValue()),
++ QMetaType::fromType<QUrl>(), true);
++ if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
++ const QUrl qurl = maybeUrl.toUrl();
++ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
++ if (r.type == ListLayout::Role::Url)
++ e->setUrlPropertyFast(r, qurl);
++ } else {
++ const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
++ if (role.type == ListLayout::Role::VariantMap)
++ e->setVariantMapFast(role, o);
++ }
++ }
++ } else if (propertyValue->isNullOrUndefined()) {
++ if (reason == SetElement::WasJustInserted) {
++ QQmlError err;
++ auto memberName = propertyName->toString(v4)->toQString();
++ 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")));
++ qmlWarning(nullptr, err);
++ } else {
++ const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
++ if (r)
++ e->clearProperty(*r);
++ }
++ }
++ }
++ }
++
++ QList<std::function<void()>> ListModel::remove(int index, int count)
++ {
++ QList<std::function<void()>> toDestroy;
++ auto layout = m_layout;
++ for (int i=0 ; i < count ; ++i) {
++ auto element = elements[index+i];
++ toDestroy.append([element, layout](){
++ element->destroy(layout);
++ delete element;
++ });
++ }
++ elements.erase(elements.begin() + index, elements.begin() + index + count);
++ updateCacheIndices(index);
++ return toDestroy;
++ }
++
++ void ListModel::insert(int elementIndex, QV4::Object *object)
++ {
++ insertElement(elementIndex);
++ set(elementIndex, object, SetElement::WasJustInserted);
++ }
++
++ int ListModel::append(QV4::Object *object)
++ {
++ int elementIndex = appendElement();
++ set(elementIndex, object, SetElement::WasJustInserted);
++ return elementIndex;
++ }
++
++ int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
++ {
++ int roleIndex = -1;
++
++ if (elementIndex >= 0 && elementIndex < elementCount()) {
++ ListElement *e = elements[elementIndex];
++
++ const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
++ if (r) {
++ roleIndex = e->setVariantProperty(*r, data);
++
++ ModelNodeMetaObject *cache = e->objectCache();
++
++ if (roleIndex != -1 && cache)
++ cache->updateValues(QList<int>(1, roleIndex));
++ }
++ }
++
++ return roleIndex;
++ }
++
++ int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng)
++ {
++ int roleIndex = -1;
++
++ if (elementIndex >= 0 && elementIndex < elementCount()) {
++ ListElement *e = elements[elementIndex];
++ const ListLayout::Role *r = m_layout->getExistingRole(key);
++ if (r)
++ roleIndex = e->setJsProperty(*r, data, eng);
++ }
++
++ return roleIndex;
++ }
++
++ inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
++ {
++ ListElement *e = this;
++ int blockIndex = 0;
++ while (blockIndex < role.blockIndex) {
++ if (e->next == nullptr) {
++ e->next = new ListElement;
++ e->next->uid = uid;
++ }
++ e = e->next;
++ ++blockIndex;
++ }
++
++ char *mem = &e->data[role.blockOffset];
++ return mem;
++ }
++
++ ModelNodeMetaObject *ListElement::objectCache()
++ {
++ if (!m_objectCache)
++ return nullptr;
++ return ModelNodeMetaObject::get(m_objectCache);
++ }
++
++ StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role)
++ {
++ char *mem = getPropertyMemory(role);
++ StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
++ return s;
++ }
++
++ QV4::QObjectWrapper *ListElement::getQObjectProperty(const ListLayout::Role &role)
++ {
++ char *mem = getPropertyMemory(role);
++ QV4::PersistentValue *g = reinterpret_cast<QV4::PersistentValue *>(mem);
++ return g->as<QV4::QObjectWrapper>();
++ }
++
++ QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
++ {
++ QVariantMap *map = nullptr;
++
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QVariantMap>(mem))
++ map = reinterpret_cast<QVariantMap *>(mem);
++
++ return map;
++ }
++
++ QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
++ {
++ QDateTime *dt = nullptr;
++
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QDateTime>(mem))
++ dt = reinterpret_cast<QDateTime *>(mem);
++
++ return dt;
++ }
++
++ QUrl *ListElement::getUrlProperty(const ListLayout::Role &role)
++ {
++ QUrl *url = nullptr;
++
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QUrl>(mem))
++ url = reinterpret_cast<QUrl *>(mem);
++
++ return url;
++ }
++
++ QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role)
++ {
++ QJSValue *f = nullptr;
++
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QJSValue>(mem))
++ f = reinterpret_cast<QJSValue *>(mem);
++
++ return f;
++ }
++
++ QV4::PersistentValue *
++ ListElement::getGuardProperty(const ListLayout::Role &role)
++ {
++ char *mem = getPropertyMemory(role);
++
++ bool existingGuard = false;
++ for (size_t i = 0; i < sizeof(QV4::PersistentValue);
++ ++i) {
++ if (mem[i] != 0) {
++ existingGuard = true;
++ break;
++ }
++ }
++
++ QV4::PersistentValue *g = nullptr;
++
++ if (existingGuard)
++ g = reinterpret_cast<QV4::PersistentValue *>(mem);
++
++ return g;
++ }
++
++ ListModel *ListElement::getListProperty(const ListLayout::Role &role)
++ {
++ char *mem = getPropertyMemory(role);
++ ListModel **value = reinterpret_cast<ListModel **>(mem);
++ return *value;
++ }
++
++ QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
++ {
++ char *mem = getPropertyMemory(role);
++
++ QVariant data;
++
++ switch (role.type) {
++ case ListLayout::Role::Number:
++ {
++ double *value = reinterpret_cast<double *>(mem);
++ data = *value;
++ }
++ break;
++ case ListLayout::Role::String:
++ {
++ StringOrTranslation *value = reinterpret_cast<StringOrTranslation *>(mem);
++ if (value->isSet())
++ data = value->toString(owner);
++ else
++ data = QString();
++ }
++ break;
++ case ListLayout::Role::Bool:
++ {
++ bool *value = reinterpret_cast<bool *>(mem);
++ data = *value;
++ }
++ break;
++ case ListLayout::Role::List:
++ {
++ ListModel **value = reinterpret_cast<ListModel **>(mem);
++ ListModel *model = *value;
++
++ if (model) {
++ if (model->m_modelCache == nullptr) {
++ model->m_modelCache = new QQmlListModel(owner, model, eng);
++ QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
++ }
++
++ QObject *object = model->m_modelCache;
++ data = QVariant::fromValue(object);
++ }
++ }
++ break;
++ case ListLayout::Role::QObject:
++ {
++ QV4::PersistentValue *guard = reinterpret_cast<QV4::PersistentValue *>(mem);
++ data = QVariant::fromValue(guard->as<QV4::QObjectWrapper>()->object());
++ }
++ break;
++ case ListLayout::Role::VariantMap:
++ {
++ if (isMemoryUsed<QVariantMap>(mem)) {
++ QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
++ data = *map;
++ }
++ }
++ break;
++ case ListLayout::Role::DateTime:
++ {
++ if (isMemoryUsed<QDateTime>(mem)) {
++ QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
++ data = *dt;
++ }
++ }
++ break;
++ case ListLayout::Role::Url:
++ {
++ if (isMemoryUsed<QUrl>(mem)) {
++ QUrl *url = reinterpret_cast<QUrl *>(mem);
++ data = *url;
++ }
++ }
++ break;
++ case ListLayout::Role::Function:
++ {
++ if (isMemoryUsed<QJSValue>(mem)) {
++ QJSValue *func = reinterpret_cast<QJSValue *>(mem);
++ data = QVariant::fromValue(*func);
++ }
++ }
++ break;
++ default:
++ break;
++ }
++
++ return data;
++ }
++
++ int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::String) {
++ char *mem = getPropertyMemory(role);
++ StringOrTranslation *c = reinterpret_cast<StringOrTranslation *>(mem);
++ bool changed;
++ if (!c->isSet() || c->isTranslation())
++ changed = true;
++ else
++ changed = c->asString().compare(s) != 0;
++ c->setString(s);
++ if (changed)
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::Number) {
++ char *mem = getPropertyMemory(role);
++ double *value = reinterpret_cast<double *>(mem);
++ bool changed = *value != d;
++ *value = d;
++ if (changed)
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::Bool) {
++ char *mem = getPropertyMemory(role);
++ bool *value = reinterpret_cast<bool *>(mem);
++ bool changed = *value != b;
++ *value = b;
++ if (changed)
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::List) {
++ char *mem = getPropertyMemory(role);
++ ListModel **value = reinterpret_cast<ListModel **>(mem);
++ if (*value && *value != m) {
++ (*value)->destroy();
++ delete *value;
++ }
++ *value = m;
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setQObjectProperty(const ListLayout::Role &role, QV4::QObjectWrapper *o)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::QObject) {
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QVariantMap>(mem))
++ reinterpret_cast<QV4::PersistentValue *>(mem)->set(o->engine(), *o);
++ else
++ new (mem) QV4::PersistentValue(o->engine(), o);
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::VariantMap) {
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QVariantMap>(mem)) {
++ QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
++ map->~QMap();
++ }
++ new (mem) QVariantMap(o->engine()->variantMapFromJS(o));
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::VariantMap) {
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QVariantMap>(mem)) {
++ QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
++ if (m && map->isSharedWith(*m))
++ return roleIndex;
++ map->~QMap();
++ } else if (!m) {
++ return roleIndex;
++ }
++ if (m)
++ new (mem) QVariantMap(*m);
++ else
++ new (mem) QVariantMap;
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::DateTime) {
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QDateTime>(mem)) {
++ QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
++ dt->~QDateTime();
++ }
++ new (mem) QDateTime(dt);
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setUrlProperty(const ListLayout::Role &role, const QUrl &url)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::Url) {
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QUrl>(mem)) {
++ QUrl *qurl = reinterpret_cast<QUrl *>(mem);
++ qurl->~QUrl();
++ }
++ new (mem) QUrl(url);
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::Function) {
++ char *mem = getPropertyMemory(role);
++ if (isMemoryUsed<QJSValue>(mem)) {
++ QJSValue *f = reinterpret_cast<QJSValue *>(mem);
++ f->~QJSValue();
++ }
++ new (mem) QJSValue(f);
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b)
++ {
++ int roleIndex = -1;
++
++ if (role.type == ListLayout::Role::String) {
++ char *mem = getPropertyMemory(role);
++ StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
++ s->setTranslation(b);
++ roleIndex = role.index;
++ }
++
++ return roleIndex;
++ }
++
++
++ void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
++ {
++ char *mem = getPropertyMemory(role);
++ reinterpret_cast<StringOrTranslation *>(mem)->setString(s);
++ }
++
++ void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
++ {
++ char *mem = getPropertyMemory(role);
++ double *value = new (mem) double;
++ *value = d;
++ }
++
++ void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
++ {
++ char *mem = getPropertyMemory(role);
++ bool *value = new (mem) bool;
++ *value = b;
++ }
++
++ void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QV4::QObjectWrapper *o)
++ {
++ char *mem = getPropertyMemory(role);
++ new (mem) QV4::PersistentValue(o->engine(), o);
++ }
++
++ void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
++ {
++ char *mem = getPropertyMemory(role);
++ ListModel **value = new (mem) ListModel *;
++ *value = m;
++ }
++
++ void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o)
++ {
++ char *mem = getPropertyMemory(role);
++ QVariantMap *map = new (mem) QVariantMap;
++ *map = o->engine()->variantMapFromJS(o);
++ }
++
++ void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt)
++ {
++ char *mem = getPropertyMemory(role);
++ new (mem) QDateTime(dt);
++ }
++
++ void ListElement::setUrlPropertyFast(const ListLayout::Role &role, const QUrl &url)
++ {
++ char *mem = getPropertyMemory(role);
++ new (mem) QUrl(url);
++ }
++
++ void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f)
++ {
++ char *mem = getPropertyMemory(role);
++ new (mem) QJSValue(f);
++ }
++
++ void ListElement::clearProperty(const ListLayout::Role &role)
++ {
++ switch (role.type) {
++ case ListLayout::Role::String:
++ setStringProperty(role, QString());
++ break;
++ case ListLayout::Role::Number:
++ setDoubleProperty(role, 0.0);
++ break;
++ case ListLayout::Role::Bool:
++ setBoolProperty(role, false);
++ break;
++ case ListLayout::Role::List:
++ setListProperty(role, nullptr);
++ break;
++ case ListLayout::Role::QObject:
++ setQObjectProperty(role, nullptr);
++ break;
++ case ListLayout::Role::DateTime:
++ setDateTimeProperty(role, QDateTime());
++ break;
++ case ListLayout::Role::Url:
++ setUrlProperty(role, QUrl());
++ break;
++ case ListLayout::Role::VariantMap:
++ setVariantMapProperty(role, (QVariantMap *)nullptr);
++ break;
++ case ListLayout::Role::Function:
++ setFunctionProperty(role, QJSValue());
++ break;
++ default:
++ break;
++ }
++ }
++
++ ListElement::ListElement()
++ {
++ m_objectCache = nullptr;
++ uid = uidCounter.fetchAndAddOrdered(1);
++ next = nullptr;
++ memset(data, 0, sizeof(data));
++ }
++
++ ListElement::ListElement(int existingUid)
++ {
++ m_objectCache = nullptr;
++ uid = existingUid;
++ next = nullptr;
++ memset(data, 0, sizeof(data));
++ }
++
++ ListElement::~ListElement()
++ {
++ delete next;
++ }
++
++ QList<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
++ {
++ QList<int> changedRoles;
++ for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
++ const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
++ const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
++
++ int roleIndex = -1;
++ switch (srcRole.type) {
++ case ListLayout::Role::List:
++ {
++ ListModel *srcSubModel = src->getListProperty(srcRole);
++ ListModel *targetSubModel = target->getListProperty(targetRole);
++
++ if (srcSubModel) {
++ if (targetSubModel == nullptr) {
++ targetSubModel = new ListModel(targetRole.subLayout, nullptr);
++ target->setListPropertyFast(targetRole, targetSubModel);
++ }
++ if (ListModel::sync(srcSubModel, targetSubModel))
++ roleIndex = targetRole.index;
++ }
++ }
++ break;
++ case ListLayout::Role::QObject:
++ {
++ QV4::QObjectWrapper *object = src->getQObjectProperty(srcRole);
++ roleIndex = target->setQObjectProperty(targetRole, object);
++ }
++ break;
++ case ListLayout::Role::String:
++ case ListLayout::Role::Number:
++ case ListLayout::Role::Bool:
++ case ListLayout::Role::DateTime:
++ case ListLayout::Role::Function:
++ {
++ QVariant v = src->getProperty(srcRole, nullptr, nullptr);
++ roleIndex = target->setVariantProperty(targetRole, v);
++ }
++ break;
++ case ListLayout::Role::VariantMap:
++ {
++ QVariantMap *map = src->getVariantMapProperty(srcRole);
++ roleIndex = target->setVariantMapProperty(targetRole, map);
++ }
++ break;
++ default:
++ break;
++ }
++ if (roleIndex >= 0)
++ changedRoles << roleIndex;
++ }
++
++ return changedRoles;
++ }
++
++ void ListElement::destroy(ListLayout *layout)
++ {
++ if (layout) {
++ for (int i=0 ; i < layout->roleCount() ; ++i) {
++ const ListLayout::Role &r = layout->getExistingRole(i);
++
++ switch (r.type) {
++ case ListLayout::Role::String:
++ {
++ StringOrTranslation *string = getStringProperty(r);
++ if (string)
++ string->~StringOrTranslation();
++ }
++ break;
++ case ListLayout::Role::List:
++ {
++ ListModel *model = getListProperty(r);
++ if (model) {
++ model->destroy();
++ delete model;
++ }
++ }
++ break;
++ case ListLayout::Role::QObject:
++ {
++ if (QV4::PersistentValue *guard = getGuardProperty(r))
++ guard->~PersistentValue();
++ }
++ break;
++ case ListLayout::Role::VariantMap:
++ {
++ QVariantMap *map = getVariantMapProperty(r);
++ if (map)
++ map->~QMap();
++ }
++ break;
++ case ListLayout::Role::DateTime:
++ {
++ QDateTime *dt = getDateTimeProperty(r);
++ if (dt)
++ dt->~QDateTime();
++ }
++ break;
++ case ListLayout::Role::Url:
++ {
++ QUrl *url = getUrlProperty(r);
++ if (url)
++ url->~QUrl();
++ break;
++ }
++ case ListLayout::Role::Function:
++ {
++ QJSValue *f = getFunctionProperty(r);
++ if (f)
++ f->~QJSValue();
++ }
++ break;
++ default:
++ // other types don't need explicit cleanup.
++ break;
++ }
++ }
++
++ if (m_objectCache) {
++ m_objectCache->~QObject();
++ operator delete(m_objectCache);
++ }
++ }
++
++ if (next)
++ next->destroy(nullptr);
++ uid = -1;
++ }
++
++ int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
++ {
++ int roleIndex = -1;
++
++ switch (role.type) {
++ case ListLayout::Role::Number:
++ roleIndex = setDoubleProperty(role, d.toDouble());
++ break;
++ case ListLayout::Role::String:
++ if (d.userType() == qMetaTypeId<const QV4::CompiledData::Binding *>())
++ roleIndex = setTranslationProperty(role, d.value<const QV4::CompiledData::Binding*>());
++ else
++ roleIndex = setStringProperty(role, d.toString());
++ break;
++ case ListLayout::Role::Bool:
++ roleIndex = setBoolProperty(role, d.toBool());
++ break;
++ case ListLayout::Role::List:
++ roleIndex = setListProperty(role, d.value<ListModel *>());
++ break;
++ case ListLayout::Role::VariantMap: {
++ QVariantMap map = d.toMap();
++ roleIndex = setVariantMapProperty(role, &map);
++ }
++ break;
++ case ListLayout::Role::DateTime:
++ roleIndex = setDateTimeProperty(role, d.toDateTime());
++ break;
++ case ListLayout::Role::Url:
++ roleIndex = setUrlProperty(role, d.toUrl());
++ break;
++ case ListLayout::Role::Function:
++ roleIndex = setFunctionProperty(role, d.value<QJSValue>());
++ break;
++ default:
++ break;
++ }
++
++ return roleIndex;
++ }
++
++ int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng)
++ {
++ // Check if this key exists yet
++ int roleIndex = -1;
++
++ QV4::Scope scope(eng);
++
++ // Add the value now
++ if (d.isString()) {
++ QString qstr = d.toQString();
++ roleIndex = setStringProperty(role, qstr);
++ } else if (d.isNumber()) {
++ roleIndex = setDoubleProperty(role, d.asDouble());
++ } else if (d.as<QV4::ArrayObject>()) {
++ QV4::ScopedArrayObject a(scope, d);
++ if (role.type == ListLayout::Role::List) {
++ QV4::Scope scope(a->engine());
++ QV4::ScopedObject o(scope);
++
++ ListModel *subModel = new ListModel(role.subLayout, nullptr);
++ int arrayLength = a->getLength();
++ for (int j=0 ; j < arrayLength ; ++j) {
++ o = a->get(j);
++ subModel->append(o);
++ }
++ roleIndex = setListProperty(role, subModel);
++ } else {
++ 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));
++ }
++ } else if (d.isBoolean()) {
++ roleIndex = setBoolProperty(role, d.booleanValue());
++ } else if (d.as<QV4::DateObject>()) {
++ QV4::Scoped<QV4::DateObject> dd(scope, d);
++ QDateTime dt = dd->toQDateTime();
++ roleIndex = setDateTimeProperty(role, dt);
++ } else if (d.as<QV4::UrlObject>()) {
++ QV4::Scoped<QV4::UrlObject> url(scope, d);
++ QUrl qurl = QUrl(url->href());
++ roleIndex = setUrlProperty(role, qurl);
++ } else if (d.as<QV4::FunctionObject>()) {
++ QV4::ScopedFunctionObject f(scope, d);
++ QJSValue jsv;
++ QJSValuePrivate::setValue(&jsv, f);
++ roleIndex = setFunctionProperty(role, jsv);
++ } else if (d.isObject()) {
++ QV4::ScopedObject o(scope, d);
++ QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>();
++ if (role.type == ListLayout::Role::QObject && wrapper) {
++ roleIndex = setQObjectProperty(role, wrapper);
++ } else if (role.type == ListLayout::Role::VariantMap) {
++ roleIndex = setVariantMapProperty(role, o);
++ } else if (role.type == ListLayout::Role::Url) {
++ QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
++ // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
++ QV4::Value::fromReturnedValue(o.asReturnedValue()),
++ QMetaType::fromType<QUrl>(), true);
++ if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
++ roleIndex = setUrlProperty(role, maybeUrl.toUrl());
++ }
++ }
++ } else if (d.isNullOrUndefined()) {
++ clearProperty(role);
++ }
++
++ return roleIndex;
++ }
++
++ ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex)
++ : QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false)
++ {}
++
++ void ModelNodeMetaObject::initialize()
++ {
++ const int roleCount = m_model->m_listModel->roleCount();
++ QList<QByteArray> properties;
++ properties.reserve(roleCount);
++ for (int i = 0 ; i < roleCount ; ++i) {
++ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
++ QByteArray name = role.name.toUtf8();
++ properties << name;
++ }
++ type()->createProperties(properties);
++ updateValues();
++ m_enabled = true;
++ }
++
++ ModelNodeMetaObject::~ModelNodeMetaObject()
++ {
++ }
++
++ #if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
++ const QMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object) const
++ #else
++ QMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object)
++ #endif
++ {
++ if (!m_initialized) {
++ m_initialized = true;
++ const_cast<ModelNodeMetaObject *>(this)->initialize();
++ }
++ return QQmlOpenMetaObject::toDynamicMetaObject(object);
++ }
++
++ ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj)
++ {
++ QObjectPrivate *op = QObjectPrivate::get(obj);
++ return static_cast<ModelNodeMetaObject*>(op->metaObject);
++ }
++
++ void ModelNodeMetaObject::updateValues()
++ {
++ Q_ALLOCA_INIT();
++ const int roleCount = m_model->m_listModel->roleCount();
++ if (!m_initialized) {
++ if (roleCount) {
++ Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int));
++ for (int i = 0; i < roleCount; ++i)
++ changedRoles[i] = i;
++ emitDirectNotifies(changedRoles, roleCount);
++ }
++ return;
++ }
++ for (int i=0 ; i < roleCount ; ++i) {
++ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
++ QByteArray name = role.name.toUtf8();
++ const QVariant &data = m_model->data(m_elementIndex, i);
++ setValue(name, data, role.type == ListLayout::Role::List);
++ }
++ }
++
++ void ModelNodeMetaObject::updateValues(const QList<int> &roles)
++ {
++ if (!m_initialized) {
++ emitDirectNotifies(roles.constData(), roles.size());
++ return;
++ }
++ int roleCount = roles.size();
++ for (int i=0 ; i < roleCount ; ++i) {
++ int roleIndex = roles.at(i);
++ const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
++ QByteArray name = role.name.toUtf8();
++ const QVariant &data = m_model->data(m_elementIndex, roleIndex);
++ setValue(name, data, role.type == ListLayout::Role::List);
++ }
++ }
++
++ void ModelNodeMetaObject::propertyWritten(int index)
++ {
++ if (!m_enabled)
++ return;
++
++ QString propName = QString::fromUtf8(name(index));
++ const QVariant value = this->value(index);
++
++ QV4::Scope scope(m_model->engine());
++ QV4::ScopedValue v(scope, scope.engine->fromVariant(value));
++
++ int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine);
++ if (roleIndex != -1)
++ m_model->emitItemsChanged(m_elementIndex, 1, QList<int>(1, roleIndex));
++ }
++
++ // Does the emission of the notifiers when we haven't created the meta-object yet
++ void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount)
++ {
++ Q_ASSERT(!m_initialized);
++ QQmlData *ddata = QQmlData::get(object(), /*create*/false);
++ if (!ddata)
++ return;
++ // There's nothing to emit if we're a list model in a worker thread.
++ if (!qmlEngine(m_model))
++ return;
++ for (int i = 0; i < roleCount; ++i) {
++ const int changedRole = changedRoles[i];
++ QQmlNotifier::notify(ddata, changedRole);
++ }
++ }
++
++ namespace QV4 {
++
++ bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
++ {
++ if (!id.isString())
++ return Object::virtualPut(m, id, value, receiver);
++ QString propName = id.toQString();
++
++ ModelObject *that = static_cast<ModelObject*>(m);
++ if (!that->object())
++ return false;
++
++ ExecutionEngine *eng = that->engine();
++ const int elementIndex = that->d()->elementIndex();
++ if (QQmlListModel *model = that->d()->m_model) {
++ const int roleIndex
++ = model->listModel()->setExistingProperty(elementIndex, propName, value, eng);
++ if (roleIndex != -1)
++ model->emitItemsChanged(elementIndex, 1, QList<int>(1, roleIndex));
++ }
++
++ ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object());
++ if (mo->initialized())
++ mo->emitPropertyNotification(propName.toUtf8());
++ return true;
++ }
++
++ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
++ {
++ if (!id.isString())
++ return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
++
++ const ModelObject *that = static_cast<const ModelObject*>(m);
++ Scope scope(that);
++ ScopedString name(scope, id.asStringOrSymbol());
++ QQmlListModel *model = that->d()->m_model;
++ if (!model || !that->object())
++ return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
++
++ const ListLayout::Role *role = model->listModel()->getExistingRole(name);
++ if (!role)
++ return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
++ if (hasProperty)
++ *hasProperty = true;
++
++ if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) {
++ QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine);
++ if (ep && ep->propertyCapture)
++ ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false);
++ }
++
++ const int elementIndex = that->d()->elementIndex();
++ QVariant value = model->data(elementIndex, role->index);
++ return that->engine()->fromVariant(value);
++ }
++
++ ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
++ {
++ lookup->call = Lookup::Call::GetterQObjectPropertyFallback;
++ return lookup->getter(engine, *object);
++ }
++
++ struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
++ {
++ int roleNameIndex = 0;
++ ~ModelObjectOwnPropertyKeyIterator() override = default;
++ PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
++
++ };
++
++ PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
++ {
++ const ModelObject *that = static_cast<const ModelObject *>(o);
++
++ ExecutionEngine *v4 = that->engine();
++
++ QQmlListModel *model = that->d()->m_model;
++ ListModel *listModel = model ? model->listModel() : nullptr;
++ if (listModel && roleNameIndex < listModel->roleCount()) {
++ Scope scope(that->engine());
++ const ListLayout::Role &role = listModel->getExistingRole(roleNameIndex);
++ ++roleNameIndex;
++ ScopedString roleName(scope, v4->newString(role.name));
++ if (attrs)
++ *attrs = QV4::Attr_Data;
++ if (pd) {
++
++ QVariant value = model->data(that->d()->elementIndex(), role.index);
++ if (auto recursiveListModel = qvariant_cast<QQmlListModel*>(value)) {
++ auto size = recursiveListModel->count();
++ auto array = ScopedArrayObject{scope, v4->newArrayObject(size)};
++ QV4::ScopedValue val(scope);
++ for (auto i = 0; i < size; i++) {
++ val = QJSValuePrivate::convertToReturnedValue(
++ v4, recursiveListModel->get(i));
++ array->arrayPut(i, val);
++ }
++ pd->value = array;
++ } else {
++ pd->value = v4->fromVariant(value);
++ }
++ }
++ return roleName->toPropertyKey();
++ }
++
++ // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add
++ // unnecessary entries that relate to the roles used. These just create extra work
++ // later on as they will just be ignored.
++ return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
++ }
++
++ OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target)
++ {
++ *target = *m;
++ return new ModelObjectOwnPropertyKeyIterator;
++ }
++
++ DEFINE_OBJECT_VTABLE(ModelObject);
++
++ } // namespace QV4
++
++ DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
++ {
++ setNodeUpdatesEnabled(true);
++ }
++
++ DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner)
++ {
++ DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
++ QList<int> roles;
++ object->updateValues(obj, roles);
++ return object;
++ }
++
++ QList<int> DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target)
++ {
++ QList<int> changedRoles;
++ for (int i = 0; i < src->m_meta->count(); ++i) {
++ const QByteArray &name = src->m_meta->name(i);
++ QVariant value = src->m_meta->value(i);
++
++ QQmlListModel *srcModel = qobject_cast<QQmlListModel *>(value.value<QObject *>());
++ QQmlListModel *targetModel = qobject_cast<QQmlListModel *>(target->m_meta->value(i).value<QObject *>());
++
++ bool modelHasChanges = false;
++ if (srcModel) {
++ if (targetModel == nullptr)
++ targetModel = QQmlListModel::createWithOwner(target->m_owner);
++
++ modelHasChanges = QQmlListModel::sync(srcModel, targetModel);
++
++ QObject *targetModelObject = targetModel;
++ value = QVariant::fromValue(targetModelObject);
++ } else if (targetModel) {
++ delete targetModel;
++ }
++
++ if (target->setValue(name, value) || modelHasChanges)
++ changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name));
++ }
++ return changedRoles;
++ }
++
++ void DynamicRoleModelNode::updateValues(const QVariantMap &object, QList<int> &roles)
++ {
++ for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) {
++ const QString &key = it.key();
++
++ int roleIndex = m_owner->m_roles.indexOf(key);
++ if (roleIndex == -1) {
++ roleIndex = m_owner->m_roles.size();
++ m_owner->m_roles.append(key);
++ }
++
++ QVariant value = it.value();
++
++ // A JS array/object is translated into a (hierarchical) QQmlListModel,
++ // so translate to a variant map/list first with toVariant().
++ if (value.userType() == qMetaTypeId<QJSValue>())
++ value = value.value<QJSValue>().toVariant();
++
++ if (value.userType() == QMetaType::QVariantList) {
++ QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);
++
++ QVariantList subArray = value.toList();
++ QVariantList::const_iterator subIt = subArray.cbegin();
++ QVariantList::const_iterator subEnd = subArray.cend();
++ while (subIt != subEnd) {
++ const QVariantMap &subObject = subIt->toMap();
++ subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
++ ++subIt;
++ }
++
++ QObject *subModelObject = subModel;
++ value = QVariant::fromValue(subModelObject);
++ }
++
++ const QByteArray &keyUtf8 = key.toUtf8();
++
++ QQmlListModel *existingModel = qobject_cast<QQmlListModel *>(m_meta->value(keyUtf8).value<QObject *>());
++ delete existingModel;
++
++ if (m_meta->setValue(keyUtf8, value))
++ roles << roleIndex;
++ }
++ }
++
++ DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
++ : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
++ {
++ }
++
++ DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
++ {
++ for (int i=0 ; i < count() ; ++i) {
++ QQmlListModel *subModel = qobject_cast<QQmlListModel *>(value(i).value<QObject *>());
++ delete subModel;
++ }
++ }
++
++ void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
++ {
++ if (!m_enabled)
++ return;
++
++ QVariant v = value(index);
++ QQmlListModel *model = qobject_cast<QQmlListModel *>(v.value<QObject *>());
++ delete model;
++ }
++
++ void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
++ {
++ if (!m_enabled)
++ return;
++
++ QQmlListModel *parentModel = m_owner->m_owner;
++
++ QVariant v = value(index);
++
++ // A JS array/object is translated into a (hierarchical) QQmlListModel,
++ // so translate to a variant map/list first with toVariant().
++ if (v.userType() == qMetaTypeId<QJSValue>())
++ v= v.value<QJSValue>().toVariant();
++
++ if (v.userType() == QMetaType::QVariantList) {
++ QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);
++
++ QVariantList subArray = v.toList();
++ QVariantList::const_iterator subIt = subArray.cbegin();
++ QVariantList::const_iterator subEnd = subArray.cend();
++ while (subIt != subEnd) {
++ const QVariantMap &subObject = subIt->toMap();
++ subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
++ ++subIt;
++ }
++
++ QObject *subModelObject = subModel;
++ v = QVariant::fromValue(subModelObject);
++
++ setValue(index, v);
++ }
++
++ int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
++ if (elementIndex != -1) {
++ int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
++ if (roleIndex != -1)
++ parentModel->emitItemsChanged(elementIndex, 1, QList<int>(1, roleIndex));
++ }
++ }
++
++ /*!
++ \qmltype ListModel
++ \nativetype QQmlListModel
++ //! \inherits AbstractListModel
++ \inqmlmodule QtQml.Models
++ \ingroup qtquick-models
++ \brief Defines a free-form list data source.
++
++ The ListModel is a simple container of ListElement definitions, each
++ containing data roles. The contents can be defined dynamically, or
++ explicitly in QML.
++
++ The number of elements in the model can be obtained from its \l count property.
++ A number of familiar methods are also provided to manipulate the contents of the
++ model, including append(), insert(), move(), remove() and set(). These methods
++ accept dictionaries as their arguments; these are translated to ListElement objects
++ by the model.
++
++ Elements can be manipulated via the model using the setProperty() method, which
++ allows the roles of the specified element to be set and changed.
++
++ ListModel inherits from \l{QAbstractListModel} and provides its \l{Q_INVOKABLE}
++ methods. You can, for example use \l{QAbstractItemModel::index} to retrieve a
++ \l{QModelIndex} for a row and column.
++
++ \section1 Example Usage
++
++ The following example shows a ListModel containing three elements, with the roles
++ "name" and "cost".
++
++ \div {class="float-right"}
++ \inlineimage listmodel.png
++ {List showing Apple, Orange, and Banana with prices}
++ \enddiv
++
++ \snippet qml/listmodel/listmodel.qml 0
++
++ Roles (properties) in each element must begin with a lower-case letter and
++ should be common to all elements in a model. The ListElement documentation
++ provides more guidelines for how elements should be defined.
++
++ Since the example model contains an \c id property, it can be referenced
++ by views, such as the ListView in this example:
++
++ \snippet qml/listmodel/listmodel-simple.qml 0
++ \dots 8
++ \snippet qml/listmodel/listmodel-simple.qml 1
++
++ It is possible for roles to contain list data. In the following example we
++ create a list of fruit attributes:
++
++ \snippet qml/listmodel/listmodel-nested.qml model
++
++ The delegate displays all the fruit attributes:
++
++ \div {class="float-right"}
++ \inlineimage listmodel-nested.png
++ {List showing fruits with nested attribute data}
++ \enddiv
++
++ \snippet qml/listmodel/listmodel-nested.qml delegate
++
++ \section1 Modifying List Models
++
++ The content of a ListModel may be created and modified using the clear(),
++ append(), set(), insert() and setProperty() methods. For example:
++
++ \snippet qml/listmodel/listmodel-modify.qml delegate
++
++ Note that when creating content dynamically the set of available properties
++ cannot be changed once set. Whatever properties are first added to the model
++ are the only permitted properties in the model.
++
++ \section1 Using Threaded List Models with WorkerScript
++
++ ListModel can be used together with WorkerScript to access a list model
++ from multiple threads. This is useful if list modifications are
++ synchronous and take some time: the list operations can be moved to a
++ different thread to avoid blocking of the main GUI thread.
++
++ Here is an example that uses WorkerScript to periodically append the
++ current time to a list model:
++
++ \snippet qml/listmodel/WorkerScript.qml 0
++
++ The included file, \tt dataloader.mjs, looks like this:
++
++ \snippet qml/listmodel/dataloader.mjs 0
++
++ The timer in the main example sends messages to the worker script by calling
++ \l WorkerScript::sendMessage(). When this message is received,
++ \c WorkerScript.onMessage() is invoked in \c dataloader.mjs,
++ which appends the current time to the list model.
++
++ Note the call to sync() from the external thread.
++ You must call sync() or else the changes made to the list from that
++ thread will not be reflected in the list model in the main thread.
++
++ \sa {qml-data-models}{Data Models}, {Qt Qml}
++ */
++
++ QQmlListModel::QQmlListModel(QObject *parent)
++ : QAbstractListModel(parent)
++ {
++ m_mainThread = true;
++ m_primary = true;
++ m_agent = nullptr;
++ m_dynamicRoles = false;
++
++ m_layout = new ListLayout;
++ m_listModel = new ListModel(m_layout, this);
++
++ m_engine = nullptr;
++ }
++
++ QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent)
++ : QAbstractListModel(parent)
++ {
++ m_mainThread = owner->m_mainThread;
++ m_primary = false;
++ m_agent = owner->m_agent;
++
++ Q_ASSERT(owner->m_dynamicRoles == false);
++ m_dynamicRoles = false;
++ m_layout = nullptr;
++ m_listModel = data;
++
++ m_engine = engine;
++ m_compilationUnit = owner->m_compilationUnit;
++ }
++
++ QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent)
++ : QAbstractListModel(agent)
++ {
++ m_mainThread = false;
++ m_primary = true;
++ m_agent = agent;
++ m_dynamicRoles = orig->m_dynamicRoles;
++
++ if (ListLayout *layout = orig->m_layout)
++ m_layout = new ListLayout(layout);
++ else
++ m_layout = new ListLayout;
++
++ m_listModel = new ListModel(m_layout, this);
++
++ if (m_dynamicRoles)
++ sync(orig, this);
++ else
++ ListModel::sync(orig->m_listModel, m_listModel);
++
++ m_engine = nullptr;
++ m_compilationUnit = orig->m_compilationUnit;
++ }
++
++ QQmlListModel::~QQmlListModel()
++ {
++ qDeleteAll(m_modelObjects);
++
++ if (m_primary) {
++ m_listModel->destroy();
++ delete m_listModel;
++
++ if (m_mainThread && m_agent)
++ m_agent->modelDestroyed();
++ }
++
++ if (m_agent && m_ownAgent)
++ m_agent->release();
++
++ m_listModel = nullptr;
++
++ delete m_layout;
++ m_layout = nullptr;
++ }
++
++ QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
++ {
++ QQmlListModel *model = new QQmlListModel;
++
++ model->m_mainThread = newOwner->m_mainThread;
++ model->m_engine = newOwner->m_engine;
++ model->m_agent = newOwner->m_agent;
++ model->m_dynamicRoles = newOwner->m_dynamicRoles;
++
++ QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
++
++ return model;
++ }
++
++ QV4::ExecutionEngine *QQmlListModel::engine() const
++ {
++ if (m_engine == nullptr) {
++ m_engine = qmlEngine(this)->handle();
++ }
++
++ return m_engine;
++ }
++
++ bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target)
++ {
++ Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
++
++ bool hasChanges = false;
++
++ target->m_roles = src->m_roles;
++
++ // Build hash of elements <-> uid for each of the lists
++ QHash<int, ElementSync> elementHash;
++ for (int i = 0 ; i < target->m_modelObjects.size(); ++i) {
++ DynamicRoleModelNode *e = target->m_modelObjects.at(i);
++ int uid = e->getUid();
++ ElementSync sync;
++ sync.target = e;
++ sync.targetIndex = i;
++ elementHash.insert(uid, sync);
++ }
++ for (int i = 0 ; i < src->m_modelObjects.size(); ++i) {
++ DynamicRoleModelNode *e = src->m_modelObjects.at(i);
++ int uid = e->getUid();
++
++ QHash<int, ElementSync>::iterator it = elementHash.find(uid);
++ if (it == elementHash.end()) {
++ ElementSync sync;
++ sync.src = e;
++ sync.srcIndex = i;
++ elementHash.insert(uid, sync);
++ } else {
++ ElementSync &sync = it.value();
++ sync.src = e;
++ sync.srcIndex = i;
++ }
++ }
++
++ // Get list of elements that are in the target but no longer in the source. These get deleted first.
++ int rowsRemoved = 0;
++ for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
++ DynamicRoleModelNode *element = target->m_modelObjects.at(i);
++ ElementSync &s = elementHash.find(element->getUid()).value();
++ Q_ASSERT(s.targetIndex >= 0);
++ // need to update the targetIndex, to keep it correct after removals
++ s.targetIndex -= rowsRemoved;
++ if (s.src == nullptr) {
++ Q_ASSERT(s.targetIndex == i);
++ hasChanges = true;
++ target->beginRemoveRows(QModelIndex(), i, i);
++ target->m_modelObjects.remove(i, 1);
++ target->endRemoveRows();
++ delete s.target;
++ ++rowsRemoved;
++ --i;
++ continue;
++ }
++ }
++
++ // Clear the target list, and append in correct order from the source
++ target->m_modelObjects.clear();
++ for (int i = 0 ; i < src->m_modelObjects.size() ; ++i) {
++ DynamicRoleModelNode *element = src->m_modelObjects.at(i);
++ ElementSync &s = elementHash.find(element->getUid()).value();
++ Q_ASSERT(s.srcIndex >= 0);
++ DynamicRoleModelNode *targetElement = s.target;
++ if (targetElement == nullptr) {
++ targetElement = new DynamicRoleModelNode(target, element->getUid());
++ }
++ s.changedRoles = DynamicRoleModelNode::sync(element, targetElement);
++ target->m_modelObjects.append(targetElement);
++ }
++
++ // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
++ // so the model indices can't be out of bounds
++ //
++ // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
++ // model indices are updated correctly
++ int rowsInserted = 0;
++ for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
++ DynamicRoleModelNode *element = target->m_modelObjects.at(i);
++ ElementSync &s = elementHash.find(element->getUid()).value();
++ Q_ASSERT(s.srcIndex >= 0);
++ s.srcIndex += rowsInserted;
++ if (s.srcIndex != s.targetIndex) {
++ if (s.targetIndex == -1) {
++ target->beginInsertRows(QModelIndex(), i, i);
++ target->endInsertRows();
++ } else {
++ target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
++ target->endMoveRows();
++ }
++ hasChanges = true;
++ ++rowsInserted;
++ }
++ if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
++ QModelIndex idx = target->createIndex(i, 0);
++ emit target->dataChanged(idx, idx, s.changedRoles);
++ hasChanges = true;
++ }
++ }
++ return hasChanges;
++ }
++
++ void QQmlListModel::emitItemsChanged(int index, int count, const QList<int> &roles)
++ {
++ if (count <= 0)
++ return;
++
++ if (m_mainThread)
++ emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
++ }
++
++ void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
++ {
++ Q_ASSERT(index >= 0 && count >= 0);
++ if (m_mainThread)
++ beginInsertRows(QModelIndex(), index, index + count - 1);
++ }
++
++ void QQmlListModel::emitItemsInserted()
++ {
++ if (m_mainThread) {
++ endInsertRows();
++ emit countChanged();
++ }
++ }
++
++ QQmlListModelWorkerAgent *QQmlListModel::agent()
++ {
++ if (m_agent)
++ return m_agent;
++
++ m_agent = new QQmlListModelWorkerAgent(this);
++ m_ownAgent = true;
++ return m_agent;
++ }
++
++ QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const
++ {
++ return row >= 0 && row < count() && column == 0 && !parent.isValid()
++ ? createIndex(row, column)
++ : QModelIndex();
++ }
++
++ int QQmlListModel::rowCount(const QModelIndex &parent) const
++ {
++ return !parent.isValid() ? count() : 0;
++ }
++
++ QVariant QQmlListModel::data(const QModelIndex &index, int role) const
++ {
++ return data(index.row(), role);
++ }
++
++ bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
++ {
++ const int row = index.row();
++ if (row >= count() || row < 0)
++ return false;
++
++ if (m_dynamicRoles) {
++ const QByteArray property = m_roles.at(role).toUtf8();
++ if (m_modelObjects[row]->setValue(property, value)) {
++ emitItemsChanged(row, 1, QList<int>(1, role));
++ return true;
++ }
++ } else {
++ const ListLayout::Role &r = m_listModel->getExistingRole(role);
++ const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value);
++ if (roleIndex != -1) {
++ emitItemsChanged(row, 1, QList<int>(1, role));
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ QVariant QQmlListModel::data(int index, int role) const
++ {
++ QVariant v;
++
++ if (index >= count() || index < 0)
++ return v;
++
++ if (m_dynamicRoles)
++ v = m_modelObjects[index]->getValue(m_roles[role]);
++ else
++ v = m_listModel->getProperty(index, role, this, engine());
++
++ return v;
++ }
++
++ QHash<int, QByteArray> QQmlListModel::roleNames() const
++ {
++ QHash<int, QByteArray> roleNames;
++
++ if (m_dynamicRoles) {
++ const auto size = m_roles.size();
++ roleNames.reserve(size);
++ for (int i = 0 ; i < size ; ++i)
++ roleNames.insert(i, m_roles.at(i).toUtf8());
++ } else {
++ const auto size = m_listModel->roleCount();
++ roleNames.reserve(size);
++ for (int i = 0 ; i < size; ++i) {
++ const ListLayout::Role &r = m_listModel->getExistingRole(i);
++ roleNames.insert(i, r.name.toUtf8());
++ }
++ }
++
++ return roleNames;
++ }
++
++ /*!
++ \qmlproperty bool ListModel::dynamicRoles
++
++ By default, the type of a role is fixed the first time
++ the role is used. For example, if you create a role called
++ "data" and assign a number to it, you can no longer assign
++ a string to the "data" role. However, when the dynamicRoles
++ property is enabled, the type of a given role is not fixed
++ and can be different between elements.
++
++ The dynamicRoles property must be set before any data is
++ added to the ListModel, and must be set from the main
++ thread.
++
++ A ListModel that has data statically defined (via the
++ ListElement QML syntax) cannot have the dynamicRoles
++ property enabled.
++
++ There is a significant performance cost to using a
++ ListModel with dynamic roles enabled. The cost varies
++ from platform to platform but is typically somewhere
++ between 4-6x slower than using static role types.
++
++ Due to the performance cost of using dynamic roles,
++ they are disabled by default.
++ */
++ void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
++ {
++ if (m_mainThread && m_agent == nullptr) {
++ if (enableDynamicRoles) {
++ if (m_layout && m_layout->roleCount())
++ qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
++ else
++ m_dynamicRoles = true;
++ } else {
++ if (m_roles.size()) {
++ qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
++ } else {
++ m_dynamicRoles = false;
++ }
++ }
++ } else {
++ qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
++ }
++ }
++
++ /*!
++ \qmlproperty int ListModel::count
++ The number of data entries in the model.
++ */
++ int QQmlListModel::count() const
++ {
++ return m_dynamicRoles ? m_modelObjects.size() : m_listModel->elementCount();
++ }
++
++ /*!
++ \qmlmethod void ListModel::clear()
++
++ Deletes all content from the model. In particular this invalidates all objects you may have
++ retrieved using \l get().
++
++ \sa append(), remove(), get()
++ */
++ void QQmlListModel::clear()
++ {
++ removeElements(0, count());
++ }
++
++ /*!
++ \qmlmethod void ListModel::remove(int index, int count = 1)
++
++ Deletes \a count number of items at \a index from the model.
++
++ \sa clear()
++ */
++ void QQmlListModel::remove(QQmlV4FunctionPtr args)
++ {
++ int argLength = args->length();
++
++ if (argLength == 1 || argLength == 2) {
++ QV4::Scope scope(args->v4engine());
++ int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
++ int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);
++
++ if (index < 0 || index+removeCount > count() || removeCount <= 0) {
++ qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
++ return;
++ }
++
++ removeElements(index, removeCount);
++ } else {
++ qmlWarning(this) << tr("remove: incorrect number of arguments");
++ }
++ }
++
++ void QQmlListModel::removeElements(int index, int removeCount)
++ {
++ Q_ASSERT(index >= 0 && removeCount >= 0);
++
++ if (!removeCount)
++ return;
++
++ if (m_mainThread)
++ beginRemoveRows(QModelIndex(), index, index + removeCount - 1);
++
++ QList<std::function<void()>> toDestroy;
++ if (m_dynamicRoles) {
++ for (int i=0 ; i < removeCount ; ++i) {
++ auto modelObject = m_modelObjects[index+i];
++ toDestroy.append([modelObject](){
++ delete modelObject;
++ });
++ }
++ m_modelObjects.remove(index, removeCount);
++ } else {
++ toDestroy = m_listModel->remove(index, removeCount);
++ }
++
++ if (m_mainThread) {
++ endRemoveRows();
++ emit countChanged();
++ }
++ for (const auto &destroyer : std::as_const(toDestroy))
++ destroyer();
++ }
++
++ void QQmlListModel::updateTranslations()
++ {
++ // assumption: it is impossible to have retranslatable strings in a
++ // dynamic list model, as they would already have "decayed" to strings
++ // when they were inserted
++ if (m_dynamicRoles)
++ return;
++ Q_ASSERT(m_listModel);
++
++ QList<int> roles;
++ for (int i = 0, end = m_listModel->roleCount(); i != end; ++i) {
++ if (m_listModel->getExistingRole(i).type == ListLayout::Role::String)
++ roles.append(i);
++ }
++
++ if (!roles.isEmpty())
++ emitItemsChanged(0, rowCount(QModelIndex()), roles);
++
++ m_listModel->updateTranslations();
++ }
++
++ /*!
++ \qmlmethod void ListModel::insert(int index, jsobject dict)
++
++ Adds a new item to the list model at position \a index, with the
++ values in \a dict.
++
++ \code
++ fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
++ \endcode
++
++ The \a index must be to an existing item in the list, or one past
++ the end of the list (equivalent to append).
++
++ \sa set(), append()
++ */
++
++ void QQmlListModel::insert(QQmlV4FunctionPtr args)
++ {
++ if (args->length() == 2) {
++ QV4::Scope scope(args->v4engine());
++ QV4::ScopedValue arg0(scope, (*args)[0]);
++ int index = arg0->toInt32();
++
++ if (index < 0 || index > count()) {
++ qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
++ return;
++ }
++
++ QV4::ScopedObject argObject(scope, (*args)[1]);
++ QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
++ if (objectArray) {
++ QV4::ScopedObject argObject(scope);
++
++ int objectArrayLength = objectArray->getLength();
++ emitItemsAboutToBeInserted(index, objectArrayLength);
++ for (int i=0 ; i < objectArrayLength ; ++i) {
++ argObject = objectArray->get(i);
++
++ if (m_dynamicRoles) {
++ m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
++ } else {
++ m_listModel->insert(index+i, argObject);
++ }
++ }
++ emitItemsInserted();
++ } else if (argObject) {
++ emitItemsAboutToBeInserted(index, 1);
++
++ if (m_dynamicRoles) {
++ m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
++ } else {
++ m_listModel->insert(index, argObject);
++ }
++
++ emitItemsInserted();
++ } else {
++ qmlWarning(this) << tr("insert: value is not an object");
++ }
++ } else {
++ qmlWarning(this) << tr("insert: value is not an object");
++ }
++ }
++
++ bool QQmlListModel::canMove(int from, int to, int n) const
++ {
++ if (from < 0 || to < 0 || n < 0 || n > count())
++ return false;
++
++ const int maxIndex = count() - n;
++ return from <= maxIndex && to <= maxIndex;
++ }
++
++ /*!
++ \qmlmethod void ListModel::move(int from, int to, int n)
++
++ Moves \a n items \a from one position \a to another.
++
++ The from and to ranges must exist; for example, to move the first 3 items
++ to the end of the list:
++
++ \code
++ fruitModel.move(0, fruitModel.count - 3, 3)
++ \endcode
++
++ \sa append()
++ */
++ void QQmlListModel::move(int from, int to, int n)
++ {
++ if (n == 0 || from == to)
++ return;
++ if (!canMove(from, to, n)) {
++ qmlWarning(this) << tr("move: out of range");
++ return;
++ }
++
++ if (m_mainThread)
++ beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
++
++ if (m_dynamicRoles) {
++
++ int realFrom = from;
++ int realTo = to;
++ int realN = n;
++
++ if (from > to) {
++ // Only move forwards - flip if backwards moving
++ int tfrom = from;
++ int tto = to;
++ realFrom = tto;
++ realTo = tto+n;
++ realN = tfrom-tto;
++ }
++
++ std::rotate(m_modelObjects.begin() + realFrom,
++ m_modelObjects.begin() + realFrom + realN,
++ m_modelObjects.begin() + realTo + realN);
++
++ } else {
++ m_listModel->move(from, to, n);
++ }
++
++ if (m_mainThread)
++ endMoveRows();
++ }
++
++ /*!
++ \qmlmethod void ListModel::append(jsobject dict)
++
++ Adds a new item to the end of the list model, with the
++ values in \a dict.
++
++ \code
++ fruitModel.append({"cost": 5.95, "name":"Pizza"})
++ \endcode
++
++ \sa set(), remove()
++ */
++ void QQmlListModel::append(QQmlV4FunctionPtr args)
++ {
++ if (args->length() == 1) {
++ QV4::Scope scope(args->v4engine());
++ QV4::ScopedObject argObject(scope, (*args)[0]);
++ QV4::ScopedArrayObject objectArray(scope, (*args)[0]);
++
++ if (objectArray) {
++ QV4::ScopedObject argObject(scope);
++
++ int objectArrayLength = objectArray->getLength();
++ if (objectArrayLength > 0) {
++ int index = count();
++ emitItemsAboutToBeInserted(index, objectArrayLength);
++
++ for (int i=0 ; i < objectArrayLength ; ++i) {
++ argObject = objectArray->get(i);
++
++ if (m_dynamicRoles) {
++ m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
++ } else {
++ m_listModel->append(argObject);
++ }
++ }
++
++ emitItemsInserted();
++ }
++ } else if (argObject) {
++ int index;
++
++ if (m_dynamicRoles) {
++ index = m_modelObjects.size();
++ emitItemsAboutToBeInserted(index, 1);
++ m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
++ } else {
++ index = m_listModel->elementCount();
++ emitItemsAboutToBeInserted(index, 1);
++ m_listModel->append(argObject);
++ }
++
++ emitItemsInserted();
++ } else {
++ qmlWarning(this) << tr("append: value is not an object");
++ }
++ } else {
++ qmlWarning(this) << tr("append: value is not an object");
++ }
++ }
++
++ /*!
++ \qmlmethod var ListModel::get(int index)
++
++ Returns the item at \a index in the list model. This allows the item
++ data to be accessed or modified from JavaScript:
++
++ \code
++ Component.onCompleted: {
++ fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
++ console.log(fruitModel.get(0).cost);
++ fruitModel.get(0).cost = 10.95;
++ }
++ \endcode
++
++ The \a index must be an element in the list.
++
++ Note that properties of the returned object that are themselves objects
++ will also be models, and this get() method is used to access elements:
++
++ \code
++ fruitModel.append(..., "attributes":
++ [{"name":"spikes","value":"7mm"},
++ {"name":"color","value":"green"}]);
++ fruitModel.get(0).attributes.get(1).value; // == "green"
++ \endcode
++
++ \warning The returned object is not guaranteed to remain valid. It
++ should not be used in \l{Property Binding}{property bindings} or for
++ storing data across modifications of its origin ListModel.
++
++ \sa append(), clear()
++ */
++ QJSValue QQmlListModel::get(int index) const
++ {
++ QV4::Scope scope(engine());
++ QV4::ScopedValue result(scope, QV4::Value::undefinedValue());
++
++ if (index >= 0 && index < count()) {
++
++ if (m_dynamicRoles) {
++ DynamicRoleModelNode *object = m_modelObjects[index];
++ result = QV4::QObjectWrapper::wrap(scope.engine, object);
++ } else {
++ QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
++ QQmlData *ddata = QQmlData::get(object);
++ if (ddata->jsWrapper.isNullOrUndefined()) {
++ result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
++ // Keep track of the QObjectWrapper in persistent value storage
++ ddata->jsWrapper.set(scope.engine, result);
++ } else {
++ result = ddata->jsWrapper.value();
++ }
++ }
++ }
++
++ return QJSValuePrivate::fromReturnedValue(result->asReturnedValue());
++ }
++
++ /*!
++ \qmlmethod void ListModel::set(int index, jsobject dict)
++
++ Changes the item at \a index in the list model with the
++ values in \a dict. Properties not appearing in \a dict
++ are left unchanged.
++
++ \code
++ fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
++ \endcode
++
++ If \a index is equal to count() then a new item is appended to the
++ list. Otherwise, \a index must be an element in the list.
++
++ \sa append()
++ */
++ void QQmlListModel::set(int index, const QJSValue &value)
++ {
++ QV4::Scope scope(engine());
++ QV4::ScopedObject object(scope, QJSValuePrivate::asReturnedValue(&value));
++
++ if (!object) {
++ qmlWarning(this) << tr("set: value is not an object");
++ return;
++ }
++ if (index > count() || index < 0) {
++ qmlWarning(this) << tr("set: index %1 out of range").arg(index);
++ return;
++ }
++
++
++ if (index == count()) {
++ emitItemsAboutToBeInserted(index, 1);
++
++ if (m_dynamicRoles) {
++ m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
++ } else {
++ m_listModel->insert(index, object);
++ }
++
++ emitItemsInserted();
++ } else {
++
++ QList<int> roles;
++
++ if (m_dynamicRoles) {
++ m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
++ } else {
++ m_listModel->set(index, object, &roles);
++ }
++
++ if (roles.size())
++ emitItemsChanged(index, 1, roles);
++ }
++ }
++
++ /*!
++ \qmlmethod void ListModel::setProperty(int index, string property, var value)
++
++ Changes the \a property of the item at \a index in the list model to \a value.
++
++ \code
++ fruitModel.setProperty(3, "cost", 5.95)
++ \endcode
++
++ The \a index must be an element in the list.
++
++ \sa append()
++ */
++ void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
++ {
++ if (count() == 0 || index >= count() || index < 0) {
++ qmlWarning(this) << tr("set: index %1 out of range").arg(index);
++ return;
++ }
++
++ if (m_dynamicRoles) {
++ int roleIndex = m_roles.indexOf(property);
++ if (roleIndex == -1) {
++ roleIndex = m_roles.size();
++ m_roles.append(property);
++ }
++ if (m_modelObjects[index]->setValue(property.toUtf8(), value))
++ emitItemsChanged(index, 1, QList<int>(1, roleIndex));
++ } else {
++ int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
++ if (roleIndex != -1)
++ emitItemsChanged(index, 1, QList<int>(1, roleIndex));
++ }
++ }
++
++ /*!
++ \qmlmethod void ListModel::sync()
++
++ Writes any unsaved changes to the list model after it has been modified
++ from a worker script.
++ */
++ void QQmlListModel::sync()
++ {
++ // This is just a dummy method to make it look like sync() exists in
++ // ListModel (and not just QQmlListModelWorkerAgent) and to let
++ // us document sync().
++ qmlWarning(this) << "List sync() can only be called from a WorkerScript";
++ }
++
++ bool QQmlListModelParser::verifyProperty(
++ const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
++ const QV4::CompiledData::Binding *binding)
++ {
++ if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
++ const quint32 targetObjectIndex = binding->value.objectIndex;
++ const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
++ QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
++ if (objName != listElementTypeName) {
++ const QMetaObject *mo = resolveType(objName);
++ if (mo != &QQmlListElement::staticMetaObject) {
++ error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
++ return false;
++ }
++ listElementTypeName = objName; // cache right name for next time
++ }
++
++ if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
++ error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
++ return false;
++ }
++
++ const QV4::CompiledData::Binding *binding = target->bindingTable();
++ for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
++ QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
++ if (propName.isEmpty()) {
++ error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
++ return false;
++ }
++ if (!verifyProperty(compilationUnit, binding))
++ return false;
++ }
++ } else if (binding->type() == QV4::CompiledData::Binding::Type_Script) {
++ QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
++ if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
++ bool ok;
++ evaluateEnum(scriptStr, &ok);
++ if (!ok) {
++ error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
++ return false;
++ }
++ }
++ }
++
++ return true;
++ }
++
++ bool QQmlListModelParser::applyProperty(
++ const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
++ const QV4::CompiledData::Binding *binding, ListModel *model, QQmlListModel *owner,
++ int outterElementIndex)
++ {
++ const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
++
++ bool roleSet = false;
++ const QV4::CompiledData::Binding::Type bindingType = binding->type();
++ if (bindingType >= QV4::CompiledData::Binding::Type_Object) {
++ const quint32 targetObjectIndex = binding->value.objectIndex;
++ const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
++
++ ListModel *subModel = nullptr;
++ if (outterElementIndex == -1) {
++ subModel = model;
++ } else {
++ const ListLayout::Role &role = model->getOrCreateListRole(elementName);
++ if (role.type == ListLayout::Role::List) {
++ subModel = model->getListProperty(outterElementIndex, role);
++ if (subModel == nullptr) {
++ subModel = new ListModel(role.subLayout, nullptr);
++ model->setOrCreateProperty(
++ outterElementIndex, elementName,
++ QVariant::fromValue(subModel));
++ }
++ }
++ }
++
++ int elementIndex = subModel ? subModel->appendElement() : -1;
++
++ const QV4::CompiledData::Binding *subBinding = target->bindingTable();
++ for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
++ roleSet |= applyProperty(compilationUnit, subBinding, subModel, owner, elementIndex);
++ }
++ } else if (!model) {
++ return false;
++ } else {
++ QVariant value;
++
++ const bool isTranslationBinding = binding->isTranslationBinding();
++ if (isTranslationBinding) {
++ value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
++ } else if (binding->evaluatesToString()) {
++ value = compilationUnit->bindingValueAsString(binding);
++ } else if (bindingType == QV4::CompiledData::Binding::Type_Number) {
++ value = compilationUnit->bindingValueAsNumber(binding);
++ } else if (bindingType == QV4::CompiledData::Binding::Type_Boolean) {
++ value = binding->valueAsBoolean();
++ } else if (bindingType == QV4::CompiledData::Binding::Type_Null) {
++ value = QVariant::fromValue(nullptr);
++ } else if (bindingType == QV4::CompiledData::Binding::Type_Script) {
++ QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
++ if (definesEmptyList(scriptStr)) {
++ value = QVariant::fromValue(
++ new ListModel(model->getOrCreateListRole(elementName).subLayout, nullptr));
++ } else if (binding->isFunctionExpression()) {
++ QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
++ Q_ASSERT(id != QQmlBinding::Invalid);
++
++ auto v4 = compilationUnit->engine;
++ QV4::Scope scope(v4);
++
++ if (model->m_modelCache == nullptr) {
++ model->m_modelCache = new QQmlListModel(owner, model, v4);
++ QQmlEngine::setContextForObject(
++ model->m_modelCache, QQmlEngine::contextForObject(owner));
++ }
++
++ // for now we do not provide a context object; data from the ListElement must be passed to the function
++ QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
++ QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
++
++ QJSValue v;
++ QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0));
++ if (v4->hasException)
++ v4->catchException();
++ else
++ QJSValuePrivate::setValue(&v, result);
++ value.setValue(v);
++ } else {
++ bool ok;
++ value = evaluateEnum(scriptStr, &ok);
++ }
++ } else {
++ Q_UNREACHABLE();
++ }
++
++ model->setOrCreateProperty(outterElementIndex, elementName, value);
++ auto listModel = model->m_modelCache;
++ if (isTranslationBinding && listModel) {
++ if (!listModel->translationChangeHandler) {
++ auto ep = QQmlEnginePrivate::get(compilationUnit->engine);
++ model->m_modelCache->translationChangeHandler = std::make_unique<QPropertyNotifier>(
++ ep->translationLanguage.addNotifier([listModel](){ listModel->updateTranslations(); }));
++ }
++ }
++ roleSet = true;
++ }
++ return roleSet;
++ }
++
++ void QQmlListModelParser::verifyBindings(
++ const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
++ const QList<const QV4::CompiledData::Binding *> &bindings)
++ {
++ listElementTypeName = QString(); // unknown
++
++ for (const QV4::CompiledData::Binding *binding : bindings) {
++ QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
++ if (!propName.isEmpty()) { // isn't default property
++ error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
++ return;
++ }
++ if (!verifyProperty(compilationUnit, binding))
++ return;
++ }
++ }
++
++ void QQmlListModelParser::applyBindings(
++ QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
++ const QList<const QV4::CompiledData::Binding *> &bindings)
++ {
++ QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
++
++ rv->m_engine = qmlEngine(rv)->handle();
++ rv->m_compilationUnit = compilationUnit;
++
++ bool setRoles = false;
++
++ for (const QV4::CompiledData::Binding *binding : bindings) {
++ if (binding->type() != QV4::CompiledData::Binding::Type_Object)
++ continue;
++ setRoles |= applyProperty(
++ compilationUnit, binding, rv->m_listModel, rv, /*outter element index*/-1);
++ }
++
++ if (setRoles == false)
++ qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
++ }
++
++ bool QQmlListModelParser::definesEmptyList(QStringView s)
++ {
++ if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
++ for (auto c : s.sliced(1).chopped(1)) {
++ if (!c.isSpace())
++ return false;
++ }
++ return true;
++ }
++ return false;
++ }
++
++
++ /*!
++ \qmltype ListElement
++ \nativetype QQmlListElement
++ \inqmlmodule QtQml.Models
++ \brief Defines a data item in a ListModel.
++ \ingroup qtquick-models
++
++ List elements are defined inside ListModel definitions, and represent items in a
++ list that will be displayed using ListView or \l Repeater items.
++
++ List elements are defined like other QML elements except that they contain
++ a collection of \e role definitions instead of properties. Using the same
++ syntax as property definitions, roles both define how the data is accessed
++ and include the data itself.
++
++ The names used for roles must begin with a lower-case letter and should be
++ common to all elements in a given model. Values must be simple constants; either
++ strings (quoted and optionally within a call to
++ \l [QML] {Qt::} {QT_TR_NOOP()}, boolean values (true, false), numbers, or
++ enumeration values (such as AlignText.AlignHCenter).
++
++ Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
++ a role. This allows the definition of ListElements with callable actions.
++
++ \section1 Referencing Roles
++
++ The role names are used by delegates to obtain data from list elements.
++ Each role name is accessible in the delegate's scope, and refers to the
++ corresponding role in the current element. Where a role name would be
++ ambiguous to use, it can be accessed via the \l{ListView::}{model}
++ property (e.g., \c{model.cost} instead of \c{cost}).
++
++ \section1 Example Usage
++
++ The following model defines a series of list elements, each of which
++ contain "name" and "cost" roles and their associated values.
++
++ \snippet qml/listmodel/listelements.qml model
++
++ The delegate obtains the name and cost for each element by simply referring
++ to \c name and \c cost:
++
++ \snippet qml/listmodel/listelements.qml view
++
++ \sa ListModel
++ */
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmllistmodel_p_p.cpp"
++
++ #include "moc_qqmllistmodel_p.cpp"