diff options
19 files changed, 684 insertions, 6 deletions
diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp index 7b3ae31c08..5c3ecbfb60 100644 --- a/src/qml/qml/qqmlincubator.cpp +++ b/src/qml/qml/qqmlincubator.cpp @@ -690,6 +690,11 @@ RequiredProperties &QQmlIncubatorPrivate::requiredProperties() return creator->requiredProperties(); } +bool QQmlIncubatorPrivate::hadRequiredProperties() const +{ + return creator->componentHadRequiredProperties(); +} + /*! Stores a mapping from property names to initial values with which the incubated component will be initialized diff --git a/src/qml/qml/qqmlincubator_p.h b/src/qml/qml/qqmlincubator_p.h index 731db7aad3..aadb147bd5 100644 --- a/src/qml/qml/qqmlincubator_p.h +++ b/src/qml/qml/qqmlincubator_p.h @@ -108,6 +108,7 @@ public: void forceCompletion(QQmlInstantiationInterrupt &i); void incubate(QQmlInstantiationInterrupt &i); RequiredProperties &requiredProperties(); + bool hadRequiredProperties() const; }; QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index b723ddb381..d2e9d36d55 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -82,6 +82,7 @@ QQmlObjectCreator::QQmlObjectCreator(QQmlContextData *parentContext, const QQmlR , propertyCaches(&compilationUnit->propertyCaches) , sharedState(new QQmlObjectCreatorSharedState) , topLevelCreator(true) + , hadRequiredProperties(false) , incubator(incubator) { init(parentContext); @@ -1523,6 +1524,7 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * const QV4::CompiledData::Property* property = _compiledObject->propertiesBegin() + propertyIndex; QQmlPropertyData *propertyData = _propertyCache->property(_propertyCache->propertyOffset() + propertyIndex); if (property->isRequired) { + hadRequiredProperties = true; sharedState->requiredProperties.insert(propertyData, RequiredPropertyInfo {compilationUnit->stringAt(property->nameIndex), compilationUnit->finalUrl(), property->location, {}}); } diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h index ee1d82d4e3..c302660799 100644 --- a/src/qml/qml/qqmlobjectcreator_p.h +++ b/src/qml/qml/qqmlobjectcreator_p.h @@ -126,6 +126,7 @@ public: QFiniteStack<QPointer<QObject> > &allCreatedObjects() { return sharedState->allCreatedObjects; } RequiredProperties &requiredProperties() {return sharedState->requiredProperties;} + bool componentHadRequiredProperties() const {return hadRequiredProperties;} private: QQmlObjectCreator(QQmlContextData *contextData, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, QQmlObjectCreatorSharedState *inheritedSharedState); @@ -172,6 +173,7 @@ private: const QQmlPropertyCacheVector *propertyCaches; QExplicitlySharedDataPointer<QQmlObjectCreatorSharedState> sharedState; bool topLevelCreator; + bool hadRequiredProperties; QQmlIncubatorPrivate *incubator; QObject *_qobject; diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index 6c80f4a3e5..c37dc114a1 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -48,7 +48,6 @@ #include <private/qqmlchangeset_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlcomponent_p.h> -#include <private/qqmlincubator_p.h> #include <private/qv4value_p.h> #include <private/qv4functionobject_p.h> @@ -887,6 +886,81 @@ static bool isDoneIncubating(QQmlIncubator::Status status) return status == QQmlIncubator::Ready || status == QQmlIncubator::Error; } +PropertyUpdater::PropertyUpdater(QObject *parent) : + QObject(parent) {} + +void PropertyUpdater::doUpdate() +{ + auto sender = QObject::sender(); + auto mo = sender->metaObject(); + auto signalIndex = QObject::senderSignalIndex(); + // start at 0 instead of propertyOffset to handle properties from parent hierarchy + for (auto i = 0; i < mo->propertyCount() + mo->propertyOffset(); ++i) { + auto property = mo->property(i); + if (property.notifySignal().methodIndex() == signalIndex) { + // we synchronize between required properties and model rolenames by name + // that's why the QQmlProperty and the metaobject property must have the same name + QQmlProperty qmlProp(parent(), QString::fromLatin1(property.name())); + qmlProp.write(property.read(QObject::sender())); + return; + } + } +} + +void QQDMIncubationTask::initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject *object) +{ + auto incubatorPriv = QQmlIncubatorPrivate::get(this); + if (incubatorPriv->hadRequiredProperties()) { + if (incubatorPriv->requiredProperties().empty()) + return; + RequiredProperties &requiredProperties = incubatorPriv->requiredProperties(); + + auto qmlMetaObject = modelItemToIncubate->metaObject(); + // if a required property was not in the model, it might still be a static property of the + // QQmlDelegateModelItem or one of its derived classes this is the case for index, row, + // column, model and more + // the most derived subclass of QQmlDelegateModelItem is QQmlDMAbstractModelData at depth 2, + // so 4 should be plenty + QVarLengthArray<const QMetaObject *, 4> mos; + // we first check the dynamic meta object for properties originating from the model + mos.push_back(qmlMetaObject); // contains abstractitemmodelproperties + auto delegateModelItemSubclassMO = qmlMetaObject->superClass(); + mos.push_back(delegateModelItemSubclassMO); + + while (strcmp(delegateModelItemSubclassMO->className(), modelItemToIncubate->staticMetaObject.className())) { + delegateModelItemSubclassMO = delegateModelItemSubclassMO->superClass(); + mos.push_back(delegateModelItemSubclassMO); + } + if (proxiedObject) + mos.push_back(proxiedObject->metaObject()); + + auto updater = new PropertyUpdater(object); + for (const QMetaObject *mo : mos) { + for (int i = mo->propertyOffset(); i < mo->propertyCount() + mo->propertyOffset(); ++i) { + auto prop = mo->property(i); + if (!prop.name()) + continue; + auto propName = QString::fromUtf8(prop.name()); + bool wasInRequired = false; + QQmlProperty componentProp = QQmlComponentPrivate::removePropertyFromRequired( + object, propName, requiredProperties, &wasInRequired); + // only write to property if it was actually requested by the component + if (wasInRequired && prop.hasNotifySignal()) { + QMetaMethod changeSignal = prop.notifySignal(); + QMetaMethod updateSlot = PropertyUpdater::staticMetaObject.method(PropertyUpdater::staticMetaObject.indexOfSlot("doUpdate()")); + QObject::connect(modelItemToIncubate, changeSignal, updater, updateSlot); + } + if (wasInRequired) + componentProp.write(prop.read(modelItemToIncubate)); + } + } + } else { + modelItemToIncubate->contextData->contextObject = modelItemToIncubate; + if (proxiedObject) + proxyContext->contextObject = proxiedObject; + } +} + void QQDMIncubationTask::statusChanged(Status status) { if (vdm) { @@ -987,6 +1061,7 @@ void QQDMIncubationTask::setInitialState(QObject *o) void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o) { QQmlDelegateModelItem *cacheItem = incubationTask->incubating; + incubationTask->initializeRequiredProperties(incubationTask->incubating, o); cacheItem->object = o; if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object)) @@ -1053,7 +1128,6 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ QQmlContextData *ctxt = new QQmlContextData; ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data())); - ctxt->contextObject = cacheItem; cacheItem->contextData = ctxt; if (m_adaptorModel.hasProxyObject()) { @@ -1062,7 +1136,8 @@ QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQ ctxt = new QQmlContextData; ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true); QObject *proxied = proxy->proxiedObject(); - ctxt->contextObject = proxied; + cacheItem->incubationTask->proxiedObject = proxied; + cacheItem->incubationTask->proxyContext = ctxt; // We don't own the proxied object. We need to clear it if it goes away. QObject::connect(proxied, &QObject::destroyed, cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed); diff --git a/src/qmlmodels/qqmldelegatemodel_p_p.h b/src/qmlmodels/qqmldelegatemodel_p_p.h index 92362b8876..f9dbc61a94 100644 --- a/src/qmlmodels/qqmldelegatemodel_p_p.h +++ b/src/qmlmodels/qqmldelegatemodel_p_p.h @@ -201,11 +201,14 @@ public: , incubating(nullptr) , vdm(l) {} + void initializeRequiredProperties(QQmlDelegateModelItem *modelItemToIncubate, QObject* object); void statusChanged(Status) override; void setInitialState(QObject *) override; QQmlDelegateModelItem *incubating = nullptr; QQmlDelegateModelPrivate *vdm = nullptr; + QQmlContextData *proxyContext = nullptr; + QPointer<QObject> proxiedObject = nullptr; // the proxied object might disapear, so we use a QPointer instead of a raw one int index[QQmlListCompositor::MaximumGroupCount]; }; @@ -445,6 +448,16 @@ private: const int indexPropertyOffset; }; +class PropertyUpdater : public QObject +{ + Q_OBJECT + +public: + PropertyUpdater(QObject *parent); +public Q_SLOTS: + void doUpdate(); +}; + QT_END_NAMESPACE #endif diff --git a/src/qmlmodels/qqmltableinstancemodel.cpp b/src/qmlmodels/qqmltableinstancemodel.cpp index b244a007e5..9b79df3441 100644 --- a/src/qmlmodels/qqmltableinstancemodel.cpp +++ b/src/qmlmodels/qqmltableinstancemodel.cpp @@ -525,8 +525,13 @@ const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object) { - modelItemToIncubate->object = object; - emit tableInstanceModel->initItem(modelItemToIncubate->index, object); + initializeRequiredProperties(modelItemToIncubate, object); + if (QQmlIncubatorPrivate::get(this)->requiredProperties().empty()) { + modelItemToIncubate->object = object; + emit tableInstanceModel->initItem(modelItemToIncubate->index, object); + } else { + object->deleteLater(); + } } void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status) diff --git a/tests/auto/quick/qquicklistview/data/delegatesWithRequiredProperties.qml b/tests/auto/quick/qquicklistview/data/delegatesWithRequiredProperties.qml new file mode 100644 index 0000000000..f354517678 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/delegatesWithRequiredProperties.qml @@ -0,0 +1,47 @@ +import QtQuick 2.12 +import Qt.fruit 1.0 + +Rectangle { + id: root + required property bool useCpp + width: 200; height: 200 + + + ListModel { + id: fruitModel + + ListElement { + name: "Apple" + cost: 2 + } + ListElement { + name: "Orange" + cost: 3 + } + ListElement { + name: "Banana" + cost: 1 + } + } + + + Component { + id: fruitDelegate + Row { + id: row + spacing: 10 + required property string name + required property int cost + Text { text: row.name } + Text { text: '$' + row.cost } + Component.onCompleted: () => { console.debug(row.name+row.cost) }; + } + } + + ListView { + anchors.fill: parent + model: root.useCpp ? FruitModelCpp : fruitModel + delegate: fruitDelegate + } + +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 7c26c22217..bf593cfd2e 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -280,6 +280,8 @@ private slots: void touchCancel(); void resizeAfterComponentComplete(); + void delegateWithRequiredProperties(); + private: template <class T> void items(const QUrl &source); template <class T> void changed(const QUrl &source); @@ -9037,6 +9039,90 @@ void tst_QQuickListView::resizeAfterComponentComplete() // QTBUG-76487 QTRY_COMPARE(lastItem->property("y").toInt(), 9 * lastItem->property("height").toInt()); } +class Animal +{ +public: + Animal(const int cost, const QString &name) {m_name = name; m_cost = cost;} + + int cost() const {return m_cost;} + QString name() const {return m_name;} + + QString m_name; + int m_cost; +}; + +class FruitModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum AnimalRoles { + NameRole = Qt::UserRole + 1, + CostRole + }; + + FruitModel(QObject* = nullptr) { + m_animals.push_back(Animal {4, QLatin1String("Melon")}); + m_animals.push_back(Animal {5, QLatin1String("Cherry")}); + } + + int rowCount(const QModelIndex & = QModelIndex()) const override {return m_animals.count();} + + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override { + if (!checkIndex(index)) + return {}; + const Animal &animal = m_animals[index.row()]; + if (role == CostRole) + return animal.cost(); + else if (role == NameRole) + return animal.name(); + return QVariant(); + } + +protected: + QHash<int, QByteArray> roleNames() const override { + QHash<int, QByteArray> roles; + roles[CostRole] = "cost"; + roles[NameRole] = "name"; + return roles; + } +private: + QList<Animal> m_animals; +}; + +void tst_QQuickListView::delegateWithRequiredProperties() +{ + FruitModel myModel; + qmlRegisterSingletonInstance("Qt.fruit", 1, 0, "FruitModelCpp", &myModel); + { + // ListModel + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Apple2"); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Orange3"); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Banana1"); + QScopedPointer<QQuickView> window(createView()); + window->setInitialProperties({{QLatin1String("useCpp"), false}}); + window->setSource(testFileUrl("delegatesWithRequiredProperties.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QObject *listView = window->rootObject(); + QVERIFY(listView); + } + { + // C++ model + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Melon4"); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "Cherry5"); + QScopedPointer<QQuickView> window(createView()); + window->setInitialProperties({{QLatin1String("useCpp"), true}}); + window->setSource(testFileUrl("delegatesWithRequiredProperties.qml")); + + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QObject *listView = window->rootObject(); + QVERIFY(listView); + } +} + QTEST_MAIN(tst_QQuickListView) #include "tst_qquicklistview.moc" diff --git a/tests/auto/quick/qquickpathview/data/delegateWithRequiredProperties.2.qml b/tests/auto/quick/qquickpathview/data/delegateWithRequiredProperties.2.qml new file mode 100644 index 0000000000..ae8ca784bc --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/delegateWithRequiredProperties.2.qml @@ -0,0 +1,59 @@ +import QtQuick 2.14 + +Item { + id: root + width: 800 + height: 600 + property bool working: false + + ListModel { + id: myModel + ListElement { + name: "Bill Jones" + place: "Berlin" + } + ListElement { + name: "Jane Doe" + place: "Oslo" + } + ListElement { + name: "John Smith" + place: "Oulo" + } + } + + Component { + id: delegateComponent + Rectangle { + id: myDelegate + height: 50 + width: 50 + required property string name + required property int index + onNameChanged: () => {if (myDelegate.name === "You-know-who") root.working = true} + Text { + text: myDelegate.name + font.pointSize: 10 + anchors.fill: myDelegate + } + } + } + + PathView { + anchors.fill: parent + model: myModel + delegate: delegateComponent + path: Path { + startX: 80; startY: 100 + PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 } + PathQuad { x: 140; y: 100; controlX: -20; controlY: 75 } + } + } + Timer { + interval: 1 + running: true + repeat: false + onTriggered: () => { myModel.setProperty(1, "name", "You-know-who"); } + } + +} diff --git a/tests/auto/quick/qquickpathview/data/delegateWithRequiredProperties.qml b/tests/auto/quick/qquickpathview/data/delegateWithRequiredProperties.qml new file mode 100644 index 0000000000..5d721fd0c4 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/delegateWithRequiredProperties.qml @@ -0,0 +1,53 @@ +import QtQuick 2.14 + +Item { + width: 400 + height: 200 + + ListModel { + id: myModel + ListElement { + name: "Bill Jones" + place: "Berlin" + } + ListElement { + name: "Jane Doe" + place: "Oslo" + } + ListElement { + name: "John Smith" + place: "Oulo" + } + } + + Component { + id: delegateComponent + Rectangle { + id: myDelegate + required property int index + required property string name + required property string place + height: 100 + width: 100 + Text { + text: myDelegate.name + " lives in " + myDelegate.place + myDelegate.index + font.pointSize: 16 + anchors.fill: myDelegate + + Component.onCompleted: () => {console.info(myDelegate.name+myDelegate.place+myDelegate.index)} + } + } + } + + PathView { + anchors.fill: parent + model: myModel + delegate: delegateComponent + path: Path { + startX: 120; startY: 100 + PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 } + PathQuad { x: 120; y: 100; controlX: -20; controlY: 75 } + } + } + +} diff --git a/tests/auto/quick/qquickpathview/data/delegatewithUnrelatedRequiredPreventsAccessToModel.qml b/tests/auto/quick/qquickpathview/data/delegatewithUnrelatedRequiredPreventsAccessToModel.qml new file mode 100644 index 0000000000..bf130a2d73 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/delegatewithUnrelatedRequiredPreventsAccessToModel.qml @@ -0,0 +1,52 @@ +import QtQuick 2.14 + +Item { + width: 400 + height: 200 + + ListModel { + id: myModel + ListElement { + name: "Bill Jones" + place: "Berlin" + } + ListElement { + name: "Jane Doe" + place: "Oslo" + } + ListElement { + name: "John Smith" + place: "Oulo" + } + } + + Component { + id: delegateComponent + Rectangle { + id: myDelegate + required property int set + set: 42 + height: 100 + width: 100 + Text { + text: "Test" + font.pointSize: 16 + anchors.fill: myDelegate + + Component.onCompleted: () => { try {index; console.log(index); console.log(name)} catch(ex) {console.info(ex.name)} } + } + } + } + + PathView { + anchors.fill: parent + model: myModel + delegate: delegateComponent + path: Path { + startX: 120; startY: 100 + PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 } + PathQuad { x: 120; y: 100; controlX: -20; controlY: 75 } + } + } + +} diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 8b963117ed..36a242bf3b 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -149,6 +149,8 @@ private slots: void movementDirection(); void removePath(); void objectModelMove(); + void requiredPropertiesInDelegate(); + void requiredPropertiesInDelegatePreventUnrelated(); }; class TestObject : public QObject @@ -2658,6 +2660,34 @@ void tst_QQuickPathView::objectModelMove() } } +void tst_QQuickPathView::requiredPropertiesInDelegate() +{ + { + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "Bill JonesBerlin0"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "Jane DoeOslo1"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "John SmithOulo2"); + QScopedPointer<QQuickView> window(createView()); + window->setSource(testFileUrl("delegateWithRequiredProperties.qml")); + window->show(); + } + { + QScopedPointer<QQuickView> window(createView()); + window->setSource(testFileUrl("delegateWithRequiredProperties.2.qml")); + window->show(); + QTRY_VERIFY(window->rootObject()->property("working").toBool()); + } +} + +void tst_QQuickPathView::requiredPropertiesInDelegatePreventUnrelated() +{ + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "ReferenceError"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "ReferenceError"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "ReferenceError"); + QScopedPointer<QQuickView> window(createView()); + window->setSource(testFileUrl("delegatewithUnrelatedRequiredPreventsAccessToModel.qml")); + window->show(); +} + QTEST_MAIN(tst_QQuickPathView) #include "tst_qquickpathview.moc" diff --git a/tests/auto/quick/qquickrepeater/data/objlist_required.qml b/tests/auto/quick/qquickrepeater/data/objlist_required.qml new file mode 100644 index 0000000000..cc9dd9566c --- /dev/null +++ b/tests/auto/quick/qquickrepeater/data/objlist_required.qml @@ -0,0 +1,23 @@ +import QtQuick 2.0 + +Rectangle { + id: container + objectName: "container" + width: 240 + height: 320 + color: "white" + Repeater { + id: repeater + objectName: "repeater" + model: testData + property int errors: 0 + property int instantiated: 0 + Component { + Item{ + required property int index + required property int idx + Component.onCompleted: {if (index != idx) repeater.errors += 1; repeater.instantiated++} + } + } + } +} diff --git a/tests/auto/quick/qquickrepeater/data/requiredProperty.qml b/tests/auto/quick/qquickrepeater/data/requiredProperty.qml new file mode 100644 index 0000000000..80eb3c28ee --- /dev/null +++ b/tests/auto/quick/qquickrepeater/data/requiredProperty.qml @@ -0,0 +1,16 @@ +import QtQuick 2.14 + +Item { + Column { + Repeater { + model: ["apples", "oranges", "pears"] + Text { + id: txt + required property string modelData + required property int index + text: modelData + index + Component.onCompleted: () => {console.info(txt.text)} + } + } + } +} diff --git a/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp b/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp index 65e7d29595..ccfef63902 100644 --- a/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp +++ b/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp @@ -55,6 +55,7 @@ public: private slots: void numberModel(); + void objectList_data(); void objectList(); void stringList(); void dataModel_adding(); @@ -79,6 +80,7 @@ private slots: void QTBUG54859_asynchronousMove(); void package(); void ownership(); + void requiredProperties(); }; class TestObject : public QObject @@ -143,6 +145,14 @@ void tst_QQuickRepeater::numberModel() delete window; } +void tst_QQuickRepeater::objectList_data() +{ + QTest::addColumn<QUrl>("filename"); + + QTest::newRow("normal") << testFileUrl("objlist.qml"); + QTest::newRow("required") << testFileUrl("objlist_required.qml"); +} + class MyObject : public QObject { Q_OBJECT @@ -157,6 +167,7 @@ public: void tst_QQuickRepeater::objectList() { + QFETCH(QUrl, filename); QQuickView *window = createView(); QObjectList data; for (int i=0; i<100; i++) @@ -165,7 +176,7 @@ void tst_QQuickRepeater::objectList() QQmlContext *ctxt = window->rootContext(); ctxt->setContextProperty("testData", QVariant::fromValue(data)); - window->setSource(testFileUrl("objlist.qml")); + window->setSource(filename); qApp->processEvents(); QQuickRepeater *repeater = findItem<QQuickRepeater>(window->rootObject(), "repeater"); @@ -1108,6 +1119,18 @@ void tst_QQuickRepeater::ownership() QVERIFY(!modelGuard); } +void tst_QQuickRepeater::requiredProperties() +{ + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "apples0"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "oranges1"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "pears2"); + QQmlEngine engine; + + QQmlComponent component(&engine, testFileUrl("requiredProperty.qml")); + QScopedPointer<QObject> o {component.create()}; + QVERIFY(o); +} + QTEST_MAIN(tst_QQuickRepeater) #include "tst_qquickrepeater.moc" diff --git a/tests/auto/quick/qquicktableview/data/delegateWithRequired.qml b/tests/auto/quick/qquicktableview/data/delegateWithRequired.qml new file mode 100644 index 0000000000..bebfd86931 --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/delegateWithRequired.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Window 2.3 + +Item { + width: 640 + height: 450 + + property alias tableView: tableView + + TableView { + id: tableView + width: 600 + height: 400 + delegate: tableViewDelegate + } + + Component { + id: tableViewDelegate + Rectangle { + id: rect + required property string position + required property bool hasModelChildren + required property QtObject model + Text {text: rect.position} + implicitWidth: 100 + implicitHeight: 100 + Component.onCompleted: () => {if (rect.position === "R1:C1" && rect.model.hasModelChildren == rect.hasModelChildren) console.info("success")} + } + } + +} diff --git a/tests/auto/quick/qquicktableview/data/delegatewithRequiredUnset.qml b/tests/auto/quick/qquicktableview/data/delegatewithRequiredUnset.qml new file mode 100644 index 0000000000..0c685cd49e --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/delegatewithRequiredUnset.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Window 2.3 + +Item { + width: 640 + height: 450 + + property alias tableView: tableView + + TableView { + id: tableView + width: 600 + height: 400 + delegate: tableViewDelegate + } + + Component { + id: tableViewDelegate + Rectangle { + id: rect + required property string position + required property bool unset + required property bool hasModelChildren + required property QtObject model + Text {text: rect.position} + implicitWidth: 100 + implicitHeight: 100 + Component.onCompleted: () => {if (rect.position === "R1:C1" && rect.model.hasModelChildren == rect.hasModelChildren) console.info("success")} + } + } + +} diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 19967efcd9..e3f79995d2 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -172,6 +172,7 @@ private slots: void checkSyncView_differentSizedModels(); void checkSyncView_connect_late_data(); void checkSyncView_connect_late(); + void delegateWithRequiredProperties(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -2592,6 +2593,50 @@ void tst_QQuickTableView::checkSyncView_connect_late() } +void tst_QQuickTableView::delegateWithRequiredProperties() +{ + constexpr static int PositionRole = Qt::UserRole+1; + struct MyTable : QAbstractTableModel { + + + using QAbstractTableModel::QAbstractTableModel; + + int rowCount(const QModelIndex& = QModelIndex()) const override { + return 3; + } + + int columnCount(const QModelIndex& = QModelIndex()) const override { + return 3; + } + + QVariant data(const QModelIndex &index, int = Qt::DisplayRole) const override { + return QVariant::fromValue(QString::asprintf("R%d:C%d", index.row(), index.column())); + } + + QHash<int, QByteArray> roleNames() const override { + return QHash<int, QByteArray> { {PositionRole, "position"} }; + } + }; + + auto model = QVariant::fromValue(QSharedPointer<MyTable>(new MyTable)); + { + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "success"); + LOAD_TABLEVIEW("delegateWithRequired.qml") + QVERIFY(tableView); + tableView->setModel(model); + WAIT_UNTIL_POLISHED; + QVERIFY(view->errors().empty()); + } + { + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(R"|(TableView: failed loading index: \d)|")); + LOAD_TABLEVIEW("delegatewithRequiredUnset.qml") + QVERIFY(tableView); + tableView->setModel(model); + WAIT_UNTIL_POLISHED; + QTRY_VERIFY(view->errors().empty()); + } +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" |