diff options
author | Ulf Hermann <[email protected]> | 2025-05-26 17:07:28 +0200 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2025-05-29 19:03:34 +0200 |
commit | 58f50363b1e7d21d20b83bac945bd24e85863885 (patch) | |
tree | 56ebcb2a1e6f4697742f85e7bcdfa610ac2659a9 | |
parent | 7142e4bcceb6017191dc22935f0758bd68a25e5c (diff) |
It mirrors the same property from any internal delegate model. By
default, the value is "Qt5ReadWrite".
[ChangeLog][Location] MapItemView now has a new property
delegateModelAccess. Setting it to DelegateModel.ReadWrite allows you to
write values into the model via required properties just as you could
with context properties.
Task-number: QTBUG-132420
Change-Id: Ie0795fc6f793e71def2a9fa2af711dfac942ddfc
Reviewed-by: Fabian Kosmale <[email protected]>
Reviewed-by: Matthias Rauter <[email protected]>
-rw-r--r-- | src/location/quickmapitems/qdeclarativegeomapitemview.cpp | 49 | ||||
-rw-r--r-- | src/location/quickmapitems/qdeclarativegeomapitemview_p.h | 17 | ||||
-rw-r--r-- | tests/auto/declarative_ui/Delegate.qml | 12 | ||||
-rw-r--r-- | tests/auto/declarative_ui/Model.qml | 14 | ||||
-rw-r--r-- | tests/auto/declarative_ui/tst_map_itemview.qml | 224 |
5 files changed, 312 insertions, 4 deletions
diff --git a/src/location/quickmapitems/qdeclarativegeomapitemview.cpp b/src/location/quickmapitems/qdeclarativegeomapitemview.cpp index a97174a6..aae41965 100644 --- a/src/location/quickmapitems/qdeclarativegeomapitemview.cpp +++ b/src/location/quickmapitems/qdeclarativegeomapitemview.cpp @@ -87,6 +87,7 @@ void QDeclarativeGeoMapItemView::componentComplete() if (m_delegate) m_delegateModel->setDelegate(m_delegate); + m_delegateModel->setDelegateModelAccess(m_delegateModelAccess); m_delegateModel->componentComplete(); } @@ -189,8 +190,14 @@ void QDeclarativeGeoMapItemView::setModel(const QVariant &model) return; m_itemModel = model; - if (m_componentCompleted) + if (m_componentCompleted) { + + // Make sure to clear all stale items from the map and the model + m_delegateModel->drainReusableItemsPool(0); + removeInstantiatedItems(false); + m_delegateModel->setModel(m_itemModel); + } emit modelChanged(); } @@ -315,6 +322,46 @@ bool QDeclarativeGeoMapItemView::incubateDelegates() const return m_incubationMode == QQmlIncubator::Asynchronous; } +/*! + \qmlproperty enumeration QtLocation::MapItemView::delegateModelAccess + \since 6.10 + + This property determines how delegates can access the model. + + \value DelegateModel.ReadOnly + Prohibit delegates from writing the model via either context properties, + the \c model object, or required properties. + + \value DelegateModel.ReadWrite + Allow delegates to write the model via either context properties, + the \c model object, or required properties. + + \value DelegateModel.Qt5ReadWrite + Allow delegates to write the model via the \c model object and context + properties but \e not via required properties. + + The default is \c DelegateModel.Qt5ReadWrite. + + \sa {Models and Views in Qt Quick#Changing Model Data} +*/ +QQmlDelegateModel::DelegateModelAccess QDeclarativeGeoMapItemView::delegateModelAccess() const +{ + return m_delegateModelAccess; +} + +void QDeclarativeGeoMapItemView::setDelegateModelAccess( + QQmlDelegateModel::DelegateModelAccess delegateModelAccess) +{ + if (m_delegateModelAccess == delegateModelAccess) + return; + + m_delegateModelAccess = delegateModelAccess; + if (m_componentCompleted) + m_delegateModel->setDelegateModelAccess(m_delegateModelAccess); + + emit delegateModelAccessChanged(); +} + QList<QQuickItem *> QDeclarativeGeoMapItemView::mapItems() { return m_instantiatedItems; diff --git a/src/location/quickmapitems/qdeclarativegeomapitemview_p.h b/src/location/quickmapitems/qdeclarativegeomapitemview_p.h index 4b4078ee..57ae5561 100644 --- a/src/location/quickmapitems/qdeclarativegeomapitemview_p.h +++ b/src/location/quickmapitems/qdeclarativegeomapitemview_p.h @@ -52,6 +52,9 @@ class Q_LOCATION_EXPORT QDeclarativeGeoMapItemView : public QDeclarativeGeoMapIt Q_PROPERTY(QQuickTransition *remove MEMBER m_exit REVISION(5, 12)) Q_PROPERTY(QList<QQuickItem *> mapItems READ mapItems REVISION(5, 12)) Q_PROPERTY(bool incubateDelegates READ incubateDelegates WRITE setIncubateDelegates NOTIFY incubateDelegatesChanged REVISION(5, 12)) + Q_PROPERTY(QQmlDelegateModel::DelegateModelAccess delegateModelAccess READ delegateModelAccess + WRITE setDelegateModelAccess NOTIFY delegateModelAccessChanged REVISION(6, 10) FINAL) + public: explicit QDeclarativeGeoMapItemView(QQuickItem *parent = nullptr); @@ -73,6 +76,9 @@ public: void setIncubateDelegates(bool useIncubators); bool incubateDelegates() const; + QQmlDelegateModel::DelegateModelAccess delegateModelAccess() const; + void setDelegateModelAccess(QQmlDelegateModel::DelegateModelAccess delegateModelAccess); + QList<QQuickItem *> mapItems(); // From QQmlParserStatus @@ -84,6 +90,7 @@ Q_SIGNALS: void delegateChanged(); void autoFitViewportChanged(); void incubateDelegatesChanged(); + Q_REVISION(6, 10) void delegateModelAccessChanged(); private Q_SLOTS: void destroyingItem(QObject *object); @@ -106,18 +113,22 @@ private: void addItemGroupToMap(QDeclarativeGeoMapItemGroup *item, int index, bool createdItem); void addDelegateToMap(QQuickItem *object, int index, bool createdItem = false); - bool m_componentCompleted = false; QQmlIncubator::IncubationMode m_incubationMode = QQmlIncubator::Asynchronous; QQmlComponent *m_delegate = nullptr; QVariant m_itemModel; QDeclarativeGeoMap *m_map = nullptr; QList<QQuickItem *> m_instantiatedItems; - bool m_fitViewport = false; - bool m_creatingObject = false; QQmlDelegateModel *m_delegateModel = nullptr; QQuickTransition *m_enter = nullptr; QQuickTransition *m_exit = nullptr; + QQmlDelegateModel::DelegateModelAccess m_delegateModelAccess + = QQmlDelegateModel::Qt5ReadWrite; + + bool m_componentCompleted = false; + bool m_fitViewport = false; + bool m_creatingObject = false; + friend class QDeclarativeGeoMap; friend class QDeclarativeGeoMapItemBase; friend class QDeclarativeGeoMapItemTransitionManager; diff --git a/tests/auto/declarative_ui/Delegate.qml b/tests/auto/declarative_ui/Delegate.qml new file mode 100644 index 00000000..210aae29 --- /dev/null +++ b/tests/auto/declarative_ui/Delegate.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + enum Variants { + None = -1, + Untyped, + Typed + } +} diff --git a/tests/auto/declarative_ui/Model.qml b/tests/auto/declarative_ui/Model.qml new file mode 100644 index 00000000..fb3681de --- /dev/null +++ b/tests/auto/declarative_ui/Model.qml @@ -0,0 +1,14 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + enum Variants { + None = -1, + Singular, + List, + Array, + Object + } +} diff --git a/tests/auto/declarative_ui/tst_map_itemview.qml b/tests/auto/declarative_ui/tst_map_itemview.qml index fc4e9472..d4f84b72 100644 --- a/tests/auto/declarative_ui/tst_map_itemview.qml +++ b/tests/auto/declarative_ui/tst_map_itemview.qml @@ -20,6 +20,7 @@ Item { && mapForView.mapReady && mapForTestingListModel.mapReady && mapForTestingRouteModel.mapReady + && mapForTestingDelegateModelAccess.mapReady MapItemView { id: routeItemViewExtra @@ -274,6 +275,100 @@ Item { } } + Map { + id: mapForTestingDelegateModelAccess + + property int mapItemsLength: mapItems.length + + plugin: testPlugin + center: mapDefaultCenter + anchors.fill: parent + zoomLevel: 2 + + MapItemView { + id: delegateModelAccessItemView + width: 100 + height: 100 + + property Component typedDelegate: MapQuickItem { + implicitWidth: 10 + implicitHeight: 10 + + required property QtObject model + + required property real a + + property real immediateX: a + property real modelX: model.a + + function writeImmediate() { + a = 1; + } + + function writeThroughModel() { + model.a = 3; + } + } + + property Component untypedDelegate: MapQuickItem { + implicitWidth: 10 + implicitHeight: 10 + + property real immediateX: a + property real modelX: model.a + + function writeImmediate() { + a = 1; + } + + function writeThroughModel() { + model.a = 3; + } + } + + property Component singularModel: ListModel { + ListElement { + a: 11 + } + } + + property Component listModel: ListModel { + ListElement { + a: 11 + y: 12 + } + } + + function array() { return [ {a: 11, y: 12} ] } + + property Component object: QtObject { + property int a: 11 + property int y: 12 + } + + property int modelIndex: Model.None + property int delegateIndex: Delegate.None + + model: { + switch (modelIndex) { + case Model.Singular: return singularModel.createObject() + case Model.List: return listModel.createObject() + case Model.Array: return array() + case Model.Object: return object.createObject() + } + return undefined; + } + + delegate: { + switch (delegateIndex) { + case Delegate.Untyped: return untypedDelegate + case Delegate.Typed: return typedDelegate + } + return null + } + } + } + TestCase { name: "MapItem" when: windowShown && allMapsReady @@ -555,5 +650,134 @@ Item { mapForTestingRouteModel.clearMapItems() compare(mapForTestingRouteModel.mapItems.length, 0) } + + function test_delegateModelAccess_data() { + function modelKey(value) { + switch (value) { + case Model.None: + return "None" + case Model.Singular: + return "Singular" + case Model.List: + return "List" + case Model.Array: + return "Array" + case Model.Object: + return "Object" + default: + break + } + + return "" + } + + function delegateKey(value) { + switch (value) { + case Delegate.None: + return "None" + case Delegate.Untyped: + return "Untyped" + case Delegate.Typed: + return "Typed" + default: + break + } + + return "" + } + + function accessKey(value) { + switch (value) { + case DelegateModel.Qt5ReadWrite: + return "Qt5ReadWrite" + case DelegateModel.ReadOnly: + return "ReadOnly" + case DelegateModel.ReadWrite: + return "ReadWrite" + default: + break; + } + + return ""; + } + + let data = []; + for (let access of [ + DelegateModel.Qt5ReadWrite, + DelegateModel.ReadOnly, + DelegateModel.ReadWrite]) { + for (let model of [Model.Singular, Model.List, Model.Array, Model.Object]) { + for (let delegate of [Delegate.Untyped, Delegate.Typed]) { + data.push({ + tag: `${accessKey(access)}-${modelKey(model)}-${delegateKey(delegate)}`, + access: access, + modelKind: model, + delegateKind: delegate + }); + } + } + } + return data + } + + function test_delegateModelAccess(data) { + delegateModelAccessItemView.delegateModelAccess = DelegateModel.Qt5ReadWrite + delegateModelAccessItemView.modelIndex = Model.None + delegateModelAccessItemView.delegateIndex = Delegate.None + tryCompare(mapForTestingDelegateModelAccess, "mapItemsLength", 0) + + const access = data.access + const modelKind = data.modelKind + const delegateKind = data.delegateKind + + if (delegateKind === Delegate.Untyped && modelKind === Model.Array) + skip("Properties of objects in arrays are not exposed as context properties") + + delegateModelAccessItemView.delegateModelAccess = access + delegateModelAccessItemView.modelIndex = modelKind + delegateModelAccessItemView.delegateIndex = delegateKind + + tryCompare(mapForTestingDelegateModelAccess, "mapItemsLength", 1) + const delegate = mapForTestingDelegateModelAccess.mapItems[0] + verify(delegate) + + const modelWritable = (access !== DelegateModel.ReadOnly) + const immediateWritable = (delegateKind === Delegate.Untyped) + ? access !== DelegateModel.ReadOnly + : access === DelegateModel.ReadWrite + + let expected = 11 + + compare(delegate.immediateX, expected) + compare(delegate.modelX, expected) + + if (modelWritable) + expected = 3 + + try { + delegate.writeThroughModel() + } catch (e1) { + compare(e1.message, 'Cannot assign to read-only property "a"') + compare(access, DelegateModel.ReadOnly) + } + + compare(delegate.immediateX, expected) + compare(delegate.modelX, expected) + + if (immediateWritable) + expected = 1 + + try { + delegate.writeImmediate() + } catch (e2) { + compare(e2.message, 'Cannot assign to read-only property "a"') + compare(access, DelegateModel.ReadOnly) + compare(delegateKind, Delegate.Untyped) + } + + // Writes to required properties always succeed, but might not be propagated to the model + compare(delegate.immediateX, delegateKind === Delegate.Untyped ? expected : 1) + compare(delegate.modelX, expected) + } } } |