aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlmodels
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlmodels')
-rw-r--r--src/qmlmodels/configure.json46
-rw-r--r--src/qmlmodels/doc/qtqmlmodels.qdocconf37
-rw-r--r--src/qmlmodels/doc/snippets/delegatemodel/delegatemodel.qml75
-rw-r--r--src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/main.cpp72
-rw-r--r--src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/view.qml76
-rw-r--r--src/qmlmodels/doc/snippets/delegatemodel/delegatemodelgroup.qml92
-rw-r--r--src/qmlmodels/doc/snippets/package/Delegate.qml88
-rw-r--r--src/qmlmodels/doc/snippets/package/view.qml103
-rw-r--r--src/qmlmodels/doc/snippets/qml/listmodel/listelements.qml87
-rw-r--r--src/qmlmodels/doc/snippets/qml/listmodel/listmodel-modify.qml106
-rw-r--r--src/qmlmodels/doc/snippets/qml/listmodel/listmodel-nested.qml112
-rw-r--r--src/qmlmodels/doc/snippets/qml/listmodel/listmodel-simple.qml89
-rw-r--r--src/qmlmodels/doc/snippets/qml/listmodel/listmodel.qml69
-rw-r--r--src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-complex.qml134
-rw-r--r--src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml130
-rw-r--r--src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml119
-rw-r--r--src/qmlmodels/qmlmodels.pro71
-rw-r--r--src/qmlmodels/qqmladaptormodel.cpp1043
-rw-r--r--src/qmlmodels/qqmladaptormodel_p.h181
-rw-r--r--src/qmlmodels/qqmlchangeset.cpp583
-rw-r--r--src/qmlmodels/qqmlchangeset_p.h161
-rw-r--r--src/qmlmodels/qqmldelegatecomponent.cpp321
-rw-r--r--src/qmlmodels/qqmldelegatecomponent_p.h155
-rw-r--r--src/qmlmodels/qqmldelegatemodel.cpp3560
-rw-r--r--src/qmlmodels/qqmldelegatemodel_p.h247
-rw-r--r--src/qmlmodels/qqmldelegatemodel_p_p.h450
-rw-r--r--src/qmlmodels/qqmlinstantiator.cpp512
-rw-r--r--src/qmlmodels/qqmlinstantiator_p.h121
-rw-r--r--src/qmlmodels/qqmlinstantiator_p_p.h100
-rw-r--r--src/qmlmodels/qqmlitemmodels.qdoc110
-rw-r--r--src/qmlmodels/qqmlitemselectionmodel.qdoc239
-rw-r--r--src/qmlmodels/qqmllistaccessor.cpp160
-rw-r--r--src/qmlmodels/qqmllistaccessor_p.h83
-rw-r--r--src/qmlmodels/qqmllistcompositor.cpp1482
-rw-r--r--src/qmlmodels/qqmllistcompositor_p.h372
-rw-r--r--src/qmlmodels/qqmllistmodel.cpp2920
-rw-r--r--src/qmlmodels/qqmllistmodel_p.h209
-rw-r--r--src/qmlmodels/qqmllistmodel_p_p.h431
-rw-r--r--src/qmlmodels/qqmllistmodelworkeragent.cpp185
-rw-r--r--src/qmlmodels/qqmllistmodelworkeragent_p.h128
-rw-r--r--src/qmlmodels/qqmlmodelsmodule.cpp139
-rw-r--r--src/qmlmodels/qqmlmodelsmodule_p.h72
-rw-r--r--src/qmlmodels/qqmlobjectmodel.cpp431
-rw-r--r--src/qmlmodels/qqmlobjectmodel_p.h196
-rw-r--r--src/qmlmodels/qqmltableinstancemodel.cpp547
-rw-r--r--src/qmlmodels/qqmltableinstancemodel_p.h164
-rw-r--r--src/qmlmodels/qqmltablemodel.cpp1059
-rw-r--r--src/qmlmodels/qqmltablemodel_p.h172
-rw-r--r--src/qmlmodels/qqmltablemodelcolumn.cpp200
-rw-r--r--src/qmlmodels/qqmltablemodelcolumn_p.h226
-rw-r--r--src/qmlmodels/qquickpackage.cpp201
-rw-r--r--src/qmlmodels/qquickpackage_p.h104
-rw-r--r--src/qmlmodels/qtqmlmodelsglobal.h59
-rw-r--r--src/qmlmodels/qtqmlmodelsglobal_p.h61
54 files changed, 18890 insertions, 0 deletions
diff --git a/src/qmlmodels/configure.json b/src/qmlmodels/configure.json
new file mode 100644
index 0000000000..1ccca5e35e
--- /dev/null
+++ b/src/qmlmodels/configure.json
@@ -0,0 +1,46 @@
+{
+ "module": "qmlmodels",
+ "depends": [
+ "core-private",
+ "qml-private"
+ ],
+
+ "features": {
+ "qml-object-model" : {
+ "label": "QML object model",
+ "purpose": "Provides the ObjectModel and Instantiator QML types.",
+ "section": "QML",
+ "output": [ "privateFeature" ]
+ },
+ "qml-list-model": {
+ "label": "QML list model",
+ "purpose": "Provides the ListModel QML type.",
+ "section": "QML",
+ "condition": "features.qml-itemmodel",
+ "output": [ "privateFeature" ]
+ },
+ "qml-delegate-model": {
+ "label": "QML delegate model",
+ "purpose": "Provides the DelegateModel QML type.",
+ "section": "QML",
+ "condition": "features.qml-object-model && features.qml-itemmodel",
+ "output": [ "privateFeature" ]
+ },
+ "qml-table-model": {
+ "label": "QML table model",
+ "purpose": "Provides the TableModel QML type.",
+ "section": "QML",
+ "condition": "features.qml-itemmodel && features.qml-delegate-model",
+ "output": [ "privateFeature" ]
+ }
+ },
+ "summary": [
+ {
+ "section": "Qt QML Models",
+ "entries": [
+ "qml-list-model",
+ "qml-delegate-model"
+ ]
+ }
+ ]
+}
diff --git a/src/qmlmodels/doc/qtqmlmodels.qdocconf b/src/qmlmodels/doc/qtqmlmodels.qdocconf
new file mode 100644
index 0000000000..a4153f4a53
--- /dev/null
+++ b/src/qmlmodels/doc/qtqmlmodels.qdocconf
@@ -0,0 +1,37 @@
+include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+include($QT_INSTALL_DOCS/config/exampleurl-qtdeclarative.qdocconf)
+
+project = QtQmlModels
+description = Qt Qml Models Reference Documentation
+version = $QT_VERSION
+moduleheader = QtQmlModels
+qhp.projects = QtQmlModels
+
+qhp.QtQmlModels.file = qtqmlmodels.qhp
+qhp.QtQmlModels.namespace = org.qt-project.qtqmlmodels.$QT_VERSION_TAG
+qhp.QtQmlModels.virtualFolder = qtqmlmodels
+qhp.QtQmlModels.indexRoot =
+
+qhp.QtQmlModels.filterAttributes = qtqmlmodels $QT_VERSION qtrefdoc
+qhp.QtQmlModels.customFilters.Qt.name = QtQmlModels $QT_VERSION
+qhp.QtQmlModels.customFilters.Qt.filterAttributes = qtqmlmodels $QT_VERSION
+
+qhp.QtQmlModels.title = QML Types
+qhp.QtQmlModels.indexTitle = Qt QML Models QML Types
+qhp.QtQmlModels.selectors = qmlclass
+qhp.QtQmlModels.sortPages = true
+
+tagfile = qtqmlmodels.tags
+
+depends += qtcore qtqml qtdoc
+
+headerdirs += ..
+
+sourcedirs += .. \
+ ../../imports/models
+
+exampledirs += ../../../examples/qml \
+ ../ \
+ snippets
+
+navigation.qmltypespage = "Qt Qml Models QML Types"
diff --git a/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel.qml b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel.qml
new file mode 100644
index 0000000000..1a7baa6b1e
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel.qml
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.0
+import QtQml.Models 2.2
+
+Rectangle {
+ width: 200; height: 100
+
+ DelegateModel {
+ id: visualModel
+ model: ListModel {
+ ListElement { name: "Apple" }
+ ListElement { name: "Orange" }
+ }
+ delegate: Rectangle {
+ height: 25
+ width: 100
+ Text { text: "Name: " + name}
+ }
+ }
+
+ ListView {
+ anchors.fill: parent
+ model: visualModel
+ }
+}
+//![0]
diff --git a/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/main.cpp b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/main.cpp
new file mode 100644
index 0000000000..a56eb69616
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/main.cpp
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QQuickView>
+#include <QQmlContext>
+
+#include <QApplication>
+#include <QDirModel>
+
+//![0]
+int main(int argc, char ** argv)
+{
+ QApplication app(argc, argv);
+
+ QQuickView view;
+
+ QDirModel model;
+ view.rootContext()->setContextProperty("dirModel", &model);
+
+ view.setSource(QUrl::fromLocalFile("view.qml"));
+ view.show();
+
+ return app.exec();
+}
+//![0]
+
diff --git a/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/view.qml b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/view.qml
new file mode 100644
index 0000000000..2e17eed8f0
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodel_rootindex/view.qml
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.0
+import QtQml.Models 2.2
+
+ListView {
+ id: view
+ width: 300
+ height: 400
+
+ model: DelegateModel {
+ model: dirModel
+
+ delegate: Rectangle {
+ width: 200; height: 25
+ Text { text: filePath }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (model.hasModelChildren)
+ view.model.rootIndex = view.model.modelIndex(index)
+ }
+ }
+ }
+ }
+}
+//![0]
diff --git a/src/qmlmodels/doc/snippets/delegatemodel/delegatemodelgroup.qml b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodelgroup.qml
new file mode 100644
index 0000000000..8562deeeda
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/delegatemodel/delegatemodelgroup.qml
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.0
+import QtQml.Models 2.2
+
+Rectangle {
+ width: 200; height: 100
+
+ DelegateModel {
+ id: visualModel
+ model: ListModel {
+ ListElement { name: "Apple" }
+ ListElement { name: "Orange" }
+ }
+
+ groups: [
+ DelegateModelGroup { name: "selected" }
+ ]
+
+ delegate: Rectangle {
+ id: item
+ height: 25
+ width: 200
+ Text {
+ text: {
+ var text = "Name: " + name
+ if (item.DelegateModel.inSelected)
+ text += " (" + item.DelegateModel.selectedIndex + ")"
+ return text;
+ }
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: item.DelegateModel.inSelected = !item.DelegateModel.inSelected
+ }
+ }
+ }
+
+ ListView {
+ anchors.fill: parent
+ model: visualModel
+ }
+}
+//![0]
diff --git a/src/qmlmodels/doc/snippets/package/Delegate.qml b/src/qmlmodels/doc/snippets/package/Delegate.qml
new file mode 100644
index 0000000000..7c73f35c3d
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/package/Delegate.qml
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+//! [0]
+Package {
+ Text { id: listDelegate; width: parent.width; height: 25; text: 'Empty'; Package.name: 'list' }
+ Text { id: gridDelegate; width: parent.width / 2; height: 50; text: 'Empty'; Package.name: 'grid' }
+
+ Rectangle {
+ id: wrapper
+ width: parent.width; height: 25
+ color: 'lightsteelblue'
+
+ Text { text: display; anchors.centerIn: parent }
+ state: root.upTo > index ? 'inGrid' : 'inList'
+ states: [
+ State {
+ name: 'inList'
+ ParentChange { target: wrapper; parent: listDelegate }
+ },
+ State {
+ name: 'inGrid'
+ ParentChange {
+ target: wrapper; parent: gridDelegate
+ x: 0; y: 0; width: gridDelegate.width; height: gridDelegate.height
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ ParentAnimation {
+ NumberAnimation { properties: 'x,y,width,height'; duration: 300 }
+ }
+ }
+ ]
+ }
+}
+//! [0]
diff --git a/src/qmlmodels/doc/snippets/package/view.qml b/src/qmlmodels/doc/snippets/package/view.qml
new file mode 100644
index 0000000000..311cc3be8e
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/package/view.qml
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQml.Models 2.1
+
+Rectangle {
+ id: root
+ color: "white"
+ width: 320
+ height: 480
+ property int upTo: 0
+ SequentialAnimation on upTo {
+ loops: -1
+ NumberAnimation { to: 8; duration: 3500 }
+ NumberAnimation { to: 0; duration: 3500 }
+ }
+
+ ListModel {
+ id: myModel
+ ListElement { display: "One" }
+ ListElement { display: "Two" }
+ ListElement { display: "Three" }
+ ListElement { display: "Four" }
+ ListElement { display: "Five" }
+ ListElement { display: "Six" }
+ ListElement { display: "Seven" }
+ ListElement { display: "Eight" }
+ }
+ //![0]
+ DelegateModel {
+ id: visualModel
+ delegate: Delegate {}
+ model: myModel
+ }
+
+ ListView {
+ id: lv
+ height: parent.height/2
+ width: parent.width
+
+ model: visualModel.parts.list
+ }
+ GridView {
+ y: parent.height/2
+ height: parent.height/2
+ width: parent.width
+ cellWidth: width / 2
+ cellHeight: 50
+ model: visualModel.parts.grid
+ }
+ //![0]
+ Text {
+ anchors.bottom: parent.bottom
+ }
+}
diff --git a/src/qmlmodels/doc/snippets/qml/listmodel/listelements.qml b/src/qmlmodels/doc/snippets/qml/listmodel/listelements.qml
new file mode 100644
index 0000000000..12146c1420
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/listmodel/listelements.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//! [document]
+import QtQuick 2.0
+
+Item {
+ width: 200; height: 250
+
+ //! [model]
+ ListModel {
+ id: fruitModel
+
+ ListElement {
+ name: "Apple"
+ cost: 2.45
+ }
+ ListElement {
+ name: "Orange"
+ cost: 3.25
+ }
+ ListElement {
+ name: "Banana"
+ cost: 1.95
+ }
+ }
+ //! [model]
+
+ //! [view]
+ ListView {
+ anchors.fill: parent
+ model: fruitModel
+ delegate: Row {
+ Text { text: "Fruit: " + name }
+ Text { text: "Cost: $" + cost }
+ }
+ }
+ //! [view]
+}
+//! [document]
diff --git a/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-modify.qml b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-modify.qml
new file mode 100644
index 0000000000..f293eff8ec
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-modify.qml
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.0
+
+Rectangle {
+ width: 200; height: 200
+
+ListModel {
+ id: fruitModel
+
+ ListElement {
+ name: "Apple"
+ cost: 2.45
+ attributes: [
+ ListElement { description: "Core" },
+ ListElement { description: "Deciduous" }
+ ]
+ }
+ ListElement {
+ name: "Orange"
+ cost: 3.25
+ attributes: [
+ ListElement { description: "Citrus" }
+ ]
+ }
+ ListElement {
+ name: "Banana"
+ cost: 1.95
+ attributes: [
+ ListElement { description: "Tropical" },
+ ListElement { description: "Seedless" }
+ ]
+ }
+}
+
+//![delegate]
+ Component {
+ id: fruitDelegate
+ Item {
+ width: 200; height: 50
+ Text { text: name }
+ Text { text: '$' + cost; anchors.right: parent.right }
+
+ // Double the price when clicked.
+ MouseArea {
+ anchors.fill: parent
+ onClicked: fruitModel.setProperty(index, "cost", cost * 2)
+ }
+ }
+ }
+//![delegate]
+
+ListView {
+ width: 200; height: 200
+ model: fruitModel
+ delegate: fruitDelegate
+}
+
+}
diff --git a/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-nested.qml b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-nested.qml
new file mode 100644
index 0000000000..8c193d6a5e
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-nested.qml
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.0
+
+Rectangle {
+ width: 200; height: 200
+
+
+//![model]
+ListModel {
+ id: fruitModel
+
+ ListElement {
+ name: "Apple"
+ cost: 2.45
+ attributes: [
+ ListElement { description: "Core" },
+ ListElement { description: "Deciduous" }
+ ]
+ }
+ ListElement {
+ name: "Orange"
+ cost: 3.25
+ attributes: [
+ ListElement { description: "Citrus" }
+ ]
+ }
+ ListElement {
+ name: "Banana"
+ cost: 1.95
+ attributes: [
+ ListElement { description: "Tropical" },
+ ListElement { description: "Seedless" }
+ ]
+ }
+}
+//![model]
+
+//![delegate]
+Component {
+ id: fruitDelegate
+ Item {
+ width: 200; height: 50
+ Text { id: nameField; text: name }
+ Text { text: '$' + cost; anchors.left: nameField.right }
+ Row {
+ anchors.top: nameField.bottom
+ spacing: 5
+ Text { text: "Attributes:" }
+ Repeater {
+ model: attributes
+ Text { text: description }
+ }
+ }
+ }
+}
+//![delegate]
+
+ListView {
+ width: 200; height: 200
+ model: fruitModel
+ delegate: fruitDelegate
+}
+
+}
diff --git a/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-simple.qml b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-simple.qml
new file mode 100644
index 0000000000..d07f868476
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel-simple.qml
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.0
+
+Rectangle {
+ width: 200; height: 200
+
+ ListModel {
+ id: fruitModel
+//![0]
+ ListElement {
+ name: "Apple"
+ cost: 2.45
+ }
+ ListElement {
+ name: "Orange"
+ cost: 3.25
+ }
+ ListElement {
+ name: "Banana"
+ cost: 1.95
+ }
+//![1]
+ }
+
+ Component {
+ id: fruitDelegate
+ Row {
+ spacing: 10
+ Text { text: name }
+ Text { text: '$' + cost }
+ }
+ }
+
+ ListView {
+ anchors.fill: parent
+ model: fruitModel
+ delegate: fruitDelegate
+ }
+}
+//![1]
diff --git a/src/qmlmodels/doc/snippets/qml/listmodel/listmodel.qml b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel.qml
new file mode 100644
index 0000000000..c2a69d6e8f
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/listmodel/listmodel.qml
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+//![0]
+import QtQuick 2.0
+
+ListModel {
+ id: fruitModel
+
+ ListElement {
+ name: "Apple"
+ cost: 2.45
+ }
+ ListElement {
+ name: "Orange"
+ cost: 3.25
+ }
+ ListElement {
+ name: "Banana"
+ cost: 1.95
+ }
+}
+//![0]
diff --git a/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-complex.qml b/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-complex.qml
new file mode 100644
index 0000000000..104a2209d7
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-complex.qml
@@ -0,0 +1,134 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//![file]
+import QtQuick 2.12
+import QtQuick.Window 2.12
+import Qt.labs.qmlmodels 1.0
+
+Window {
+ width: 400
+ height: 400
+ visible: true
+
+ TableView {
+ anchors.fill: parent
+ columnSpacing: 1
+ rowSpacing: 1
+ boundsBehavior: Flickable.StopAtBounds
+
+ model: TableModel {
+ TableModelColumn {
+ display: function(modelIndex) { return rows[modelIndex.row][0].checked }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData }
+ }
+ TableModelColumn {
+ display: function(modelIndex) { return rows[modelIndex.row][1].amount }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData }
+ }
+ TableModelColumn {
+ display: function(modelIndex) { return rows[modelIndex.row][2].fruitType }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][2].fruitType = cellData }
+ }
+ TableModelColumn {
+ display: function(modelIndex) { return rows[modelIndex.row][3].fruitName }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][3].fruitName = cellData }
+ }
+ TableModelColumn {
+ display: function(modelIndex) { return rows[modelIndex.row][4].fruitPrice }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][4].fruitPrice = cellData }
+ }
+
+ // Each row is one type of fruit that can be ordered
+//![rows]
+ rows: [
+ [
+ // Each object (line) is one cell/column.
+ { checked: false, checkable: true },
+ { amount: 1 },
+ { fruitType: "Apple" },
+ { fruitName: "Granny Smith" },
+ { fruitPrice: 1.50 }
+ ],
+ [
+ { checked: true, checkable: true },
+ { amount: 4 },
+ { fruitType: "Orange" },
+ { fruitName: "Navel" },
+ { fruitPrice: 2.50 }
+ ],
+ [
+ { checked: false, checkable: false },
+ { amount: 1 },
+ { fruitType: "Banana" },
+ { fruitName: "Cavendish" },
+ { fruitPrice: 3.50 }
+ ]
+ ]
+//![rows]
+ }
+//![delegate]
+ delegate: TextInput {
+ text: model.display
+ padding: 12
+ selectByMouse: true
+
+ onAccepted: model.display = text
+
+ Rectangle {
+ anchors.fill: parent
+ color: "#efefef"
+ z: -1
+ }
+ }
+//![delegate]
+ }
+}
+//![file]
diff --git a/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml b/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml
new file mode 100644
index 0000000000..d3f6176c70
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//![file]
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import Qt.labs.qmlmodels 1.0
+
+ApplicationWindow {
+ width: 400
+ height: 400
+ visible: true
+
+ TableView {
+ anchors.fill: parent
+ columnSpacing: 1
+ rowSpacing: 1
+ boundsBehavior: Flickable.StopAtBounds
+
+ model: TableModel {
+ TableModelColumn { display: "checked" }
+ TableModelColumn { display: "amount" }
+ TableModelColumn { display: "fruitType" }
+ TableModelColumn { display: "fruitName" }
+ TableModelColumn { display: "fruitPrice" }
+
+ // Each row is one type of fruit that can be ordered
+//![rows]
+ rows: [
+ {
+ // Each property is one cell/column.
+ checked: false,
+ amount: 1,
+ fruitType: "Apple",
+ fruitName: "Granny Smith",
+ fruitPrice: 1.50
+ },
+ {
+ checked: true,
+ amount: 4,
+ fruitType: "Orange",
+ fruitName: "Navel",
+ fruitPrice: 2.50
+ },
+ {
+ checked: false,
+ amount: 1,
+ fruitType: "Banana",
+ fruitName: "Cavendish",
+ fruitPrice: 3.50
+ }
+ ]
+//![rows]
+ }
+//![delegate]
+ delegate: DelegateChooser {
+ DelegateChoice {
+ column: 0
+ delegate: CheckBox {
+ checked: model.display
+ onToggled: model.display = checked
+ }
+ }
+ DelegateChoice {
+ column: 1
+ delegate: SpinBox {
+ value: model.display
+ onValueModified: model.display = value
+ }
+ }
+ DelegateChoice {
+ delegate: TextField {
+ text: model.display
+ selectByMouse: true
+ implicitWidth: 140
+ onAccepted: model.display = text
+ }
+ }
+ }
+//![delegate]
+ }
+}
+//![file]
diff --git a/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml b/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml
new file mode 100644
index 0000000000..f51c1818c3
--- /dev/null
+++ b/src/qmlmodels/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//![file]
+import QtQuick 2.12
+import QtQuick.Window 2.12
+import Qt.labs.qmlmodels 1.0
+
+Window {
+ width: 400
+ height: 400
+ visible: true
+
+ TableView {
+ anchors.fill: parent
+ columnSpacing: 1
+ rowSpacing: 1
+ boundsBehavior: Flickable.StopAtBounds
+
+ model: TableModel {
+ TableModelColumn { display: "checked" }
+ TableModelColumn { display: "amount" }
+ TableModelColumn { display: "fruitType" }
+ TableModelColumn { display: "fruitName" }
+ TableModelColumn { display: "fruitPrice" }
+
+ // Each row is one type of fruit that can be ordered
+//![rows]
+ rows: [
+ {
+ // Each property is one cell/column.
+ checked: false,
+ amount: 1,
+ fruitType: "Apple",
+ fruitName: "Granny Smith",
+ fruitPrice: 1.50
+ },
+ {
+ checked: true,
+ amount: 4,
+ fruitType: "Orange",
+ fruitName: "Navel",
+ fruitPrice: 2.50
+ },
+ {
+ checked: false,
+ amount: 1,
+ fruitType: "Banana",
+ fruitName: "Cavendish",
+ fruitPrice: 3.50
+ }
+ ]
+//![rows]
+ }
+//![delegate]
+ delegate: TextInput {
+ text: model.display
+ padding: 12
+ selectByMouse: true
+
+ onAccepted: model.display = text
+
+ Rectangle {
+ anchors.fill: parent
+ color: "#efefef"
+ z: -1
+ }
+ }
+//![delegate]
+ }
+}
+//![file]
diff --git a/src/qmlmodels/qmlmodels.pro b/src/qmlmodels/qmlmodels.pro
new file mode 100644
index 0000000000..1d733f5bdb
--- /dev/null
+++ b/src/qmlmodels/qmlmodels.pro
@@ -0,0 +1,71 @@
+TARGET = QtQmlModels
+QT = core-private qml-private
+
+QMAKE_DOCS = $$PWD/doc/qtqmlmodels.qdocconf
+
+DEFINES += QT_NO_URL_CAST_FROM_STRING QT_NO_INTEGER_EVENT_COORDINATES QT_NO_FOREACH
+
+HEADERS += \
+ $$PWD/qqmlchangeset_p.h \
+ $$PWD/qqmlmodelsmodule_p.h \
+ $$PWD/qtqmlmodelsglobal_p.h \
+ $$PWD/qtqmlmodelsglobal.h
+
+SOURCES += \
+ $$PWD/qqmlchangeset.cpp \
+ $$PWD/qqmlmodelsmodule.cpp
+
+qtConfig(qml-object-model) {
+ SOURCES += \
+ $$PWD/qqmlinstantiator.cpp \
+ $$PWD/qqmlobjectmodel.cpp
+
+ HEADERS += \
+ $$PWD/qqmlinstantiator_p.h \
+ $$PWD/qqmlinstantiator_p_p.h \
+ $$PWD/qqmlobjectmodel_p.h
+}
+
+qtConfig(qml-table-model) {
+ SOURCES += \
+ $$PWD/qqmltableinstancemodel.cpp \
+ $$PWD/qqmltablemodel.cpp \
+ $$PWD/qqmltablemodelcolumn.cpp
+
+ HEADERS += \
+ $$PWD/qqmltableinstancemodel_p.h \
+ $$PWD/qqmltablemodel_p.h \
+ $$PWD/qqmltablemodelcolumn_p.h
+}
+
+qtConfig(qml-list-model) {
+ SOURCES += \
+ $$PWD/qqmllistmodel.cpp \
+ $$PWD/qqmllistmodelworkeragent.cpp
+
+ HEADERS += \
+ $$PWD/qqmllistmodel_p.h \
+ $$PWD/qqmllistmodel_p_p.h \
+ $$PWD/qqmllistmodelworkeragent_p.h
+}
+
+qtConfig(qml-delegate-model) {
+ SOURCES += \
+ $$PWD/qqmladaptormodel.cpp \
+ $$PWD/qqmldelegatemodel.cpp \
+ $$PWD/qqmldelegatecomponent.cpp \
+ $$PWD/qqmllistaccessor.cpp \
+ $$PWD/qqmllistcompositor.cpp \
+ $$PWD/qquickpackage.cpp
+
+ HEADERS += \
+ $$PWD/qqmladaptormodel_p.h \
+ $$PWD/qqmldelegatemodel_p.h \
+ $$PWD/qqmldelegatemodel_p_p.h \
+ $$PWD/qqmldelegatecomponent_p.h \
+ $$PWD/qqmllistaccessor_p.h \
+ $$PWD/qqmllistcompositor_p.h \
+ $$PWD/qquickpackage_p.h
+}
+
+load(qt_module)
diff --git a/src/qmlmodels/qqmladaptormodel.cpp b/src/qmlmodels/qqmladaptormodel.cpp
new file mode 100644
index 0000000000..48ff4e7d5b
--- /dev/null
+++ b/src/qmlmodels/qqmladaptormodel.cpp
@@ -0,0 +1,1043 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmladaptormodel_p.h"
+
+#include <private/qqmldelegatemodel_p_p.h>
+#include <private/qmetaobjectbuilder_p.h>
+#include <private/qqmlproperty_p.h>
+
+#include <private/qv4value_p.h>
+#include <private/qv4functionobject_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlAdaptorModelEngineData : public QV4::ExecutionEngine::Deletable
+{
+public:
+ QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4);
+ ~QQmlAdaptorModelEngineData();
+
+ QV4::ExecutionEngine *v4;
+ QV4::PersistentValue listItemProto;
+};
+
+V4_DEFINE_EXTENSION(QQmlAdaptorModelEngineData, engineData)
+
+static QV4::ReturnedValue get_index(const QV4::FunctionObject *f, const QV4::Value *thisObject, const QV4::Value *, int)
+{
+ QV4::Scope scope(f);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")));
+
+ RETURN_RESULT(QV4::Encode(o->d()->item->index));
+}
+
+template <typename T, typename M> static void setModelDataType(QMetaObjectBuilder *builder, M *metaType)
+{
+ builder->setFlags(QMetaObjectBuilder::DynamicMetaObject);
+ builder->setClassName(T::staticMetaObject.className());
+ builder->setSuperClass(&T::staticMetaObject);
+ metaType->propertyOffset = T::staticMetaObject.propertyCount();
+ metaType->signalOffset = T::staticMetaObject.methodCount();
+}
+
+static void addProperty(QMetaObjectBuilder *builder, int propertyId, const QByteArray &propertyName, const QByteArray &propertyType)
+{
+ builder->addSignal("__" + QByteArray::number(propertyId) + "()");
+ QMetaPropertyBuilder property = builder->addProperty(
+ propertyName, propertyType, propertyId);
+ property.setWritable(true);
+}
+
+class VDMModelDelegateDataType;
+
+class QQmlDMCachedModelData : public QQmlDelegateModelItem
+{
+public:
+ QQmlDMCachedModelData(
+ QQmlDelegateModelItemMetaType *metaType,
+ VDMModelDelegateDataType *dataType,
+ int index, int row, int column);
+
+ int metaCall(QMetaObject::Call call, int id, void **arguments);
+
+ virtual QVariant value(int role) const = 0;
+ virtual void setValue(int role, const QVariant &value) = 0;
+
+ void setValue(const QString &role, const QVariant &value) override;
+ bool resolveIndex(const QQmlAdaptorModel &model, int idx) override;
+
+ static QV4::ReturnedValue get_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+ static QV4::ReturnedValue set_property(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+
+ VDMModelDelegateDataType *type;
+ QVector<QVariant> cachedData;
+};
+
+class VDMModelDelegateDataType
+ : public QQmlRefCount
+ , public QQmlAdaptorModel::Accessors
+ , public QAbstractDynamicMetaObject
+{
+public:
+ VDMModelDelegateDataType(QQmlAdaptorModel *model)
+ : model(model)
+ , propertyOffset(0)
+ , signalOffset(0)
+ , hasModelData(false)
+ {
+ }
+
+ bool notify(
+ const QQmlAdaptorModel &,
+ const QList<QQmlDelegateModelItem *> &items,
+ int index,
+ int count,
+ const QVector<int> &roles) const override
+ {
+ bool changed = roles.isEmpty() && !watchedRoles.isEmpty();
+ if (!changed && !watchedRoles.isEmpty() && watchedRoleIds.isEmpty()) {
+ QList<int> roleIds;
+ for (const QByteArray &r : watchedRoles) {
+ QHash<QByteArray, int>::const_iterator it = roleNames.find(r);
+ if (it != roleNames.end())
+ roleIds << it.value();
+ }
+ const_cast<VDMModelDelegateDataType *>(this)->watchedRoleIds = roleIds;
+ }
+
+ QVector<int> signalIndexes;
+ for (int i = 0; i < roles.count(); ++i) {
+ const int role = roles.at(i);
+ if (!changed && watchedRoleIds.contains(role))
+ changed = true;
+
+ int propertyId = propertyRoles.indexOf(role);
+ if (propertyId != -1)
+ signalIndexes.append(propertyId + signalOffset);
+ }
+ if (roles.isEmpty()) {
+ const int propertyRolesCount = propertyRoles.count();
+ signalIndexes.reserve(propertyRolesCount);
+ for (int propertyId = 0; propertyId < propertyRolesCount; ++propertyId)
+ signalIndexes.append(propertyId + signalOffset);
+ }
+
+ QVarLengthArray<QQmlGuard<QQmlDelegateModelItem>> guardedItems;
+ for (const auto item : items)
+ guardedItems.append(item);
+
+ for (const auto &item : qAsConst(guardedItems)) {
+ if (item.isNull())
+ continue;
+
+ const int idx = item->modelIndex();
+ if (idx >= index && idx < index + count) {
+ for (int i = 0; i < signalIndexes.count(); ++i)
+ QMetaObject::activate(item, signalIndexes.at(i), nullptr);
+ }
+ }
+ return changed;
+ }
+
+ void replaceWatchedRoles(
+ QQmlAdaptorModel &,
+ const QList<QByteArray> &oldRoles,
+ const QList<QByteArray> &newRoles) const override
+ {
+ VDMModelDelegateDataType *dataType = const_cast<VDMModelDelegateDataType *>(this);
+
+ dataType->watchedRoleIds.clear();
+ for (const QByteArray &oldRole : oldRoles)
+ dataType->watchedRoles.removeOne(oldRole);
+ dataType->watchedRoles += newRoles;
+ }
+
+ static QV4::ReturnedValue get_hasModelChildren(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
+ {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ RETURN_RESULT(scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object")));
+
+ const QQmlAdaptorModel *const model = static_cast<QQmlDMCachedModelData *>(o->d()->item)->type->model;
+ if (o->d()->item->index >= 0 && *model) {
+ const QAbstractItemModel * const aim = model->aim();
+ RETURN_RESULT(QV4::Encode(aim->hasChildren(aim->index(o->d()->item->index, 0, model->rootIndex))));
+ } else {
+ RETURN_RESULT(QV4::Encode(false));
+ }
+ }
+
+
+ void initializeConstructor(QQmlAdaptorModelEngineData *const data)
+ {
+ QV4::ExecutionEngine *v4 = data->v4;
+ QV4::Scope scope(v4);
+ QV4::ScopedObject proto(scope, v4->newObject());
+ proto->defineAccessorProperty(QStringLiteral("index"), get_index, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("hasModelChildren"), get_hasModelChildren, nullptr);
+ QV4::ScopedProperty p(scope);
+
+ typedef QHash<QByteArray, int>::const_iterator iterator;
+ for (iterator it = roleNames.constBegin(), end = roleNames.constEnd(); it != end; ++it) {
+ const int propertyId = propertyRoles.indexOf(it.value());
+ const QByteArray &propertyName = it.key();
+
+ QV4::ScopedString name(scope, v4->newString(QString::fromUtf8(propertyName)));
+ QV4::ExecutionContext *global = v4->rootContext();
+ QV4::ScopedFunctionObject g(scope, v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>(global, propertyId, QQmlDMCachedModelData::get_property));
+ QV4::ScopedFunctionObject s(scope, v4->memoryManager->allocate<QV4::IndexedBuiltinFunction>(global, propertyId, QQmlDMCachedModelData::set_property));
+ p->setGetter(g);
+ p->setSetter(s);
+ proto->insertMember(name, p, QV4::Attr_Accessor|QV4::Attr_NotEnumerable|QV4::Attr_NotConfigurable);
+ }
+ prototype.set(v4, proto);
+ }
+
+ // QAbstractDynamicMetaObject
+
+ void objectDestroyed(QObject *) override
+ {
+ release();
+ }
+
+ int metaCall(QObject *object, QMetaObject::Call call, int id, void **arguments) override
+ {
+ return static_cast<QQmlDMCachedModelData *>(object)->metaCall(call, id, arguments);
+ }
+
+ QV4::PersistentValue prototype;
+ QList<int> propertyRoles;
+ QList<int> watchedRoleIds;
+ QList<QByteArray> watchedRoles;
+ QHash<QByteArray, int> roleNames;
+ QQmlAdaptorModel *model;
+ int propertyOffset;
+ int signalOffset;
+ bool hasModelData;
+};
+
+QQmlDMCachedModelData::QQmlDMCachedModelData(QQmlDelegateModelItemMetaType *metaType, VDMModelDelegateDataType *dataType, int index, int row, int column)
+ : QQmlDelegateModelItem(metaType, dataType, index, row, column)
+ , type(dataType)
+{
+ if (index == -1)
+ cachedData.resize(type->hasModelData ? 1 : type->propertyRoles.count());
+
+ QObjectPrivate::get(this)->metaObject = type;
+
+ type->addref();
+}
+
+int QQmlDMCachedModelData::metaCall(QMetaObject::Call call, int id, void **arguments)
+{
+ if (call == QMetaObject::ReadProperty && id >= type->propertyOffset) {
+ const int propertyIndex = id - type->propertyOffset;
+ if (index == -1) {
+ if (!cachedData.isEmpty()) {
+ *static_cast<QVariant *>(arguments[0]) = cachedData.at(
+ type->hasModelData ? 0 : propertyIndex);
+ }
+ } else if (*type->model) {
+ *static_cast<QVariant *>(arguments[0]) = value(type->propertyRoles.at(propertyIndex));
+ }
+ return -1;
+ } else if (call == QMetaObject::WriteProperty && id >= type->propertyOffset) {
+ const int propertyIndex = id - type->propertyOffset;
+ if (index == -1) {
+ const QMetaObject *meta = metaObject();
+ if (cachedData.count() > 1) {
+ cachedData[propertyIndex] = *static_cast<QVariant *>(arguments[0]);
+ QMetaObject::activate(this, meta, propertyIndex, nullptr);
+ } else if (cachedData.count() == 1) {
+ cachedData[0] = *static_cast<QVariant *>(arguments[0]);
+ QMetaObject::activate(this, meta, 0, nullptr);
+ QMetaObject::activate(this, meta, 1, nullptr);
+ }
+ } else if (*type->model) {
+ setValue(type->propertyRoles.at(propertyIndex), *static_cast<QVariant *>(arguments[0]));
+ }
+ return -1;
+ } else {
+ return qt_metacall(call, id, arguments);
+ }
+}
+
+void QQmlDMCachedModelData::setValue(const QString &role, const QVariant &value)
+{
+ QHash<QByteArray, int>::iterator it = type->roleNames.find(role.toUtf8());
+ if (it != type->roleNames.end()) {
+ for (int i = 0; i < type->propertyRoles.count(); ++i) {
+ if (type->propertyRoles.at(i) == *it) {
+ cachedData[i] = value;
+ return;
+ }
+ }
+ }
+}
+
+bool QQmlDMCachedModelData::resolveIndex(const QQmlAdaptorModel &adaptorModel, int idx)
+{
+ if (index == -1) {
+ Q_ASSERT(idx >= 0);
+ cachedData.clear();
+ setModelIndex(idx, adaptorModel.rowAt(idx), adaptorModel.columnAt(idx));
+ const QMetaObject *meta = metaObject();
+ const int propertyCount = type->propertyRoles.count();
+ for (int i = 0; i < propertyCount; ++i)
+ QMetaObject::activate(this, meta, i, nullptr);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+QV4::ReturnedValue QQmlDMCachedModelData::get_property(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
+{
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+
+ uint propertyId = static_cast<const QV4::IndexedBuiltinFunction *>(b)->d()->index;
+
+ QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(o->d()->item);
+ if (o->d()->item->index == -1) {
+ if (!modelData->cachedData.isEmpty()) {
+ return scope.engine->fromVariant(
+ modelData->cachedData.at(modelData->type->hasModelData ? 0 : propertyId));
+ }
+ } else if (*modelData->type->model) {
+ return scope.engine->fromVariant(
+ modelData->value(modelData->type->propertyRoles.at(propertyId)));
+ }
+ return QV4::Encode::undefined();
+}
+
+QV4::ReturnedValue QQmlDMCachedModelData::set_property(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
+{
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+ if (!argc)
+ return scope.engine->throwTypeError();
+
+ uint propertyId = static_cast<const QV4::IndexedBuiltinFunction *>(b)->d()->index;
+
+ if (o->d()->item->index == -1) {
+ QQmlDMCachedModelData *modelData = static_cast<QQmlDMCachedModelData *>(o->d()->item);
+ if (!modelData->cachedData.isEmpty()) {
+ if (modelData->cachedData.count() > 1) {
+ modelData->cachedData[propertyId] = scope.engine->toVariant(argv[0], QVariant::Invalid);
+ QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), propertyId, nullptr);
+ } else if (modelData->cachedData.count() == 1) {
+ modelData->cachedData[0] = scope.engine->toVariant(argv[0], QVariant::Invalid);
+ QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 0, nullptr);
+ QMetaObject::activate(o->d()->item, o->d()->item->metaObject(), 1, nullptr);
+ }
+ }
+ }
+ return QV4::Encode::undefined();
+}
+
+//-----------------------------------------------------------------
+// QAbstractItemModel
+//-----------------------------------------------------------------
+
+class QQmlDMAbstractItemModelData : public QQmlDMCachedModelData
+{
+ Q_OBJECT
+ Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT)
+
+public:
+ QQmlDMAbstractItemModelData(
+ QQmlDelegateModelItemMetaType *metaType,
+ VDMModelDelegateDataType *dataType,
+ int index, int row, int column)
+ : QQmlDMCachedModelData(metaType, dataType, index, row, column)
+ {
+ }
+
+ bool hasModelChildren() const
+ {
+ if (index >= 0 && *type->model) {
+ const QAbstractItemModel * const model = type->model->aim();
+ return model->hasChildren(model->index(row, column, type->model->rootIndex));
+ } else {
+ return false;
+ }
+ }
+
+ QVariant value(int role) const override
+ {
+ return type->model->aim()->index(row, column, type->model->rootIndex).data(role);
+ }
+
+ void setValue(int role, const QVariant &value) override
+ {
+ type->model->aim()->setData(
+ type->model->aim()->index(row, column, type->model->rootIndex), value, role);
+ }
+
+ QV4::ReturnedValue get() override
+ {
+ if (type->prototype.isUndefined()) {
+ QQmlAdaptorModelEngineData * const data = engineData(v4);
+ type->initializeConstructor(data);
+ }
+ QV4::Scope scope(v4);
+ QV4::ScopedObject proto(scope, type->prototype.value());
+ QV4::ScopedObject o(scope, proto->engine()->memoryManager->allocate<QQmlDelegateModelItemObject>(this));
+ o->setPrototypeOf(proto);
+ ++scriptRef;
+ return o.asReturnedValue();
+ }
+};
+
+class VDMAbstractItemModelDataType : public VDMModelDelegateDataType
+{
+public:
+ VDMAbstractItemModelDataType(QQmlAdaptorModel *model)
+ : VDMModelDelegateDataType(model)
+ {
+ }
+
+ int rowCount(const QQmlAdaptorModel &model) const override
+ {
+ return model.aim()->rowCount(model.rootIndex);
+ }
+
+ int columnCount(const QQmlAdaptorModel &model) const override
+ {
+ return model.aim()->columnCount(model.rootIndex);
+ }
+
+ void cleanup(QQmlAdaptorModel &) const override
+ {
+ const_cast<VDMAbstractItemModelDataType *>(this)->release();
+ }
+
+ QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
+ {
+ QHash<QByteArray, int>::const_iterator it = roleNames.find(role.toUtf8());
+ if (it != roleNames.end()) {
+ return model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex).data(*it);
+ } else if (role == QLatin1String("hasModelChildren")) {
+ return QVariant(model.aim()->hasChildren(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex)));
+ } else {
+ return QVariant();
+ }
+ }
+
+ QVariant parentModelIndex(const QQmlAdaptorModel &model) const override
+ {
+ return model
+ ? QVariant::fromValue(model.aim()->parent(model.rootIndex))
+ : QVariant();
+ }
+
+ QVariant modelIndex(const QQmlAdaptorModel &model, int index) const override
+ {
+ return model
+ ? QVariant::fromValue(model.aim()->index(model.rowAt(index), model.columnAt(index), model.rootIndex))
+ : QVariant();
+ }
+
+ bool canFetchMore(const QQmlAdaptorModel &model) const override
+ {
+ return model && model.aim()->canFetchMore(model.rootIndex);
+ }
+
+ void fetchMore(QQmlAdaptorModel &model) const override
+ {
+ if (model)
+ model.aim()->fetchMore(model.rootIndex);
+ }
+
+ QQmlDelegateModelItem *createItem(
+ QQmlAdaptorModel &model,
+ QQmlDelegateModelItemMetaType *metaType,
+ int index, int row, int column) const override
+ {
+ VDMAbstractItemModelDataType *dataType = const_cast<VDMAbstractItemModelDataType *>(this);
+ if (!metaObject)
+ dataType->initializeMetaType(model);
+ return new QQmlDMAbstractItemModelData(metaType, dataType, index, row, column);
+ }
+
+ void initializeMetaType(QQmlAdaptorModel &model)
+ {
+ QMetaObjectBuilder builder;
+ setModelDataType<QQmlDMAbstractItemModelData>(&builder, this);
+
+ const QByteArray propertyType = QByteArrayLiteral("QVariant");
+ const QHash<int, QByteArray> names = model.aim()->roleNames();
+ for (QHash<int, QByteArray>::const_iterator it = names.begin(), cend = names.end(); it != cend; ++it) {
+ const int propertyId = propertyRoles.count();
+ propertyRoles.append(it.key());
+ roleNames.insert(it.value(), it.key());
+ addProperty(&builder, propertyId, it.value(), propertyType);
+ }
+ if (propertyRoles.count() == 1) {
+ hasModelData = true;
+ const int role = names.begin().key();
+ const QByteArray propertyName = QByteArrayLiteral("modelData");
+
+ propertyRoles.append(role);
+ roleNames.insert(propertyName, role);
+ addProperty(&builder, 1, propertyName, propertyType);
+ }
+
+ metaObject.reset(builder.toMetaObject());
+ *static_cast<QMetaObject *>(this) = *metaObject;
+ propertyCache.adopt(new QQmlPropertyCache(metaObject.data(), model.modelItemRevision));
+ }
+};
+
+//-----------------------------------------------------------------
+// QQmlListAccessor
+//-----------------------------------------------------------------
+
+class QQmlDMListAccessorData : public QQmlDelegateModelItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
+public:
+ QQmlDMListAccessorData(QQmlDelegateModelItemMetaType *metaType,
+ QQmlAdaptorModel::Accessors *accessor,
+ int index, int row, int column, const QVariant &value)
+ : QQmlDelegateModelItem(metaType, accessor, index, row, column)
+ , cachedData(value)
+ {
+ }
+
+ QVariant modelData() const
+ {
+ return cachedData;
+ }
+
+ void setModelData(const QVariant &data)
+ {
+ if (data == cachedData)
+ return;
+
+ cachedData = data;
+ emit modelDataChanged();
+ }
+
+ static QV4::ReturnedValue get_modelData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
+ {
+ QV4::ExecutionEngine *v4 = b->engine();
+ const QQmlDelegateModelItemObject *o = thisObject->as<QQmlDelegateModelItemObject>();
+ if (!o)
+ return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+
+ return v4->fromVariant(static_cast<QQmlDMListAccessorData *>(o->d()->item)->cachedData);
+ }
+
+ static QV4::ReturnedValue set_modelData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
+ {
+ QV4::ExecutionEngine *v4 = b->engine();
+ const QQmlDelegateModelItemObject *o = thisObject->as<QQmlDelegateModelItemObject>();
+ if (!o)
+ return v4->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+ if (!argc)
+ return v4->throwTypeError();
+
+ static_cast<QQmlDMListAccessorData *>(o->d()->item)->setModelData(v4->toVariant(argv[0], QVariant::Invalid));
+ return QV4::Encode::undefined();
+ }
+
+ QV4::ReturnedValue get() override
+ {
+ QQmlAdaptorModelEngineData *data = engineData(v4);
+ QV4::Scope scope(v4);
+ QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(this));
+ QV4::ScopedObject p(scope, data->listItemProto.value());
+ o->setPrototypeOf(p);
+ ++scriptRef;
+ return o.asReturnedValue();
+ }
+
+ void setValue(const QString &role, const QVariant &value) override
+ {
+ if (role == QLatin1String("modelData"))
+ cachedData = value;
+ }
+
+ bool resolveIndex(const QQmlAdaptorModel &model, int idx) override
+ {
+ if (index == -1) {
+ index = idx;
+ cachedData = model.list.at(idx);
+ emit modelIndexChanged();
+ emit modelDataChanged();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+Q_SIGNALS:
+ void modelDataChanged();
+
+private:
+ QVariant cachedData;
+};
+
+
+class VDMListDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors
+{
+public:
+ VDMListDelegateDataType()
+ : QQmlRefCount()
+ , QQmlAdaptorModel::Accessors()
+ {}
+
+ void cleanup(QQmlAdaptorModel &) const override
+ {
+ const_cast<VDMListDelegateDataType *>(this)->release();
+ }
+
+ int rowCount(const QQmlAdaptorModel &model) const override
+ {
+ return model.list.count();
+ }
+
+ int columnCount(const QQmlAdaptorModel &) const override
+ {
+ return 1;
+ }
+
+ QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
+ {
+ return role == QLatin1String("modelData")
+ ? model.list.at(index)
+ : QVariant();
+ }
+
+ QQmlDelegateModelItem *createItem(
+ QQmlAdaptorModel &model,
+ QQmlDelegateModelItemMetaType *metaType,
+ int index, int row, int column) const override
+ {
+ VDMListDelegateDataType *dataType = const_cast<VDMListDelegateDataType *>(this);
+ if (!propertyCache) {
+ dataType->propertyCache.adopt(new QQmlPropertyCache(
+ &QQmlDMListAccessorData::staticMetaObject, model.modelItemRevision));
+ }
+
+ return new QQmlDMListAccessorData(
+ metaType,
+ dataType,
+ index, row, column,
+ index >= 0 && index < model.list.count() ? model.list.at(index) : QVariant());
+ }
+
+ bool notify(const QQmlAdaptorModel &model, const QList<QQmlDelegateModelItem *> &items, int index, int count, const QVector<int> &) const override
+ {
+ for (auto modelItem : items) {
+ const int modelItemIndex = modelItem->index;
+ if (modelItemIndex < index || modelItemIndex >= index + count)
+ continue;
+
+ auto listModelItem = static_cast<QQmlDMListAccessorData *>(modelItem);
+ QVariant updatedModelData = model.list.at(listModelItem->index);
+ listModelItem->setModelData(updatedModelData);
+ }
+ return true;
+ }
+};
+
+//-----------------------------------------------------------------
+// QObject
+//-----------------------------------------------------------------
+
+class VDMObjectDelegateDataType;
+class QQmlDMObjectData : public QQmlDelegateModelItem, public QQmlAdaptorModelProxyInterface
+{
+ Q_OBJECT
+ Q_PROPERTY(QObject *modelData READ modelData NOTIFY modelDataChanged)
+ Q_INTERFACES(QQmlAdaptorModelProxyInterface)
+public:
+ QQmlDMObjectData(
+ QQmlDelegateModelItemMetaType *metaType,
+ VDMObjectDelegateDataType *dataType,
+ int index, int row, int column,
+ QObject *object);
+
+ void setModelData(QObject *modelData)
+ {
+ if (modelData == object)
+ return;
+
+ object = modelData;
+ emit modelDataChanged();
+ }
+
+ QObject *modelData() const { return object; }
+ QObject *proxiedObject() override { return object; }
+
+ QPointer<QObject> object;
+
+Q_SIGNALS:
+ void modelDataChanged();
+};
+
+class VDMObjectDelegateDataType : public QQmlRefCount, public QQmlAdaptorModel::Accessors
+{
+public:
+ int propertyOffset;
+ int signalOffset;
+ bool shared;
+ QMetaObjectBuilder builder;
+
+ VDMObjectDelegateDataType()
+ : propertyOffset(0)
+ , signalOffset(0)
+ , shared(true)
+ {
+ }
+
+ VDMObjectDelegateDataType(const VDMObjectDelegateDataType &type)
+ : QQmlRefCount()
+ , QQmlAdaptorModel::Accessors()
+ , propertyOffset(type.propertyOffset)
+ , signalOffset(type.signalOffset)
+ , shared(false)
+ , builder(type.metaObject.data(), QMetaObjectBuilder::Properties
+ | QMetaObjectBuilder::Signals
+ | QMetaObjectBuilder::SuperClass
+ | QMetaObjectBuilder::ClassName)
+ {
+ builder.setFlags(QMetaObjectBuilder::DynamicMetaObject);
+ }
+
+ int rowCount(const QQmlAdaptorModel &model) const override
+ {
+ return model.list.count();
+ }
+
+ int columnCount(const QQmlAdaptorModel &) const override
+ {
+ return 1;
+ }
+
+ QVariant value(const QQmlAdaptorModel &model, int index, const QString &role) const override
+ {
+ if (QObject *object = model.list.at(index).value<QObject *>())
+ return object->property(role.toUtf8());
+ return QVariant();
+ }
+
+ QQmlDelegateModelItem *createItem(
+ QQmlAdaptorModel &model,
+ QQmlDelegateModelItemMetaType *metaType,
+ int index, int row, int column) const override
+ {
+ VDMObjectDelegateDataType *dataType = const_cast<VDMObjectDelegateDataType *>(this);
+ if (!metaObject)
+ dataType->initializeMetaType(model);
+ return index >= 0 && index < model.list.count()
+ ? new QQmlDMObjectData(metaType, dataType, index, row, column, qvariant_cast<QObject *>(model.list.at(index)))
+ : nullptr;
+ }
+
+ void initializeMetaType(QQmlAdaptorModel &model)
+ {
+ Q_UNUSED(model);
+ setModelDataType<QQmlDMObjectData>(&builder, this);
+
+ metaObject.reset(builder.toMetaObject());
+ // Note: ATM we cannot create a shared property cache for this class, since each model
+ // object can have different properties. And to make those properties available to the
+ // delegate, QQmlDMObjectData makes use of a QAbstractDynamicMetaObject subclass
+ // (QQmlDMObjectDataMetaObject), which we cannot represent in a QQmlPropertyCache.
+ // By not having a shared property cache, revisioned properties in QQmlDelegateModelItem
+ // will always be available to the delegate, regardless of the import version.
+ }
+
+ void cleanup(QQmlAdaptorModel &) const override
+ {
+ const_cast<VDMObjectDelegateDataType *>(this)->release();
+ }
+
+ bool notify(const QQmlAdaptorModel &model, const QList<QQmlDelegateModelItem *> &items, int index, int count, const QVector<int> &) const override
+ {
+ for (auto modelItem : items) {
+ const int modelItemIndex = modelItem->index;
+ if (modelItemIndex < index || modelItemIndex >= index + count)
+ continue;
+
+ auto objectModelItem = static_cast<QQmlDMObjectData *>(modelItem);
+ QObject *updatedModelData = qvariant_cast<QObject *>(model.list.at(objectModelItem->index));
+ objectModelItem->setModelData(updatedModelData);
+ }
+ return true;
+ }
+};
+
+class QQmlDMObjectDataMetaObject : public QAbstractDynamicMetaObject
+{
+public:
+ QQmlDMObjectDataMetaObject(QQmlDMObjectData *data, VDMObjectDelegateDataType *type)
+ : m_data(data)
+ , m_type(type)
+ {
+ QObjectPrivate *op = QObjectPrivate::get(m_data);
+ *static_cast<QMetaObject *>(this) = *type->metaObject;
+ op->metaObject = this;
+ m_type->addref();
+ }
+
+ ~QQmlDMObjectDataMetaObject()
+ {
+ m_type->release();
+ }
+
+ int metaCall(QObject *o, QMetaObject::Call call, int id, void **arguments) override
+ {
+ Q_ASSERT(o == m_data);
+ Q_UNUSED(o);
+
+ static const int objectPropertyOffset = QObject::staticMetaObject.propertyCount();
+ if (id >= m_type->propertyOffset
+ && (call == QMetaObject::ReadProperty
+ || call == QMetaObject::WriteProperty
+ || call == QMetaObject::ResetProperty)) {
+ if (m_data->object)
+ QMetaObject::metacall(m_data->object, call, id - m_type->propertyOffset + objectPropertyOffset, arguments);
+ return -1;
+ } else if (id >= m_type->signalOffset && call == QMetaObject::InvokeMetaMethod) {
+ QMetaObject::activate(m_data, this, id - m_type->signalOffset, nullptr);
+ return -1;
+ } else {
+ return m_data->qt_metacall(call, id, arguments);
+ }
+ }
+
+ int createProperty(const char *name, const char *) override
+ {
+ if (!m_data->object)
+ return -1;
+ const QMetaObject *metaObject = m_data->object->metaObject();
+ static const int objectPropertyOffset = QObject::staticMetaObject.propertyCount();
+
+ const int previousPropertyCount = propertyCount() - propertyOffset();
+ int propertyIndex = metaObject->indexOfProperty(name);
+ if (propertyIndex == -1)
+ return -1;
+ if (previousPropertyCount + objectPropertyOffset == metaObject->propertyCount())
+ return propertyIndex + m_type->propertyOffset - objectPropertyOffset;
+
+ if (m_type->shared) {
+ VDMObjectDelegateDataType *type = m_type;
+ m_type = new VDMObjectDelegateDataType(*m_type);
+ type->release();
+ }
+
+ const int previousMethodCount = methodCount();
+ int notifierId = previousMethodCount - methodOffset();
+ for (int propertyId = previousPropertyCount; propertyId < metaObject->propertyCount() - objectPropertyOffset; ++propertyId) {
+ QMetaProperty property = metaObject->property(propertyId + objectPropertyOffset);
+ QMetaPropertyBuilder propertyBuilder;
+ if (property.hasNotifySignal()) {
+ m_type->builder.addSignal("__" + QByteArray::number(propertyId) + "()");
+ propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName(), notifierId);
+ ++notifierId;
+ } else {
+ propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName());
+ }
+ propertyBuilder.setWritable(property.isWritable());
+ propertyBuilder.setResettable(property.isResettable());
+ propertyBuilder.setConstant(property.isConstant());
+ }
+
+ m_type->metaObject.reset(m_type->builder.toMetaObject());
+ *static_cast<QMetaObject *>(this) = *m_type->metaObject;
+
+ notifierId = previousMethodCount;
+ for (int i = previousPropertyCount; i < metaObject->propertyCount() - objectPropertyOffset; ++i) {
+ QMetaProperty property = metaObject->property(i + objectPropertyOffset);
+ if (property.hasNotifySignal()) {
+ QQmlPropertyPrivate::connect(
+ m_data->object, property.notifySignalIndex(), m_data, notifierId);
+ ++notifierId;
+ }
+ }
+ return propertyIndex + m_type->propertyOffset - objectPropertyOffset;
+ }
+
+ QQmlDMObjectData *m_data;
+ VDMObjectDelegateDataType *m_type;
+};
+
+QQmlDMObjectData::QQmlDMObjectData(QQmlDelegateModelItemMetaType *metaType,
+ VDMObjectDelegateDataType *dataType,
+ int index, int row, int column,
+ QObject *object)
+ : QQmlDelegateModelItem(metaType, dataType, index, row, column)
+ , object(object)
+{
+ new QQmlDMObjectDataMetaObject(this, dataType);
+}
+
+//-----------------------------------------------------------------
+// QQmlAdaptorModel
+//-----------------------------------------------------------------
+
+static const QQmlAdaptorModel::Accessors qt_vdm_null_accessors;
+
+QQmlAdaptorModel::Accessors::~Accessors()
+{
+}
+
+QQmlAdaptorModel::QQmlAdaptorModel()
+ : accessors(&qt_vdm_null_accessors)
+{
+}
+
+QQmlAdaptorModel::~QQmlAdaptorModel()
+{
+ accessors->cleanup(*this);
+}
+
+void QQmlAdaptorModel::setModel(const QVariant &variant, QObject *parent, QQmlEngine *engine)
+{
+ accessors->cleanup(*this);
+
+ list.setList(variant, engine);
+
+ if (QObject *object = qvariant_cast<QObject *>(list.list())) {
+ setObject(object, parent);
+ if (qobject_cast<QAbstractItemModel *>(object))
+ accessors = new VDMAbstractItemModelDataType(this);
+ else
+ accessors = new VDMObjectDelegateDataType;
+ } else if (list.type() == QQmlListAccessor::ListProperty) {
+ setObject(static_cast<const QQmlListReference *>(variant.constData())->object(), parent);
+ accessors = new VDMObjectDelegateDataType;
+ } else if (list.type() != QQmlListAccessor::Invalid
+ && list.type() != QQmlListAccessor::Instance) { // Null QObject
+ setObject(nullptr, parent);
+ accessors = new VDMListDelegateDataType;
+ } else {
+ setObject(nullptr, parent);
+ accessors = &qt_vdm_null_accessors;
+ }
+}
+
+void QQmlAdaptorModel::invalidateModel()
+{
+ accessors->cleanup(*this);
+ accessors = &qt_vdm_null_accessors;
+ // Don't clear the model object as we still need the guard to clear the list variant if the
+ // object is destroyed.
+}
+
+bool QQmlAdaptorModel::isValid() const
+{
+ return accessors != &qt_vdm_null_accessors;
+}
+
+int QQmlAdaptorModel::count() const
+{
+ return rowCount() * columnCount();
+}
+
+int QQmlAdaptorModel::rowCount() const
+{
+ return qMax(0, accessors->rowCount(*this));
+}
+
+int QQmlAdaptorModel::columnCount() const
+{
+ return qMax(0, accessors->columnCount(*this));
+}
+
+int QQmlAdaptorModel::rowAt(int index) const
+{
+ int count = rowCount();
+ return count <= 0 ? -1 : index % count;
+}
+
+int QQmlAdaptorModel::columnAt(int index) const
+{
+ int count = rowCount();
+ return count <= 0 ? -1 : index / count;
+}
+
+int QQmlAdaptorModel::indexAt(int row, int column) const
+{
+ return column * rowCount() + row;
+}
+
+void QQmlAdaptorModel::useImportVersion(int minorVersion)
+{
+ modelItemRevision = minorVersion;
+}
+
+void QQmlAdaptorModel::objectDestroyed(QObject *)
+{
+ setModel(QVariant(), nullptr, nullptr);
+}
+
+QQmlAdaptorModelEngineData::QQmlAdaptorModelEngineData(QV4::ExecutionEngine *v4)
+ : v4(v4)
+{
+ QV4::Scope scope(v4);
+ QV4::ScopedObject proto(scope, v4->newObject());
+ proto->defineAccessorProperty(QStringLiteral("index"), get_index, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("modelData"),
+ QQmlDMListAccessorData::get_modelData, QQmlDMListAccessorData::set_modelData);
+ listItemProto.set(v4, proto);
+}
+
+QQmlAdaptorModelEngineData::~QQmlAdaptorModelEngineData()
+{
+}
+
+QT_END_NAMESPACE
+
+#include <qqmladaptormodel.moc>
diff --git a/src/qmlmodels/qqmladaptormodel_p.h b/src/qmlmodels/qqmladaptormodel_p.h
new file mode 100644
index 0000000000..a4549127af
--- /dev/null
+++ b/src/qmlmodels/qqmladaptormodel_p.h
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLADAPTORMODEL_P_H
+#define QQMLADAPTORMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qabstractitemmodel.h>
+
+#include <private/qqmlglobal_p.h>
+#include <private/qqmllistaccessor_p.h>
+#include <private/qtqmlmodelsglobal_p.h>
+#include <private/qqmlguard_p.h>
+#include <private/qqmlnullablevalue_p.h>
+#include <private/qqmlpropertycache_p.h>
+
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
+QT_BEGIN_NAMESPACE
+
+class QQmlEngine;
+
+class QQmlDelegateModel;
+class QQmlDelegateModelItem;
+class QQmlDelegateModelItemMetaType;
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlAdaptorModel : public QQmlStrongJSQObjectReference<QObject>
+{
+public:
+ class Accessors
+ {
+ public:
+ inline Accessors() {}
+ virtual ~Accessors();
+ virtual int rowCount(const QQmlAdaptorModel &) const { return 0; }
+ virtual int columnCount(const QQmlAdaptorModel &) const { return 0; }
+ virtual void cleanup(QQmlAdaptorModel &) const {}
+
+ virtual QVariant value(const QQmlAdaptorModel &, int, const QString &) const {
+ return QVariant(); }
+
+ virtual QQmlDelegateModelItem *createItem(
+ QQmlAdaptorModel &,
+ QQmlDelegateModelItemMetaType *,
+ int, int, int) const { return nullptr; }
+
+ virtual bool notify(
+ const QQmlAdaptorModel &,
+ const QList<QQmlDelegateModelItem *> &,
+ int,
+ int,
+ const QVector<int> &) const { return false; }
+ virtual void replaceWatchedRoles(
+ QQmlAdaptorModel &,
+ const QList<QByteArray> &,
+ const QList<QByteArray> &) const {}
+ virtual QVariant parentModelIndex(const QQmlAdaptorModel &) const {
+ return QVariant(); }
+ virtual QVariant modelIndex(const QQmlAdaptorModel &, int) const {
+ return QVariant(); }
+ virtual bool canFetchMore(const QQmlAdaptorModel &) const { return false; }
+ virtual void fetchMore(QQmlAdaptorModel &) const {}
+
+ QScopedPointer<QMetaObject, QScopedPointerPodDeleter> metaObject;
+ QQmlRefPointer<QQmlPropertyCache> propertyCache;
+ };
+
+ const Accessors *accessors;
+ QPersistentModelIndex rootIndex;
+ QQmlListAccessor list;
+
+ int modelItemRevision = 0;
+
+ QQmlAdaptorModel();
+ ~QQmlAdaptorModel();
+
+ inline QVariant model() const { return list.list(); }
+ void setModel(const QVariant &variant, QObject *parent, QQmlEngine *engine);
+ void invalidateModel();
+
+ bool isValid() const;
+ int count() const;
+ int rowCount() const;
+ int columnCount() const;
+ int rowAt(int index) const;
+ int columnAt(int index) const;
+ int indexAt(int row, int column) const;
+
+ void useImportVersion(int minorVersion);
+
+ inline bool adaptsAim() const { return qobject_cast<QAbstractItemModel *>(object()); }
+ inline QAbstractItemModel *aim() { return static_cast<QAbstractItemModel *>(object()); }
+ inline const QAbstractItemModel *aim() const { return static_cast<const QAbstractItemModel *>(object()); }
+
+ inline QVariant value(int index, const QString &role) const {
+ return accessors->value(*this, index, role); }
+ inline QQmlDelegateModelItem *createItem(QQmlDelegateModelItemMetaType *metaType, int index) {
+ return accessors->createItem(*this, metaType, index, rowAt(index), columnAt(index)); }
+ inline bool hasProxyObject() const {
+ return list.type() == QQmlListAccessor::Instance || list.type() == QQmlListAccessor::ListProperty; }
+
+ inline bool notify(
+ const QList<QQmlDelegateModelItem *> &items,
+ int index,
+ int count,
+ const QVector<int> &roles) const {
+ return accessors->notify(*this, items, index, count, roles); }
+ inline void replaceWatchedRoles(
+ const QList<QByteArray> &oldRoles, const QList<QByteArray> &newRoles) {
+ accessors->replaceWatchedRoles(*this, oldRoles, newRoles); }
+
+ inline QVariant modelIndex(int index) const { return accessors->modelIndex(*this, index); }
+ inline QVariant parentModelIndex() const { return accessors->parentModelIndex(*this); }
+ inline bool canFetchMore() const { return accessors->canFetchMore(*this); }
+ inline void fetchMore() { return accessors->fetchMore(*this); }
+
+protected:
+ void objectDestroyed(QObject *) override;
+};
+
+class QQmlAdaptorModelProxyInterface
+{
+public:
+ virtual ~QQmlAdaptorModelProxyInterface() {}
+
+ virtual QObject *proxiedObject() = 0;
+};
+
+#define QQmlAdaptorModelProxyInterface_iid "org.qt-project.Qt.QQmlAdaptorModelProxyInterface"
+
+Q_DECLARE_INTERFACE(QQmlAdaptorModelProxyInterface, QQmlAdaptorModelProxyInterface_iid)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qmlmodels/qqmlchangeset.cpp b/src/qmlmodels/qqmlchangeset.cpp
new file mode 100644
index 0000000000..ba876b42e2
--- /dev/null
+++ b/src/qmlmodels/qqmlchangeset.cpp
@@ -0,0 +1,583 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmlchangeset_p.h"
+
+QT_BEGIN_NAMESPACE
+
+
+/*!
+ \class QQmlChangeSet
+ \brief The QQmlChangeSet class stores an ordered list of notifications about
+ changes to a linear data set.
+ \internal
+
+ QQmlChangeSet can be used to record a series of notifications about items in an indexed list
+ being inserted, removed, moved, and changed. Notifications in the set are re-ordered so that
+ all notifications of a single type are grouped together and sorted in order of ascending index,
+ with remove notifications preceding all others, followed by insert notification, and then
+ change notifications.
+
+ Moves in a change set are represented by a remove notification paired with an insert
+ notification by way of a shared unique moveId. Re-ordering may result in one or both of the
+ paired notifications being divided, when this happens the offset member of the notification
+ will indicate the relative offset of the divided notification from the beginning of the
+ original.
+*/
+
+/*!
+ Constructs an empty change set.
+*/
+
+QQmlChangeSet::QQmlChangeSet()
+ : m_difference(0)
+{
+}
+
+/*!
+ Constructs a copy of a \a changeSet.
+*/
+
+QQmlChangeSet::QQmlChangeSet(const QQmlChangeSet &changeSet)
+ : m_removes(changeSet.m_removes)
+ , m_inserts(changeSet.m_inserts)
+ , m_changes(changeSet.m_changes)
+ , m_difference(changeSet.m_difference)
+{
+}
+
+/*!
+ Destroys a change set.
+*/
+
+QQmlChangeSet::~QQmlChangeSet()
+{
+}
+
+/*!
+ Assigns the value of a \a changeSet to another.
+*/
+
+QQmlChangeSet &QQmlChangeSet::operator =(const QQmlChangeSet &changeSet)
+{
+ m_removes = changeSet.m_removes;
+ m_inserts = changeSet.m_inserts;
+ m_changes = changeSet.m_changes;
+ m_difference = changeSet.m_difference;
+ return *this;
+}
+
+/*!
+ Appends a notification that \a count items were inserted at \a index.
+*/
+
+void QQmlChangeSet::insert(int index, int count)
+{
+ insert(QVector<Change>() << Change(index, count));
+}
+
+/*!
+ Appends a notification that \a count items were removed at \a index.
+*/
+
+void QQmlChangeSet::remove(int index, int count)
+{
+ QVector<Change> removes;
+ removes.append(Change(index, count));
+ remove(&removes, nullptr);
+}
+
+/*!
+ Appends a notification that \a count items were moved \a from one index \a to another.
+
+ The \a moveId must be unique across the lifetime of the change set and any related
+ change sets.
+*/
+
+void QQmlChangeSet::move(int from, int to, int count, int moveId)
+{
+ QVector<Change> removes;
+ removes.append(Change(from, count, moveId));
+ QVector<Change> inserts;
+ inserts.append(Change(to, count, moveId));
+ remove(&removes, &inserts);
+ insert(inserts);
+}
+
+/*!
+ Appends a notification that \a count items were changed at \a index.
+*/
+
+void QQmlChangeSet::change(int index, int count)
+{
+ QVector<Change> changes;
+ changes.append(Change(index, count));
+ change(changes);
+}
+
+/*!
+ Applies the changes in a \a changeSet to another.
+*/
+
+void QQmlChangeSet::apply(const QQmlChangeSet &changeSet)
+{
+ QVector<Change> r = changeSet.m_removes;
+ QVector<Change> i = changeSet.m_inserts;
+ QVector<Change> c = changeSet.m_changes;
+ remove(&r, &i);
+ insert(i);
+ change(c);
+}
+
+/*!
+ Applies a list of \a removes to a change set.
+
+ If a remove contains a moveId then any intersecting insert in the set will replace the
+ corresponding intersection in the optional \a inserts list.
+*/
+
+void QQmlChangeSet::remove(const QVector<Change> &removes, QVector<Change> *inserts)
+{
+ QVector<Change> r = removes;
+ remove(&r, inserts);
+}
+
+void QQmlChangeSet::remove(QVector<Change> *removes, QVector<Change> *inserts)
+{
+ int removeCount = 0;
+ int insertCount = 0;
+ QVector<Change>::iterator insert = m_inserts.begin();
+ QVector<Change>::iterator change = m_changes.begin();
+ QVector<Change>::iterator rit = removes->begin();
+ for (; rit != removes->end(); ++rit) {
+ int index = rit->index + removeCount;
+ int count = rit->count;
+
+ // Decrement the accumulated remove count from the indexes of any changes prior to the
+ // current remove.
+ for (; change != m_changes.end() && change->end() < rit->index; ++change)
+ change->index -= removeCount;
+ // Remove any portion of a change notification that intersects the current remove.
+ for (; change != m_changes.end() && change->index > rit->end(); ++change) {
+ change->count -= qMin(change->end(), rit->end()) - qMax(change->index, rit->index);
+ if (change->count == 0) {
+ change = m_changes.erase(change);
+ } else if (rit->index < change->index) {
+ change->index = rit->index;
+ }
+ }
+
+ // Decrement the accumulated remove count from the indexes of any inserts prior to the
+ // current remove.
+ for (; insert != m_inserts.end() && insert->end() <= index; ++insert) {
+ insertCount += insert->count;
+ insert->index -= removeCount;
+ }
+
+ rit->index -= insertCount;
+
+ // Remove any portion of a insert notification that intersects the current remove.
+ while (insert != m_inserts.end() && insert->index < index + count) {
+ int offset = index - insert->index;
+ const int difference = qMin(insert->end(), index + count) - qMax(insert->index, index);
+
+ // If part of the remove or insert that precedes the intersection has a moveId create
+ // a new delta for that portion and subtract the size of that delta from the current
+ // one.
+ if (offset < 0 && rit->moveId != -1) {
+ rit = removes->insert(rit, Change(
+ rit->index, -offset, rit->moveId, rit->offset));
+ ++rit;
+ rit->count -= -offset;
+ rit->offset += -offset;
+ index += -offset;
+ count -= -offset;
+ removeCount += -offset;
+ offset = 0;
+ } else if (offset > 0 && insert->moveId != -1) {
+ insert = m_inserts.insert(insert, Change(
+ insert->index - removeCount, offset, insert->moveId, insert->offset));
+ ++insert;
+ insert->index += offset;
+ insert->count -= offset;
+ insert->offset += offset;
+ rit->index -= offset;
+ insertCount += offset;
+ }
+
+ // If the current remove has a move id, find any inserts with the same move id and
+ // replace the corresponding sections with the insert removed from the change set.
+ if (rit->moveId != -1 && difference > 0 && inserts) {
+ for (QVector<Change>::iterator iit = inserts->begin(); iit != inserts->end(); ++iit) {
+ if (iit->moveId != rit->moveId
+ || rit->offset > iit->offset + iit->count
+ || iit->offset > rit->offset + difference) {
+ continue;
+ }
+ // If the intersecting insert starts before the replacement one create
+ // a new insert for the portion prior to the replacement insert.
+ const int overlapOffset = rit->offset - iit->offset;
+ if (overlapOffset > 0) {
+ iit = inserts->insert(iit, Change(
+ iit->index, overlapOffset, iit->moveId, iit->offset));
+ ++iit;
+ iit->index += overlapOffset;
+ iit->count -= overlapOffset;
+ iit->offset += overlapOffset;
+ }
+ if (iit->offset >= rit->offset
+ && iit->offset + iit->count <= rit->offset + difference) {
+ // If the replacement insert completely encapsulates the existing
+ // one just change the moveId.
+ iit->moveId = insert->moveId;
+ iit->offset = insert->offset + qMax(0, -overlapOffset);
+ } else {
+ // Create a new insertion before the intersecting one with the number of intersecting
+ // items and remove that number from that insert.
+ const int count
+ = qMin(iit->offset + iit->count, rit->offset + difference)
+ - qMax(iit->offset, rit->offset);
+ iit = inserts->insert(iit, Change(
+ iit->index,
+ count,
+ insert->moveId,
+ insert->offset + qMax(0, -overlapOffset)));
+ ++iit;
+ iit->index += count;
+ iit->count -= count;
+ iit->offset += count;
+ }
+ }
+ }
+
+ // Subtract the number of intersecting items from the current remove and insert.
+ insert->count -= difference;
+ insert->offset += difference;
+ rit->count -= difference;
+ rit->offset += difference;
+
+ index += difference;
+ count -= difference;
+ removeCount += difference;
+
+ if (insert->count == 0) {
+ insert = m_inserts.erase(insert);
+ } else if (rit->count == -offset || rit->count == 0) {
+ insert->index += difference;
+ break;
+ } else {
+ insert->index -= removeCount - difference;
+ rit->index -= insert->count;
+ insertCount += insert->count;
+ ++insert;
+ }
+ }
+ removeCount += rit->count;
+ }
+ for (; insert != m_inserts.end(); ++insert)
+ insert->index -= removeCount;
+
+ removeCount = 0;
+ QVector<Change>::iterator remove = m_removes.begin();
+ for (rit = removes->begin(); rit != removes->end(); ++rit) {
+ if (rit->count == 0)
+ continue;
+ // Accumulate consecutive removes into a single delta before attempting to apply.
+ for (QVector<Change>::iterator next = rit + 1; next != removes->end()
+ && next->index == rit->index
+ && next->moveId == -1
+ && rit->moveId == -1; ++next) {
+ next->count += rit->count;
+ rit = next;
+ }
+ int index = rit->index + removeCount;
+ // Decrement the accumulated remove count from the indexes of any inserts prior to the
+ // current remove.
+ for (; remove != m_removes.end() && index > remove->index; ++remove)
+ remove->index -= removeCount;
+ while (remove != m_removes.end() && index + rit->count >= remove->index) {
+ int count = 0;
+ const int offset = remove->index - index;
+ QVector<Change>::iterator rend = remove;
+ for (; rend != m_removes.end()
+ && rit->moveId == -1
+ && rend->moveId == -1
+ && index + rit->count >= rend->index; ++rend) {
+ count += rend->count;
+ }
+ if (remove != rend) {
+ // Accumulate all existing non-move removes that are encapsulated by or immediately
+ // follow the current remove into it.
+ int difference = 0;
+ if (rend == m_removes.end()) {
+ difference = rit->count;
+ } else if (rit->index + rit->count < rend->index - removeCount) {
+ difference = rit->count;
+ } else if (rend->moveId != -1) {
+ difference = rend->index - removeCount - rit->index;
+ index += difference;
+ }
+ count += difference;
+
+ rit->count -= difference;
+ removeCount += difference;
+ remove->index = rit->index;
+ remove->count = count;
+ remove = m_removes.erase(++remove, rend);
+ } else {
+ // Insert a remove for the portion of the unmergable current remove prior to the
+ // point of intersection.
+ if (offset > 0) {
+ remove = m_removes.insert(remove, Change(
+ rit->index, offset, rit->moveId, rit->offset));
+ ++remove;
+ rit->count -= offset;
+ rit->offset += offset;
+ removeCount += offset;
+ index += offset;
+ }
+ remove->index = rit->index;
+
+ ++remove;
+ }
+ }
+
+ if (rit->count > 0) {
+ remove = m_removes.insert(remove, *rit);
+ ++remove;
+ }
+ removeCount += rit->count;
+ }
+ for (; remove != m_removes.end(); ++remove)
+ remove->index -= removeCount;
+ m_difference -= removeCount;
+}
+
+/*!
+ Applies a list of \a inserts to a change set.
+*/
+
+void QQmlChangeSet::insert(const QVector<Change> &inserts)
+{
+ int insertCount = 0;
+ QVector<Change>::iterator insert = m_inserts.begin();
+ QVector<Change>::iterator change = m_changes.begin();
+ for (QVector<Change>::const_iterator iit = inserts.begin(); iit != inserts.end(); ++iit) {
+ if (iit->count == 0)
+ continue;
+ int index = iit->index - insertCount;
+
+ Change current = *iit;
+ // Accumulate consecutive inserts into a single delta before attempting to insert.
+ for (QVector<Change>::const_iterator next = iit + 1; next != inserts.end()
+ && next->index == iit->index + iit->count
+ && next->moveId == -1
+ && iit->moveId == -1; ++next) {
+ current.count += next->count;
+ iit = next;
+ }
+
+ // Increment the index of any changes before the current insert by the accumlated insert
+ // count.
+ for (; change != m_changes.end() && change->index >= index; ++change)
+ change->index += insertCount;
+ // If the current insert index is in the middle of a change split it in two at that
+ // point and increment the index of the latter half.
+ if (change != m_changes.end() && change->index < index + iit->count) {
+ int offset = index - change->index;
+ change = m_changes.insert(change, Change(change->index + insertCount, offset));
+ ++change;
+ change->index += iit->count + offset;
+ change->count -= offset;
+ }
+
+ // Increment the index of any inserts before the current insert by the accumlated insert
+ // count.
+ for (; insert != m_inserts.end() && index > insert->index + insert->count; ++insert)
+ insert->index += insertCount;
+ if (insert == m_inserts.end()) {
+ insert = m_inserts.insert(insert, current);
+ ++insert;
+ } else {
+ const int offset = index - insert->index;
+
+ if (offset < 0) {
+ // If the current insert is before an existing insert and not adjacent just insert
+ // it into the list.
+ insert = m_inserts.insert(insert, current);
+ ++insert;
+ } else if (iit->moveId == -1 && insert->moveId == -1) {
+ // If neither the current nor existing insert has a moveId add the current insert
+ // to the existing one.
+ if (offset < insert->count) {
+ insert->index -= current.count;
+ insert->count += current.count;
+ } else {
+ insert->index += insertCount;
+ insert->count += current.count;
+ ++insert;
+ }
+ } else if (offset < insert->count) {
+ // If either insert has a moveId then split the existing insert and insert the
+ // current one in the middle.
+ if (offset > 0) {
+ insert = m_inserts.insert(insert, Change(
+ insert->index + insertCount, offset, insert->moveId, insert->offset));
+ ++insert;
+ insert->index += offset;
+ insert->count -= offset;
+ insert->offset += offset;
+ }
+ insert = m_inserts.insert(insert, current);
+ ++insert;
+ } else {
+ insert->index += insertCount;
+ ++insert;
+ insert = m_inserts.insert(insert, current);
+ ++insert;
+ }
+ }
+ insertCount += current.count;
+ }
+ for (; insert != m_inserts.end(); ++insert)
+ insert->index += insertCount;
+ m_difference += insertCount;
+}
+
+/*!
+ Applies a combined list of \a removes and \a inserts to a change set. This is equivalent
+ calling \l remove() followed by \l insert() with the same lists.
+*/
+
+void QQmlChangeSet::move(const QVector<Change> &removes, const QVector<Change> &inserts)
+{
+ QVector<Change> r = removes;
+ QVector<Change> i = inserts;
+ remove(&r, &i);
+ insert(i);
+}
+
+/*!
+ Applies a list of \a changes to a change set.
+*/
+
+void QQmlChangeSet::change(const QVector<Change> &changes)
+{
+ QVector<Change> c = changes;
+ change(&c);
+}
+
+void QQmlChangeSet::change(QVector<Change> *changes)
+{
+ QVector<Change>::iterator insert = m_inserts.begin();
+ QVector<Change>::iterator change = m_changes.begin();
+ for (QVector<Change>::iterator cit = changes->begin(); cit != changes->end(); ++cit) {
+ for (; insert != m_inserts.end() && insert->end() < cit->index; ++insert) {}
+ for (; insert != m_inserts.end() && insert->index < cit->end(); ++insert) {
+ const int offset = insert->index - cit->index;
+ const int count = cit->count + cit->index - insert->index - insert->count;
+ if (offset == 0) {
+ cit->index = insert->index + insert->count;
+ cit->count = count;
+ } else {
+ cit = changes->insert(++cit, Change(insert->index + insert->count, count));
+ --cit;
+ cit->count = offset;
+ }
+ }
+
+ for (; change != m_changes.end() && change->index + change->count < cit->index; ++change) {}
+ if (change == m_changes.end() || change->index > cit->index + cit->count) {
+ if (cit->count > 0) {
+ change = m_changes.insert(change, *cit);
+ ++change;
+ }
+ } else {
+ if (cit->index < change->index) {
+ change->count += change->index - cit->index;
+ change->index = cit->index;
+ }
+
+ if (cit->index + cit->count > change->index + change->count) {
+ change->count = cit->index + cit->count - change->index;
+ QVector<Change>::iterator cbegin = change;
+ QVector<Change>::iterator cend = ++cbegin;
+ for (; cend != m_changes.end() && cend->index <= change->index + change->count; ++cend) {
+ if (cend->index + cend->count > change->index + change->count)
+ change->count = cend->index + cend->count - change->index;
+ }
+ if (cbegin != cend) {
+ change = m_changes.erase(cbegin, cend);
+ --change;
+ }
+ }
+ }
+ }
+}
+
+/*!
+ Prints the contents of a change \a set to the \a debug stream.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlChangeSet &set)
+{
+ debug.nospace() << "QQmlChangeSet(";
+ const QVector<QQmlChangeSet::Change> &removes = set.removes();
+ for (const QQmlChangeSet::Change &remove : removes)
+ debug << remove;
+ const QVector<QQmlChangeSet::Change> &inserts = set.inserts();
+ for (const QQmlChangeSet::Change &insert : inserts)
+ debug << insert;
+ const QVector<QQmlChangeSet::Change> &changes = set.changes();
+ for (const QQmlChangeSet::Change &change : changes)
+ debug << change;
+ return debug.nospace() << ')';
+}
+
+/*!
+ Prints a \a change to the \a debug stream.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlChangeSet::Change &change)
+{
+ return (debug.nospace() << "Change(" << change.index << ',' << change.count << ')').space();
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/qmlmodels/qqmlchangeset_p.h b/src/qmlmodels/qqmlchangeset_p.h
new file mode 100644
index 0000000000..5b44d2958c
--- /dev/null
+++ b/src/qmlmodels/qqmlchangeset_p.h
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLCHANGESET_P_H
+#define QQMLCHANGESET_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qvector.h>
+#include <QtQmlModels/private/qtqmlmodelsglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlChangeSet
+{
+public:
+ struct MoveKey
+ {
+ MoveKey() {}
+ MoveKey(int moveId, int offset) : moveId(moveId), offset(offset) {}
+ int moveId = -1;
+ int offset = 0;
+ };
+
+ // The storrage for Change (below). This struct is trivial, which it has to be in order to store
+ // it in a QV4::Heap::Base object. The Change struct doesn't add any storage fields, so it is
+ // safe to cast ChangeData to/from Change.
+ struct ChangeData
+ {
+ int index;
+ int count;
+ int moveId;
+ int offset;
+ };
+
+ struct Change: ChangeData
+ {
+ Change() {
+ index = 0;
+ count = 0;
+ moveId = -1;
+ offset = 0;
+ }
+ Change(int index, int count, int moveId = -1, int offset = 0) {
+ this->index = index;
+ this->count = count;
+ this->moveId = moveId;
+ this->offset = offset;
+ }
+
+ bool isMove() const { return moveId >= 0; }
+
+ MoveKey moveKey(int index) const {
+ return MoveKey(moveId, index - Change::index + offset); }
+
+ int start() const { return index; }
+ int end() const { return index + count; }
+ };
+
+ QQmlChangeSet();
+ QQmlChangeSet(const QQmlChangeSet &changeSet);
+ ~QQmlChangeSet();
+
+ QQmlChangeSet &operator =(const QQmlChangeSet &changeSet);
+
+ const QVector<Change> &removes() const { return m_removes; }
+ const QVector<Change> &inserts() const { return m_inserts; }
+ const QVector<Change> &changes() const { return m_changes; }
+
+ void insert(int index, int count);
+ void remove(int index, int count);
+ void move(int from, int to, int count, int moveId);
+ void change(int index, int count);
+
+ void insert(const QVector<Change> &inserts);
+ void remove(const QVector<Change> &removes, QVector<Change> *inserts = nullptr);
+ void move(const QVector<Change> &removes, const QVector<Change> &inserts);
+ void change(const QVector<Change> &changes);
+ void apply(const QQmlChangeSet &changeSet);
+
+ bool isEmpty() const { return m_removes.empty() && m_inserts.empty() && m_changes.isEmpty(); }
+
+ void clear()
+ {
+ m_removes.clear();
+ m_inserts.clear();
+ m_changes.clear();
+ m_difference = 0;
+ }
+
+ int difference() const { return m_difference; }
+
+private:
+ void remove(QVector<Change> *removes, QVector<Change> *inserts);
+ void change(QVector<Change> *changes);
+
+ QVector<Change> m_removes;
+ QVector<Change> m_inserts;
+ QVector<Change> m_changes;
+ int m_difference;
+};
+
+Q_DECLARE_TYPEINFO(QQmlChangeSet::Change, Q_PRIMITIVE_TYPE);
+Q_DECLARE_TYPEINFO(QQmlChangeSet::MoveKey, Q_PRIMITIVE_TYPE);
+
+inline uint qHash(const QQmlChangeSet::MoveKey &key) { return qHash(qMakePair(key.moveId, key.offset)); }
+inline bool operator ==(const QQmlChangeSet::MoveKey &l, const QQmlChangeSet::MoveKey &r) {
+ return l.moveId == r.moveId && l.offset == r.offset; }
+
+Q_QMLMODELS_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet::Change &change);
+Q_QMLMODELS_PRIVATE_EXPORT QDebug operator <<(QDebug debug, const QQmlChangeSet &change);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qmlmodels/qqmldelegatecomponent.cpp b/src/qmlmodels/qqmldelegatecomponent.cpp
new file mode 100644
index 0000000000..cc3b38ec93
--- /dev/null
+++ b/src/qmlmodels/qqmldelegatecomponent.cpp
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmldelegatecomponent_p.h"
+#include <QtQmlModels/private/qqmladaptormodel_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlAbstractDelegateComponent::QQmlAbstractDelegateComponent(QObject *parent)
+ : QQmlComponent(parent)
+{
+}
+
+QQmlAbstractDelegateComponent::~QQmlAbstractDelegateComponent()
+{
+}
+
+QVariant QQmlAbstractDelegateComponent::value(QQmlAdaptorModel *adaptorModel, int row, int column, const QString &role) const
+{
+ if (!adaptorModel)
+ return QVariant();
+ return adaptorModel->value(adaptorModel->indexAt(row, column), role);
+}
+
+/*!
+ \qmltype DelegateChoice
+ \instantiates QQmlDelegateChoice
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Encapsulates a delegate and when to use it.
+
+ The DelegateChoice type wraps a delegate and defines the circumstances
+ in which it should be chosen.
+
+ DelegateChoices can be nested inside a DelegateChooser.
+
+ \sa DelegateChooser
+*/
+
+/*!
+ \qmlproperty string QtQml.Models::DelegateChoice::roleValue
+ This property holds the value used to match the role data for the role provided by \l DelegateChooser::role.
+*/
+QVariant QQmlDelegateChoice::roleValue() const
+{
+ return m_value;
+}
+
+void QQmlDelegateChoice::setRoleValue(const QVariant &value)
+{
+ if (m_value == value)
+ return;
+ m_value = value;
+ emit roleValueChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty index QtQml.Models::DelegateChoice::row
+ This property holds the value used to match the row value of model elements.
+ With models that have only the index property (and thus only one column), this property
+ should be intended as an index, and set to the desired index value.
+
+ \note Setting both row and index has undefined behavior. The two are equivalent and only
+ one should be used.
+
+ \sa index
+*/
+
+/*!
+ \qmlproperty index QtQml.Models::DelegateChoice::index
+ This property holds the value used to match the index value of model elements.
+ This is effectively an alias for \l row.
+
+ \sa row
+*/
+int QQmlDelegateChoice::row() const
+{
+ return m_row;
+}
+
+void QQmlDelegateChoice::setRow(int r)
+{
+ if (m_row == r)
+ return;
+ m_row = r;
+ emit rowChanged();
+ emit indexChanged();
+ emit changed();
+}
+
+/*!
+ \qmlproperty index QtQml.Models::DelegateChoice::column
+ This property holds the value used to match the column value of model elements.
+*/
+int QQmlDelegateChoice::column() const
+{
+ return m_column;
+}
+
+void QQmlDelegateChoice::setColumn(int c)
+{
+ if (m_column == c)
+ return;
+ m_column = c;
+ emit columnChanged();
+ emit changed();
+}
+
+QQmlComponent *QQmlDelegateChoice::delegate() const
+{
+ return m_delegate;
+}
+
+/*!
+ \qmlproperty Component QtQml.Models::DelegateChoice::delegate
+ This property holds the delegate to use if this choice matches the model item.
+*/
+void QQmlDelegateChoice::setDelegate(QQmlComponent *delegate)
+{
+ if (m_delegate == delegate)
+ return;
+ QQmlAbstractDelegateComponent *adc = static_cast<QQmlAbstractDelegateComponent *>(m_delegate);
+ if (adc)
+ disconnect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged);
+ m_delegate = delegate;
+ adc = static_cast<QQmlAbstractDelegateComponent *>(delegate);
+ if (adc)
+ connect(adc, &QQmlAbstractDelegateComponent::delegateChanged, this, &QQmlDelegateChoice::delegateChanged);
+ emit delegateChanged();
+ emit changed();
+}
+
+bool QQmlDelegateChoice::match(int row, int column, const QVariant &value) const
+{
+ if (!m_value.isValid() && m_row < 0 && m_column < 0)
+ return true;
+
+ const bool roleMatched = (m_value.isValid()) ? value == m_value : true;
+ const bool rowMatched = (m_row < 0 ) ? true : m_row == row;
+ const bool columnMatched = (m_column < 0 ) ? true : m_column == column;
+ return roleMatched && rowMatched && columnMatched;
+}
+
+/*!
+ \qmltype DelegateChooser
+ \instantiates QQmlDelegateChooser
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Allows a view to use different delegates for different types of items in the model.
+
+ The DelegateChooser is a special \l Component type intended for those scenarios where a Component is required
+ by a view and used as a delegate.
+ DelegateChooser encapsulates a set of \l {DelegateChoice}s.
+ These choices are used to determine the delegate that will be instantiated for each
+ item in the model.
+ The selection of the choice is performed based on the value that a model item has for \l role,
+ and also based on index.
+
+ DelegateChooser is commonly used when a view needs to display a set of delegates that are significantly
+ different from each other. For example, a typical phone settings view might include toggle switches,
+ sliders, radio buttons, and other visualizations based on the type of each setting. In this case, DelegateChooser
+ could provide an easy way to associate a different type of delegate with each setting:
+
+ \qml \QtMinorVersion
+ import QtQuick 2.\1
+ import QtQuick.Controls 2.\1
+ import Qt.labs.qmlmodels 1.0
+
+ ListView {
+ width: 200; height: 400
+
+ ListModel {
+ id: listModel
+ ListElement { type: "info"; ... }
+ ListElement { type: "switch"; ... }
+ ListElement { type: "swipe"; ... }
+ ListElement { type: "switch"; ... }
+ }
+
+ DelegateChooser {
+ id: chooser
+ role: "type"
+ DelegateChoice { roleValue: "info"; ItemDelegate { ... } }
+ DelegateChoice { roleValue: "switch"; SwitchDelegate { ... } }
+ DelegateChoice { roleValue: "swipe"; SwipeDelegate { ... } }
+ }
+
+ model: listModel
+ delegate: chooser
+ }
+ \endqml
+
+ \note This type is intended to transparently work only with TableView and any DelegateModel-based view.
+ Views (including user-defined views) that aren't internally based on a DelegateModel need to explicitly support
+ this type of component to make it function as described.
+
+ \sa DelegateChoice
+*/
+
+/*!
+ \qmlproperty string QtQml.Models::DelegateChooser::role
+ This property holds the role used to determine the delegate for a given model item.
+
+ \sa DelegateChoice
+*/
+void QQmlDelegateChooser::setRole(const QString &role)
+{
+ if (m_role == role)
+ return;
+ m_role = role;
+ emit roleChanged();
+}
+
+/*!
+ \qmlproperty list<DelegateChoice> QtQml.Models::DelegateChooser::choices
+ \default
+
+ The list of DelegateChoices for the chooser.
+
+ The list is treated as an ordered list, where the first DelegateChoice to match
+ will be used be a view.
+
+ It should not generally be necessary to refer to the \c choices property,
+ as it is the default property for DelegateChooser and thus all child items are
+ automatically assigned to this property.
+*/
+
+QQmlListProperty<QQmlDelegateChoice> QQmlDelegateChooser::choices()
+{
+ return QQmlListProperty<QQmlDelegateChoice>(this, nullptr,
+ QQmlDelegateChooser::choices_append,
+ QQmlDelegateChooser::choices_count,
+ QQmlDelegateChooser::choices_at,
+ QQmlDelegateChooser::choices_clear);
+}
+
+void QQmlDelegateChooser::choices_append(QQmlListProperty<QQmlDelegateChoice> *prop, QQmlDelegateChoice *choice)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object);
+ q->m_choices.append(choice);
+ connect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged);
+ q->delegateChanged();
+}
+
+int QQmlDelegateChooser::choices_count(QQmlListProperty<QQmlDelegateChoice> *prop)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser*>(prop->object);
+ return q->m_choices.count();
+}
+
+QQmlDelegateChoice *QQmlDelegateChooser::choices_at(QQmlListProperty<QQmlDelegateChoice> *prop, int index)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser*>(prop->object);
+ return q->m_choices.at(index);
+}
+
+void QQmlDelegateChooser::choices_clear(QQmlListProperty<QQmlDelegateChoice> *prop)
+{
+ QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object);
+ for (QQmlDelegateChoice *choice : q->m_choices)
+ disconnect(choice, &QQmlDelegateChoice::changed, q, &QQmlAbstractDelegateComponent::delegateChanged);
+ q->m_choices.clear();
+ q->delegateChanged();
+}
+
+QQmlComponent *QQmlDelegateChooser::delegate(QQmlAdaptorModel *adaptorModel, int row, int column) const
+{
+ QVariant v;
+ if (!m_role.isNull())
+ v = value(adaptorModel, row, column, m_role);
+ if (!v.isValid()) { // check if the row only has modelData, for example if the row is a QVariantMap
+ v = value(adaptorModel, row, column, QStringLiteral("modelData"));
+ if (v.isValid())
+ v = v.toMap().value(m_role);
+ }
+ // loop through choices, finding first one that fits
+ for (int i = 0; i < m_choices.count(); ++i) {
+ const QQmlDelegateChoice *choice = m_choices.at(i);
+ if (choice->match(row, column, v))
+ return choice->delegate();
+ }
+
+ return nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlmodels/qqmldelegatecomponent_p.h b/src/qmlmodels/qqmldelegatecomponent_p.h
new file mode 100644
index 0000000000..1d20f0327b
--- /dev/null
+++ b/src/qmlmodels/qqmldelegatecomponent_p.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLDELEGATECOMPONENT_P_H
+#define QQMLDELEGATECOMPONENT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlmodelsglobal_p.h>
+#include <qqmlcomponent.h>
+
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
+QT_BEGIN_NAMESPACE
+
+// TODO: consider making QQmlAbstractDelegateComponent public API
+class QQmlAbstractDelegateComponentPrivate;
+class QQmlAdaptorModel;
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlAbstractDelegateComponent : public QQmlComponent
+{
+ Q_OBJECT
+public:
+ QQmlAbstractDelegateComponent(QObject *parent = nullptr);
+ ~QQmlAbstractDelegateComponent() override;
+
+ virtual QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = 0) const = 0;
+
+signals:
+ void delegateChanged();
+
+protected:
+ QVariant value(QQmlAdaptorModel *adaptorModel,int row, int column, const QString &role) const;
+
+private:
+ Q_DECLARE_PRIVATE(QQmlAbstractDelegateComponent)
+ Q_DISABLE_COPY(QQmlAbstractDelegateComponent)
+};
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateChoice : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
+ Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged)
+ Q_PROPERTY(int index READ row WRITE setRow NOTIFY indexChanged)
+ Q_PROPERTY(int column READ column WRITE setColumn NOTIFY columnChanged)
+ Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_CLASSINFO("DefaultProperty", "delegate")
+public:
+ QVariant roleValue() const;
+ void setRoleValue(const QVariant &roleValue);
+
+ int row() const;
+ void setRow(int r);
+
+ int column() const;
+ void setColumn(int c);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *delegate);
+
+ virtual bool match(int row, int column, const QVariant &value) const;
+
+signals:
+ void roleValueChanged();
+ void rowChanged();
+ void indexChanged();
+ void columnChanged();
+ void delegateChanged();
+ void changed();
+
+private:
+ QVariant m_value;
+ int m_row = -1;
+ int m_column = -1;
+ QQmlComponent *m_delegate = nullptr;
+};
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateChooser : public QQmlAbstractDelegateComponent
+{
+ Q_OBJECT
+ Q_PROPERTY(QString role READ role WRITE setRole NOTIFY roleChanged)
+ Q_PROPERTY(QQmlListProperty<QQmlDelegateChoice> choices READ choices CONSTANT)
+ Q_CLASSINFO("DefaultProperty", "choices")
+
+public:
+ QString role() const { return m_role; }
+ void setRole(const QString &role);
+
+ virtual QQmlListProperty<QQmlDelegateChoice> choices();
+ static void choices_append(QQmlListProperty<QQmlDelegateChoice> *, QQmlDelegateChoice *);
+ static int choices_count(QQmlListProperty<QQmlDelegateChoice> *);
+ static QQmlDelegateChoice *choices_at(QQmlListProperty<QQmlDelegateChoice> *, int);
+ static void choices_clear(QQmlListProperty<QQmlDelegateChoice> *);
+
+ QQmlComponent *delegate(QQmlAdaptorModel *adaptorModel, int row, int column = -1) const override;
+
+signals:
+ void roleChanged();
+
+private:
+ QString m_role;
+ QList<QQmlDelegateChoice *> m_choices;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlDelegateChoice)
+QML_DECLARE_TYPE(QQmlDelegateChooser)
+
+#endif // QQMLDELEGATECOMPONENT_P_H
diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp
new file mode 100644
index 0000000000..65c99d9bc0
--- /dev/null
+++ b/src/qmlmodels/qqmldelegatemodel.cpp
@@ -0,0 +1,3560 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmldelegatemodel_p_p.h"
+#include "qqmldelegatecomponent_p.h"
+
+#include <QtQml/qqmlinfo.h>
+
+#include <private/qquickpackage_p.h>
+#include <private/qmetaobjectbuilder_p.h>
+#include <private/qqmladaptormodel_p.h>
+#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>
+#include <private/qv4objectiterator_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlDelegateModelItem;
+
+namespace QV4 {
+
+namespace Heap {
+
+struct DelegateModelGroupFunction : FunctionObject {
+ void init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg));
+
+ QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg);
+ uint flag;
+};
+
+struct QQmlDelegateModelGroupChange : Object {
+ void init() { Object::init(); }
+
+ QQmlChangeSet::ChangeData change;
+};
+
+struct QQmlDelegateModelGroupChangeArray : Object {
+ void init(const QVector<QQmlChangeSet::Change> &changes);
+ void destroy() {
+ delete changes;
+ Object::destroy();
+ }
+
+ QVector<QQmlChangeSet::Change> *changes;
+};
+
+
+}
+
+struct DelegateModelGroupFunction : QV4::FunctionObject
+{
+ V4_OBJECT2(DelegateModelGroupFunction, FunctionObject)
+
+ static Heap::DelegateModelGroupFunction *create(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg))
+ {
+ return scope->engine()->memoryManager->allocate<DelegateModelGroupFunction>(scope, flag, code);
+ }
+
+ static ReturnedValue virtualCall(const QV4::FunctionObject *that, const Value *thisObject, const Value *argv, int argc)
+ {
+ QV4::Scope scope(that->engine());
+ QV4::Scoped<DelegateModelGroupFunction> f(scope, static_cast<const DelegateModelGroupFunction *>(that));
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject);
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+
+ QV4::ScopedValue v(scope, argc ? argv[0] : Value::undefinedValue());
+ return f->d()->code(o->d()->item, f->d()->flag, v);
+ }
+};
+
+void Heap::DelegateModelGroupFunction::init(QV4::ExecutionContext *scope, uint flag, QV4::ReturnedValue (*code)(QQmlDelegateModelItem *item, uint flag, const QV4::Value &arg))
+{
+ QV4::Heap::FunctionObject::init(scope, QStringLiteral("DelegateModelGroupFunction"));
+ this->flag = flag;
+ this->code = code;
+}
+
+}
+
+DEFINE_OBJECT_VTABLE(QV4::DelegateModelGroupFunction);
+
+
+
+class QQmlDelegateModelEngineData : public QV4::ExecutionEngine::Deletable
+{
+public:
+ QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4);
+ ~QQmlDelegateModelEngineData();
+
+ QV4::ReturnedValue array(QV4::ExecutionEngine *engine,
+ const QVector<QQmlChangeSet::Change> &changes);
+
+ QV4::PersistentValue changeProto;
+};
+
+V4_DEFINE_EXTENSION(QQmlDelegateModelEngineData, engineData)
+
+
+void QQmlDelegateModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop)
+{
+ prop.setWritable(false);
+}
+
+QVariant QQmlDelegateModelPartsMetaObject::initialValue(int id)
+{
+ QQmlDelegateModelParts *parts = static_cast<QQmlDelegateModelParts *>(object());
+ QQmlPartsModel *m = new QQmlPartsModel(
+ parts->model, QString::fromUtf8(name(id)), parts);
+ parts->models.append(m);
+ return QVariant::fromValue(static_cast<QObject *>(m));
+}
+
+QQmlDelegateModelParts::QQmlDelegateModelParts(QQmlDelegateModel *parent)
+: QObject(parent), model(parent)
+{
+ new QQmlDelegateModelPartsMetaObject(this);
+}
+
+//---------------------------------------------------------------------------
+
+/*!
+ \qmltype DelegateModel
+ \instantiates QQmlDelegateModel
+ \inqmlmodule QtQml.Models
+ \brief Encapsulates a model and delegate.
+
+ The DelegateModel type encapsulates a model and the delegate that will
+ be instantiated for items in the model.
+
+ It is usually not necessary to create a DelegateModel.
+ However, it can be useful for manipulating and accessing the \l modelIndex
+ when a QAbstractItemModel subclass is used as the
+ model. Also, DelegateModel is used together with \l Package to
+ provide delegates to multiple views, and with DelegateModelGroup to sort and filter
+ delegate items.
+
+ The example below illustrates using a DelegateModel with a ListView.
+
+ \snippet delegatemodel/delegatemodel.qml 0
+*/
+
+QQmlDelegateModelPrivate::QQmlDelegateModelPrivate(QQmlContext *ctxt)
+ : m_delegateChooser(nullptr)
+ , m_cacheMetaType(nullptr)
+ , m_context(ctxt)
+ , m_parts(nullptr)
+ , m_filterGroup(QStringLiteral("items"))
+ , m_count(0)
+ , m_groupCount(Compositor::MinimumGroupCount)
+ , m_compositorGroup(Compositor::Cache)
+ , m_complete(false)
+ , m_delegateValidated(false)
+ , m_reset(false)
+ , m_transaction(false)
+ , m_incubatorCleanupScheduled(false)
+ , m_waitingToFetchMore(false)
+ , m_cacheItems(nullptr)
+ , m_items(nullptr)
+ , m_persistedItems(nullptr)
+{
+}
+
+QQmlDelegateModelPrivate::~QQmlDelegateModelPrivate()
+{
+ qDeleteAll(m_finishedIncubating);
+
+ if (m_cacheMetaType)
+ m_cacheMetaType->release();
+}
+
+int QQmlDelegateModelPrivate::adaptorModelCount() const
+{
+ // QQmlDelegateModel currently only support list models.
+ // So even if a model is a table model, only the first
+ // column will be used.
+ return m_adaptorModel.rowCount();
+}
+
+void QQmlDelegateModelPrivate::requestMoreIfNecessary()
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_waitingToFetchMore && m_adaptorModel.canFetchMore()) {
+ m_waitingToFetchMore = true;
+ QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
+ }
+}
+
+void QQmlDelegateModelPrivate::init()
+{
+ Q_Q(QQmlDelegateModel);
+ m_compositor.setRemoveGroups(Compositor::GroupMask & ~Compositor::PersistedFlag);
+
+ m_items = new QQmlDelegateModelGroup(QStringLiteral("items"), q, Compositor::Default, q);
+ m_items->setDefaultInclude(true);
+ m_persistedItems = new QQmlDelegateModelGroup(QStringLiteral("persistedItems"), q, Compositor::Persisted, q);
+ QQmlDelegateModelGroupPrivate::get(m_items)->emitters.insert(this);
+}
+
+QQmlDelegateModel::QQmlDelegateModel()
+ : QQmlDelegateModel(nullptr, nullptr)
+{
+}
+
+QQmlDelegateModel::QQmlDelegateModel(QQmlContext *ctxt, QObject *parent)
+: QQmlInstanceModel(*(new QQmlDelegateModelPrivate(ctxt)), parent)
+{
+ Q_D(QQmlDelegateModel);
+ d->init();
+}
+
+QQmlDelegateModel::~QQmlDelegateModel()
+{
+ Q_D(QQmlDelegateModel);
+ d->disconnectFromAbstractItemModel();
+ d->m_adaptorModel.setObject(nullptr, this);
+
+ for (QQmlDelegateModelItem *cacheItem : qAsConst(d->m_cache)) {
+ if (cacheItem->object) {
+ delete cacheItem->object;
+
+ cacheItem->object = nullptr;
+ cacheItem->contextData->invalidate();
+ Q_ASSERT(cacheItem->contextData->refCount == 1);
+ cacheItem->contextData = nullptr;
+ cacheItem->scriptRef -= 1;
+ }
+ cacheItem->groups &= ~Compositor::UnresolvedFlag;
+ cacheItem->objectRef = 0;
+ if (!cacheItem->isReferenced())
+ delete cacheItem;
+ else if (cacheItem->incubationTask)
+ cacheItem->incubationTask->vdm = nullptr;
+ }
+}
+
+
+void QQmlDelegateModel::classBegin()
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_context)
+ d->m_context = qmlContext(this);
+}
+
+void QQmlDelegateModel::componentComplete()
+{
+ Q_D(QQmlDelegateModel);
+ d->m_complete = true;
+
+ int defaultGroups = 0;
+ QStringList groupNames;
+ groupNames.append(QStringLiteral("items"));
+ groupNames.append(QStringLiteral("persistedItems"));
+ if (QQmlDelegateModelGroupPrivate::get(d->m_items)->defaultInclude)
+ defaultGroups |= Compositor::DefaultFlag;
+ if (QQmlDelegateModelGroupPrivate::get(d->m_persistedItems)->defaultInclude)
+ defaultGroups |= Compositor::PersistedFlag;
+ for (int i = Compositor::MinimumGroupCount; i < d->m_groupCount; ++i) {
+ QString name = d->m_groups[i]->name();
+ if (name.isEmpty()) {
+ d->m_groups[i] = d->m_groups[d->m_groupCount - 1];
+ --d->m_groupCount;
+ --i;
+ } else if (name.at(0).isUpper()) {
+ qmlWarning(d->m_groups[i]) << QQmlDelegateModelGroup::tr("Group names must start with a lower case letter");
+ d->m_groups[i] = d->m_groups[d->m_groupCount - 1];
+ --d->m_groupCount;
+ --i;
+ } else {
+ groupNames.append(name);
+
+ QQmlDelegateModelGroupPrivate *group = QQmlDelegateModelGroupPrivate::get(d->m_groups[i]);
+ group->setModel(this, Compositor::Group(i));
+ if (group->defaultInclude)
+ defaultGroups |= (1 << i);
+ }
+ }
+
+ d->m_cacheMetaType = new QQmlDelegateModelItemMetaType(
+ d->m_context->engine()->handle(), this, groupNames);
+
+ d->m_compositor.setGroupCount(d->m_groupCount);
+ d->m_compositor.setDefaultGroups(defaultGroups);
+ d->updateFilterGroup();
+
+ while (!d->m_pendingParts.isEmpty())
+ static_cast<QQmlPartsModel *>(d->m_pendingParts.first())->updateFilterGroup();
+
+ QVector<Compositor::Insert> inserts;
+ d->m_count = d->adaptorModelCount();
+ d->m_compositor.append(
+ &d->m_adaptorModel,
+ 0,
+ d->m_count,
+ defaultGroups | Compositor::AppendFlag | Compositor::PrependFlag,
+ &inserts);
+ d->itemsInserted(inserts);
+ d->emitChanges();
+ d->requestMoreIfNecessary();
+}
+
+/*!
+ \qmlproperty model QtQml.Models::DelegateModel::model
+ This property holds the model providing data for the DelegateModel.
+
+ The model provides a set of data that is used to create the items
+ for a view. For large or dynamic datasets the model is usually
+ provided by a C++ model object. The C++ model object must be a \l
+ {QAbstractItemModel} subclass or a simple list.
+
+ Models can also be created directly in QML, using a \l{ListModel} or
+ \l{QtQuick.XmlListModel::XmlListModel}{XmlListModel}.
+
+ \sa {qml-data-models}{Data Models}
+ \keyword dm-model-property
+*/
+QVariant QQmlDelegateModel::model() const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_adaptorModel.model();
+}
+
+void QQmlDelegateModelPrivate::connectToAbstractItemModel()
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_adaptorModel.adaptsAim())
+ return;
+
+ auto aim = m_adaptorModel.aim();
+
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsInserted(QModelIndex,int,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
+ q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+ q, QQmlDelegateModel, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(modelReset()),
+ q, QQmlDelegateModel, SLOT(_q_modelReset()));
+ qmlobject_connect(aim, QAbstractItemModel, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
+ q, QQmlDelegateModel, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+}
+
+void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel()
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_adaptorModel.adaptsAim())
+ return;
+
+ auto aim = m_adaptorModel.aim();
+
+ QObject::disconnect(aim, SIGNAL(rowsInserted(QModelIndex,int,int)),
+ q, SLOT(_q_rowsInserted(QModelIndex,int,int)));
+ QObject::disconnect(aim, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+ q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)));
+ QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)),
+ q, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
+ QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
+ q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
+ q, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int)));
+ QObject::disconnect(aim, SIGNAL(modelReset()),
+ q, SLOT(_q_modelReset()));
+ QObject::disconnect(aim, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)),
+ q, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)));
+}
+
+void QQmlDelegateModel::setModel(const QVariant &model)
+{
+ Q_D(QQmlDelegateModel);
+
+ if (d->m_complete)
+ _q_itemsRemoved(0, d->m_count);
+
+ d->disconnectFromAbstractItemModel();
+ d->m_adaptorModel.setModel(model, this, d->m_context->engine());
+ d->connectToAbstractItemModel();
+
+ d->m_adaptorModel.replaceWatchedRoles(QList<QByteArray>(), d->m_watchedRoles);
+ for (int i = 0; d->m_parts && i < d->m_parts->models.count(); ++i) {
+ d->m_adaptorModel.replaceWatchedRoles(
+ QList<QByteArray>(), d->m_parts->models.at(i)->watchedRoles());
+ }
+
+ if (d->m_complete) {
+ _q_itemsInserted(0, d->adaptorModelCount());
+ d->requestMoreIfNecessary();
+ }
+}
+
+/*!
+ \qmlproperty Component QtQml.Models::DelegateModel::delegate
+
+ The delegate provides a template defining each item instantiated by a view.
+ The index is exposed as an accessible \c index property. Properties of the
+ model are also available depending upon the type of \l {qml-data-models}{Data Model}.
+*/
+QQmlComponent *QQmlDelegateModel::delegate() const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_delegate;
+}
+
+void QQmlDelegateModel::setDelegate(QQmlComponent *delegate)
+{
+ Q_D(QQmlDelegateModel);
+ if (d->m_transaction) {
+ qmlWarning(this) << tr("The delegate of a DelegateModel cannot be changed within onUpdated.");
+ return;
+ }
+ if (d->m_delegate == delegate)
+ return;
+ if (d->m_complete)
+ _q_itemsRemoved(0, d->m_count);
+ d->m_delegate.setObject(delegate, this);
+ d->m_delegateValidated = false;
+ if (d->m_delegateChooser)
+ QObject::disconnect(d->m_delegateChooserChanged);
+
+ d->m_delegateChooser = nullptr;
+ if (delegate) {
+ QQmlAbstractDelegateComponent *adc =
+ qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ if (adc) {
+ d->m_delegateChooser = adc;
+ d->m_delegateChooserChanged = connect(adc, &QQmlAbstractDelegateComponent::delegateChanged,
+ [d](){ d->delegateChanged(); });
+ }
+ }
+ if (d->m_complete) {
+ _q_itemsInserted(0, d->adaptorModelCount());
+ d->requestMoreIfNecessary();
+ }
+ emit delegateChanged();
+}
+
+/*!
+ \qmlproperty QModelIndex QtQml.Models::DelegateModel::rootIndex
+
+ QAbstractItemModel provides a hierarchical tree of data, whereas
+ QML only operates on list data. \c rootIndex allows the children of
+ any node in a QAbstractItemModel to be provided by this model.
+
+ This property only affects models of type QAbstractItemModel that
+ are hierarchical (e.g, a tree model).
+
+ For example, here is a simple interactive file system browser.
+ When a directory name is clicked, the view's \c rootIndex is set to the
+ QModelIndex node of the clicked directory, thus updating the view to show
+ the new directory's contents.
+
+ \c main.cpp:
+ \snippet delegatemodel/delegatemodel_rootindex/main.cpp 0
+
+ \c view.qml:
+ \snippet delegatemodel/delegatemodel_rootindex/view.qml 0
+
+ If the \l {dm-model-property}{model} is a QAbstractItemModel subclass,
+ the delegate can also reference a \c hasModelChildren property (optionally
+ qualified by a \e model. prefix) that indicates whether the delegate's
+ model item has any child nodes.
+
+ \sa modelIndex(), parentModelIndex()
+*/
+QVariant QQmlDelegateModel::rootIndex() const
+{
+ Q_D(const QQmlDelegateModel);
+ return QVariant::fromValue(QModelIndex(d->m_adaptorModel.rootIndex));
+}
+
+void QQmlDelegateModel::setRootIndex(const QVariant &root)
+{
+ Q_D(QQmlDelegateModel);
+
+ QModelIndex modelIndex = qvariant_cast<QModelIndex>(root);
+ const bool changed = d->m_adaptorModel.rootIndex != modelIndex;
+ if (changed || !d->m_adaptorModel.isValid()) {
+ const int oldCount = d->m_count;
+ d->m_adaptorModel.rootIndex = modelIndex;
+ if (!d->m_adaptorModel.isValid() && d->m_adaptorModel.aim()) {
+ // The previous root index was invalidated, so we need to reconnect the model.
+ d->disconnectFromAbstractItemModel();
+ d->m_adaptorModel.setModel(d->m_adaptorModel.list.list(), this, d->m_context->engine());
+ d->connectToAbstractItemModel();
+ }
+ if (d->m_adaptorModel.canFetchMore())
+ d->m_adaptorModel.fetchMore();
+ if (d->m_complete) {
+ const int newCount = d->adaptorModelCount();
+ if (oldCount)
+ _q_itemsRemoved(0, oldCount);
+ if (newCount)
+ _q_itemsInserted(0, newCount);
+ }
+ if (changed)
+ emit rootIndexChanged();
+ }
+}
+
+/*!
+ \qmlmethod QModelIndex QtQml.Models::DelegateModel::modelIndex(int index)
+
+ QAbstractItemModel provides a hierarchical tree of data, whereas
+ QML only operates on list data. This function assists in using
+ tree models in QML.
+
+ Returns a QModelIndex for the specified \a index.
+ This value can be assigned to rootIndex.
+
+ \sa rootIndex
+*/
+QVariant QQmlDelegateModel::modelIndex(int idx) const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_adaptorModel.modelIndex(idx);
+}
+
+/*!
+ \qmlmethod QModelIndex QtQml.Models::DelegateModel::parentModelIndex()
+
+ QAbstractItemModel provides a hierarchical tree of data, whereas
+ QML only operates on list data. This function assists in using
+ tree models in QML.
+
+ Returns a QModelIndex for the parent of the current rootIndex.
+ This value can be assigned to rootIndex.
+
+ \sa rootIndex
+*/
+QVariant QQmlDelegateModel::parentModelIndex() const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_adaptorModel.parentModelIndex();
+}
+
+/*!
+ \qmlproperty int QtQml.Models::DelegateModel::count
+*/
+
+int QQmlDelegateModel::count() const
+{
+ Q_D(const QQmlDelegateModel);
+ if (!d->m_delegate)
+ return 0;
+ return d->m_compositor.count(d->m_compositorGroup);
+}
+
+QQmlDelegateModel::ReleaseFlags QQmlDelegateModelPrivate::release(QObject *object)
+{
+ if (!object)
+ return QQmlDelegateModel::ReleaseFlags(0);
+
+ QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(object);
+ if (!cacheItem)
+ return QQmlDelegateModel::ReleaseFlags(0);
+
+ if (!cacheItem->releaseObject())
+ return QQmlDelegateModel::Referenced;
+
+ cacheItem->destroyObject();
+ emitDestroyingItem(object);
+ if (cacheItem->incubationTask) {
+ releaseIncubator(cacheItem->incubationTask);
+ cacheItem->incubationTask = nullptr;
+ }
+ cacheItem->Dispose();
+ return QQmlInstanceModel::Destroyed;
+}
+
+/*
+ Returns ReleaseStatus flags.
+*/
+
+QQmlDelegateModel::ReleaseFlags QQmlDelegateModel::release(QObject *item)
+{
+ Q_D(QQmlDelegateModel);
+ QQmlInstanceModel::ReleaseFlags stat = d->release(item);
+ return stat;
+}
+
+// Cancel a requested async item
+void QQmlDelegateModel::cancel(int index)
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) {
+ qWarning() << "DelegateModel::cancel: index out range" << index << d->m_compositor.count(d->m_compositorGroup);
+ return;
+ }
+
+ Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index);
+ QQmlDelegateModelItem *cacheItem = it->inCache() ? d->m_cache.at(it.cacheIndex) : 0;
+ if (cacheItem) {
+ if (cacheItem->incubationTask && !cacheItem->isObjectReferenced()) {
+ d->releaseIncubator(cacheItem->incubationTask);
+ cacheItem->incubationTask = nullptr;
+
+ if (cacheItem->object) {
+ QObject *object = cacheItem->object;
+ cacheItem->destroyObject();
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
+ d->emitDestroyingPackage(package);
+ else
+ d->emitDestroyingItem(object);
+ }
+
+ cacheItem->scriptRef -= 1;
+ }
+ if (!cacheItem->isReferenced()) {
+ d->m_compositor.clearFlags(Compositor::Cache, it.cacheIndex, 1, Compositor::CacheFlag);
+ d->m_cache.removeAt(it.cacheIndex);
+ delete cacheItem;
+ Q_ASSERT(d->m_cache.count() == d->m_compositor.count(Compositor::Cache));
+ }
+ }
+}
+
+void QQmlDelegateModelPrivate::group_append(
+ QQmlListProperty<QQmlDelegateModelGroup> *property, QQmlDelegateModelGroup *group)
+{
+ QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
+ if (d->m_complete)
+ return;
+ if (d->m_groupCount == Compositor::MaximumGroupCount) {
+ qmlWarning(d->q_func()) << QQmlDelegateModel::tr("The maximum number of supported DelegateModelGroups is 8");
+ return;
+ }
+ d->m_groups[d->m_groupCount] = group;
+ d->m_groupCount += 1;
+}
+
+int QQmlDelegateModelPrivate::group_count(
+ QQmlListProperty<QQmlDelegateModelGroup> *property)
+{
+ QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
+ return d->m_groupCount - 1;
+}
+
+QQmlDelegateModelGroup *QQmlDelegateModelPrivate::group_at(
+ QQmlListProperty<QQmlDelegateModelGroup> *property, int index)
+{
+ QQmlDelegateModelPrivate *d = static_cast<QQmlDelegateModelPrivate *>(property->data);
+ return index >= 0 && index < d->m_groupCount - 1
+ ? d->m_groups[index + 1]
+ : nullptr;
+}
+
+/*!
+ \qmlproperty list<DelegateModelGroup> QtQml.Models::DelegateModel::groups
+
+ This property holds a delegate model's group definitions.
+
+ Groups define a sub-set of the items in a delegate model and can be used to filter
+ a model.
+
+ For every group defined in a DelegateModel two attached properties are added to each
+ delegate item. The first of the form DelegateModel.in\e{GroupName} holds whether the
+ item belongs to the group and the second DelegateModel.\e{groupName}Index holds the
+ index of the item in that group.
+
+ The following example illustrates using groups to select items in a model.
+
+ \snippet delegatemodel/delegatemodelgroup.qml 0
+ \keyword dm-groups-property
+*/
+
+QQmlListProperty<QQmlDelegateModelGroup> QQmlDelegateModel::groups()
+{
+ Q_D(QQmlDelegateModel);
+ return QQmlListProperty<QQmlDelegateModelGroup>(
+ this,
+ d,
+ QQmlDelegateModelPrivate::group_append,
+ QQmlDelegateModelPrivate::group_count,
+ QQmlDelegateModelPrivate::group_at,
+ nullptr);
+}
+
+/*!
+ \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::items
+
+ This property holds default group to which all new items are added.
+*/
+
+QQmlDelegateModelGroup *QQmlDelegateModel::items()
+{
+ Q_D(QQmlDelegateModel);
+ return d->m_items;
+}
+
+/*!
+ \qmlproperty DelegateModelGroup QtQml.Models::DelegateModel::persistedItems
+
+ This property holds delegate model's persisted items group.
+
+ Items in this group are not destroyed when released by a view, instead they are persisted
+ until removed from the group.
+
+ An item can be removed from the persistedItems group by setting the
+ DelegateModel.inPersistedItems property to false. If the item is not referenced by a view
+ at that time it will be destroyed. Adding an item to this group will not create a new
+ instance.
+
+ Items returned by the \l QtQml.Models::DelegateModelGroup::create() function are automatically added
+ to this group.
+*/
+
+QQmlDelegateModelGroup *QQmlDelegateModel::persistedItems()
+{
+ Q_D(QQmlDelegateModel);
+ return d->m_persistedItems;
+}
+
+/*!
+ \qmlproperty string QtQml.Models::DelegateModel::filterOnGroup
+
+ This property holds name of the group that is used to filter the delegate model.
+
+ Only items that belong to this group are visible to a view.
+
+ By default this is the \l items group.
+*/
+
+QString QQmlDelegateModel::filterGroup() const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_filterGroup;
+}
+
+void QQmlDelegateModel::setFilterGroup(const QString &group)
+{
+ Q_D(QQmlDelegateModel);
+
+ if (d->m_transaction) {
+ qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged");
+ return;
+ }
+
+ if (d->m_filterGroup != group) {
+ d->m_filterGroup = group;
+ d->updateFilterGroup();
+ emit filterGroupChanged();
+ }
+}
+
+void QQmlDelegateModel::resetFilterGroup()
+{
+ setFilterGroup(QStringLiteral("items"));
+}
+
+void QQmlDelegateModelPrivate::updateFilterGroup()
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_cacheMetaType)
+ return;
+
+ QQmlListCompositor::Group previousGroup = m_compositorGroup;
+ m_compositorGroup = Compositor::Default;
+ for (int i = 1; i < m_groupCount; ++i) {
+ if (m_filterGroup == m_cacheMetaType->groupNames.at(i - 1)) {
+ m_compositorGroup = Compositor::Group(i);
+ break;
+ }
+ }
+
+ QQmlDelegateModelGroupPrivate::get(m_groups[m_compositorGroup])->emitters.insert(this);
+ if (m_compositorGroup != previousGroup) {
+ QVector<QQmlChangeSet::Change> removes;
+ QVector<QQmlChangeSet::Change> inserts;
+ m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts);
+
+ QQmlChangeSet changeSet;
+ changeSet.move(removes, inserts);
+ emit q->modelUpdated(changeSet, false);
+
+ if (changeSet.difference() != 0)
+ emit q->countChanged();
+
+ if (m_parts) {
+ auto partsCopy = m_parts->models; // deliberate; this may alter m_parts
+ for (QQmlPartsModel *model : qAsConst(partsCopy))
+ model->updateFilterGroup(m_compositorGroup, changeSet);
+ }
+ }
+}
+
+/*!
+ \qmlproperty object QtQml.Models::DelegateModel::parts
+
+ The \a parts property selects a DelegateModel which creates
+ delegates from the part named. This is used in conjunction with
+ the \l Package type.
+
+ For example, the code below selects a model which creates
+ delegates named \e list from a \l Package:
+
+ \code
+ DelegateModel {
+ id: visualModel
+ delegate: Package {
+ Item { Package.name: "list" }
+ }
+ model: myModel
+ }
+
+ ListView {
+ width: 200; height:200
+ model: visualModel.parts.list
+ }
+ \endcode
+
+ \sa Package
+*/
+
+QObject *QQmlDelegateModel::parts()
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_parts)
+ d->m_parts = new QQmlDelegateModelParts(this);
+ return d->m_parts;
+}
+
+const QAbstractItemModel *QQmlDelegateModel::abstractItemModel() const
+{
+ Q_D(const QQmlDelegateModel);
+ return d->m_adaptorModel.adaptsAim() ? d->m_adaptorModel.aim() : nullptr;
+}
+
+void QQmlDelegateModelPrivate::emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package)
+{
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->createdPackage(incubationTask->index[i], package);
+}
+
+void QQmlDelegateModelPrivate::emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package)
+{
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->initPackage(incubationTask->index[i], package);
+}
+
+void QQmlDelegateModelPrivate::emitDestroyingPackage(QQuickPackage *package)
+{
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->destroyingPackage(package);
+}
+
+static bool isDoneIncubating(QQmlIncubator::Status status)
+{
+ return status == QQmlIncubator::Ready || status == QQmlIncubator::Error;
+}
+
+void QQDMIncubationTask::statusChanged(Status status)
+{
+ if (vdm) {
+ vdm->incubatorStatusChanged(this, status);
+ } else if (isDoneIncubating(status)) {
+ Q_ASSERT(incubating);
+ // The model was deleted from under our feet, cleanup ourselves
+ delete incubating->object;
+ incubating->object = nullptr;
+ if (incubating->contextData) {
+ incubating->contextData->invalidate();
+ Q_ASSERT(incubating->contextData->refCount == 1);
+ incubating->contextData = nullptr;
+ }
+ incubating->scriptRef = 0;
+ incubating->deleteLater();
+ }
+}
+
+void QQmlDelegateModelPrivate::releaseIncubator(QQDMIncubationTask *incubationTask)
+{
+ Q_Q(QQmlDelegateModel);
+ if (!incubationTask->isError())
+ incubationTask->clear();
+ m_finishedIncubating.append(incubationTask);
+ if (!m_incubatorCleanupScheduled) {
+ m_incubatorCleanupScheduled = true;
+ QCoreApplication::postEvent(q, new QEvent(QEvent::User));
+ }
+}
+
+void QQmlDelegateModelPrivate::addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it)
+{
+ m_cache.insert(it.cacheIndex, item);
+ m_compositor.setFlags(it, 1, Compositor::CacheFlag);
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+}
+
+void QQmlDelegateModelPrivate::removeCacheItem(QQmlDelegateModelItem *cacheItem)
+{
+ int cidx = m_cache.lastIndexOf(cacheItem);
+ if (cidx >= 0) {
+ m_compositor.clearFlags(Compositor::Cache, cidx, 1, Compositor::CacheFlag);
+ m_cache.removeAt(cidx);
+ }
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+}
+
+void QQmlDelegateModelPrivate::incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status)
+{
+ if (!isDoneIncubating(status))
+ return;
+
+ const QList<QQmlError> incubationTaskErrors = incubationTask->errors();
+
+ QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
+ cacheItem->incubationTask = nullptr;
+ incubationTask->incubating = nullptr;
+ releaseIncubator(incubationTask);
+
+ if (status == QQmlIncubator::Ready) {
+ cacheItem->referenceObject();
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
+ emitCreatedPackage(incubationTask, package);
+ else
+ emitCreatedItem(incubationTask, cacheItem->object);
+ cacheItem->releaseObject();
+ } else if (status == QQmlIncubator::Error) {
+ qmlInfo(m_delegate, incubationTaskErrors + m_delegate->errors()) << "Cannot create delegate";
+ }
+
+ if (!cacheItem->isObjectReferenced()) {
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
+ emitDestroyingPackage(package);
+ else
+ emitDestroyingItem(cacheItem->object);
+ delete cacheItem->object;
+ cacheItem->object = nullptr;
+ cacheItem->scriptRef -= 1;
+ if (cacheItem->contextData) {
+ cacheItem->contextData->invalidate();
+ Q_ASSERT(cacheItem->contextData->refCount == 1);
+ }
+ cacheItem->contextData = nullptr;
+
+ if (!cacheItem->isReferenced()) {
+ removeCacheItem(cacheItem);
+ delete cacheItem;
+ }
+ }
+}
+
+void QQDMIncubationTask::setInitialState(QObject *o)
+{
+ vdm->setInitialState(this, o);
+}
+
+void QQmlDelegateModelPrivate::setInitialState(QQDMIncubationTask *incubationTask, QObject *o)
+{
+ QQmlDelegateModelItem *cacheItem = incubationTask->incubating;
+ cacheItem->object = o;
+
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(cacheItem->object))
+ emitInitPackage(incubationTask, package);
+ else
+ emitInitItem(incubationTask, cacheItem->object);
+}
+
+QObject *QQmlDelegateModelPrivate::object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode)
+{
+ if (!m_delegate || index < 0 || index >= m_compositor.count(group)) {
+ qWarning() << "DelegateModel::item: index out range" << index << m_compositor.count(group);
+ return nullptr;
+ } else if (!m_context || !m_context->isValid()) {
+ return nullptr;
+ }
+
+ Compositor::iterator it = m_compositor.find(group, index);
+
+ QQmlDelegateModelItem *cacheItem = it->inCache() ? m_cache.at(it.cacheIndex) : 0;
+
+ if (!cacheItem) {
+ cacheItem = m_adaptorModel.createItem(m_cacheMetaType, it.modelIndex());
+ if (!cacheItem)
+ return nullptr;
+
+ cacheItem->groups = it->flags;
+ addCacheItem(cacheItem, it);
+ }
+
+ // Bump the reference counts temporarily so neither the content data or the delegate object
+ // are deleted if incubatorStatusChanged() is called synchronously.
+ cacheItem->scriptRef += 1;
+ cacheItem->referenceObject();
+
+ if (cacheItem->incubationTask) {
+ bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
+ if (sync && cacheItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous) {
+ // previously requested async - now needed immediately
+ cacheItem->incubationTask->forceCompletion();
+ }
+ } else if (!cacheItem->object) {
+ QQmlComponent *delegate = m_delegate;
+ if (m_delegateChooser) {
+ QQmlAbstractDelegateComponent *chooser = m_delegateChooser;
+ do {
+ delegate = chooser->delegate(&m_adaptorModel, index);
+ chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ } while (chooser);
+ if (!delegate)
+ return nullptr;
+ }
+
+ QQmlContext *creationContext = delegate->creationContext();
+
+ cacheItem->scriptRef += 1;
+
+ cacheItem->incubationTask = new QQDMIncubationTask(this, incubationMode);
+ cacheItem->incubationTask->incubating = cacheItem;
+ cacheItem->incubationTask->clear();
+
+ for (int i = 1; i < m_groupCount; ++i)
+ cacheItem->incubationTask->index[i] = it.index[i];
+
+ QQmlContextData *ctxt = new QQmlContextData;
+ ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_context.data()));
+ ctxt->contextObject = cacheItem;
+ cacheItem->contextData = ctxt;
+
+ if (m_adaptorModel.hasProxyObject()) {
+ if (QQmlAdaptorModelProxyInterface *proxy
+ = qobject_cast<QQmlAdaptorModelProxyInterface *>(cacheItem)) {
+ ctxt = new QQmlContextData;
+ ctxt->setParent(cacheItem->contextData, /*stronglyReferencedByParent*/true);
+ QObject *proxied = proxy->proxiedObject();
+ ctxt->contextObject = proxied;
+ // We don't own the proxied object. We need to clear it if it goes away.
+ QObject::connect(proxied, &QObject::destroyed,
+ cacheItem, &QQmlDelegateModelItem::childContextObjectDestroyed);
+ }
+ }
+
+ QQmlComponentPrivate *cp = QQmlComponentPrivate::get(delegate);
+ cp->incubateObject(
+ cacheItem->incubationTask,
+ delegate,
+ m_context->engine(),
+ ctxt,
+ QQmlContextData::get(m_context));
+ }
+
+ if (index == m_compositor.count(group) - 1)
+ requestMoreIfNecessary();
+
+ // Remove the temporary reference count.
+ cacheItem->scriptRef -= 1;
+ if (cacheItem->object && (!cacheItem->incubationTask || isDoneIncubating(cacheItem->incubationTask->status())))
+ return cacheItem->object;
+
+ cacheItem->releaseObject();
+ if (!cacheItem->isReferenced()) {
+ removeCacheItem(cacheItem);
+ delete cacheItem;
+ }
+
+ return nullptr;
+}
+
+/*
+ If asynchronous is true or the component is being loaded asynchronously due
+ to an ancestor being loaded asynchronously, object() may return 0. In this
+ case createdItem() will be emitted when the object is available. The object
+ at this stage does not have any references, so object() must be called again
+ to ensure a reference is held. Any call to object() which returns a valid object
+ must be matched by a call to release() in order to destroy the object.
+*/
+QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_delegate || index < 0 || index >= d->m_compositor.count(d->m_compositorGroup)) {
+ qWarning() << "DelegateModel::item: index out range" << index << d->m_compositor.count(d->m_compositorGroup);
+ return nullptr;
+ }
+
+ return d->object(d->m_compositorGroup, index, incubationMode);
+}
+
+QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index)
+{
+ Q_D(QQmlDelegateModel);
+ Compositor::iterator it = d->m_compositor.find(d->m_compositorGroup, index);
+ if (!it->inCache())
+ return QQmlIncubator::Null;
+
+ if (auto incubationTask = d->m_cache.at(it.cacheIndex)->incubationTask)
+ return incubationTask->status();
+
+ return QQmlIncubator::Ready;
+}
+
+QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name)
+{
+ Compositor::iterator it = m_compositor.find(group, index);
+ if (QQmlAdaptorModel *model = it.list<QQmlAdaptorModel>()) {
+ QString role = name;
+ int dot = name.indexOf(QLatin1Char('.'));
+ if (dot > 0)
+ role = name.left(dot);
+ QVariant value = model->value(it.modelIndex(), role);
+ while (dot > 0) {
+ QObject *obj = qvariant_cast<QObject*>(value);
+ if (!obj)
+ return QVariant();
+ const int from = dot + 1;
+ dot = name.indexOf(QLatin1Char('.'), from);
+ value = obj->property(name.midRef(from, dot - from).toUtf8());
+ }
+ return value;
+ }
+ return QVariant();
+}
+
+QVariant QQmlDelegateModel::variantValue(int index, const QString &role)
+{
+ Q_D(QQmlDelegateModel);
+ return d->variantValue(d->m_compositorGroup, index, role);
+}
+
+int QQmlDelegateModel::indexOf(QObject *item, QObject *) const
+{
+ Q_D(const QQmlDelegateModel);
+ if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(item))
+ return cacheItem->groupIndex(d->m_compositorGroup);
+ return -1;
+}
+
+void QQmlDelegateModel::setWatchedRoles(const QList<QByteArray> &roles)
+{
+ Q_D(QQmlDelegateModel);
+ d->m_adaptorModel.replaceWatchedRoles(d->m_watchedRoles, roles);
+ d->m_watchedRoles = roles;
+}
+
+void QQmlDelegateModelPrivate::addGroups(
+ Compositor::iterator from, int count, Compositor::Group group, int groupFlags)
+{
+ QVector<Compositor::Insert> inserts;
+ m_compositor.setFlags(from, count, group, groupFlags, &inserts);
+ itemsInserted(inserts);
+ emitChanges();
+}
+
+void QQmlDelegateModelPrivate::removeGroups(
+ Compositor::iterator from, int count, Compositor::Group group, int groupFlags)
+{
+ QVector<Compositor::Remove> removes;
+ m_compositor.clearFlags(from, count, group, groupFlags, &removes);
+ itemsRemoved(removes);
+ emitChanges();
+}
+
+void QQmlDelegateModelPrivate::setGroups(
+ Compositor::iterator from, int count, Compositor::Group group, int groupFlags)
+{
+ QVector<Compositor::Remove> removes;
+ QVector<Compositor::Insert> inserts;
+
+ m_compositor.setFlags(from, count, group, groupFlags, &inserts);
+ itemsInserted(inserts);
+ const int removeFlags = ~groupFlags & Compositor::GroupMask;
+
+ from = m_compositor.find(from.group, from.index[from.group]);
+ m_compositor.clearFlags(from, count, group, removeFlags, &removes);
+ itemsRemoved(removes);
+ emitChanges();
+}
+
+bool QQmlDelegateModel::event(QEvent *e)
+{
+ Q_D(QQmlDelegateModel);
+ if (e->type() == QEvent::UpdateRequest) {
+ d->m_waitingToFetchMore = false;
+ d->m_adaptorModel.fetchMore();
+ } else if (e->type() == QEvent::User) {
+ d->m_incubatorCleanupScheduled = false;
+ qDeleteAll(d->m_finishedIncubating);
+ d->m_finishedIncubating.clear();
+ }
+ return QQmlInstanceModel::event(e);
+}
+
+void QQmlDelegateModelPrivate::itemsChanged(const QVector<Compositor::Change> &changes)
+{
+ if (!m_delegate)
+ return;
+
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedChanges(m_groupCount);
+
+ for (const Compositor::Change &change : changes) {
+ for (int i = 1; i < m_groupCount; ++i) {
+ if (change.inGroup(i)) {
+ translatedChanges[i].append(QQmlChangeSet::Change(change.index[i], change.count));
+ }
+ }
+ }
+
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.change(translatedChanges.at(i));
+}
+
+void QQmlDelegateModel::_q_itemsChanged(int index, int count, const QVector<int> &roles)
+{
+ Q_D(QQmlDelegateModel);
+ if (count <= 0 || !d->m_complete)
+ return;
+
+ if (d->m_adaptorModel.notify(d->m_cache, index, count, roles)) {
+ QVector<Compositor::Change> changes;
+ d->m_compositor.listItemsChanged(&d->m_adaptorModel, index, count, &changes);
+ d->itemsChanged(changes);
+ d->emitChanges();
+ }
+}
+
+static void incrementIndexes(QQmlDelegateModelItem *cacheItem, int count, const int *deltas)
+{
+ if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
+ for (int i = 1; i < count; ++i)
+ incubationTask->index[i] += deltas[i];
+ }
+ if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
+ for (int i = 1; i < qMin<int>(count, Compositor::MaximumGroupCount); ++i)
+ attached->m_currentIndex[i] += deltas[i];
+ }
+}
+
+void QQmlDelegateModelPrivate::itemsInserted(
+ const QVector<Compositor::Insert> &inserts,
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedInserts,
+ QHash<int, QList<QQmlDelegateModelItem *> > *movedItems)
+{
+ int cacheIndex = 0;
+
+ int inserted[Compositor::MaximumGroupCount];
+ for (int i = 1; i < m_groupCount; ++i)
+ inserted[i] = 0;
+
+ for (const Compositor::Insert &insert : inserts) {
+ for (; cacheIndex < insert.cacheIndex; ++cacheIndex)
+ incrementIndexes(m_cache.at(cacheIndex), m_groupCount, inserted);
+
+ for (int i = 1; i < m_groupCount; ++i) {
+ if (insert.inGroup(i)) {
+ (*translatedInserts)[i].append(
+ QQmlChangeSet::Change(insert.index[i], insert.count, insert.moveId));
+ inserted[i] += insert.count;
+ }
+ }
+
+ if (!insert.inCache())
+ continue;
+
+ if (movedItems && insert.isMove()) {
+ QList<QQmlDelegateModelItem *> items = movedItems->take(insert.moveId);
+ Q_ASSERT(items.count() == insert.count);
+ m_cache = m_cache.mid(0, insert.cacheIndex) + items + m_cache.mid(insert.cacheIndex);
+ }
+ if (insert.inGroup()) {
+ for (int offset = 0; cacheIndex < insert.cacheIndex + insert.count; ++cacheIndex, ++offset) {
+ QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex);
+ cacheItem->groups |= insert.flags & Compositor::GroupMask;
+
+ if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
+ for (int i = 1; i < m_groupCount; ++i)
+ incubationTask->index[i] = cacheItem->groups & (1 << i)
+ ? insert.index[i] + offset
+ : insert.index[i];
+ }
+ if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
+ for (int i = 1; i < m_groupCount; ++i)
+ attached->m_currentIndex[i] = cacheItem->groups & (1 << i)
+ ? insert.index[i] + offset
+ : insert.index[i];
+ }
+ }
+ } else {
+ cacheIndex = insert.cacheIndex + insert.count;
+ }
+ }
+ for (const QList<QQmlDelegateModelItem *> cache = m_cache; cacheIndex < cache.count(); ++cacheIndex)
+ incrementIndexes(cache.at(cacheIndex), m_groupCount, inserted);
+}
+
+void QQmlDelegateModelPrivate::itemsInserted(const QVector<Compositor::Insert> &inserts)
+{
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount);
+ itemsInserted(inserts, &translatedInserts);
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+ if (!m_delegate)
+ return;
+
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(translatedInserts.at(i));
+}
+
+void QQmlDelegateModel::_q_itemsInserted(int index, int count)
+{
+
+ Q_D(QQmlDelegateModel);
+ if (count <= 0 || !d->m_complete)
+ return;
+
+ d->m_count += count;
+
+ const QList<QQmlDelegateModelItem *> cache = d->m_cache;
+ for (int i = 0, c = cache.count(); i < c; ++i) {
+ QQmlDelegateModelItem *item = cache.at(i);
+ // layout change triggered by changing the modelIndex might have
+ // already invalidated this item in d->m_cache and deleted it.
+ if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
+ continue;
+
+ if (item->modelIndex() >= index) {
+ const int newIndex = item->modelIndex() + count;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ }
+ }
+
+ QVector<Compositor::Insert> inserts;
+ d->m_compositor.listItemsInserted(&d->m_adaptorModel, index, count, &inserts);
+ d->itemsInserted(inserts);
+ d->emitChanges();
+}
+
+//### This method should be split in two. It will remove delegates, and it will re-render the list.
+// When e.g. QQmlListModel::remove is called, the removal of the delegates should be done on
+// QAbstractItemModel::rowsAboutToBeRemoved, and the re-rendering on
+// QAbstractItemModel::rowsRemoved. Currently both are done on the latter signal. The problem is
+// that the destruction of an item will emit a changed signal that ends up at the delegate, which
+// in turn will try to load the data from the model (which should have already freed it), resulting
+// in a use-after-free. See QTBUG-59256.
+void QQmlDelegateModelPrivate::itemsRemoved(
+ const QVector<Compositor::Remove> &removes,
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedRemoves,
+ QHash<int, QList<QQmlDelegateModelItem *> > *movedItems)
+{
+ int cacheIndex = 0;
+ int removedCache = 0;
+
+ int removed[Compositor::MaximumGroupCount];
+ for (int i = 1; i < m_groupCount; ++i)
+ removed[i] = 0;
+
+ for (const Compositor::Remove &remove : removes) {
+ for (; cacheIndex < remove.cacheIndex; ++cacheIndex)
+ incrementIndexes(m_cache.at(cacheIndex), m_groupCount, removed);
+
+ for (int i = 1; i < m_groupCount; ++i) {
+ if (remove.inGroup(i)) {
+ (*translatedRemoves)[i].append(
+ QQmlChangeSet::Change(remove.index[i], remove.count, remove.moveId));
+ removed[i] -= remove.count;
+ }
+ }
+
+ if (!remove.inCache())
+ continue;
+
+ if (movedItems && remove.isMove()) {
+ movedItems->insert(remove.moveId, m_cache.mid(remove.cacheIndex, remove.count));
+ QList<QQmlDelegateModelItem *>::iterator begin = m_cache.begin() + remove.cacheIndex;
+ QList<QQmlDelegateModelItem *>::iterator end = begin + remove.count;
+ m_cache.erase(begin, end);
+ } else {
+ for (; cacheIndex < remove.cacheIndex + remove.count - removedCache; ++cacheIndex) {
+ QQmlDelegateModelItem *cacheItem = m_cache.at(cacheIndex);
+ if (remove.inGroup(Compositor::Persisted) && cacheItem->objectRef == 0 && cacheItem->object) {
+ QObject *object = cacheItem->object;
+ cacheItem->destroyObject();
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
+ emitDestroyingPackage(package);
+ else
+ emitDestroyingItem(object);
+ cacheItem->scriptRef -= 1;
+ }
+ if (!cacheItem->isReferenced()) {
+ m_compositor.clearFlags(Compositor::Cache, cacheIndex, 1, Compositor::CacheFlag);
+ m_cache.removeAt(cacheIndex);
+ delete cacheItem;
+ --cacheIndex;
+ ++removedCache;
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+ } else if (remove.groups() == cacheItem->groups) {
+ cacheItem->groups = 0;
+ if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
+ for (int i = 1; i < m_groupCount; ++i)
+ incubationTask->index[i] = -1;
+ }
+ if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
+ for (int i = 1; i < m_groupCount; ++i)
+ attached->m_currentIndex[i] = -1;
+ }
+ } else {
+ if (QQDMIncubationTask *incubationTask = cacheItem->incubationTask) {
+ if (!cacheItem->isObjectReferenced()) {
+ releaseIncubator(cacheItem->incubationTask);
+ cacheItem->incubationTask = nullptr;
+ if (cacheItem->object) {
+ QObject *object = cacheItem->object;
+ cacheItem->destroyObject();
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
+ emitDestroyingPackage(package);
+ else
+ emitDestroyingItem(object);
+ }
+ cacheItem->scriptRef -= 1;
+ } else {
+ for (int i = 1; i < m_groupCount; ++i) {
+ if (remove.inGroup(i))
+ incubationTask->index[i] = remove.index[i];
+ }
+ }
+ }
+ if (QQmlDelegateModelAttached *attached = cacheItem->attached) {
+ for (int i = 1; i < m_groupCount; ++i) {
+ if (remove.inGroup(i))
+ attached->m_currentIndex[i] = remove.index[i];
+ }
+ }
+ cacheItem->groups &= ~remove.flags;
+ }
+ }
+ }
+ }
+
+ for (const QList<QQmlDelegateModelItem *> cache = m_cache; cacheIndex < cache.count(); ++cacheIndex)
+ incrementIndexes(cache.at(cacheIndex), m_groupCount, removed);
+}
+
+void QQmlDelegateModelPrivate::itemsRemoved(const QVector<Compositor::Remove> &removes)
+{
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount);
+ itemsRemoved(removes, &translatedRemoves);
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+ if (!m_delegate)
+ return;
+
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(translatedRemoves.at(i));
+}
+
+void QQmlDelegateModel::_q_itemsRemoved(int index, int count)
+{
+ Q_D(QQmlDelegateModel);
+ if (count <= 0|| !d->m_complete)
+ return;
+
+ d->m_count -= count;
+ const QList<QQmlDelegateModelItem *> cache = d->m_cache;
+ for (int i = 0, c = cache.count(); i < c; ++i) {
+ QQmlDelegateModelItem *item = cache.at(i);
+ // layout change triggered by removal of a previous item might have
+ // already invalidated this item in d->m_cache and deleted it
+ if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
+ continue;
+
+ if (item->modelIndex() >= index + count) {
+ const int newIndex = item->modelIndex() - count;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ } else if (item->modelIndex() >= index) {
+ item->setModelIndex(-1, -1, -1);
+ }
+ }
+
+ QVector<Compositor::Remove> removes;
+ d->m_compositor.listItemsRemoved(&d->m_adaptorModel, index, count, &removes);
+ d->itemsRemoved(removes);
+
+ d->emitChanges();
+}
+
+void QQmlDelegateModelPrivate::itemsMoved(
+ const QVector<Compositor::Remove> &removes, const QVector<Compositor::Insert> &inserts)
+{
+ QHash<int, QList<QQmlDelegateModelItem *> > movedItems;
+
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedRemoves(m_groupCount);
+ itemsRemoved(removes, &translatedRemoves, &movedItems);
+
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> translatedInserts(m_groupCount);
+ itemsInserted(inserts, &translatedInserts, &movedItems);
+ Q_ASSERT(m_cache.count() == m_compositor.count(Compositor::Cache));
+ Q_ASSERT(movedItems.isEmpty());
+ if (!m_delegate)
+ return;
+
+ for (int i = 1; i < m_groupCount; ++i) {
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.move(
+ translatedRemoves.at(i),
+ translatedInserts.at(i));
+ }
+}
+
+void QQmlDelegateModel::_q_itemsMoved(int from, int to, int count)
+{
+ Q_D(QQmlDelegateModel);
+ if (count <= 0 || !d->m_complete)
+ return;
+
+ const int minimum = qMin(from, to);
+ const int maximum = qMax(from, to) + count;
+ const int difference = from > to ? count : -count;
+
+ const QList<QQmlDelegateModelItem *> cache = d->m_cache;
+ for (int i = 0, c = cache.count(); i < c; ++i) {
+ QQmlDelegateModelItem *item = cache.at(i);
+ // layout change triggered by changing the modelIndex might have
+ // already invalidated this item in d->m_cache and deleted it.
+ if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
+ continue;
+
+ if (item->modelIndex() >= from && item->modelIndex() < from + count) {
+ const int newIndex = item->modelIndex() - from + to;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ } else if (item->modelIndex() >= minimum && item->modelIndex() < maximum) {
+ const int newIndex = item->modelIndex() + difference;
+ const int row = newIndex;
+ const int column = 0;
+ item->setModelIndex(newIndex, row, column);
+ }
+ }
+
+ QVector<Compositor::Remove> removes;
+ QVector<Compositor::Insert> inserts;
+ d->m_compositor.listItemsMoved(&d->m_adaptorModel, from, to, count, &removes, &inserts);
+ d->itemsMoved(removes, inserts);
+ d->emitChanges();
+}
+
+void QQmlDelegateModelPrivate::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset)
+{
+ Q_Q(QQmlDelegateModel);
+ emit q->modelUpdated(changeSet, reset);
+ if (changeSet.difference() != 0)
+ emit q->countChanged();
+}
+
+void QQmlDelegateModelPrivate::delegateChanged(bool add, bool remove)
+{
+ Q_Q(QQmlDelegateModel);
+ if (!m_complete)
+ return;
+
+ if (m_transaction) {
+ qmlWarning(q) << QQmlDelegateModel::tr("The delegates of a DelegateModel cannot be changed within onUpdated.");
+ return;
+ }
+
+ if (remove) {
+ for (int i = 1; i < m_groupCount; ++i) {
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.remove(
+ 0, m_compositor.count(Compositor::Group(i)));
+ }
+ }
+ if (add) {
+ for (int i = 1; i < m_groupCount; ++i) {
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->changeSet.insert(
+ 0, m_compositor.count(Compositor::Group(i)));
+ }
+ }
+ emitChanges();
+}
+
+void QQmlDelegateModelPrivate::emitChanges()
+{
+ if (m_transaction || !m_complete || !m_context || !m_context->isValid())
+ return;
+
+ m_transaction = true;
+ QV4::ExecutionEngine *engine = m_context->engine()->handle();
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitChanges(engine);
+ m_transaction = false;
+
+ const bool reset = m_reset;
+ m_reset = false;
+ for (int i = 1; i < m_groupCount; ++i)
+ QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset);
+
+ auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache
+ for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) {
+ if (cacheItem->attached)
+ cacheItem->attached->emitChanges();
+ }
+}
+
+void QQmlDelegateModel::_q_modelReset()
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_delegate)
+ return;
+
+ int oldCount = d->m_count;
+ d->m_adaptorModel.rootIndex = QModelIndex();
+
+ if (d->m_complete) {
+ d->m_count = d->adaptorModelCount();
+
+ const QList<QQmlDelegateModelItem *> cache = d->m_cache;
+ for (int i = 0, c = cache.count(); i < c; ++i) {
+ QQmlDelegateModelItem *item = cache.at(i);
+ // layout change triggered by changing the modelIndex might have
+ // already invalidated this item in d->m_cache and deleted it.
+ if (!d->m_cache.isSharedWith(cache) && !d->m_cache.contains(item))
+ continue;
+
+ if (item->modelIndex() != -1)
+ item->setModelIndex(-1, -1, -1);
+ }
+
+ QVector<Compositor::Remove> removes;
+ QVector<Compositor::Insert> inserts;
+ if (oldCount)
+ d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes);
+ if (d->m_count)
+ d->m_compositor.listItemsInserted(&d->m_adaptorModel, 0, d->m_count, &inserts);
+ d->itemsMoved(removes, inserts);
+ d->m_reset = true;
+
+ if (d->m_adaptorModel.canFetchMore())
+ d->m_adaptorModel.fetchMore();
+
+ d->emitChanges();
+ }
+ emit rootIndexChanged();
+}
+
+void QQmlDelegateModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end)
+{
+ Q_D(QQmlDelegateModel);
+ if (parent == d->m_adaptorModel.rootIndex)
+ _q_itemsInserted(begin, end - begin + 1);
+}
+
+void QQmlDelegateModel::_q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end)
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_adaptorModel.rootIndex.isValid())
+ return;
+ const QModelIndex index = d->m_adaptorModel.rootIndex;
+ if (index.parent() == parent && index.row() >= begin && index.row() <= end) {
+ const int oldCount = d->m_count;
+ d->m_count = 0;
+ d->disconnectFromAbstractItemModel();
+ d->m_adaptorModel.invalidateModel();
+
+ if (d->m_complete && oldCount > 0) {
+ QVector<Compositor::Remove> removes;
+ d->m_compositor.listItemsRemoved(&d->m_adaptorModel, 0, oldCount, &removes);
+ d->itemsRemoved(removes);
+ d->emitChanges();
+ }
+ }
+}
+
+void QQmlDelegateModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end)
+{
+ Q_D(QQmlDelegateModel);
+ if (parent == d->m_adaptorModel.rootIndex)
+ _q_itemsRemoved(begin, end - begin + 1);
+}
+
+void QQmlDelegateModel::_q_rowsMoved(
+ const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
+ const QModelIndex &destinationParent, int destinationRow)
+{
+ Q_D(QQmlDelegateModel);
+ const int count = sourceEnd - sourceStart + 1;
+ if (destinationParent == d->m_adaptorModel.rootIndex && sourceParent == d->m_adaptorModel.rootIndex) {
+ _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow - count, count);
+ } else if (sourceParent == d->m_adaptorModel.rootIndex) {
+ _q_itemsRemoved(sourceStart, count);
+ } else if (destinationParent == d->m_adaptorModel.rootIndex) {
+ _q_itemsInserted(destinationRow, count);
+ }
+}
+
+void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles)
+{
+ Q_D(QQmlDelegateModel);
+ if (begin.parent() == d->m_adaptorModel.rootIndex)
+ _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, roles);
+}
+
+bool QQmlDelegateModel::isDescendantOf(const QPersistentModelIndex& desc, const QList< QPersistentModelIndex >& parents) const
+{
+ for (int i = 0, c = parents.count(); i < c; ++i) {
+ for (QPersistentModelIndex parent = desc; parent.isValid(); parent = parent.parent()) {
+ if (parent == parents[i])
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QQmlDelegateModel::_q_layoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
+{
+ Q_D(QQmlDelegateModel);
+ if (!d->m_complete)
+ return;
+
+ if (hint == QAbstractItemModel::VerticalSortHint) {
+ if (!parents.isEmpty() && d->m_adaptorModel.rootIndex.isValid() && !isDescendantOf(d->m_adaptorModel.rootIndex, parents)) {
+ return;
+ }
+
+ // mark all items as changed
+ _q_itemsChanged(0, d->m_count, QVector<int>());
+
+ } else if (hint == QAbstractItemModel::HorizontalSortHint) {
+ // Ignored
+ } else {
+ // We don't know what's going on, so reset the model
+ _q_modelReset();
+ }
+}
+
+QQmlDelegateModelAttached *QQmlDelegateModel::qmlAttachedProperties(QObject *obj)
+{
+ if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(obj)) {
+ if (cacheItem->object == obj) { // Don't create attached item for child objects.
+ cacheItem->attached = new QQmlDelegateModelAttached(cacheItem, obj);
+ return cacheItem->attached;
+ }
+ }
+ return new QQmlDelegateModelAttached(obj);
+}
+
+bool QQmlDelegateModelPrivate::insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups)
+{
+ if (!m_context || !m_context->isValid())
+ return false;
+
+ QQmlDelegateModelItem *cacheItem = m_adaptorModel.createItem(m_cacheMetaType, -1);
+ if (!cacheItem)
+ return false;
+ if (!object.isObject())
+ return false;
+
+ QV4::ExecutionEngine *v4 = object.as<QV4::Object>()->engine();
+ QV4::Scope scope(v4);
+ QV4::ScopedObject o(scope, object);
+ if (!o)
+ return false;
+
+ QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
+ QV4::ScopedValue propertyName(scope);
+ QV4::ScopedValue v(scope);
+ while (1) {
+ propertyName = it.nextPropertyNameAsString(v);
+ if (propertyName->isNull())
+ break;
+ cacheItem->setValue(propertyName->toQStringNoThrow(), scope.engine->toVariant(v, QVariant::Invalid));
+ }
+
+ cacheItem->groups = groups | Compositor::UnresolvedFlag | Compositor::CacheFlag;
+
+ // Must be before the new object is inserted into the cache or its indexes will be adjusted too.
+ itemsInserted(QVector<Compositor::Insert>(1, Compositor::Insert(before, 1, cacheItem->groups & ~Compositor::CacheFlag)));
+
+ before = m_compositor.insert(before, nullptr, 0, 1, cacheItem->groups);
+ m_cache.insert(before.cacheIndex, cacheItem);
+
+ return true;
+}
+
+//============================================================================
+
+QQmlDelegateModelItemMetaType::QQmlDelegateModelItemMetaType(
+ QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames)
+ : model(model)
+ , groupCount(groupNames.count() + 1)
+ , v4Engine(engine)
+ , metaObject(nullptr)
+ , groupNames(groupNames)
+{
+}
+
+QQmlDelegateModelItemMetaType::~QQmlDelegateModelItemMetaType()
+{
+ if (metaObject)
+ metaObject->release();
+}
+
+void QQmlDelegateModelItemMetaType::initializeMetaObject()
+{
+ QMetaObjectBuilder builder;
+ builder.setFlags(QMetaObjectBuilder::DynamicMetaObject);
+ builder.setClassName(QQmlDelegateModelAttached::staticMetaObject.className());
+ builder.setSuperClass(&QQmlDelegateModelAttached::staticMetaObject);
+
+ int notifierId = 0;
+ for (int i = 0; i < groupNames.count(); ++i, ++notifierId) {
+ QString propertyName = QLatin1String("in") + groupNames.at(i);
+ propertyName.replace(2, 1, propertyName.at(2).toUpper());
+ builder.addSignal("__" + propertyName.toUtf8() + "Changed()");
+ QMetaPropertyBuilder propertyBuilder = builder.addProperty(
+ propertyName.toUtf8(), "bool", notifierId);
+ propertyBuilder.setWritable(true);
+ }
+ for (int i = 0; i < groupNames.count(); ++i, ++notifierId) {
+ const QString propertyName = groupNames.at(i) + QLatin1String("Index");
+ builder.addSignal("__" + propertyName.toUtf8() + "Changed()");
+ QMetaPropertyBuilder propertyBuilder = builder.addProperty(
+ propertyName.toUtf8(), "int", notifierId);
+ propertyBuilder.setWritable(true);
+ }
+
+ metaObject = new QQmlDelegateModelAttachedMetaObject(this, builder.toMetaObject());
+}
+
+void QQmlDelegateModelItemMetaType::initializePrototype()
+{
+ QV4::Scope scope(v4Engine);
+
+ QV4::ScopedObject proto(scope, v4Engine->newObject());
+ proto->defineAccessorProperty(QStringLiteral("model"), QQmlDelegateModelItem::get_model, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("groups"), QQmlDelegateModelItem::get_groups, QQmlDelegateModelItem::set_groups);
+ QV4::ScopedString s(scope);
+ QV4::ScopedProperty p(scope);
+
+ s = v4Engine->newString(QStringLiteral("isUnresolved"));
+ QV4::ScopedFunctionObject f(scope);
+ QV4::ExecutionContext *global = scope.engine->rootContext();
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, 30, QQmlDelegateModelItem::get_member)));
+ p->setSetter(nullptr);
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+
+ s = v4Engine->newString(QStringLiteral("inItems"));
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_member)));
+ p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::set_member)));
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+
+ s = v4Engine->newString(QStringLiteral("inPersistedItems"));
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_member)));
+ p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::set_member)));
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+
+ s = v4Engine->newString(QStringLiteral("itemsIndex"));
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Default, QQmlDelegateModelItem::get_index)));
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+
+ s = v4Engine->newString(QStringLiteral("persistedItemsIndex"));
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, QQmlListCompositor::Persisted, QQmlDelegateModelItem::get_index)));
+ p->setSetter(nullptr);
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+
+ for (int i = 2; i < groupNames.count(); ++i) {
+ QString propertyName = QLatin1String("in") + groupNames.at(i);
+ propertyName.replace(2, 1, propertyName.at(2).toUpper());
+ s = v4Engine->newString(propertyName);
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_member)));
+ p->setSetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::set_member)));
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+ }
+ for (int i = 2; i < groupNames.count(); ++i) {
+ const QString propertyName = groupNames.at(i) + QLatin1String("Index");
+ s = v4Engine->newString(propertyName);
+ p->setGetter((f = QV4::DelegateModelGroupFunction::create(global, i + 1, QQmlDelegateModelItem::get_index)));
+ p->setSetter(nullptr);
+ proto->insertMember(s, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable);
+ }
+ modelItemProto.set(v4Engine, proto);
+}
+
+int QQmlDelegateModelItemMetaType::parseGroups(const QStringList &groups) const
+{
+ int groupFlags = 0;
+ for (const QString &groupName : groups) {
+ int index = groupNames.indexOf(groupName);
+ if (index != -1)
+ groupFlags |= 2 << index;
+ }
+ return groupFlags;
+}
+
+int QQmlDelegateModelItemMetaType::parseGroups(const QV4::Value &groups) const
+{
+ int groupFlags = 0;
+ QV4::Scope scope(v4Engine);
+
+ QV4::ScopedString s(scope, groups);
+ if (s) {
+ const QString groupName = s->toQString();
+ int index = groupNames.indexOf(groupName);
+ if (index != -1)
+ groupFlags |= 2 << index;
+ return groupFlags;
+ }
+
+ QV4::ScopedArrayObject array(scope, groups);
+ if (array) {
+ QV4::ScopedValue v(scope);
+ uint arrayLength = array->getLength();
+ for (uint i = 0; i < arrayLength; ++i) {
+ v = array->get(i);
+ const QString groupName = v->toQString();
+ int index = groupNames.indexOf(groupName);
+ if (index != -1)
+ groupFlags |= 2 << index;
+ }
+ }
+ return groupFlags;
+}
+
+QV4::ReturnedValue QQmlDelegateModelItem::get_model(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
+{
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return b->engine()->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+ if (!o->d()->item->metaType->model)
+ RETURN_UNDEFINED();
+
+ return o->d()->item->get();
+}
+
+QV4::ReturnedValue QQmlDelegateModelItem::get_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
+{
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+
+ QStringList groups;
+ for (int i = 1; i < o->d()->item->metaType->groupCount; ++i) {
+ if (o->d()->item->groups & (1 << i))
+ groups.append(o->d()->item->metaType->groupNames.at(i - 1));
+ }
+
+ return scope.engine->fromVariant(groups);
+}
+
+QV4::ReturnedValue QQmlDelegateModelItem::set_groups(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
+{
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelItemObject> o(scope, thisObject->as<QQmlDelegateModelItemObject>());
+ if (!o)
+ return scope.engine->throwTypeError(QStringLiteral("Not a valid DelegateModel object"));
+
+ if (!argc)
+ THROW_TYPE_ERROR();
+
+ if (!o->d()->item->metaType->model)
+ RETURN_UNDEFINED();
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(o->d()->item->metaType->model);
+
+ const int groupFlags = model->m_cacheMetaType->parseGroups(argv[0]);
+ const int cacheIndex = model->m_cache.indexOf(o->d()->item);
+ Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
+ model->setGroups(it, 1, Compositor::Cache, groupFlags);
+ return QV4::Encode::undefined();
+}
+
+QV4::ReturnedValue QQmlDelegateModelItem::get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &)
+{
+ return QV4::Encode(bool(thisItem->groups & (1 << flag)));
+}
+
+QV4::ReturnedValue QQmlDelegateModelItem::set_member(QQmlDelegateModelItem *cacheItem, uint flag, const QV4::Value &arg)
+{
+ if (!cacheItem->metaType->model)
+ return QV4::Encode::undefined();
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(cacheItem->metaType->model);
+
+ bool member = arg.toBoolean();
+ uint groupFlag = (1 << flag);
+ if (member == ((cacheItem->groups & groupFlag) != 0))
+ return QV4::Encode::undefined();
+
+ const int cacheIndex = model->m_cache.indexOf(cacheItem);
+ Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
+ if (member)
+ model->addGroups(it, 1, Compositor::Cache, groupFlag);
+ else
+ model->removeGroups(it, 1, Compositor::Cache, groupFlag);
+ return QV4::Encode::undefined();
+}
+
+QV4::ReturnedValue QQmlDelegateModelItem::get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &)
+{
+ return QV4::Encode((int)thisItem->groupIndex(Compositor::Group(flag)));
+}
+
+void QQmlDelegateModelItem::childContextObjectDestroyed(QObject *childContextObject)
+{
+ if (!contextData)
+ return;
+
+ for (QQmlContextData *ctxt = contextData->childContexts; ctxt; ctxt = ctxt->nextChild) {
+ if (ctxt->contextObject == childContextObject)
+ ctxt->contextObject = nullptr;
+ }
+}
+
+
+//---------------------------------------------------------------------------
+
+DEFINE_OBJECT_VTABLE(QQmlDelegateModelItemObject);
+
+void QV4::Heap::QQmlDelegateModelItemObject::destroy()
+{
+ item->Dispose();
+ Object::destroy();
+}
+
+
+QQmlDelegateModelItem::QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType,
+ QQmlAdaptorModel::Accessors *accessor,
+ int modelIndex, int row, int column)
+ : v4(metaType->v4Engine)
+ , metaType(metaType)
+ , contextData(nullptr)
+ , object(nullptr)
+ , attached(nullptr)
+ , incubationTask(nullptr)
+ , delegate(nullptr)
+ , poolTime(0)
+ , objectRef(0)
+ , scriptRef(0)
+ , groups(0)
+ , index(modelIndex)
+ , row(row)
+ , column(column)
+{
+ metaType->addref();
+
+ if (accessor->propertyCache) {
+ // The property cache in the accessor is common for all the model
+ // items in the model it wraps. It describes available model roles,
+ // together with revisioned properties like row, column and index, all
+ // which should be available in the delegate. We assign this cache to the
+ // model item so that the QML engine can use the revision information
+ // when resolving the properties (rather than falling back to just
+ // inspecting the QObject in the model item directly).
+ QQmlData *qmldata = QQmlData::get(this, true);
+ if (qmldata->propertyCache)
+ qmldata->propertyCache->release();
+ qmldata->propertyCache = accessor->propertyCache.data();
+ qmldata->propertyCache->addref();
+ }
+}
+
+QQmlDelegateModelItem::~QQmlDelegateModelItem()
+{
+ Q_ASSERT(scriptRef == 0);
+ Q_ASSERT(objectRef == 0);
+ Q_ASSERT(!object);
+
+ if (incubationTask) {
+ if (metaType->model)
+ QQmlDelegateModelPrivate::get(metaType->model)->releaseIncubator(incubationTask);
+ else
+ delete incubationTask;
+ }
+
+ metaType->release();
+
+}
+
+void QQmlDelegateModelItem::Dispose()
+{
+ --scriptRef;
+ if (isReferenced())
+ return;
+
+ if (metaType->model) {
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model);
+ model->removeCacheItem(this);
+ }
+ delete this;
+}
+
+void QQmlDelegateModelItem::setModelIndex(int idx, int newRow, int newColumn)
+{
+ const int prevIndex = index;
+ const int prevRow = row;
+ const int prevColumn = column;
+
+ index = idx;
+ row = newRow;
+ column = newColumn;
+
+ if (idx != prevIndex)
+ emit modelIndexChanged();
+ if (row != prevRow)
+ emit rowChanged();
+ if (column != prevColumn)
+ emit columnChanged();
+}
+
+void QQmlDelegateModelItem::destroyObject()
+{
+ Q_ASSERT(object);
+ Q_ASSERT(contextData);
+
+ QQmlData *data = QQmlData::get(object);
+ Q_ASSERT(data);
+ if (data->ownContext) {
+ data->ownContext->clearContext();
+ if (data->ownContext->contextObject == object)
+ data->ownContext->contextObject = nullptr;
+ data->ownContext = nullptr;
+ data->context = nullptr;
+ }
+ object->deleteLater();
+
+ if (attached) {
+ attached->m_cacheItem = nullptr;
+ attached = nullptr;
+ }
+
+ contextData->invalidate();
+ contextData = nullptr;
+ object = nullptr;
+}
+
+QQmlDelegateModelItem *QQmlDelegateModelItem::dataForObject(QObject *object)
+{
+ QQmlData *d = QQmlData::get(object);
+ QQmlContextData *context = d ? d->context : nullptr;
+ for (context = context ? context->parent : nullptr; context; context = context->parent) {
+ if (QQmlDelegateModelItem *cacheItem = qobject_cast<QQmlDelegateModelItem *>(
+ context->contextObject)) {
+ return cacheItem;
+ }
+ }
+ return nullptr;
+}
+
+int QQmlDelegateModelItem::groupIndex(Compositor::Group group)
+{
+ if (QQmlDelegateModelPrivate * const model = metaType->model
+ ? QQmlDelegateModelPrivate::get(metaType->model)
+ : nullptr) {
+ return model->m_compositor.find(Compositor::Cache, model->m_cache.indexOf(this)).index[group];
+ }
+ return -1;
+}
+
+//---------------------------------------------------------------------------
+
+QQmlDelegateModelAttachedMetaObject::QQmlDelegateModelAttachedMetaObject(
+ QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject)
+ : metaType(metaType)
+ , metaObject(metaObject)
+ , memberPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount())
+ , indexPropertyOffset(QQmlDelegateModelAttached::staticMetaObject.propertyCount() + metaType->groupNames.count())
+{
+ // Don't reference count the meta-type here as that would create a circular reference.
+ // Instead we rely the fact that the meta-type's reference count can't reach 0 without first
+ // destroying all delegates with attached objects.
+ *static_cast<QMetaObject *>(this) = *metaObject;
+}
+
+QQmlDelegateModelAttachedMetaObject::~QQmlDelegateModelAttachedMetaObject()
+{
+ ::free(metaObject);
+}
+
+void QQmlDelegateModelAttachedMetaObject::objectDestroyed(QObject *)
+{
+ release();
+}
+
+int QQmlDelegateModelAttachedMetaObject::metaCall(QObject *object, QMetaObject::Call call, int _id, void **arguments)
+{
+ QQmlDelegateModelAttached *attached = static_cast<QQmlDelegateModelAttached *>(object);
+ if (call == QMetaObject::ReadProperty) {
+ if (_id >= indexPropertyOffset) {
+ Compositor::Group group = Compositor::Group(_id - indexPropertyOffset + 1);
+ *static_cast<int *>(arguments[0]) = attached->m_currentIndex[group];
+ return -1;
+ } else if (_id >= memberPropertyOffset) {
+ Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1);
+ *static_cast<bool *>(arguments[0]) = attached->m_cacheItem->groups & (1 << group);
+ return -1;
+ }
+ } else if (call == QMetaObject::WriteProperty) {
+ if (_id >= memberPropertyOffset) {
+ if (!metaType->model)
+ return -1;
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(metaType->model);
+ Compositor::Group group = Compositor::Group(_id - memberPropertyOffset + 1);
+ const int groupFlag = 1 << group;
+ const bool member = attached->m_cacheItem->groups & groupFlag;
+ if (member && !*static_cast<bool *>(arguments[0])) {
+ Compositor::iterator it = model->m_compositor.find(
+ group, attached->m_currentIndex[group]);
+ model->removeGroups(it, 1, group, groupFlag);
+ } else if (!member && *static_cast<bool *>(arguments[0])) {
+ for (int i = 1; i < metaType->groupCount; ++i) {
+ if (attached->m_cacheItem->groups & (1 << i)) {
+ Compositor::iterator it = model->m_compositor.find(
+ Compositor::Group(i), attached->m_currentIndex[i]);
+ model->addGroups(it, 1, Compositor::Group(i), groupFlag);
+ break;
+ }
+ }
+ }
+ return -1;
+ }
+ }
+ return attached->qt_metacall(call, _id, arguments);
+}
+
+QQmlDelegateModelAttached::QQmlDelegateModelAttached(QObject *parent)
+ : m_cacheItem(nullptr)
+ , m_previousGroups(0)
+{
+ QQml_setParent_noEvent(this, parent);
+}
+
+QQmlDelegateModelAttached::QQmlDelegateModelAttached(
+ QQmlDelegateModelItem *cacheItem, QObject *parent)
+ : m_cacheItem(cacheItem)
+ , m_previousGroups(cacheItem->groups)
+{
+ QQml_setParent_noEvent(this, parent);
+ resetCurrentIndex();
+ // Let m_previousIndex be equal to m_currentIndex
+ std::copy(std::begin(m_currentIndex), std::end(m_currentIndex), std::begin(m_previousIndex));
+
+ if (!cacheItem->metaType->metaObject)
+ cacheItem->metaType->initializeMetaObject();
+
+ QObjectPrivate::get(this)->metaObject = cacheItem->metaType->metaObject;
+ cacheItem->metaType->metaObject->addref();
+}
+
+void QQmlDelegateModelAttached::resetCurrentIndex()
+{
+ if (QQDMIncubationTask *incubationTask = m_cacheItem->incubationTask) {
+ for (int i = 1; i < qMin<int>(m_cacheItem->metaType->groupCount, Compositor::MaximumGroupCount); ++i)
+ m_currentIndex[i] = incubationTask->index[i];
+ } else {
+ QQmlDelegateModelPrivate * const model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model);
+ Compositor::iterator it = model->m_compositor.find(
+ Compositor::Cache, model->m_cache.indexOf(m_cacheItem));
+ for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i)
+ m_currentIndex[i] = it.index[i];
+ }
+}
+
+/*!
+ \qmlattachedproperty model QtQml.Models::DelegateModel::model
+
+ This attached property holds the data model this delegate instance belongs to.
+
+ It is attached to each instance of the delegate.
+*/
+
+QQmlDelegateModel *QQmlDelegateModelAttached::model() const
+{
+ return m_cacheItem ? m_cacheItem->metaType->model : nullptr;
+}
+
+/*!
+ \qmlattachedproperty stringlist QtQml.Models::DelegateModel::groups
+
+ This attached property holds the name of DelegateModelGroups the item belongs to.
+
+ It is attached to each instance of the delegate.
+*/
+
+QStringList QQmlDelegateModelAttached::groups() const
+{
+ QStringList groups;
+
+ if (!m_cacheItem)
+ return groups;
+ for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) {
+ if (m_cacheItem->groups & (1 << i))
+ groups.append(m_cacheItem->metaType->groupNames.at(i - 1));
+ }
+ return groups;
+}
+
+void QQmlDelegateModelAttached::setGroups(const QStringList &groups)
+{
+ if (!m_cacheItem)
+ return;
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_cacheItem->metaType->model);
+
+ const int groupFlags = model->m_cacheMetaType->parseGroups(groups);
+ const int cacheIndex = model->m_cache.indexOf(m_cacheItem);
+ Compositor::iterator it = model->m_compositor.find(Compositor::Cache, cacheIndex);
+ model->setGroups(it, 1, Compositor::Cache, groupFlags);
+}
+
+/*!
+ \qmlattachedproperty bool QtQml.Models::DelegateModel::isUnresolved
+
+ This attached property indicates whether the visual item is bound to a data model index.
+ Returns true if the item is not bound to the model, and false if it is.
+
+ An unresolved item can be bound to the data model using the DelegateModelGroup::resolve()
+ function.
+
+ It is attached to each instance of the delegate.
+*/
+
+bool QQmlDelegateModelAttached::isUnresolved() const
+{
+ if (!m_cacheItem)
+ return false;
+
+ return m_cacheItem->groups & Compositor::UnresolvedFlag;
+}
+
+/*!
+ \qmlattachedproperty int QtQml.Models::DelegateModel::inItems
+
+ This attached property holds whether the item belongs to the default \l items DelegateModelGroup.
+
+ Changing this property will add or remove the item from the items group.
+
+ It is attached to each instance of the delegate.
+*/
+
+/*!
+ \qmlattachedproperty int QtQml.Models::DelegateModel::itemsIndex
+
+ This attached property holds the index of the item in the default \l items DelegateModelGroup.
+
+ It is attached to each instance of the delegate.
+*/
+
+/*!
+ \qmlattachedproperty int QtQml.Models::DelegateModel::inPersistedItems
+
+ This attached property holds whether the item belongs to the \l persistedItems DelegateModelGroup.
+
+ Changing this property will add or remove the item from the items group. Change with caution
+ as removing an item from the persistedItems group will destroy the current instance if it is
+ not referenced by a model.
+
+ It is attached to each instance of the delegate.
+*/
+
+/*!
+ \qmlattachedproperty int QtQml.Models::DelegateModel::persistedItemsIndex
+
+ This attached property holds the index of the item in the \l persistedItems DelegateModelGroup.
+
+ It is attached to each instance of the delegate.
+*/
+
+void QQmlDelegateModelAttached::emitChanges()
+{
+ const int groupChanges = m_previousGroups ^ m_cacheItem->groups;
+ m_previousGroups = m_cacheItem->groups;
+
+ int indexChanges = 0;
+ for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) {
+ if (m_previousIndex[i] != m_currentIndex[i]) {
+ m_previousIndex[i] = m_currentIndex[i];
+ indexChanges |= (1 << i);
+ }
+ }
+
+ int notifierId = 0;
+ const QMetaObject *meta = metaObject();
+ for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) {
+ if (groupChanges & (1 << i))
+ QMetaObject::activate(this, meta, notifierId, nullptr);
+ }
+ for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) {
+ if (indexChanges & (1 << i))
+ QMetaObject::activate(this, meta, notifierId, nullptr);
+ }
+
+ if (groupChanges)
+ emit groupsChanged();
+}
+
+//============================================================================
+
+void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::Group g)
+{
+ Q_ASSERT(!model);
+ model = m;
+ group = g;
+}
+
+bool QQmlDelegateModelGroupPrivate::isChangedConnected()
+{
+ Q_Q(QQmlDelegateModelGroup);
+ IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &));
+}
+
+void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4)
+{
+ Q_Q(QQmlDelegateModelGroup);
+ if (isChangedConnected() && !changeSet.isEmpty()) {
+ emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())),
+ QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts())));
+ }
+ if (changeSet.difference() != 0)
+ emit q->countChanged();
+}
+
+void QQmlDelegateModelGroupPrivate::emitModelUpdated(bool reset)
+{
+ for (QQmlDelegateModelGroupEmitterList::iterator it = emitters.begin(); it != emitters.end(); ++it)
+ it->emitModelUpdated(changeSet, reset);
+ changeSet.clear();
+}
+
+typedef QQmlDelegateModelGroupEmitterList::iterator GroupEmitterListIt;
+
+void QQmlDelegateModelGroupPrivate::createdPackage(int index, QQuickPackage *package)
+{
+ for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it)
+ it->createdPackage(index, package);
+}
+
+void QQmlDelegateModelGroupPrivate::initPackage(int index, QQuickPackage *package)
+{
+ for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it)
+ it->initPackage(index, package);
+}
+
+void QQmlDelegateModelGroupPrivate::destroyingPackage(QQuickPackage *package)
+{
+ for (GroupEmitterListIt it = emitters.begin(), end = emitters.end(); it != end; ++it)
+ it->destroyingPackage(package);
+}
+
+/*!
+ \qmltype DelegateModelGroup
+ \instantiates QQmlDelegateModelGroup
+ \inqmlmodule QtQml.Models
+ \ingroup qtquick-models
+ \brief Encapsulates a filtered set of visual data items.
+
+ The DelegateModelGroup type provides a means to address the model data of a
+ DelegateModel's delegate items, as well as sort and filter these delegate
+ items.
+
+ The initial set of instantiable delegate items in a DelegateModel is represented
+ by its \l {QtQml.Models::DelegateModel::items}{items} group, which normally directly reflects
+ the contents of the model assigned to DelegateModel::model. This set can be changed to
+ the contents of any other member of DelegateModel::groups by assigning the \l name of that
+ DelegateModelGroup to the DelegateModel::filterOnGroup property.
+
+ The data of an item in a DelegateModelGroup can be accessed using the get() function, which returns
+ information about group membership and indexes as well as model data. In combination
+ with the move() function this can be used to implement view sorting, with remove() to filter
+ items out of a view, or with setGroups() and \l Package delegates to categorize items into
+ different views. Different groups can only be sorted independently if they are disjunct. Moving
+ an item in one group will also move it in all other groups it is a part of.
+
+ Data from models can be supplemented by inserting data directly into a DelegateModelGroup
+ with the insert() function. This can be used to introduce mock items into a view, or
+ placeholder items that are later \l {resolve()}{resolved} to real model data when it becomes
+ available.
+
+ Delegate items can also be instantiated directly from a DelegateModelGroup using the
+ create() function, making it possible to use DelegateModel without an accompanying view
+ type or to cherry-pick specific items that should be instantiated irregardless of whether
+ they're currently within a view's visible area.
+
+ \sa {QML Dynamic View Ordering Tutorial}
+*/
+QQmlDelegateModelGroup::QQmlDelegateModelGroup(QObject *parent)
+ : QObject(*new QQmlDelegateModelGroupPrivate, parent)
+{
+}
+
+QQmlDelegateModelGroup::QQmlDelegateModelGroup(
+ const QString &name, QQmlDelegateModel *model, int index, QObject *parent)
+ : QQmlDelegateModelGroup(parent)
+{
+ Q_D(QQmlDelegateModelGroup);
+ d->name = name;
+ d->setModel(model, Compositor::Group(index));
+}
+
+QQmlDelegateModelGroup::~QQmlDelegateModelGroup()
+{
+}
+
+/*!
+ \qmlproperty string QtQml.Models::DelegateModelGroup::name
+
+ This property holds the name of the group.
+
+ Each group in a model must have a unique name starting with a lower case letter.
+*/
+
+QString QQmlDelegateModelGroup::name() const
+{
+ Q_D(const QQmlDelegateModelGroup);
+ return d->name;
+}
+
+void QQmlDelegateModelGroup::setName(const QString &name)
+{
+ Q_D(QQmlDelegateModelGroup);
+ if (d->model)
+ return;
+ if (d->name != name) {
+ d->name = name;
+ emit nameChanged();
+ }
+}
+
+/*!
+ \qmlproperty int QtQml.Models::DelegateModelGroup::count
+
+ This property holds the number of items in the group.
+*/
+
+int QQmlDelegateModelGroup::count() const
+{
+ Q_D(const QQmlDelegateModelGroup);
+ if (!d->model)
+ return 0;
+ return QQmlDelegateModelPrivate::get(d->model)->m_compositor.count(d->group);
+}
+
+/*!
+ \qmlproperty bool QtQml.Models::DelegateModelGroup::includeByDefault
+
+ This property holds whether new items are assigned to this group by default.
+*/
+
+bool QQmlDelegateModelGroup::defaultInclude() const
+{
+ Q_D(const QQmlDelegateModelGroup);
+ return d->defaultInclude;
+}
+
+void QQmlDelegateModelGroup::setDefaultInclude(bool include)
+{
+ Q_D(QQmlDelegateModelGroup);
+ if (d->defaultInclude != include) {
+ d->defaultInclude = include;
+
+ if (d->model) {
+ if (include)
+ QQmlDelegateModelPrivate::get(d->model)->m_compositor.setDefaultGroup(d->group);
+ else
+ QQmlDelegateModelPrivate::get(d->model)->m_compositor.clearDefaultGroup(d->group);
+ }
+ emit defaultIncludeChanged();
+ }
+}
+
+/*!
+ \qmlmethod object QtQml.Models::DelegateModelGroup::get(int index)
+
+ Returns a javascript object describing the item at \a index in the group.
+
+ The returned object contains the same information that is available to a delegate from the
+ DelegateModel attached as well as the model for that item. It has the properties:
+
+ \list
+ \li \b model The model data of the item. This is the same as the model context property in
+ a delegate
+ \li \b groups A list the of names of groups the item is a member of. This property can be
+ written to change the item's membership.
+ \li \b inItems Whether the item belongs to the \l {QtQml.Models::DelegateModel::items}{items} group.
+ Writing to this property will add or remove the item from the group.
+ \li \b itemsIndex The index of the item within the \l {QtQml.Models::DelegateModel::items}{items} group.
+ \li \b {in<GroupName>} Whether the item belongs to the dynamic group \e groupName. Writing to
+ this property will add or remove the item from the group.
+ \li \b {<groupName>Index} The index of the item within the dynamic group \e groupName.
+ \li \b isUnresolved Whether the item is bound to an index in the model assigned to
+ DelegateModel::model. Returns true if the item is not bound to the model, and false if it is.
+ \endlist
+*/
+
+QJSValue QQmlDelegateModelGroup::get(int index)
+{
+ Q_D(QQmlDelegateModelGroup);
+ if (!d->model)
+ return QJSValue();
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+ if (!model->m_context || !model->m_context->isValid()) {
+ return QJSValue();
+ } else if (index < 0 || index >= model->m_compositor.count(d->group)) {
+ qmlWarning(this) << tr("get: index out of range");
+ return QJSValue();
+ }
+
+ Compositor::iterator it = model->m_compositor.find(d->group, index);
+ QQmlDelegateModelItem *cacheItem = it->inCache()
+ ? model->m_cache.at(it.cacheIndex)
+ : 0;
+
+ if (!cacheItem) {
+ cacheItem = model->m_adaptorModel.createItem(
+ model->m_cacheMetaType, it.modelIndex());
+ if (!cacheItem)
+ return QJSValue();
+ cacheItem->groups = it->flags;
+
+ model->m_cache.insert(it.cacheIndex, cacheItem);
+ model->m_compositor.setFlags(it, 1, Compositor::CacheFlag);
+ }
+
+ if (model->m_cacheMetaType->modelItemProto.isUndefined())
+ model->m_cacheMetaType->initializePrototype();
+ QV4::ExecutionEngine *v4 = model->m_cacheMetaType->v4Engine;
+ QV4::Scope scope(v4);
+ ++cacheItem->scriptRef;
+ QV4::ScopedObject o(scope, v4->memoryManager->allocate<QQmlDelegateModelItemObject>(cacheItem));
+ QV4::ScopedObject p(scope, model->m_cacheMetaType->modelItemProto.value());
+ o->setPrototypeOf(p);
+
+ return QJSValue(v4, o->asReturnedValue());
+}
+
+bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const
+{
+ if (value.isNumber()) {
+ *index = value.toInt32();
+ return true;
+ }
+
+ if (!value.isObject())
+ return false;
+
+ QV4::ExecutionEngine *v4 = value.as<QV4::Object>()->engine();
+ QV4::Scope scope(v4);
+ QV4::Scoped<QQmlDelegateModelItemObject> object(scope, value);
+
+ if (object) {
+ QQmlDelegateModelItem * const cacheItem = object->d()->item;
+ if (QQmlDelegateModelPrivate *model = cacheItem->metaType->model
+ ? QQmlDelegateModelPrivate::get(cacheItem->metaType->model)
+ : nullptr) {
+ *index = model->m_cache.indexOf(cacheItem);
+ *group = Compositor::Cache;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::insert(int index, jsdict data, array groups = undefined)
+ \qmlmethod QtQml.Models::DelegateModelGroup::insert(jsdict data, var groups = undefined)
+
+ Creates a new entry at \a index in a DelegateModel with the values from \a data that
+ correspond to roles in the model assigned to DelegateModel::model.
+
+ If no index is supplied the data is appended to the model.
+
+ The optional \a groups parameter identifies the groups the new entry should belong to,
+ if unspecified this is equal to the group insert was called on.
+
+ Data inserted into a DelegateModel can later be merged with an existing entry in
+ DelegateModel::model using the \l resolve() function. This can be used to create placeholder
+ items that are later replaced by actual data.
+*/
+
+void QQmlDelegateModelGroup::insert(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+
+ int index = model->m_compositor.count(d->group);
+ Compositor::Group group = d->group;
+
+ if (args->length() == 0)
+ return;
+
+ int i = 0;
+ QV4::Scope scope(args->v4engine());
+ QV4::ScopedValue v(scope, (*args)[i]);
+ if (d->parseIndex(v, &index, &group)) {
+ if (index < 0 || index > model->m_compositor.count(group)) {
+ qmlWarning(this) << tr("insert: index out of range");
+ return;
+ }
+ if (++i == args->length())
+ return;
+ v = (*args)[i];
+ }
+
+ Compositor::insert_iterator before = index < model->m_compositor.count(group)
+ ? model->m_compositor.findInsertPosition(group, index)
+ : model->m_compositor.end();
+
+ int groups = 1 << d->group;
+ if (++i < args->length()) {
+ QV4::ScopedValue val(scope, (*args)[i]);
+ groups |= model->m_cacheMetaType->parseGroups(val);
+ }
+
+ if (v->as<QV4::ArrayObject>()) {
+ return;
+ } else if (v->as<QV4::Object>()) {
+ model->insert(before, v, groups);
+ model->emitChanges();
+ }
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::create(int index)
+ \qmlmethod QtQml.Models::DelegateModelGroup::create(int index, jsdict data, array groups = undefined)
+ \qmlmethod QtQml.Models::DelegateModelGroup::create(jsdict data, array groups = undefined)
+
+ Returns a reference to the instantiated item at \a index in the group.
+
+ If a \a data object is provided it will be \l {insert}{inserted} at \a index and an item
+ referencing this new entry will be returned. The optional \a groups parameter identifies
+ the groups the new entry should belong to, if unspecified this is equal to the group create()
+ was called on.
+
+ All items returned by create are added to the
+ \l {QtQml.Models::DelegateModel::persistedItems}{persistedItems} group. Items in this
+ group remain instantiated when not referenced by any view.
+*/
+
+void QQmlDelegateModelGroup::create(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ if (!d->model)
+ return;
+
+ if (args->length() == 0)
+ return;
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+
+ int index = model->m_compositor.count(d->group);
+ Compositor::Group group = d->group;
+
+ int i = 0;
+ QV4::Scope scope(args->v4engine());
+ QV4::ScopedValue v(scope, (*args)[i]);
+ if (d->parseIndex(v, &index, &group))
+ ++i;
+
+ if (i < args->length() && index >= 0 && index <= model->m_compositor.count(group)) {
+ v = (*args)[i];
+ if (v->as<QV4::Object>()) {
+ int groups = 1 << d->group;
+ if (++i < args->length()) {
+ QV4::ScopedValue val(scope, (*args)[i]);
+ groups |= model->m_cacheMetaType->parseGroups(val);
+ }
+
+ Compositor::insert_iterator before = index < model->m_compositor.count(group)
+ ? model->m_compositor.findInsertPosition(group, index)
+ : model->m_compositor.end();
+
+ index = before.index[d->group];
+ group = d->group;
+
+ if (!model->insert(before, v, groups)) {
+ return;
+ }
+ }
+ }
+ if (index < 0 || index >= model->m_compositor.count(group)) {
+ qmlWarning(this) << tr("create: index out of range");
+ return;
+ }
+
+ QObject *object = model->object(group, index, QQmlIncubator::AsynchronousIfNested);
+ if (object) {
+ QVector<Compositor::Insert> inserts;
+ Compositor::iterator it = model->m_compositor.find(group, index);
+ model->m_compositor.setFlags(it, 1, d->group, Compositor::PersistedFlag, &inserts);
+ model->itemsInserted(inserts);
+ model->m_cache.at(it.cacheIndex)->releaseObject();
+ }
+
+ args->setReturnValue(QV4::QObjectWrapper::wrap(args->v4engine(), object));
+ model->emitChanges();
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::resolve(int from, int to)
+
+ Binds an unresolved item at \a from to an item in DelegateModel::model at index \a to.
+
+ Unresolved items are entries whose data has been \l {insert()}{inserted} into a DelegateModelGroup
+ instead of being derived from a DelegateModel::model index. Resolving an item will replace
+ the item at the target index with the unresolved item. A resolved an item will reflect the data
+ of the source model at its bound index and will move when that index moves like any other item.
+
+ If a new item is replaced in the DelegateModelGroup onChanged() handler its insertion and
+ replacement will be communicated to views as an atomic operation, creating the appearance
+ that the model contents have not changed, or if the unresolved and model item are not adjacent
+ that the previously unresolved item has simply moved.
+
+*/
+void QQmlDelegateModelGroup::resolve(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ if (!d->model)
+ return;
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+
+ if (args->length() < 2)
+ return;
+
+ int from = -1;
+ int to = -1;
+ Compositor::Group fromGroup = d->group;
+ Compositor::Group toGroup = d->group;
+
+ QV4::Scope scope(args->v4engine());
+ QV4::ScopedValue v(scope, (*args)[0]);
+ if (d->parseIndex(v, &from, &fromGroup)) {
+ if (from < 0 || from >= model->m_compositor.count(fromGroup)) {
+ qmlWarning(this) << tr("resolve: from index out of range");
+ return;
+ }
+ } else {
+ qmlWarning(this) << tr("resolve: from index invalid");
+ return;
+ }
+
+ v = (*args)[1];
+ if (d->parseIndex(v, &to, &toGroup)) {
+ if (to < 0 || to >= model->m_compositor.count(toGroup)) {
+ qmlWarning(this) << tr("resolve: to index out of range");
+ return;
+ }
+ } else {
+ qmlWarning(this) << tr("resolve: to index invalid");
+ return;
+ }
+
+ Compositor::iterator fromIt = model->m_compositor.find(fromGroup, from);
+ Compositor::iterator toIt = model->m_compositor.find(toGroup, to);
+
+ if (!fromIt->isUnresolved()) {
+ qmlWarning(this) << tr("resolve: from is not an unresolved item");
+ return;
+ }
+ if (!toIt->list) {
+ qmlWarning(this) << tr("resolve: to is not a model item");
+ return;
+ }
+
+ const int unresolvedFlags = fromIt->flags;
+ const int resolvedFlags = toIt->flags;
+ const int resolvedIndex = toIt.modelIndex();
+ void * const resolvedList = toIt->list;
+
+ QQmlDelegateModelItem *cacheItem = model->m_cache.at(fromIt.cacheIndex);
+ cacheItem->groups &= ~Compositor::UnresolvedFlag;
+
+ if (toIt.cacheIndex > fromIt.cacheIndex)
+ toIt.decrementIndexes(1, unresolvedFlags);
+ if (!toIt->inGroup(fromGroup) || toIt.index[fromGroup] > from)
+ from += 1;
+
+ model->itemsMoved(
+ QVector<Compositor::Remove>(1, Compositor::Remove(fromIt, 1, unresolvedFlags, 0)),
+ QVector<Compositor::Insert>(1, Compositor::Insert(toIt, 1, unresolvedFlags, 0)));
+ model->itemsInserted(
+ QVector<Compositor::Insert>(1, Compositor::Insert(toIt, 1, (resolvedFlags & ~unresolvedFlags) | Compositor::CacheFlag)));
+ toIt.incrementIndexes(1, resolvedFlags | unresolvedFlags);
+ model->itemsRemoved(QVector<Compositor::Remove>(1, Compositor::Remove(toIt, 1, resolvedFlags)));
+
+ model->m_compositor.setFlags(toGroup, to, 1, unresolvedFlags & ~Compositor::UnresolvedFlag);
+ model->m_compositor.clearFlags(fromGroup, from, 1, unresolvedFlags);
+
+ if (resolvedFlags & Compositor::CacheFlag)
+ model->m_compositor.insert(Compositor::Cache, toIt.cacheIndex, resolvedList, resolvedIndex, 1, Compositor::CacheFlag);
+
+ Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache));
+
+ if (!cacheItem->isReferenced()) {
+ Q_ASSERT(toIt.cacheIndex == model->m_cache.indexOf(cacheItem));
+ model->m_cache.removeAt(toIt.cacheIndex);
+ model->m_compositor.clearFlags(Compositor::Cache, toIt.cacheIndex, 1, Compositor::CacheFlag);
+ delete cacheItem;
+ Q_ASSERT(model->m_cache.count() == model->m_compositor.count(Compositor::Cache));
+ } else {
+ cacheItem->resolveIndex(model->m_adaptorModel, resolvedIndex);
+ if (cacheItem->attached)
+ cacheItem->attached->emitUnresolvedChanged();
+ }
+
+ model->emitChanges();
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::remove(int index, int count)
+
+ Removes \a count items starting at \a index from the group.
+*/
+
+void QQmlDelegateModelGroup::remove(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ if (!d->model)
+ return;
+ Compositor::Group group = d->group;
+ int index = -1;
+ int count = 1;
+
+ if (args->length() == 0)
+ return;
+
+ int i = 0;
+ QV4::Scope scope(args->v4engine());
+ QV4::ScopedValue v(scope, (*args)[0]);
+ if (!d->parseIndex(v, &index, &group)) {
+ qmlWarning(this) << tr("remove: invalid index");
+ return;
+ }
+
+ if (++i < args->length()) {
+ v = (*args)[i];
+ if (v->isNumber())
+ count = v->toInt32();
+ }
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+ if (index < 0 || index >= model->m_compositor.count(group)) {
+ qmlWarning(this) << tr("remove: index out of range");
+ } else if (count != 0) {
+ Compositor::iterator it = model->m_compositor.find(group, index);
+ if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
+ qmlWarning(this) << tr("remove: invalid count");
+ } else {
+ model->removeGroups(it, count, d->group, 1 << d->group);
+ }
+ }
+}
+
+bool QQmlDelegateModelGroupPrivate::parseGroupArgs(
+ QQmlV4Function *args, Compositor::Group *group, int *index, int *count, int *groups) const
+{
+ if (!model || !QQmlDelegateModelPrivate::get(model)->m_cacheMetaType)
+ return false;
+
+ if (args->length() < 2)
+ return false;
+
+ int i = 0;
+ QV4::Scope scope(args->v4engine());
+ QV4::ScopedValue v(scope, (*args)[i]);
+ if (!parseIndex(v, index, group))
+ return false;
+
+ v = (*args)[++i];
+ if (v->isNumber()) {
+ *count = v->toInt32();
+
+ if (++i == args->length())
+ return false;
+ v = (*args)[i];
+ }
+
+ *groups = QQmlDelegateModelPrivate::get(model)->m_cacheMetaType->parseGroups(v);
+
+ return true;
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::addGroups(int index, int count, stringlist groups)
+
+ Adds \a count items starting at \a index to \a groups.
+*/
+
+void QQmlDelegateModelGroup::addGroups(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ Compositor::Group group = d->group;
+ int index = -1;
+ int count = 1;
+ int groups = 0;
+
+ if (!d->parseGroupArgs(args, &group, &index, &count, &groups))
+ return;
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+ if (index < 0 || index >= model->m_compositor.count(group)) {
+ qmlWarning(this) << tr("addGroups: index out of range");
+ } else if (count != 0) {
+ Compositor::iterator it = model->m_compositor.find(group, index);
+ if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
+ qmlWarning(this) << tr("addGroups: invalid count");
+ } else {
+ model->addGroups(it, count, d->group, groups);
+ }
+ }
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::removeGroups(int index, int count, stringlist groups)
+
+ Removes \a count items starting at \a index from \a groups.
+*/
+
+void QQmlDelegateModelGroup::removeGroups(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ Compositor::Group group = d->group;
+ int index = -1;
+ int count = 1;
+ int groups = 0;
+
+ if (!d->parseGroupArgs(args, &group, &index, &count, &groups))
+ return;
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+ if (index < 0 || index >= model->m_compositor.count(group)) {
+ qmlWarning(this) << tr("removeGroups: index out of range");
+ } else if (count != 0) {
+ Compositor::iterator it = model->m_compositor.find(group, index);
+ if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
+ qmlWarning(this) << tr("removeGroups: invalid count");
+ } else {
+ model->removeGroups(it, count, d->group, groups);
+ }
+ }
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups)
+
+ Sets the \a groups \a count items starting at \a index belong to.
+*/
+
+void QQmlDelegateModelGroup::setGroups(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+ Compositor::Group group = d->group;
+ int index = -1;
+ int count = 1;
+ int groups = 0;
+
+ if (!d->parseGroupArgs(args, &group, &index, &count, &groups))
+ return;
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+ if (index < 0 || index >= model->m_compositor.count(group)) {
+ qmlWarning(this) << tr("setGroups: index out of range");
+ } else if (count != 0) {
+ Compositor::iterator it = model->m_compositor.find(group, index);
+ if (count < 0 || count > model->m_compositor.count(d->group) - it.index[d->group]) {
+ qmlWarning(this) << tr("setGroups: invalid count");
+ } else {
+ model->setGroups(it, count, d->group, groups);
+ }
+ }
+}
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::setGroups(int index, int count, stringlist groups)
+
+ Sets the \a groups \a count items starting at \a index belong to.
+*/
+
+/*!
+ \qmlmethod QtQml.Models::DelegateModelGroup::move(var from, var to, int count)
+
+ Moves \a count at \a from in a group \a to a new position.
+
+ \note The DelegateModel acts as a proxy model: it holds the delegates in a
+ different order than the \l{dm-model-property}{underlying model} has them.
+ Any subsequent changes to the underlying model will not undo whatever
+ reordering you have done via this function.
+*/
+
+void QQmlDelegateModelGroup::move(QQmlV4Function *args)
+{
+ Q_D(QQmlDelegateModelGroup);
+
+ if (args->length() < 2)
+ return;
+
+ Compositor::Group fromGroup = d->group;
+ Compositor::Group toGroup = d->group;
+ int from = -1;
+ int to = -1;
+ int count = 1;
+
+ QV4::Scope scope(args->v4engine());
+ QV4::ScopedValue v(scope, (*args)[0]);
+ if (!d->parseIndex(v, &from, &fromGroup)) {
+ qmlWarning(this) << tr("move: invalid from index");
+ return;
+ }
+
+ v = (*args)[1];
+ if (!d->parseIndex(v, &to, &toGroup)) {
+ qmlWarning(this) << tr("move: invalid to index");
+ return;
+ }
+
+ if (args->length() > 2) {
+ v = (*args)[2];
+ if (v->isNumber())
+ count = v->toInt32();
+ }
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model);
+
+ if (count < 0) {
+ qmlWarning(this) << tr("move: invalid count");
+ } else if (from < 0 || from + count > model->m_compositor.count(fromGroup)) {
+ qmlWarning(this) << tr("move: from index out of range");
+ } else if (!model->m_compositor.verifyMoveTo(fromGroup, from, toGroup, to, count, d->group)) {
+ qmlWarning(this) << tr("move: to index out of range");
+ } else if (count > 0) {
+ QVector<Compositor::Remove> removes;
+ QVector<Compositor::Insert> inserts;
+
+ model->m_compositor.move(fromGroup, from, toGroup, to, count, d->group, &removes, &inserts);
+ model->itemsMoved(removes, inserts);
+ model->emitChanges();
+ }
+
+}
+
+/*!
+ \qmlsignal QtQml.Models::DelegateModelGroup::changed(array removed, array inserted)
+
+ This signal is emitted when items have been removed from or inserted into the group.
+
+ Each object in the \a removed and \a inserted arrays has two values; the \e index of the first
+ item inserted or removed and a \e count of the number of consecutive items inserted or removed.
+
+ Each index is adjusted for previous changes with all removed items preceding any inserted
+ items.
+
+ The corresponding handler is \c onChanged.
+*/
+
+//============================================================================
+
+QQmlPartsModel::QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent)
+ : QQmlInstanceModel(*new QObjectPrivate, parent)
+ , m_model(model)
+ , m_part(part)
+ , m_compositorGroup(Compositor::Cache)
+ , m_inheritGroup(true)
+{
+ QQmlDelegateModelPrivate *d = QQmlDelegateModelPrivate::get(m_model);
+ if (d->m_cacheMetaType) {
+ QQmlDelegateModelGroupPrivate::get(d->m_groups[1])->emitters.insert(this);
+ m_compositorGroup = Compositor::Default;
+ } else {
+ d->m_pendingParts.insert(this);
+ }
+}
+
+QQmlPartsModel::~QQmlPartsModel()
+{
+}
+
+QString QQmlPartsModel::filterGroup() const
+{
+ if (m_inheritGroup)
+ return m_model->filterGroup();
+ return m_filterGroup;
+}
+
+void QQmlPartsModel::setFilterGroup(const QString &group)
+{
+ if (QQmlDelegateModelPrivate::get(m_model)->m_transaction) {
+ qmlWarning(this) << tr("The group of a DelegateModel cannot be changed within onChanged");
+ return;
+ }
+
+ if (m_filterGroup != group || m_inheritGroup) {
+ m_filterGroup = group;
+ m_inheritGroup = false;
+ updateFilterGroup();
+
+ emit filterGroupChanged();
+ }
+}
+
+void QQmlPartsModel::resetFilterGroup()
+{
+ if (!m_inheritGroup) {
+ m_inheritGroup = true;
+ updateFilterGroup();
+ emit filterGroupChanged();
+ }
+}
+
+void QQmlPartsModel::updateFilterGroup()
+{
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ if (!model->m_cacheMetaType)
+ return;
+
+ if (m_inheritGroup) {
+ if (m_filterGroup == model->m_filterGroup)
+ return;
+ m_filterGroup = model->m_filterGroup;
+ }
+
+ QQmlListCompositor::Group previousGroup = m_compositorGroup;
+ m_compositorGroup = Compositor::Default;
+ QQmlDelegateModelGroupPrivate::get(model->m_groups[Compositor::Default])->emitters.insert(this);
+ for (int i = 1; i < model->m_groupCount; ++i) {
+ if (m_filterGroup == model->m_cacheMetaType->groupNames.at(i - 1)) {
+ m_compositorGroup = Compositor::Group(i);
+ break;
+ }
+ }
+
+ QQmlDelegateModelGroupPrivate::get(model->m_groups[m_compositorGroup])->emitters.insert(this);
+ if (m_compositorGroup != previousGroup) {
+ QVector<QQmlChangeSet::Change> removes;
+ QVector<QQmlChangeSet::Change> inserts;
+ model->m_compositor.transition(previousGroup, m_compositorGroup, &removes, &inserts);
+
+ QQmlChangeSet changeSet;
+ changeSet.move(removes, inserts);
+ if (!changeSet.isEmpty())
+ emit modelUpdated(changeSet, false);
+
+ if (changeSet.difference() != 0)
+ emit countChanged();
+ }
+}
+
+void QQmlPartsModel::updateFilterGroup(
+ Compositor::Group group, const QQmlChangeSet &changeSet)
+{
+ if (!m_inheritGroup)
+ return;
+
+ m_compositorGroup = group;
+ QQmlDelegateModelGroupPrivate::get(QQmlDelegateModelPrivate::get(m_model)->m_groups[m_compositorGroup])->emitters.insert(this);
+
+ if (!changeSet.isEmpty())
+ emit modelUpdated(changeSet, false);
+
+ if (changeSet.difference() != 0)
+ emit countChanged();
+
+ emit filterGroupChanged();
+}
+
+int QQmlPartsModel::count() const
+{
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ return model->m_delegate
+ ? model->m_compositor.count(m_compositorGroup)
+ : 0;
+}
+
+bool QQmlPartsModel::isValid() const
+{
+ return m_model->isValid();
+}
+
+QObject *QQmlPartsModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
+{
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+
+ if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup)) {
+ qWarning() << "DelegateModel::item: index out range" << index << model->m_compositor.count(m_compositorGroup);
+ return nullptr;
+ }
+
+ QObject *object = model->object(m_compositorGroup, index, incubationMode);
+
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object)) {
+ QObject *part = package->part(m_part);
+ if (!part)
+ return nullptr;
+ m_packaged.insertMulti(part, package);
+ return part;
+ }
+
+ model->release(object);
+ if (!model->m_delegateValidated) {
+ if (object)
+ qmlWarning(model->m_delegate) << tr("Delegate component must be Package type.");
+ model->m_delegateValidated = true;
+ }
+
+ return nullptr;
+}
+
+QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item)
+{
+ QQmlInstanceModel::ReleaseFlags flags = nullptr;
+
+ QHash<QObject *, QQuickPackage *>::iterator it = m_packaged.find(item);
+ if (it != m_packaged.end()) {
+ QQuickPackage *package = *it;
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ flags = model->release(package);
+ m_packaged.erase(it);
+ if (!m_packaged.contains(item))
+ flags &= ~Referenced;
+ if (flags & Destroyed)
+ QQmlDelegateModelPrivate::get(m_model)->emitDestroyingPackage(package);
+ }
+ return flags;
+}
+
+QVariant QQmlPartsModel::variantValue(int index, const QString &role)
+{
+ return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role);
+}
+
+void QQmlPartsModel::setWatchedRoles(const QList<QByteArray> &roles)
+{
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ model->m_adaptorModel.replaceWatchedRoles(m_watchedRoles, roles);
+ m_watchedRoles = roles;
+}
+
+QQmlIncubator::Status QQmlPartsModel::incubationStatus(int index)
+{
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ Compositor::iterator it = model->m_compositor.find(model->m_compositorGroup, index);
+ if (!it->inCache())
+ return QQmlIncubator::Null;
+
+ if (auto incubationTask = model->m_cache.at(it.cacheIndex)->incubationTask)
+ return incubationTask->status();
+
+ return QQmlIncubator::Ready;
+}
+
+int QQmlPartsModel::indexOf(QObject *item, QObject *) const
+{
+ QHash<QObject *, QQuickPackage *>::const_iterator it = m_packaged.find(item);
+ if (it != m_packaged.end()) {
+ if (QQmlDelegateModelItem *cacheItem = QQmlDelegateModelItem::dataForObject(*it))
+ return cacheItem->groupIndex(m_compositorGroup);
+ }
+ return -1;
+}
+
+void QQmlPartsModel::createdPackage(int index, QQuickPackage *package)
+{
+ emit createdItem(index, package->part(m_part));
+}
+
+void QQmlPartsModel::initPackage(int index, QQuickPackage *package)
+{
+ if (m_modelUpdatePending)
+ m_pendingPackageInitializations << index;
+ else
+ emit initItem(index, package->part(m_part));
+}
+
+void QQmlPartsModel::destroyingPackage(QQuickPackage *package)
+{
+ QObject *item = package->part(m_part);
+ Q_ASSERT(!m_packaged.contains(item));
+ emit destroyingItem(item);
+}
+
+void QQmlPartsModel::emitModelUpdated(const QQmlChangeSet &changeSet, bool reset)
+{
+ m_modelUpdatePending = false;
+ emit modelUpdated(changeSet, reset);
+ if (changeSet.difference() != 0)
+ emit countChanged();
+
+ QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(m_model);
+ QVector<int> pendingPackageInitializations;
+ qSwap(pendingPackageInitializations, m_pendingPackageInitializations);
+ for (int index : pendingPackageInitializations) {
+ if (!model->m_delegate || index < 0 || index >= model->m_compositor.count(m_compositorGroup))
+ continue;
+ QObject *object = model->object(m_compositorGroup, index, QQmlIncubator::Asynchronous);
+ if (QQuickPackage *package = qmlobject_cast<QQuickPackage *>(object))
+ emit initItem(index, package->part(m_part));
+ model->release(object);
+ }
+}
+
+//============================================================================
+
+struct QQmlDelegateModelGroupChange : QV4::Object
+{
+ V4_OBJECT2(QQmlDelegateModelGroupChange, QV4::Object)
+
+ static QV4::Heap::QQmlDelegateModelGroupChange *create(QV4::ExecutionEngine *e) {
+ return e->memoryManager->allocate<QQmlDelegateModelGroupChange>();
+ }
+
+ static QV4::ReturnedValue method_get_index(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
+ if (!that)
+ THROW_TYPE_ERROR();
+ return QV4::Encode(that->d()->change.index);
+ }
+ static QV4::ReturnedValue method_get_count(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
+ if (!that)
+ THROW_TYPE_ERROR();
+ return QV4::Encode(that->d()->change.count);
+ }
+ static QV4::ReturnedValue method_get_moveId(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) {
+ QV4::Scope scope(b);
+ QV4::Scoped<QQmlDelegateModelGroupChange> that(scope, thisObject->as<QQmlDelegateModelGroupChange>());
+ if (!that)
+ THROW_TYPE_ERROR();
+ if (that->d()->change.moveId < 0)
+ RETURN_UNDEFINED();
+ return QV4::Encode(that->d()->change.moveId);
+ }
+};
+
+DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChange);
+
+struct QQmlDelegateModelGroupChangeArray : public QV4::Object
+{
+ V4_OBJECT2(QQmlDelegateModelGroupChangeArray, QV4::Object)
+ V4_NEEDS_DESTROY
+public:
+ static QV4::Heap::QQmlDelegateModelGroupChangeArray *create(QV4::ExecutionEngine *engine, const QVector<QQmlChangeSet::Change> &changes)
+ {
+ return engine->memoryManager->allocate<QQmlDelegateModelGroupChangeArray>(changes);
+ }
+
+ quint32 count() const { return d()->changes->count(); }
+ const QQmlChangeSet::Change &at(int index) const { return d()->changes->at(index); }
+
+ static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty)
+ {
+ if (id.isArrayIndex()) {
+ uint index = id.asArrayIndex();
+ Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
+ QV4::ExecutionEngine *v4 = static_cast<const QQmlDelegateModelGroupChangeArray *>(m)->engine();
+ QV4::Scope scope(v4);
+ QV4::Scoped<QQmlDelegateModelGroupChangeArray> array(scope, static_cast<const QQmlDelegateModelGroupChangeArray *>(m));
+
+ if (index >= array->count()) {
+ if (hasProperty)
+ *hasProperty = false;
+ return QV4::Value::undefinedValue().asReturnedValue();
+ }
+
+ const QQmlChangeSet::Change &change = array->at(index);
+
+ QV4::ScopedObject changeProto(scope, engineData(v4)->changeProto.value());
+ QV4::Scoped<QQmlDelegateModelGroupChange> object(scope, QQmlDelegateModelGroupChange::create(v4));
+ object->setPrototypeOf(changeProto);
+ object->d()->change = change;
+
+ if (hasProperty)
+ *hasProperty = true;
+ return object.asReturnedValue();
+ }
+
+ Q_ASSERT(m->as<QQmlDelegateModelGroupChangeArray>());
+ const QQmlDelegateModelGroupChangeArray *array = static_cast<const QQmlDelegateModelGroupChangeArray *>(m);
+
+ if (id == array->engine()->id_length()->propertyKey()) {
+ if (hasProperty)
+ *hasProperty = true;
+ return QV4::Encode(array->count());
+ }
+
+ return Object::virtualGet(m, id, receiver, hasProperty);
+ }
+};
+
+void QV4::Heap::QQmlDelegateModelGroupChangeArray::init(const QVector<QQmlChangeSet::Change> &changes)
+{
+ Object::init();
+ this->changes = new QVector<QQmlChangeSet::Change>(changes);
+ QV4::Scope scope(internalClass->engine);
+ QV4::ScopedObject o(scope, this);
+ o->setArrayType(QV4::Heap::ArrayData::Custom);
+}
+
+DEFINE_OBJECT_VTABLE(QQmlDelegateModelGroupChangeArray);
+
+QQmlDelegateModelEngineData::QQmlDelegateModelEngineData(QV4::ExecutionEngine *v4)
+{
+ QV4::Scope scope(v4);
+
+ QV4::ScopedObject proto(scope, v4->newObject());
+ proto->defineAccessorProperty(QStringLiteral("index"), QQmlDelegateModelGroupChange::method_get_index, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("count"), QQmlDelegateModelGroupChange::method_get_count, nullptr);
+ proto->defineAccessorProperty(QStringLiteral("moveId"), QQmlDelegateModelGroupChange::method_get_moveId, nullptr);
+ changeProto.set(v4, proto);
+}
+
+QQmlDelegateModelEngineData::~QQmlDelegateModelEngineData()
+{
+}
+
+QV4::ReturnedValue QQmlDelegateModelEngineData::array(QV4::ExecutionEngine *v4,
+ const QVector<QQmlChangeSet::Change> &changes)
+{
+ QV4::Scope scope(v4);
+ QV4::ScopedObject o(scope, QQmlDelegateModelGroupChangeArray::create(v4, changes));
+ return o.asReturnedValue();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqmldelegatemodel_p.cpp"
diff --git a/src/qmlmodels/qqmldelegatemodel_p.h b/src/qmlmodels/qqmldelegatemodel_p.h
new file mode 100644
index 0000000000..9e846ccc3e
--- /dev/null
+++ b/src/qmlmodels/qqmldelegatemodel_p.h
@@ -0,0 +1,247 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLDATAMODEL_P_H
+#define QQMLDATAMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlmodelsglobal_p.h>
+#include <private/qqmllistcompositor_p.h>
+#include <private/qqmlobjectmodel_p.h>
+#include <private/qqmlincubator_p.h>
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qstringlist.h>
+
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
+QT_BEGIN_NAMESPACE
+
+class QQmlChangeSet;
+class QQuickPackage;
+class QQmlV4Function;
+class QQmlDelegateModelGroup;
+class QQmlDelegateModelAttached;
+class QQmlDelegateModelPrivate;
+
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateModel : public QQmlInstanceModel, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QQmlDelegateModel)
+
+ Q_PROPERTY(QVariant model READ model WRITE setModel)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup)
+ Q_PROPERTY(QQmlDelegateModelGroup *items READ items CONSTANT) //TODO : worth renaming?
+ Q_PROPERTY(QQmlDelegateModelGroup *persistedItems READ persistedItems CONSTANT)
+ Q_PROPERTY(QQmlListProperty<QQmlDelegateModelGroup> groups READ groups CONSTANT)
+ Q_PROPERTY(QObject *parts READ parts CONSTANT)
+ Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged)
+ Q_CLASSINFO("DefaultProperty", "delegate")
+ Q_INTERFACES(QQmlParserStatus)
+public:
+ QQmlDelegateModel();
+ QQmlDelegateModel(QQmlContext *, QObject *parent=nullptr);
+ ~QQmlDelegateModel();
+
+ void classBegin() override;
+ void componentComplete() override;
+
+ QVariant model() const;
+ void setModel(const QVariant &);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *);
+
+ QVariant rootIndex() const;
+ void setRootIndex(const QVariant &root);
+
+ Q_INVOKABLE QVariant modelIndex(int idx) const;
+ Q_INVOKABLE QVariant parentModelIndex() const;
+
+ int count() const override;
+ bool isValid() const override { return delegate() != nullptr; }
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
+ ReleaseFlags release(QObject *object) override;
+ void cancel(int index) override;
+ QVariant variantValue(int index, const QString &role) override;
+ void setWatchedRoles(const QList<QByteArray> &roles) override;
+ QQmlIncubator::Status incubationStatus(int index) override;
+
+ int indexOf(QObject *object, QObject *objectContext) const override;
+
+ QString filterGroup() const;
+ void setFilterGroup(const QString &group);
+ void resetFilterGroup();
+
+ QQmlDelegateModelGroup *items();
+ QQmlDelegateModelGroup *persistedItems();
+ QQmlListProperty<QQmlDelegateModelGroup> groups();
+ QObject *parts();
+
+ const QAbstractItemModel *abstractItemModel() const override;
+
+ bool event(QEvent *) override;
+
+ static QQmlDelegateModelAttached *qmlAttachedProperties(QObject *obj);
+
+Q_SIGNALS:
+ void filterGroupChanged();
+ void defaultGroupsChanged();
+ void rootIndexChanged();
+ void delegateChanged();
+
+private Q_SLOTS:
+ void _q_itemsChanged(int index, int count, const QVector<int> &roles);
+ void _q_itemsInserted(int index, int count);
+ void _q_itemsRemoved(int index, int count);
+ void _q_itemsMoved(int from, int to, int count);
+ void _q_modelReset();
+ void _q_rowsInserted(const QModelIndex &,int,int);
+ void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
+ void _q_rowsRemoved(const QModelIndex &,int,int);
+ void _q_rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int);
+ void _q_dataChanged(const QModelIndex&,const QModelIndex&,const QVector<int> &);
+ void _q_layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint);
+
+private:
+ bool isDescendantOf(const QPersistentModelIndex &desc, const QList<QPersistentModelIndex> &parents) const;
+
+ Q_DISABLE_COPY(QQmlDelegateModel)
+};
+
+class QQmlDelegateModelGroupPrivate;
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateModelGroup : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+ Q_PROPERTY(bool includeByDefault READ defaultInclude WRITE setDefaultInclude NOTIFY defaultIncludeChanged)
+public:
+ QQmlDelegateModelGroup(QObject *parent = nullptr);
+ QQmlDelegateModelGroup(const QString &name, QQmlDelegateModel *model, int compositorType, QObject *parent = nullptr);
+ ~QQmlDelegateModelGroup();
+
+ QString name() const;
+ void setName(const QString &name);
+
+ int count() const;
+
+ bool defaultInclude() const;
+ void setDefaultInclude(bool include);
+
+ Q_INVOKABLE QJSValue get(int index);
+
+public Q_SLOTS:
+ void insert(QQmlV4Function *);
+ void create(QQmlV4Function *);
+ void resolve(QQmlV4Function *);
+ void remove(QQmlV4Function *);
+ void addGroups(QQmlV4Function *);
+ void removeGroups(QQmlV4Function *);
+ void setGroups(QQmlV4Function *);
+ void move(QQmlV4Function *);
+
+Q_SIGNALS:
+ void countChanged();
+ void nameChanged();
+ void defaultIncludeChanged();
+ void changed(const QJSValue &removed, const QJSValue &inserted);
+private:
+ Q_DECLARE_PRIVATE(QQmlDelegateModelGroup)
+};
+
+class QQmlDelegateModelItem;
+class QQmlDelegateModelAttachedMetaObject;
+class QQmlDelegateModelAttached : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlDelegateModel *model READ model CONSTANT)
+ Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged)
+ Q_PROPERTY(bool isUnresolved READ isUnresolved NOTIFY unresolvedChanged)
+public:
+ QQmlDelegateModelAttached(QObject *parent);
+ QQmlDelegateModelAttached(QQmlDelegateModelItem *cacheItem, QObject *parent);
+ ~QQmlDelegateModelAttached() {}
+
+ void resetCurrentIndex();
+ void setCacheItem(QQmlDelegateModelItem *item);
+
+ QQmlDelegateModel *model() const;
+
+ QStringList groups() const;
+ void setGroups(const QStringList &groups);
+
+ bool isUnresolved() const;
+
+ void emitChanges();
+
+ void emitUnresolvedChanged() { Q_EMIT unresolvedChanged(); }
+
+Q_SIGNALS:
+ void groupsChanged();
+ void unresolvedChanged();
+
+public:
+ QQmlDelegateModelItem *m_cacheItem;
+ int m_previousGroups;
+ int m_currentIndex[QQmlListCompositor::MaximumGroupCount];
+ int m_previousIndex[QQmlListCompositor::MaximumGroupCount];
+
+ friend class QQmlDelegateModelAttachedMetaObject;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlDelegateModel)
+QML_DECLARE_TYPEINFO(QQmlDelegateModel, QML_HAS_ATTACHED_PROPERTIES)
+QML_DECLARE_TYPE(QQmlDelegateModelGroup)
+
+#endif // QQMLDATAMODEL_P_H
diff --git a/src/qmlmodels/qqmldelegatemodel_p_p.h b/src/qmlmodels/qqmldelegatemodel_p_p.h
new file mode 100644
index 0000000000..92362b8876
--- /dev/null
+++ b/src/qmlmodels/qqmldelegatemodel_p_p.h
@@ -0,0 +1,450 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLDATAMODEL_P_P_H
+#define QQMLDATAMODEL_P_P_H
+
+#include "qqmldelegatemodel_p.h"
+#include <private/qv4qobjectwrapper_p.h>
+
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlincubator.h>
+
+#include <private/qqmladaptormodel_p.h>
+#include <private/qqmlopenmetaobject_p.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
+QT_BEGIN_NAMESPACE
+
+typedef QQmlListCompositor Compositor;
+
+class QQmlDelegateModelAttachedMetaObject;
+class QQmlAbstractDelegateComponent;
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlDelegateModelItemMetaType : public QQmlRefCount
+{
+public:
+ QQmlDelegateModelItemMetaType(QV4::ExecutionEngine *engine, QQmlDelegateModel *model, const QStringList &groupNames);
+ ~QQmlDelegateModelItemMetaType();
+
+ void initializeMetaObject();
+ void initializePrototype();
+
+ int parseGroups(const QStringList &groupNames) const;
+ int parseGroups(const QV4::Value &groupNames) const;
+
+ QPointer<QQmlDelegateModel> model;
+ const int groupCount;
+ QV4::ExecutionEngine * const v4Engine;
+ QQmlDelegateModelAttachedMetaObject *metaObject;
+ const QStringList groupNames;
+ QV4::PersistentValue modelItemProto;
+};
+
+class QQmlAdaptorModel;
+class QQDMIncubationTask;
+
+class QQmlDelegateModelItem : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int index READ modelIndex NOTIFY modelIndexChanged)
+ Q_PROPERTY(int row READ modelRow NOTIFY rowChanged REVISION 12)
+ Q_PROPERTY(int column READ modelColumn NOTIFY columnChanged REVISION 12)
+ Q_PROPERTY(QObject *model READ modelObject CONSTANT)
+public:
+ QQmlDelegateModelItem(QQmlDelegateModelItemMetaType *metaType,
+ QQmlAdaptorModel::Accessors *accessor, int modelIndex,
+ int row, int column);
+ ~QQmlDelegateModelItem();
+
+ void referenceObject() { ++objectRef; }
+ bool releaseObject() { return --objectRef == 0 && !(groups & Compositor::PersistedFlag); }
+ bool isObjectReferenced() const { return objectRef != 0 || (groups & Compositor::PersistedFlag); }
+ void childContextObjectDestroyed(QObject *childContextObject);
+
+ bool isReferenced() const {
+ return scriptRef
+ || incubationTask
+ || ((groups & Compositor::UnresolvedFlag) && (groups & Compositor::GroupMask));
+ }
+
+ void Dispose();
+
+ QObject *modelObject() { return this; }
+
+ void destroyObject();
+
+ static QQmlDelegateModelItem *dataForObject(QObject *object);
+
+ int groupIndex(Compositor::Group group);
+
+ int modelRow() const { return row; }
+ int modelColumn() const { return column; }
+ int modelIndex() const { return index; }
+ virtual void setModelIndex(int idx, int newRow, int newColumn);
+
+ virtual QV4::ReturnedValue get() { return QV4::QObjectWrapper::wrap(v4, this); }
+
+ virtual void setValue(const QString &role, const QVariant &value) { Q_UNUSED(role); Q_UNUSED(value); }
+ virtual bool resolveIndex(const QQmlAdaptorModel &, int) { return false; }
+
+ static QV4::ReturnedValue get_model(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+ static QV4::ReturnedValue get_groups(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+ static QV4::ReturnedValue set_groups(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
+ static QV4::ReturnedValue get_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &);
+ static QV4::ReturnedValue set_member(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &arg);
+ static QV4::ReturnedValue get_index(QQmlDelegateModelItem *thisItem, uint flag, const QV4::Value &arg);
+
+ QV4::ExecutionEngine *v4;
+ QQmlDelegateModelItemMetaType * const metaType;
+ QQmlContextDataRef contextData;
+ QPointer<QObject> object;
+ QPointer<QQmlDelegateModelAttached> attached;
+ QQDMIncubationTask *incubationTask;
+ QQmlComponent *delegate;
+ int poolTime;
+ int objectRef;
+ int scriptRef;
+ int groups;
+ int index;
+
+Q_SIGNALS:
+ void modelIndexChanged();
+ Q_REVISION(12) void rowChanged();
+ Q_REVISION(12) void columnChanged();
+
+protected:
+ void objectDestroyed(QObject *);
+ int row;
+ int column;
+};
+
+namespace QV4 {
+namespace Heap {
+struct QQmlDelegateModelItemObject : Object {
+ inline void init(QQmlDelegateModelItem *item);
+ void destroy();
+ QQmlDelegateModelItem *item;
+};
+
+}
+}
+
+struct QQmlDelegateModelItemObject : QV4::Object
+{
+ V4_OBJECT2(QQmlDelegateModelItemObject, QV4::Object)
+ V4_NEEDS_DESTROY
+};
+
+void QV4::Heap::QQmlDelegateModelItemObject::init(QQmlDelegateModelItem *item)
+{
+ Object::init();
+ this->item = item;
+}
+
+
+
+class QQmlDelegateModelPrivate;
+class QQDMIncubationTask : public QQmlIncubator
+{
+public:
+ QQDMIncubationTask(QQmlDelegateModelPrivate *l, IncubationMode mode)
+ : QQmlIncubator(mode)
+ , incubating(nullptr)
+ , vdm(l) {}
+
+ void statusChanged(Status) override;
+ void setInitialState(QObject *) override;
+
+ QQmlDelegateModelItem *incubating = nullptr;
+ QQmlDelegateModelPrivate *vdm = nullptr;
+ int index[QQmlListCompositor::MaximumGroupCount];
+};
+
+
+class QQmlDelegateModelGroupEmitter
+{
+public:
+ virtual ~QQmlDelegateModelGroupEmitter() {}
+ virtual void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) = 0;
+ virtual void createdPackage(int, QQuickPackage *) {}
+ virtual void initPackage(int, QQuickPackage *) {}
+ virtual void destroyingPackage(QQuickPackage *) {}
+
+ QIntrusiveListNode emitterNode;
+};
+
+typedef QIntrusiveList<QQmlDelegateModelGroupEmitter, &QQmlDelegateModelGroupEmitter::emitterNode> QQmlDelegateModelGroupEmitterList;
+
+class QQmlDelegateModelGroupPrivate : public QObjectPrivate
+{
+public:
+ Q_DECLARE_PUBLIC(QQmlDelegateModelGroup)
+
+ QQmlDelegateModelGroupPrivate() : group(Compositor::Cache), defaultInclude(false) {}
+
+ static QQmlDelegateModelGroupPrivate *get(QQmlDelegateModelGroup *group) {
+ return static_cast<QQmlDelegateModelGroupPrivate *>(QObjectPrivate::get(group)); }
+
+ void setModel(QQmlDelegateModel *model, Compositor::Group group);
+ bool isChangedConnected();
+ void emitChanges(QV4::ExecutionEngine *engine);
+ void emitModelUpdated(bool reset);
+
+ void createdPackage(int index, QQuickPackage *package);
+ void initPackage(int index, QQuickPackage *package);
+ void destroyingPackage(QQuickPackage *package);
+
+ bool parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const;
+ bool parseGroupArgs(
+ QQmlV4Function *args, Compositor::Group *group, int *index, int *count, int *groups) const;
+
+ Compositor::Group group;
+ QPointer<QQmlDelegateModel> model;
+ QQmlDelegateModelGroupEmitterList emitters;
+ QQmlChangeSet changeSet;
+ QString name;
+ bool defaultInclude;
+};
+
+class QQmlDelegateModelParts;
+
+class QQmlDelegateModelPrivate : public QObjectPrivate, public QQmlDelegateModelGroupEmitter
+{
+ Q_DECLARE_PUBLIC(QQmlDelegateModel)
+public:
+ QQmlDelegateModelPrivate(QQmlContext *);
+ ~QQmlDelegateModelPrivate();
+
+ static QQmlDelegateModelPrivate *get(QQmlDelegateModel *m) {
+ return static_cast<QQmlDelegateModelPrivate *>(QObjectPrivate::get(m));
+ }
+
+ void init();
+ void connectModel(QQmlAdaptorModel *model);
+ void connectToAbstractItemModel();
+ void disconnectFromAbstractItemModel();
+
+ void requestMoreIfNecessary();
+ QObject *object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode);
+ QQmlDelegateModel::ReleaseFlags release(QObject *object);
+ QVariant variantValue(Compositor::Group group, int index, const QString &name);
+ void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package);
+ void emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package);
+ void emitCreatedItem(QQDMIncubationTask *incubationTask, QObject *item) {
+ Q_EMIT q_func()->createdItem(incubationTask->index[m_compositorGroup], item); }
+ void emitInitItem(QQDMIncubationTask *incubationTask, QObject *item) {
+ Q_EMIT q_func()->initItem(incubationTask->index[m_compositorGroup], item); }
+ void emitDestroyingPackage(QQuickPackage *package);
+ void emitDestroyingItem(QObject *item) { Q_EMIT q_func()->destroyingItem(item); }
+ void addCacheItem(QQmlDelegateModelItem *item, Compositor::iterator it);
+ void removeCacheItem(QQmlDelegateModelItem *cacheItem);
+
+ void updateFilterGroup();
+
+ void addGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags);
+ void removeGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags);
+ void setGroups(Compositor::iterator from, int count, Compositor::Group group, int groupFlags);
+
+ void itemsInserted(
+ const QVector<Compositor::Insert> &inserts,
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedInserts,
+ QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = nullptr);
+ void itemsInserted(const QVector<Compositor::Insert> &inserts);
+ void itemsRemoved(
+ const QVector<Compositor::Remove> &removes,
+ QVarLengthArray<QVector<QQmlChangeSet::Change>, Compositor::MaximumGroupCount> *translatedRemoves,
+ QHash<int, QList<QQmlDelegateModelItem *> > *movedItems = nullptr);
+ void itemsRemoved(const QVector<Compositor::Remove> &removes);
+ void itemsMoved(
+ const QVector<Compositor::Remove> &removes, const QVector<Compositor::Insert> &inserts);
+ void itemsChanged(const QVector<Compositor::Change> &changes);
+ void emitChanges();
+ void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) override;
+ void delegateChanged(bool add = true, bool remove = true);
+
+ bool insert(Compositor::insert_iterator &before, const QV4::Value &object, int groups);
+
+ int adaptorModelCount() const;
+
+ static void group_append(QQmlListProperty<QQmlDelegateModelGroup> *property, QQmlDelegateModelGroup *group);
+ static int group_count(QQmlListProperty<QQmlDelegateModelGroup> *property);
+ static QQmlDelegateModelGroup *group_at(QQmlListProperty<QQmlDelegateModelGroup> *property, int index);
+
+ void releaseIncubator(QQDMIncubationTask *incubationTask);
+ void incubatorStatusChanged(QQDMIncubationTask *incubationTask, QQmlIncubator::Status status);
+ void setInitialState(QQDMIncubationTask *incubationTask, QObject *o);
+
+ QQmlAdaptorModel m_adaptorModel;
+ QQmlListCompositor m_compositor;
+ QQmlStrongJSQObjectReference<QQmlComponent> m_delegate;
+ QQmlAbstractDelegateComponent *m_delegateChooser;
+ QMetaObject::Connection m_delegateChooserChanged;
+ QQmlDelegateModelItemMetaType *m_cacheMetaType;
+ QPointer<QQmlContext> m_context;
+ QQmlDelegateModelParts *m_parts;
+ QQmlDelegateModelGroupEmitterList m_pendingParts;
+
+ QList<QQmlDelegateModelItem *> m_cache;
+ QList<QQDMIncubationTask *> m_finishedIncubating;
+ QList<QByteArray> m_watchedRoles;
+
+ QString m_filterGroup;
+
+ int m_count;
+ int m_groupCount;
+
+ QQmlListCompositor::Group m_compositorGroup;
+ bool m_complete : 1;
+ bool m_delegateValidated : 1;
+ bool m_reset : 1;
+ bool m_transaction : 1;
+ bool m_incubatorCleanupScheduled : 1;
+ bool m_waitingToFetchMore : 1;
+
+ union {
+ struct {
+ QQmlDelegateModelGroup *m_cacheItems;
+ QQmlDelegateModelGroup *m_items;
+ QQmlDelegateModelGroup *m_persistedItems;
+ };
+ QQmlDelegateModelGroup *m_groups[Compositor::MaximumGroupCount];
+ };
+};
+
+class QQmlPartsModel : public QQmlInstanceModel, public QQmlDelegateModelGroupEmitter
+{
+ Q_OBJECT
+ Q_PROPERTY(QString filterOnGroup READ filterGroup WRITE setFilterGroup NOTIFY filterGroupChanged RESET resetFilterGroup)
+public:
+ QQmlPartsModel(QQmlDelegateModel *model, const QString &part, QObject *parent = nullptr);
+ ~QQmlPartsModel();
+
+ QString filterGroup() const;
+ void setFilterGroup(const QString &group);
+ void resetFilterGroup();
+ void updateFilterGroup();
+ void updateFilterGroup(Compositor::Group group, const QQmlChangeSet &changeSet);
+
+ int count() const override;
+ bool isValid() const override;
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
+ ReleaseFlags release(QObject *item) override;
+ QVariant variantValue(int index, const QString &role) override;
+ QList<QByteArray> watchedRoles() const { return m_watchedRoles; }
+ void setWatchedRoles(const QList<QByteArray> &roles) override;
+ QQmlIncubator::Status incubationStatus(int index) override;
+
+ int indexOf(QObject *item, QObject *objectContext) const override;
+
+ void emitModelUpdated(const QQmlChangeSet &changeSet, bool reset) override;
+
+ void createdPackage(int index, QQuickPackage *package) override;
+ void initPackage(int index, QQuickPackage *package) override;
+ void destroyingPackage(QQuickPackage *package) override;
+
+Q_SIGNALS:
+ void filterGroupChanged();
+
+private:
+ QQmlDelegateModel *m_model;
+ QHash<QObject *, QQuickPackage *> m_packaged;
+ QString m_part;
+ QString m_filterGroup;
+ QList<QByteArray> m_watchedRoles;
+ QVector<int> m_pendingPackageInitializations; // vector holds model indices
+ Compositor::Group m_compositorGroup;
+ bool m_inheritGroup;
+ bool m_modelUpdatePending = true;
+};
+
+class QMetaPropertyBuilder;
+
+class QQmlDelegateModelPartsMetaObject : public QQmlOpenMetaObject
+{
+public:
+ QQmlDelegateModelPartsMetaObject(QObject *parent)
+ : QQmlOpenMetaObject(parent) {}
+
+ void propertyCreated(int, QMetaPropertyBuilder &) override;
+ QVariant initialValue(int) override;
+};
+
+class QQmlDelegateModelParts : public QObject
+{
+Q_OBJECT
+public:
+ QQmlDelegateModelParts(QQmlDelegateModel *parent);
+
+ QQmlDelegateModel *model;
+ QList<QQmlPartsModel *> models;
+};
+
+class QQmlDelegateModelAttachedMetaObject : public QAbstractDynamicMetaObject, public QQmlRefCount
+{
+public:
+ QQmlDelegateModelAttachedMetaObject(
+ QQmlDelegateModelItemMetaType *metaType, QMetaObject *metaObject);
+ ~QQmlDelegateModelAttachedMetaObject();
+
+ void objectDestroyed(QObject *) override;
+ int metaCall(QObject *, QMetaObject::Call, int _id, void **) override;
+
+private:
+ QQmlDelegateModelItemMetaType * const metaType;
+ QMetaObject * const metaObject;
+ const int memberPropertyOffset;
+ const int indexPropertyOffset;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qmlmodels/qqmlinstantiator.cpp b/src/qmlmodels/qqmlinstantiator.cpp
new file mode 100644
index 0000000000..f9d5762f6e
--- /dev/null
+++ b/src/qmlmodels/qqmlinstantiator.cpp
@@ -0,0 +1,512 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Research In Motion.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmlinstantiator_p.h"
+#include "qqmlinstantiator_p_p.h"
+#include <QtQml/QQmlContext>
+#include <QtQml/QQmlComponent>
+#include <QtQml/QQmlInfo>
+#include <QtQml/QQmlError>
+#include <QtQmlModels/private/qqmlobjectmodel_p.h>
+#if QT_CONFIG(qml_delegate_model)
+#include <QtQmlModels/private/qqmldelegatemodel_p.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+QQmlInstantiatorPrivate::QQmlInstantiatorPrivate()
+ : componentComplete(true)
+ , effectiveReset(false)
+ , active(true)
+ , async(false)
+#if QT_CONFIG(qml_delegate_model)
+ , ownModel(false)
+#endif
+ , requestedIndex(-1)
+ , model(QVariant(1))
+ , instanceModel(nullptr)
+ , delegate(nullptr)
+{
+}
+
+QQmlInstantiatorPrivate::~QQmlInstantiatorPrivate()
+{
+ qDeleteAll(objects);
+}
+
+void QQmlInstantiatorPrivate::clear()
+{
+ Q_Q(QQmlInstantiator);
+ if (!instanceModel)
+ return;
+ if (!objects.count())
+ return;
+
+ for (int i=0; i < objects.count(); i++) {
+ q->objectRemoved(i, objects[i]);
+ instanceModel->release(objects[i]);
+ }
+ objects.clear();
+ q->objectChanged();
+}
+
+QObject *QQmlInstantiatorPrivate::modelObject(int index, bool async)
+{
+ requestedIndex = index;
+ QObject *o = instanceModel->object(index, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
+ requestedIndex = -1;
+ return o;
+}
+
+
+void QQmlInstantiatorPrivate::regenerate()
+{
+ Q_Q(QQmlInstantiator);
+ if (!componentComplete)
+ return;
+
+ int prevCount = q->count();
+
+ clear();
+
+ if (!active || !instanceModel || !instanceModel->count() || !instanceModel->isValid()) {
+ if (prevCount)
+ q->countChanged();
+ return;
+ }
+
+ for (int i = 0; i < instanceModel->count(); i++) {
+ QObject *object = modelObject(i, async);
+ // If the item was already created we won't get a createdItem
+ if (object)
+ _q_createdItem(i, object);
+ }
+ if (q->count() != prevCount)
+ q->countChanged();
+}
+
+void QQmlInstantiatorPrivate::_q_createdItem(int idx, QObject* item)
+{
+ Q_Q(QQmlInstantiator);
+ if (objects.contains(item)) //Case when it was created synchronously in regenerate
+ return;
+ if (requestedIndex != idx) // Asynchronous creation, reference the object
+ (void)instanceModel->object(idx);
+ item->setParent(q);
+ if (objects.size() < idx + 1) {
+ int modelCount = instanceModel->count();
+ if (objects.capacity() < modelCount)
+ objects.reserve(modelCount);
+ objects.resize(idx + 1);
+ }
+ if (QObject *o = objects.at(idx))
+ instanceModel->release(o);
+ objects.replace(idx, item);
+ if (objects.count() == 1)
+ q->objectChanged();
+ q->objectAdded(idx, item);
+}
+
+void QQmlInstantiatorPrivate::_q_modelUpdated(const QQmlChangeSet &changeSet, bool reset)
+{
+ Q_Q(QQmlInstantiator);
+
+ if (!componentComplete || effectiveReset)
+ return;
+
+ if (reset) {
+ regenerate();
+ if (changeSet.difference() != 0)
+ q->countChanged();
+ return;
+ }
+
+ int difference = 0;
+ QHash<int, QVector<QPointer<QObject> > > moved;
+ const QVector<QQmlChangeSet::Change> &removes = changeSet.removes();
+ for (const QQmlChangeSet::Change &remove : removes) {
+ int index = qMin(remove.index, objects.count());
+ int count = qMin(remove.index + remove.count, objects.count()) - index;
+ if (remove.isMove()) {
+ moved.insert(remove.moveId, objects.mid(index, count));
+ objects.erase(
+ objects.begin() + index,
+ objects.begin() + index + count);
+ } else while (count--) {
+ QObject *obj = objects.at(index);
+ objects.remove(index);
+ q->objectRemoved(index, obj);
+ if (obj)
+ instanceModel->release(obj);
+ }
+
+ difference -= remove.count;
+ }
+
+ const QVector<QQmlChangeSet::Change> &inserts = changeSet.inserts();
+ for (const QQmlChangeSet::Change &insert : inserts) {
+ int index = qMin(insert.index, objects.count());
+ if (insert.isMove()) {
+ QVector<QPointer<QObject> > movedObjects = moved.value(insert.moveId);
+ objects = objects.mid(0, index) + movedObjects + objects.mid(index);
+ } else {
+ if (insert.index <= objects.size())
+ objects.insert(insert.index, insert.count, nullptr);
+ for (int i = 0; i < insert.count; ++i) {
+ int modelIndex = index + i;
+ QObject* obj = modelObject(modelIndex, async);
+ if (obj)
+ _q_createdItem(modelIndex, obj);
+ }
+ }
+ difference += insert.count;
+ }
+
+ if (difference != 0)
+ q->countChanged();
+}
+
+#if QT_CONFIG(qml_delegate_model)
+void QQmlInstantiatorPrivate::makeModel()
+{
+ Q_Q(QQmlInstantiator);
+ QQmlDelegateModel* delegateModel = new QQmlDelegateModel(qmlContext(q), q);
+ instanceModel = delegateModel;
+ ownModel = true;
+ delegateModel->setDelegate(delegate);
+ delegateModel->classBegin(); //Pretend it was made in QML
+ if (componentComplete)
+ delegateModel->componentComplete();
+}
+#endif
+
+
+/*!
+ \qmltype Instantiator
+ \instantiates QQmlInstantiator
+ \inqmlmodule QtQml.Models
+ \ingroup qtquick-models
+ \brief Dynamically creates objects.
+
+ A Instantiator can be used to control the dynamic creation of objects, or to dynamically
+ create multiple objects from a template.
+
+ The Instantiator element will manage the objects it creates. Those objects are parented to the
+ Instantiator and can also be deleted by the Instantiator if the Instantiator's properties change. Objects
+ can also be destroyed dynamically through other means, and the Instantiator will not recreate
+ them unless the properties of the Instantiator change.
+
+ \note Instantiator is part of QtQml.Models since version 2.14 and part of QtQml since
+ version 2.1. Importing Instantiator via QtQml is deprecated since Qt 5.14.
+*/
+QQmlInstantiator::QQmlInstantiator(QObject *parent)
+ : QObject(*(new QQmlInstantiatorPrivate), parent)
+{
+}
+
+QQmlInstantiator::~QQmlInstantiator()
+{
+}
+
+/*!
+ \qmlsignal QtQml::Instantiator::objectAdded(int index, QtObject object)
+
+ This signal is emitted when an object is added to the Instantiator. The \a index
+ parameter holds the index which the object has been given, and the \a object
+ parameter holds the \l QtObject that has been added.
+
+ The corresponding handler is \c onObjectAdded.
+*/
+
+/*!
+ \qmlsignal QtQml::Instantiator::objectRemoved(int index, QtObject object)
+
+ This signal is emitted when an object is removed from the Instantiator. The \a index
+ parameter holds the index which the object had been given, and the \a object
+ parameter holds the \l QtObject that has been removed.
+
+ Do not keep a reference to \a object if it was created by this Instantiator, as
+ in these cases it will be deleted shortly after the signal is handled.
+
+ The corresponding handler is \c onObjectRemoved.
+*/
+/*!
+ \qmlproperty bool QtQml::Instantiator::active
+
+ When active is true, and the delegate component is ready, the Instantiator will
+ create objects according to the model. When active is false, no objects
+ will be created and any previously created objects will be destroyed.
+
+ Default is true.
+*/
+bool QQmlInstantiator::isActive() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->active;
+}
+
+void QQmlInstantiator::setActive(bool newVal)
+{
+ Q_D(QQmlInstantiator);
+ if (newVal == d->active)
+ return;
+ d->active = newVal;
+ emit activeChanged();
+ d->regenerate();
+}
+
+/*!
+ \qmlproperty bool QtQml::Instantiator::asynchronous
+
+ When asynchronous is true the Instantiator will attempt to create objects
+ asynchronously. This means that objects may not be available immediately,
+ even if active is set to true.
+
+ You can use the objectAdded signal to respond to items being created.
+
+ Default is false.
+*/
+bool QQmlInstantiator::isAsync() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->async;
+}
+
+void QQmlInstantiator::setAsync(bool newVal)
+{
+ Q_D(QQmlInstantiator);
+ if (newVal == d->async)
+ return;
+ d->async = newVal;
+ emit asynchronousChanged();
+}
+
+
+/*!
+ \qmlproperty int QtQml::Instantiator::count
+
+ The number of objects the Instantiator is currently managing.
+*/
+
+int QQmlInstantiator::count() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->objects.count();
+}
+
+/*!
+ \qmlproperty QtQml::Component QtQml::Instantiator::delegate
+ \default
+
+ The component used to create all objects.
+
+ Note that an extra variable, index, will be available inside instances of the
+ delegate. This variable refers to the index of the instance inside the Instantiator,
+ and can be used to obtain the object through the objectAt method of the Instantiator.
+
+ If this property is changed, all instances using the old delegate will be destroyed
+ and new instances will be created using the new delegate.
+*/
+QQmlComponent* QQmlInstantiator::delegate()
+{
+ Q_D(QQmlInstantiator);
+ return d->delegate;
+}
+
+void QQmlInstantiator::setDelegate(QQmlComponent* c)
+{
+ Q_D(QQmlInstantiator);
+ if (c == d->delegate)
+ return;
+
+ d->delegate = c;
+ emit delegateChanged();
+
+#if QT_CONFIG(qml_delegate_model)
+ if (!d->ownModel)
+ return;
+
+ if (QQmlDelegateModel *dModel = qobject_cast<QQmlDelegateModel*>(d->instanceModel))
+ dModel->setDelegate(c);
+ if (d->componentComplete)
+ d->regenerate();
+#endif
+}
+
+/*!
+ \qmlproperty variant QtQml::Instantiator::model
+
+ This property can be set to any of the supported \l {qml-data-models}{data models}:
+
+ \list
+ \li A number that indicates the number of delegates to be created by the repeater
+ \li A model (e.g. a ListModel item, or a QAbstractItemModel subclass)
+ \li A string list
+ \li An object list
+ \endlist
+
+ The type of model affects the properties that are exposed to the \l delegate.
+
+ Default value is 1, which creates a single delegate instance.
+
+ \sa {qml-data-models}{Data Models}
+*/
+
+QVariant QQmlInstantiator::model() const
+{
+ Q_D(const QQmlInstantiator);
+ return d->model;
+}
+
+void QQmlInstantiator::setModel(const QVariant &v)
+{
+ Q_D(QQmlInstantiator);
+ if (d->model == v)
+ return;
+
+ d->model = v;
+ //Don't actually set model until componentComplete in case it wants to create its delegates immediately
+ if (!d->componentComplete)
+ return;
+
+ QQmlInstanceModel *prevModel = d->instanceModel;
+ QObject *object = qvariant_cast<QObject*>(v);
+ QQmlInstanceModel *vim = nullptr;
+ if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
+#if QT_CONFIG(qml_delegate_model)
+ if (d->ownModel) {
+ delete d->instanceModel;
+ prevModel = nullptr;
+ d->ownModel = false;
+ }
+#endif
+ d->instanceModel = vim;
+#if QT_CONFIG(qml_delegate_model)
+ } else if (v != QVariant(0)){
+ if (!d->ownModel)
+ d->makeModel();
+
+ if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel *>(d->instanceModel)) {
+ d->effectiveReset = true;
+ dataModel->setModel(v);
+ d->effectiveReset = false;
+ }
+#endif
+ }
+
+ if (d->instanceModel != prevModel) {
+ if (prevModel) {
+ disconnect(prevModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
+ this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
+ disconnect(prevModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*)));
+ //disconnect(prevModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
+ }
+
+ if (d->instanceModel) {
+ connect(d->instanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
+ this, SLOT(_q_modelUpdated(QQmlChangeSet,bool)));
+ connect(d->instanceModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(_q_createdItem(int,QObject*)));
+ //connect(d->instanceModel, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*)));
+ }
+ }
+
+ d->regenerate();
+ emit modelChanged();
+}
+
+/*!
+ \qmlproperty QtObject QtQml::Instantiator::object
+
+ This is a reference to the first created object, intended as a convenience
+ for the case where only one object has been created.
+*/
+QObject *QQmlInstantiator::object() const
+{
+ Q_D(const QQmlInstantiator);
+ if (d->objects.count())
+ return d->objects[0];
+ return nullptr;
+}
+
+/*!
+ \qmlmethod QtObject QtQml::Instantiator::objectAt(int index)
+
+ Returns a reference to the object with the given \a index.
+*/
+QObject *QQmlInstantiator::objectAt(int index) const
+{
+ Q_D(const QQmlInstantiator);
+ if (index >= 0 && index < d->objects.count())
+ return d->objects[index];
+ return nullptr;
+}
+
+/*!
+ \internal
+*/
+void QQmlInstantiator::classBegin()
+{
+ Q_D(QQmlInstantiator);
+ d->componentComplete = false;
+}
+
+/*!
+ \internal
+*/
+void QQmlInstantiator::componentComplete()
+{
+ Q_D(QQmlInstantiator);
+ d->componentComplete = true;
+#if QT_CONFIG(qml_delegate_model)
+ if (d->ownModel) {
+ static_cast<QQmlDelegateModel*>(d->instanceModel)->componentComplete();
+ d->regenerate();
+ } else
+#endif
+ {
+ QVariant realModel = d->model;
+ d->model = QVariant(0);
+ setModel(realModel); //If realModel == d->model this won't do anything, but that's fine since the model's 0
+ //setModel calls regenerate
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqmlinstantiator_p.cpp"
diff --git a/src/qmlmodels/qqmlinstantiator_p.h b/src/qmlmodels/qqmlinstantiator_p.h
new file mode 100644
index 0000000000..87accc304f
--- /dev/null
+++ b/src/qmlmodels/qqmlinstantiator_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Research In Motion.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLINSTANTIATOR_P_H
+#define QQMLINSTANTIATOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlparserstatus.h>
+#include <QtQmlModels/private/qtqmlmodelsglobal_p.h>
+
+QT_REQUIRE_CONFIG(qml_object_model);
+
+QT_BEGIN_NAMESPACE
+
+class QQmlInstantiatorPrivate;
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstantiator : public QObject, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+
+ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
+ Q_PROPERTY(bool asynchronous READ isAsync WRITE setAsync NOTIFY asynchronousChanged)
+ Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_PROPERTY(QObject *object READ object NOTIFY objectChanged)
+ Q_CLASSINFO("DefaultProperty", "delegate")
+
+public:
+ QQmlInstantiator(QObject *parent = nullptr);
+ ~QQmlInstantiator();
+
+ bool isActive() const;
+ void setActive(bool newVal);
+
+ bool isAsync() const;
+ void setAsync(bool newVal);
+
+ int count() const;
+
+ QQmlComponent* delegate();
+ void setDelegate(QQmlComponent* c);
+
+ QVariant model() const;
+ void setModel(const QVariant &v);
+
+ QObject *object() const;
+
+ Q_INVOKABLE QObject *objectAt(int index) const;
+
+ void classBegin() override;
+ void componentComplete() override;
+
+Q_SIGNALS:
+ void modelChanged();
+ void delegateChanged();
+ void countChanged();
+ void objectChanged();
+ void activeChanged();
+ void asynchronousChanged();
+
+ void objectAdded(int index, QObject* object);
+ void objectRemoved(int index, QObject* object);
+
+private:
+ Q_DISABLE_COPY(QQmlInstantiator)
+ Q_DECLARE_PRIVATE(QQmlInstantiator)
+ Q_PRIVATE_SLOT(d_func(), void _q_createdItem(int, QObject *))
+ Q_PRIVATE_SLOT(d_func(), void _q_modelUpdated(const QQmlChangeSet &, bool))
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLCREATOR_P_H
diff --git a/src/qmlmodels/qqmlinstantiator_p_p.h b/src/qmlmodels/qqmlinstantiator_p_p.h
new file mode 100644
index 0000000000..33cc2613e5
--- /dev/null
+++ b/src/qmlmodels/qqmlinstantiator_p_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Research In Motion.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLINSTANTIATOR_P_P_H
+#define QQMLINSTANTIATOR_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmlinstantiator_p.h"
+#include <QObject>
+#include <private/qobject_p.h>
+#include <private/qqmlchangeset_p.h>
+#include <private/qqmlobjectmodel_p.h>
+
+QT_REQUIRE_CONFIG(qml_object_model);
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstantiatorPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QQmlInstantiator)
+
+public:
+ QQmlInstantiatorPrivate();
+ ~QQmlInstantiatorPrivate();
+
+ void clear();
+ void regenerate();
+#if QT_CONFIG(qml_delegate_model)
+ void makeModel();
+#endif
+ void _q_createdItem(int, QObject *);
+ void _q_modelUpdated(const QQmlChangeSet &, bool);
+ QObject *modelObject(int index, bool async);
+
+ static QQmlInstantiatorPrivate *get(QQmlInstantiator *instantiator) { return instantiator->d_func(); }
+ static const QQmlInstantiatorPrivate *get(const QQmlInstantiator *instantiator) { return instantiator->d_func(); }
+
+ bool componentComplete:1;
+ bool effectiveReset:1;
+ bool active:1;
+ bool async:1;
+#if QT_CONFIG(qml_delegate_model)
+ bool ownModel:1;
+#endif
+ int requestedIndex;
+ QVariant model;
+ QQmlInstanceModel *instanceModel;
+ QQmlComponent *delegate;
+ QVector<QPointer<QObject> > objects;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLCREATOR_P_P_H
diff --git a/src/qmlmodels/qqmlitemmodels.qdoc b/src/qmlmodels/qqmlitemmodels.qdoc
new file mode 100644
index 0000000000..2e12dbf656
--- /dev/null
+++ b/src/qmlmodels/qqmlitemmodels.qdoc
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \page qmodelindex-and-related-classes-in-qml.html
+ \title QModelIndex and related Classes in QML
+
+ Since Qt 5.5, QModelIndex and QPersistentModelIndex are exposed in QML as
+ value-based types. Also exposed in a similar fashion are QModelIndexList,
+ QItemSelectionRange and QItemSelection. All objects from these types can
+ be passed back and forth between QML and C++ as \c var properties or plain
+ JavaScript variables.
+
+ Below you will find an overview of the API exposed to QML for these classes.
+ For more information, refer to their C++ documentation.
+
+ \note Since all these types are exposed as \l{Q_GADGET}{gadgets}, there are no property
+ change notification signals emitted. Therefore binding to their properties
+ may not give the expected results. This is especially true for QPersistentModelIndex.
+
+ \section1 QModelIndex and QPersistentModelIndex Types
+
+ \list
+ \li \b row : int
+ \li \b column : int
+ \li \b parent : QModelIndex
+ \li \b valid : bool
+ \li \b model : QAbstractItemModel
+ \li \b internalId : quint64
+ \endlist
+
+ All these properties are read-only, as are their C++ counterparts.
+
+ \note The usual caveats apply to QModelIndex in QML. If the underlying model changes
+ or gets deleted, it may become dangerous to access its properties. Therefore, you
+ should not store any QModelIndex objects. You can, however, store QPersistentModelIndexe
+ objects in a safe way.
+
+ \section1 QModelIndexList Type
+
+ QModelIndexList is exposed in QML as a JavaScript array. Conversions are
+ automatically made from and to C++. In fact, any JavaScript array can be
+ converted back to QModelIndexList, with non-QModelIndex objects replaced by
+ invalid \l{QModelIndex}es.
+
+ \note QModelIndex to QPersistentModelIndex conversion happens when accessing
+ the array elements because any QModelIndexList property retains reference
+ semantics when exposed this way.
+
+ \section1 \l QItemSelectionRange Type
+
+ \list
+ \li \b top : int
+ \li \b left : int
+ \li \b bottom : int
+ \li \b right : int
+ \li \b width : int
+ \li \b height : int
+ \li \b topLeft : QPersistentModelIndex
+ \li \b bottomRight : QPersistentModelIndex
+ \li \b parent : QModelIndex
+ \li \b valid : bool
+ \li \b empty : bool
+ \li \b model : QAbstractItemModel
+ \endlist
+
+ All these properties are read-only, as are their C++ counterparts. In addition,
+ we also expose the following functions:
+
+ \list
+ \li bool \b{contains}(QModelIndex \e index)
+ \li bool \b{contains}(int \e row, int \e column, QModelIndex \e parentIndex)
+ \li bool \b{intersects}(QItemSelectionRange \e other)
+ \li QItemSelectionRange \b{intersected}(QItemSelectionRange \e other)
+ \endlist
+
+ \section1 QItemSelection Type
+
+ Similarly to QModelIndexList, \l QItemSelection is exposed in QML as a JavaScript
+ array of QItemSelectionRange objects. Conversions are automatically made from and to C++.
+ In fact, any JavaScript array can be converted back to QItemSelection, with
+ non-QItemSelectionRange objects replaced by empty \l {QItemSelectionRange}s.
+
+
+ \sa ItemSelectionModel
+*/
diff --git a/src/qmlmodels/qqmlitemselectionmodel.qdoc b/src/qmlmodels/qqmlitemselectionmodel.qdoc
new file mode 100644
index 0000000000..43da4f7a55
--- /dev/null
+++ b/src/qmlmodels/qqmlitemselectionmodel.qdoc
@@ -0,0 +1,239 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \qmltype ItemSelectionModel
+ \instantiates QItemSelectionModel
+ \inqmlmodule QtQml.Models
+ \since 5.5
+ \ingroup qtquick-models
+
+ \brief Instantiates a QItemSelectionModel to be used in conjunction
+ with a QAbstractItemModel and any view supporting it.
+
+ \sa QItemSelectionModel, {Models and Views in Qt Quick}
+*/
+
+
+/*!
+ \qmlproperty QAbstractItemModel ItemSelectionModel::model
+
+ This property's value must match the view's model.
+*/
+
+/*!
+ \qmlproperty bool ItemSelectionModel::hasSelection
+ \readonly
+
+ It will trigger property binding updates every time \l selectionChanged()
+ is emitted, even though its value hasn't changed.
+
+ \sa selection, selectedIndexes, select(), selectionChanged()
+*/
+
+/*!
+ \qmlproperty QModelIndex ItemSelectionModel::currentIndex
+ \readonly
+
+ Use \l setCurrentIndex() to set its value.
+
+ \sa setCurrentIndex(), currentChanged()
+*/
+
+/*!
+ \qmlproperty QModelIndexList ItemSelectionModel::selectedIndexes
+ \readonly
+
+ Contains the list of all the indexes in the selection model.
+*/
+
+/*!
+ \qmlmethod bool ItemSelectionModel::isSelected(QModelIndex index)
+
+ Returns \c true if the given model item \a index is selected.
+*/
+
+/*!
+ \qmlmethod bool ItemSelectionModel::isRowSelected(int row, QModelIndex parent)
+
+ Returns \c true if all items are selected in the \a row with the given
+ \a parent.
+
+ Note that this function is usually faster than calling isSelected()
+ on all items in the same row, and that unselectable items are ignored.
+*/
+
+/*!
+ \qmlmethod bool ItemSelectionModel::isColumnSelected(int column, QModelIndex parent)
+
+ Returns \c true if all items are selected in the \a column with the given
+ \a parent.
+
+ Note that this function is usually faster than calling isSelected()
+ on all items in the same column, and that unselectable items are ignored.
+*/
+
+/*!
+ \qmlmethod bool ItemSelectionModel::rowIntersectsSelection(int row, QModelIndex parent)
+
+ Returns \c true if there are any items selected in the \a row with the
+ given \a parent.
+*/
+
+/*!
+ \qmlmethod bool ItemSelectionModel::columnIntersectsSelection(int column, QModelIndex parent)
+
+ Returns \c true if there are any items selected in the \a column with the
+ given \a parent.
+*/
+
+/*!
+ \qmlmethod QModelIndexList ItemSelectionModel::selectedRows(int column)
+
+ Returns the indexes in the given \a column for the rows where all columns
+ are selected.
+
+ \sa selectedColumns()
+*/
+
+/*!
+ \qmlmethod QModelIndexList ItemSelectionModel::selectedColumns(int row)
+
+ Returns the indexes in the given \a row for columns where all rows are
+ selected.
+
+ \sa selectedRows()
+*/
+
+/*!
+ \qmlproperty object ItemSelectionModel::selection
+ \readonly
+
+ Holds the selection ranges stored in the selection model.
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::setCurrentIndex(QModelIndex index, SelectionFlags command)
+
+ Sets the model item \a index to be the current item, and emits
+ currentChanged(). The current item is used for keyboard navigation and
+ focus indication; it is independent of any selected items, although a
+ selected item can also be the current item.
+
+ Depending on the specified \a command, the \a index can also become part
+ of the current selection.
+
+ Valid \a command values are described in \l {itemselectionmodelselectindex}
+ {select(\e index, \e command)}.
+
+ \sa select()
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::select(QModelIndex index, SelectionFlags command)
+ \keyword itemselectionmodelselectindex
+
+ Selects the model item \a index using the specified \a command, and emits
+ selectionChanged().
+
+ Valid values for the \a command parameter, are:
+
+ \value NoUpdate No selection will be made.
+ \value Clear The complete selection will be cleared.
+ \value Select All specified indexes will be selected.
+ \value Deselect All specified indexes will be deselected.
+ \value Toggle All specified indexes will be selected or
+ deselected depending on their current state.
+ \value Current The current selection will be updated.
+ \value Rows All indexes will be expanded to span rows.
+ \value Columns All indexes will be expanded to span columns.
+ \value SelectCurrent A combination of Select and Current, provided for
+ convenience.
+ \value ToggleCurrent A combination of Toggle and Current, provided for
+ convenience.
+ \value ClearAndSelect A combination of Clear and Select, provided for
+ convenience.
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::select(QItemSelection selection, SelectionFlags command)
+
+ Selects the item \a selection using the specified \a command, and emits
+ selectionChanged().
+
+ Valid \a command values are described in \l {itemselectionmodelselectindex}
+ {select(\e index, \e command)}.
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::clear()
+
+ Clears the selection model. Emits selectionChanged() and currentChanged().
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::reset()
+
+ Clears the selection model. Does not emit any signals.
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::clearSelection()
+
+ Clears the selection in the selection model. Emits selectionChanged().
+*/
+
+/*!
+ \qmlmethod void ItemSelectionModel::clearCurrentIndex()
+
+ Clears the current index. Emits currentChanged().
+*/
+
+/*!
+ \qmlsignal ItemSelectionModel::selectionChanged(QItemSelection selected, QItemSelection deselected)
+
+ This signal is emitted whenever the selection changes. The change in the
+ selection is represented as an item selection of \a deselected items and
+ an item selection of \a selected items.
+
+ Note the that the current index changes independently from the selection.
+ Also note that this signal will not be emitted when the item model is reset.
+
+ \sa select(), currentChanged()
+*/
+
+/*!
+ \qmlsignal ItemSelectionModel::currentChanged(QModelIndex current, QModelIndex previous)
+
+ This signal is emitted whenever the current item changes. The \a previous
+ model item index is replaced by the \a current index as the selection's
+ current item.
+
+ Note that this signal will not be emitted when the item model is reset.
+
+ \sa currentIndex, setCurrentIndex(), selectionChanged()
+*/
diff --git a/src/qmlmodels/qqmllistaccessor.cpp b/src/qmlmodels/qqmllistaccessor.cpp
new file mode 100644
index 0000000000..46a11e2bc2
--- /dev/null
+++ b/src/qmlmodels/qqmllistaccessor.cpp
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmllistaccessor_p.h"
+
+#include <private/qqmlmetatype_p.h>
+
+#include <QtCore/qstringlist.h>
+#include <QtCore/qdebug.h>
+
+// ### Remove me
+#include <private/qqmlengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlListAccessor::QQmlListAccessor()
+: m_type(Invalid)
+{
+}
+
+QQmlListAccessor::~QQmlListAccessor()
+{
+}
+
+QVariant QQmlListAccessor::list() const
+{
+ return d;
+}
+
+void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine)
+{
+ d = v;
+
+ // An incoming JS array as model is treated as a variant list, so we need to
+ // convert it first with toVariant().
+ if (d.userType() == qMetaTypeId<QJSValue>())
+ d = d.value<QJSValue>().toVariant();
+
+ QQmlEnginePrivate *enginePrivate = engine?QQmlEnginePrivate::get(engine):nullptr;
+
+ if (!d.isValid()) {
+ m_type = Invalid;
+ } else if (d.userType() == QVariant::StringList) {
+ m_type = StringList;
+ } else if (d.userType() == QMetaType::QVariantList) {
+ m_type = VariantList;
+ } else if (d.canConvert(QVariant::Int)) {
+ // Here we have to check for an upper limit, because down the line code might (well, will)
+ // allocate memory depending on the number of elements. The upper limit cannot be INT_MAX:
+ // QVector<QPointer<QQuickItem>> something;
+ // something.resize(count());
+ // (See e.g. QQuickRepeater::regenerate())
+ // This will allocate data along the lines of:
+ // sizeof(QPointer<QQuickItem>) * count() + QVector::headerSize
+ // So, doing an approximate round-down-to-nice-number, we get:
+ const int upperLimit = 100 * 1000 * 1000;
+
+ int i = v.toInt();
+ if (i < 0) {
+ qWarning("Model size of %d is less than 0", i);
+ m_type = Invalid;
+ } else if (i > upperLimit) {
+ qWarning("Model size of %d is bigger than the upper limit %d", i, upperLimit);
+ m_type = Invalid;
+ } else {
+ m_type = Integer;
+ }
+ } else if ((!enginePrivate && QQmlMetaType::isQObject(d.userType())) ||
+ (enginePrivate && enginePrivate->isQObject(d.userType()))) {
+ QObject *data = enginePrivate?enginePrivate->toQObject(d):QQmlMetaType::toQObject(d);
+ d = QVariant::fromValue(data);
+ m_type = Instance;
+ } else if (d.userType() == qMetaTypeId<QQmlListReference>()) {
+ m_type = ListProperty;
+ } else {
+ m_type = Instance;
+ }
+}
+
+int QQmlListAccessor::count() const
+{
+ switch(m_type) {
+ case StringList:
+ return qvariant_cast<QStringList>(d).count();
+ case VariantList:
+ return qvariant_cast<QVariantList>(d).count();
+ case ListProperty:
+ return ((const QQmlListReference *)d.constData())->count();
+ case Instance:
+ return 1;
+ case Integer:
+ return d.toInt();
+ default:
+ case Invalid:
+ return 0;
+ }
+}
+
+QVariant QQmlListAccessor::at(int idx) const
+{
+ Q_ASSERT(idx >= 0 && idx < count());
+ switch(m_type) {
+ case StringList:
+ return QVariant::fromValue(qvariant_cast<QStringList>(d).at(idx));
+ case VariantList:
+ return qvariant_cast<QVariantList>(d).at(idx);
+ case ListProperty:
+ return QVariant::fromValue(((const QQmlListReference *)d.constData())->at(idx));
+ case Instance:
+ return d;
+ case Integer:
+ return QVariant(idx);
+ default:
+ case Invalid:
+ return QVariant();
+ }
+}
+
+bool QQmlListAccessor::isValid() const
+{
+ return m_type != Invalid;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlmodels/qqmllistaccessor_p.h b/src/qmlmodels/qqmllistaccessor_p.h
new file mode 100644
index 0000000000..bcd079adef
--- /dev/null
+++ b/src/qmlmodels/qqmllistaccessor_p.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLLISTACCESSOR_H
+#define QQMLLISTACCESSOR_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QVariant>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlEngine;
+class Q_AUTOTEST_EXPORT QQmlListAccessor
+{
+public:
+ QQmlListAccessor();
+ ~QQmlListAccessor();
+
+ QVariant list() const;
+ void setList(const QVariant &, QQmlEngine * = nullptr);
+
+ bool isValid() const;
+
+ int count() const;
+ QVariant at(int) const;
+
+ enum Type { Invalid, StringList, VariantList, ListProperty, Instance, Integer };
+ Type type() const { return m_type; }
+
+private:
+ Type m_type;
+ QVariant d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLLISTACCESSOR_H
diff --git a/src/qmlmodels/qqmllistcompositor.cpp b/src/qmlmodels/qqmllistcompositor.cpp
new file mode 100644
index 0000000000..921e86f355
--- /dev/null
+++ b/src/qmlmodels/qqmllistcompositor.cpp
@@ -0,0 +1,1482 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmllistcompositor_p.h"
+
+#include <QtCore/qvarlengtharray.h>
+
+//#define QT_QML_VERIFY_MINIMAL
+//#define QT_QML_VERIFY_INTEGRITY
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QQmlListCompositor
+ \brief The QQmlListCompositor class provides a lookup table for filtered, or re-ordered list
+ indexes.
+ \internal
+
+ QQmlListCompositor is intended as an aid for developing proxy models. It doesn't however
+ directly proxy a list or model, instead a range of indexes from one or many lists can be
+ inserted into the compositor and then categorized and shuffled around and it will manage the
+ task of translating from an index in the combined space into an index in a particular list.
+
+ Within a compositor indexes are categorized into groups where a group is a sub-set of the
+ total indexes referenced by the compositor, each with an address space ranging from 0 to
+ the number of indexes in the group - 1. Group memberships are independent of each other with
+ the one exception that items always retain the same order so if an index is moved within a
+ group, its position in other groups will change as well.
+
+ The iterator classes encapsulate information about a specific position in a compositor group.
+ This includes a source list, the index of an item within that list and the groups that item
+ is a member of. The iterator for a specific position in a group can be retrieved with the
+ find() function and the addition and subtraction operators of the iterators can be used to
+ navigate to adjacent items in the same group.
+
+ Items can be added to the compositor with the append() and insert() functions, group
+ membership can be changed with the setFlags() and clearFlags() functions, and the position
+ of items in the compositor can be changed with the move() function. Each of these functions
+ optionally returns a list of the changes made to indexes within each group which can then
+ be propagated to view so that it can correctly refresh its contents; e.g. 3 items
+ removed at index 6, and 5 items inserted at index 1. The notification changes are always
+ ordered from the start of the list to the end and accumulate, so if 5 items are removed at
+ index 4, one is skipped and then 3 move are removed, the changes returned are 5 items removed
+ at index 4, followed by 3 items removed at index 4.
+
+ When the contents of a source list change, the mappings within the compositor can be updated
+ with the listItemsInserted(), listItemsRemoved(), listItemsMoved(), and listItemsChanged()
+ functions. Like the direct manipulation functions these too return a list of group indexes
+ affected by the change. If items are removed from a source list they are also removed from
+ any groups they belong to with the one exception being items belonging to the \l Cache group.
+ When items belonging to this group are removed the list, index, and other group membership
+ information are discarded but Cache membership is retained until explicitly removed. This
+ allows the cache index to be retained until cached resources for that item are actually
+ released.
+
+ Internally the index mapping is stored as a list of Range objects, each has a list identifier,
+ a start index, a count, and a set of flags which represent group membership and some other
+ properties. The group index of a range is the sum of all preceding ranges that are members of
+ that group. To avoid the inefficiency of iterating over potentially all ranges when looking
+ for a specific index, each time a lookup is done the range and its indexes are cached and the
+ next lookup is done relative to this. This works out to near constant time in most relevant
+ use cases because successive index lookups are most frequently adjacent. The total number of
+ ranges is often quite small, which helps as well. If there is a need for faster random access
+ then a skip list like index may be an appropriate addition.
+
+ \sa DelegateModel
+*/
+
+#ifdef QT_QML_VERIFY_MINIMAL
+#define QT_QML_VERIFY_INTEGRITY
+/*
+ Diagnostic to verify there are no consecutive ranges, or that the compositor contains the
+ most compact representation possible.
+
+ Returns false and prints a warning if any range has a starting index equal to the end
+ (index + count) index of the previous range, and both ranges also have the same flags and list
+ property.
+
+ If there are no consecutive ranges this will return true.
+*/
+
+static bool qt_verifyMinimal(
+ const QQmlListCompositor::iterator &begin,
+ const QQmlListCompositor::iterator &end)
+{
+ bool minimal = true;
+ int index = 0;
+
+ for (const QQmlListCompositor::Range *range = begin->next; range != *end; range = range->next, ++index) {
+ if (range->previous->list == range->list
+ && range->previous->flags == (range->flags & ~QQmlListCompositor::AppendFlag)
+ && range->previous->end() == range->index) {
+ qWarning() << index << "Consecutive ranges";
+ qWarning() << *range->previous;
+ qWarning() << *range;
+ minimal = false;
+ }
+ }
+
+ return minimal;
+}
+
+#endif
+
+#ifdef QT_QML_VERIFY_INTEGRITY
+static bool qt_printInfo(const QQmlListCompositor &compositor)
+{
+ qWarning() << compositor;
+ return true;
+}
+
+/*
+ Diagnostic to verify the integrity of a compositor.
+
+ Per range this verifies there are no invalid range combinations, that non-append ranges have
+ positive non-zero counts, and that list ranges have non-negative indexes.
+
+ Accumulatively this verifies that the cached total group counts match the sum of counts
+ of member ranges.
+*/
+
+static bool qt_verifyIntegrity(
+ const QQmlListCompositor::iterator &begin,
+ const QQmlListCompositor::iterator &end,
+ const QQmlListCompositor::iterator &cachedIt)
+{
+ bool valid = true;
+
+ int index = 0;
+ QQmlListCompositor::iterator it;
+ for (it = begin; *it != *end; *it = it->next) {
+ if (it->count == 0 && !it->append()) {
+ qWarning() << index << "Empty non-append range";
+ valid = false;
+ }
+ if (it->count < 0) {
+ qWarning() << index << "Negative count";
+ valid = false;
+ }
+ if (it->list && it->flags != QQmlListCompositor::CacheFlag && it->index < 0) {
+ qWarning() << index <<"Negative index";
+ valid = false;
+ }
+ if (it->previous->next != it.range) {
+ qWarning() << index << "broken list: it->previous->next != it.range";
+ valid = false;
+ }
+ if (it->next->previous != it.range) {
+ qWarning() << index << "broken list: it->next->previous != it.range";
+ valid = false;
+ }
+ if (*it == *cachedIt) {
+ for (int i = 0; i < end.groupCount; ++i) {
+ int groupIndex = it.index[i];
+ if (cachedIt->flags & (1 << i))
+ groupIndex += cachedIt.offset;
+ if (groupIndex != cachedIt.index[i]) {
+ qWarning() << index
+ << "invalid cached index"
+ << QQmlListCompositor::Group(i)
+ << "Expected:"
+ << groupIndex
+ << "Actual"
+ << cachedIt.index[i]
+ << cachedIt;
+ valid = false;
+ }
+ }
+ }
+ it.incrementIndexes(it->count);
+ ++index;
+ }
+
+ for (int i = 0; i < end.groupCount; ++i) {
+ if (end.index[i] != it.index[i]) {
+ qWarning() << "Group" << i << "count invalid. Expected:" << end.index[i] << "Actual:" << it.index[i];
+ valid = false;
+ }
+ }
+ return valid;
+}
+#endif
+
+#if defined(QT_QML_VERIFY_MINIMAL)
+# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!(qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \
+ && qt_verifyMinimal(iterator(m_ranges.next, 0, Default, m_groupCount), m_end)) \
+ && qt_printInfo(*this)));
+#elif defined(QT_QML_VERIFY_INTEGRITY)
+# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \
+ && qt_printInfo(*this)));
+#else
+# define QT_QML_VERIFY_LISTCOMPOSITOR
+#endif
+
+//#define QT_QML_TRACE_LISTCOMPOSITOR(args) qDebug() << m_end.index[1] << m_end.index[0] << Q_FUNC_INFO args;
+#define QT_QML_TRACE_LISTCOMPOSITOR(args)
+
+QQmlListCompositor::iterator &QQmlListCompositor::iterator::operator +=(int difference)
+{
+ // Update all indexes to the start of the range.
+ decrementIndexes(offset);
+
+ // If the iterator group isn't a member of the current range ignore the current offset.
+ if (!(range->flags & groupFlag))
+ offset = 0;
+
+ offset += difference;
+
+ // Iterate backwards looking for a range with a positive offset.
+ while (offset <= 0 && range->previous->flags) {
+ range = range->previous;
+ if (range->flags & groupFlag)
+ offset += range->count;
+ decrementIndexes(range->count);
+ }
+
+ // Iterate forwards looking for the first range which contains both the offset and the
+ // iterator group.
+ while (range->flags && (offset >= range->count || !(range->flags & groupFlag))) {
+ if (range->flags & groupFlag)
+ offset -= range->count;
+ incrementIndexes(range->count);
+ range = range->next;
+ }
+
+ // Update all the indexes to inclue the remaining offset.
+ incrementIndexes(offset);
+
+ return *this;
+}
+
+QQmlListCompositor::insert_iterator &QQmlListCompositor::insert_iterator::operator +=(int difference)
+{
+ iterator::operator +=(difference);
+
+ // If the previous range contains the append flag move the iterator to the tail of the previous
+ // range so that appended appear after the insert position.
+ if (offset == 0 && range->previous->append()) {
+ range = range->previous;
+ offset = range->inGroup() ? range->count : 0;
+ }
+
+ return *this;
+}
+
+
+/*!
+ Constructs an empty list compositor.
+*/
+
+QQmlListCompositor::QQmlListCompositor()
+ : m_end(m_ranges.next, 0, Default, 2)
+ , m_cacheIt(m_end)
+ , m_groupCount(2)
+ , m_defaultFlags(PrependFlag | DefaultFlag)
+ , m_removeFlags(AppendFlag | PrependFlag | GroupMask)
+ , m_moveId(0)
+{
+}
+
+/*!
+ Destroys a list compositor.
+*/
+
+QQmlListCompositor::~QQmlListCompositor()
+{
+ for (Range *next, *range = m_ranges.next; range != &m_ranges; range = next) {
+ next = range->next;
+ delete range;
+ }
+}
+
+/*!
+ Inserts a range with the given source \a list, start \a index, \a count and \a flags, in front
+ of the existing range \a before.
+*/
+
+inline QQmlListCompositor::Range *QQmlListCompositor::insert(
+ Range *before, void *list, int index, int count, uint flags)
+{
+ return new Range(before, list, index, count, flags);
+}
+
+/*!
+ Erases a \a range from the compositor.
+
+ Returns a pointer to the next range in the compositor.
+*/
+
+inline QQmlListCompositor::Range *QQmlListCompositor::erase(
+ Range *range)
+{
+ Range *next = range->next;
+ next->previous = range->previous;
+ next->previous->next = range->next;
+ delete range;
+ return next;
+}
+
+/*!
+ Sets the number (\a count) of possible groups that items may belong to in a compositor.
+*/
+
+void QQmlListCompositor::setGroupCount(int count)
+{
+ m_groupCount = count;
+ m_end = iterator(&m_ranges, 0, Default, m_groupCount);
+ m_cacheIt = m_end;
+}
+
+/*!
+ Returns the number of items that belong to a \a group.
+*/
+
+int QQmlListCompositor::count(Group group) const
+{
+ return m_end.index[group];
+}
+
+/*!
+ Returns an iterator representing the item at \a index in a \a group.
+
+ The index must be between 0 and count(group) - 1.
+*/
+
+QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< group << index)
+ Q_ASSERT(index >=0 && index < count(group));
+ if (m_cacheIt == m_end) {
+ m_cacheIt = iterator(m_ranges.next, 0, group, m_groupCount);
+ m_cacheIt += index;
+ } else {
+ const int offset = index - m_cacheIt.index[group];
+ m_cacheIt.setGroup(group);
+ m_cacheIt += offset;
+ }
+ Q_ASSERT(m_cacheIt.index[group] == index);
+ Q_ASSERT(m_cacheIt->inGroup(group));
+ QT_QML_VERIFY_LISTCOMPOSITOR
+ return m_cacheIt;
+}
+
+/*!
+ Returns an iterator representing the item at \a index in a \a group.
+
+ The index must be between 0 and count(group) - 1.
+*/
+
+QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index) const
+{
+ return const_cast<QQmlListCompositor *>(this)->find(group, index);
+}
+
+/*!
+ Returns an iterator representing an insert position in front of the item at \a index in a
+ \a group.
+
+ The iterator for an insert position can sometimes resolve to a different Range than a regular
+ iterator. This is because when items are inserted on a boundary between Ranges, if the first
+ range has the Append flag set then the items should be inserted into that range to ensure
+ that the append position for the existing range remains after the insert position.
+
+ The index must be between 0 and count(group) - 1.
+*/
+
+QQmlListCompositor::insert_iterator QQmlListCompositor::findInsertPosition(Group group, int index)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< group << index)
+ Q_ASSERT(index >=0 && index <= count(group));
+ insert_iterator it;
+ if (m_cacheIt == m_end) {
+ it = iterator(m_ranges.next, 0, group, m_groupCount);
+ it += index;
+ } else {
+ const int offset = index - m_cacheIt.index[group];
+ it = m_cacheIt;
+ it.setGroup(group);
+ it += offset;
+ }
+ Q_ASSERT(it.index[group] == index);
+ return it;
+}
+
+/*!
+ Appends a range of \a count indexes starting at \a index from a \a list into a compositor
+ with the given \a flags.
+
+ If supplied the \a inserts list will be populated with the positions of the inserted items
+ in each group.
+*/
+
+void QQmlListCompositor::append(
+ void *list, int index, int count, uint flags, QVector<Insert> *inserts)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count << flags)
+ insert(m_end, list, index, count, flags, inserts);
+}
+
+/*!
+ Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags
+ into a \a group at index \a before.
+
+ If supplied the \a inserts list will be populated with the positions of items inserted into
+ each group.
+*/
+
+void QQmlListCompositor::insert(
+ Group group, int before, void *list, int index, int count, uint flags, QVector<Insert> *inserts)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< group << before << list << index << count << flags)
+ insert(findInsertPosition(group, before), list, index, count, flags, inserts);
+}
+
+/*!
+ Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags
+ into a compositor at position \a before.
+
+ If supplied the \a inserts list will be populated with the positions of items inserted into
+ each group.
+*/
+
+QQmlListCompositor::iterator QQmlListCompositor::insert(
+ iterator before, void *list, int index, int count, uint flags, QVector<Insert> *inserts)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< before << list << index << count << flags)
+ if (inserts) {
+ inserts->append(Insert(before, count, flags & GroupMask));
+ }
+ if (before.offset > 0) {
+ // Inserting into the middle of a range. Split it two and update the iterator so it's
+ // positioned at the start of the second half.
+ *before = insert(
+ *before, before->list, before->index, before.offset, before->flags & ~AppendFlag)->next;
+ before->index += before.offset;
+ before->count -= before.offset;
+ before.offset = 0;
+ }
+
+
+ if (!(flags & AppendFlag) && *before != m_ranges.next
+ && before->previous->list == list
+ && before->previous->flags == flags
+ && (!list || before->previous->end() == index)) {
+ // The insert arguments represent a continuation of the previous range so increment
+ // its count instead of inserting a new range.
+ before->previous->count += count;
+ before.incrementIndexes(count, flags);
+ } else {
+ *before = insert(*before, list, index, count, flags);
+ before.offset = 0;
+ }
+
+ if (!(flags & AppendFlag) && before->next != &m_ranges
+ && before->list == before->next->list
+ && before->flags == before->next->flags
+ && (!list || before->end() == before->next->index)) {
+ // The current range and the next are continuous so add their counts and delete one.
+ before->next->index = before->index;
+ before->next->count += before->count;
+ *before = erase(*before);
+ }
+
+ m_end.incrementIndexes(count, flags);
+ m_cacheIt = before;
+ QT_QML_VERIFY_LISTCOMPOSITOR
+ return before;
+}
+
+/*!
+ Sets the given flags \a flags on \a count items belonging to \a group starting at the position
+ identified by \a fromGroup and the index \a from.
+
+ If supplied the \a inserts list will be populated with insert notifications for affected groups.
+*/
+
+void QQmlListCompositor::setFlags(
+ Group fromGroup, int from, int count, Group group, int flags, QVector<Insert> *inserts)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags)
+ setFlags(find(fromGroup, from), count, group, flags, inserts);
+}
+
+/*!
+ Sets the given flags \a flags on \a count items belonging to \a group starting at the position
+ \a from.
+
+ If supplied the \a inserts list will be populated with insert notifications for affected groups.
+*/
+
+void QQmlListCompositor::setFlags(
+ iterator from, int count, Group group, uint flags, QVector<Insert> *inserts)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags)
+ if (!flags || !count)
+ return;
+
+ if (from != group) {
+ // Skip to the next full range if the start one is not a member of the target group.
+ from.incrementIndexes(from->count - from.offset);
+ from.offset = 0;
+ *from = from->next;
+ } else if (from.offset > 0) {
+ // If the start position is mid range split off the portion unaffected.
+ *from = insert(*from, from->list, from->index, from.offset, from->flags & ~AppendFlag)->next;
+ from->index += from.offset;
+ from->count -= from.offset;
+ from.offset = 0;
+ }
+
+ for (; count > 0; *from = from->next) {
+ if (from != from.group) {
+ // Skip ranges that are not members of the target group.
+ from.incrementIndexes(from->count);
+ continue;
+ }
+ // Find the number of items affected in the current range.
+ const int difference = qMin(count, from->count);
+ count -= difference;
+
+ // Determine the actual changes made to the range and increment counts accordingly.
+ const uint insertFlags = ~from->flags & flags;
+ const uint setFlags = (from->flags | flags) & ~AppendFlag;
+ if (insertFlags && inserts)
+ inserts->append(Insert(from, difference, insertFlags | (from->flags & CacheFlag)));
+ m_end.incrementIndexes(difference, insertFlags);
+ from.incrementIndexes(difference, setFlags);
+
+ if (from->previous != &m_ranges
+ && from->previous->list == from->list
+ && (!from->list || from->previous->end() == from->index)
+ && from->previous->flags == setFlags) {
+ // If the additional flags make the current range a continuation of the previous
+ // then move the affected items over to the previous range.
+ from->previous->count += difference;
+ from->index += difference;
+ from->count -= difference;
+ if (from->count == 0) {
+ // Delete the current range if it is now empty, preserving the append flag
+ // in the previous range.
+ if (from->append())
+ from->previous->flags |= AppendFlag;
+ *from = erase(*from)->previous;
+ continue;
+ } else {
+ break;
+ }
+ } else if (!insertFlags) {
+ // No new flags, so roll onto the next range.
+ from.incrementIndexes(from->count - difference);
+ continue;
+ } else if (difference < from->count) {
+ // Create a new range with the updated flags, and remove the affected items
+ // from the current range.
+ *from = insert(*from, from->list, from->index, difference, setFlags)->next;
+ from->index += difference;
+ from->count -= difference;
+ } else {
+ // The whole range is affected so simply update the flags.
+ from->flags |= flags;
+ continue;
+ }
+ from.incrementIndexes(from->count);
+ }
+
+ if (from->previous != &m_ranges
+ && from->previous->list == from->list
+ && (!from->list || from->previous->end() == from->index)
+ && from->previous->flags == (from->flags & ~AppendFlag)) {
+ // If the following range is now a continuation, merge it with its previous range.
+ from.offset = from->previous->count;
+ from->previous->count += from->count;
+ from->previous->flags = from->flags;
+ *from = erase(*from)->previous;
+ }
+ m_cacheIt = from;
+ QT_QML_VERIFY_LISTCOMPOSITOR
+}
+
+/*!
+ Clears the given flags \a flags on \a count items belonging to \a group starting at the position
+ \a from.
+
+ If supplied the \a removes list will be populated with remove notifications for affected groups.
+*/
+
+void QQmlListCompositor::clearFlags(
+ Group fromGroup, int from, int count, Group group, uint flags, QVector<Remove> *removes)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags)
+ clearFlags(find(fromGroup, from), count, group, flags, removes);
+}
+
+/*!
+ Clears the given flags \a flags on \a count items belonging to \a group starting at the position
+ identified by \a fromGroup and the index \a from.
+
+ If supplied the \a removes list will be populated with remove notifications for affected groups.
+*/
+
+void QQmlListCompositor::clearFlags(
+ iterator from, int count, Group group, uint flags, QVector<Remove> *removes)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags)
+ if (!flags || !count)
+ return;
+
+ const bool clearCache = flags & CacheFlag;
+
+ if (from != group) {
+ // Skip to the next full range if the start one is not a member of the target group.
+ from.incrementIndexes(from->count - from.offset);
+ from.offset = 0;
+ *from = from->next;
+ } else if (from.offset > 0) {
+ // If the start position is mid range split off the portion unaffected.
+ *from = insert(*from, from->list, from->index, from.offset, from->flags & ~AppendFlag)->next;
+ from->index += from.offset;
+ from->count -= from.offset;
+ from.offset = 0;
+ }
+
+ for (; count > 0; *from = from->next) {
+ if (from != group) {
+ // Skip ranges that are not members of the target group.
+ from.incrementIndexes(from->count);
+ continue;
+ }
+ // Find the number of items affected in the current range.
+ const int difference = qMin(count, from->count);
+ count -= difference;
+
+
+ // Determine the actual changes made to the range and decrement counts accordingly.
+ const uint removeFlags = from->flags & flags & ~(AppendFlag | PrependFlag);
+ const uint clearedFlags = from->flags & ~(flags | AppendFlag | UnresolvedFlag);
+ if (removeFlags && removes) {
+ const int maskedFlags = clearCache
+ ? (removeFlags & ~CacheFlag)
+ : (removeFlags | (from->flags & CacheFlag));
+ if (maskedFlags)
+ removes->append(Remove(from, difference, maskedFlags));
+ }
+ m_end.decrementIndexes(difference, removeFlags);
+ from.incrementIndexes(difference, clearedFlags);
+
+ if (from->previous != &m_ranges
+ && from->previous->list == from->list
+ && (!from->list || clearedFlags == CacheFlag || from->previous->end() == from->index)
+ && from->previous->flags == clearedFlags) {
+ // If the removed flags make the current range a continuation of the previous
+ // then move the affected items over to the previous range.
+ from->previous->count += difference;
+ from->index += difference;
+ from->count -= difference;
+ if (from->count == 0) {
+ // Delete the current range if it is now empty, preserving the append flag
+ if (from->append())
+ from->previous->flags |= AppendFlag;
+ *from = erase(*from)->previous;
+ } else {
+ from.incrementIndexes(from->count);
+ }
+ } else if (difference < from->count) {
+ // Create a new range with the reduced flags, and remove the affected items from
+ // the current range.
+ if (clearedFlags)
+ *from = insert(*from, from->list, from->index, difference, clearedFlags)->next;
+ from->index += difference;
+ from->count -= difference;
+ from.incrementIndexes(from->count);
+ } else if (clearedFlags) {
+ // The whole range is affected so simply update the flags.
+ from->flags &= ~flags;
+ } else {
+ // All flags have been removed from the range so remove it.
+ *from = erase(*from)->previous;
+ }
+ }
+
+ if (*from != &m_ranges && from->previous != &m_ranges
+ && from->previous->list == from->list
+ && (!from->list || from->previous->end() == from->index)
+ && from->previous->flags == (from->flags & ~AppendFlag)) {
+ // If the following range is now a continuation, merge it with its previous range.
+ from.offset = from->previous->count;
+ from->previous->count += from->count;
+ from->previous->flags = from->flags;
+ *from = erase(*from)->previous;
+ }
+ m_cacheIt = from;
+ QT_QML_VERIFY_LISTCOMPOSITOR
+}
+
+bool QQmlListCompositor::verifyMoveTo(
+ Group fromGroup, int from, Group toGroup, int to, int count, Group group) const
+{
+ if (group != toGroup) {
+ // determine how many items from the destination group intersect with the source group.
+ iterator fromIt = find(fromGroup, from);
+
+ int intersectingCount = 0;
+
+ for (; count > 0; *fromIt = fromIt->next) {
+ if (*fromIt == &m_ranges)
+ return false;
+ if (!fromIt->inGroup(group))
+ continue;
+ if (fromIt->inGroup(toGroup))
+ intersectingCount += qMin(count, fromIt->count - fromIt.offset);
+ count -= fromIt->count - fromIt.offset;
+ fromIt.offset = 0;
+ }
+ count = intersectingCount;
+ }
+
+ return to >= 0 && to + count <= m_end.index[toGroup];
+}
+
+/*!
+ \internal
+
+ Moves \a count items belonging to \a moveGroup from the index \a from in \a fromGroup
+ to the index \a to in \a toGroup.
+
+ If \a removes and \a inserts are not null they will be populated with per group notifications
+ of the items moved.
+ */
+
+void QQmlListCompositor::move(
+ Group fromGroup,
+ int from,
+ Group toGroup,
+ int to,
+ int count,
+ Group moveGroup,
+ QVector<Remove> *removes,
+ QVector<Insert> *inserts)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << toGroup << to << count)
+ Q_ASSERT(count > 0);
+ Q_ASSERT(from >=0);
+ Q_ASSERT(verifyMoveTo(fromGroup, from, toGroup, to, count, moveGroup));
+
+ // Find the position of the first item to move.
+ iterator fromIt = find(fromGroup, from);
+
+ if (fromIt != moveGroup) {
+ // If the range at the from index doesn't contain items from the move group; skip
+ // to the next range.
+ fromIt.incrementIndexes(fromIt->count - fromIt.offset);
+ fromIt.offset = 0;
+ *fromIt = fromIt->next;
+ } else if (fromIt.offset > 0) {
+ // If the range at the from index contains items from the move group and the index isn't
+ // at the start of the range; split the range at the index and move the iterator to start
+ // of the second range.
+ *fromIt = insert(
+ *fromIt, fromIt->list, fromIt->index, fromIt.offset, fromIt->flags & ~AppendFlag)->next;
+ fromIt->index += fromIt.offset;
+ fromIt->count -= fromIt.offset;
+ fromIt.offset = 0;
+ }
+
+ // Remove count items belonging to the move group from the list.
+ Range movedFlags;
+ for (int moveId = m_moveId; count > 0;) {
+ if (fromIt != moveGroup) {
+ // Skip ranges not containing items from the move group.
+ fromIt.incrementIndexes(fromIt->count);
+ *fromIt = fromIt->next;
+ continue;
+ }
+ int difference = qMin(count, fromIt->count);
+
+ // Create a new static range containing the moved items from an existing range.
+ new Range(
+ &movedFlags,
+ fromIt->list,
+ fromIt->index,
+ difference,
+ fromIt->flags & ~(PrependFlag | AppendFlag));
+ // Remove moved items from the count, the existing range, and a remove notification.
+ if (removes)
+ removes->append(Remove(fromIt, difference, fromIt->flags, ++moveId));
+ count -= difference;
+ fromIt->count -= difference;
+
+ // If the existing range contains the prepend flag replace the removed items with
+ // a placeholder range for new items inserted into the source model.
+ int removeIndex = fromIt->index;
+ if (fromIt->prepend()
+ && fromIt->previous != &m_ranges
+ && fromIt->previous->flags == PrependFlag
+ && fromIt->previous->list == fromIt->list
+ && fromIt->previous->end() == fromIt->index) {
+ // Grow the previous range instead of creating a new one if possible.
+ fromIt->previous->count += difference;
+ } else if (fromIt->prepend()) {
+ *fromIt = insert(*fromIt, fromIt->list, removeIndex, difference, PrependFlag)->next;
+ }
+ fromIt->index += difference;
+
+ if (fromIt->count == 0) {
+ // If the existing range has no items remaining; remove it from the list.
+ if (fromIt->append())
+ fromIt->previous->flags |= AppendFlag;
+ *fromIt = erase(*fromIt);
+
+ // If the ranges before and after the removed range can be joined, do so.
+ if (*fromIt != m_ranges.next && fromIt->flags == PrependFlag
+ && fromIt->previous != &m_ranges
+ && fromIt->previous->flags == PrependFlag
+ && fromIt->previous->list == fromIt->list
+ && fromIt->previous->end() == fromIt->index) {
+ fromIt.incrementIndexes(fromIt->count);
+ fromIt->previous->count += fromIt->count;
+ *fromIt = erase(*fromIt);
+ }
+ } else if (count > 0) {
+ *fromIt = fromIt->next;
+ }
+ }
+
+ // Try and join the range following the removed items to the range preceding it.
+ if (*fromIt != m_ranges.next
+ && *fromIt != &m_ranges
+ && fromIt->previous->list == fromIt->list
+ && (!fromIt->list || fromIt->previous->end() == fromIt->index)
+ && fromIt->previous->flags == (fromIt->flags & ~AppendFlag)) {
+ if (fromIt == fromIt.group)
+ fromIt.offset = fromIt->previous->count;
+ fromIt.offset = fromIt->previous->count;
+ fromIt->previous->count += fromIt->count;
+ fromIt->previous->flags = fromIt->flags;
+ *fromIt = erase(*fromIt)->previous;
+ }
+
+ // Find the destination position of the move.
+ insert_iterator toIt = fromIt;
+ toIt.setGroup(toGroup);
+
+ const int difference = to - toIt.index[toGroup];
+ toIt += difference;
+
+ // If the insert position is part way through a range; split it and move the iterator to the
+ // start of the second range.
+ if (toIt.offset > 0) {
+ *toIt = insert(*toIt, toIt->list, toIt->index, toIt.offset, toIt->flags & ~AppendFlag)->next;
+ toIt->index += toIt.offset;
+ toIt->count -= toIt.offset;
+ toIt.offset = 0;
+ }
+
+ // Insert the moved ranges before the insert iterator, growing the previous range if that
+ // is an option.
+ for (Range *range = movedFlags.previous; range != &movedFlags; range = range->previous) {
+ if (*toIt != &m_ranges
+ && range->list == toIt->list
+ && (!range->list || range->end() == toIt->index)
+ && range->flags == (toIt->flags & ~AppendFlag)) {
+ toIt->index -= range->count;
+ toIt->count += range->count;
+ } else {
+ *toIt = insert(*toIt, range->list, range->index, range->count, range->flags);
+ }
+ }
+
+ // Try and join the range after the inserted ranges to the last range inserted.
+ if (*toIt != m_ranges.next
+ && toIt->previous->list == toIt->list
+ && (!toIt->list || (toIt->previous->end() == toIt->index && toIt->previous->flags == (toIt->flags & ~AppendFlag)))) {
+ toIt.offset = toIt->previous->count;
+ toIt->previous->count += toIt->count;
+ toIt->previous->flags = toIt->flags;
+ *toIt = erase(*toIt)->previous;
+ }
+ // Create insert notification for the ranges moved.
+ Insert insert(toIt, 0, 0, 0);
+ for (Range *next, *range = movedFlags.next; range != &movedFlags; range = next) {
+ insert.count = range->count;
+ insert.flags = range->flags;
+ if (inserts) {
+ insert.moveId = ++m_moveId;
+ inserts->append(insert);
+ }
+ for (int i = 0; i < m_groupCount; ++i) {
+ if (insert.inGroup(i))
+ insert.index[i] += range->count;
+ }
+
+ next = range->next;
+ delete range;
+ }
+
+ m_cacheIt = toIt;
+
+ QT_QML_VERIFY_LISTCOMPOSITOR
+}
+
+/*!
+ Clears the contents of a compositor.
+*/
+
+void QQmlListCompositor::clear()
+{
+ QT_QML_TRACE_LISTCOMPOSITOR("")
+ for (Range *range = m_ranges.next; range != &m_ranges; range = erase(range)) {}
+ m_end = iterator(m_ranges.next, 0, Default, m_groupCount);
+ m_cacheIt = m_end;
+}
+
+void QQmlListCompositor::listItemsInserted(
+ QVector<Insert> *translatedInsertions,
+ void *list,
+ const QVector<QQmlChangeSet::Change> &insertions,
+ const QVector<MovedFlags> *movedFlags)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << insertions)
+ for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
+ if (it->list != list || it->flags == CacheFlag) {
+ // Skip ranges that don't reference list.
+ it.incrementIndexes(it->count);
+ continue;
+ } else if (it->flags & MovedFlag) {
+ // Skip ranges that were already moved in listItemsRemoved.
+ it->flags &= ~MovedFlag;
+ it.incrementIndexes(it->count);
+ continue;
+ }
+ for (const QQmlChangeSet::Change &insertion : insertions) {
+ int offset = insertion.index - it->index;
+ if ((offset > 0 && offset < it->count)
+ || (offset == 0 && it->prepend())
+ || (offset == it->count && it->append())) {
+ // The insert index is within the current range.
+ if (it->prepend()) {
+ // The range has the prepend flag set so we insert new items into the range.
+ uint flags = m_defaultFlags;
+ if (insertion.isMove()) {
+ // If the insert was part of a move replace the default flags with
+ // the flags from the source range.
+ for (QVector<MovedFlags>::const_iterator move = movedFlags->begin();
+ move != movedFlags->end();
+ ++move) {
+ if (move->moveId == insertion.moveId) {
+ flags = move->flags;
+ break;
+ }
+ }
+ }
+ if (flags & ~(AppendFlag | PrependFlag)) {
+ // If any items are added to groups append an insert notification.
+ Insert translatedInsert(it, insertion.count, flags, insertion.moveId);
+ for (int i = 0; i < m_groupCount; ++i) {
+ if (it->inGroup(i))
+ translatedInsert.index[i] += offset;
+ }
+ translatedInsertions->append(translatedInsert);
+ }
+ if ((it->flags & ~AppendFlag) == flags) {
+ // Accumulate items on the current range it its flags are the same as
+ // the insert flags.
+ it->count += insertion.count;
+ } else if (offset == 0
+ && it->previous != &m_ranges
+ && it->previous->list == list
+ && it->previous->end() == insertion.index
+ && it->previous->flags == flags) {
+ // Attempt to append to the previous range if the insert position is at
+ // the start of the current range.
+ it->previous->count += insertion.count;
+ it->index += insertion.count;
+ it.incrementIndexes(insertion.count);
+ } else {
+ if (offset > 0) {
+ // Divide the current range at the insert position.
+ it.incrementIndexes(offset);
+ *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next;
+ }
+ // Insert a new range, and increment the start index of the current range.
+ *it = insert(*it, it->list, insertion.index, insertion.count, flags)->next;
+ it.incrementIndexes(insertion.count, flags);
+ it->index += offset + insertion.count;
+ it->count -= offset;
+ }
+ m_end.incrementIndexes(insertion.count, flags);
+ } else {
+ // The range doesn't have the prepend flag set so split the range into parts;
+ // one before the insert position and one after the last inserted item.
+ if (offset > 0) {
+ *it = insert(*it, it->list, it->index, offset, it->flags)->next;
+ it->index += offset;
+ it->count -= offset;
+ }
+ it->index += insertion.count;
+ }
+ } else if (offset <= 0) {
+ // The insert position was before the current range so increment the start index.
+ it->index += insertion.count;
+ }
+ }
+ it.incrementIndexes(it->count);
+ }
+ m_cacheIt = m_end;
+ QT_QML_VERIFY_LISTCOMPOSITOR
+}
+
+/*!
+ Updates the contents of a compositor when \a count items are inserted into a \a list at
+ \a index.
+
+ This corrects the indexes of each range for that list in the compositor, splitting the range
+ in two if the insert index is in the middle of that range. If a range at the insert position
+ has the Prepend flag set then a new range will be inserted at that position with the groups
+ specified in defaultGroups(). If the insert index corresponds to the end of a range with
+ the Append flag set a new range will be inserted before the end of the append range.
+
+ The \a translatedInsertions list is populated with insert notifications for affected
+ groups.
+*/
+
+void QQmlListCompositor::listItemsInserted(
+ void *list, int index, int count, QVector<Insert> *translatedInsertions)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count)
+ Q_ASSERT(count > 0);
+
+ QVector<QQmlChangeSet::Change> insertions;
+ insertions.append(QQmlChangeSet::Change(index, count));
+
+ listItemsInserted(translatedInsertions, list, insertions);
+}
+
+void QQmlListCompositor::listItemsRemoved(
+ QVector<Remove> *translatedRemovals,
+ void *list,
+ QVector<QQmlChangeSet::Change> *removals,
+ QVector<QQmlChangeSet::Change> *insertions,
+ QVector<MovedFlags> *movedFlags)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << *removals)
+
+ for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
+ if (it->list != list || it->flags == CacheFlag) {
+ // Skip ranges that don't reference list.
+ it.incrementIndexes(it->count);
+ continue;
+ }
+ bool removed = false;
+ for (QVector<QQmlChangeSet::Change>::iterator removal = removals->begin();
+ !removed && removal != removals->end();
+ ++removal) {
+ int relativeIndex = removal->index - it->index;
+ int itemsRemoved = removal->count;
+ if (relativeIndex + removal->count > 0 && relativeIndex < it->count) {
+ // If the current range intersects the remove; remove the intersecting items.
+ const int offset = qMax(0, relativeIndex);
+ int removeCount = qMin(it->count, relativeIndex + removal->count) - offset;
+ it->count -= removeCount;
+ int removeFlags = it->flags & m_removeFlags;
+ Remove translatedRemoval(it, removeCount, it->flags);
+ for (int i = 0; i < m_groupCount; ++i) {
+ if (it->inGroup(i))
+ translatedRemoval.index[i] += offset;
+ }
+ if (removal->isMove()) {
+ // If the removal was part of a move find the corresponding insert.
+ QVector<QQmlChangeSet::Change>::iterator insertion = insertions->begin();
+ for (; insertion != insertions->end() && insertion->moveId != removal->moveId;
+ ++insertion) {}
+ Q_ASSERT(insertion != insertions->end());
+ Q_ASSERT(insertion->count == removal->count);
+
+ if (relativeIndex < 0) {
+ // If the remove started before the current range, split it and the
+ // corresponding insert so we're only working with intersecting part.
+ int splitMoveId = ++m_moveId;
+ removal = removals->insert(removal, QQmlChangeSet::Change(
+ removal->index, -relativeIndex, splitMoveId));
+ ++removal;
+ removal->count -= -relativeIndex;
+ insertion = insertions->insert(insertion, QQmlChangeSet::Change(
+ insertion->index, -relativeIndex, splitMoveId));
+ ++insertion;
+ insertion->index += -relativeIndex;
+ insertion->count -= -relativeIndex;
+ }
+
+ if (it->prepend()) {
+ // If the current range has the prepend flag preserve its flags to transfer
+ // to its new location.
+ removeFlags |= it->flags & CacheFlag;
+ translatedRemoval.moveId = ++m_moveId;
+ movedFlags->append(MovedFlags(m_moveId, it->flags & ~AppendFlag));
+
+ if (removeCount < removal->count) {
+ // If the remove doesn't encompass all of the current range,
+ // split it and the corresponding insert.
+ removal = removals->insert(removal, QQmlChangeSet::Change(
+ removal->index, removeCount, translatedRemoval.moveId));
+ ++removal;
+ insertion = insertions->insert(insertion, QQmlChangeSet::Change(
+ insertion->index, removeCount, translatedRemoval.moveId));
+ ++insertion;
+
+ removal->count -= removeCount;
+ insertion->index += removeCount;
+ insertion->count -= removeCount;
+ } else {
+ // If there's no need to split the move simply replace its moveId
+ // with that of the translated move.
+ removal->moveId = translatedRemoval.moveId;
+ insertion->moveId = translatedRemoval.moveId;
+ }
+ } else {
+ // If the current range doesn't have prepend flags then insert a new range
+ // with list indexes from the corresponding insert location. The MoveFlag
+ // is to notify listItemsInserted that it can skip evaluation of that range.
+ if (offset > 0) {
+ *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next;
+ it->index += offset;
+ it->count -= offset;
+ it.incrementIndexes(offset);
+ }
+ if (it->previous != &m_ranges
+ && it->previous->list == it->list
+ && it->end() == insertion->index
+ && it->previous->flags == (it->flags | MovedFlag)) {
+ it->previous->count += removeCount;
+ } else {
+ *it = insert(*it, it->list, insertion->index, removeCount, it->flags | MovedFlag)->next;
+ }
+ // Clear the changed flags as the item hasn't been removed.
+ translatedRemoval.flags = 0;
+ removeFlags = 0;
+ }
+ } else if (it->inCache()) {
+ // If not moving and the current range has the cache flag, insert a new range
+ // with just the cache flag set to retain caching information for the removed
+ // range.
+ if (offset > 0) {
+ *it = insert(*it, it->list, it->index, offset, it->flags & ~AppendFlag)->next;
+ it->index += offset;
+ it->count -= offset;
+ it.incrementIndexes(offset);
+ }
+ if (it->previous != &m_ranges
+ && it->previous->list == it->list
+ && it->previous->flags == CacheFlag) {
+ it->previous->count += removeCount;
+ } else {
+ *it = insert(*it, it->list, -1, removeCount, CacheFlag)->next;
+ }
+ it.index[Cache] += removeCount;
+ }
+ if (removeFlags & GroupMask)
+ translatedRemovals->append(translatedRemoval);
+ m_end.decrementIndexes(removeCount, removeFlags);
+ if (it->count == 0 && !it->append()) {
+ // Erase empty non-append ranges.
+ *it = erase(*it)->previous;
+ removed = true;
+ } else if (relativeIndex <= 0) {
+ // If the remove started before the current range move the start index of
+ // the range to the remove index.
+ it->index = removal->index;
+ }
+ } else if (relativeIndex < 0) {
+ // If the remove was before the current range decrement the start index by the
+ // number of items removed.
+ it->index -= itemsRemoved;
+
+ if (it->previous != &m_ranges
+ && it->previous->list == it->list
+ && it->previous->end() == it->index
+ && it->previous->flags == (it->flags & ~AppendFlag)) {
+ // Compress ranges made continuous by the removal of separating ranges.
+ it.decrementIndexes(it->previous->count);
+ it->previous->count += it->count;
+ it->previous->flags = it->flags;
+ *it = erase(*it)->previous;
+ }
+ }
+ }
+ if (it->flags == CacheFlag && it->next->flags == CacheFlag && it->next->list == it->list) {
+ // Compress consecutive cache only ranges.
+ it.index[Cache] += it->next->count;
+ it->count += it->next->count;
+ erase(it->next);
+ } else if (!removed) {
+ it.incrementIndexes(it->count);
+ }
+ }
+ m_cacheIt = m_end;
+ QT_QML_VERIFY_LISTCOMPOSITOR
+}
+
+
+/*!
+ Updates the contents of a compositor when \a count items are removed from a \a list at
+ \a index.
+
+ Ranges that intersect the specified range are removed from the compositor and the indexes of
+ ranges after index + count are updated.
+
+ The \a translatedRemovals list is populated with remove notifications for the affected
+ groups.
+*/
+
+
+void QQmlListCompositor::listItemsRemoved(
+ void *list, int index, int count, QVector<Remove> *translatedRemovals)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count)
+ Q_ASSERT(count >= 0);
+
+ QVector<QQmlChangeSet::Change> removals;
+ removals.append(QQmlChangeSet::Change(index, count));
+ listItemsRemoved(translatedRemovals, list, &removals, nullptr, nullptr);
+}
+
+/*!
+ Updates the contents of a compositor when \a count items are removed from a \a list at
+ \a index.
+
+ Ranges that intersect the specified range are removed from the compositor and the indexes of
+ ranges after index + count are updated.
+
+ The \a translatedRemovals and translatedInserts lists are populated with move
+ notifications for the affected groups.
+*/
+
+void QQmlListCompositor::listItemsMoved(
+ void *list,
+ int from,
+ int to,
+ int count,
+ QVector<Remove> *translatedRemovals,
+ QVector<Insert> *translatedInsertions)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << from << to << count)
+ Q_ASSERT(count >= 0);
+
+ QVector<QQmlChangeSet::Change> removals;
+ QVector<QQmlChangeSet::Change> insertions;
+ QVector<MovedFlags> movedFlags;
+ removals.append(QQmlChangeSet::Change(from, count, 0));
+ insertions.append(QQmlChangeSet::Change(to, count, 0));
+
+ listItemsRemoved(translatedRemovals, list, &removals, &insertions, &movedFlags);
+ listItemsInserted(translatedInsertions, list, insertions, &movedFlags);
+}
+
+void QQmlListCompositor::listItemsChanged(
+ QVector<Change> *translatedChanges,
+ void *list,
+ const QVector<QQmlChangeSet::Change> &changes)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << changes)
+ for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
+ if (it->list != list || it->flags == CacheFlag) {
+ it.incrementIndexes(it->count);
+ continue;
+ } else if (!it->inGroup()) {
+ continue;
+ }
+ for (const QQmlChangeSet::Change &change : changes) {
+ const int offset = change.index - it->index;
+ if (offset + change.count > 0 && offset < it->count) {
+ const int changeOffset = qMax(0, offset);
+ const int changeCount = qMin(it->count, offset + change.count) - changeOffset;
+
+ Change translatedChange(it, changeCount, it->flags);
+ for (int i = 0; i < m_groupCount; ++i) {
+ if (it->inGroup(i))
+ translatedChange.index[i] += changeOffset;
+ }
+ translatedChanges->append(translatedChange);
+ }
+ }
+ it.incrementIndexes(it->count);
+ }
+}
+
+
+/*!
+ Translates the positions of \a count changed items at \a index in a \a list.
+
+ The \a translatedChanges list is populated with change notifications for the
+ affected groups.
+*/
+
+void QQmlListCompositor::listItemsChanged(
+ void *list, int index, int count, QVector<Change> *translatedChanges)
+{
+ QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count)
+ Q_ASSERT(count >= 0);
+ QVector<QQmlChangeSet::Change> changes;
+ changes.append(QQmlChangeSet::Change(index, count));
+ listItemsChanged(translatedChanges, list, changes);
+}
+
+void QQmlListCompositor::transition(
+ Group from,
+ Group to,
+ QVector<QQmlChangeSet::Change> *removes,
+ QVector<QQmlChangeSet::Change> *inserts)
+{
+ int removeCount = 0;
+ for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
+ if (it == from && it != to) {
+ removes->append(QQmlChangeSet::Change(it.index[from]- removeCount, it->count));
+ removeCount += it->count;
+ } else if (it != from && it == to) {
+ inserts->append(QQmlChangeSet::Change(it.index[to], it->count));
+ }
+ it.incrementIndexes(it->count);
+ }
+}
+
+/*!
+ \internal
+ Writes the name of \a group to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group)
+{
+ switch (group) {
+ case QQmlListCompositor::Cache: return debug << "Cache";
+ case QQmlListCompositor::Default: return debug << "Default";
+ default: return (debug.nospace() << "Group" << int(group)).space();
+ }
+
+}
+
+/*!
+ \internal
+ Writes the contents of \a range to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range)
+{
+ (debug.nospace()
+ << "Range("
+ << range.list) << ' '
+ << range.index << ' '
+ << range.count << ' '
+ << (range.isUnresolved() ? 'U' : '0')
+ << (range.append() ? 'A' : '0')
+ << (range.prepend() ? 'P' : '0');
+ for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i)
+ debug << (range.inGroup(i) ? '1' : '0');
+ return (debug
+ << (range.inGroup(QQmlListCompositor::Default) ? 'D' : '0')
+ << (range.inGroup(QQmlListCompositor::Cache) ? 'C' : '0'));
+}
+
+static void qt_print_indexes(QDebug &debug, int count, const int *indexes)
+{
+ for (int i = count - 1; i >= 0; --i)
+ debug << indexes[i];
+}
+
+/*!
+ \internal
+ Writes the contents of \a it to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it)
+{
+ (debug.nospace() << "iterator(" << it.group).space() << "offset:" << it.offset;
+ qt_print_indexes(debug, it.groupCount, it.index);
+ return ((debug << **it).nospace() << ')').space();
+}
+
+static QDebug qt_print_change(QDebug debug, const char *name, const QQmlListCompositor::Change &change)
+{
+ debug.nospace() << name << '(' << change.moveId << ' ' << change.count << ' ';
+ for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i)
+ debug << (change.inGroup(i) ? '1' : '0');
+ debug << (change.inGroup(QQmlListCompositor::Default) ? 'D' : '0')
+ << (change.inGroup(QQmlListCompositor::Cache) ? 'C' : '0');
+ int i = QQmlListCompositor::MaximumGroupCount - 1;
+ for (; i >= 0 && !change.inGroup(i); --i) {}
+ for (; i >= 0; --i)
+ debug << ' ' << change.index[i];
+ return (debug << ')').maybeSpace();
+}
+
+/*!
+ \internal
+ Writes the contents of \a change to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change)
+{
+ return qt_print_change(debug, "Change", change);
+}
+
+/*!
+ \internal
+ Writes the contents of \a remove to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove)
+{
+ return qt_print_change(debug, "Remove", remove);
+}
+
+/*!
+ \internal
+ Writes the contents of \a insert to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert)
+{
+ return qt_print_change(debug, "Insert", insert);
+}
+
+/*!
+ \internal
+ Writes the contents of \a list to \a debug.
+*/
+
+QDebug operator <<(QDebug debug, const QQmlListCompositor &list)
+{
+ int indexes[QQmlListCompositor::MaximumGroupCount];
+ for (int i = 0; i < QQmlListCompositor::MaximumGroupCount; ++i)
+ indexes[i] = 0;
+ debug.nospace() << "QQmlListCompositor(";
+ qt_print_indexes(debug, list.m_groupCount, list.m_end.index);
+ for (QQmlListCompositor::Range *range = list.m_ranges.next; range != &list.m_ranges; range = range->next) {
+ (debug << '\n').space();
+ qt_print_indexes(debug, list.m_groupCount, indexes);
+ debug << ' ' << *range;
+
+ for (int i = 0; i < list.m_groupCount; ++i) {
+ if (range->inGroup(i))
+ indexes[i] += range->count;
+ }
+ }
+ return (debug << ')').maybeSpace();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlmodels/qqmllistcompositor_p.h b/src/qmlmodels/qqmllistcompositor_p.h
new file mode 100644
index 0000000000..172040559c
--- /dev/null
+++ b/src/qmlmodels/qqmllistcompositor_p.h
@@ -0,0 +1,372 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLLISTCOMPOSITOR_P_H
+#define QQMLLISTCOMPOSITOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qvector.h>
+
+#include <private/qqmlchangeset_p.h>
+
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_AUTOTEST_EXPORT QQmlListCompositor
+{
+public:
+ enum { MinimumGroupCount = 3, MaximumGroupCount = 11 };
+
+ enum Group
+ {
+ Cache = 0,
+ Default = 1,
+ Persisted = 2
+ };
+
+ enum Flag
+ {
+ CacheFlag = 1 << Cache,
+ DefaultFlag = 1 << Default,
+ PersistedFlag = 1 << Persisted,
+ PrependFlag = 0x10000000,
+ AppendFlag = 0x20000000,
+ UnresolvedFlag = 0x40000000,
+ MovedFlag = 0x80000000,
+ GroupMask = ~(PrependFlag | AppendFlag | UnresolvedFlag | MovedFlag | CacheFlag)
+ };
+
+ class Range
+ {
+ public:
+ Range() : next(this), previous(this) {}
+ Range(Range *next, void *list, int index, int count, uint flags)
+ : next(next), previous(next->previous), list(list), index(index), count(count), flags(flags) {
+ next->previous = this; previous->next = this; }
+
+ Range *next;
+ Range *previous;
+ void *list = nullptr;
+ int index = 0;
+ int count = 0;
+ uint flags = 0;
+
+ inline int start() const { return index; }
+ inline int end() const { return index + count; }
+
+ inline int groups() const { return flags & GroupMask; }
+
+ inline bool inGroup() const { return flags & GroupMask; }
+ inline bool inCache() const { return flags & CacheFlag; }
+ inline bool inGroup(int group) const { return flags & (1 << group); }
+ inline bool isUnresolved() const { return flags & UnresolvedFlag; }
+
+ inline bool prepend() const { return flags & PrependFlag; }
+ inline bool append() const { return flags & AppendFlag; }
+ };
+
+ class Q_AUTOTEST_EXPORT iterator
+ {
+ public:
+ inline iterator();
+ inline iterator(const iterator &it);
+ inline iterator(Range *range, int offset, Group group, int groupCount);
+ inline ~iterator() {}
+
+ bool operator ==(const iterator &it) const { return range == it.range && offset == it.offset; }
+ bool operator !=(const iterator &it) const { return range != it.range || offset != it.offset; }
+
+ bool operator ==(Group group) const { return range->flags & (1 << group); }
+ bool operator !=(Group group) const { return !(range->flags & (1 << group)); }
+
+ Range *&operator *() { return range; }
+ Range * const &operator *() const { return range; }
+ Range *operator ->() { return range; }
+ const Range *operator ->() const { return range; }
+
+ iterator &operator +=(int difference);
+
+ template<typename T> T *list() const { return static_cast<T *>(range->list); }
+ int modelIndex() const { return range->index + offset; }
+
+ void incrementIndexes(int difference) { incrementIndexes(difference, range->flags); }
+ void decrementIndexes(int difference) { decrementIndexes(difference, range->flags); }
+
+ inline void incrementIndexes(int difference, uint flags);
+ inline void decrementIndexes(int difference, uint flags);
+
+ void setGroup(Group g) { group = g; groupFlag = 1 << g; }
+
+ Range *range = nullptr;
+ int offset = 0;
+ Group group = Default;
+ int groupFlag;
+ int groupCount = 0;
+ union {
+ struct {
+ int cacheIndex;
+ };
+ int index[MaximumGroupCount];
+ };
+ };
+
+ class Q_AUTOTEST_EXPORT insert_iterator : public iterator
+ {
+ public:
+ inline insert_iterator() {}
+ inline insert_iterator(const iterator &it) : iterator(it) {}
+ inline insert_iterator(Range *, int, Group, int);
+ inline ~insert_iterator() {}
+
+ insert_iterator &operator +=(int difference);
+ };
+
+ struct Change
+ {
+ inline Change() {}
+ inline Change(const iterator &it, int count, uint flags, int moveId = -1);
+ int count;
+ uint flags;
+ int moveId;
+ union {
+ struct {
+ int cacheIndex;
+ };
+ int index[MaximumGroupCount];
+ };
+
+ inline bool isMove() const { return moveId >= 0; }
+ inline bool inCache() const { return flags & CacheFlag; }
+ inline bool inGroup() const { return flags & GroupMask; }
+ inline bool inGroup(int group) const { return flags & (CacheFlag << group); }
+
+ inline int groups() const { return flags & GroupMask; }
+ };
+
+ struct Insert : public Change
+ {
+ Insert() {}
+ Insert(const iterator &it, int count, uint flags, int moveId = -1)
+ : Change(it, count, flags, moveId) {}
+ };
+
+ struct Remove : public Change
+ {
+ Remove() {}
+ Remove(const iterator &it, int count, uint flags, int moveId = -1)
+ : Change(it, count, flags, moveId) {}
+ };
+
+ QQmlListCompositor();
+ ~QQmlListCompositor();
+
+ int defaultGroups() const { return m_defaultFlags & ~PrependFlag; }
+ void setDefaultGroups(int groups) { m_defaultFlags = groups | PrependFlag; }
+ void setDefaultGroup(Group group) { m_defaultFlags |= (1 << group); }
+ void clearDefaultGroup(Group group) { m_defaultFlags &= ~(1 << group); }
+ void setRemoveGroups(int groups) { m_removeFlags = PrependFlag | AppendFlag | groups; }
+ void setGroupCount(int count);
+
+ int count(Group group) const;
+ iterator find(Group group, int index);
+ iterator find(Group group, int index) const;
+ insert_iterator findInsertPosition(Group group, int index);
+
+ const iterator &end() { return m_end; }
+
+ void append(void *list, int index, int count, uint flags, QVector<Insert> *inserts = nullptr);
+ void insert(Group group, int before, void *list, int index, int count, uint flags, QVector<Insert> *inserts = nullptr);
+ iterator insert(iterator before, void *list, int index, int count, uint flags, QVector<Insert> *inserts = nullptr);
+
+ void setFlags(Group fromGroup, int from, int count, Group group, int flags, QVector<Insert> *inserts = nullptr);
+ void setFlags(iterator from, int count, Group group, uint flags, QVector<Insert> *inserts = nullptr);
+ void setFlags(Group fromGroup, int from, int count, uint flags, QVector<Insert> *inserts = nullptr) {
+ setFlags(fromGroup, from, count, fromGroup, flags, inserts); }
+ void setFlags(const iterator from, int count, uint flags, QVector<Insert> *inserts = nullptr) {
+ setFlags(from, count, from.group, flags, inserts); }
+
+ void clearFlags(Group fromGroup, int from, int count, Group group, uint flags, QVector<Remove> *removals = nullptr);
+ void clearFlags(iterator from, int count, Group group, uint flags, QVector<Remove> *removals = nullptr);
+ void clearFlags(Group fromGroup, int from, int count, uint flags, QVector<Remove> *removals = nullptr) {
+ clearFlags(fromGroup, from, count, fromGroup, flags, removals); }
+ void clearFlags(const iterator &from, int count, uint flags, QVector<Remove> *removals = nullptr) {
+ clearFlags(from, count, from.group, flags, removals); }
+
+ bool verifyMoveTo(Group fromGroup, int from, Group toGroup, int to, int count, Group group) const;
+
+ void move(
+ Group fromGroup,
+ int from,
+ Group toGroup,
+ int to,
+ int count,
+ Group group,
+ QVector<Remove> *removals = nullptr,
+ QVector<Insert> *inserts = nullptr);
+ void clear();
+
+ void listItemsInserted(void *list, int index, int count, QVector<Insert> *inserts);
+ void listItemsRemoved(void *list, int index, int count, QVector<Remove> *removals);
+ void listItemsMoved(void *list, int from, int to, int count, QVector<Remove> *removals, QVector<Insert> *inserts);
+ void listItemsChanged(void *list, int index, int count, QVector<Change> *changes);
+
+ void transition(
+ Group from,
+ Group to,
+ QVector<QQmlChangeSet::Change> *removes,
+ QVector<QQmlChangeSet::Change> *inserts);
+
+private:
+ Range m_ranges;
+ iterator m_end;
+ iterator m_cacheIt;
+ int m_groupCount;
+ int m_defaultFlags;
+ int m_removeFlags;
+ int m_moveId;
+
+ inline Range *insert(Range *before, void *list, int index, int count, uint flags);
+ inline Range *erase(Range *range);
+
+ struct MovedFlags
+ {
+ MovedFlags() {}
+ MovedFlags(int moveId, uint flags) : moveId(moveId), flags(flags) {}
+
+ int moveId;
+ uint flags;
+ };
+
+ void listItemsRemoved(
+ QVector<Remove> *translatedRemovals,
+ void *list,
+ QVector<QQmlChangeSet::Change> *removals,
+ QVector<QQmlChangeSet::Change> *insertions = nullptr,
+ QVector<MovedFlags> *movedFlags = nullptr);
+ void listItemsInserted(
+ QVector<Insert> *translatedInsertions,
+ void *list,
+ const QVector<QQmlChangeSet::Change> &insertions,
+ const QVector<MovedFlags> *movedFlags = nullptr);
+ void listItemsChanged(
+ QVector<Change> *translatedChanges,
+ void *list,
+ const QVector<QQmlChangeSet::Change> &changes);
+
+ friend Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list);
+};
+
+Q_DECLARE_TYPEINFO(QQmlListCompositor::Change, Q_PRIMITIVE_TYPE);
+Q_DECLARE_TYPEINFO(QQmlListCompositor::Remove, Q_PRIMITIVE_TYPE);
+Q_DECLARE_TYPEINFO(QQmlListCompositor::Insert, Q_PRIMITIVE_TYPE);
+
+inline QQmlListCompositor::iterator::iterator() {}
+inline QQmlListCompositor::iterator::iterator(const iterator &it)
+ : range(it.range)
+ , offset(it.offset)
+ , group(it.group)
+ , groupFlag(it.groupFlag)
+ , groupCount(it.groupCount)
+{
+ for (int i = 0; i < groupCount; ++i)
+ index[i] = it.index[i];
+}
+
+inline QQmlListCompositor::iterator::iterator(
+ Range *range, int offset, Group group, int groupCount)
+ : range(range)
+ , offset(offset)
+ , group(group)
+ , groupFlag(1 << group)
+ , groupCount(groupCount)
+{
+ for (int i = 0; i < groupCount; ++i)
+ index[i] = 0;
+}
+
+inline void QQmlListCompositor::iterator::incrementIndexes(int difference, uint flags)
+{
+ for (int i = 0; i < groupCount; ++i) {
+ if (flags & (1 << i))
+ index[i] += difference;
+ }
+}
+
+inline void QQmlListCompositor::iterator::decrementIndexes(int difference, uint flags)
+{
+ for (int i = 0; i < groupCount; ++i) {
+ if (flags & (1 << i))
+ index[i] -= difference;
+ }
+}
+
+inline QQmlListCompositor::insert_iterator::insert_iterator(
+ Range *range, int offset, Group group, int groupCount)
+ : iterator(range, offset, group, groupCount) {}
+
+inline QQmlListCompositor::Change::Change(const iterator &it, int count, uint flags, int moveId)
+ : count(count), flags(flags), moveId(moveId)
+{
+ for (int i = 0; i < MaximumGroupCount; ++i)
+ index[i] = it.index[i];
+}
+
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group);
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range);
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it);
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change);
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove);
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert);
+Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qmlmodels/qqmllistmodel.cpp b/src/qmlmodels/qqmllistmodel.cpp
new file mode 100644
index 0000000000..e0a66e7170
--- /dev/null
+++ b/src/qmlmodels/qqmllistmodel.cpp
@@ -0,0 +1,2920 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#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/qqmlnotifier_p.h>
+
+#include <private/qv4object_p.h>
+#include <private/qv4dateobject_p.h>
+#include <private/qv4objectiterator_p.h>
+#include <private/qv4alloca_p.h>
+#include <private/qv4lookup_p.h>
+#include <private/qv4qmlcontext_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*);
+
+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("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(QPointer<QObject>), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
+ const int dataAlignments[] = { sizeof(StringOrTranslation), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime), sizeof(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.count();
+ 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.count();
+ 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.count();
+ int newRoleCount = src->roles.count() - 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.type()) {
+ case QVariant::Double: type = Role::Number; break;
+ case QVariant::Int: type = Role::Number; break;
+ case QVariant::Bool: type = Role::Bool; break;
+ case QVariant::String: type = Role::String; break;
+ case QVariant::Map: type = Role::VariantMap; break;
+ case QVariant::DateTime: type = Role::DateTime; break;
+ case QVariant::UserType: {
+ 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 {
+ type = Role::List;
+ break;
+ }
+ }
+ default: 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(const QString &s)
+{
+ d.setFlag();
+ setString(s);
+}
+
+StringOrTranslation::StringOrTranslation(const QV4::CompiledData::Binding *binding)
+{
+ d.setFlag();
+ clear();
+ d = binding;
+}
+
+StringOrTranslation::~StringOrTranslation()
+{
+ clear();
+}
+
+void StringOrTranslation::setString(const QString &s)
+{
+ d.setFlag();
+ clear();
+ QStringData *stringData = const_cast<QString &>(s).data_ptr();
+ d = stringData;
+ if (stringData)
+ stringData->ref.ref();
+}
+
+void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding)
+{
+ d.setFlag();
+ clear();
+ d = binding;
+}
+
+QString StringOrTranslation::toString(const QQmlListModel *owner) const
+{
+ if (d.isNull())
+ return QString();
+ if (d.isT1()) {
+ QStringDataPtr holder = { d.asT1() };
+ holder.ptr->ref.ref();
+ return QString(holder);
+ }
+ if (!owner)
+ return QString();
+ return owner->m_compilationUnit->bindingValueAsString(d.asT2());
+}
+
+QString StringOrTranslation::asString() const
+{
+ if (d.isNull())
+ return QString();
+ if (!d.isT1())
+ return QString();
+ QStringDataPtr holder = { d.asT1() };
+ holder.ptr->ref.ref();
+ return QString(holder);
+}
+
+void StringOrTranslation::clear()
+{
+ if (QStringData *strData = d.isT1() ? d.asT1() : nullptr) {
+ if (!strData->ref.deref())
+ QStringData::deallocate(strData);
+ }
+ d = static_cast<QStringData *>(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;
+ QQmlData *ddata = new (ddataMemory) QQmlData;
+ ddata->ownMemory = false;
+ QObjectPrivate::get(e->m_objectCache)->declarativeData = ddata;
+ (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->elements.count(); ++i) {
+ ListElement *e = target->elements.at(i);
+ int uid = e->getUid();
+ ElementSync sync;
+ sync.target = e;
+ sync.targetIndex = i;
+ elementHash.insert(uid, sync);
+ }
+ for (int i = 0; i < src->elements.count(); ++i) {
+ ListElement *e = src->elements.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;
+ }
+ }
+
+ 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->elements.count() ; ++i) {
+ ListElement *element = target->elements.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;
+ if (targetModel)
+ targetModel->beginRemoveRows(QModelIndex(), i, i);
+ s.target->destroy(target->m_layout);
+ target->elements.removeOne(s.target);
+ 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->elements.count(); ++i) {
+ ListElement *srcElement = src->elements.at(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.append(targetElement);
+ }
+
+ target->updateCacheIndices();
+
+ // Update values stored in target meta objects
+ for (int i=0 ; i < target->elements.count() ; ++i) {
+ ListElement *e = target->elements[i];
+ 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;
+ for (int i = 0 ; i < target->elements.count() ; ++i) {
+ ListElement *element = target->elements.at(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();
+ } else {
+ targetModel->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
+ targetModel->endMoveRows();
+ }
+ }
+ hasChanges = true;
+ ++rowsInserted;
+ }
+ 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, elements.count()))
+ destroyer();
+
+ m_layout = nullptr;
+ if (m_modelCache && m_modelCache->m_primary == false)
+ delete m_modelCache;
+ m_modelCache = nullptr;
+}
+
+int ListModel::appendElement()
+{
+ int elementIndex = elements.count();
+ 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;
+ }
+
+ QPODVector<ListElement *, 4> store;
+ for (int i=0 ; i < (to-from) ; ++i)
+ store.append(elements[from+n+i]);
+ for (int i=0 ; i < n ; ++i)
+ store.append(elements[from+i]);
+ for (int i=0 ; i < store.count() ; ++i)
+ elements[from+i] = store[i];
+
+ updateCacheIndices(from, to + n);
+}
+
+void ListModel::newElement(int index)
+{
+ ListElement *e = new ListElement;
+ elements.insert(index, e);
+}
+
+void ListModel::updateCacheIndices(int start, int end)
+{
+ int count = elements.count();
+
+ if (end < 0 || end > count)
+ end = count;
+
+ for (int i = start; i < end; ++i) {
+ ListElement *e = elements.at(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::set(int elementIndex, QV4::Object *object, QVector<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>()) {
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, 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);
+ }
+
+ roleIndex = e->setListProperty(r, subModel);
+ } 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::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, v4, func);
+ roleIndex = e->setFunctionProperty(r, jsv);
+ } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
+ if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
+ QObject *o = wrapper->object();
+ const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+ if (role.type == ListLayout::Role::QObject)
+ roleIndex = e->setQObjectProperty(role, o);
+ } 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>()) {
+ 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);
+ }
+ } 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::Object *o = propertyValue->as<QV4::Object>()) {
+ if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
+ QObject *o = wrapper->object();
+ const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
+ if (r.type == ListLayout::Role::QObject)
+ e->setQObjectPropertyFast(r, o);
+ } 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(m_modelCache->engine())->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);
+ }
+ }
+ }
+}
+
+QVector<std::function<void()>> ListModel::remove(int index, int count)
+{
+ QVector<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.remove(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 < elements.count()) {
+ 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(QVector<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 < elements.count()) {
+ 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;
+}
+
+QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
+{
+ char *mem = getPropertyMemory(role);
+ QPointer<QObject> *o = reinterpret_cast<QPointer<QObject> *>(mem);
+ return o->data();
+}
+
+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;
+}
+
+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;
+}
+
+QPointer<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
+{
+ char *mem = getPropertyMemory(role);
+
+ bool existingGuard = false;
+ for (size_t i=0 ; i < sizeof(QPointer<QObject>) ; ++i) {
+ if (mem[i] != 0) {
+ existingGuard = true;
+ break;
+ }
+ }
+
+ QPointer<QObject> *o = nullptr;
+
+ if (existingGuard)
+ o = reinterpret_cast<QPointer<QObject> *>(mem);
+
+ return o;
+}
+
+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);
+ }
+ 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:
+ {
+ QPointer<QObject> *guard = reinterpret_cast<QPointer<QObject> *>(mem);
+ QObject *object = guard->data();
+ if (object)
+ data = QVariant::fromValue(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::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, QObject *o)
+{
+ int roleIndex = -1;
+
+ if (role.type == ListLayout::Role::QObject) {
+ char *mem = getPropertyMemory(role);
+ QPointer<QObject> *g = reinterpret_cast<QPointer<QObject> *>(mem);
+ bool existingGuard = false;
+ for (size_t i=0 ; i < sizeof(QPointer<QObject>) ; ++i) {
+ if (mem[i] != 0) {
+ existingGuard = true;
+ break;
+ }
+ }
+ bool changed;
+ if (existingGuard) {
+ changed = g->data() != o;
+ g->~QPointer();
+ } else {
+ changed = true;
+ }
+ new (mem) QPointer<QObject>(o);
+ if (changed)
+ 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::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);
+ new (mem) StringOrTranslation(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, QObject *o)
+{
+ char *mem = getPropertyMemory(role);
+ new (mem) QPointer<QObject>(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::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::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;
+}
+
+QVector<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
+{
+ QVector<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:
+ {
+ QObject *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:
+ {
+ QPointer<QObject> *guard = getGuardProperty(r);
+ if (guard)
+ guard->~QPointer();
+ }
+ 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::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::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::FunctionObject>()) {
+ QV4::ScopedFunctionObject f(scope, d);
+ QJSValue jsv;
+ QJSValuePrivate::setValue(&jsv, eng, 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) {
+ QObject *o = wrapper->object();
+ roleIndex = setQObjectProperty(role, o);
+ } else if (role.type == ListLayout::Role::VariantMap) {
+ roleIndex = setVariantMapProperty(role, o);
+ }
+ } 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();
+ QVector<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()
+{
+}
+
+QAbstractDynamicMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object)
+{
+ if (!m_initialized) {
+ m_initialized = true;
+ initialize();
+ }
+ return QQmlOpenMetaObject::toDynamicMetaObject(object);
+}
+
+ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj)
+{
+ QObjectPrivate *op = QObjectPrivate::get(obj);
+ return static_cast<ModelNodeMetaObject*>(op->metaObject);
+}
+
+void ModelNodeMetaObject::updateValues()
+{
+ 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 QVector<int> &roles)
+{
+ if (!m_initialized) {
+ emitDirectNotifies(roles.constData(), roles.count());
+ return;
+ }
+ int roleCount = roles.count();
+ 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, QVector<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);
+
+ ExecutionEngine *eng = that->engine();
+ const int elementIndex = that->d()->elementIndex();
+ int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng);
+ if (roleIndex != -1)
+ that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector<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());
+ const ListLayout::Role *role = that->d()->m_model->m_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 = that->d()->m_model->data(elementIndex, role->index);
+ return that->engine()->fromVariant(value);
+}
+
+ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
+{
+ lookup->getter = Lookup::getterFallback;
+ return lookup->getter(lookup, 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();
+ if (roleNameIndex < that->listModel()->roleCount()) {
+ Scope scope(that->engine());
+ const ListLayout::Role &role = that->listModel()->getExistingRole(roleNameIndex);
+ ++roleNameIndex;
+ ScopedString roleName(scope, v4->newString(role.name));
+ if (attrs)
+ *attrs = QV4::Attr_Data;
+ if (pd) {
+
+ QVariant value = that->d()->m_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)};
+ for (auto i = 0; i < size; i++) {
+ array->arrayPut(i, QJSValuePrivate::convertedToValue(v4, recursiveListModel->get(i)));
+ }
+ 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));
+ QVector<int> roles;
+ object->updateValues(obj, roles);
+ return object;
+}
+
+QVector<int> DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target)
+{
+ QVector<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, QVector<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.count();
+ 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.type() == QVariant::List) {
+ 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.type() == QVariant::List) {
+ 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, QVector<int>(1, roleIndex));
+ }
+}
+
+/*!
+ \qmltype ListModel
+ \instantiates QQmlListModel
+ \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.
+
+ \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
+ \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
+ \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 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 ../quick/threading/threadedlistmodel/timedisplay.qml 0
+
+ The included file, \tt dataloader.mjs, looks like this:
+
+ \snippet ../quick/threading/threadedlistmodel/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 Quick Examples - Threading}, {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;
+
+ m_layout = new ListLayout(orig->m_layout);
+ 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();
+ 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;
+
+ if (model->m_mainThread && model->m_agent)
+ model->m_agent->addref();
+
+ 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.count(); ++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.count(); ++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.count() ; ++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.count() ; ++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.count() ; ++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 QVector<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);
+ 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, QVector<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, QVector<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) {
+ for (int i = 0 ; i < m_roles.count() ; ++i)
+ roleNames.insert(i, m_roles.at(i).toUtf8());
+ } else {
+ for (int i = 0 ; i < m_listModel->roleCount() ; ++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->roleCount())
+ qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
+ else
+ m_dynamicRoles = true;
+ } else {
+ if (m_roles.count()) {
+ 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.count() : m_listModel->elementCount();
+}
+
+/*!
+ \qmlmethod ListModel::clear()
+
+ Deletes all content from the model.
+
+ \sa append(), remove()
+*/
+void QQmlListModel::clear()
+{
+ removeElements(0, count());
+}
+
+/*!
+ \qmlmethod ListModel::remove(int index, int count = 1)
+
+ Deletes the content at \a index from the model.
+
+ \sa clear()
+*/
+void QQmlListModel::remove(QQmlV4Function *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);
+
+ QVector<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 : toDestroy)
+ destroyer();
+}
+
+/*!
+ \qmlmethod 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(QQmlV4Function *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");
+ }
+}
+
+/*!
+ \qmlmethod 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;
+ }
+
+ QPODVector<DynamicRoleModelNode *, 4> store;
+ for (int i=0 ; i < (realTo-realFrom) ; ++i)
+ store.append(m_modelObjects[realFrom+realN+i]);
+ for (int i=0 ; i < realN ; ++i)
+ store.append(m_modelObjects[realFrom+i]);
+ for (int i=0 ; i < store.count() ; ++i)
+ m_modelObjects[realFrom+i] = store[i];
+
+ } else {
+ m_listModel->move(from, to, n);
+ }
+
+ if (m_mainThread)
+ endMoveRows();
+}
+
+/*!
+ \qmlmethod 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(QQmlV4Function *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.count();
+ 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 object 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}.
+
+ \sa append()
+*/
+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 QJSValue(engine(), result->asReturnedValue());
+}
+
+/*!
+ \qmlmethod 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::getValue(&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 {
+
+ QVector<int> roles;
+
+ if (m_dynamicRoles) {
+ m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
+ } else {
+ m_listModel->set(index, object, &roles);
+ }
+
+ if (roles.count())
+ emitItemsChanged(index, 1, roles);
+ }
+}
+
+/*!
+ \qmlmethod ListModel::setProperty(int index, string property, variant 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.count();
+ m_roles.append(property);
+ }
+ if (m_modelObjects[index]->setValue(property.toUtf8(), value))
+ emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
+ } else {
+ int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
+ if (roleIndex != -1)
+ emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
+ }
+}
+
+/*!
+ \qmlmethod 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::ExecutableCompilationUnit> &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)) {
+ QByteArray script = scriptStr.toUtf8();
+ bool ok;
+ evaluateEnum(script, &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, int outterElementIndex)
+{
+ const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);
+
+ bool roleSet = false;
+ if (binding->type >= 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);
+ QVariant vModel = QVariant::fromValue(subModel);
+ model->setOrCreateProperty(outterElementIndex, elementName, vModel);
+ }
+ }
+ }
+
+ 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, elementIndex);
+ }
+
+ } else {
+ QVariant value;
+
+ if (binding->isTranslationBinding()) {
+ value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
+ } else if (binding->evaluatesToString()) {
+ value = compilationUnit->bindingValueAsString(binding);
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Number) {
+ value = compilationUnit->bindingValueAsNumber(binding);
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) {
+ value = binding->valueAsBoolean();
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Null) {
+ value = QVariant::fromValue(nullptr);
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
+ QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
+ if (definesEmptyList(scriptStr)) {
+ const ListLayout::Role &role = model->getOrCreateListRole(elementName);
+ ListModel *emptyModel = new ListModel(role.subLayout, nullptr);
+ value = QVariant::fromValue(emptyModel);
+ } else if (binding->isFunctionExpression()) {
+ QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
+ Q_ASSERT(id != QQmlBinding::Invalid);
+
+ auto v4 = compilationUnit->engine;
+ QV4::Scope scope(v4);
+ // 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]));
+
+ QV4::ReturnedValue result = function->call(v4->globalObject, nullptr, 0);
+
+ QJSValue v;
+ QJSValuePrivate::setValue(&v, v4, result);
+ value.setValue<QJSValue>(v);
+ } else {
+ QByteArray script = scriptStr.toUtf8();
+ bool ok;
+ value = evaluateEnum(script, &ok);
+ }
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ model->setOrCreateProperty(outterElementIndex, elementName, value);
+ roleSet = true;
+ }
+ return roleSet;
+}
+
+void QQmlListModelParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &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, /*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(const QString &s)
+{
+ if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
+ for (int i=1; i<s.length()-1; i++) {
+ if (!s[i].isSpace())
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+
+/*!
+ \qmltype ListElement
+ \instantiates 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 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.cpp"
diff --git a/src/qmlmodels/qqmllistmodel_p.h b/src/qmlmodels/qqmllistmodel_p.h
new file mode 100644
index 0000000000..10d67c1c6f
--- /dev/null
+++ b/src/qmlmodels/qqmllistmodel_p.h
@@ -0,0 +1,209 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLLISTMODEL_H
+#define QQMLLISTMODEL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlmodelsglobal_p.h>
+#include <private/qqmlcustomparser_p.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QStringList>
+#include <QtCore/QHash>
+#include <QtCore/QList>
+#include <QtCore/QVariant>
+#include <QtCore/qabstractitemmodel.h>
+
+#include <private/qv4engine_p.h>
+#include <private/qpodvector_p.h>
+
+QT_REQUIRE_CONFIG(qml_list_model);
+
+QT_BEGIN_NAMESPACE
+
+
+class QQmlListModelWorkerAgent;
+class ListModel;
+class ListLayout;
+
+namespace QV4 {
+struct ModelObject;
+}
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlListModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_PROPERTY(bool dynamicRoles READ dynamicRoles WRITE setDynamicRoles)
+ Q_PROPERTY(QObject *agent READ agent CONSTANT REVISION(14))
+
+public:
+ QQmlListModel(QObject *parent=nullptr);
+ ~QQmlListModel();
+
+ QModelIndex index(int row, int column, const QModelIndex &parent) const override;
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+ QHash<int,QByteArray> roleNames() const override;
+
+ QVariant data(int index, int role) const;
+ int count() const;
+
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE void remove(QQmlV4Function *args);
+ Q_INVOKABLE void append(QQmlV4Function *args);
+ Q_INVOKABLE void insert(QQmlV4Function *args);
+ Q_INVOKABLE QJSValue get(int index) const;
+ Q_INVOKABLE void set(int index, const QJSValue &value);
+ Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value);
+ Q_INVOKABLE void move(int from, int to, int count);
+ Q_INVOKABLE void sync();
+
+ QQmlListModelWorkerAgent *agent();
+
+ bool dynamicRoles() const { return m_dynamicRoles; }
+ void setDynamicRoles(bool enableDynamicRoles);
+
+Q_SIGNALS:
+ void countChanged();
+
+private:
+ friend class QQmlListModelParser;
+ friend class QQmlListModelWorkerAgent;
+ friend class ModelObject;
+ friend struct QV4::ModelObject;
+ friend class ModelNodeMetaObject;
+ friend class ListModel;
+ friend class ListElement;
+ friend class DynamicRoleModelNode;
+ friend class DynamicRoleModelNodeMetaObject;
+ friend struct StringOrTranslation;
+
+ // Constructs a flat list model for a worker agent
+ QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent);
+ QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent=nullptr);
+
+ QV4::ExecutionEngine *engine() const;
+
+ inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); }
+
+ mutable QQmlListModelWorkerAgent *m_agent;
+ mutable QV4::ExecutionEngine *m_engine;
+ QQmlRefPointer<QV4::ExecutableCompilationUnit> m_compilationUnit;
+ bool m_mainThread;
+ bool m_primary;
+
+ bool m_dynamicRoles;
+
+ ListLayout *m_layout;
+ ListModel *m_listModel;
+
+ QVector<class DynamicRoleModelNode *> m_modelObjects;
+ QVector<QString> m_roles;
+
+ struct ElementSync
+ {
+ DynamicRoleModelNode *src = nullptr;
+ DynamicRoleModelNode *target = nullptr;
+ int srcIndex = -1;
+ int targetIndex = -1;
+ QVector<int> changedRoles;
+ };
+
+ static bool sync(QQmlListModel *src, QQmlListModel *target);
+ static QQmlListModel *createWithOwner(QQmlListModel *newOwner);
+
+ void emitItemsChanged(int index, int count, const QVector<int> &roles);
+ void emitItemsAboutToBeInserted(int index, int count);
+ void emitItemsInserted();
+
+ void removeElements(int index, int removeCount);
+};
+
+// ### FIXME
+class QQmlListElement : public QObject
+{
+Q_OBJECT
+};
+
+class QQmlListModelParser : public QQmlCustomParser
+{
+public:
+ enum PropertyType {
+ Invalid,
+ Boolean,
+ Number,
+ String,
+ Script
+ };
+
+
+ QQmlListModelParser() : QQmlCustomParser(QQmlCustomParser::AcceptsSignalHandlers) {}
+
+ void verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) override;
+ void applyBindings(QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) override;
+
+private:
+ bool verifyProperty(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding);
+ // returns true if a role was set
+ bool applyProperty(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex);
+
+ static bool definesEmptyList(const QString &);
+
+ QString listElementTypeName;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlListModel)
+QML_DECLARE_TYPE(QQmlListElement)
+
+#endif // QQMLLISTMODEL_H
diff --git a/src/qmlmodels/qqmllistmodel_p_p.h b/src/qmlmodels/qqmllistmodel_p_p.h
new file mode 100644
index 0000000000..2ad5158050
--- /dev/null
+++ b/src/qmlmodels/qqmllistmodel_p_p.h
@@ -0,0 +1,431 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLLISTMODEL_P_P_H
+#define QQMLLISTMODEL_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmllistmodel_p.h"
+#include <private/qtqmlmodelsglobal_p.h>
+#include <private/qqmlengine_p.h>
+#include <private/qqmlopenmetaobject_p.h>
+#include <private/qv4qobjectwrapper_p.h>
+#include <qqml.h>
+
+QT_REQUIRE_CONFIG(qml_list_model);
+
+QT_BEGIN_NAMESPACE
+
+
+class DynamicRoleModelNode;
+
+class DynamicRoleModelNodeMetaObject : public QQmlOpenMetaObject
+{
+public:
+ DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object);
+ ~DynamicRoleModelNodeMetaObject();
+
+ bool m_enabled;
+
+protected:
+ void propertyWrite(int index) override;
+ void propertyWritten(int index) override;
+
+private:
+ DynamicRoleModelNode *m_owner;
+};
+
+class DynamicRoleModelNode : public QObject
+{
+ Q_OBJECT
+public:
+ DynamicRoleModelNode(QQmlListModel *owner, int uid);
+
+ static DynamicRoleModelNode *create(const QVariantMap &obj, QQmlListModel *owner);
+
+ void updateValues(const QVariantMap &object, QVector<int> &roles);
+
+ QVariant getValue(const QString &name) const
+ {
+ return m_meta->value(name.toUtf8());
+ }
+
+ bool setValue(const QByteArray &name, const QVariant &val)
+ {
+ return m_meta->setValue(name, val);
+ }
+
+ void setNodeUpdatesEnabled(bool enable)
+ {
+ m_meta->m_enabled = enable;
+ }
+
+ int getUid() const
+ {
+ return m_uid;
+ }
+
+ static QVector<int> sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target);
+
+private:
+ QQmlListModel *m_owner;
+ int m_uid;
+ DynamicRoleModelNodeMetaObject *m_meta;
+
+ friend class DynamicRoleModelNodeMetaObject;
+};
+
+class ModelNodeMetaObject : public QQmlOpenMetaObject
+{
+public:
+ ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex);
+ ~ModelNodeMetaObject();
+
+ QAbstractDynamicMetaObject *toDynamicMetaObject(QObject *object) override;
+
+ static ModelNodeMetaObject *get(QObject *obj);
+
+ bool m_enabled;
+ QQmlListModel *m_model;
+ int m_elementIndex;
+
+ void updateValues();
+ void updateValues(const QVector<int> &roles);
+
+ bool initialized() const { return m_initialized; }
+
+protected:
+ void propertyWritten(int index) override;
+
+private:
+ using QQmlOpenMetaObject::setValue;
+
+ void emitDirectNotifies(const int *changedRoles, int roleCount);
+
+ void initialize();
+ bool m_initialized;
+};
+
+namespace QV4 {
+
+namespace Heap {
+
+struct ModelObject : public QObjectWrapper {
+ void init(QObject *object, QQmlListModel *model)
+ {
+ QObjectWrapper::init(object);
+ m_model = model;
+ QObjectPrivate *op = QObjectPrivate::get(object);
+ m_nodeModelMetaObject = static_cast<ModelNodeMetaObject *>(op->metaObject);
+ }
+ void destroy() { QObjectWrapper::destroy(); }
+ int elementIndex() const { return m_nodeModelMetaObject->m_elementIndex; }
+ QQmlListModel *m_model;
+ ModelNodeMetaObject *m_nodeModelMetaObject;
+};
+
+}
+
+struct ModelObject : public QObjectWrapper
+{
+ V4_OBJECT2(ModelObject, QObjectWrapper)
+ V4_NEEDS_DESTROY
+
+ ListModel *listModel() const { return d()->m_model->m_listModel; }
+
+protected:
+ static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver);
+ static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty);
+ static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup);
+ static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object);
+ static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target);
+};
+
+} // namespace QV4
+
+class ListLayout
+{
+public:
+ ListLayout() : currentBlock(0), currentBlockOffset(0) {}
+ ListLayout(const ListLayout *other);
+ ~ListLayout();
+
+ class Role
+ {
+ public:
+
+ Role() : type(Invalid), blockIndex(-1), blockOffset(-1), index(-1), subLayout(0) {}
+ explicit Role(const Role *other);
+ ~Role();
+
+ // This enum must be kept in sync with the roleTypeNames variable in qqmllistmodel.cpp
+ enum DataType
+ {
+ Invalid = -1,
+
+ String,
+ Number,
+ Bool,
+ List,
+ QObject,
+ VariantMap,
+ DateTime,
+ Function,
+
+ MaxDataType
+ };
+
+ QString name;
+ DataType type;
+ int blockIndex;
+ int blockOffset;
+ int index;
+ ListLayout *subLayout;
+ };
+
+ const Role *getRoleOrCreate(const QString &key, const QVariant &data);
+ const Role &getRoleOrCreate(QV4::String *key, Role::DataType type);
+ const Role &getRoleOrCreate(const QString &key, Role::DataType type);
+
+ const Role &getExistingRole(int index) const { return *roles.at(index); }
+ const Role *getExistingRole(const QString &key) const;
+ const Role *getExistingRole(QV4::String *key) const;
+
+ int roleCount() const { return roles.count(); }
+
+ static void sync(ListLayout *src, ListLayout *target);
+
+private:
+ const Role &createRole(const QString &key, Role::DataType type);
+
+ int currentBlock;
+ int currentBlockOffset;
+ QVector<Role *> roles;
+ QStringHash<Role *> roleHash;
+};
+
+struct StringOrTranslation
+{
+ explicit StringOrTranslation(const QString &s);
+ explicit StringOrTranslation(const QV4::CompiledData::Binding *binding);
+ ~StringOrTranslation();
+ bool isSet() const { return d.flag(); }
+ bool isTranslation() const { return d.isT2(); }
+ void setString(const QString &s);
+ void setTranslation(const QV4::CompiledData::Binding *binding);
+ QString toString(const QQmlListModel *owner) const;
+ QString asString() const;
+private:
+ void clear();
+ QBiPointer<QStringData, const QV4::CompiledData::Binding> d;
+};
+
+/*!
+\internal
+*/
+class ListElement
+{
+public:
+
+ ListElement();
+ ListElement(int existingUid);
+ ~ListElement();
+
+ static QVector<int> sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout);
+
+ enum
+ {
+ BLOCK_SIZE = 64 - sizeof(int) - sizeof(ListElement *) - sizeof(ModelNodeMetaObject *)
+ };
+
+private:
+
+ void destroy(ListLayout *layout);
+
+ int setVariantProperty(const ListLayout::Role &role, const QVariant &d);
+
+ int setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng);
+
+ int setStringProperty(const ListLayout::Role &role, const QString &s);
+ int setDoubleProperty(const ListLayout::Role &role, double n);
+ int setBoolProperty(const ListLayout::Role &role, bool b);
+ int setListProperty(const ListLayout::Role &role, ListModel *m);
+ int setQObjectProperty(const ListLayout::Role &role, QObject *o);
+ int setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o);
+ int setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m);
+ int setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt);
+ int setFunctionProperty(const ListLayout::Role &role, const QJSValue &f);
+ int setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b);
+
+ void setStringPropertyFast(const ListLayout::Role &role, const QString &s);
+ void setDoublePropertyFast(const ListLayout::Role &role, double n);
+ void setBoolPropertyFast(const ListLayout::Role &role, bool b);
+ void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o);
+ void setListPropertyFast(const ListLayout::Role &role, ListModel *m);
+ void setVariantMapFast(const ListLayout::Role &role, QV4::Object *o);
+ void setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt);
+ void setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f);
+
+ void clearProperty(const ListLayout::Role &role);
+
+ QVariant getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng);
+ ListModel *getListProperty(const ListLayout::Role &role);
+ StringOrTranslation *getStringProperty(const ListLayout::Role &role);
+ QObject *getQObjectProperty(const ListLayout::Role &role);
+ QPointer<QObject> *getGuardProperty(const ListLayout::Role &role);
+ QVariantMap *getVariantMapProperty(const ListLayout::Role &role);
+ QDateTime *getDateTimeProperty(const ListLayout::Role &role);
+ QJSValue *getFunctionProperty(const ListLayout::Role &role);
+
+ inline char *getPropertyMemory(const ListLayout::Role &role);
+
+ int getUid() const { return uid; }
+
+ ModelNodeMetaObject *objectCache();
+
+ char data[BLOCK_SIZE];
+ ListElement *next;
+
+ int uid;
+ QObject *m_objectCache;
+
+ friend class ListModel;
+};
+
+/*!
+\internal
+*/
+class ListModel
+{
+public:
+
+ ListModel(ListLayout *layout, QQmlListModel *modelCache);
+ ~ListModel() {}
+
+ void destroy();
+
+ int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data);
+ int setExistingProperty(int uid, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng);
+
+ QVariant getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng);
+ ListModel *getListProperty(int elementIndex, const ListLayout::Role &role);
+
+ int roleCount() const
+ {
+ return m_layout->roleCount();
+ }
+
+ const ListLayout::Role &getExistingRole(int index) const
+ {
+ return m_layout->getExistingRole(index);
+ }
+
+ const ListLayout::Role *getExistingRole(QV4::String *key) const
+ {
+ return m_layout->getExistingRole(key);
+ }
+
+ const ListLayout::Role &getOrCreateListRole(const QString &name)
+ {
+ return m_layout->getRoleOrCreate(name, ListLayout::Role::List);
+ }
+
+ int elementCount() const
+ {
+ return elements.count();
+ }
+
+ enum class SetElement {WasJustInserted, IsCurrentlyUpdated};
+
+ void set(int elementIndex, QV4::Object *object, QVector<int> *roles);
+ void set(int elementIndex, QV4::Object *object, SetElement reason = SetElement::IsCurrentlyUpdated);
+
+ int append(QV4::Object *object);
+ void insert(int elementIndex, QV4::Object *object);
+
+ Q_REQUIRED_RESULT QVector<std::function<void()>> remove(int index, int count);
+
+ int appendElement();
+ void insertElement(int index);
+
+ void move(int from, int to, int n);
+
+ static bool sync(ListModel *src, ListModel *target);
+
+ QObject *getOrCreateModelObject(QQmlListModel *model, int elementIndex);
+
+private:
+ QPODVector<ListElement *, 4> elements;
+ ListLayout *m_layout;
+
+ QQmlListModel *m_modelCache;
+
+ struct ElementSync
+ {
+ ListElement *src = nullptr;
+ ListElement *target = nullptr;
+ int srcIndex = -1;
+ int targetIndex = -1;
+ QVector<int> changedRoles;
+ };
+
+ void newElement(int index);
+
+ void updateCacheIndices(int start = 0, int end = -1);
+
+ friend class ListElement;
+ friend class QQmlListModelWorkerAgent;
+ friend class QQmlListModelParser;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(ListModel *);
+
+#endif // QQUICKLISTMODEL_P_P_H
diff --git a/src/qmlmodels/qqmllistmodelworkeragent.cpp b/src/qmlmodels/qqmllistmodelworkeragent.cpp
new file mode 100644
index 0000000000..7e92810f78
--- /dev/null
+++ b/src/qmlmodels/qqmllistmodelworkeragent.cpp
@@ -0,0 +1,185 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmllistmodelworkeragent_p.h"
+#include "qqmllistmodel_p_p.h"
+#include <private/qqmldata_p.h>
+#include <private/qqmlengine_p.h>
+#include <qqmlinfo.h>
+
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+
+
+QT_BEGIN_NAMESPACE
+
+QQmlListModelWorkerAgent::Sync::~Sync()
+{
+}
+
+QQmlListModelWorkerAgent::QQmlListModelWorkerAgent(QQmlListModel *model)
+: m_ref(1), m_orig(model), m_copy(new QQmlListModel(model, this))
+{
+}
+
+QQmlListModelWorkerAgent::~QQmlListModelWorkerAgent()
+{
+ mutex.lock();
+ syncDone.wakeAll();
+ mutex.unlock();
+}
+
+QV4::ExecutionEngine *QQmlListModelWorkerAgent::engine() const
+{
+ return m_copy->m_engine;
+}
+
+void QQmlListModelWorkerAgent::setEngine(QV4::ExecutionEngine *eng)
+{
+ if (eng != m_copy->m_engine) {
+ m_copy->m_engine = eng;
+ emit engineChanged(eng);
+ }
+}
+
+void QQmlListModelWorkerAgent::addref()
+{
+ m_ref.ref();
+}
+
+void QQmlListModelWorkerAgent::release()
+{
+ bool del = !m_ref.deref();
+
+ if (del)
+ deleteLater();
+}
+
+void QQmlListModelWorkerAgent::modelDestroyed()
+{
+ m_orig = nullptr;
+}
+
+int QQmlListModelWorkerAgent::count() const
+{
+ return m_copy->count();
+}
+
+void QQmlListModelWorkerAgent::clear()
+{
+ m_copy->clear();
+}
+
+void QQmlListModelWorkerAgent::remove(QQmlV4Function *args)
+{
+ m_copy->remove(args);
+}
+
+void QQmlListModelWorkerAgent::append(QQmlV4Function *args)
+{
+ m_copy->append(args);
+}
+
+void QQmlListModelWorkerAgent::insert(QQmlV4Function *args)
+{
+ m_copy->insert(args);
+}
+
+QJSValue QQmlListModelWorkerAgent::get(int index) const
+{
+ return m_copy->get(index);
+}
+
+void QQmlListModelWorkerAgent::set(int index, const QJSValue &value)
+{
+ m_copy->set(index, value);
+}
+
+void QQmlListModelWorkerAgent::setProperty(int index, const QString& property, const QVariant& value)
+{
+ m_copy->setProperty(index, property, value);
+}
+
+void QQmlListModelWorkerAgent::move(int from, int to, int count)
+{
+ m_copy->move(from, to, count);
+}
+
+void QQmlListModelWorkerAgent::sync()
+{
+ Sync *s = new Sync(m_copy);
+
+ mutex.lock();
+ QCoreApplication::postEvent(this, s);
+ syncDone.wait(&mutex);
+ mutex.unlock();
+}
+
+bool QQmlListModelWorkerAgent::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ bool cc = false;
+ QMutexLocker locker(&mutex);
+ if (m_orig) {
+ Sync *s = static_cast<Sync *>(e);
+
+ cc = (m_orig->count() != s->list->count());
+
+ Q_ASSERT(m_orig->m_dynamicRoles == s->list->m_dynamicRoles);
+ if (m_orig->m_dynamicRoles)
+ QQmlListModel::sync(s->list, m_orig);
+ else
+ ListModel::sync(s->list->m_listModel, m_orig->m_listModel);
+ }
+
+ syncDone.wakeAll();
+ locker.unlock();
+
+ if (cc)
+ emit m_orig->countChanged();
+ return true;
+ }
+
+ return QObject::event(e);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqmllistmodelworkeragent_p.cpp"
diff --git a/src/qmlmodels/qqmllistmodelworkeragent_p.h b/src/qmlmodels/qqmllistmodelworkeragent_p.h
new file mode 100644
index 0000000000..1ef27cea3f
--- /dev/null
+++ b/src/qmlmodels/qqmllistmodelworkeragent_p.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQUICKLISTMODELWORKERAGENT_P_H
+#define QQUICKLISTMODELWORKERAGENT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qtqmlmodelsglobal_p.h>
+
+#include <QEvent>
+#include <QMutex>
+#include <QWaitCondition>
+
+#include <private/qv4engine_p.h>
+
+QT_REQUIRE_CONFIG(qml_list_model);
+
+QT_BEGIN_NAMESPACE
+
+
+class QQmlListModel;
+
+class QQmlListModelWorkerAgent : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int count READ count)
+ Q_PROPERTY(QV4::ExecutionEngine *engine READ engine WRITE setEngine NOTIFY engineChanged)
+
+public:
+ QQmlListModelWorkerAgent(QQmlListModel *);
+ ~QQmlListModelWorkerAgent();
+
+ QV4::ExecutionEngine *engine() const;
+ void setEngine(QV4::ExecutionEngine *eng);
+
+ Q_INVOKABLE void addref();
+ Q_INVOKABLE void release();
+
+ int count() const;
+
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE void remove(QQmlV4Function *args);
+ Q_INVOKABLE void append(QQmlV4Function *args);
+ Q_INVOKABLE void insert(QQmlV4Function *args);
+ Q_INVOKABLE QJSValue get(int index) const;
+ Q_INVOKABLE void set(int index, const QJSValue &value);
+ Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value);
+ Q_INVOKABLE void move(int from, int to, int count);
+ Q_INVOKABLE void sync();
+
+ void modelDestroyed();
+
+signals:
+ void engineChanged(QV4::ExecutionEngine *engine);
+
+protected:
+ bool event(QEvent *) override;
+
+private:
+ friend class QQuickWorkerScriptEnginePrivate;
+ friend class QQmlListModel;
+
+ struct Sync : public QEvent {
+ Sync(QQmlListModel *l)
+ : QEvent(QEvent::User)
+ , list(l)
+ {}
+ ~Sync();
+ QQmlListModel *list;
+ };
+
+ QAtomicInt m_ref;
+ QQmlListModel *m_orig;
+ QQmlListModel *m_copy;
+ QMutex mutex;
+ QWaitCondition syncDone;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKLISTMODELWORKERAGENT_P_H
+
diff --git a/src/qmlmodels/qqmlmodelsmodule.cpp b/src/qmlmodels/qqmlmodelsmodule.cpp
new file mode 100644
index 0000000000..d569d8e23c
--- /dev/null
+++ b/src/qmlmodels/qqmlmodelsmodule.cpp
@@ -0,0 +1,139 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Research In Motion.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmlmodelsmodule_p.h"
+#include <private/qtqmlmodelsglobal_p.h>
+
+#if QT_CONFIG(itemmodel)
+#include <QtCore/qitemselectionmodel.h>
+#endif
+#if QT_CONFIG(qml_list_model)
+#include <private/qqmllistmodel_p.h>
+#endif
+#if QT_CONFIG(qml_delegate_model)
+#include <private/qqmldelegatemodel_p.h>
+#include <private/qqmldelegatecomponent_p.h>
+#include <private/qquickpackage_p.h>
+#endif
+#if QT_CONFIG(qml_object_model)
+#include <private/qqmlobjectmodel_p.h>
+#include <private/qqmlinstantiator_p.h>
+#endif
+#if QT_CONFIG(qml_table_model)
+#include <private/qqmltablemodel_p.h>
+#include <private/qqmltablemodelcolumn_p.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+
+void QQmlModelsModule::registerQmlTypes()
+{
+ // Don't add anything here. These are only for backwards compatibility.
+#if QT_CONFIG(qml_object_model)
+ qmlRegisterType<QQmlInstantiator>("QtQml", 2, 1, "Instantiator"); // Only available in >= 2.1
+ qmlRegisterAnonymousType<QQmlInstanceModel>("QtQml", 2);
+#endif
+}
+
+void QQmlModelsModule::registerQuickTypes()
+{
+ // Don't add anything here. These are only for backwards compatibility.
+
+ const char uri[] = "QtQuick";
+
+#if QT_CONFIG(qml_object_model)
+ qmlRegisterType<QQmlInstantiator>(uri, 2, 1, "Instantiator");
+ qmlRegisterAnonymousType<QQmlInstanceModel>(uri, 2);
+ qmlRegisterType<QQmlObjectModel>(uri, 2, 0, "VisualItemModel");
+#endif
+#if QT_CONFIG(qml_list_model)
+ qmlRegisterType<QQmlListElement>(uri, 2, 0, "ListElement");
+ qmlRegisterCustomType<QQmlListModel>(uri, 2, 0, "ListModel", new QQmlListModelParser);
+#endif
+#if QT_CONFIG(qml_delegate_model)
+ qmlRegisterType<QQmlDelegateModel>(uri, 2, 0, "VisualDataModel");
+ qmlRegisterType<QQmlDelegateModelGroup>(uri, 2, 0, "VisualDataGroup");
+ qmlRegisterType<QQuickPackage>(uri, 2, 0, "Package");
+#endif
+}
+
+#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+
+void QQmlModelsModule::defineModule()
+{
+ const char uri[] = "QtQml.Models";
+
+#if QT_CONFIG(qml_list_model)
+ qmlRegisterType<QQmlListElement>(uri, 2, 1, "ListElement");
+ qmlRegisterCustomType<QQmlListModel>(uri, 2, 1, "ListModel", new QQmlListModelParser);
+#endif
+#if QT_CONFIG(qml_delegate_model)
+ qmlRegisterType<QQmlDelegateModel>(uri, 2, 1, "DelegateModel");
+ qmlRegisterType<QQmlDelegateModelGroup>(uri, 2, 1, "DelegateModelGroup");
+ qmlRegisterType<QQuickPackage>(uri, 2, 14, "Package");
+#endif
+#if QT_CONFIG(qml_object_model)
+ qmlRegisterType<QQmlObjectModel>(uri, 2, 1, "ObjectModel");
+ qmlRegisterType<QQmlObjectModel,3>(uri, 2, 3, "ObjectModel");
+ qmlRegisterType<QQmlInstantiator>(uri, 2, 14, "Instantiator");
+ qmlRegisterAnonymousType<QQmlInstanceModel>(uri, 2);
+#endif
+#if QT_CONFIG(itemmodel)
+ qmlRegisterType<QItemSelectionModel>(uri, 2, 2, "ItemSelectionModel");
+#endif
+}
+
+void QQmlModelsModule::defineLabsModule()
+{
+ const char uri[] = "Qt.labs.qmlmodels";
+
+#if QT_CONFIG(qml_delegate_model)
+ qmlRegisterUncreatableType<QQmlAbstractDelegateComponent>(uri, 1, 0, "AbstractDelegateComponent", QQmlAbstractDelegateComponent::tr("Cannot create instance of abstract class AbstractDelegateComponent."));
+ qmlRegisterType<QQmlDelegateChooser>(uri, 1, 0, "DelegateChooser");
+ qmlRegisterType<QQmlDelegateChoice>(uri, 1, 0, "DelegateChoice");
+#endif
+#if QT_CONFIG(qml_table_model)
+ qmlRegisterType<QQmlTableModel>(uri, 1, 0, "TableModel");
+ qmlRegisterType<QQmlTableModelColumn>(uri, 1, 0, "TableModelColumn");
+#endif
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlmodels/qqmlmodelsmodule_p.h b/src/qmlmodels/qqmlmodelsmodule_p.h
new file mode 100644
index 0000000000..7e02578db9
--- /dev/null
+++ b/src/qmlmodels/qqmlmodelsmodule_p.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Research In Motion.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLMODELSMODULE_H
+#define QQMLMODELSMODULE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlmodelsglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlModelsModule
+{
+public:
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ static void registerQmlTypes();
+ static void registerQuickTypes();
+#endif
+
+ static void defineModule();
+ static void defineLabsModule();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qmlmodels/qqmlobjectmodel.cpp b/src/qmlmodels/qqmlobjectmodel.cpp
new file mode 100644
index 0000000000..7409178616
--- /dev/null
+++ b/src/qmlmodels/qqmlobjectmodel.cpp
@@ -0,0 +1,431 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmlobjectmodel_p.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlinfo.h>
+
+#include <private/qqmlchangeset_p.h>
+#include <private/qqmlglobal_p.h>
+#include <private/qobject_p.h>
+#include <private/qpodvector_p.h>
+
+#include <QtCore/qhash.h>
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+QHash<QObject*, QQmlObjectModelAttached*> QQmlObjectModelAttached::attachedProperties;
+
+
+class QQmlObjectModelPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QQmlObjectModel)
+public:
+ class Item {
+ public:
+ Item(QObject *i) : item(i), ref(0) {}
+
+ void addRef() { ++ref; }
+ bool deref() { return --ref == 0; }
+
+ QObject *item;
+ int ref;
+ };
+
+ QQmlObjectModelPrivate() : QObjectPrivate(), moveId(0) {}
+
+ static void children_append(QQmlListProperty<QObject> *prop, QObject *item) {
+ int index = static_cast<QQmlObjectModelPrivate *>(prop->data)->children.count();
+ static_cast<QQmlObjectModelPrivate *>(prop->data)->insert(index, item);
+ }
+
+ static int children_count(QQmlListProperty<QObject> *prop) {
+ return static_cast<QQmlObjectModelPrivate *>(prop->data)->children.count();
+ }
+
+ static QObject *children_at(QQmlListProperty<QObject> *prop, int index) {
+ return static_cast<QQmlObjectModelPrivate *>(prop->data)->children.at(index).item;
+ }
+
+ static void children_clear(QQmlListProperty<QObject> *prop) {
+ static_cast<QQmlObjectModelPrivate *>(prop->data)->clear();
+ }
+
+ void insert(int index, QObject *item) {
+ Q_Q(QQmlObjectModel);
+ children.insert(index, Item(item));
+ for (int i = index; i < children.count(); ++i) {
+ QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item);
+ attached->setIndex(i);
+ }
+ QQmlChangeSet changeSet;
+ changeSet.insert(index, 1);
+ emit q->modelUpdated(changeSet, false);
+ emit q->countChanged();
+ emit q->childrenChanged();
+ }
+
+ void move(int from, int to, int n) {
+ Q_Q(QQmlObjectModel);
+ if (from > to) {
+ // Only move forwards - flip if backwards moving
+ int tfrom = from;
+ int tto = to;
+ from = tto;
+ to = tto+n;
+ n = tfrom-tto;
+ }
+
+ QPODVector<QQmlObjectModelPrivate::Item, 4> store;
+ for (int i = 0; i < to - from; ++i)
+ store.append(children[from + n + i]);
+ for (int i = 0; i < n; ++i)
+ store.append(children[from + i]);
+
+ for (int i = 0; i < store.count(); ++i) {
+ children[from + i] = store[i];
+ QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(from + i).item);
+ attached->setIndex(from + i);
+ }
+
+ QQmlChangeSet changeSet;
+ changeSet.move(from, to, n, ++moveId);
+ emit q->modelUpdated(changeSet, false);
+ emit q->childrenChanged();
+ }
+
+ void remove(int index, int n) {
+ Q_Q(QQmlObjectModel);
+ for (int i = index; i < index + n; ++i) {
+ QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item);
+ attached->setIndex(-1);
+ }
+ children.erase(children.begin() + index, children.begin() + index + n);
+ for (int i = index; i < children.count(); ++i) {
+ QQmlObjectModelAttached *attached = QQmlObjectModelAttached::properties(children.at(i).item);
+ attached->setIndex(i);
+ }
+ QQmlChangeSet changeSet;
+ changeSet.remove(index, n);
+ emit q->modelUpdated(changeSet, false);
+ emit q->countChanged();
+ emit q->childrenChanged();
+ }
+
+ void clear() {
+ Q_Q(QQmlObjectModel);
+ for (const Item &child : qAsConst(children))
+ emit q->destroyingItem(child.item);
+ remove(0, children.count());
+ }
+
+ int indexOf(QObject *item) const {
+ for (int i = 0; i < children.count(); ++i)
+ if (children.at(i).item == item)
+ return i;
+ return -1;
+ }
+
+ uint moveId;
+ QList<Item> children;
+};
+
+
+/*!
+ \qmltype ObjectModel
+ \instantiates QQmlObjectModel
+ \inqmlmodule QtQml.Models
+ \ingroup qtquick-models
+ \brief Defines a set of items to be used as a model.
+
+ An ObjectModel contains the visual items to be used in a view.
+ When an ObjectModel is used in a view, the view does not require
+ a delegate since the ObjectModel already contains the visual
+ delegate (items).
+
+ An item can determine its index within the
+ model via the \l{ObjectModel::index}{index} attached property.
+
+ The example below places three colored rectangles in a ListView.
+ \code
+ import QtQuick 2.0
+ import QtQml.Models 2.1
+
+ Rectangle {
+ ObjectModel {
+ id: itemModel
+ Rectangle { height: 30; width: 80; color: "red" }
+ Rectangle { height: 30; width: 80; color: "green" }
+ Rectangle { height: 30; width: 80; color: "blue" }
+ }
+
+ ListView {
+ anchors.fill: parent
+ model: itemModel
+ }
+ }
+ \endcode
+
+ \image objectmodel.png
+
+ \sa {Qt Quick Examples - Views}
+*/
+
+QQmlObjectModel::QQmlObjectModel(QObject *parent)
+ : QQmlInstanceModel(*(new QQmlObjectModelPrivate), parent)
+{
+}
+
+/*!
+ \qmlattachedproperty int QtQml.Models::ObjectModel::index
+ This attached property holds the index of this delegate's item within the model.
+
+ It is attached to each instance of the delegate.
+*/
+
+QQmlListProperty<QObject> QQmlObjectModel::children()
+{
+ Q_D(QQmlObjectModel);
+ return QQmlListProperty<QObject>(this,
+ d,
+ d->children_append,
+ d->children_count,
+ d->children_at,
+ d->children_clear);
+}
+
+/*!
+ \qmlproperty int QtQml.Models::ObjectModel::count
+
+ The number of items in the model. This property is readonly.
+*/
+int QQmlObjectModel::count() const
+{
+ Q_D(const QQmlObjectModel);
+ return d->children.count();
+}
+
+bool QQmlObjectModel::isValid() const
+{
+ return true;
+}
+
+QObject *QQmlObjectModel::object(int index, QQmlIncubator::IncubationMode)
+{
+ Q_D(QQmlObjectModel);
+ QQmlObjectModelPrivate::Item &item = d->children[index];
+ item.addRef();
+ if (item.ref == 1) {
+ emit initItem(index, item.item);
+ emit createdItem(index, item.item);
+ }
+ return item.item;
+}
+
+QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item)
+{
+ Q_D(QQmlObjectModel);
+ int idx = d->indexOf(item);
+ if (idx >= 0) {
+ if (!d->children[idx].deref())
+ return QQmlInstanceModel::Referenced;
+ }
+ return nullptr;
+}
+
+QVariant QQmlObjectModel::variantValue(int index, const QString &role)
+{
+ Q_D(QQmlObjectModel);
+ if (index < 0 || index >= d->children.count())
+ return QString();
+ return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(role);
+}
+
+QQmlIncubator::Status QQmlObjectModel::incubationStatus(int)
+{
+ return QQmlIncubator::Ready;
+}
+
+int QQmlObjectModel::indexOf(QObject *item, QObject *) const
+{
+ Q_D(const QQmlObjectModel);
+ return d->indexOf(item);
+}
+
+QQmlObjectModelAttached *QQmlObjectModel::qmlAttachedProperties(QObject *obj)
+{
+ return QQmlObjectModelAttached::properties(obj);
+}
+
+/*!
+ \qmlmethod object QtQml.Models::ObjectModel::get(int index)
+ \since 5.6
+
+ Returns the item at \a index in the model. This allows the item
+ to be accessed or modified from JavaScript:
+
+ \code
+ Component.onCompleted: {
+ objectModel.append(objectComponent.createObject())
+ console.log(objectModel.get(0).objectName);
+ objectModel.get(0).objectName = "first";
+ }
+ \endcode
+
+ The \a index must be an element in the list.
+
+ \sa append()
+*/
+QObject *QQmlObjectModel::get(int index) const
+{
+ Q_D(const QQmlObjectModel);
+ if (index < 0 || index >= d->children.count())
+ return nullptr;
+ return d->children.at(index).item;
+}
+
+/*!
+ \qmlmethod QtQml.Models::ObjectModel::append(object item)
+ \since 5.6
+
+ Appends a new \a item to the end of the model.
+
+ \code
+ objectModel.append(objectComponent.createObject())
+ \endcode
+
+ \sa insert(), remove()
+*/
+void QQmlObjectModel::append(QObject *object)
+{
+ Q_D(QQmlObjectModel);
+ d->insert(count(), object);
+}
+
+/*!
+ \qmlmethod QtQml.Models::ObjectModel::insert(int index, object item)
+ \since 5.6
+
+ Inserts a new \a item to the model at position \a index.
+
+ \code
+ objectModel.insert(2, objectComponent.createObject())
+ \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 append(), remove()
+*/
+void QQmlObjectModel::insert(int index, QObject *object)
+{
+ Q_D(QQmlObjectModel);
+ if (index < 0 || index > count()) {
+ qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
+ return;
+ }
+ d->insert(index, object);
+}
+
+/*!
+ \qmlmethod QtQml.Models::ObjectModel::move(int from, int to, int n = 1)
+ \since 5.6
+
+ Moves \e 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 model:
+
+ \code
+ objectModel.move(0, objectModel.count - 3, 3)
+ \endcode
+
+ \sa append()
+*/
+void QQmlObjectModel::move(int from, int to, int n)
+{
+ Q_D(QQmlObjectModel);
+ if (n <= 0 || from == to)
+ return;
+ if (from < 0 || to < 0 || from + n > count() || to + n > count()) {
+ qmlWarning(this) << tr("move: out of range");
+ return;
+ }
+ d->move(from, to, n);
+}
+
+/*!
+ \qmlmethod QtQml.Models::ObjectModel::remove(int index, int n = 1)
+ \since 5.6
+
+ Removes \e n items at \a index from the model.
+
+ \sa clear()
+*/
+void QQmlObjectModel::remove(int index, int n)
+{
+ Q_D(QQmlObjectModel);
+ if (index < 0 || n <= 0 || index + n > count()) {
+ qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+n).arg(count());
+ return;
+ }
+ d->remove(index, n);
+}
+
+/*!
+ \qmlmethod QtQml.Models::ObjectModel::clear()
+ \since 5.6
+
+ Clears all items from the model.
+
+ \sa append(), remove()
+*/
+void QQmlObjectModel::clear()
+{
+ Q_D(QQmlObjectModel);
+ d->clear();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqmlobjectmodel_p.cpp"
diff --git a/src/qmlmodels/qqmlobjectmodel_p.h b/src/qmlmodels/qqmlobjectmodel_p.h
new file mode 100644
index 0000000000..78a5615ae2
--- /dev/null
+++ b/src/qmlmodels/qqmlobjectmodel_p.h
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLINSTANCEMODEL_P_H
+#define QQMLINSTANCEMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtqmlmodelsglobal_p.h>
+#include <private/qqmlincubator_p.h>
+#include <QtQml/qqml.h>
+#include <QtCore/qobject.h>
+
+QT_REQUIRE_CONFIG(qml_object_model);
+
+QT_BEGIN_NAMESPACE
+
+class QObject;
+class QQmlChangeSet;
+class QAbstractItemModel;
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlInstanceModel : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+
+public:
+ virtual ~QQmlInstanceModel() {}
+
+ enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 };
+ Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag)
+
+ virtual int count() const = 0;
+ virtual bool isValid() const = 0;
+ virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0;
+ virtual ReleaseFlags release(QObject *object) = 0;
+ virtual void cancel(int) {}
+ QString stringValue(int index, const QString &role) { return variantValue(index, role).toString(); }
+ virtual QVariant variantValue(int, const QString &) = 0;
+ virtual void setWatchedRoles(const QList<QByteArray> &roles) = 0;
+ virtual QQmlIncubator::Status incubationStatus(int index) = 0;
+
+ virtual int indexOf(QObject *object, QObject *objectContext) const = 0;
+ virtual const QAbstractItemModel *abstractItemModel() const { return nullptr; }
+
+Q_SIGNALS:
+ void countChanged();
+ void modelUpdated(const QQmlChangeSet &changeSet, bool reset);
+ void createdItem(int index, QObject *object);
+ void initItem(int index, QObject *object);
+ void destroyingItem(QObject *object);
+
+protected:
+ QQmlInstanceModel(QObjectPrivate &dd, QObject *parent = nullptr)
+ : QObject(dd, parent) {}
+
+private:
+ Q_DISABLE_COPY(QQmlInstanceModel)
+};
+
+class QQmlObjectModelAttached;
+class QQmlObjectModelPrivate;
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlObjectModel : public QQmlInstanceModel
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QQmlObjectModel)
+
+ Q_PROPERTY(QQmlListProperty<QObject> children READ children NOTIFY childrenChanged DESIGNABLE false)
+ Q_CLASSINFO("DefaultProperty", "children")
+
+public:
+ QQmlObjectModel(QObject *parent=nullptr);
+ ~QQmlObjectModel() {}
+
+ int count() const override;
+ bool isValid() const override;
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
+ ReleaseFlags release(QObject *object) override;
+ QVariant variantValue(int index, const QString &role) override;
+ void setWatchedRoles(const QList<QByteArray> &) override {}
+ QQmlIncubator::Status incubationStatus(int index) override;
+
+ int indexOf(QObject *object, QObject *objectContext) const override;
+
+ QQmlListProperty<QObject> children();
+
+ static QQmlObjectModelAttached *qmlAttachedProperties(QObject *obj);
+
+ Q_REVISION(3) Q_INVOKABLE QObject *get(int index) const;
+ Q_REVISION(3) Q_INVOKABLE void append(QObject *object);
+ Q_REVISION(3) Q_INVOKABLE void insert(int index, QObject *object);
+ Q_REVISION(3) Q_INVOKABLE void move(int from, int to, int n = 1);
+ Q_REVISION(3) Q_INVOKABLE void remove(int index, int n = 1);
+
+public Q_SLOTS:
+ Q_REVISION(3) void clear();
+
+Q_SIGNALS:
+ void childrenChanged();
+
+private:
+ Q_DISABLE_COPY(QQmlObjectModel)
+};
+
+class QQmlObjectModelAttached : public QObject
+{
+ Q_OBJECT
+
+public:
+ QQmlObjectModelAttached(QObject *parent)
+ : QObject(parent), m_index(-1) {}
+ ~QQmlObjectModelAttached() {
+ attachedProperties.remove(parent());
+ }
+
+ Q_PROPERTY(int index READ index NOTIFY indexChanged)
+ int index() const { return m_index; }
+ void setIndex(int idx) {
+ if (m_index != idx) {
+ m_index = idx;
+ Q_EMIT indexChanged();
+ }
+ }
+
+ static QQmlObjectModelAttached *properties(QObject *obj) {
+ QQmlObjectModelAttached *rv = attachedProperties.value(obj);
+ if (!rv) {
+ rv = new QQmlObjectModelAttached(obj);
+ attachedProperties.insert(obj, rv);
+ }
+ return rv;
+ }
+
+Q_SIGNALS:
+ void indexChanged();
+
+public:
+ int m_index;
+
+ static QHash<QObject*, QQmlObjectModelAttached*> attachedProperties;
+};
+
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlInstanceModel)
+QML_DECLARE_TYPE(QQmlObjectModel)
+QML_DECLARE_TYPEINFO(QQmlObjectModel, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // QQMLINSTANCEMODEL_P_H
diff --git a/src/qmlmodels/qqmltableinstancemodel.cpp b/src/qmlmodels/qqmltableinstancemodel.cpp
new file mode 100644
index 0000000000..b244a007e5
--- /dev/null
+++ b/src/qmlmodels/qqmltableinstancemodel.cpp
@@ -0,0 +1,547 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmltableinstancemodel_p.h"
+#include "qqmldelegatecomponent_p.h"
+
+#include <QtCore/QTimer>
+
+#include <QtQml/private/qqmlincubator_p.h>
+#include <QtQmlModels/private/qqmlchangeset_p.h>
+#include <QtQml/private/qqmlcomponent_p.h>
+
+QT_BEGIN_NAMESPACE
+
+const char* kModelItemTag = "_tableinstancemodel_modelItem";
+
+bool QQmlTableInstanceModel::isDoneIncubating(QQmlDelegateModelItem *modelItem)
+{
+ if (!modelItem->incubationTask)
+ return true;
+
+ const auto status = modelItem->incubationTask->status();
+ return (status == QQmlIncubator::Ready) || (status == QQmlIncubator::Error);
+}
+
+void QQmlTableInstanceModel::deleteModelItemLater(QQmlDelegateModelItem *modelItem)
+{
+ Q_ASSERT(modelItem);
+
+ delete modelItem->object;
+ modelItem->object = nullptr;
+
+ if (modelItem->contextData) {
+ modelItem->contextData->invalidate();
+ Q_ASSERT(modelItem->contextData->refCount == 1);
+ modelItem->contextData = nullptr;
+ }
+
+ modelItem->deleteLater();
+}
+
+QQmlTableInstanceModel::QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent)
+ : QQmlInstanceModel(*(new QObjectPrivate()), parent)
+ , m_qmlContext(qmlContext)
+ , m_metaType(new QQmlDelegateModelItemMetaType(m_qmlContext->engine()->handle(), nullptr, QStringList()))
+{
+}
+
+void QQmlTableInstanceModel::useImportVersion(int minorVersion)
+{
+ m_adaptorModel.useImportVersion(minorVersion);
+}
+
+QQmlTableInstanceModel::~QQmlTableInstanceModel()
+{
+ for (const auto modelItem : m_modelItems) {
+ // No item in m_modelItems should be referenced at this point. The view
+ // should release all its items before it deletes this model. Only model items
+ // that are still being incubated should be left for us to delete.
+ Q_ASSERT(modelItem->objectRef == 0);
+ Q_ASSERT(modelItem->incubationTask);
+ // Check that we are not being deleted while we're
+ // in the process of e.g emitting a created signal.
+ Q_ASSERT(modelItem->scriptRef == 0);
+
+ if (modelItem->object) {
+ delete modelItem->object;
+ modelItem->object = nullptr;
+ modelItem->contextData->invalidate();
+ modelItem->contextData = nullptr;
+ }
+ }
+
+ deleteAllFinishedIncubationTasks();
+ qDeleteAll(m_modelItems);
+ drainReusableItemsPool(0);
+}
+
+QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index)
+{
+ if (m_delegateChooser) {
+ const int row = m_adaptorModel.rowAt(index);
+ const int column = m_adaptorModel.columnAt(index);
+ QQmlComponent *delegate = nullptr;
+ QQmlAbstractDelegateComponent *chooser = m_delegateChooser;
+ do {
+ delegate = chooser->delegate(&m_adaptorModel, row, column);
+ chooser = qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ } while (chooser);
+ return delegate;
+ }
+
+ return m_delegate;
+}
+
+QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index)
+{
+ // Check if an item for the given index is already loaded and ready
+ QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr);
+ if (modelItem)
+ return modelItem;
+
+ QQmlComponent *delegate = resolveDelegate(index);
+ if (!delegate)
+ return nullptr;
+
+ // Check if the pool contains an item that can be reused
+ modelItem = takeFromReusableItemsPool(delegate);
+ if (modelItem) {
+ reuseItem(modelItem, index);
+ m_modelItems.insert(index, modelItem);
+ return modelItem;
+ }
+
+ // Create a new item from scratch
+ modelItem = m_adaptorModel.createItem(m_metaType, index);
+ if (modelItem) {
+ modelItem->delegate = delegate;
+ m_modelItems.insert(index, modelItem);
+ return modelItem;
+ }
+
+ qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << index;
+ return nullptr;
+}
+
+QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode)
+{
+ Q_ASSERT(m_delegate);
+ Q_ASSERT(index >= 0 && index < m_adaptorModel.count());
+ Q_ASSERT(m_qmlContext && m_qmlContext->isValid());
+
+ QQmlDelegateModelItem *modelItem = resolveModelItem(index);
+ if (!modelItem)
+ return nullptr;
+
+ if (modelItem->object) {
+ // The model item has already been incubated. So
+ // just bump the ref-count and return it.
+ modelItem->referenceObject();
+ return modelItem->object;
+ }
+
+ // The object is not ready, and needs to be incubated
+ incubateModelItem(modelItem, incubationMode);
+ if (!isDoneIncubating(modelItem))
+ return nullptr;
+
+ // Incubation is done, so the task should be removed
+ Q_ASSERT(!modelItem->incubationTask);
+
+ if (!modelItem->object) {
+ // The object was incubated synchronously (otherwise we would return above). But since
+ // we have no object, the incubation must have failed. And when we have no object, there
+ // should be no object references either. And there should also not be any internal script
+ // refs at this point. So we delete the model item.
+ Q_ASSERT(!modelItem->isObjectReferenced());
+ Q_ASSERT(!modelItem->isReferenced());
+ m_modelItems.remove(modelItem->index);
+ delete modelItem;
+ return nullptr;
+ }
+
+ // Incubation was completed sync and successful
+ modelItem->referenceObject();
+ return modelItem->object;
+}
+
+QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, ReusableFlag reusable)
+{
+ Q_ASSERT(object);
+ auto modelItem = qvariant_cast<QQmlDelegateModelItem *>(object->property(kModelItemTag));
+ Q_ASSERT(modelItem);
+
+ if (!modelItem->releaseObject())
+ return QQmlDelegateModel::Referenced;
+
+ if (modelItem->isReferenced()) {
+ // We still have an internal reference to this object, which means that we are told to release an
+ // object while the createdItem signal for it is still on the stack. This can happen when objects
+ // are e.g delivered async, and the user flicks back and forth quicker than the loading can catch
+ // up with. The view might then find that the object is no longer visible and should be released.
+ // We detect this case in incubatorStatusChanged(), and delete it there instead. But from the callers
+ // point of view, it should consider it destroyed.
+ return QQmlDelegateModel::Destroyed;
+ }
+
+ // The item is not referenced by anyone
+ m_modelItems.remove(modelItem->index);
+
+ if (reusable == Reusable) {
+ insertIntoReusableItemsPool(modelItem);
+ return QQmlInstanceModel::Referenced;
+ }
+
+ // The item is not reused or referenced by anyone, so just delete it
+ modelItem->destroyObject();
+ emit destroyingItem(object);
+
+ delete modelItem;
+ return QQmlInstanceModel::Destroyed;
+}
+
+void QQmlTableInstanceModel::cancel(int index)
+{
+ auto modelItem = m_modelItems.value(index);
+ Q_ASSERT(modelItem);
+
+ // Since the view expects the item to be incubating, there should be
+ // an incubation task. And since the incubation is not done, no-one
+ // should yet have received, and therfore hold a reference to, the object.
+ Q_ASSERT(modelItem->incubationTask);
+ Q_ASSERT(!modelItem->isObjectReferenced());
+
+ m_modelItems.remove(index);
+
+ if (modelItem->object)
+ delete modelItem->object;
+
+ // modelItem->incubationTask will be deleted from the modelItems destructor
+ delete modelItem;
+}
+
+void QQmlTableInstanceModel::insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem)
+{
+ // Currently, the only way for a view to reuse items is to call QQmlTableInstanceModel::release()
+ // with the second argument explicitly set to QQmlTableInstanceModel::Reusable. If the released
+ // item is no longer referenced, it will be added to the pool. Reusing of items can be specified
+ // per item, in case certain items cannot be recycled.
+ // A QQmlDelegateModelItem knows which delegate its object was created from. So when we are
+ // about to create a new item, we first check if the pool contains an item based on the same
+ // delegate from before. If so, we take it out of the pool (instead of creating a new item), and
+ // update all its context-, and attached properties.
+ // When a view is recycling items, it should call QQmlTableInstanceModel::drainReusableItemsPool()
+ // regularly. As there is currently no logic to 'hibernate' items in the pool, they are only
+ // meant to rest there for a short while, ideally only from the time e.g a row is unloaded
+ // on one side of the view, and until a new row is loaded on the opposite side. In-between
+ // this time, the application will see the item as fully functional and 'alive' (just not
+ // visible on screen). Since this time is supposed to be short, we don't take any action to
+ // notify the application about it, since we don't want to trigger any bindings that can
+ // disturb performance.
+ // A recommended time for calling drainReusableItemsPool() is each time a view has finished
+ // loading e.g a new row or column. If there are more items in the pool after that, it means
+ // that the view most likely doesn't need them anytime soon. Those items should be destroyed to
+ // not consume resources.
+ // Depending on if a view is a list or a table, it can sometimes be performant to keep
+ // items in the pool for a bit longer than one "row out/row in" cycle. E.g for a table, if the
+ // number of visible rows in a view is much larger than the number of visible columns.
+ // In that case, if you flick out a row, and then flick in a column, you would throw away a lot
+ // of items in the pool if completely draining it. The reason is that unloading a row places more
+ // items in the pool than what ends up being recycled when loading a new column. And then, when you
+ // next flick in a new row, you would need to load all those drained items again from scratch. For
+ // that reason, you can specify a maxPoolTime to the drainReusableItemsPool() that allows you to keep
+ // items in the pool for a bit longer, effectively keeping more items in circulation.
+ // A recommended maxPoolTime would be equal to the number of dimenstions in the view, which
+ // means 1 for a list view and 2 for a table view. If you specify 0, all items will be drained.
+ Q_ASSERT(!modelItem->incubationTask);
+ Q_ASSERT(!modelItem->isObjectReferenced());
+ Q_ASSERT(!modelItem->isReferenced());
+ Q_ASSERT(modelItem->object);
+
+ modelItem->poolTime = 0;
+ m_reusableItemsPool.append(modelItem);
+ emit itemPooled(modelItem->index, modelItem->object);
+}
+
+QQmlDelegateModelItem *QQmlTableInstanceModel::takeFromReusableItemsPool(const QQmlComponent *delegate)
+{
+ // Find the oldest item in the pool that was made from the same delegate as
+ // the given argument, remove it from the pool, and return it.
+ if (m_reusableItemsPool.isEmpty())
+ return nullptr;
+
+ for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end(); ++it) {
+ if ((*it)->delegate != delegate)
+ continue;
+ auto modelItem = *it;
+ m_reusableItemsPool.erase(it);
+ return modelItem;
+ }
+
+ return nullptr;
+}
+
+void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime)
+{
+ // Rather than releasing all pooled items upon a call to this function, each
+ // item has a poolTime. The poolTime specifies for how many loading cycles an item
+ // has been resting in the pool. And for each invocation of this function, poolTime
+ // will increase. If poolTime is equal to, or exceeds, maxPoolTime, it will be removed
+ // from the pool and released. This way, the view can tweak a bit for how long
+ // items should stay in "circulation", even if they are not recycled right away.
+ for (auto it = m_reusableItemsPool.begin(); it != m_reusableItemsPool.end();) {
+ auto modelItem = *it;
+ modelItem->poolTime++;
+ if (modelItem->poolTime <= maxPoolTime) {
+ ++it;
+ } else {
+ it = m_reusableItemsPool.erase(it);
+ release(modelItem->object, NotReusable);
+ }
+ }
+}
+
+void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex)
+{
+ // Update the context properties index, row and column on
+ // the delegate item, and inform the application about it.
+ const int newRow = m_adaptorModel.rowAt(newModelIndex);
+ const int newColumn = m_adaptorModel.columnAt(newModelIndex);
+ item->setModelIndex(newModelIndex, newRow, newColumn);
+
+ // Notify the application that all 'dynamic'/role-based context data has
+ // changed as well (their getter function will use the updated index).
+ auto const itemAsList = QList<QQmlDelegateModelItem *>() << item;
+ auto const updateAllRoles = QVector<int>();
+ m_adaptorModel.notify(itemAsList, newModelIndex, 1, updateAllRoles);
+
+ // Inform the view that the item is recycled. This will typically result
+ // in the view updating its own attached delegate item properties.
+ emit itemReused(newModelIndex, item->object);
+}
+
+void QQmlTableInstanceModel::incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode)
+{
+ // Guard the model item temporarily so that it's not deleted from
+ // incubatorStatusChanged(), in case the incubation is done synchronously.
+ modelItem->scriptRef++;
+
+ if (modelItem->incubationTask) {
+ // We're already incubating the model item from a previous request. If the previous call requested
+ // the item async, but the current request needs it sync, we need to force-complete the incubation.
+ const bool sync = (incubationMode == QQmlIncubator::Synchronous || incubationMode == QQmlIncubator::AsynchronousIfNested);
+ if (sync && modelItem->incubationTask->incubationMode() == QQmlIncubator::Asynchronous)
+ modelItem->incubationTask->forceCompletion();
+ } else {
+ modelItem->incubationTask = new QQmlTableInstanceModelIncubationTask(this, modelItem, incubationMode);
+
+ QQmlContextData *ctxt = new QQmlContextData;
+ QQmlContext *creationContext = modelItem->delegate->creationContext();
+ ctxt->setParent(QQmlContextData::get(creationContext ? creationContext : m_qmlContext.data()));
+ ctxt->contextObject = modelItem;
+ modelItem->contextData = ctxt;
+
+ QQmlComponentPrivate::get(modelItem->delegate)->incubateObject(
+ modelItem->incubationTask,
+ modelItem->delegate,
+ m_qmlContext->engine(),
+ ctxt,
+ QQmlContextData::get(m_qmlContext));
+ }
+
+ // Remove the temporary guard
+ modelItem->scriptRef--;
+}
+
+void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *incubationTask, QQmlIncubator::Status status)
+{
+ QQmlDelegateModelItem *modelItem = incubationTask->modelItemToIncubate;
+ Q_ASSERT(modelItem->incubationTask);
+
+ modelItem->incubationTask = nullptr;
+ incubationTask->modelItemToIncubate = nullptr;
+
+ if (status == QQmlIncubator::Ready) {
+ // Tag the incubated object with the model item for easy retrieval upon release etc.
+ modelItem->object->setProperty(kModelItemTag, QVariant::fromValue(modelItem));
+
+ // Emit that the item has been created. What normally happens next is that the view
+ // upon receiving the signal asks for the model item once more. And since the item is
+ // now in the map, it will be returned directly.
+ Q_ASSERT(modelItem->object);
+ modelItem->scriptRef++;
+ emit createdItem(modelItem->index, modelItem->object);
+ modelItem->scriptRef--;
+ } else if (status == QQmlIncubator::Error) {
+ qWarning() << "Error incubating delegate:" << incubationTask->errors();
+ }
+
+ if (!modelItem->isReferenced() && !modelItem->isObjectReferenced()) {
+ // We have no internal reference to the model item, and the view has no
+ // reference to the incubated object. So just delete the model item.
+ // Note that being here means that the object was incubated _async_
+ // (otherwise modelItem->isReferenced() would be true).
+ m_modelItems.remove(modelItem->index);
+
+ if (modelItem->object) {
+ modelItem->scriptRef++;
+ emit destroyingItem(modelItem->object);
+ modelItem->scriptRef--;
+ Q_ASSERT(!modelItem->isReferenced());
+ }
+
+ deleteModelItemLater(modelItem);
+ }
+
+ deleteIncubationTaskLater(incubationTask);
+}
+
+QQmlIncubator::Status QQmlTableInstanceModel::incubationStatus(int index) {
+ const auto modelItem = m_modelItems.value(index, nullptr);
+ if (!modelItem)
+ return QQmlIncubator::Null;
+
+ if (modelItem->incubationTask)
+ return modelItem->incubationTask->status();
+
+ // Since we clear the incubation task when we're done
+ // incubating, it means that the status is Ready.
+ return QQmlIncubator::Ready;
+}
+
+void QQmlTableInstanceModel::deleteIncubationTaskLater(QQmlIncubator *incubationTask)
+{
+ // We often need to post-delete incubation tasks, since we cannot
+ // delete them while we're in the middle of an incubation change callback.
+ Q_ASSERT(!m_finishedIncubationTasks.contains(incubationTask));
+ m_finishedIncubationTasks.append(incubationTask);
+ if (m_finishedIncubationTasks.count() == 1)
+ QTimer::singleShot(1, this, &QQmlTableInstanceModel::deleteAllFinishedIncubationTasks);
+}
+
+void QQmlTableInstanceModel::deleteAllFinishedIncubationTasks()
+{
+ qDeleteAll(m_finishedIncubationTasks);
+ m_finishedIncubationTasks.clear();
+}
+
+QVariant QQmlTableInstanceModel::model() const
+{
+ return m_adaptorModel.model();
+}
+
+void QQmlTableInstanceModel::setModel(const QVariant &model)
+{
+ // Pooled items are still accessible/alive for the application, and
+ // needs to stay in sync with the model. So we need to drain the pool
+ // completely when the model changes.
+ drainReusableItemsPool(0);
+ if (auto const aim = abstractItemModel())
+ disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback);
+ m_adaptorModel.setModel(model, this, m_qmlContext->engine());
+ if (auto const aim = abstractItemModel())
+ connect(aim, &QAbstractItemModel::dataChanged, this, &QQmlTableInstanceModel::dataChangedCallback);
+}
+
+void QQmlTableInstanceModel::dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles)
+{
+ // This function is called when model data has changed. In that case, we tell the adaptor model
+ // to go through all the items we have created, find the ones that are affected, and notify that
+ // their model data has changed. This will in turn update QML bindings inside the delegate items.
+ int numberOfRowsChanged = end.row() - begin.row() + 1;
+ int numberOfColumnsChanged = end.column() - begin.column() + 1;
+
+ for (int column = 0; column < numberOfColumnsChanged; ++column) {
+ const int columnIndex = begin.column() + column;
+ const int rowIndex = begin.row() + (columnIndex * rows());
+ m_adaptorModel.notify(m_modelItems.values(), rowIndex, numberOfRowsChanged, roles);
+ }
+}
+
+QQmlComponent *QQmlTableInstanceModel::delegate() const
+{
+ return m_delegate;
+}
+
+void QQmlTableInstanceModel::setDelegate(QQmlComponent *delegate)
+{
+ if (m_delegate == delegate)
+ return;
+
+ m_delegateChooser = nullptr;
+ if (delegate) {
+ QQmlAbstractDelegateComponent *adc =
+ qobject_cast<QQmlAbstractDelegateComponent *>(delegate);
+ if (adc)
+ m_delegateChooser = adc;
+ }
+
+ m_delegate = delegate;
+}
+
+const QAbstractItemModel *QQmlTableInstanceModel::abstractItemModel() const
+{
+ return m_adaptorModel.adaptsAim() ? m_adaptorModel.aim() : nullptr;
+}
+
+// --------------------------------------------------------
+
+void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object)
+{
+ modelItemToIncubate->object = object;
+ emit tableInstanceModel->initItem(modelItemToIncubate->index, object);
+}
+
+void QQmlTableInstanceModelIncubationTask::statusChanged(QQmlIncubator::Status status)
+{
+ if (!QQmlTableInstanceModel::isDoneIncubating(modelItemToIncubate))
+ return;
+
+ // We require the view to cancel any ongoing load
+ // requests before the tableInstanceModel is destructed.
+ Q_ASSERT(tableInstanceModel);
+
+ tableInstanceModel->incubatorStatusChanged(this, status);
+}
+
+#include "moc_qqmltableinstancemodel_p.cpp"
+
+QT_END_NAMESPACE
+
diff --git a/src/qmlmodels/qqmltableinstancemodel_p.h b/src/qmlmodels/qqmltableinstancemodel_p.h
new file mode 100644
index 0000000000..ce5a37bc98
--- /dev/null
+++ b/src/qmlmodels/qqmltableinstancemodel_p.h
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLTABLEINSTANCEMODEL_P_H
+#define QQMLTABLEINSTANCEMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQmlModels/private/qqmldelegatemodel_p.h>
+#include <QtQmlModels/private/qqmldelegatemodel_p_p.h>
+
+QT_REQUIRE_CONFIG(qml_table_model);
+
+QT_BEGIN_NAMESPACE
+
+class QQmlTableInstanceModel;
+class QQmlAbstractDelegateComponent;
+
+class QQmlTableInstanceModelIncubationTask : public QQDMIncubationTask
+{
+public:
+ QQmlTableInstanceModelIncubationTask(
+ QQmlTableInstanceModel *tableInstanceModel
+ , QQmlDelegateModelItem* modelItemToIncubate
+ , IncubationMode mode)
+ : QQDMIncubationTask(nullptr, mode)
+ , modelItemToIncubate(modelItemToIncubate)
+ , tableInstanceModel(tableInstanceModel) {
+ clear();
+ }
+
+ void statusChanged(Status status) override;
+ void setInitialState(QObject *object) override;
+
+ QQmlDelegateModelItem *modelItemToIncubate = nullptr;
+ QQmlTableInstanceModel *tableInstanceModel = nullptr;
+};
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableInstanceModel : public QQmlInstanceModel
+{
+ Q_OBJECT
+
+public:
+
+ enum ReusableFlag {
+ NotReusable,
+ Reusable
+ };
+
+ QQmlTableInstanceModel(QQmlContext *qmlContext, QObject *parent = nullptr);
+ ~QQmlTableInstanceModel() override;
+
+ void useImportVersion(int minorVersion);
+
+ int count() const override { return m_adaptorModel.count(); }
+ int rows() const { return m_adaptorModel.rowCount(); }
+ int columns() const { return m_adaptorModel.columnCount(); }
+
+ bool isValid() const override { return true; }
+
+ QVariant model() const;
+ void setModel(const QVariant &model);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *);
+
+ const QAbstractItemModel *abstractItemModel() const override;
+
+ QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override;
+ ReleaseFlags release(QObject *object) override { return release(object, NotReusable); }
+ ReleaseFlags release(QObject *object, ReusableFlag reusable);
+ void cancel(int) override;
+
+ void insertIntoReusableItemsPool(QQmlDelegateModelItem *modelItem);
+ QQmlDelegateModelItem *takeFromReusableItemsPool(const QQmlComponent *delegate);
+ void drainReusableItemsPool(int maxPoolTime);
+ int poolSize() { return m_reusableItemsPool.size(); }
+ void reuseItem(QQmlDelegateModelItem *item, int newModelIndex);
+
+ QQmlIncubator::Status incubationStatus(int index) override;
+
+ QVariant variantValue(int, const QString &) override { Q_UNREACHABLE(); return QVariant(); }
+ void setWatchedRoles(const QList<QByteArray> &) override { Q_UNREACHABLE(); }
+ int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; }
+
+Q_SIGNALS:
+ void itemPooled(int index, QObject *object);
+ void itemReused(int index, QObject *object);
+
+private:
+ QQmlComponent *resolveDelegate(int index);
+
+ QQmlAdaptorModel m_adaptorModel;
+ QQmlAbstractDelegateComponent *m_delegateChooser = nullptr;
+ QQmlComponent *m_delegate = nullptr;
+ QPointer<QQmlContext> m_qmlContext;
+ QQmlDelegateModelItemMetaType *m_metaType;
+
+ QHash<int, QQmlDelegateModelItem *> m_modelItems;
+ QList<QQmlDelegateModelItem *> m_reusableItemsPool;
+ QList<QQmlIncubator *> m_finishedIncubationTasks;
+
+ void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode);
+ void incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *dmIncubationTask, QQmlIncubator::Status status);
+ void deleteIncubationTaskLater(QQmlIncubator *incubationTask);
+ void deleteAllFinishedIncubationTasks();
+ QQmlDelegateModelItem *resolveModelItem(int index);
+
+ void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QVector<int> &roles);
+
+ static bool isDoneIncubating(QQmlDelegateModelItem *modelItem);
+ static void deleteModelItemLater(QQmlDelegateModelItem *modelItem);
+
+ friend class QQmlTableInstanceModelIncubationTask;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLTABLEINSTANCEMODEL_P_H
diff --git a/src/qmlmodels/qqmltablemodel.cpp b/src/qmlmodels/qqmltablemodel.cpp
new file mode 100644
index 0000000000..f190ad86b1
--- /dev/null
+++ b/src/qmlmodels/qqmltablemodel.cpp
@@ -0,0 +1,1059 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmltablemodel_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtQml/qqmlinfo.h>
+#include <QtQml/qqmlengine.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel")
+
+/*!
+ \qmltype TableModel
+ \instantiates QQmlTableModel
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Encapsulates a simple table model.
+ \since 5.14
+
+ The TableModel type stores JavaScript/JSON objects as data for a table
+ model that can be used with \l TableView. It is intended to support
+ very simple models without requiring the creation of a custom
+ QAbstractTableModel subclass in C++.
+
+ \snippet qml/tablemodel/fruit-example-simpledelegate.qml file
+
+ The model's initial row data is set with either the \l rows property or by
+ calling \l appendRow(). Each column in the model is specified by declaring
+ a \l TableModelColumn instance, where the order of each instance determines
+ its column index. Once the model's \l Component::completed() signal has been
+ emitted, the columns and roles will have been established and are then
+ fixed for the lifetime of the model.
+
+ To access a specific row, the \l getRow() function can be used.
+ It's also possible to access the model's JavaScript data
+ directly via the \l rows property, but it is not possible to
+ modify the model data this way.
+
+ To add new rows, use \l appendRow() and \l insertRow(). To modify
+ existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and
+ \l clear().
+
+ It is also possible to modify the model's data via the delegate,
+ as shown in the example above:
+
+ \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate
+
+ If the type of the data at the modified role does not match the type of the
+ data that is set, it will be automatically converted via
+ \l {QVariant::canConvert()}{QVariant}.
+
+ \section1 Supported Row Data Structures
+
+ TableModel is designed to work with JavaScript/JSON data, where each row
+ is a simple key-pair object:
+
+ \code
+ {
+ // Each property is one cell/column.
+ checked: false,
+ amount: 1,
+ fruitType: "Apple",
+ fruitName: "Granny Smith",
+ fruitPrice: 1.50
+ },
+ // ...
+ \endcode
+
+ As model manipulation in Qt is done via row and column indices,
+ and because object keys are unordered, each column must be specified via
+ TableModelColumn. This allows mapping Qt's built-in roles to any property
+ in each row object.
+
+ Complex row structures are supported, but with limited functionality.
+ As TableModel has no way of knowing how each row is structured,
+ it cannot manipulate it. As a consequence of this, the copy of the
+ model data that TableModel has stored in \l rows is not kept in sync
+ with the source data that was set in QML. For these reasons, TableModel
+ relies on the user to handle simple data manipulation.
+
+ For example, suppose you wanted to have several roles per column. One way
+ of doing this is to use a data source where each row is an array and each
+ cell is an object. To use this data source with TableModel, define a
+ getter and setter:
+
+ \code
+ TableModel {
+ TableModelColumn {
+ display: function(modelIndex) { return rows[modelIndex.row][0].checked }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData }
+ }
+ // ...
+
+ rows: [
+ [
+ { checked: false, checkable: true },
+ { amount: 1 },
+ { fruitType: "Apple" },
+ { fruitName: "Granny Smith" },
+ { fruitPrice: 1.50 }
+ ]
+ // ...
+ ]
+ }
+ \endcode
+
+ The row above is one example of a complex row.
+
+ \note Row manipulation functions such as \l appendRow(), \l removeRow(),
+ etc. are not supported when using complex rows.
+
+ \section1 Using DelegateChooser with TableModel
+
+ For most real world use cases, it is recommended to use DelegateChooser
+ as the delegate of a TableView that uses TableModel. This allows you to
+ use specific roles in the relevant delegates. For example, the snippet
+ above can be rewritten to use DelegateChooser like so:
+
+ \snippet qml/tablemodel/fruit-example-delegatechooser.qml file
+
+ The most specific delegates are declared first: the columns at index \c 0
+ and \c 1 have \c bool and \c integer data types, so they use a
+ \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox},
+ respectively. The remaining columns can simply use a
+ \l [QtQuickControls2]{TextField}, and so that delegate is declared
+ last as a fallback.
+
+ \sa TableModelColumn, TableView, QAbstractTableModel
+*/
+
+QQmlTableModel::QQmlTableModel(QObject *parent)
+ : QAbstractTableModel(parent)
+{
+}
+
+QQmlTableModel::~QQmlTableModel()
+{
+}
+
+/*!
+ \qmlproperty object TableModel::rows
+
+ This property holds the model data in the form of an array of rows:
+
+ \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows
+
+ \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount
+*/
+QVariant QQmlTableModel::rows() const
+{
+ return mRows;
+}
+
+void QQmlTableModel::setRows(const QVariant &rows)
+{
+ if (rows.userType() != qMetaTypeId<QJSValue>()) {
+ qmlWarning(this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName();
+ return;
+ }
+
+ const QJSValue rowsAsJSValue = rows.value<QJSValue>();
+ const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList();
+ if (rowsAsVariantList == mRows) {
+ // No change.
+ return;
+ }
+
+ if (!componentCompleted) {
+ // Store the rows until we can call doSetRows() after component completion.
+ mRows = rowsAsVariantList;
+ return;
+ }
+
+ doSetRows(rowsAsVariantList);
+}
+
+void QQmlTableModel::doSetRows(const QVariantList &rowsAsVariantList)
+{
+ Q_ASSERT(componentCompleted);
+
+ // By now, all TableModelColumns should have been set.
+ if (mColumns.isEmpty()) {
+ qmlWarning(this) << "No TableModelColumns were set; model will be empty";
+ return;
+ }
+
+ const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty();
+ if (!firstTimeValidRowsHaveBeenSet) {
+ // This is not the first time rows have been set; validate each one.
+ for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) {
+ // validateNewRow() expects a QVariant wrapping a QJSValue, so to
+ // simplify the code, just create one here.
+ const QVariant row = QVariant::fromValue(rowsAsVariantList.at(rowIndex));
+ if (!validateNewRow("setRows()", row, rowIndex, SetRowsOperation))
+ return;
+ }
+ }
+
+ const int oldRowCount = mRowCount;
+ const int oldColumnCount = mColumnCount;
+
+ beginResetModel();
+
+ // We don't clear the column or role data, because a TableModel should not be reused in that way.
+ // Once it has valid data, its columns and roles are fixed.
+ mRows = rowsAsVariantList;
+ mRowCount = mRows.size();
+
+ // Gather metadata the first time rows is set.
+ if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty())
+ fetchColumnMetadata();
+
+ endResetModel();
+
+ emit rowsChanged();
+
+ if (mRowCount != oldRowCount)
+ emit rowCountChanged();
+ if (mColumnCount != oldColumnCount)
+ emit columnCountChanged();
+}
+
+QQmlTableModel::ColumnRoleMetadata QQmlTableModel::fetchColumnRoleData(const QString &roleNameKey,
+ QQmlTableModelColumn *tableModelColumn, int columnIndex) const
+{
+ const QVariant firstRow = mRows.first();
+ ColumnRoleMetadata roleData;
+
+ QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleNameKey);
+ if (columnRoleGetter.isUndefined()) {
+ // This role is not defined, which is fine; just skip it.
+ return roleData;
+ }
+
+ if (columnRoleGetter.isString()) {
+ // The role is set as a string, so we assume the row is a simple object.
+ if (firstRow.type() != QVariant::Map) {
+ qmlWarning(this).quote() << "expected row for role "
+ << roleNameKey << " of TableModelColumn at index "
+ << columnIndex << " to be a simple object, but it's "
+ << firstRow.typeName() << " instead: " << firstRow;
+ return roleData;
+ }
+ const QVariantMap firstRowAsMap = firstRow.toMap();
+ const QString rolePropertyName = columnRoleGetter.toString();
+ const QVariant roleProperty = firstRowAsMap.value(rolePropertyName);
+
+ roleData.isStringRole = true;
+ roleData.name = rolePropertyName;
+ roleData.type = roleProperty.type();
+ roleData.typeName = QString::fromLatin1(roleProperty.typeName());
+ } else if (columnRoleGetter.isCallable()) {
+ // The role is provided via a function, which means the row is complex and
+ // the user needs to provide the data for it.
+ const auto modelIndex = index(0, columnIndex);
+ const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(modelIndex);
+ const QVariant cellData = columnRoleGetter.call(args).toVariant();
+
+ // We don't know the property name since it's provided through the function.
+ // roleData.name = ???
+ roleData.isStringRole = false;
+ roleData.type = cellData.type();
+ roleData.typeName = QString::fromLatin1(cellData.typeName());
+ } else {
+ // Invalid role.
+ qmlWarning(this) << "TableModelColumn role for column at index "
+ << columnIndex << " must be either a string or a function; actual type is: "
+ << columnRoleGetter.toString();
+ }
+
+ return roleData;
+}
+
+void QQmlTableModel::fetchColumnMetadata()
+{
+ qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:";
+
+ static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames();
+
+ // Since we support different data structures at the row level, we require that there
+ // is a TableModelColumn for each column.
+ // Collect and cache metadata for each column. This makes data lookup faster.
+ for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) {
+ QQmlTableModelColumn *column = mColumns.at(columnIndex);
+ qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":";
+
+ ColumnMetadata metaData;
+ const auto builtInRoleKeys = supportedRoleNames.keys();
+ for (const int builtInRoleKey : builtInRoleKeys) {
+ const QString builtInRoleName = supportedRoleNames.value(builtInRoleKey);
+ ColumnRoleMetadata roleData = fetchColumnRoleData(builtInRoleName, column, columnIndex);
+ if (roleData.type == QVariant::Invalid) {
+ // This built-in role was not specified in this column.
+ continue;
+ }
+
+ qCDebug(lcTableModel).nospace() << " - added metadata for built-in role "
+ << builtInRoleName << " at column index " << columnIndex
+ << ": name=" << roleData.name << " typeName=" << roleData.typeName
+ << " type=" << roleData.type;
+
+ // This column now supports this specific built-in role.
+ metaData.roles.insert(builtInRoleName, roleData);
+ // Add it if it doesn't already exist.
+ mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1();
+ }
+ mColumnMetadata.insert(columnIndex, metaData);
+ }
+}
+
+/*!
+ \qmlmethod TableModel::appendRow(object row)
+
+ Adds a new row to the end of the model, with the
+ values (cells) in \a row.
+
+ \code
+ model.appendRow({
+ checkable: true,
+ amount: 1,
+ fruitType: "Pear",
+ fruitName: "Williams",
+ fruitPrice: 1.50,
+ })
+ \endcode
+
+ \sa insertRow(), setRow(), removeRow()
+*/
+void QQmlTableModel::appendRow(const QVariant &row)
+{
+ if (!validateNewRow("appendRow()", row, -1, AppendOperation))
+ return;
+
+ doInsert(mRowCount, row);
+}
+
+/*!
+ \qmlmethod TableModel::clear()
+
+ Removes all rows from the model.
+
+ \sa removeRow()
+*/
+void QQmlTableModel::clear()
+{
+ QQmlEngine *engine = qmlEngine(this);
+ Q_ASSERT(engine);
+ setRows(QVariant::fromValue(engine->newArray()));
+}
+
+/*!
+ \qmlmethod object TableModel::getRow(int rowIndex)
+
+ Returns the row at \a rowIndex in the model.
+
+ Note that this equivalent to accessing the row directly
+ through the \l rows property:
+
+ \code
+ Component.onCompleted: {
+ // These two lines are equivalent.
+ console.log(model.getRow(0).display);
+ console.log(model.rows[0].fruitName);
+ }
+ \endcode
+
+ \note the returned object cannot be used to modify the contents of the
+ model; use setRow() instead.
+
+ \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow()
+*/
+QVariant QQmlTableModel::getRow(int rowIndex)
+{
+ if (!validateRowIndex("getRow()", "rowIndex", rowIndex))
+ return QVariant();
+
+ return mRows.at(rowIndex);
+}
+
+/*!
+ \qmlmethod TableModel::insertRow(int rowIndex, object row)
+
+ Adds a new row to the list model at position \a rowIndex, with the
+ values (cells) in \a row.
+
+ \code
+ model.insertRow(2, {
+ checkable: true, checked: false,
+ amount: 1,
+ fruitType: "Pear",
+ fruitName: "Williams",
+ fruitPrice: 1.50,
+ })
+ \endcode
+
+ The \a rowIndex must be to an existing item in the list, or one past
+ the end of the list (equivalent to \l appendRow()).
+
+ \sa appendRow(), setRow(), removeRow(), rowCount
+*/
+void QQmlTableModel::insertRow(int rowIndex, const QVariant &row)
+{
+ if (!validateNewRow("insertRow()", row, rowIndex))
+ return;
+
+ doInsert(rowIndex, row);
+}
+
+void QQmlTableModel::doInsert(int rowIndex, const QVariant &row)
+{
+ beginInsertRows(QModelIndex(), rowIndex, rowIndex);
+
+ // Adding rowAsVariant.toList() will add each invidual variant in the list,
+ // which is definitely not what we want.
+ const QVariant rowAsVariant = row.value<QJSValue>().toVariant();
+ mRows.insert(rowIndex, rowAsVariant);
+ ++mRowCount;
+
+ qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index "
+ << rowIndex << ":\n" << rowAsVariant.toMap();
+
+ // Gather metadata the first time a row is added.
+ if (mColumnMetadata.isEmpty())
+ fetchColumnMetadata();
+
+ endInsertRows();
+ emit rowCountChanged();
+}
+
+void QQmlTableModel::classBegin()
+{
+}
+
+void QQmlTableModel::componentComplete()
+{
+ componentCompleted = true;
+
+ mColumnCount = mColumns.size();
+ if (mColumnCount > 0)
+ emit columnCountChanged();
+
+ doSetRows(mRows);
+}
+
+/*!
+ \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows)
+
+ Moves \a rows from the index at \a fromRowIndex to the index at
+ \a toRowIndex.
+
+ The from and to ranges must exist; for example, to move the first 3 items
+ to the end of the list:
+
+ \code
+ model.moveRow(0, model.rowCount - 3, 3)
+ \endcode
+
+ \sa appendRow(), insertRow(), removeRow(), rowCount
+*/
+void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows)
+{
+ if (fromRowIndex == toRowIndex) {
+ qmlWarning(this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\"";
+ return;
+ }
+
+ if (rows <= 0) {
+ qmlWarning(this) << "moveRow(): \"rows\" is less than or equal to 0";
+ return;
+ }
+
+ if (!validateRowIndex("moveRow()", "fromRowIndex", fromRowIndex))
+ return;
+
+ if (!validateRowIndex("moveRow()", "toRowIndex", toRowIndex))
+ return;
+
+ if (fromRowIndex + rows > mRowCount) {
+ qmlWarning(this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex
+ << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows)
+ << ", which is greater than rowCount() of " << mRowCount;
+ return;
+ }
+
+ if (toRowIndex + rows > mRowCount) {
+ qmlWarning(this) << "moveRow(): \"toRowIndex\" (" << toRowIndex
+ << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows)
+ << ", which is greater than rowCount() of " << mRowCount;
+ return;
+ }
+
+ qCDebug(lcTableModel).nospace() << "moving " << rows
+ << " row(s) from index " << fromRowIndex
+ << " to index " << toRowIndex;
+
+ // Based on the same call in QQmlListModel::moveRow().
+ beginMoveRows(QModelIndex(), fromRowIndex, fromRowIndex + rows - 1, QModelIndex(),
+ toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex);
+
+ // Based on ListModel::moveRow().
+ if (fromRowIndex > toRowIndex) {
+ // Only move forwards - flip if moving backwards.
+ const int from = fromRowIndex;
+ const int to = toRowIndex;
+ fromRowIndex = to;
+ toRowIndex = to + rows;
+ rows = from - to;
+ }
+
+ QVector<QVariant> store;
+ store.reserve(rows);
+ for (int i = 0; i < (toRowIndex - fromRowIndex); ++i)
+ store.append(mRows.at(fromRowIndex + rows + i));
+ for (int i = 0; i < rows; ++i)
+ store.append(mRows.at(fromRowIndex + i));
+ for (int i = 0; i < store.size(); ++i)
+ mRows[fromRowIndex + i] = store[i];
+
+ qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows;
+
+ endMoveRows();
+}
+
+/*!
+ \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1)
+
+ Removes the row at \a rowIndex from the model.
+
+ \sa clear(), rowCount
+*/
+void QQmlTableModel::removeRow(int rowIndex, int rows)
+{
+ if (!validateRowIndex("removeRow()", "rowIndex", rowIndex))
+ return;
+
+ if (rows <= 0) {
+ qmlWarning(this) << "removeRow(): \"rows\" is less than or equal to zero";
+ return;
+ }
+
+ if (rowIndex + rows - 1 >= mRowCount) {
+ qmlWarning(this) << "removeRow(): \"rows\" " << rows
+ << " exceeds available rowCount() of " << mRowCount
+ << " when removing from \"rowIndex\" " << rowIndex;
+ return;
+ }
+
+ beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1);
+
+ auto firstIterator = mRows.begin() + rowIndex;
+ // The "last" argument to erase() is exclusive, so we go one past the last item.
+ auto lastIterator = firstIterator + rows;
+ mRows.erase(firstIterator, lastIterator);
+ mRowCount -= rows;
+
+ endRemoveRows();
+ emit rowCountChanged();
+
+ qCDebug(lcTableModel).nospace() << "removed " << rows
+ << " items from the model, starting at index " << rowIndex;
+}
+
+/*!
+ \qmlmethod TableModel::setRow(int rowIndex, object row)
+
+ Changes the row at \a rowIndex in the model with \a row.
+
+ All columns/cells must be present in \c row, and in the correct order.
+
+ \code
+ model.setRow(0, {
+ checkable: true,
+ amount: 1,
+ fruitType: "Pear",
+ fruitName: "Williams",
+ fruitPrice: 1.50,
+ })
+ \endcode
+
+ If \a rowIndex is equal to \c rowCount(), then a new row is appended to the
+ model. Otherwise, \a rowIndex must point to an existing row in the model.
+
+ \sa appendRow(), insertRow(), rowCount
+*/
+void QQmlTableModel::setRow(int rowIndex, const QVariant &row)
+{
+ if (!validateNewRow("setRow()", row, rowIndex))
+ return;
+
+ if (rowIndex != mRowCount) {
+ // Setting an existing row.
+ mRows[rowIndex] = row;
+
+ // For now we just assume the whole row changed, as it's simpler.
+ const QModelIndex topLeftModelIndex(createIndex(rowIndex, 0));
+ const QModelIndex bottomRightModelIndex(createIndex(rowIndex, mColumnCount - 1));
+ emit dataChanged(topLeftModelIndex, bottomRightModelIndex);
+ } else {
+ // Appending a row.
+ doInsert(rowIndex, row);
+ }
+}
+
+QQmlListProperty<QQmlTableModelColumn> QQmlTableModel::columns()
+{
+ return QQmlListProperty<QQmlTableModelColumn>(this, nullptr,
+ &QQmlTableModel::columns_append,
+ &QQmlTableModel::columns_count,
+ &QQmlTableModel::columns_at,
+ &QQmlTableModel::columns_clear);
+}
+
+void QQmlTableModel::columns_append(QQmlListProperty<QQmlTableModelColumn> *property,
+ QQmlTableModelColumn *value)
+{
+ QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object);
+ QQmlTableModelColumn *column = qobject_cast<QQmlTableModelColumn*>(value);
+ if (column)
+ model->mColumns.append(column);
+}
+
+int QQmlTableModel::columns_count(QQmlListProperty<QQmlTableModelColumn> *property)
+{
+ const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object);
+ return model->mColumns.count();
+}
+
+QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty<QQmlTableModelColumn> *property, int index)
+{
+ const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object);
+ return model->mColumns.at(index);
+}
+
+void QQmlTableModel::columns_clear(QQmlListProperty<QQmlTableModelColumn> *property)
+{
+ QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object);
+ return model->mColumns.clear();
+}
+
+/*!
+ \qmlmethod QModelIndex TableModel::index(int row, int column)
+
+ Returns a \l QModelIndex object referencing the given \a row and \a column,
+ which can be passed to the data() function to get the data from that cell,
+ or to setData() to edit the contents of that cell.
+
+ \code
+ import QtQml 2.14
+ import Qt.labs.qmlmodels 1.0
+
+ TableModel {
+ id: model
+
+ TableModelColumn { display: "fruitType" }
+ TableModelColumn { display: "fruitPrice" }
+
+ rows: [
+ { fruitType: "Apple", fruitPrice: 1.50 },
+ { fruitType: "Orange", fruitPrice: 2.50 }
+ ]
+
+ Component.onCompleted: {
+ for (var r = 0; r < model.rowCount; ++r) {
+ console.log("An " + model.data(model.index(r, 0)).display +
+ " costs " + model.data(model.index(r, 1)).display.toFixed(2))
+ }
+ }
+ }
+ \endcode
+
+ \sa {QModelIndex and related Classes in QML}, data()
+*/
+// Note: we don't document the parent argument, because you never need it, because
+// cells in a TableModel don't have parents. But it is there because this function is an override.
+QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const
+{
+ return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid()
+ ? createIndex(row, column)
+ : QModelIndex();
+}
+
+/*!
+ \qmlproperty int TableModel::rowCount
+ \readonly
+
+ This read-only property holds the number of rows in the model.
+
+ This value changes whenever rows are added or removed from the model.
+*/
+int QQmlTableModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mRowCount;
+}
+
+/*!
+ \qmlproperty int TableModel::columnCount
+ \readonly
+
+ This read-only property holds the number of columns in the model.
+
+ The number of columns is fixed for the lifetime of the model
+ after the \l rows property is set or \l appendRow() is called for the first
+ time.
+*/
+int QQmlTableModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mColumnCount;
+}
+
+/*!
+ \qmlmethod variant TableModel::data(QModelIndex index, string role)
+
+ Returns the data from the table cell at the given \a index belonging to the
+ given \a role.
+
+ \sa index()
+*/
+QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const
+{
+ const int iRole = mRoleNames.key(role.toUtf8(), -1);
+ if (iRole >= 0)
+ return data(index, iRole);
+ return QVariant();
+}
+
+QVariant QQmlTableModel::data(const QModelIndex &index, int role) const
+{
+ const int row = index.row();
+ if (row < 0 || row >= rowCount())
+ return QVariant();
+
+ const int column = index.column();
+ if (column < 0 || column >= columnCount())
+ return QVariant();
+
+ const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column());
+ const QString roleName = QString::fromUtf8(mRoleNames.value(role));
+ if (!columnMetadata.roles.contains(roleName)) {
+ qmlWarning(this) << "setData(): no role named " << roleName
+ << " at column index " << column << ". The available roles for that column are: "
+ << columnMetadata.roles.keys();
+ return QVariant();
+ }
+
+ const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName);
+ if (roleData.isStringRole) {
+ // We know the data structure, so we can get the data for the user.
+ const QVariantMap rowData = mRows.at(row).toMap();
+ const QString propertyName = columnMetadata.roles.value(roleName).name;
+ const QVariant value = rowData.value(propertyName);
+ return value;
+ }
+
+ // We don't know the data structure, so the user has to modify their data themselves.
+ // First, find the getter for this column and role.
+ QJSValue getter = mColumns.at(column)->getterAtRole(roleName);
+
+ // Then, call it and return what it returned.
+ const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(index);
+ return getter.call(args).toVariant();
+}
+
+/*!
+ \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value)
+
+ Inserts or updates the data field named by \a role in the table cell at the
+ given \a index with \a value. Returns true if sucessful, false if not.
+
+ \sa index()
+*/
+bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value)
+{
+ const int intRole = mRoleNames.key(role.toUtf8(), -1);
+ if (intRole >= 0)
+ return setData(index, value, intRole);
+ return false;
+}
+
+bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ const int row = index.row();
+ if (row < 0 || row >= rowCount())
+ return false;
+
+ const int column = index.column();
+ if (column < 0 || column >= columnCount())
+ return false;
+
+ const QString roleName = QString::fromUtf8(mRoleNames.value(role));
+
+ qCDebug(lcTableModel).nospace() << "setData() called with index "
+ << index << ", value " << value << " and role " << roleName;
+
+ // Verify that the role exists for this column.
+ const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column());
+ if (!columnMetadata.roles.contains(roleName)) {
+ qmlWarning(this) << "setData(): no role named \"" << roleName
+ << "\" at column index " << column << ". The available roles for that column are: "
+ << columnMetadata.roles.keys();
+ return false;
+ }
+
+ // Verify that the type of the value is what we expect.
+ // If the value set is not of the expected type, we can try to convert it automatically.
+ const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName);
+ QVariant effectiveValue = value;
+ if (value.type() != roleData.type) {
+ if (!value.canConvert(int(roleData.type))) {
+ qmlWarning(this).nospace() << "setData(): the value " << value
+ << " set at row " << row << " column " << column << " with role " << roleName
+ << " cannot be converted to " << roleData.typeName;
+ return false;
+ }
+
+ if (!effectiveValue.convert(int(roleData.type))) {
+ qmlWarning(this).nospace() << "setData(): failed converting value " << value
+ << " set at row " << row << " column " << column << " with role " << roleName
+ << " to " << roleData.typeName;
+ return false;
+ }
+ }
+
+ if (roleData.isStringRole) {
+ // We know the data structure, so we can set it for the user.
+ QVariantMap modifiedRow = mRows.at(row).toMap();
+ modifiedRow[roleData.name] = value;
+
+ mRows[row] = modifiedRow;
+ } else {
+ // We don't know the data structure, so the user has to modify their data themselves.
+ auto engine = qmlEngine(this);
+ auto args = QJSValueList()
+ // arg 0: modelIndex.
+ << engine->toScriptValue(index)
+ // arg 1: cellData.
+ << engine->toScriptValue(value);
+ // Do the actual setting.
+ QJSValue setter = mColumns.at(column)->setterAtRole(roleName);
+ setter.call(args);
+
+ /*
+ The chain of events so far:
+
+ - User did e.g.: model.edit = textInput.text
+ - setData() is called
+ - setData() calls the setter
+ (remember that we need to emit the dataChanged() signal,
+ which is why the user can't just set the data directly in the delegate)
+
+ Now the user's setter function has modified *their* copy of the
+ data, but *our* copy of the data is old. Imagine the getters and setters looked like this:
+
+ display: function(modelIndex) { return rows[modelIndex.row][1].amount }
+ setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData }
+
+ We don't know the structure of the user's data, so we can't just do
+ what we do above for the isStringRole case:
+
+ modifiedRow[column][roleName] = value
+
+ This means that, besides getting the implicit row count when rows is initially set,
+ our copy of the data is unused when it comes to complex columns.
+
+ Another point to note is that we can't pass rowData in to the getter as a convenience,
+ because we would be passing in *our* copy of the row, which is not up-to-date.
+ Since the user already has access to the data, it's not a big deal for them to do:
+
+ display: function(modelIndex) { return rows[modelIndex.row][1].amount }
+
+ instead of:
+
+ display: function(modelIndex, rowData) { return rowData[1].amount }
+ */
+ }
+
+ QVector<int> rolesChanged;
+ rolesChanged.append(role);
+ emit dataChanged(index, index, rolesChanged);
+
+ return true;
+}
+
+QHash<int, QByteArray> QQmlTableModel::roleNames() const
+{
+ return mRoleNames;
+}
+
+QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata()
+{
+}
+
+QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata(
+ bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) :
+ isStringRole(isStringRole),
+ name(name),
+ type(type),
+ typeName(typeName)
+{
+}
+
+bool QQmlTableModel::ColumnRoleMetadata::isValid() const
+{
+ return !name.isEmpty();
+}
+
+bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const
+{
+ if (!row.canConvert<QJSValue>()) {
+ qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue,"
+ << " but got " << row.typeName() << " instead:\n" << row;
+ return false;
+ }
+
+ const QJSValue rowAsJSValue = row.value<QJSValue>();
+ if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) {
+ qmlWarning(this) << functionName << ": expected \"row\" argument "
+ << "to be an object or array, but got:\n" << rowAsJSValue.toString();
+ return false;
+ }
+
+ return true;
+}
+
+bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row,
+ int rowIndex, NewRowOperationFlag operation) const
+{
+ if (mColumnMetadata.isEmpty()) {
+ // There is no column metadata, so we have nothing to validate the row against.
+ // Rows have to be added before we can gather metadata from them, so just this
+ // once we'll return true to allow the rows to be added.
+ return true;
+ }
+
+ // Don't require each row to be a QJSValue when setting all rows,
+ // as they won't be; they'll be QVariantMap.
+ if (operation != SetRowsOperation && !validateRowType(functionName, row))
+ return false;
+
+ if (operation == OtherOperation) {
+ // Inserting/setting.
+ if (rowIndex < 0) {
+ qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative";
+ return false;
+ }
+
+ if (rowIndex > mRowCount) {
+ qmlWarning(this) << functionName << ": \"rowIndex\" " << rowIndex
+ << " is greater than rowCount() of " << mRowCount;
+ return false;
+ }
+ }
+
+ const QVariant rowAsVariant = operation == SetRowsOperation
+ ? row : row.value<QJSValue>().toVariant();
+ if (rowAsVariant.type() != QVariant::Map) {
+ qmlWarning(this) << functionName << ": row manipulation functions "
+ << "do not support complex rows (row index: " << rowIndex << ")";
+ return false;
+ }
+
+ const QVariantMap rowAsMap = rowAsVariant.toMap();
+ const int columnCount = rowAsMap.size();
+ if (columnCount < mColumnCount) {
+ qmlWarning(this) << functionName << ": expected " << mColumnCount
+ << " columns, but only got " << columnCount;
+ return false;
+ }
+
+ // We can't validate complex structures, but we can make sure that
+ // each simple string-based role in each column is correct.
+ for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) {
+ QQmlTableModelColumn *column = mColumns.at(columnIndex);
+ const QHash<QString, QJSValue> getters = column->getters();
+ const auto roleNames = getters.keys();
+ const ColumnMetadata columnMetadata = mColumnMetadata.at(columnIndex);
+ for (const QString &roleName : roleNames) {
+ const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName);
+ if (!roleData.isStringRole)
+ continue;
+
+ if (!rowAsMap.contains(roleData.name)) {
+ qmlWarning(this).quote() << functionName << ": expected a property named "
+ << roleData.name << " in row at index " << rowIndex << ", but couldn't find one";
+ return false;
+ }
+
+ const QVariant rolePropertyValue = rowAsMap.value(roleData.name);
+ if (rolePropertyValue.type() != roleData.type) {
+ qmlWarning(this).quote() << functionName << ": expected the property named "
+ << roleData.name << " to be of type " << roleData.typeName
+ << ", but got " << QString::fromLatin1(rolePropertyValue.typeName()) << " instead";
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const
+{
+ if (rowIndex < 0) {
+ qmlWarning(this) << functionName << ": \"" << argumentName << "\" cannot be negative";
+ return false;
+ }
+
+ if (rowIndex >= mRowCount) {
+ qmlWarning(this) << functionName << ": \"" << argumentName
+ << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount;
+ return false;
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlmodels/qqmltablemodel_p.h b/src/qmlmodels/qqmltablemodel_p.h
new file mode 100644
index 0000000000..b3c0cc2848
--- /dev/null
+++ b/src/qmlmodels/qqmltablemodel_p.h
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLTABLEMODEL_P_H
+#define QQMLTABLEMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractTableModel>
+#include <QtQml/qqml.h>
+#include <QtQmlModels/private/qtqmlmodelsglobal_p.h>
+#include <QtQmlModels/private/qqmltablemodelcolumn_p.h>
+#include <QtQml/QJSValue>
+#include <QtQml/QQmlListProperty>
+
+QT_REQUIRE_CONFIG(qml_table_model);
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged FINAL)
+ Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged FINAL)
+ Q_PROPERTY(QVariant rows READ rows WRITE setRows NOTIFY rowsChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<QQmlTableModelColumn> columns READ columns CONSTANT FINAL)
+ Q_INTERFACES(QQmlParserStatus)
+ Q_CLASSINFO("DefaultProperty", "columns")
+
+public:
+ QQmlTableModel(QObject *parent = nullptr);
+ ~QQmlTableModel() override;
+
+ QVariant rows() const;
+ void setRows(const QVariant &rows);
+
+ Q_INVOKABLE void appendRow(const QVariant &row);
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE QVariant getRow(int rowIndex);
+ Q_INVOKABLE void insertRow(int rowIndex, const QVariant &row);
+ Q_INVOKABLE void moveRow(int fromRowIndex, int toRowIndex, int rows = 1);
+ Q_INVOKABLE void removeRow(int rowIndex, int rows = 1);
+ Q_INVOKABLE void setRow(int rowIndex, const QVariant &row);
+
+ QQmlListProperty<QQmlTableModelColumn> columns();
+
+ static void columns_append(QQmlListProperty<QQmlTableModelColumn> *property, QQmlTableModelColumn *value);
+ static int columns_count(QQmlListProperty<QQmlTableModelColumn> *property);
+ static QQmlTableModelColumn *columns_at(QQmlListProperty<QQmlTableModelColumn> *property, int index);
+ static void columns_clear(QQmlListProperty<QQmlTableModelColumn> *property);
+
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ Q_INVOKABLE QVariant data(const QModelIndex &index, const QString &role) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ Q_INVOKABLE bool setData(const QModelIndex &index, const QString &role, const QVariant &value);
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
+ QHash<int, QByteArray> roleNames() const override;
+
+Q_SIGNALS:
+ void columnCountChanged();
+ void rowCountChanged();
+ void rowsChanged();
+
+private:
+ class ColumnRoleMetadata
+ {
+ public:
+ ColumnRoleMetadata();
+ ColumnRoleMetadata(bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName);
+
+ bool isValid() const;
+
+ // If this is false, it's a function role.
+ bool isStringRole = false;
+ QString name;
+ QVariant::Type type = QVariant::Invalid;
+ QString typeName;
+ };
+
+ struct ColumnMetadata
+ {
+ // Key = role name that will be made visible to the delegate
+ // Value = metadata about that role, including actual name in the model data, type, etc.
+ QHash<QString, ColumnRoleMetadata> roles;
+ };
+
+ enum NewRowOperationFlag {
+ OtherOperation, // insert(), set(), etc.
+ SetRowsOperation,
+ AppendOperation
+ };
+
+ void doSetRows(const QVariantList &rowsAsVariantList);
+ ColumnRoleMetadata fetchColumnRoleData(const QString &roleNameKey,
+ QQmlTableModelColumn *tableModelColumn, int columnIndex) const;
+ void fetchColumnMetadata();
+
+ bool validateRowType(const char *functionName, const QVariant &row) const;
+ bool validateNewRow(const char *functionName, const QVariant &row,
+ int rowIndex, NewRowOperationFlag operation = OtherOperation) const;
+ bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const;
+
+ void doInsert(int rowIndex, const QVariant &row);
+
+ void classBegin() override;
+ void componentComplete() override;
+
+ bool componentCompleted = false;
+ QVariantList mRows;
+ QList<QQmlTableModelColumn *> mColumns;
+ int mRowCount = 0;
+ int mColumnCount = 0;
+ // Each entry contains information about the properties of the column at that index.
+ QVector<ColumnMetadata> mColumnMetadata;
+ // key = property index (0 to number of properties across all columns)
+ // value = role name
+ QHash<int, QByteArray> mRoleNames;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlTableModel)
+
+#endif // QQMLTABLEMODEL_P_H
diff --git a/src/qmlmodels/qqmltablemodelcolumn.cpp b/src/qmlmodels/qqmltablemodelcolumn.cpp
new file mode 100644
index 0000000000..93da0642de
--- /dev/null
+++ b/src/qmlmodels/qqmltablemodelcolumn.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qqmltablemodelcolumn_p.h"
+
+#include <QtQml/qqmlinfo.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype TableModelColumn
+ \instantiates QQmlTableModelColumn
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Represents a column in a model.
+ \since 5.14
+
+ \section1 Supported Roles
+
+ TableModelColumn supports all of \l {Qt::ItemDataRole}{Qt's roles},
+ with the exception of \c Qt::InitialSortOrderRole.
+
+ \sa TableModel, TableView
+*/
+
+static const QString displayRoleName = QStringLiteral("display");
+static const QString decorationRoleName = QStringLiteral("decoration");
+static const QString editRoleName = QStringLiteral("edit");
+static const QString toolTipRoleName = QStringLiteral("toolTip");
+static const QString statusTipRoleName = QStringLiteral("statusTip");
+static const QString whatsThisRoleName = QStringLiteral("whatsThis");
+
+static const QString fontRoleName = QStringLiteral("font");
+static const QString textAlignmentRoleName = QStringLiteral("textAlignment");
+static const QString backgroundRoleName = QStringLiteral("background");
+static const QString foregroundRoleName = QStringLiteral("foreground");
+static const QString checkStateRoleName = QStringLiteral("checkState");
+
+static const QString accessibleTextRoleName = QStringLiteral("accessibleText");
+static const QString accessibleDescriptionRoleName = QStringLiteral("accessibleDescription");
+
+static const QString sizeHintRoleName = QStringLiteral("sizeHint");
+
+
+QQmlTableModelColumn::QQmlTableModelColumn(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QQmlTableModelColumn::~QQmlTableModelColumn()
+{
+}
+
+#define DEFINE_ROLE_PROPERTIES(getterGetterName, getterSetterName, getterSignal, setterGetterName, setterSetterName, setterSignal, roleName) \
+QJSValue QQmlTableModelColumn::getterGetterName() const \
+{ \
+ return mGetters.value(roleName); \
+} \
+\
+void QQmlTableModelColumn::getterSetterName(const QJSValue &stringOrFunction) \
+{ \
+ if (!stringOrFunction.isString() && !stringOrFunction.isCallable()) { \
+ qmlWarning(this).quote() << "getter for " << roleName << " must be a function"; \
+ return; \
+ } \
+ if (stringOrFunction.strictlyEquals(decoration())) \
+ return; \
+\
+ mGetters[roleName] = stringOrFunction; \
+ emit decorationChanged(); \
+} \
+\
+QJSValue QQmlTableModelColumn::setterGetterName() const \
+{ \
+ return mSetters.value(roleName); \
+} \
+\
+void QQmlTableModelColumn::setterSetterName(const QJSValue &function) \
+{ \
+ if (!function.isCallable()) { \
+ qmlWarning(this).quote() << "setter for " << roleName << " must be a function"; \
+ return; \
+ } \
+\
+ if (function.strictlyEquals(getSetDisplay())) \
+ return; \
+\
+ mSetters[roleName] = function; \
+ emit setDisplayChanged(); \
+}
+
+DEFINE_ROLE_PROPERTIES(display, setDisplay, displayChanged,
+ getSetDisplay, setSetDisplay, setDisplayChanged, displayRoleName)
+DEFINE_ROLE_PROPERTIES(decoration, setDecoration, decorationChanged,
+ getSetDecoration, setSetDecoration, setDecorationChanged, decorationRoleName)
+DEFINE_ROLE_PROPERTIES(edit, setEdit, editChanged,
+ getSetEdit, setSetEdit, setEditChanged, editRoleName)
+DEFINE_ROLE_PROPERTIES(toolTip, setToolTip, toolTipChanged,
+ getSetToolTip, setSetToolTip, setToolTipChanged, toolTipRoleName)
+DEFINE_ROLE_PROPERTIES(statusTip, setStatusTip, statusTipChanged,
+ getSetStatusTip, setSetStatusTip, setStatusTipChanged, statusTipRoleName)
+DEFINE_ROLE_PROPERTIES(whatsThis, setWhatsThis, whatsThisChanged,
+ getSetWhatsThis, setSetWhatsThis, setWhatsThisChanged, whatsThisRoleName)
+
+DEFINE_ROLE_PROPERTIES(font, setFont, fontChanged,
+ getSetFont, setSetFont, setFontChanged, fontRoleName)
+DEFINE_ROLE_PROPERTIES(textAlignment, setTextAlignment, textAlignmentChanged,
+ getSetTextAlignment, setSetTextAlignment, setTextAlignmentChanged, textAlignmentRoleName)
+DEFINE_ROLE_PROPERTIES(background, setBackground, backgroundChanged,
+ getSetBackground, setSetBackground, setBackgroundChanged, backgroundRoleName)
+DEFINE_ROLE_PROPERTIES(foreground, setForeground, foregroundChanged,
+ getSetForeground, setSetForeground, setForegroundChanged, foregroundRoleName)
+DEFINE_ROLE_PROPERTIES(checkState, setCheckState, checkStateChanged,
+ getSetCheckState, setSetCheckState, setCheckStateChanged, checkStateRoleName)
+
+DEFINE_ROLE_PROPERTIES(accessibleText, setAccessibleText, accessibleTextChanged,
+ getSetAccessibleText, setSetAccessibleText, setAccessibleTextChanged, accessibleTextRoleName)
+DEFINE_ROLE_PROPERTIES(accessibleDescription, setAccessibleDescription, accessibleDescriptionChanged,
+ getSetAccessibleDescription, setSetAccessibleDescription, setAccessibleDescriptionChanged, accessibleDescriptionRoleName)
+
+DEFINE_ROLE_PROPERTIES(sizeHint, setSizeHint, sizeHintChanged,
+ getSetSizeHint, setSetSizeHint, setSizeHintChanged, sizeHintRoleName)
+
+QJSValue QQmlTableModelColumn::getterAtRole(const QString &roleName)
+{
+ auto it = mGetters.find(roleName);
+ if (it == mGetters.end())
+ return QJSValue();
+ return *it;
+}
+
+QJSValue QQmlTableModelColumn::setterAtRole(const QString &roleName)
+{
+ auto it = mSetters.find(roleName);
+ if (it == mSetters.end())
+ return QJSValue();
+ return *it;
+}
+
+const QHash<QString, QJSValue> QQmlTableModelColumn::getters() const
+{
+ return mGetters;
+}
+
+const QHash<int, QString> QQmlTableModelColumn::supportedRoleNames()
+{
+ QHash<int, QString> names;
+ names[Qt::DisplayRole] = QLatin1String("display");
+ names[Qt::DecorationRole] = QLatin1String("decoration");
+ names[Qt::EditRole] = QLatin1String("edit");
+ names[Qt::ToolTipRole] = QLatin1String("toolTip");
+ names[Qt::StatusTipRole] = QLatin1String("statusTip");
+ names[Qt::WhatsThisRole] = QLatin1String("whatsThis");
+ names[Qt::FontRole] = QLatin1String("font");
+ names[Qt::TextAlignmentRole] = QLatin1String("textAlignment");
+ names[Qt::BackgroundRole] = QLatin1String("background");
+ names[Qt::ForegroundRole] = QLatin1String("foreground");
+ names[Qt::CheckStateRole] = QLatin1String("checkState");
+ names[Qt::AccessibleTextRole] = QLatin1String("accessibleText");
+ names[Qt::AccessibleDescriptionRole] = QLatin1String("accessibleDescription");
+ names[Qt::SizeHintRole] = QLatin1String("sizeHint");
+ return names;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlmodels/qqmltablemodelcolumn_p.h b/src/qmlmodels/qqmltablemodelcolumn_p.h
new file mode 100644
index 0000000000..0b6478ce38
--- /dev/null
+++ b/src/qmlmodels/qqmltablemodelcolumn_p.h
@@ -0,0 +1,226 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQMLTABLEMODELCOLUMN_P_H
+#define QQMLTABLEMODELCOLUMN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtQml/qqml.h>
+#include <QtQmlModels/private/qtqmlmodelsglobal_p.h>
+#include <QtQml/qjsvalue.h>
+
+QT_REQUIRE_CONFIG(qml_table_model);
+
+QT_BEGIN_NAMESPACE
+
+class Q_QMLMODELS_PRIVATE_EXPORT QQmlTableModelColumn : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QJSValue display READ display WRITE setDisplay NOTIFY displayChanged FINAL)
+ Q_PROPERTY(QJSValue setDisplay READ getSetDisplay WRITE setSetDisplay NOTIFY setDisplayChanged)
+ Q_PROPERTY(QJSValue decoration READ decoration WRITE setDecoration NOTIFY decorationChanged FINAL)
+ Q_PROPERTY(QJSValue setDecoration READ getSetDecoration WRITE setSetDecoration NOTIFY setDecorationChanged FINAL)
+ Q_PROPERTY(QJSValue edit READ edit WRITE setEdit NOTIFY editChanged FINAL)
+ Q_PROPERTY(QJSValue setEdit READ getSetEdit WRITE setSetEdit NOTIFY setEditChanged FINAL)
+ Q_PROPERTY(QJSValue toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged FINAL)
+ Q_PROPERTY(QJSValue setToolTip READ getSetToolTip WRITE setSetToolTip NOTIFY setToolTipChanged FINAL)
+ Q_PROPERTY(QJSValue statusTip READ statusTip WRITE setStatusTip NOTIFY statusTipChanged FINAL)
+ Q_PROPERTY(QJSValue setStatusTip READ getSetStatusTip WRITE setSetStatusTip NOTIFY setStatusTipChanged FINAL)
+ Q_PROPERTY(QJSValue whatsThis READ whatsThis WRITE setWhatsThis NOTIFY whatsThisChanged FINAL)
+ Q_PROPERTY(QJSValue setWhatsThis READ getSetWhatsThis WRITE setSetWhatsThis NOTIFY setWhatsThisChanged FINAL)
+
+ Q_PROPERTY(QJSValue font READ font WRITE setFont NOTIFY fontChanged FINAL)
+ Q_PROPERTY(QJSValue setFont READ getSetFont WRITE setSetFont NOTIFY setFontChanged FINAL)
+ Q_PROPERTY(QJSValue textAlignment READ textAlignment WRITE setTextAlignment NOTIFY textAlignmentChanged FINAL)
+ Q_PROPERTY(QJSValue setTextAlignment READ getSetTextAlignment WRITE setSetTextAlignment NOTIFY setTextAlignmentChanged FINAL)
+ Q_PROPERTY(QJSValue background READ background WRITE setBackground NOTIFY backgroundChanged FINAL)
+ Q_PROPERTY(QJSValue setBackground READ getSetBackground WRITE setSetBackground NOTIFY setBackgroundChanged FINAL)
+ Q_PROPERTY(QJSValue foreground READ foreground WRITE setForeground NOTIFY foregroundChanged FINAL)
+ Q_PROPERTY(QJSValue setForeground READ getSetForeground WRITE setSetForeground NOTIFY setForegroundChanged FINAL)
+ Q_PROPERTY(QJSValue checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL)
+ Q_PROPERTY(QJSValue setCheckState READ getSetCheckState WRITE setSetCheckState NOTIFY setCheckStateChanged FINAL)
+
+ Q_PROPERTY(QJSValue accessibleText READ accessibleText WRITE setAccessibleText NOTIFY accessibleTextChanged FINAL)
+ Q_PROPERTY(QJSValue setAccessibleText READ getSetAccessibleText WRITE setSetAccessibleText NOTIFY setAccessibleTextChanged FINAL)
+ Q_PROPERTY(QJSValue accessibleDescription READ accessibleDescription
+ WRITE setAccessibleDescription NOTIFY accessibleDescriptionChanged FINAL)
+ Q_PROPERTY(QJSValue setAccessibleDescription READ getSetAccessibleDescription
+ WRITE setSetAccessibleDescription NOTIFY setAccessibleDescriptionChanged FINAL)
+
+ Q_PROPERTY(QJSValue sizeHint READ sizeHint WRITE setSizeHint NOTIFY sizeHintChanged FINAL)
+ Q_PROPERTY(QJSValue setSizeHint READ getSetSizeHint WRITE setSetSizeHint NOTIFY setSizeHintChanged FINAL)
+
+public:
+ QQmlTableModelColumn(QObject *parent = nullptr);
+ ~QQmlTableModelColumn() override;
+
+ QJSValue display() const;
+ void setDisplay(const QJSValue &stringOrFunction);
+ QJSValue getSetDisplay() const;
+ void setSetDisplay(const QJSValue &function);
+
+ QJSValue decoration() const;
+ void setDecoration(const QJSValue &stringOrFunction);
+ QJSValue getSetDecoration() const;
+ void setSetDecoration(const QJSValue &function);
+
+ QJSValue edit() const;
+ void setEdit(const QJSValue &stringOrFunction);
+ QJSValue getSetEdit() const;
+ void setSetEdit(const QJSValue &function);
+
+ QJSValue toolTip() const;
+ void setToolTip(const QJSValue &stringOrFunction);
+ QJSValue getSetToolTip() const;
+ void setSetToolTip(const QJSValue &function);
+
+ QJSValue statusTip() const;
+ void setStatusTip(const QJSValue &stringOrFunction);
+ QJSValue getSetStatusTip() const;
+ void setSetStatusTip(const QJSValue &function);
+
+ QJSValue whatsThis() const;
+ void setWhatsThis(const QJSValue &stringOrFunction);
+ QJSValue getSetWhatsThis() const;
+ void setSetWhatsThis(const QJSValue &function);
+
+ QJSValue font() const;
+ void setFont(const QJSValue &stringOrFunction);
+ QJSValue getSetFont() const;
+ void setSetFont(const QJSValue &function);
+
+ QJSValue textAlignment() const;
+ void setTextAlignment(const QJSValue &stringOrFunction);
+ QJSValue getSetTextAlignment() const;
+ void setSetTextAlignment(const QJSValue &function);
+
+ QJSValue background() const;
+ void setBackground(const QJSValue &stringOrFunction);
+ QJSValue getSetBackground() const;
+ void setSetBackground(const QJSValue &function);
+
+ QJSValue foreground() const;
+ void setForeground(const QJSValue &stringOrFunction);
+ QJSValue getSetForeground() const;
+ void setSetForeground(const QJSValue &function);
+
+ QJSValue checkState() const;
+ void setCheckState(const QJSValue &stringOrFunction);
+ QJSValue getSetCheckState() const;
+ void setSetCheckState(const QJSValue &function);
+
+ QJSValue accessibleText() const;
+ void setAccessibleText(const QJSValue &stringOrFunction);
+ QJSValue getSetAccessibleText() const;
+ void setSetAccessibleText(const QJSValue &function);
+
+ QJSValue accessibleDescription() const;
+ void setAccessibleDescription(const QJSValue &stringOrFunction);
+ QJSValue getSetAccessibleDescription() const;
+ void setSetAccessibleDescription(const QJSValue &function);
+
+ QJSValue sizeHint() const;
+ void setSizeHint(const QJSValue &stringOrFunction);
+ QJSValue getSetSizeHint() const;
+ void setSetSizeHint(const QJSValue &function);
+
+ QJSValue getterAtRole(const QString &roleName);
+ QJSValue setterAtRole(const QString &roleName);
+
+ const QHash<QString, QJSValue> getters() const;
+
+ static const QHash<int, QString> supportedRoleNames();
+
+Q_SIGNALS:
+ void indexChanged();
+ void displayChanged();
+ void setDisplayChanged();
+ void decorationChanged();
+ void setDecorationChanged();
+ void editChanged();
+ void setEditChanged();
+ void toolTipChanged();
+ void setToolTipChanged();
+ void statusTipChanged();
+ void setStatusTipChanged();
+ void whatsThisChanged();
+ void setWhatsThisChanged();
+
+ void fontChanged();
+ void setFontChanged();
+ void textAlignmentChanged();
+ void setTextAlignmentChanged();
+ void backgroundChanged();
+ void setBackgroundChanged();
+ void foregroundChanged();
+ void setForegroundChanged();
+ void checkStateChanged();
+ void setCheckStateChanged();
+
+ void accessibleTextChanged();
+ void setAccessibleTextChanged();
+ void accessibleDescriptionChanged();
+ void setAccessibleDescriptionChanged();
+ void sizeHintChanged();
+ void setSizeHintChanged();
+
+private:
+ int mIndex = -1;
+
+ // We store these in hashes because QQuickTableModel needs string-based lookup in certain situations.
+ QHash<QString, QJSValue> mGetters;
+ QHash<QString, QJSValue> mSetters;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlTableModelColumn)
+
+#endif // QQMLTABLEMODELCOLUMN_P_H
diff --git a/src/qmlmodels/qquickpackage.cpp b/src/qmlmodels/qquickpackage.cpp
new file mode 100644
index 0000000000..567381e5ab
--- /dev/null
+++ b/src/qmlmodels/qquickpackage.cpp
@@ -0,0 +1,201 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#include "qquickpackage_p.h"
+
+#include <private/qobject_p.h>
+#include <private/qqmlguard_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype Package
+ \instantiates QQuickPackage
+ \inqmlmodule QtQml.Models
+ \ingroup qtquick-models
+ \brief Specifies a collection of named items.
+
+ The Package type is used in conjunction with
+ DelegateModel to enable delegates with a shared context
+ to be provided to multiple views.
+
+ Any item within a Package may be assigned a name via the
+ \l{Package::name}{Package.name} attached property.
+
+ The example below creates a Package containing two named items;
+ \e list and \e grid. The third item in the package (the \l Rectangle) is parented to whichever
+ delegate it should appear in. This allows an item to move
+ between views.
+
+ \snippet package/Delegate.qml 0
+
+ These named items are used as the delegates by the two views who
+ reference the special \l{DelegateModel::parts} property to select
+ a model which provides the chosen delegate.
+
+ \snippet package/view.qml 0
+
+ \note Package is part of QtQml.Models since version 2.14 and part of QtQuick since version 2.0.
+ Importing Package via QtQuick is deprecated since Qt 5.14.
+
+ \sa {Qt Quick Examples - Views}, {Qt Quick Demo - Photo Viewer}, {Qt QML}
+*/
+
+/*!
+ \qmlattachedproperty string QtQuick::Package::name
+ This attached property holds the name of an item within a Package.
+*/
+
+
+class QQuickPackagePrivate : public QObjectPrivate
+{
+public:
+ QQuickPackagePrivate() {}
+
+ struct DataGuard : public QQmlGuard<QObject>
+ {
+ DataGuard(QObject *obj, QList<DataGuard> *l) : list(l) { (QQmlGuard<QObject>&)*this = obj; }
+ QList<DataGuard> *list;
+ void objectDestroyed(QObject *) override {
+ // we assume priv will always be destroyed after objectDestroyed calls
+ list->removeOne(*this);
+ }
+ };
+
+ QList<DataGuard> dataList;
+ static void data_append(QQmlListProperty<QObject> *prop, QObject *o) {
+ QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data);
+ list->append(DataGuard(o, list));
+ }
+ static void data_clear(QQmlListProperty<QObject> *prop) {
+ QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data);
+ list->clear();
+ }
+ static QObject *data_at(QQmlListProperty<QObject> *prop, int index) {
+ QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data);
+ return list->at(index);
+ }
+ static int data_count(QQmlListProperty<QObject> *prop) {
+ QList<DataGuard> *list = static_cast<QList<DataGuard> *>(prop->data);
+ return list->count();
+ }
+};
+
+QHash<QObject *, QQuickPackageAttached *> QQuickPackageAttached::attached;
+
+QQuickPackageAttached::QQuickPackageAttached(QObject *parent)
+: QObject(parent)
+{
+ attached.insert(parent, this);
+}
+
+QQuickPackageAttached::~QQuickPackageAttached()
+{
+ attached.remove(parent());
+}
+
+QString QQuickPackageAttached::name() const
+{
+ return _name;
+}
+
+void QQuickPackageAttached::setName(const QString &n)
+{
+ _name = n;
+}
+
+QQuickPackage::QQuickPackage(QObject *parent)
+ : QObject(*(new QQuickPackagePrivate), parent)
+{
+}
+
+QQuickPackage::~QQuickPackage()
+{
+}
+
+QQmlListProperty<QObject> QQuickPackage::data()
+{
+ Q_D(QQuickPackage);
+ return QQmlListProperty<QObject>(this, &d->dataList, QQuickPackagePrivate::data_append,
+ QQuickPackagePrivate::data_count,
+ QQuickPackagePrivate::data_at,
+ QQuickPackagePrivate::data_clear);
+}
+
+bool QQuickPackage::hasPart(const QString &name)
+{
+ Q_D(QQuickPackage);
+ for (int ii = 0; ii < d->dataList.count(); ++ii) {
+ QObject *obj = d->dataList.at(ii);
+ QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj);
+ if (a && a->name() == name)
+ return true;
+ }
+ return false;
+}
+
+QObject *QQuickPackage::part(const QString &name)
+{
+ Q_D(QQuickPackage);
+ if (name.isEmpty() && !d->dataList.isEmpty())
+ return d->dataList.at(0);
+
+ for (int ii = 0; ii < d->dataList.count(); ++ii) {
+ QObject *obj = d->dataList.at(ii);
+ QQuickPackageAttached *a = QQuickPackageAttached::attached.value(obj);
+ if (a && a->name() == name)
+ return obj;
+ }
+
+ if (name == QLatin1String("default") && !d->dataList.isEmpty())
+ return d->dataList.at(0);
+
+ return nullptr;
+}
+
+QQuickPackageAttached *QQuickPackage::qmlAttachedProperties(QObject *o)
+{
+ return new QQuickPackageAttached(o);
+}
+
+
+
+QT_END_NAMESPACE
+
+#include "moc_qquickpackage_p.cpp"
diff --git a/src/qmlmodels/qquickpackage_p.h b/src/qmlmodels/qquickpackage_p.h
new file mode 100644
index 0000000000..97f7818fee
--- /dev/null
+++ b/src/qmlmodels/qquickpackage_p.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QQUICKPACKAGE_H
+#define QQUICKPACKAGE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qqml.h>
+#include <QtQmlModels/private/qtqmlmodelsglobal_p.h>
+
+QT_REQUIRE_CONFIG(qml_delegate_model);
+
+QT_BEGIN_NAMESPACE
+
+class QQuickPackagePrivate;
+class QQuickPackageAttached;
+class Q_AUTOTEST_EXPORT QQuickPackage : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QQuickPackage)
+
+ Q_CLASSINFO("DefaultProperty", "data")
+ Q_PROPERTY(QQmlListProperty<QObject> data READ data)
+
+public:
+ QQuickPackage(QObject *parent=nullptr);
+ virtual ~QQuickPackage();
+
+ QQmlListProperty<QObject> data();
+
+ QObject *part(const QString & = QString());
+ bool hasPart(const QString &);
+
+ static QQuickPackageAttached *qmlAttachedProperties(QObject *);
+};
+
+class QQuickPackageAttached : public QObject
+{
+Q_OBJECT
+Q_PROPERTY(QString name READ name WRITE setName)
+public:
+ QQuickPackageAttached(QObject *parent);
+ virtual ~QQuickPackageAttached();
+
+ QString name() const;
+ void setName(const QString &n);
+
+ static QHash<QObject *, QQuickPackageAttached *> attached;
+private:
+ QString _name;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickPackage)
+QML_DECLARE_TYPEINFO(QQuickPackage, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // QQUICKPACKAGE_H
diff --git a/src/qmlmodels/qtqmlmodelsglobal.h b/src/qmlmodels/qtqmlmodelsglobal.h
new file mode 100644
index 0000000000..6e6cf299b2
--- /dev/null
+++ b/src/qmlmodels/qtqmlmodelsglobal.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QTQMLMODELSGLOBAL_H
+#define QTQMLMODELSGLOBAL_H
+
+#include <QtQml/qtqmlglobal.h>
+#include <QtQmlModels/qtqmlmodels-config.h>
+
+QT_BEGIN_NAMESPACE
+
+#if !defined(QT_STATIC)
+# if defined(QT_BUILD_QMLMODELS_LIB)
+# define Q_QMLMODELS_EXPORT Q_DECL_EXPORT
+# else
+# define Q_QMLMODELS_EXPORT Q_DECL_IMPORT
+# endif
+#else
+# define Q_QMLMODELS_EXPORT
+#endif
+
+QT_END_NAMESPACE
+#endif // QTQMLMODELSGLOBAL_H
diff --git a/src/qmlmodels/qtqmlmodelsglobal_p.h b/src/qmlmodels/qtqmlmodelsglobal_p.h
new file mode 100644
index 0000000000..145112c9c1
--- /dev/null
+++ b/src/qmlmodels/qtqmlmodelsglobal_p.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml 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$
+**
+****************************************************************************/
+
+#ifndef QTQMLMODELSGLOBAL_P_H
+#define QTQMLMODELSGLOBAL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQml/private/qtqmlglobal_p.h>
+#include <QtQmlModels/qtqmlmodelsglobal.h>
+#include <QtQmlModels/private/qtqmlmodels-config_p.h>
+
+#define Q_QMLMODELS_PRIVATE_EXPORT Q_QMLMODELS_EXPORT
+#define Q_QMLMODELS_AUTOTEST_EXPORT Q_AUTOTEST_EXPORT
+
+#endif // QTQMLMODELSGLOBAL_P_H