ListModel: Treat QV4::Sequence and QV4::QmlListWrapper like arrays

Pick-to: 6.7
Fixes: QTBUG-124084
Change-Id: I99f900b71d71f33f807f5f69829bb9ef760b40c8
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
Ulf Hermann 2024-04-05 15:48:52 +02:00
parent 57c6168d00
commit 07cfd1b9eb
4 changed files with 219 additions and 19 deletions

View File

@ -3,22 +3,25 @@
#include "qqmllistmodel_p_p.h"
#include "qqmllistmodelworkeragent_p.h"
#include <private/qqmlopenmetaobject_p.h>
#include <private/qqmljsast_p.h>
#include <private/qqmljsengine_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/qv4object_p.h>
#include <private/qv4dateobject_p.h>
#include <private/qv4urlobject_p.h>
#include <private/qv4objectiterator_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>
@ -681,18 +684,11 @@ void ListModel::set(int elementIndex, QV4::Object *object, ListModel::SetElement
e->setDoublePropertyFast(r, propertyValue->asDouble());
}
} else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
if (r.type == ListLayout::Role::List) {
ListModel *subModel = new ListModel(r.subLayout, nullptr);
int arrayLength = a->getLength();
for (int j=0 ; j < arrayLength ; ++j) {
o = a->get(j);
subModel->append(o);
}
e->setListPropertyFast(r, subModel);
}
setArrayLike(&o, propertyName, e, a);
} else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
setArrayLike(&o, propertyName, e, s);
} else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
setArrayLike(&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) {

View File

@ -391,6 +391,23 @@ private:
void updateCacheIndices(int start = 0, int end = -1);
template<typename ArrayLike>
void setArrayLike(QV4::ScopedObject *o, QV4::String *propertyName, ListElement *e, ArrayLike *a)
{
const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
if (r.type == ListLayout::Role::List) {
ListModel *subModel = new ListModel(r.subLayout, nullptr);
int arrayLength = a->getLength();
for (int j=0 ; j < arrayLength ; ++j) {
*o = a->get(j);
subModel->append(*o);
}
e->setListPropertyFast(r, subModel);
}
}
friend class ListElement;
friend class QQmlListModelWorkerAgent;
friend class QQmlListModelParser;

View File

@ -0,0 +1,37 @@
import QtQuick
Item {
id: mainWindow
function load(data) {
model.clear()
for (var i = 0; i < data.length; i++)
model.append(data[i])
}
ListModel {
id: model
}
Repeater {
objectName: "topLevel"
model: model
Item {
objectName: _headline
Repeater {
objectName: "month"
model: _weeks
Item {
objectName: index
Repeater {
objectName: "week"
model: _week
Item {
objectName: _day
}
}
}
}
}
}
}

View File

@ -5,6 +5,7 @@
#include <QtQuick/private/qquicktext_p.h>
#include <QtQuick/private/qquickanimation_p.h>
#include <QtQuick/private/qquicklistview_p.h>
#include <QtQuick/private/qquickrepeater_p.h>
#include <QtQml/private/qqmlengine_p.h>
#include <QtQmlModels/private/qqmllistmodel_p.h>
#include <QtQml/private/qqmlexpression_p.h>
@ -120,6 +121,7 @@ private slots:
void objectOwnershipFlip();
void enumsInListElement();
void protectQObjectFromGC();
void nestedLists();
};
bool tst_qqmllistmodel::compareVariantList(const QVariantList &testList, QVariant object)
@ -1906,6 +1908,154 @@ void tst_qqmllistmodel::protectQObjectFromGC()
}
}
static QVariantList createLast7Days()
{
QVariantList last7DaysList;
for (int i = 0; i < 7; i++) {
QVariantMap map;
map.insert("_day", i);
last7DaysList.append(map);
}
return last7DaysList;
}
static QVariantList createWeekChartModels()
{
QVariantList list;
for (int i = 0; i < 4; i++) {
QVariantMap map;
map.insert("_week", createLast7Days());
list.append(map);
}
return list;
}
static QVariantList createVariantModel()
{
QVariantMap element1;
element1.insert("_headline", "Element 1");
element1.insert("_weeks", createWeekChartModels());
QVariantMap element2;
element2.insert("_headline", "Element 2");
element2.insert("_weeks", createWeekChartModels());
QVariantMap element3;
element3.insert("_headline", "Element 3");
element3.insert("_weeks", createWeekChartModels());
QVariantList list;
list.append(element1);
list.append(element2);
list.append(element3);
return list;
}
class Day : public QObject
{
Q_OBJECT
Q_PROPERTY(int _day READ _day CONSTANT)
public:
Day(int day, QObject *parent = nullptr) : QObject(parent), day(day) {}
int _day() const { return day; }
private:
int day = 0;
};
class Week : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Day> _week READ _week)
public:
Week(QObject *parent = nullptr) : QObject(parent)
{
for (int i = 0; i < 7; ++i)
week.append(new Day(i, this));
}
QQmlListProperty<Day> _week() { return QQmlListProperty<Day>(this, &week); }
private:
QList<Day *> week;
};
class Month : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Week> _weeks READ _weeks)
Q_PROPERTY(QString _headline READ _headline CONSTANT)
public:
Month(int i, QObject *parent = nullptr)
: QObject(parent)
, headline(QLatin1String("Element ") + QString::number(i))
{
for (int i = 0; i < 4; ++i)
weeks.append(new Week(this));
}
QQmlListProperty<Week> _weeks() { return QQmlListProperty<Week>(this, &weeks); }
QString _headline() const { return headline; }
private:
QList<Week *> weeks;
QString headline;
};
static void verifyLists(const QVariantList &list, QQuickRepeater *topLevel)
{
QVERIFY(topLevel);
QCOMPARE(topLevel->count(), 3);
for (int month = 0; month < 3; ++month) {
const QVariantMap monthData = list[month].toMap();
const QQuickItem *monthItem = topLevel->itemAt(month);
QCOMPARE(monthItem->objectName(), monthData["_headline"].toString());
const QQuickRepeater *monthRepeater = monthItem->findChild<QQuickRepeater *>("month");
QVERIFY(monthRepeater);
QCOMPARE(monthRepeater->count(), 4);
const QVariantList weekList = monthData["_weeks"].toList();
for (int week = 0; week < 4; ++week) {
const QVariantList weekData = weekList[week].toMap()["_week"].toList();
const QQuickItem *weekItem = monthRepeater->itemAt(week);
QCOMPARE(weekItem->objectName(), QString::number(week));
const QQuickRepeater *weekRepeater = weekItem->findChild<QQuickRepeater *>("week");
QVERIFY(weekRepeater);
QCOMPARE(weekRepeater->count(), 7);
for (int day = 0; day < 7; ++day) {
const QVariantMap dayData = weekData[day].toMap();
const QQuickItem *dayItem = weekRepeater->itemAt(day);
QCOMPARE(dayItem->objectName(), dayData["_day"]);
}
}
}
}
void tst_qqmllistmodel::nestedLists()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("nestedLists.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> o(component.create());
QVERIFY(!o.isNull());
QQuickRepeater *topLevel = o->findChild<QQuickRepeater *>("topLevel");
const QVariantList list = createVariantModel();
QMetaObject::invokeMethod(o.data(), "load", Q_ARG(QVariant, QVariant::fromValue(list)));
verifyLists(list, topLevel);
const QObjectList objects {
new Month(1, o.data()),
new Month(2, o.data()),
new Month(3, o.data())
};
QMetaObject::invokeMethod(o.data(), "load", Q_ARG(QVariant, QVariant::fromValue(objects)));
verifyLists(list, topLevel);
}
QTEST_MAIN(tst_qqmllistmodel)
#include "tst_qqmllistmodel.moc"