Built-in transition support for ListView & GridView
authorBea Lam <bea.lam@nokia.com>
Thu, 9 Feb 2012 07:59:44 +0000 (17:59 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 16 Feb 2012 07:30:27 +0000 (08:30 +0100)
ListView and GridView can now be assigned transitions to be run when:
    - Populating the view (when initially setting the model / resetting)
    - Adding items
    - Removing items
    - Moving items

The ViewTransition attached object can be used from within a transition
declaration to access various information about the items that are
being transitioned.

Task-number: QTBUG-21504

Change-Id: Ie5c75ea511c8b15acc3f06fccf19abe34d3677f9
Reviewed-by: Martin Jones <martin.jones@nokia.com>
36 files changed:
doc/src/images/viewtransitions-basic.gif [new file with mode: 0644]
doc/src/images/viewtransitions-delayedbyindex.gif [new file with mode: 0644]
doc/src/images/viewtransitions-intermediatemove.gif [new file with mode: 0644]
doc/src/images/viewtransitions-interruptedbad.gif [new file with mode: 0644]
doc/src/images/viewtransitions-interruptedgood.gif [new file with mode: 0644]
doc/src/images/viewtransitions-pathanim.gif [new file with mode: 0644]
doc/src/images/viewtransitions-scriptactionbad.gif [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-delayedbyindex.qml [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-intermediatemove.qml [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-interruptedgood.qml [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-pathanim.qml [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactionbad.qml [new file with mode: 0644]
doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactiongood.qml [new file with mode: 0644]
doc/src/whatsnew.qdoc
src/quick/items/qquickgridview.cpp
src/quick/items/qquickitemsmodule.cpp
src/quick/items/qquickitemview.cpp
src/quick/items/qquickitemview_p.h
src/quick/items/qquickitemview_p_p.h
src/quick/items/qquicklistview.cpp
tests/auto/qtquick2/qquickgridview/data/addTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquickgridview/data/moveTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquickgridview/data/multipleTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquickgridview/data/populateTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquickgridview/data/removeTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquickgridview/tst_qquickgridview.cpp
tests/auto/qtquick2/qquicklistview/data/addTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml [new file with mode: 0644]
tests/auto/qtquick2/qquicklistview/tst_qquicklistview.cpp
tests/auto/qtquick2/shared/viewtestutil.cpp
tests/auto/qtquick2/shared/viewtestutil.h
tests/auto/qtquick2/shared/visualtestutil.h

diff --git a/doc/src/images/viewtransitions-basic.gif b/doc/src/images/viewtransitions-basic.gif
new file mode 100644 (file)
index 0000000..b2a6a61
Binary files /dev/null and b/doc/src/images/viewtransitions-basic.gif differ
diff --git a/doc/src/images/viewtransitions-delayedbyindex.gif b/doc/src/images/viewtransitions-delayedbyindex.gif
new file mode 100644 (file)
index 0000000..4ece2a2
Binary files /dev/null and b/doc/src/images/viewtransitions-delayedbyindex.gif differ
diff --git a/doc/src/images/viewtransitions-intermediatemove.gif b/doc/src/images/viewtransitions-intermediatemove.gif
new file mode 100644 (file)
index 0000000..e826183
Binary files /dev/null and b/doc/src/images/viewtransitions-intermediatemove.gif differ
diff --git a/doc/src/images/viewtransitions-interruptedbad.gif b/doc/src/images/viewtransitions-interruptedbad.gif
new file mode 100644 (file)
index 0000000..d1f88f9
Binary files /dev/null and b/doc/src/images/viewtransitions-interruptedbad.gif differ
diff --git a/doc/src/images/viewtransitions-interruptedgood.gif b/doc/src/images/viewtransitions-interruptedgood.gif
new file mode 100644 (file)
index 0000000..1d59db3
Binary files /dev/null and b/doc/src/images/viewtransitions-interruptedgood.gif differ
diff --git a/doc/src/images/viewtransitions-pathanim.gif b/doc/src/images/viewtransitions-pathanim.gif
new file mode 100644 (file)
index 0000000..e6bc737
Binary files /dev/null and b/doc/src/images/viewtransitions-pathanim.gif differ
diff --git a/doc/src/images/viewtransitions-scriptactionbad.gif b/doc/src/images/viewtransitions-scriptactionbad.gif
new file mode 100644 (file)
index 0000000..9e618d9
Binary files /dev/null and b/doc/src/images/viewtransitions-scriptactionbad.gif differ
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml
new file mode 100644 (file)
index 0000000..8a05491
--- /dev/null
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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]
+ListView {
+    width: 240; height: 320
+    model: ListModel {}
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+    }
+
+    add: Transition {
+        NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 }
+        NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 }
+    }
+
+    addDisplaced: Transition {
+        NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce }
+    }
+
+    focus: true
+    Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count })
+}
+//! [0]
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-delayedbyindex.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-delayedbyindex.qml
new file mode 100644 (file)
index 0000000..79d00d2
--- /dev/null
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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
+
+ListView {
+    width: 240; height: 320
+    model: ListModel {}
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+    }
+
+    add: Transition {
+        NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 }
+        NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 }
+    }
+
+//! [0]
+    addDisplaced: Transition {
+        id: addDispTrans
+        SequentialAnimation {
+            PauseAnimation {
+                duration: (addDispTrans.ViewTransition.index -
+                        addDispTrans.ViewTransition.targetIndexes[0]) * 100
+            }
+            NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce }
+        }
+    }
+//! [0]
+
+    focus: true
+    Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count })
+}
+
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-intermediatemove.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-intermediatemove.qml
new file mode 100644 (file)
index 0000000..a43d3a8
--- /dev/null
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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
+
+ListView {
+    width: 240; height: 320
+    model: ListModel {}
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+    }
+
+    add: Transition {
+        NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 }
+        NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 }
+    }
+
+//! [0]
+    addDisplaced: Transition {
+        id: addDispTrans
+        SequentialAnimation {
+            PauseAnimation {
+                duration: (addDispTrans.ViewTransition.index -
+                        addDispTrans.ViewTransition.targetIndexes[0]) * 100
+            }
+            ParallelAnimation {
+                NumberAnimation {
+                    property: "x"; to: addDispTrans.ViewTransition.item.x + 20
+                    easing.type: Easing.OutQuad
+                }
+                NumberAnimation {
+                    property: "y"; to: addDispTrans.ViewTransition.item.y + 50
+                    easing.type: Easing.OutQuad
+                }
+            }
+            NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.OutBounce }
+        }
+    }
+
+//! [0]
+
+    focus: true
+    Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count })
+}
+
+
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-interruptedgood.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-interruptedgood.qml
new file mode 100644 (file)
index 0000000..1355268
--- /dev/null
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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
+
+ListView {
+    width: 240; height: 320
+    model: ListModel {}
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+    }
+
+    add: Transition {
+        NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 }
+        NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 }
+    }
+
+//! [0]
+    addDisplaced: Transition {
+        NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce }
+
+        // ensure opacity and scale values return to 1.0
+        NumberAnimation { property: "opacity"; to: 1.0 }
+        NumberAnimation { property: "scale"; to: 1.0 }
+    }
+//! [0]
+
+    focus: true
+    Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count })
+}
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-pathanim.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-pathanim.qml
new file mode 100644 (file)
index 0000000..3d075c4
--- /dev/null
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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
+
+ListView {
+    width: 240; height: 320
+    model: ListModel {}
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+    }
+
+//! [0]
+    add: Transition {
+        id: addTrans
+        NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 }
+        NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 }
+
+        PathAnimation {
+            duration: 1000
+            path: Path {
+                startX: addTrans.ViewTransition.destination.x + 200
+                startY: addTrans.ViewTransition.destination.y + 200
+                PathCurve { relativeX: -100; relativeY: -50 }
+                PathCurve { relativeX: 50; relativeY: -150 }
+                PathCurve {
+                    x: addTrans.ViewTransition.destination.x
+                    y: addTrans.ViewTransition.destination.y
+                }
+            }
+        }
+    }
+//! [0]
+
+    addDisplaced: Transition {
+        id: addDispTrans
+        SequentialAnimation {
+            PauseAnimation {
+                duration: (addDispTrans.ViewTransition.index -
+                        addDispTrans.ViewTransition.targetIndexes[0]) * 100
+            }
+            ParallelAnimation {
+                NumberAnimation {
+                    property: "x"; to: addDispTrans.ViewTransition.item.x + 20
+                    easing.type: Easing.OutQuad
+                }
+                NumberAnimation {
+                    property: "y"; to: addDispTrans.ViewTransition.item.y + 50
+                    easing.type: Easing.OutQuad
+                }
+            }
+            NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.OutBounce }
+        }
+    }
+
+    focus: true
+    Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count })
+}
+
+
+
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactionbad.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactionbad.qml
new file mode 100644 (file)
index 0000000..0334835
--- /dev/null
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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]
+ListView {
+    width: 240; height: 320
+    model: ListModel {
+        Component.onCompleted: {
+            for (var i=0; i<8; i++)
+                append({"name": i})
+        }
+    }
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+        objectName: name
+    }
+
+    move: Transition {
+        id: moveTrans
+        SequentialAnimation {
+            ColorAnimation { property: "color"; to: "yellow"; duration: 400 }
+            NumberAnimation { properties: "x,y"; duration: 800; easing.type: Easing.OutBack }
+            ScriptAction { script: moveTrans.ViewTransition.item.color = "lightsteelblue" }
+        }
+    }
+
+    moveDisplaced: Transition {
+        NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce }
+    }
+
+    focus: true
+    Keys.onSpacePressed: model.move(5, 1, 3)
+}
+//! [0]
+
diff --git a/doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactiongood.qml b/doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactiongood.qml
new file mode 100644 (file)
index 0000000..eda5c35
--- /dev/null
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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 Nokia Corporation and its Subsidiary(-ies) 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
+
+ListView {
+    width: 240; height: 320
+    model: ListModel {
+        Component.onCompleted: {
+            for (var i=0; i<8; i++)
+                append({"name": i})
+        }
+    }
+
+    delegate: Rectangle {
+        width: 100; height: 30
+        border.width: 1
+        color: "lightsteelblue"
+        Text {
+            anchors.centerIn: parent
+            text: name
+        }
+        objectName: name
+    }
+
+//! [0]
+    move: Transition {
+        id: moveTrans
+        SequentialAnimation {
+            ColorAnimation { property: "color"; to: "yellow"; duration: 400 }
+            NumberAnimation { properties: "x,y"; duration: 800; easing.type: Easing.OutBack }
+            //ScriptAction { script: moveTrans.ViewTransition.item.color = "lightsteelblue" } BAD!
+
+            PropertyAction { property: "color"; value: "lightsteelblue" }
+        }
+    }
+//! [0]
+
+    moveDisplaced: Transition {
+        NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce }
+    }
+
+    focus: true
+    Keys.onSpacePressed: model.move(5, 1, 3)
+}
+
+
index c7baa26..34dfe21 100644 (file)
@@ -134,7 +134,10 @@ a y parameter.
 PathView now has a \c currentItem property
 
 ListView and GridView:
- - now have headerItem and footerItem properties (the instantiated header and footer items).
+ - Can now apply specified transitions whenever items are added, removed or moved in a view.
+   See the documentation for ViewTransition and ListView.add, ListView.addDisplaced,
+   GridView.add, GridView.addDisplaced etc. for details.
+ - These now have headerItem and footerItem properties (the instantiated header and footer items).
  - In RightToLeft layout the preferredHighlightBegin/End are now also reversed.
 
 ListView section.labelPositioning property added to allow keeping the current section label
index 522c09a..39c7eab 100644 (file)
@@ -92,9 +92,9 @@ public:
 
     qreal rowPos() const {
         if (view->flow() == QQuickGridView::LeftToRight)
-            return item->y();
+            return itemY();
         else
-            return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-item->x() : item->x());
+            return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-itemX() : itemX());
     }
 
     qreal colPos() const {
@@ -102,45 +102,53 @@ public:
             if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
                 qreal colSize = view->cellWidth();
                 int columns = view->width()/colSize;
-                return colSize * (columns-1) - item->x();
+                return colSize * (columns-1) - itemX();
             } else {
-                return item->x();
+                return itemX();
             }
         } else {
-            return item->y();
+            return itemY();
         }
     }
     qreal endRowPos() const {
         if (view->flow() == QQuickGridView::LeftToRight) {
-            return item->y() + view->cellHeight();
+            return itemY() + view->cellHeight();
         } else {
             if (view->effectiveLayoutDirection() == Qt::RightToLeft)
-                return -item->x();
+                return -itemX();
             else
-                return item->x() + view->cellWidth();
+                return itemX() + view->cellWidth();
         }
     }
     void setPosition(qreal col, qreal row) {
+        moveTo(pointForPosition(col, row));
+    }
+    bool contains(qreal x, qreal y) const {
+        return (x >= itemX() && x < itemX() + view->cellWidth() &&
+                y >= itemY() && y < itemY() + view->cellHeight());
+    }
+    QQuickItemView *itemView() const {
+        return view;
+    }
+
+    QQuickGridView *view;
+
+private:
+    QPointF pointForPosition(qreal col, qreal row) const {
         if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
             if (view->flow() == QQuickGridView::LeftToRight) {
                 int columns = view->width()/view->cellWidth();
-                item->setPos(QPointF((view->cellWidth() * (columns-1) - col), row));
+                return QPointF(view->cellWidth() * (columns-1) - col, row);
             } else {
-                item->setPos(QPointF(-view->cellWidth()-row, col));
+                return QPointF(-view->cellWidth() - row, col);
             }
         } else {
             if (view->flow() == QQuickGridView::LeftToRight)
-                item->setPos(QPointF(col, row));
+                return QPointF(col, row);
             else
-                item->setPos(QPointF(row, col));
+                return QPointF(row, col);
         }
     }
-    bool contains(qreal x, qreal y) const {
-        return (x >= item->x() && x < item->x() + view->cellWidth() &&
-                y >= item->y() && y < item->y() + view->cellHeight());
-    }
-
-    QQuickGridView *view;
 };
 
 //----------------------------------------------------------------------------
@@ -173,6 +181,8 @@ public:
     virtual bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
 
     virtual FxViewItem *newViewItem(int index, QQuickItem *item);
+    virtual void initializeViewItem(FxViewItem *item);
+    virtual void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer);
     virtual void repositionPackageItemAt(QQuickItem *item, int index);
     virtual void resetFirstItemPosition(qreal pos = 0.0);
     virtual void adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible);
@@ -183,7 +193,8 @@ public:
 
     virtual void setPosition(qreal pos);
     virtual void layoutVisibleItems(int fromModelIndex = 0);
-    virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems);
+    virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView);
+    virtual void translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult);
     virtual bool needsRefillForAddedOrRemovedIndex(int index) const;
 
     virtual qreal headerSize() const;
@@ -309,8 +320,11 @@ qreal QQuickGridViewPrivate::colPosAt(int modelIndex) const
             col = (columns - count + col) % columns;
             return col * colSize();
         } else {
-            int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns;
-            return static_cast<FxGridItemSG*>(visibleItems.last())->colPos() - count * colSize();
+            FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last());
+            int count = modelIndex - lastItem->index;
+            int col = lastItem->colPos() / colSize();
+            col = (col + count) % columns;
+            return col * colSize();
         }
     }
     return (modelIndex % columns) * colSize();
@@ -415,6 +429,15 @@ FxViewItem *QQuickGridViewPrivate::newViewItem(int modelIndex, QQuickItem *item)
     return new FxGridItemSG(item, q, false);
 }
 
+void QQuickGridViewPrivate::initializeViewItem(FxViewItem *item)
+{
+    QQuickItemViewPrivate::initializeViewItem(item);
+
+    // need to track current items that are animating
+    QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item->item);
+    itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
+}
+
 bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool doBuffer)
 {
     qreal colPos = colPosAt(visibleIndex);
@@ -462,7 +485,8 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
 #endif
         if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex, doBuffer))))
             break;
-        item->setPosition(colPos, rowPos);
+        if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+            item->setPosition(colPos, rowPos);
         item->item->setVisible(!doBuffer);
         visibleItems.append(item);
         if (++colNum >= columns) {
@@ -499,7 +523,8 @@ bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
         if (!(item = static_cast<FxGridItemSG*>(createItem(visibleIndex-1, doBuffer))))
             break;
         --visibleIndex;
-        item->setPosition(colPos, rowPos);
+        if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+            item->setPosition(colPos, rowPos);
         item->item->setVisible(!doBuffer);
         visibleItems.prepend(item);
         if (--colNum < 0) {
@@ -529,7 +554,15 @@ bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
         if (item->index != -1)
             visibleIndex++;
         visibleItems.removeFirst();
-        releaseItem(item);
+        if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+            qDebug() << "\tnot releasing animating item:" << item->index << item->item->objectName();
+#endif
+            item->releaseAfterTransition = true;
+            releasePendingTransition.append(item);
+        } else {
+            releaseItem(item);
+        }
         changed = true;
     }
     while (visibleItems.count() > 1
@@ -541,7 +574,15 @@ bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
         qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1;
 #endif
         visibleItems.removeLast();
-        releaseItem(item);
+        if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+            qDebug() << "\tnot releasing animating item:" << item->index << item->item->objectName();
+#endif
+            item->releaseAfterTransition = true;
+            releasePendingTransition.append(item);
+        } else {
+            releaseItem(item);
+        }
         changed = true;
     }
 
@@ -567,7 +608,7 @@ void QQuickGridViewPrivate::layoutVisibleItems(int fromModelIndex)
         if (colPos != col * colSize()) {
             colPos = col * colSize();
             firstItem->setPosition(colPos, rowPos);
-            firstItem->item->setVisible(rowPos + rowSize() >= from && rowPos <= to);
+            firstItem->setVisible(firstItem->rowPos() + rowSize() >= from && firstItem->rowPos() <= to);
         }
         for (int i = 1; i < visibleItems.count(); ++i) {
             FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.at(i));
@@ -578,12 +619,18 @@ void QQuickGridViewPrivate::layoutVisibleItems(int fromModelIndex)
             colPos = col * colSize();
             if (item->index >= fromModelIndex) {
                 item->setPosition(colPos, rowPos);
-                item->item->setVisible(rowPos + rowSize() >= from && rowPos <= to);
+                item->setVisible(item->rowPos() + rowSize() >= from && item->rowPos() <= to);
             }
         }
     }
 }
 
+void QQuickGridViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer)
+{
+    int count = sizeBuffer / rowSize();
+    static_cast<FxGridItemSG *>(item)->setPosition(colPosAt(index + count), rowPosAt(index + count));
+}
+
 void QQuickGridViewPrivate::repositionPackageItemAt(QQuickItem *item, int index)
 {
     Q_Q(QQuickGridView);
@@ -668,8 +715,8 @@ void QQuickGridViewPrivate::updateHighlight()
     bool strictHighlight = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange;
     if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) {
         // auto-update highlight
-        highlightXAnimator->to = currentItem->item->x();
-        highlightYAnimator->to = currentItem->item->y();
+        highlightXAnimator->to = currentItem->itemX();
+        highlightYAnimator->to = currentItem->itemY();
         highlight->item->setWidth(currentItem->item->width());
         highlight->item->setHeight(currentItem->item->height());
 
@@ -797,7 +844,11 @@ void QQuickGridViewPrivate::initializeCurrentItem()
 {
     if (currentItem && currentIndex >= 0) {
         FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(currentItem);
-        gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex));
+        FxViewItem *actualItem = visibleItem(currentIndex);
+
+        // don't reposition the item if it's about to be transitioned to another position
+        if ((!actualItem || !actualItem->transitionScheduledOrRunning()))
+            gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex));
     }
 }
 
@@ -1144,17 +1195,17 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
 
 /*!
     \qmlattachedproperty bool QtQuick2::GridView::delayRemove
-    This attached property holds whether the delegate may be destroyed.
-
-    It is attached to each instance of the delegate.
+    This attached property holds whether the delegate may be destroyed. It
+    is attached to each instance of the delegate. The default value is false.
 
     It is sometimes necessary to delay the destruction of an item
-    until an animation completes.
-
-    The example below ensures that the animation completes before
-    the item is removed from the grid.
+    until an animation completes. The example delegate below ensures that the
+    animation completes before the item is removed from the list.
 
     \snippet doc/src/snippets/declarative/gridview/gridview.qml delayRemove
+
+    If a \l remove transition has been specified, it will not be applied until
+    delayRemove is returned to \c false.
 */
 
 /*!
@@ -1165,6 +1216,9 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight)
 /*!
     \qmlattachedsignal QtQuick2::GridView::onRemove()
     This attached handler is called immediately before an item is removed from the view.
+
+    If a \l remove transition has been specified, it is applied after
+    this signal handler is called, providing that delayRemove is false.
 */
 
 
@@ -1530,6 +1584,228 @@ void QQuickGridView::setSnapMode(SnapMode mode)
     \sa footer, headerItem
 */
 
+/*!
+    \qmlproperty Transition QtQuick2::GridView::populate
+    This property holds the transition to apply to items that are initially created for a
+    view.
+
+    This transition is applied to all the items that are created when:
+
+    \list
+    \o The view is first created
+    \o The view's \l model changes
+    \o The view's \l model is \l {QAbstractItemModel::reset}{reset}, if the model is a QAbstractItemModel subclass
+    \endlist
+
+    For example, here is a view that specifies such a transition:
+
+    \code
+    GridView {
+        ...
+        populate: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    When the view is initialized, the view will create all the necessary items for the view,
+    then animate them to their correct positions within the view over one second.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa add, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::GridView::add
+    This property holds the transition to apply to items that are added within the view.
+
+    The transition is applied to items that have been added to the visible area of the view. For
+    example, here is a view that specifies such a transition:
+
+    \code
+    GridView {
+        ...
+        add: Transition {
+            NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is added to the above view, the item will be animated from the position (100,100)
+    to its final x,y position within the view, over one second. The transition only applies to
+    the new items that are added to the view; it does not apply to the items below that are
+    displaced by the addition of the new items. To animate the displaced items, set the \l
+    addDisplaced property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \note This transition is not applied to the items that are created when the view is initially
+    populated, or when the view's \l model changes. In those cases, the \l populate transition is
+    applied instead.
+
+    \sa addDisplaced, populate, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::GridView::addDisplaced
+    This property holds the transition to apply to items in the view that are displaced by other
+    items that have been added to the view.
+
+    The transition is applied to items that are currently visible and have been displaced by newly
+    added items. For example, here is a view that specifies such a transition:
+
+    \code
+    GridView {
+        ...
+        addDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is added to the above view, all items beneath the new item are displaced, causing
+    them to move down (or sideways, if horizontally orientated) within the view. As this
+    displacement occurs, the items' movement to their new x,y positions within the view will be
+    animated by a NumberAnimation over one second, as specified. This transition is not applied to
+    the new item that has been added to the view; to animate the added items, set the \l add
+    property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \note This transition is not applied to the items that are created when the view is initially
+    populated, or when the view's \l model changes. In those cases, the \l populate transition is
+    applied instead.
+
+    \sa add, populate, ViewTransition
+*/
+/*!
+    \qmlproperty Transition QtQuick2::GridView::move
+    This property holds the transition to apply to items in the view that are moved by a move
+    operation.
+
+    The transition is applied to items that are moving within the view or are moving
+    into the view as a result of a move operation in the view's model. For example:
+
+    \code
+    GridView {
+        ...
+        move: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is moved within the above view, the item will be animated to its new position in
+    the view over one second. The transition only applies to the items that are the subject of the
+    move operation in the model; it does not apply to the items below them that are displaced by
+    the move operation. To animate the displaced items, set the \l moveDisplaced property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa moveDisplaced, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::GridView::moveDisplaced
+    This property holds the transition to apply to items in the view that are displaced by a
+    move operation in the view.
+
+    The transition is applied to items that are currently visible and have been displaced following
+    a move operation in the view's model. For example, here is a view that specifies such a transition:
+
+    \code
+    GridView {
+        ...
+        moveDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item moves within (or moves into) the above view, all items beneath it are
+    displaced, causing them to move upwards (or sideways, if horizontally orientated) within the
+    view. As this displacement occurs, the items' movement to their new x,y positions within the
+    view will be animated by a NumberAnimation over one second, as specified. This transition is
+    not applied to the item that are actually the subject of the move operation; to animate the
+    moved items, set the \l move property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa move, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::GridView::remove
+    This property holds the transition to apply to items that are removed from the view.
+
+    The transition is applied to items that have been removed from the visible area of the view. For
+    example:
+
+    \code
+    GridView {
+        ...
+        remove: Transition {
+            ParallelAnimation {
+                NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
+                NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
+            }
+        }
+    }
+    \endcode
+
+    Whenever an item is removed from the above view, the item will be animated to the position (100,100)
+    over one second, and in parallel will also change its opacity to 0. The transition
+    only applies to the items that are removed from the view; it does not apply to the items below
+    them that are displaced by the removal of the  items. To animate the displaced items, set the \l
+    removeDisplaced property.
+
+    Note that by the time the transition is applied, the item has already been removed from the
+    model; any references to the model data for the removed index will not be valid.
+
+    Additionally, if the \l delayRemove attached property has been set for a delegate item, the
+    remove transition will not be applied until \l delayRemove becomes false again.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa removeDisplaced, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::GridView::removeDisplaced
+    This property holds the transition to apply to items in the view that are displaced by the
+    removal of other items in the view.
+
+    The transition is applied to items that are currently visible and have been displaced by
+    the removal of items. For example, here is a view that specifies such a transition:
+
+    \code
+    GridView {
+        ...
+        removeDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is removed from the above view, all items beneath it are displaced, causing
+    them to move upwards (or sideways, if horizontally orientated) within the view. As this
+    displacement occurs, the items' movement to their new x,y positions within the view will be
+    animated by a NumberAnimation over one second, as specified. This transition is not applied to
+    the item that has actually been removed from the view; to animate the removed items, set the
+    \l remove property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa remove, ViewTransition
+*/
 void QQuickGridView::viewportMoved()
 {
     Q_D(QQuickGridView);
@@ -1583,9 +1859,9 @@ void QQuickGridView::viewportMoved()
                 d->updateCurrent(idx);
                 if (d->currentItem && static_cast<FxGridItemSG*>(d->currentItem)->colPos() != static_cast<FxGridItemSG*>(d->highlight)->colPos() && d->autoHighlight) {
                     if (d->flow == LeftToRight)
-                        d->highlightXAnimator->to = d->currentItem->item->x();
+                        d->highlightXAnimator->to = d->currentItem->itemX();
                     else
-                        d->highlightYAnimator->to = d->currentItem->item->y();
+                        d->highlightYAnimator->to = d->currentItem->itemY();
                 }
             }
         }
@@ -1775,7 +2051,7 @@ void QQuickGridView::moveCurrentIndexRight()
     }
 }
 
-bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems)
+bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
 {
     Q_Q(QQuickGridView);
 
@@ -1831,8 +2107,13 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
     // Update the indexes of the following visible items.
     for (int i = 0; i < visibleItems.count(); ++i) {
         FxViewItem *item = visibleItems.at(i);
-        if (item->index != -1 && item->index >= modelIndex)
+        if (item->index != -1 && item->index >= modelIndex) {
             item->index += count;
+            if (change.isMove())
+                transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, false);
+            else
+                transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, false);
+        }
     }
 
     int prevVisibleCount = visibleItems.count();
@@ -1845,7 +2126,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
         while (i >= 0) {
             if (rowPos > from && insertionIdx < visibleIndex) {
                 // item won't be visible, just note the size for repositioning
-                insertResult->changeBeforeVisible++;
+                insertResult->countChangeBeforeVisible++;
             } else {
                 // item is before first visible e.g. in cache buffer
                 FxViewItem *item = 0;
@@ -1858,8 +2139,12 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
 
                 item->item->setVisible(true);
                 visibleItems.insert(insertionIdx, item);
-                if (!change.isMove())
+                if (insertionIdx == 0)
+                    insertResult->changedFirstItem = true;
+                if (!change.isMove()) {
                     addedItems->append(item);
+                    transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+                }
                 insertResult->sizeChangesBeforeVisiblePos += rowSize();
             }
 
@@ -1878,6 +2163,7 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
             FxViewItem *item = 0;
             if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i))))
                 item->index = modelIndex + i;
+            bool newItem = !item;
             if (!item)
                 item = createItem(modelIndex + i);
             if (!item)
@@ -1887,8 +2173,15 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
             visibleItems.insert(index, item);
             if (index == 0)
                 insertResult->changedFirstItem = true;
-            if (!change.isMove())
+            if (change.isMove()) {
+                // we know this is a move target, since move displaced items that are
+                // shuffled into view due to a move would be added in refill()
+                if (moveTransition && newItem)
+                    movingIntoView->append(MovedItem(item, change.moveKey(item->index)));
+            } else {
                 addedItems->append(item);
+                transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+            }
             insertResult->sizeChangesAfterVisiblePos += rowSize();
 
             if (++colNum >= columns) {
@@ -1906,6 +2199,41 @@ bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
     return visibleItems.count() > prevVisibleCount;
 }
 
+void QQuickGridViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
+{
+    int markerItemIndex = -1;
+    for (int i=0; i<visibleItems.count(); i++) {
+        if (visibleItems[i]->index == afterModelIndex) {
+            markerItemIndex = i;
+            break;
+        }
+    }
+    if (markerItemIndex < 0)
+        return;
+
+    const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
+    int countItemsRemoved = -(removalResult.sizeChangesAfterVisiblePos / rowSize());
+
+    // account for whether first item has changed if < 1 row was removed before visible
+    int changeBeforeVisible = insertionResult.countChangeBeforeVisible - removalResult.countChangeBeforeVisible;
+    if (changeBeforeVisible != 0)
+        countItemsRemoved += (changeBeforeVisible % columns) - (columns - 1);
+
+    countItemsRemoved -= removalResult.countChangeAfterVisibleItems;
+
+    for (int i=markerItemIndex+1; i<visibleItems.count() && visibleItems.at(i)->position() < viewEndPos; i++) {
+        FxGridItemSG *gridItem = static_cast<FxGridItemSG *>(visibleItems[i]);
+        if (!gridItem->transitionScheduledOrRunning()) {
+            qreal origRowPos = gridItem->colPos();
+            qreal origColPos = gridItem->rowPos();
+            int indexDiff = gridItem->index - countItemsRemoved;
+            gridItem->setPosition((indexDiff % columns) * colSize(), (indexDiff / columns) * rowSize());
+            transitionNextReposition(gridItem, FxViewItemTransitionManager::RemoveTransition, false);
+            gridItem->setPosition(origRowPos, origColPos);
+        }
+    }
+}
+
 bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const
 {
     // If we add or remove items before visible items, a layout may be
index 3d1e96d..a4cfa26 100644 (file)
@@ -184,6 +184,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
     qmlRegisterUncreatableType<QQuickKeyNavigationAttached>(uri,major,minor,"KeyNavigation",QQuickKeyNavigationAttached::tr("KeyNavigation is only available via attached properties"));
     qmlRegisterUncreatableType<QQuickKeysAttached>(uri,major,minor,"Keys",QQuickKeysAttached::tr("Keys is only available via attached properties"));
     qmlRegisterUncreatableType<QQuickLayoutMirroringAttached>(uri,major,minor,"LayoutMirroring", QQuickLayoutMirroringAttached::tr("LayoutMirroring is only available via attached properties"));
+    qmlRegisterUncreatableType<QQuickViewTransitionAttached>(uri,major,minor,"ViewTransition",QQuickViewTransitionAttached::tr("ViewTransition is only available via attached properties"));
 
     qmlRegisterType<QQuickPinchArea>(uri,major,minor,"PinchArea");
     qmlRegisterType<QQuickPinch>(uri,major,minor,"Pinch");
index f09be2b..87a428a 100644 (file)
 ****************************************************************************/
 
 #include "qquickitemview_p_p.h"
+#include <QtQuick/private/qdeclarativetransition_p.h>
 
 QT_BEGIN_NAMESPACE
 
 
 FxViewItem::FxViewItem(QQuickItem *i, bool own)
-    : item(i), ownItem(own), index(-1)
+    : item(i), ownItem(own), index(-1), releaseAfterTransition(false)
+    , transition(0)
+    , nextTransitionType(FxViewItemTransitionManager::NoTransition)
+    , isTransitionTarget(false)
+    , nextTransitionToSet(false)
 {
 }
 
 FxViewItem::~FxViewItem()
 {
+    if (transition)
+        transition->m_item = 0;
+    delete transition;
+
     if (ownItem && item) {
         item->setParentItem(0);
         item->deleteLater();
@@ -58,6 +67,273 @@ FxViewItem::~FxViewItem()
     }
 }
 
+qreal FxViewItem::itemX() const
+{
+    if (nextTransitionType != FxViewItemTransitionManager::NoTransition)
+        return nextTransitionToSet ? nextTransitionTo.x() : item->x();
+    else if (transition && transition->isActive())
+        return transition->m_toPos.x();
+    else
+        return item->x();
+}
+
+qreal FxViewItem::itemY() const
+{
+    // If item is transitioning to some pos, return that dest pos.
+    // If item was redirected to some new pos before the current transition finished,
+    // return that new pos.
+    if (nextTransitionType != FxViewItemTransitionManager::NoTransition)
+        return nextTransitionToSet ? nextTransitionTo.y() : item->y();
+    else if (transition && transition->isActive())
+        return transition->m_toPos.y();
+    else
+        return item->y();
+}
+
+void FxViewItem::setVisible(bool visible)
+{
+    if (!visible && transitionScheduledOrRunning())
+        return;
+    item->setVisible(visible);
+}
+
+void FxViewItem::setNextTransition(FxViewItemTransitionManager::TransitionType type, bool isTargetItem)
+{
+    // Don't reset nextTransitionToSet - once it is set, it cannot be changed
+    // until the animation finishes since the itemX() and itemY() may be used
+    // to calculate positions for transitions for other items in the view.
+    nextTransitionType = type;
+    isTransitionTarget = isTargetItem;
+}
+
+bool FxViewItem::transitionScheduledOrRunning() const
+{
+    return (transition && transition->isActive())
+            || nextTransitionType != FxViewItemTransitionManager::NoTransition;
+}
+
+bool FxViewItem::prepareTransition(const QRectF &viewBounds)
+{
+    bool doTransition = false;
+
+    switch (nextTransitionType) {
+    case FxViewItemTransitionManager::NoTransition:
+    {
+        return false;
+    }
+    case FxViewItemTransitionManager::PopulateTransition:
+    {
+        return true;
+    }
+    case FxViewItemTransitionManager::AddTransition:
+    case FxViewItemTransitionManager::RemoveTransition:
+        // For Add targets, do transition if item is moving into visible area
+        // For Remove targets, do transition if item is currently in visible area
+        if (isTransitionTarget) {
+            doTransition = (nextTransitionType == FxViewItemTransitionManager::AddTransition)
+                    ? viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))
+                    : viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()));
+            if (!doTransition)
+                item->setPos(nextTransitionTo);
+        } else {
+            if (viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()))
+                    || viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()))) {
+                doTransition = (nextTransitionTo != item->pos());
+            } else {
+                item->setPos(nextTransitionTo);
+            }
+        }
+        break;
+    case FxViewItemTransitionManager::MoveTransition:
+        // do transition if moving from or into visible area
+        if (nextTransitionTo != item->pos()) {
+            doTransition = viewBounds.intersects(QRectF(item->x(), item->y(), item->width(), item->height()))
+                    || viewBounds.intersects(QRectF(nextTransitionTo.x(), nextTransitionTo.y(), item->width(), item->height()));
+            if (!doTransition)
+                item->setPos(nextTransitionTo);
+        }
+        break;
+    }
+
+    if (!doTransition)
+        resetTransitionData();
+    return doTransition;
+}
+
+void FxViewItem::startTransition()
+{
+    if (nextTransitionType == FxViewItemTransitionManager::NoTransition)
+        return;
+
+    if (!transition || transition->m_type != nextTransitionType || transition->m_type != isTransitionTarget) {
+        delete transition;
+        transition = new FxViewItemTransitionManager;
+    }
+
+    // if item is not already moving somewhere, set it to not move anywhere
+    // so that removed items do not move to the default (0,0)
+    if (!nextTransitionToSet)
+        moveTo(item->pos());
+
+    transition->startTransition(this, nextTransitionType, nextTransitionTo, isTransitionTarget);
+    nextTransitionType = FxViewItemTransitionManager::NoTransition;
+}
+
+void FxViewItem::stopTransition()
+{
+    if (transition) {
+        transition->cancel();
+        delete transition;
+        transition = 0;
+    }
+    resetTransitionData();
+    finishedTransition();
+}
+
+void FxViewItem::finishedTransition()
+{
+    nextTransitionToSet = false;
+    nextTransitionTo = QPointF();
+
+    if (releaseAfterTransition) {
+        QQuickItemViewPrivate *vp = static_cast<QQuickItemViewPrivate*>(QObjectPrivate::get(itemView()));
+        vp->releasePendingTransition.removeOne(this);
+        vp->releaseItem(this);
+    }
+}
+
+void FxViewItem::resetTransitionData()
+{
+    nextTransitionType = FxViewItemTransitionManager::NoTransition;
+    isTransitionTarget = false;
+    nextTransitionTo = QPointF();
+    nextTransitionToSet = false;
+}
+
+bool FxViewItem::isPendingRemoval() const
+{
+    if (nextTransitionType == FxViewItemTransitionManager::RemoveTransition)
+        return isTransitionTarget;
+    if (transition && transition->isActive() && transition->m_type == FxViewItemTransitionManager::RemoveTransition)
+        return transition->m_isTarget;
+    return false;
+}
+
+void FxViewItem::moveTo(const QPointF &pos)
+{
+    if (transitionScheduledOrRunning()) {
+        nextTransitionTo = pos;
+        nextTransitionToSet = true;
+    } else {
+        item->setPos(pos);
+    }
+}
+
+
+FxViewItemTransitionManager::FxViewItemTransitionManager()
+    : m_active(false), m_item(0), m_type(FxViewItemTransitionManager::NoTransition), m_isTarget(false)
+{
+}
+
+FxViewItemTransitionManager::~FxViewItemTransitionManager()
+{
+}
+
+bool FxViewItemTransitionManager::isActive() const
+{
+    return m_active;
+}
+
+void FxViewItemTransitionManager::startTransition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, const QPointF &to, bool isTargetItem)
+{
+    if (!item) {
+        qWarning("startTransition(): invalid item");
+        return;
+    }
+
+    QQuickItemViewPrivate *vp = static_cast<QQuickItemViewPrivate*>(QObjectPrivate::get(item->itemView()));
+
+    QDeclarativeTransition *trans = 0;
+    switch (type) {
+    case NoTransition:
+        break;
+    case PopulateTransition:
+        trans = vp->populateTransition;
+        break;
+    case AddTransition:
+        trans = isTargetItem ? vp->addTransition : vp->addDisplacedTransition;
+        break;
+    case MoveTransition:
+        trans = isTargetItem ? vp->moveTransition : vp->moveDisplacedTransition;
+        break;
+    case RemoveTransition:
+        trans = isTargetItem ? vp->removeTransition : vp->removeDisplacedTransition;
+        break;
+    }
+
+    if (!trans) {
+        qWarning("QQuickItemView: invalid view transition!");
+        return;
+    }
+
+    m_active = true;
+    m_item = item;
+    m_toPos = to;
+    m_type = type;
+    m_isTarget = isTargetItem;
+
+    QQuickViewTransitionAttached *attached =
+            static_cast<QQuickViewTransitionAttached*>(qmlAttachedPropertiesObject<QQuickViewTransitionAttached>(trans));
+    if (attached) {
+        attached->m_index = item->index;
+        attached->m_item = item->item;
+        attached->m_destination = to;
+        switch (type) {
+        case NoTransition:
+            break;
+        case PopulateTransition:
+        case AddTransition:
+            attached->m_targetIndexes = vp->addTransitionIndexes;
+            attached->m_targetItems = vp->addTransitionTargets;
+            break;
+        case MoveTransition:
+            attached->m_targetIndexes = vp->moveTransitionIndexes;
+            attached->m_targetItems = vp->moveTransitionTargets;
+            break;
+        case RemoveTransition:
+            attached->m_targetIndexes = vp->removeTransitionIndexes;
+            attached->m_targetItems = vp->removeTransitionTargets;
+            break;
+        }
+        emit attached->indexChanged();
+        emit attached->itemChanged();
+        emit attached->destinationChanged();
+        emit attached->targetIndexesChanged();
+        emit attached->targetItemsChanged();
+    }
+
+    QDeclarativeStateOperation::ActionList actions;
+    actions << QDeclarativeAction(item->item, QLatin1String("x"), QVariant(to.x()));
+    actions << QDeclarativeAction(item->item, QLatin1String("y"), QVariant(to.y()));
+
+    QDeclarativeTransitionManager::transition(actions, trans, item->item);
+}
+
+void FxViewItemTransitionManager::finished()
+{
+    QDeclarativeTransitionManager::finished();
+
+    m_active = false;
+
+    if (m_item)
+        m_item->finishedTransition();
+    m_item = 0;
+    m_toPos.setX(0);
+    m_toPos.setY(0);
+    m_type = NoTransition;
+    m_isTarget = false;
+}
+
 
 QQuickItemViewChangeSet::QQuickItemViewChangeSet()
     : active(false)
@@ -137,6 +413,364 @@ void QQuickItemViewChangeSet::reset()
 }
 
 
+QQuickViewTransitionAttached::QQuickViewTransitionAttached(QObject *parent)
+    : QObject(parent), m_index(-1), m_item(0)
+{
+}
+/*!
+    \qmlclass ViewTransition QQuickViewTransitionAttached
+    \inqmlmodule QtQuick 2
+    \ingroup qml-view-elements
+    \brief The ViewTransition attached property provides details on items under transition in a view.
+
+    With ListView and GridView, it is possible to specify transitions that should be applied whenever
+    the items in the view change as a result of modifications to the view's model. They both have the
+    following properties that can be set to the appropriate transitions to be run for various
+    operations:
+
+    \list
+    \o \c add and \c addDisplaced - the transitions to run when items are added to the view
+    \o \c remove and \c removeDisplaced - the transitions to run when items are removed from the view
+    \o \c move and \c moveDisplaced - the transitions to run when items are moved within the view
+       (i.e. as a result of a move operation in the model)
+    \o \c populate - the transition to run when a view is created, or when the model changes
+    \endlist
+
+    Such view transitions additionally have access to a ViewTransition attached property that
+    provides details of the items that are under transition and the operation that triggered the
+    transition. Since view transitions are run once per item, these details can be used to customise
+    each transition for each individual item.
+
+    The ViewTransition attached property provides the following properties specific to the item to
+    which the transition is applied:
+
+    \list
+    \o ViewTransition.item - the item that is under transition
+    \o ViewTransition.index - the index of this item
+    \o ViewTransition.destination - the (x,y) point to which this item is moving for the relevant view operation
+    \endlist
+
+    In addition, ViewTransition provides properties specific to the items which are the target
+    of the operation that triggered the transition:
+
+    \list
+    \o ViewTransition.targetIndexes - the indexes of the target items
+    \o ViewTransition.targetItems - the target items themselves
+    \endlist
+
+    View transitions can be written without referring to any of the attributes listed
+    above. These attributes merely provide extra details that are useful for customising view
+    transitions.
+
+    Following is an introduction to view transitions and the ways in which the ViewTransition
+    attached property can be used to augment view transitions.
+
+
+    \section2 View transitions: a simple example
+
+    Here is a basic example of the use of view transitions. The view below specifies transitions for
+    the \c add and \c addDisplaced properties, which will be run when items are added to the view:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml 0
+
+    When the space key is pressed, adding an item to the model, the new item will fade in and
+    increase in scale over 400 milliseconds as it is added to the view. Also, any item that is
+    displaced by the addition of a new item will animate to its new position in the view over
+    400 milliseconds, as specified by the \c addDisplaced transition.
+
+    If five items were inserted in succession at index 0, the effect would be this:
+
+    \image viewtransitions-basic.gif
+
+    Notice that the NumberAnimation objects above do not need to specify a \c target to animate
+    the appropriate item. Also, the NumberAnimation in the \c addTransition does not need to specify
+    the \c to value to move the item to its correct position in the view. This is because the view
+    implicitly sets the \c target and \c to values with the correct item and final item position
+    values if these properties are not explicitly defined.
+
+    At its simplest, a view transition may just animate an item to its new position following a
+    view operation, just as the \c addDisplaced transition does above, or animate some item properties,
+    as in the \c add transition above. Additionally, a view transition may make use of the
+    ViewTransition attached property to customise animation behavior for different items. Following
+    are some examples of how this can be achieved.
+
+
+    \section2 Using the ViewTransition attached property
+
+    As stated, the various ViewTransition properties provide details specific to the individual item
+    being transitioned as well as the operation that triggered the transition. In the animation above,
+    five items are inserted in succession at index 0. When the fifth and final insertion takes place,
+    adding "Item 4" to the view, the \c add transition is run once (for the inserted item) and the
+    \c addDisplaced transition is run four times (once for each of the four existing items in the view).
+
+    At this point, if we examined the \c addDisplaced transition that was run for the bottom displaced
+    item ("Item 0"), the ViewTransition property values provided to this transition would be as follows:
+
+    \table
+    \header
+        \o Property
+        \o Value
+        \o Explanation
+    \row
+        \o ViewTransition.item
+        \o "Item 0" delegate instance
+        \o The "Item 0" \l Rectangle object itself
+    \row
+        \o ViewTransition.index
+        \o \c int value of 4
+        \o The index of "Item 0" within the model following the add operation
+    \row
+        \o ViewTransition.destination
+        \o \l point value of (0, 120)
+        \o The position that "Item 0" is moving to
+    \row
+        \o ViewTransition.targetIndexes
+        \o \c int array, just contains the integer "0" (zero)
+        \o The index of "Item 4", the new item added to the view
+    \row
+        \o ViewTransition.targetItems
+        \o object array, just contains the "Item 4" delegate instance
+        \o The "Item 4" \l Rectangle object - the new item added to the view
+    \endtable
+
+    The ViewTransition.targetIndexes and ViewTransition.targetItems lists provide the items and
+    indexes of all delegate instances that are the targets of the relevant operation. For an add
+    operation, these are all the items that are added into the view; for a remove, these are all
+    the items removed from the view, and so on. (Note these lists will only contain references to
+    items that have been created within the view or its cached items; targets that are not within
+    the visible area of the view or within the item cache will not be accessible.)
+
+    So, while the ViewTransition.item, ViewTransition.index and ViewTransition.destination values
+    vary for each individual transition that is run, the ViewTransition.targetIndexes and
+    ViewTransition.targetItems values are the same for every \c add and \c addDisplaced transition
+    that is triggered by a particular add operation.
+
+
+    \section3 Delaying animations based on index
+
+    Since each view transition is run once for each item affected by the transition, the ViewTransition
+    properties can be used within a transition to define custom behavior for each item's transition.
+    For example, the ListView in the previous example could use this information to create a ripple-type
+    effect on the movement of the displaced items.
+
+    This can be achieved by modifying the \c addDisplaced transition so that it delays the animation of
+    each displaced item based on the difference between its index (provided by ViewTransition.index)
+    and the first removed index (provided by ViewTransition.targetIndexes):
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-delayedbyindex.qml 0
+
+    Each displaced item delays its animation by an additional 100 milliseconds, producing a subtle
+    ripple-type effect when items are displaced by the add, like this:
+
+    \image viewtransitions-delayedbyindex.gif
+
+
+    \section3 Animating items to intermediate positions
+
+    The ViewTransition.item property gives a reference to the item to which the transition is being
+    applied. This can be used to access any of the item's attributes, custom \c property values,
+    and so on.
+
+    Below is a modification of the \c addDisplaced transition from the previous example. It adds a
+    ParallelAnimation with nested NumberAnimation objects that reference ViewTransition.item to access
+    each item's \c x and \c y values at the start of their transitions. This allows each item to
+    animate to an intermediate position relative to its starting point for the transition, before
+    animating to its final position in the view:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-intermediatemove.qml 0
+
+    Now, a displaced item will first move to a position of (20, 50) relative to its starting
+    position, and then to its final, correct position in the view:
+
+    \image viewtransitions-intermediatemove.gif
+
+    Since the final NumberAnimation does not specify a \c to value, the view implicitly sets this
+    value to the item's final position in the view, and so this last animation will move this item
+    to the correct place. If the transition requires the final position of the item for some calculation,
+    this is accessible through ViewTransition.destination.
+
+    Instead of using multiple NumberAnimations, you could use a PathAnimation to animate an item over
+    a curved path. For example, the \c add transition in the previous example could be augmented with
+    a PathAnimation as follows: to animate newly added items along a path:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-pathanim.qml 0
+
+    This animates newly added items along a path. Notice that each path is specified relative to
+    each item's final destination point, so that items inserted at different indexes start their
+    paths from different positions:
+
+    \image viewtransitions-pathanim.gif
+
+
+    \section2 Handling interrupted animations
+
+    A view transition may be interrupted at any time if a different view transition needs to be
+    applied while the original transition is in progress. For example, say Item A is inserted at index 0
+    and undergoes an "add" transition; then, Item B is inserted at index 0 in quick succession before
+    Item A's transition has finished. Since Item B is inserted before Item A, it will displace Item
+    A, causing the view to interrupt Item A's "add" transition mid-way and start an "addDisplaced"
+    transition on Item A instead.
+
+    For simple animations that simply animate an item's movement to its final destination, this
+    interruption is unlikely to require additional consideration. However, if a transition changes other
+    properties, this interruption may cause unwanted side effects. Consider the first example on this
+    page, repeated below for convenience:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-basic.qml 0
+
+    If multiple items are added in rapid succession, without waiting for a previous transition
+    to finish, this is the result:
+
+    \image viewtransitions-interruptedbad.gif
+
+    Each newly added item undergoes an \c add transition, but before the transition can finish,
+    another item is added, displacing the previously added item. Because of this, the \c add
+    transition on the previously added item is interrupted and an \c addDisplaced transition is
+    started on the item instead. Due to the interruption, the \c opacity and \c scale animations
+    have not completed, thus producing items with opacity and scale that are below 1.0.
+
+    To fix this, the \c addDisplaced transition should additionally ensure the item properties are
+    set to the end values specified in the \c add transition, effectively resetting these values
+    whenever an item is displaced. In this case, it means setting the item opacity and scale to 1.0:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-interruptedgood.qml 0
+
+    Now, when an item's \c add transition is interrupted, its opacity and scale are animated to 1.0
+    upon displacement, avoiding the erroneous visual effects from before:
+
+    \image viewtransitions-interruptedgood.gif
+
+    The same principle applies to any combination of view transitions. An added item may be moved
+    before its add transition finishes, or a moved item may be removed before its moved transition
+    finishes, and so on; so, the rule of thumb is that every transition should handle the same set of
+    properties.
+
+
+    \section2 Restrictions regarding ScriptAction
+
+    When a view transition is initialized, any property bindings that refer to the ViewTransition
+    attached property are evaluated in preparation for the transition. Due to the nature of the
+    internal construction of a view transition, the attributes of the ViewTransition attached
+    property are only valid for the relevant item when the transition is initialized, and may not be
+    valid when the transition is actually run.
+
+    Therefore, a ScriptAction within a view transition should not refer to the ViewTransition
+    attached property, as it may not refer to the expected values at the time that the ScriptAction
+    is actually invoked. Consider the following example:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactionbad.qml 0
+
+    When the space key is pressed, three items are moved from index 5 to index 1. For each moved
+    item, the \c moveTransition sequence presumably animates the item's color to "yellow", then
+    animates it to its final position, then changes the item color back to "lightsteelblue" using a
+    ScriptAction. However, when run, the transition does not produce the intended result:
+
+    \image viewtransitions-scriptactionbad.gif
+
+    Only the last moved item is returned to the "lightsteelblue" color; the others remain yellow. This
+    is because the ScriptAction is not run until after the transition has already been initialized, by
+    which time the ViewTransition.item value has changed to refer to a different item; the item that
+    the script had intended to refer to is not the one held by ViewTransition.item at the time the
+    ScriptAction is actually invoked.
+
+    In this instance, to avoid this issue, the view could set the property using a PropertyAction
+    instead:
+
+    \snippet doc/src/snippets/declarative/viewtransitions/viewtransitions-scriptactiongood.qml 0
+
+    When the transition is initialized, the PropertyAction \c target will be set to the respective
+    ViewTransition.item for the transition and will later run with the correct item target as
+    expected.
+  */
+
+/*!
+    \qmlattachedproperty list QtQuick2::ViewTransition::index
+
+    This attached property holds the index of the item that is being
+    transitioned.
+
+    Note that if the item is being moved, this property holds the index that
+    the item is moving to, not from.
+*/
+
+/*!
+    \qmlattachedproperty list QtQuick2::ViewTransition::item
+
+    This attached property holds the the item that is being transitioned.
+
+    \warning This item should not be kept and referred to outside of the transition
+    as it may become invalid as the view changes.
+*/
+
+/*!
+    \qmlattachedproperty list QtQuick2::ViewTransition::destination
+
+    This attached property holds the final destination position for the transitioned
+    item within the view.
+
+    This property value is a \l point with \c x and \c y properties.
+*/
+
+/*!
+    \qmlattachedproperty list QtQuick2::ViewTransition::targetIndexes
+
+    This attached property holds a list of the indexes of the items in view
+    that are the target of the relevant operation.
+
+    The targets are the items that are the subject of the operation. For
+    an add operation, these are the items being added; for a remove, these
+    are the items being removed; for a move, these are the items being
+    moved.
+
+    For example, if the transition was triggered by an insert operation
+    that added two items at index 1 and 2, this targetIndexes list would
+    have the value [1,2].
+
+    \note The targetIndexes list only contains the indexes of items that are actually
+    in view, or will be in the view once the relevant operation completes.
+
+    \sa QtQuick2::ViewTransition::targetIndexes
+*/
+
+/*!
+    \qmlattachedproperty list QtQuick2::ViewTransition::targetItems
+
+    This attached property holds the list of items in view that are the
+    target of the relevant operation.
+
+    The targets are the items that are the subject of the operation. For
+    an add operation, these are the items being added; for a remove, these
+    are the items being removed; for a move, these are the items being
+    moved.
+
+    For example, if the transition was triggered by an insert operation
+    that added two items at index 1 and 2, this targetItems list would
+    contain these two items.
+
+    \note The targetItems list only contains items that are actually
+    in view, or will be in the view once the relevant operation completes.
+
+    \warning The objects in this list should not be kept and referred to
+    outside of the transition as the items may become invalid. The targetItems
+    are only valid when the Transition is initially created; this also means
+    they should not be used by ScriptAction objects in the Transition, which are
+    not evaluated until the transition is run.
+
+    \sa QtQuick2::ViewTransition::targetIndexes
+*/
+QDeclarativeListProperty<QObject> QQuickViewTransitionAttached::targetItems()
+{
+    return QDeclarativeListProperty<QObject>(this, m_targetItems);
+}
+
+QQuickViewTransitionAttached *QQuickViewTransitionAttached::qmlAttachedProperties(QObject *obj)
+{
+    return new QQuickViewTransitionAttached(obj);
+}
+
+
+//-----------------------------------
+
 QQuickItemView::QQuickItemView(QQuickFlickablePrivate &dd, QQuickItem *parent)
     : QQuickFlickable(dd, parent)
 {
@@ -232,6 +866,12 @@ void QQuickItemView::setModel(const QVariant &model)
                 d->moveReason = QQuickItemViewPrivate::Other;
             }
             d->updateViewport();
+
+            if (d->populateTransition) {
+                d->forceLayout = true;
+                d->usePopulateTransition = true;
+                polish();
+            }
         }
         connect(d->model, SIGNAL(modelUpdated(QDeclarativeChangeSet,bool)),
                 this, SLOT(modelUpdated(QDeclarativeChangeSet,bool)));
@@ -585,6 +1225,111 @@ void QQuickItemView::setHighlightMoveDuration(int duration)
     }
 }
 
+QDeclarativeTransition *QQuickItemView::populateTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->populateTransition;
+}
+
+void QQuickItemView::setPopulateTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->populateTransition != transition) {
+        d->populateTransition = transition;
+        emit populateTransitionChanged();
+    }
+}
+
+QDeclarativeTransition *QQuickItemView::addTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->addTransition;
+}
+
+void QQuickItemView::setAddTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->addTransition != transition) {
+        d->addTransition = transition;
+        emit addTransitionChanged();
+    }
+}
+
+QDeclarativeTransition *QQuickItemView::addDisplacedTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->addDisplacedTransition;
+}
+
+void QQuickItemView::setAddDisplacedTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->addDisplacedTransition != transition) {
+        d->addDisplacedTransition = transition;
+        emit addDisplacedTransitionChanged();
+    }
+}
+
+QDeclarativeTransition *QQuickItemView::moveTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->moveTransition;
+}
+
+void QQuickItemView::setMoveTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->moveTransition != transition) {
+        d->moveTransition = transition;
+        emit moveTransitionChanged();
+    }
+}
+
+QDeclarativeTransition *QQuickItemView::moveDisplacedTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->moveDisplacedTransition;
+}
+
+void QQuickItemView::setMoveDisplacedTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->moveDisplacedTransition != transition) {
+        d->moveDisplacedTransition = transition;
+        emit moveDisplacedTransitionChanged();
+    }
+}
+
+QDeclarativeTransition *QQuickItemView::removeTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->removeTransition;
+}
+
+void QQuickItemView::setRemoveTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->removeTransition != transition) {
+        d->removeTransition = transition;
+        emit removeTransitionChanged();
+    }
+}
+
+QDeclarativeTransition *QQuickItemView::removeDisplacedTransition() const
+{
+    Q_D(const QQuickItemView);
+    return d->removeDisplacedTransition;
+}
+
+void QQuickItemView::setRemoveDisplacedTransition(QDeclarativeTransition *transition)
+{
+    Q_D(QQuickItemView);
+    if (d->removeDisplacedTransition != transition) {
+        d->removeDisplacedTransition = transition;
+        emit removeDisplacedTransitionChanged();
+    }
+}
+
 void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode)
 {
     Q_Q(QQuickItemView);
@@ -719,6 +1464,64 @@ void QQuickItemViewPrivate::applyPendingChanges()
         layout();
 }
 
+bool QQuickItemViewPrivate::hasItemTransitions() const
+{
+    return populateTransition
+            || addTransition || addDisplacedTransition
+            || moveTransition || moveDisplacedTransition
+            || removeTransition || removeDisplacedTransition;
+}
+
+void QQuickItemViewPrivate::transitionNextReposition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, bool isTarget)
+{
+    switch (type) {
+    case FxViewItemTransitionManager::NoTransition:
+        return;
+    case FxViewItemTransitionManager::PopulateTransition:
+        if (populateTransition) {
+            item->setNextTransition(FxViewItemTransitionManager::PopulateTransition, isTarget);
+            return;
+        }
+        break;
+    case FxViewItemTransitionManager::AddTransition:
+        if (!usePopulateTransition) {
+            if ((isTarget && addTransition) || (!isTarget && addDisplacedTransition)) {
+                item->setNextTransition(type, isTarget);
+                return;
+            }
+        }
+        break;
+    case FxViewItemTransitionManager::MoveTransition:
+        if ((isTarget && moveTransition) || (!isTarget && moveDisplacedTransition)) {
+            item->setNextTransition(type, isTarget);
+            return;
+        }
+        break;
+    case FxViewItemTransitionManager::RemoveTransition:
+        if ((isTarget && removeTransition) || (!isTarget && removeDisplacedTransition)) {
+            item->setNextTransition(type, isTarget);
+            return;
+        }
+        break;
+    }
+
+    // the requested transition type is not valid, but the item is scheduled/in another
+    // transition, so cancel it to allow the item to move directly to the correct pos
+    if (item->transitionScheduledOrRunning())
+        item->stopTransition();
+}
+
+int QQuickItemViewPrivate::findMoveKeyIndex(QDeclarativeChangeSet::MoveKey key, const QVector<QDeclarativeChangeSet::Remove> &changes) const
+{
+    for (int i=0; i<changes.count(); i++) {
+        for (int j=changes[i].index; j<changes[i].index + changes[i].count; j++) {
+            if (changes[i].moveKey(j) == key)
+                return j;
+        }
+    }
+    return -1;
+}
+
 // for debugging only
 void QQuickItemViewPrivate::checkVisible() const
 {
@@ -733,6 +1536,17 @@ void QQuickItemViewPrivate::checkVisible() const
     }
 }
 
+// for debugging only
+void QQuickItemViewPrivate::showVisibleItems() const
+{
+    qDebug() << "Visible items:";
+    for (int i = 0; i < visibleItems.count(); ++i) {
+        qDebug() << "\t" << visibleItems[i]->index
+                 << visibleItems[i]->item->objectName()
+                 << visibleItems[i]->position();
+    }
+}
+
 void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry)
 {
     Q_Q(QQuickItemView);
@@ -752,8 +1566,19 @@ void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &
             fixupPosition();
     }
 
-    if (currentItem && currentItem->item == item)
+    if (currentItem && currentItem->item == item) {
+        // don't allow item movement transitions to trigger a re-layout and
+        // start new transitions
+        bool prevDisableLayout = disableLayout;
+        if (!disableLayout) {
+            FxViewItem *actualItem = hasItemTransitions() ? visibleItem(currentIndex) : 0;
+            if (actualItem && actualItem->transition && actualItem->transition->isRunning())
+                disableLayout = true;
+        }
         updateHighlight();
+        disableLayout = prevDisableLayout;
+    }
+
     if (trackedItem && trackedItem->item == item)
         q->trackedPositionChanged();
 }
@@ -765,8 +1590,15 @@ void QQuickItemView::destroyRemoved()
             it != d->visibleItems.end();) {
         FxViewItem *item = *it;
         if (item->index == -1 && item->attached->delayRemove() == false) {
-            d->releaseItem(item);
-            it = d->visibleItems.erase(it);
+            if (d->removeTransition) {
+                // don't remove from visibleItems until next layout()
+                d->runDelayedRemoveTransition = true;
+                QObject::disconnect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()));
+                ++it;
+            } else {
+                d->releaseItem(item);
+                it = d->visibleItems.erase(it);
+            }
         } else {
             ++it;
         }
@@ -782,6 +1614,7 @@ void QQuickItemView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
 {
     Q_D(QQuickItemView);
     if (reset) {
+        d->usePopulateTransition = true;
         d->moveReason = QQuickItemViewPrivate::SetIndex;
         d->regenerate();
         if (d->highlight && d->currentItem) {
@@ -790,8 +1623,11 @@ void QQuickItemView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
             d->updateTrackedItem();
         }
         d->moveReason = QQuickItemViewPrivate::Other;
-
         emit countChanged();
+        if (d->populateTransition) {
+            d->forceLayout = true;
+            polish();
+        }
     } else {
         d->currentChanges.prepare(d->currentIndex, d->itemCount);
         d->currentChanges.applyChanges(changeSet);
@@ -1088,6 +1924,8 @@ void QQuickItemView::componentComplete()
     d->updateFooter();
     d->updateViewport();
     d->setPosition(d->contentStartOffset());
+    d->usePopulateTransition = true;
+
     if (d->isValid()) {
         d->refill();
         d->moveReason = QQuickItemViewPrivate::SetIndex;
@@ -1122,11 +1960,16 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
     , highlightRangeStart(0), highlightRangeEnd(0)
     , highlightMoveDuration(150)
     , headerComponent(0), header(0), footerComponent(0), footer(0)
+    , populateTransition(0)
+    , addTransition(0), addDisplacedTransition(0)
+    , moveTransition(0), moveDisplacedTransition(0)
+    , removeTransition(0), removeDisplacedTransition(0)
     , minExtent(0), maxExtent(0)
     , ownModel(false), wrap(false)
-    , inApplyModelChanges(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
+    , disableLayout(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
     , haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false)
     , fillCacheBuffer(false), inRequest(false), requestedAsync(false)
+    , usePopulateTransition(false), runDelayedRemoveTransition(false)
 {
 }
 
@@ -1193,6 +2036,8 @@ FxViewItem *QQuickItemViewPrivate::visibleItem(int modelIndex) const {
     return 0;
 }
 
+// should rename to firstItemInView() to avoid confusion with other "*visible*" methods
+// that don't look at the view position and size
 FxViewItem *QQuickItemViewPrivate::firstVisibleItem() const {
     const qreal pos = isContentFlowReversed() ? -position()-size() : position();
     for (int i = 0; i < visibleItems.count(); ++i) {
@@ -1203,6 +2048,16 @@ FxViewItem *QQuickItemViewPrivate::firstVisibleItem() const {
     return visibleItems.count() ? visibleItems.first() : 0;
 }
 
+int QQuickItemViewPrivate::findLastIndexInView() const
+{
+    const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
+    for (int i=visibleItems.count() - 1; i>=0; i--) {
+        if (visibleItems.at(i)->position() <= viewEndPos && visibleItems.at(i)->index != -1)
+            return visibleItems.at(i)->index;
+    }
+    return -1;
+}
+
 // Map a model index to visibleItems list index.
 // These may differ if removed items are still present in the visible list,
 // e.g. doing a removal animation
@@ -1284,6 +2139,12 @@ void QQuickItemViewPrivate::clear()
     visibleItems.clear();
     visibleIndex = 0;
 
+    for (int i = 0; i < releasePendingTransition.count(); ++i) {
+        releasePendingTransition.at(i)->releaseAfterTransition = false;
+        releaseItem(releasePendingTransition.at(i));
+    }
+    releasePendingTransition.clear();
+
     releaseItem(currentItem);
     currentItem = 0;
     createHighlight();
@@ -1382,16 +2243,26 @@ void QQuickItemViewPrivate::updateViewport()
 void QQuickItemViewPrivate::layout()
 {
     Q_Q(QQuickItemView);
-    if (inApplyModelChanges)
+    if (disableLayout)
         return;
 
     if (!isValid() && !visibleItems.count()) {
         clear();
         setPosition(contentStartOffset());
+        usePopulateTransition = false;
         return;
     }
 
-    if (!applyModelChanges() && !forceLayout) {
+    if (runDelayedRemoveTransition && removeDisplacedTransition) {
+        // assume that any items moving now are moving due to the remove - if they schedule
+        // a different transition, that will override this one anyway
+        for (int i=0; i<visibleItems.count(); i++)
+            transitionNextReposition(visibleItems[i], FxViewItemTransitionManager::RemoveTransition, false);
+    }
+
+    ChangeResult insertionPosChanges;
+    ChangeResult removalPosChanges;
+    if (!applyModelChanges(&insertionPosChanges, &removalPosChanges) && !forceLayout) {
         if (fillCacheBuffer) {
             fillCacheBuffer = false;
             refill();
@@ -1400,11 +2271,15 @@ void QQuickItemViewPrivate::layout()
     }
     forceLayout = false;
 
+    if (usePopulateTransition && populateTransition) {
+        for (int i=0; i<visibleItems.count(); i++)
+            transitionNextReposition(visibleItems.at(i), FxViewItemTransitionManager::PopulateTransition, true);
+    }
     layoutVisibleItems();
-    refill();
 
+    int lastIndexInView = findLastIndexInView();
+    refill();
     markExtentsDirty();
-
     updateHighlight();
 
     if (!q->isMoving() && !q->isFlicking()) {
@@ -1416,20 +2291,49 @@ void QQuickItemViewPrivate::layout()
     updateFooter();
     updateViewport();
     updateUnrequestedPositions();
+
+    if (hasItemTransitions()) {
+        // items added in the last refill() may need to be transitioned in - e.g. a remove
+        // causes items to slide up into view
+        if (moveDisplacedTransition || removeDisplacedTransition)
+            translateAndTransitionItemsAfter(lastIndexInView, insertionPosChanges, removalPosChanges);
+
+        prepareVisibleItemTransitions();
+
+        QRectF viewBounds(0, position(), q->width(), q->height());
+        for (QList<FxViewItem*>::Iterator it = releasePendingTransition.begin();
+             it != releasePendingTransition.end(); ) {
+            FxViewItem *item = *it;
+            if ( (item->transition && item->transition->isActive())
+                 || prepareNonVisibleItemTransition(item, viewBounds)) {
+                ++it;
+            } else {
+                releaseItem(item);
+                it = releasePendingTransition.erase(it);
+            }
+        }
+
+        for (int i=0; i<visibleItems.count(); i++)
+            visibleItems[i]->startTransition();
+        for (int i=0; i<releasePendingTransition.count(); i++)
+            releasePendingTransition[i]->startTransition();
+    }
+    usePopulateTransition = false;
+    runDelayedRemoveTransition = false;
 }
 
-bool QQuickItemViewPrivate::applyModelChanges()
+bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult, ChangeResult *totalRemovalResult)
 {
     Q_Q(QQuickItemView);
-    if (!q->isComponentComplete() || !currentChanges.hasPendingChanges() || inApplyModelChanges)
+    if (!q->isComponentComplete() || (!currentChanges.hasPendingChanges() && !runDelayedRemoveTransition) || disableLayout)
         return false;
 
-    inApplyModelChanges = true;
+    disableLayout = true;
 
     updateUnrequestedIndexes();
     moveReason = QQuickItemViewPrivate::Other;
 
-    FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? visibleItems.first() : 0;
+    FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? *visibleItems.constBegin() : 0;
     int prevItemCount = itemCount;
     int prevVisibleItemsCount = visibleItems.count();
     bool visibleAffected = false;
@@ -1445,10 +2349,13 @@ bool QQuickItemViewPrivate::applyModelChanges()
     }
     qreal prevVisibleItemsFirstPos = visibleItems.count() ? visibleItems.first()->position() : 0.0;
 
+    totalInsertionResult->visiblePos = prevViewPos;
+    totalRemovalResult->visiblePos = prevViewPos;
+
     const QVector<QDeclarativeChangeSet::Remove> &removals = currentChanges.pendingChanges.removes();
     const QVector<QDeclarativeChangeSet::Insert> &insertions = currentChanges.pendingChanges.inserts();
-    ChangeResult removalResult(prevViewPos);
     ChangeResult insertionResult(prevViewPos);
+    ChangeResult removalResult(prevViewPos);
 
     int removedCount = 0;
     for (int i=0; i<removals.count(); i++) {
@@ -1459,11 +2366,25 @@ bool QQuickItemViewPrivate::applyModelChanges()
             visibleAffected = true;
         if (prevFirstVisibleIndex >= 0 && removals[i].index < prevFirstVisibleIndex) {
             if (removals[i].index + removals[i].count < prevFirstVisibleIndex)
-                removalResult.changeBeforeVisible -= removals[i].count;
+                removalResult.countChangeBeforeVisible += removals[i].count;
             else
-                removalResult.changeBeforeVisible -= (prevFirstVisibleIndex - removals[i].index);
+                removalResult.countChangeBeforeVisible += (prevFirstVisibleIndex - removals[i].index);
         }
     }
+    if (runDelayedRemoveTransition) {
+        QDeclarativeChangeSet::Remove removal;
+        for (QList<FxViewItem*>::Iterator it = visibleItems.begin(); it != visibleItems.end();) {
+            FxViewItem *item = *it;
+            if (item->index == -1 && !item->attached->delayRemove()) {
+                removeItem(item, removal, &removalResult);
+                removedCount++;
+                it = visibleItems.erase(it);
+            } else {
+               ++it;
+            }
+        }
+    }
+    *totalRemovalResult += removalResult;
     if (!removals.isEmpty()) {
         updateVisibleIndex();
 
@@ -1475,31 +2396,50 @@ bool QQuickItemViewPrivate::applyModelChanges()
     }
 
     QList<FxViewItem *> newItems;
+    QList<MovedItem> movingIntoView;
+
     for (int i=0; i<insertions.count(); i++) {
         bool wasEmpty = visibleItems.isEmpty();
-        if (applyInsertionChange(insertions[i], &insertionResult, &newItems))
+        if (applyInsertionChange(insertions[i], &insertionResult, &newItems, &movingIntoView))
             visibleAffected = true;
         if (!visibleAffected && needsRefillForAddedOrRemovedIndex(insertions[i].index))
             visibleAffected = true;
         if (wasEmpty && !visibleItems.isEmpty())
             resetFirstItemPosition();
+        *totalInsertionResult += insertionResult;
 
         // set positions correctly for the next insertion
         if (i < insertions.count() - 1) {
             repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
             layoutVisibleItems(insertions[i].index);
         }
-
         itemCount += insertions[i].count;
     }
     for (int i=0; i<newItems.count(); i++)
         newItems.at(i)->attached->emitAdd();
 
+    // for each item that was moved directly into the view as a result of a move(),
+    // find the index it was moved from in order to set its initial position, so that we
+    // can transition it from this "original" position to its new position in the view
+    if (moveTransition) {
+        for (int i=0; i<movingIntoView.count(); i++) {
+            int fromIndex = findMoveKeyIndex(movingIntoView[i].moveKey, removals);
+            if (fromIndex >= 0) {
+                if (prevFirstVisibleIndex >= 0 && fromIndex < prevFirstVisibleIndex)
+                    repositionItemAt(movingIntoView[i].item, fromIndex, -totalInsertionResult->sizeChangesAfterVisiblePos);
+                else
+                    repositionItemAt(movingIntoView[i].item, fromIndex, totalInsertionResult->sizeChangesAfterVisiblePos);
+                transitionNextReposition(movingIntoView[i].item, FxViewItemTransitionManager::MoveTransition, true);
+            }
+        }
+    }
+
     // reposition visibleItems.first() correctly so that the content y doesn't jump
     if (removedCount != prevVisibleItemsCount)
         repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
 
     // Whatever removed/moved items remain are no longer visible items.
+    prepareRemoveTransitions(&currentChanges.removedItems);
     for (QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *>::Iterator it = currentChanges.removedItems.begin();
          it != currentChanges.removedItems.end(); ++it) {
         releaseItem(it.value());
@@ -1526,7 +2466,7 @@ bool QQuickItemViewPrivate::applyModelChanges()
     if (!visibleAffected && viewportChanged)
         updateViewport();
 
-    inApplyModelChanges = false;
+    disableLayout = false;
     return visibleAffected;
 }
 
@@ -1535,6 +2475,13 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
     Q_Q(QQuickItemView);
     bool visibleAffected = false;
 
+    if (visibleItems.count() && removal.index + removal.count > visibleItems.last()->index) {
+        if (removal.index > visibleItems.last()->index)
+            removeResult->countChangeAfterVisibleItems += removal.count;
+        else
+            removeResult->countChangeAfterVisibleItems += ((removal.index + removal.count - 1) - visibleItems.last()->index);
+    }
+
     QList<FxViewItem*>::Iterator it = visibleItems.begin();
     while (it != visibleItems.end()) {
         FxViewItem *item = *it;
@@ -1546,6 +2493,10 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
         } else if (item->index >= removal.index + removal.count) {
             // after removed items
             item->index -= removal.count;
+            if (removal.isMove())
+                transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, false);
+            else
+                transitionNextReposition(item, FxViewItemTransitionManager::RemoveTransition, false);
             ++it;
         } else {
             // removed item
@@ -1558,21 +2509,9 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
                 QObject::connect(item->attached, SIGNAL(delayRemoveChanged()), q, SLOT(destroyRemoved()), Qt::QueuedConnection);
                 ++it;
             } else {
-                if (removeResult->visiblePos.isValid()) {
-                    if (item->position() < removeResult->visiblePos)
-                        removeResult->sizeChangesBeforeVisiblePos += item->size();
-                    else
-                        removeResult->sizeChangesAfterVisiblePos += item->size();
-                }
-                if (removal.isMove()) {
-                    currentChanges.removedItems.insert(removal.moveKey(item->index), item);
-                } else {
-                    // track item so it is released later
-                    currentChanges.removedItems.insertMulti(QDeclarativeChangeSet::MoveKey(), item);
+                removeItem(item, removal, removeResult);
+                if (!removal.isMove())
                     (*removedCount)++;
-                }
-                if (!removeResult->changedFirstItem && item == visibleItems.first())
-                    removeResult->changedFirstItem = true;
                 it = visibleItems.erase(it);
             }
         }
@@ -1581,6 +2520,25 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remo
     return visibleAffected;
 }
 
+void QQuickItemViewPrivate::removeItem(FxViewItem *item, const QDeclarativeChangeSet::Remove &removal, ChangeResult *removeResult)
+{
+    if (removeResult->visiblePos.isValid()) {
+        if (item->position() < removeResult->visiblePos)
+            removeResult->sizeChangesBeforeVisiblePos += item->size();
+        else
+            removeResult->sizeChangesAfterVisiblePos += item->size();
+    }
+    if (removal.isMove()) {
+        currentChanges.removedItems.insert(removal.moveKey(item->index), item);
+        transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, true);
+    } else {
+        // track item so it is released later
+        currentChanges.removedItems.insertMulti(QDeclarativeChangeSet::MoveKey(), item);
+    }
+    if (!removeResult->changedFirstItem && item == *visibleItems.constBegin())
+        removeResult->changedFirstItem = true;
+}
+
 void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirst,
                                                    qreal prevVisibleItemsFirstPos,
                                                    FxViewItem *prevFirstVisible,
@@ -1613,13 +2571,102 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs
                 moveForwardsBy = removalResult->sizeChangesBeforeVisiblePos;
                 moveBackwardsBy = insertionResult->sizeChangesBeforeVisiblePos;
             }
-            adjustFirstItem(moveForwardsBy, moveBackwardsBy, insertionResult->changeBeforeVisible + removalResult->changeBeforeVisible);
+            adjustFirstItem(moveForwardsBy, moveBackwardsBy, insertionResult->countChangeBeforeVisible - removalResult->countChangeBeforeVisible);
         }
         insertionResult->reset();
         removalResult->reset();
     }
 }
 
+void QQuickItemViewPrivate::prepareVisibleItemTransitions()
+{
+    Q_Q(QQuickItemView);
+    if (!hasItemTransitions())
+        return;
+
+    addTransitionIndexes.clear();
+    addTransitionTargets.clear();
+    moveTransitionIndexes.clear();
+    moveTransitionTargets.clear();
+
+    QRectF viewBounds(0, position(), q->width(), q->height());
+    for (int i=0; i<visibleItems.count(); i++) {
+        // must call for every visible item to init or discard transitions
+        if (!visibleItems[i]->prepareTransition(viewBounds))
+            continue;
+        if (visibleItems[i]->isTransitionTarget) {
+            switch (visibleItems[i]->nextTransitionType) {
+            case FxViewItemTransitionManager::NoTransition:
+                break;
+            case FxViewItemTransitionManager::PopulateTransition:
+            case FxViewItemTransitionManager::AddTransition:
+                addTransitionIndexes.append(visibleItems[i]->index);
+                addTransitionTargets.append(visibleItems[i]->item);
+                break;
+            case FxViewItemTransitionManager::MoveTransition:
+                moveTransitionIndexes.append(visibleItems[i]->index);
+                moveTransitionTargets.append(visibleItems[i]->item);
+                break;
+            case FxViewItemTransitionManager::RemoveTransition:
+                // removed targets won't be in visibleItems, handle these
+                // in prepareNonVisibleItemTransition()
+                break;
+            }
+        }
+    }
+}
+
+void QQuickItemViewPrivate::prepareRemoveTransitions(QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *> *removedItems)
+{
+    if (!removeTransition && !removeDisplacedTransition)
+        return;
+
+    removeTransitionIndexes.clear();
+    removeTransitionTargets.clear();
+
+    if (removeTransition) {
+        for (QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *>::Iterator it = removedItems->begin();
+             it != removedItems->end(); ) {
+            bool isRemove = it.key().moveId < 0;
+            if (isRemove) {
+                FxViewItem *item = *it;
+                item->releaseAfterTransition = true;
+                releasePendingTransition.append(item);
+                transitionNextReposition(item, FxViewItemTransitionManager::RemoveTransition, true);
+                it = removedItems->erase(it);
+            } else {
+                ++it;
+            }
+        }
+    }
+}
+
+bool QQuickItemViewPrivate::prepareNonVisibleItemTransition(FxViewItem *item, const QRectF &viewBounds)
+{
+    // Called for items that have been removed from visibleItems and may now be
+    // transitioned out of the view. This applies to items that are being directly
+    // removed, or moved to outside of the view, as well as those that are
+    // displaced to a position outside of the view due to an insert or move.
+
+    if (item->nextTransitionType == FxViewItemTransitionManager::MoveTransition)
+        repositionItemAt(item, item->index, 0);
+    if (!item->prepareTransition(viewBounds))
+        return false;
+
+    if (item->isTransitionTarget) {
+        if (item->nextTransitionType == FxViewItemTransitionManager::MoveTransition) {
+            moveTransitionIndexes.append(item->index);
+            moveTransitionTargets.append(item->item);
+        } else if (item->nextTransitionType == FxViewItemTransitionManager::RemoveTransition) {
+            removeTransitionIndexes.append(item->index);
+            removeTransitionTargets.append(item->item);
+        }
+    }
+
+    item->releaseAfterTransition = true;
+    return true;
+}
+
 /*
   This may return 0 if the item is being created asynchronously.
   When the item becomes available, refill() will be called and the item
@@ -1628,6 +2675,7 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs
 FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, bool asynchronous)
 {
     Q_Q(QQuickItemView);
+
     if (requestedIndex == modelIndex && (asynchronous || requestedAsync == asynchronous))
         return 0;
 
@@ -1638,6 +2686,14 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, bool asynchronous)
         requestedItem = 0;
     }
 
+    for (int i=0; i<releasePendingTransition.count(); i++) {
+        if (releasePendingTransition[i]->index == modelIndex
+                && !releasePendingTransition[i]->isPendingRemoval()) {
+            releasePendingTransition[i]->releaseAfterTransition = false;
+            return releasePendingTransition.takeAt(i);
+        }
+    }
+
     requestedIndex = modelIndex;
     requestedAsync = asynchronous;
     inRequest = true;
index e43f7c6..0d3cd1c 100644 (file)
@@ -48,6 +48,8 @@ QT_BEGIN_HEADER
 
 QT_BEGIN_NAMESPACE
 
+QT_MODULE(Declarative)
+
 class QDeclarativeChangeSet;
 
 class QQuickItemViewPrivate;
@@ -74,6 +76,14 @@ class Q_AUTOTEST_EXPORT QQuickItemView : public QQuickFlickable
     Q_PROPERTY(QDeclarativeComponent *footer READ footer WRITE setFooter NOTIFY footerChanged)
     Q_PROPERTY(QQuickItem *footerItem READ footerItem NOTIFY footerItemChanged)
 
+    Q_PROPERTY(QDeclarativeTransition *populate READ populateTransition WRITE setPopulateTransition NOTIFY populateTransitionChanged)
+    Q_PROPERTY(QDeclarativeTransition *add READ addTransition WRITE setAddTransition NOTIFY addTransitionChanged)
+    Q_PROPERTY(QDeclarativeTransition *addDisplaced READ addDisplacedTransition WRITE setAddDisplacedTransition NOTIFY addDisplacedTransitionChanged)
+    Q_PROPERTY(QDeclarativeTransition *move READ moveTransition WRITE setMoveTransition NOTIFY moveTransitionChanged)
+    Q_PROPERTY(QDeclarativeTransition *moveDisplaced READ moveDisplacedTransition WRITE setMoveDisplacedTransition NOTIFY moveDisplacedTransitionChanged)
+    Q_PROPERTY(QDeclarativeTransition *remove READ removeTransition WRITE setRemoveTransition NOTIFY removeTransitionChanged)
+    Q_PROPERTY(QDeclarativeTransition *removeDisplaced READ removeDisplacedTransition WRITE setRemoveDisplacedTransition NOTIFY removeDisplacedTransitionChanged)
+
     Q_PROPERTY(QDeclarativeComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged)
     Q_PROPERTY(QQuickItem *highlightItem READ highlightItem NOTIFY highlightItemChanged)
     Q_PROPERTY(bool highlightFollowsCurrentItem READ highlightFollowsCurrentItem WRITE setHighlightFollowsCurrentItem NOTIFY highlightFollowsCurrentItemChanged)
@@ -120,6 +130,27 @@ public:
     void setHeader(QDeclarativeComponent *);
     QQuickItem *headerItem() const;
 
+    QDeclarativeTransition *populateTransition() const;
+    void setPopulateTransition(QDeclarativeTransition *transition);
+
+    QDeclarativeTransition *addTransition() const;
+    void setAddTransition(QDeclarativeTransition *transition);
+
+    QDeclarativeTransition *addDisplacedTransition() const;
+    void setAddDisplacedTransition(QDeclarativeTransition *transition);
+
+    QDeclarativeTransition *moveTransition() const;
+    void setMoveTransition(QDeclarativeTransition *transition);
+
+    QDeclarativeTransition *moveDisplacedTransition() const;
+    void setMoveDisplacedTransition(QDeclarativeTransition *transition);
+
+    QDeclarativeTransition *removeTransition() const;
+    void setRemoveTransition(QDeclarativeTransition *transition);
+
+    QDeclarativeTransition *removeDisplacedTransition() const;
+    void setRemoveDisplacedTransition(QDeclarativeTransition *transition);
+
     QDeclarativeComponent *highlight() const;
     void setHighlight(QDeclarativeComponent *);
 
@@ -173,6 +204,14 @@ signals:
     void headerItemChanged();
     void footerItemChanged();
 
+    void populateTransitionChanged();
+    void addTransitionChanged();
+    void addDisplacedTransitionChanged();
+    void moveTransitionChanged();
+    void moveDisplacedTransitionChanged();
+    void removeTransitionChanged();
+    void removeDisplacedTransitionChanged();
+
     void highlightChanged();
     void highlightItemChanged();
     void highlightFollowsCurrentItemChanged();
@@ -200,8 +239,6 @@ protected slots:
     void animStopped();
     void trackedPositionChanged();
 
-
-
 private:
     Q_DECLARE_PRIVATE(QQuickItemView)
 };
@@ -287,8 +324,53 @@ public:
     QString m_nextSection;
 };
 
+class QQuickViewTransitionAttached : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(int index READ index NOTIFY indexChanged)
+    Q_PROPERTY(QQuickItem* item READ item NOTIFY itemChanged)
+    Q_PROPERTY(QPointF destination READ destination NOTIFY destinationChanged)
+
+    Q_PROPERTY(QList<int> targetIndexes READ targetIndexes NOTIFY targetIndexesChanged)
+    Q_PROPERTY(QDeclarativeListProperty<QObject> targetItems READ targetItems NOTIFY targetItemsChanged)
+
+public:
+    QQuickViewTransitionAttached(QObject *parent);
+
+    int index() const { return m_index; }
+    QQuickItem *item() const { return m_item; }
+    QPointF destination() const { return m_destination; }
+
+    QList<int> targetIndexes() const { return m_targetIndexes; }
+    QDeclarativeListProperty<QObject> targetItems();
+
+    static QQuickViewTransitionAttached *qmlAttachedProperties(QObject *);
+
+signals:
+    void indexChanged();
+    void itemChanged();
+    void destinationChanged();
+
+    void targetIndexesChanged();
+    void targetItemsChanged();
+
+private:
+    friend class FxViewItemTransitionManager;
+    int m_index;
+    QQuickItem *m_item;
+    QPointF m_destination;
+
+    QList<int> m_targetIndexes;
+    QList<QObject *> m_targetItems;
+};
+
 
 QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickViewTransitionAttached)
+QML_DECLARE_TYPEINFO(QQuickViewTransitionAttached, QML_HAS_ATTACHED_PROPERTIES)
+
 QT_END_HEADER
 
 #endif // QQUICKITEMVIEW_P_H
index f191f06..6768149 100644 (file)
@@ -45,6 +45,8 @@
 #include "qquickitemview_p.h"
 #include "qquickflickable_p_p.h"
 #include "qquickvisualdatamodel_p.h"
+#include "qquickvisualitemmodel_p.h"
+#include <private/qdeclarativetransitionmanager_p_p.h>
 #include <private/qdeclarativechangeset_p.h>
 
 
@@ -52,12 +54,58 @@ QT_BEGIN_HEADER
 
 QT_BEGIN_NAMESPACE
 
+QT_MODULE(Declarative)
+
+
+class FxViewItem;
+class FxViewItemTransitionManager : public QDeclarativeTransitionManager
+{
+public:
+    enum TransitionType {
+        NoTransition,
+        PopulateTransition,
+        AddTransition,
+        MoveTransition,
+        RemoveTransition
+    };
+
+    FxViewItemTransitionManager();
+    ~FxViewItemTransitionManager();
+
+    bool isActive() const;
+    void startTransition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, const QPointF &to, bool isTargetItem);
+
+    bool m_active;
+    FxViewItem *m_item;
+    QPointF m_toPos;
+    FxViewItemTransitionManager::TransitionType m_type;
+    bool m_isTarget;
+
+protected:
+    virtual void finished();
+};
+
+
 class FxViewItem
 {
 public:
     FxViewItem(QQuickItem *, bool own);
     virtual ~FxViewItem();
 
+    qreal itemX() const;
+    qreal itemY() const;
+
+    void setVisible(bool visible);
+
+    void setNextTransition(FxViewItemTransitionManager::TransitionType, bool isTargetItem);
+    bool transitionScheduledOrRunning() const;
+    bool isPendingRemoval() const;
+
+    bool prepareTransition(const QRectF &viewBounds);
+    void startTransition();
+    void stopTransition();
+    void finishedTransition();
+
     // these are positions and sizes along the current direction of scrolling/flicking
     virtual qreal position() const = 0;
     virtual qreal endPosition() const = 0;
@@ -65,13 +113,26 @@ public:
     virtual qreal sectionSize() const = 0;
 
     virtual bool contains(qreal x, qreal y) const = 0;
+    virtual QQuickItemView *itemView() const = 0;
 
     QQuickItem *item;
     bool ownItem;
     int index;
+    bool releaseAfterTransition;
     QQuickItemViewAttached *attached;
+
+    FxViewItemTransitionManager *transition;
+    QPointF nextTransitionTo;
+    FxViewItemTransitionManager::TransitionType nextTransitionType;
+    bool isTransitionTarget;
+    bool nextTransitionToSet;
+
+protected:
+    void moveTo(const QPointF &pos);
+    void resetTransitionData();
 };
 
+
 class QQuickItemViewChangeSet
 {
 public:
@@ -93,6 +154,7 @@ public:
     bool currentRemoved : 1;
 };
 
+
 class QQuickItemViewPrivate : public QQuickFlickablePrivate
 {
     Q_DECLARE_PUBLIC(QQuickItemView)
@@ -101,20 +163,39 @@ public:
 
     struct ChangeResult {
         QDeclarativeNullableValue<qreal> visiblePos;
+        bool changedFirstItem;
         qreal sizeChangesBeforeVisiblePos;
         qreal sizeChangesAfterVisiblePos;
-        bool changedFirstItem;
-        int changeBeforeVisible;
+        int countChangeBeforeVisible;
+        int countChangeAfterVisibleItems;
+
+        ChangeResult()
+            : visiblePos(0), changedFirstItem(false),
+              sizeChangesBeforeVisiblePos(0), sizeChangesAfterVisiblePos(0),
+              countChangeBeforeVisible(0), countChangeAfterVisibleItems(0) {}
 
         ChangeResult(const QDeclarativeNullableValue<qreal> &p)
-            : visiblePos(p), sizeChangesBeforeVisiblePos(0), sizeChangesAfterVisiblePos(0),
-            changedFirstItem(false), changeBeforeVisible(0) {}
+            : visiblePos(p), changedFirstItem(false),
+              sizeChangesBeforeVisiblePos(0), sizeChangesAfterVisiblePos(0),
+              countChangeBeforeVisible(0), countChangeAfterVisibleItems(0) {}
+
+        ChangeResult &operator+=(const ChangeResult &other) {
+            if (&other == this)
+                return *this;
+            changedFirstItem &= other.changedFirstItem;
+            sizeChangesBeforeVisiblePos += other.sizeChangesBeforeVisiblePos;
+            sizeChangesAfterVisiblePos += other.sizeChangesAfterVisiblePos;
+            countChangeBeforeVisible += other.countChangeBeforeVisible;
+            countChangeAfterVisibleItems += other.countChangeAfterVisibleItems;
+            return *this;
+        }
 
         void reset() {
+            changedFirstItem = false;
             sizeChangesBeforeVisiblePos = 0.0;
             sizeChangesAfterVisiblePos = 0.0;
-            changedFirstItem = false;
-            changeBeforeVisible = 0;
+            countChangeBeforeVisible = 0;
+            countChangeAfterVisibleItems = 0;
         }
     };
 
@@ -130,6 +211,7 @@ public:
     int findLastVisibleIndex(int defaultValue = -1) const;
     FxViewItem *visibleItem(int modelIndex) const;
     FxViewItem *firstVisibleItem() const;
+    int findLastIndexInView() const;
     int mapFromModel(int modelIndex) const;
 
     virtual void init();
@@ -155,12 +237,22 @@ public:
     void updateVisibleIndex();
     void positionViewAtIndex(int index, int mode);
     void applyPendingChanges();
-    bool applyModelChanges();
+    bool applyModelChanges(ChangeResult *insertionResult, ChangeResult *removalResult);
     bool applyRemovalChange(const QDeclarativeChangeSet::Remove &removal, ChangeResult *changeResult, int *removedCount);
+    void removeItem(FxViewItem *item, const QDeclarativeChangeSet::Remove &removal, ChangeResult *removeResult);
     void repositionFirstItem(FxViewItem *prevVisibleItemsFirst, qreal prevVisibleItemsFirstPos,
             FxViewItem *prevFirstVisible, ChangeResult *insertionResult, ChangeResult *removalResult);
 
+    void prepareVisibleItemTransitions();
+    void prepareRemoveTransitions(QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *> *removedItems);
+    bool prepareNonVisibleItemTransition(FxViewItem *item, const QRectF &viewBounds);
+
+    bool hasItemTransitions() const;
+    void transitionNextReposition(FxViewItem *item, FxViewItemTransitionManager::TransitionType type, bool isTarget);
+    int findMoveKeyIndex(QDeclarativeChangeSet::MoveKey key, const QVector<QDeclarativeChangeSet::Remove> &changes) const;
+
     void checkVisible() const;
+    void showVisibleItems() const;
 
     void markExtentsDirty() {
         if (layoutOrientation() == Qt::Vertical)
@@ -201,12 +293,35 @@ public:
     QDeclarativeComponent *footerComponent;
     FxViewItem *footer;
 
+    QDeclarativeTransition *populateTransition;
+    QDeclarativeTransition *addTransition;
+    QDeclarativeTransition *addDisplacedTransition;
+    QDeclarativeTransition *moveTransition;
+    QDeclarativeTransition *moveDisplacedTransition;
+    QDeclarativeTransition *removeTransition;
+    QDeclarativeTransition *removeDisplacedTransition;
+
+    QList<int> addTransitionIndexes;
+    QList<int> moveTransitionIndexes;
+    QList<int> removeTransitionIndexes;
+    QList<QObject *> addTransitionTargets;
+    QList<QObject *> moveTransitionTargets;
+    QList<QObject *> removeTransitionTargets;
+
+    struct MovedItem {
+        FxViewItem *item;
+        QDeclarativeChangeSet::MoveKey moveKey;
+        MovedItem(FxViewItem *i, QDeclarativeChangeSet::MoveKey k)
+            : item(i), moveKey(k) {}
+    };
+    QList<FxViewItem *> releasePendingTransition;
+
     mutable qreal minExtent;
     mutable qreal maxExtent;
 
     bool ownModel : 1;
     bool wrap : 1;
-    bool inApplyModelChanges : 1;
+    bool disableLayout : 1;
     bool inViewportMoved : 1;
     bool forceLayout : 1;
     bool currentIndexCleared : 1;
@@ -217,6 +332,8 @@ public:
     bool fillCacheBuffer : 1;
     bool inRequest : 1;
     bool requestedAsync : 1;
+    bool usePopulateTransition : 1;
+    bool runDelayedRemoveTransition : 1;
 
 protected:
     virtual Qt::Orientation layoutOrientation() const = 0;
@@ -246,15 +363,19 @@ protected:
     virtual void visibleItemsChanged() {}
 
     virtual FxViewItem *newViewItem(int index, QQuickItem *item) = 0;
+    virtual void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) = 0;
     virtual void repositionPackageItemAt(QQuickItem *item, int index) = 0;
     virtual void resetFirstItemPosition(qreal pos = 0.0) = 0;
     virtual void adjustFirstItem(qreal forwards, qreal backwards, int changeBeforeVisible) = 0;
 
     virtual void layoutVisibleItems(int fromModelIndex = 0) = 0;
     virtual void changedVisibleIndex(int newIndex) = 0;
-    virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *newItems) = 0;
+
+    virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult,
+                QList<FxViewItem *> *newItems, QList<MovedItem> *movingIntoView) = 0;
 
     virtual bool needsRefillForAddedOrRemovedIndex(int) const { return false; }
+    virtual void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) = 0;
 
     virtual void initializeViewItem(FxViewItem *) {}
     virtual void initializeCurrentItem() {}
index 424edc5..03be177 100644 (file)
@@ -94,6 +94,7 @@ public:
     virtual FxViewItem *newViewItem(int index, QQuickItem *item);
     virtual void initializeViewItem(FxViewItem *item);
     virtual void releaseItem(FxViewItem *item);
+    virtual void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer);
     virtual void repositionPackageItemAt(QQuickItem *item, int index);
     virtual void resetFirstItemPosition(qreal pos = 0.0);
     virtual void adjustFirstItem(qreal forwards, qreal backwards, int);
@@ -104,7 +105,9 @@ public:
 
     virtual void setPosition(qreal pos);
     virtual void layoutVisibleItems(int fromModelIndex = 0);
-    virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems);
+
+    virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView);
+    virtual void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult);
 
     virtual void updateSections();
     QQuickItem *getSectionItem(const QString &section);
@@ -253,9 +256,9 @@ public:
     }
     qreal itemPosition() const {
         if (view->orientation() == QQuickListView::Vertical)
-            return item->y();
+            return itemY();
         else
-            return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-item->x() : item->x());
+            return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-itemX() : itemX());
     }
     qreal size() const {
         if (section)
@@ -273,35 +276,26 @@ public:
     }
     qreal endPosition() const {
         if (view->orientation() == QQuickListView::Vertical) {
-            return item->y() + item->height();
+            return itemY() + item->height();
         } else {
             return (view->effectiveLayoutDirection() == Qt::RightToLeft
-                    ? -item->x()
-                    : item->x() + item->width());
+                    ? -itemX()
+                    : itemX() + item->width());
         }
     }
     void setPosition(qreal pos) {
-        if (view->orientation() == QQuickListView::Vertical) {
-            if (section) {
+        // position the section immediately even if there is a transition
+        if (section) {
+            if (view->orientation() == QQuickListView::Vertical) {
                 section->setY(pos);
-                pos += section->height();
-            }
-            item->setY(pos);
-        } else {
-            if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
-                if (section) {
-                    section->setX(-section->width()-pos);
-                    pos += section->width();
-                }
-                item->setX(-item->width()-pos);
             } else {
-                if (section) {
+                if (view->effectiveLayoutDirection() == Qt::RightToLeft)
+                    section->setX(-section->width()-pos);
+                else
                     section->setX(pos);
-                    pos += section->width();
-                }
-                item->setX(pos);
             }
         }
+        moveTo(pointForPosition(pos));
     }
     void setSize(qreal size) {
         if (view->orientation() == QQuickListView::Vertical)
@@ -310,12 +304,34 @@ public:
             item->setWidth(size);
     }
     bool contains(qreal x, qreal y) const {
-        return (x >= item->x() && x < item->x() + item->width() &&
-                y >= item->y() && y < item->y() + item->height());
+        return (x >= itemX() && x < itemX() + item->width() &&
+                y >= itemY() && y < itemY() + item->height());
+    }
+    QQuickItemView *itemView() const {
+        return view;
     }
 
     QQuickItem *section;
     QQuickListView *view;
+
+private:
+    QPointF pointForPosition(qreal pos) const {
+        if (view->orientation() == QQuickListView::Vertical) {
+            if (section)
+                pos += section->height();
+            return QPointF(itemX(), pos);
+        } else {
+            if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
+                if (section)
+                    pos += section->width();
+                return QPointF(-item->width() - pos, itemY());
+            } else {
+                if (section)
+                    pos += section->width();
+                return QPointF(pos, itemY());
+            }
+        }
+    }
 };
 
 //----------------------------------------------------------------------------
@@ -401,8 +417,9 @@ qreal QQuickListViewPrivate::lastPosition() const
 
 qreal QQuickListViewPrivate::positionAt(int modelIndex) const
 {
-    if (FxViewItem *item = visibleItem(modelIndex))
+    if (FxViewItem *item = visibleItem(modelIndex)) {
         return item->position();
+    }
     if (!visibleItems.isEmpty()) {
         if (modelIndex < visibleIndex) {
             int count = visibleIndex - modelIndex;
@@ -612,7 +629,8 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
 #endif
         if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex, doBuffer))))
             break;
-        item->setPosition(pos);
+        if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+            item->setPosition(pos);
         item->item->setVisible(!doBuffer);
         pos += item->size() + spacing;
         visibleItems.append(item);
@@ -631,7 +649,8 @@ bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool d
             break;
         --visibleIndex;
         visiblePos -= item->size() + spacing;
-        item->setPosition(visiblePos);
+        if (!(usePopulateTransition && populateTransition)) // pos will be set by layoutVisibleItems()
+            item->setPosition(visiblePos);
         item->item->setVisible(!doBuffer);
         visibleItems.prepend(item);
         changed = true;
@@ -654,6 +673,7 @@ bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
            && (item = visibleItems.at(index)) && item->endPosition() < bufferFrom) {
         if (item->attached->delayRemove())
             break;
+
         if (item->size() > 0) {
 #ifdef DEBUG_DELEGATE_LIFECYCLE
             qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition();
@@ -663,7 +683,15 @@ bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
                 if (item->index != -1)
                     visibleIndex++;
                 visibleItems.removeAt(index);
-                releaseItem(item);
+                if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+                    qDebug() << "refill not releasing animating item" << item->index << item->item->objectName();
+#endif
+                    item->releaseAfterTransition = true;
+                    releasePendingTransition.append(item);
+                } else {
+                    releaseItem(item);
+                }
                 if (index == 0)
                     break;
                 item = visibleItems.at(--index);
@@ -681,7 +709,15 @@ bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal buffer
         qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1 << item->position();
 #endif
         visibleItems.removeLast();
-        releaseItem(item);
+        if (item->transitionScheduledOrRunning()) {
+#ifdef DEBUG_DELEGATE_LIFECYCLE
+            qDebug() << "refill not releasing animating item" << item->index << item->item->objectName();
+#endif
+            item->releaseAfterTransition = true;
+            releasePendingTransition.append(item);
+        } else {
+            releaseItem(item);
+        }
         changed = true;
     }
 
@@ -713,11 +749,12 @@ void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex)
         qreal sum = firstItem->size();
         qreal pos = firstItem->position() + firstItem->size() + spacing;
         firstItem->item->setVisible(firstItem->endPosition() >= from && firstItem->position() <= to);
+
         for (int i=1; i < visibleItems.count(); ++i) {
             FxListItemSG *item = static_cast<FxListItemSG*>(visibleItems.at(i));
             if (item->index >= fromModelIndex) {
                 item->setPosition(pos);
-                item->item->setVisible(item->endPosition() >= from && item->position() <= to);
+                item->setVisible(item->endPosition() >= from && item->position() <= to);
             }
             pos += item->size() + spacing;
             sum += item->size();
@@ -726,12 +763,16 @@ void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex)
         averageSize = qRound(sum / visibleItems.count());
 
         // move current item if it is not a visible item.
-        if (currentIndex >= 0 && currentItem && !fixedCurrent) {
+        if (currentIndex >= 0 && currentItem && !fixedCurrent)
             static_cast<FxListItemSG*>(currentItem)->setPosition(positionAt(currentIndex));
-        }
     }
 }
 
+void QQuickListViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer)
+{
+    static_cast<FxListItemSG *>(item)->setPosition(positionAt(index) + sizeBuffer);
+}
+
 void QQuickListViewPrivate::repositionPackageItemAt(QQuickItem *item, int index)
 {
     Q_Q(QQuickListView);
@@ -1132,13 +1173,17 @@ void QQuickListViewPrivate::initializeCurrentItem()
     if (currentItem) {
         FxListItemSG *listItem = static_cast<FxListItemSG *>(currentItem);
 
-        if (currentIndex == visibleIndex - 1 && visibleItems.count()) {
-            // We can calculate exact postion in this case
-            listItem->setPosition(visibleItems.first()->position() - currentItem->size() - spacing);
-        } else {
-            // Create current item now and position as best we can.
-            // Its position will be corrected when it becomes visible.
-            listItem->setPosition(positionAt(currentIndex));
+        // don't reposition the item if it's about to be transitioned to another position
+        FxViewItem *actualItem = visibleItem(currentIndex);
+        if ((!actualItem || !actualItem->transitionScheduledOrRunning())) {
+            if (currentIndex == visibleIndex - 1 && visibleItems.count()) {
+                // We can calculate exact postion in this case
+                listItem->setPosition(visibleItems.first()->position() - currentItem->size() - spacing);
+            } else {
+                // Create current item now and position as best we can.
+                // Its position will be corrected when it becomes visible.
+                listItem->setPosition(positionAt(currentIndex));
+            }
         }
 
         // Avoid showing section delegate twice.  We still need the section heading so that
@@ -1653,27 +1698,34 @@ QQuickListView::~QQuickListView()
 
 /*!
     \qmlattachedproperty bool QtQuick2::ListView::delayRemove
-    This attached property holds whether the delegate may be destroyed.
 
-    It is attached to each instance of the delegate.
+    This attached property holds whether the delegate may be destroyed. It
+    is attached to each instance of the delegate. The default value is false.
 
     It is sometimes necessary to delay the destruction of an item
-    until an animation completes.
-
-    The example delegate below ensures that the animation completes before
-    the item is removed from the list.
+    until an animation completes. The example delegate below ensures that the
+    animation completes before the item is removed from the list.
 
     \snippet doc/src/snippets/declarative/listview/listview.qml delayRemove
+
+    If a \l remove transition has been specified, it will not be applied until
+    delayRemove is returned to \c false.
 */
 
 /*!
     \qmlattachedsignal QtQuick2::ListView::onAdd()
-    This attached handler is called immediately after an item is added to the view.
+    This attached signal handler is called immediately after an item is added to the view.
+
+    If an \l add transition is specified, it is applied immediately after
+    this signal handler is called.
 */
 
 /*!
     \qmlattachedsignal QtQuick2::ListView::onRemove()
     This attached handler is called immediately before an item is removed from the view.
+
+    If a \l remove transition has been specified, it is applied after
+    this signal handler is called, providing that delayRemove is false.
 */
 
 /*!
@@ -2191,6 +2243,231 @@ void QQuickListView::setSnapMode(SnapMode mode)
     \sa footer, headerItem
 */
 
+/*!
+    \qmlproperty Transition QtQuick2::ListView::populate
+    This property holds the transition to apply to items that are initially created for a
+    view.
+
+    This transition is applied to all the items that are created when:
+
+    \list
+    \o The view is first created
+    \o The view's \l model changes
+    \o The view's \l model is \l {QAbstractItemModel::reset}{reset}, if the model is a QAbstractItemModel subclass
+    \endlist
+
+    For example, here is a view that specifies such a transition:
+
+    \code
+    ListView {
+        ...
+        populate: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    When the view is initialized, the view will create all the necessary items for the view,
+    then animate them to their correct positions within the view over one second.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa add, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::ListView::add
+    This property holds the transition to apply to items that are added within the view.
+
+    The transition is applied to items that have been added to the visible area of the view. For
+    example, here is a view that specifies such a transition:
+
+    \code
+    ListView {
+        ...
+        add: Transition {
+            NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is added to the above view, the item will be animated from the position (100,100)
+    to its final x,y position within the view, over one second. The transition only applies to
+    the new items that are added to the view; it does not apply to the items below that are
+    displaced by the addition of the new items. To animate the displaced items, set the \l
+    addDisplaced property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \note This transition is not applied to the items that are created when the view is initially
+    populated, or when the view's \l model changes. In those cases, the \l populate transition is
+    applied instead.
+
+    \sa addDisplaced, populate, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::ListView::addDisplaced
+    This property holds the transition to apply to items in the view that are displaced by other
+    items that have been added to the view.
+
+    The transition is applied to items that are currently visible and have been displaced by newly
+    added items. For example, here is a view that specifies such a transition:
+
+    \code
+    ListView {
+        ...
+        addDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is added to the above view, all items beneath the new item are displaced, causing
+    them to move down (or sideways, if horizontally orientated) within the view. As this
+    displacement occurs, the items' movement to their new x,y positions within the view will be
+    animated by a NumberAnimation over one second, as specified. This transition is not applied to
+    the new item that has been added to the view; to animate the added items, set the \l add
+    property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \note This transition is not applied to the items that are created when the view is initially
+    populated, or when the view's \l model changes. In those cases, the \l populate transition is
+    applied instead.
+
+    \sa add, populate, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::ListView::move
+    This property holds the transition to apply to items in the view that are moved by a move
+    operation.
+
+    The transition is applied to items that are moving within the view or are moving
+    into the view as a result of a move operation in the view's model. For example:
+
+    \code
+    ListView {
+        ...
+        move: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is moved within the above view, the item will be animated to its new position in
+    the view over one second. The transition only applies to the items that are the subject of the
+    move operation in the model; it does not apply to the items below them that are displaced by
+    the move operation. To animate the displaced items, set the \l moveDisplaced property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa moveDisplaced, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::ListView::moveDisplaced
+    This property holds the transition to apply to items in the view that are displaced by a
+    move operation in the view.
+
+    The transition is applied to items that are currently visible and have been displaced following
+    a move operation in the view's model. For example, here is a view that specifies such a transition:
+
+    \code
+    ListView {
+        ...
+        moveDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item moves within (or moves into) the above view, all items beneath it are
+    displaced, causing them to move upwards (or sideways, if horizontally orientated) within the
+    view. As this displacement occurs, the items' movement to their new x,y positions within the
+    view will be animated by a NumberAnimation over one second, as specified. This transition is
+    not applied to the item that are actually the subject of the move operation; to animate the
+    moved items, set the \l move property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa move, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::ListView::remove
+    This property holds the transition to apply to items that are removed from the view.
+
+    The transition is applied to items that have been removed from the visible area of the view. For
+    example:
+
+    \code
+    ListView {
+        ...
+        remove: Transition {
+            ParallelAnimation {
+                NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
+                NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
+            }
+        }
+    }
+    \endcode
+
+    Whenever an item is removed from the above view, the item will be animated to the position (100,100)
+    over one second, and in parallel will also change its opacity to 0. The transition
+    only applies to the items that are removed from the view; it does not apply to the items below
+    them that are displaced by the removal of the  items. To animate the displaced items, set the \l
+    removeDisplaced property.
+
+    Note that by the time the transition is applied, the item has already been removed from the
+    model; any references to the model data for the removed index will not be valid.
+
+    Additionally, if the \l delayRemove attached property has been set for a delegate item, the
+    remove transition will not be applied until \l delayRemove becomes false again.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa removeDisplaced, ViewTransition
+*/
+
+/*!
+    \qmlproperty Transition QtQuick2::ListView::removeDisplaced
+    This property holds the transition to apply to items in the view that are displaced by the
+    removal of other items in the view.
+
+    The transition is applied to items that are currently visible and have been displaced by
+    the removal of items. For example, here is a view that specifies such a transition:
+
+    \code
+    ListView {
+        ...
+        removeDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+    }
+    \endcode
+
+    Whenever an item is removed from the above view, all items beneath it are displaced, causing
+    them to move upwards (or sideways, if horizontally orientated) within the view. As this
+    displacement occurs, the items' movement to their new x,y positions within the view will be
+    animated by a NumberAnimation over one second, as specified. This transition is not applied to
+    the item that has actually been removed from the view; to animate the removed items, set the
+    \l remove property.
+
+    For more details and examples on how to use view transitions, see the ViewTransition
+    documentation.
+
+    \sa remove, ViewTransition
+*/
+
+
 void QQuickListView::viewportMoved()
 {
     Q_D(QQuickListView);
@@ -2385,7 +2662,7 @@ void QQuickListView::updateSections()
     }
 }
 
-bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems)
+bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
 {
     int modelIndex = change.index;
     int count = change.count;
@@ -2450,8 +2727,10 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
                 visibleItems.insert(insertionIdx, item);
                 if (insertionIdx == 0)
                     insertResult->changedFirstItem = true;
-                if (!change.isMove())
+                if (!change.isMove()) {
                     addedItems->append(item);
+                    transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+                }
                 insertResult->sizeChangesBeforeVisiblePos += item->size() + spacing;
                 pos -= item->size() + spacing;
             }
@@ -2464,6 +2743,7 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
             FxViewItem *item = 0;
             if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i))))
                 item->index = modelIndex + i;
+            bool newItem = !item;
             if (!item)
                 item = createItem(modelIndex + i);
             if (!item)
@@ -2472,8 +2752,15 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
             visibleItems.insert(index, item);
             if (index == 0)
                 insertResult->changedFirstItem = true;
-            if (!change.isMove())
+            if (change.isMove()) {
+                // we know this is a move target, since move displaced items that are
+                // shuffled into view due to a move would be added in refill()
+                if (moveTransition && newItem)
+                    movingIntoView->append(MovedItem(item, change.moveKey(item->index)));
+            } else {
                 addedItems->append(item);
+                transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, true);
+            }
             insertResult->sizeChangesAfterVisiblePos += item->size() + spacing;
             pos += item->size() + spacing;
             ++index;
@@ -2484,6 +2771,10 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
         FxViewItem *item = visibleItems.at(index);
         if (item->index != -1)
             item->index += count;
+        if (change.isMove())
+            transitionNextReposition(item, FxViewItemTransitionManager::MoveTransition, false);
+        else
+            transitionNextReposition(item, FxViewItemTransitionManager::AddTransition, false);
     }
 
     updateVisibleIndex();
@@ -2491,6 +2782,34 @@ bool QQuickListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::In
     return visibleItems.count() > prevVisibleCount;
 }
 
+void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
+{
+    Q_UNUSED(insertionResult);
+
+    int markerItemIndex = -1;
+    for (int i=0; i<visibleItems.count(); i++) {
+        if (visibleItems[i]->index == afterModelIndex) {
+            markerItemIndex = i;
+            break;
+        }
+    }
+    if (markerItemIndex < 0)
+        return;
+
+    const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
+    qreal sizeRemoved = -removalResult.sizeChangesAfterVisiblePos
+            - (removalResult.countChangeAfterVisibleItems * (averageSize + spacing));
+
+    for (int i=markerItemIndex+1; i<visibleItems.count() && visibleItems.at(i)->position() < viewEndPos; i++) {
+        FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems[i]);
+        if (!listItem->transitionScheduledOrRunning()) {
+            qreal pos = listItem->position();
+            listItem->setPosition(pos - sizeRemoved);
+            transitionNextReposition(listItem, FxViewItemTransitionManager::RemoveTransition, false);
+            listItem->setPosition(pos);
+        }
+    }
+}
 
 /*!
     \qmlmethod QtQuick2::ListView::positionViewAtIndex(int index, PositionMode mode)
diff --git a/tests/auto/qtquick2/qquickgridview/data/addTransitions.qml b/tests/auto/qtquick2/qquickgridview/data/addTransitions.qml
new file mode 100644 (file)
index 0000000..faea02a
--- /dev/null
@@ -0,0 +1,129 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 550
+    height: 600
+
+    property int duration: 10
+    property int count: grid.count
+
+    Component {
+        id: myDelegate
+
+        Rectangle {
+            id: wrapper
+
+            property string nameData: name
+
+            objectName: "wrapper"
+            width: 80
+            height: 60
+            border.width: 1
+            Column {
+                Text { text: index }
+                Text {
+                    text: wrapper.x + ", " + wrapper.y
+                }
+                Text {
+                    id: textName
+                    objectName: "textName"
+                    text: name
+                }
+            }
+            color: GridView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == targetItems_transitionFrom)
+                    model_targetItems_transitionFrom.addItem(name, "")
+                if (Qt.point(x, y) == displacedItems_transitionVia)
+                    model_displacedItems_transitionVia.addItem(name, "")
+            }
+        }
+    }
+
+    GridView {
+        id: grid
+
+        property int targetTransitionsDone
+        property int displaceTransitionsDone
+
+        property var targetTrans_items: new Object()
+        property var targetTrans_targetIndexes: new Array()
+        property var targetTrans_targetItems: new Array()
+
+        property var displacedTrans_items: new Object()
+        property var displacedTrans_targetIndexes: new Array()
+        property var displacedTrans_targetItems: new Array()
+
+        objectName: "grid"
+        width: 240
+        height: 320
+        cellWidth: 80
+        cellHeight: 60
+        anchors.centerIn: parent
+        model: testModel
+        delegate: myDelegate
+
+        // for QDeclarativeListProperty types
+        function copyList(propList) {
+            var temp = new Array()
+            for (var i=0; i<propList.length; i++)
+                temp.push(propList[i])
+            return temp
+        }
+
+        add: Transition {
+            id: targetTransition
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        grid.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+                        grid.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+                        grid.targetTrans_targetItems.push(grid.copyList(targetTransition.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: targetItems_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: targetItems_transitionFrom.y; duration: root.duration }
+                }
+
+                ScriptAction { script: grid.targetTransitionsDone += 1 }
+            }
+        }
+
+        addDisplaced: Transition {
+            id: displaced
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        grid.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+                        grid.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+                        grid.displacedTrans_targetItems.push(grid.copyList(displaced.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; duration: root.duration; to: displacedItems_transitionVia.x }
+                    NumberAnimation { properties: "y"; duration: root.duration; to: displacedItems_transitionVia.y }
+                }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: grid.displaceTransitionsDone += 1 }
+            }
+
+        }
+    }
+
+    Rectangle {
+        anchors.fill: grid
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+}
+
+
diff --git a/tests/auto/qtquick2/qquickgridview/data/moveTransitions.qml b/tests/auto/qtquick2/qquickgridview/data/moveTransitions.qml
new file mode 100644 (file)
index 0000000..3599dcf
--- /dev/null
@@ -0,0 +1,143 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: grid.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+
+            property string nameData: name
+
+            objectName: "wrapper"
+            width: 80
+            height: 60
+            border.width: 1
+            Column {
+                Text { text: index }
+                Text {
+                    text: wrapper.x + ", " + wrapper.y
+                }
+                Text {
+                    id: textName
+                    objectName: "textName"
+                    text: name
+                }
+            }
+            color: GridView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == targetItems_transitionVia)
+                    model_targetItems_transitionVia.addItem(name, "")
+                if (Qt.point(x, y) == displacedItems_transitionVia)
+                    model_displacedItems_transitionVia.addItem(name, "")
+            }
+        }
+    }
+
+    GridView {
+        id: grid
+
+        property int targetTransitionsDone
+        property int displaceTransitionsDone
+
+        property var targetTrans_items: new Object()
+        property var targetTrans_targetIndexes: new Array()
+        property var targetTrans_targetItems: new Array()
+
+        property var displacedTrans_items: new Object()
+        property var displacedTrans_targetIndexes: new Array()
+        property var displacedTrans_targetItems: new Array()
+
+        objectName: "grid"
+        width: 240
+        height: 320
+        cellWidth: 80
+        cellHeight: 60
+        anchors.centerIn: parent
+        model: testModel
+        delegate: myDelegate
+
+        // for QDeclarativeListProperty types
+        function copyList(propList) {
+            var temp = new Array()
+            for (var i=0; i<propList.length; i++)
+                temp.push(propList[i])
+            return temp
+        }
+
+        move: Transition {
+            id: targetTransition
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        grid.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+                        grid.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+                        grid.targetTrans_targetItems.push(grid.copyList(targetTransition.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; to: targetItems_transitionVia.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; to: targetItems_transitionVia.y; duration: root.duration }
+                }
+
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: grid.targetTransitionsDone += 1 }
+            }
+        }
+
+        moveDisplaced: Transition {
+            id: displaced
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        grid.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+                        grid.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+                        grid.displacedTrans_targetItems.push(grid.copyList(displaced.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation {
+                        properties: "x"; duration: root.duration
+                        to: displacedItems_transitionVia.x
+                    }
+                    NumberAnimation {
+                        properties: "y"; duration: root.duration
+                        to: displacedItems_transitionVia.y
+                    }
+                }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: grid.displaceTransitionsDone += 1 }
+            }
+
+        }
+    }
+
+    Rectangle {
+        anchors.fill: grid
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 10000 }
+    }
+}
+
+
diff --git a/tests/auto/qtquick2/qquickgridview/data/multipleTransitions.qml b/tests/auto/qtquick2/qquickgridview/data/multipleTransitions.qml
new file mode 100644 (file)
index 0000000..45b86e2
--- /dev/null
@@ -0,0 +1,123 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    // time to pause between each add, remove, etc.
+    // (obviously, must be less than 'duration' value to actually test that
+    // interrupting transitions will still produce the correct result)
+    property int timeBetweenActions: duration / 2
+
+    property int duration: 100
+
+    property int count: grid.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+            objectName: "wrapper"
+            width: 80
+            height: 60
+            border.width: 1
+            Column {
+                Text { text: index }
+                Text {
+                    text: wrapper.x + ", " + wrapper.y
+                }
+                Text {
+                    id: textName
+                    objectName: "textName"
+                    text: name
+                }
+            }
+            color: GridView.isCurrentItem ? "lightsteelblue" : "white"
+        }
+    }
+
+    GridView {
+        id: grid
+
+        property bool populateDone
+
+        property bool runningAddTargets: false
+        property bool runningAddDisplaced: false
+        property bool runningMoveTargets: false
+        property bool runningMoveDisplaced: false
+
+        objectName: "grid"
+        width: 240
+        height: 320
+        cellWidth: 80
+        cellHeight: 60
+        anchors.centerIn: parent
+        model: testModel
+        delegate: myDelegate
+
+        add: Transition {
+            id: addTargets
+            SequentialAnimation {
+                ScriptAction { script: grid.runningAddTargets = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: addTargets_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: addTargets_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: grid.runningAddTargets = false }
+            }
+        }
+
+        addDisplaced: Transition {
+            id: addDisplaced
+            SequentialAnimation {
+                ScriptAction { script: grid.runningAddDisplaced = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: addDisplaced_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: addDisplaced_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: grid.runningAddDisplaced = false }
+            }
+        }
+
+        move: Transition {
+            id: moveTargets
+            SequentialAnimation {
+                ScriptAction { script: grid.runningMoveTargets = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: moveTargets_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: moveTargets_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: grid.runningMoveTargets = false }
+            }
+        }
+
+        moveDisplaced: Transition {
+            id: moveDisplaced
+            SequentialAnimation {
+                ScriptAction { script: grid.runningMoveDisplaced = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: moveDisplaced_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: moveDisplaced_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: grid.runningMoveDisplaced = false }
+            }
+        }
+    }
+
+    Rectangle {
+        anchors.fill: grid
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+    }
+}
+
+
+
diff --git a/tests/auto/qtquick2/qquickgridview/data/populateTransitions.qml b/tests/auto/qtquick2/qquickgridview/data/populateTransitions.qml
new file mode 100644 (file)
index 0000000..c12d5ac
--- /dev/null
@@ -0,0 +1,103 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: grid.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+            objectName: "wrapper"
+            width: 80
+            height: 60
+            border.width: 1
+            Column {
+                Text { text: index }
+                Text {
+                    text: wrapper.x + ", " + wrapper.y
+                }
+                Text {
+                    id: textName
+                    objectName: "textName"
+                    text: name
+                }
+            }
+            color: GridView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == transitionFrom)
+                    model_transitionFrom.addItem(name, "")
+                if (Qt.point(x, y) == transitionVia)
+                    model_transitionVia.addItem(name, "")
+            }
+        }
+    }
+
+    GridView {
+        id: grid
+
+        property int countPopulateTransitions
+        property int countAddTransitions
+
+        objectName: "grid"
+        focus: true
+        anchors.centerIn: parent
+        width: 240
+        height: 320
+        cellWidth: 80
+        cellHeight: 60
+        model: testModel
+        delegate: myDelegate
+
+        populate: usePopulateTransition ? popTransition : null
+
+        add: Transition {
+            SequentialAnimation {
+                ScriptAction { script: grid.countAddTransitions += 1 }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+            }
+        }
+    }
+
+    Transition {
+        id: popTransition
+        SequentialAnimation {
+            ParallelAnimation {
+                NumberAnimation { properties: "x"; from: transitionFrom.x; to: transitionVia.x; duration: root.duration }
+                NumberAnimation { properties: "y"; from: transitionFrom.y; to: transitionVia.y; duration: root.duration }
+            }
+            NumberAnimation { properties: "x,y"; duration: root.duration }
+            ScriptAction { script: grid.countPopulateTransitions += 1 }
+        }
+    }
+
+    Rectangle {
+        anchors.fill: grid
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Component.onCompleted: {
+        if (dynamicallyPopulate) {
+            for (var i=0; i<30; i++)
+                testModel.addItem("item " + i, "")
+        }
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+    }
+}
+
+
diff --git a/tests/auto/qtquick2/qquickgridview/data/removeTransitions.qml b/tests/auto/qtquick2/qquickgridview/data/removeTransitions.qml
new file mode 100644 (file)
index 0000000..b07a035
--- /dev/null
@@ -0,0 +1,146 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: grid.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+
+            property string nameData: name
+
+            objectName: "wrapper"
+            width: 80
+            height: 60
+            border.width: 1
+            Column {
+                Text { text: index }
+                Text {
+                    text: wrapper.x + ", " + wrapper.y
+                }
+                Text {
+                    id: textName
+                    objectName: "textName"
+                    text: name
+                }
+            }
+            color: GridView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == targetItems_transitionTo) {
+                    model_targetItems_transitionTo.addItem(nameData, "") // name is invalid once model removes the item
+                }
+                if (Qt.point(x, y) == displacedItems_transitionVia) {
+                    model_displacedItems_transitionVia.addItem(name, "")
+                }
+            }
+        }
+    }
+
+    GridView {
+        id: grid
+
+        property int targetTransitionsDone
+        property int displaceTransitionsDone
+
+        property var targetTrans_items: new Object()
+        property var targetTrans_targetIndexes: new Array()
+        property var targetTrans_targetItems: new Array()
+
+        property var displacedTrans_items: new Object()
+        property var displacedTrans_targetIndexes: new Array()
+        property var displacedTrans_targetItems: new Array()
+
+        objectName: "grid"
+        width: 240
+        height: 320
+        cellWidth: 80
+        cellHeight: 60
+        anchors.centerIn: parent
+        model: testModel
+        delegate: myDelegate
+
+        // for QDeclarativeListProperty types
+        function copyList(propList) {
+            var temp = new Array()
+            for (var i=0; i<propList.length; i++)
+                temp.push(propList[i])
+            return temp
+        }
+
+        remove: Transition {
+            id: targetTransition
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        grid.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+                        grid.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+                        grid.targetTrans_targetItems.push(grid.copyList(targetTransition.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; to: targetItems_transitionTo.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; to: targetItems_transitionTo.y; duration: root.duration }
+                }
+                ScriptAction { script: grid.targetTransitionsDone += 1 }
+
+                // delay deleting this item so that it stays valid for the tests
+                // (this doesn't delay the test itself)
+                PauseAnimation { duration: 10000 }
+            }
+        }
+
+        removeDisplaced: Transition {
+            id: displaced
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        grid.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+                        grid.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+                        grid.displacedTrans_targetItems.push(grid.copyList(displaced.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation {
+                        properties: "x"; duration: root.duration
+                        to: displacedItems_transitionVia.x
+                    }
+                    NumberAnimation {
+                        properties: "y"; duration: root.duration
+                        to: displacedItems_transitionVia.y
+                    }
+                }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: grid.displaceTransitionsDone += 1 }
+            }
+
+        }
+    }
+
+    Rectangle {
+        anchors.fill: grid
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 10000 }
+    }
+}
+
+
index e6b27f8..5d60e91 100644 (file)
@@ -50,7 +50,9 @@
 #include <QtQuick/private/qquickitem_p.h>
 #include <QtQuick/private/qquickgridview_p.h>
 #include <QtQuick/private/qquicktext_p.h>
+#include <QtQuick/private/qquickvisualitemmodel_p.h>
 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
+#include <QtDeclarative/private/qlistmodelinterface_p.h>
 #include "../../shared/util.h"
 #include "../shared/viewtestutil.h"
 #include "../shared/visualtestutil.h"
@@ -127,6 +129,23 @@ private slots:
     void cacheBuffer();
     void asynchronous();
     void unrequestedVisibility();
+
+    void populateTransitions();
+    void populateTransitions_data();
+    void addTransitions();
+    void addTransitions_data();
+    void moveTransitions();
+    void moveTransitions_data();
+    void removeTransitions();
+    void removeTransitions_data();
+    void multipleTransitions();
+    void multipleTransitions_data();
+
+private:
+    QList<int> toIntList(const QVariantList &list);
+    void matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes);
+    void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes);
+    void matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems);
 };
 
 tst_QQuickGridView::tst_QQuickGridView()
@@ -500,13 +519,15 @@ void tst_QQuickGridView::insertBeforeVisible()
     QTRY_VERIFY(contentItem != 0);
 
     gridview->setCacheBuffer(cacheBuffer);
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
 
     // trigger a refill (not just setting contentY) so that the visibleItems grid is updated
     int firstVisibleIndex = 12;     // move to an index where the top item is not visible
     gridview->setContentY(firstVisibleIndex/3 * 60.0);
     gridview->setCurrentIndex(firstVisibleIndex);
-
     qApp->processEvents();
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+
     QTRY_COMPARE(gridview->currentIndex(), firstVisibleIndex);
     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", firstVisibleIndex);
     QVERIFY(item);
@@ -521,6 +542,7 @@ void tst_QQuickGridView::insertBeforeVisible()
     // now, moving to the top of the view should position the inserted items correctly
     int itemsOffsetAfterMove = (insertCount / 3) * -60.0;
     gridview->setCurrentIndex(0);
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
     QTRY_COMPARE(gridview->currentIndex(), 0);
     QTRY_COMPARE(gridview->contentY(), 0.0 + itemsOffsetAfterMove);
 
@@ -1283,12 +1305,19 @@ void tst_QQuickGridView::multipleChanges()
             }
             case ListChange::Removed:
                 model.removeItems(changes[i].index, changes[i].count);
+                QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
                 break;
             case ListChange::Moved:
                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+                QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
                 break;
             case ListChange::SetCurrent:
                 gridview->setCurrentIndex(changes[i].index);
+                QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                break;
+            case ListChange::SetContentY:
+                gridview->setContentY(changes[i].pos);
+                QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
                 break;
         }
     }
@@ -2005,10 +2034,10 @@ void tst_QQuickGridView::componentChanges()
     QTRY_VERIFY(gridView);
 
     QDeclarativeComponent component(canvas->engine());
-    component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
+    component.setData("import QtQuick 1.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
 
     QDeclarativeComponent delegateComponent(canvas->engine());
-    delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
+    delegateComponent.setData("import QtQuick 1.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
 
     QSignalSpy highlightSpy(gridView, SIGNAL(highlightChanged()));
     QSignalSpy delegateSpy(gridView, SIGNAL(delegateChanged()));
@@ -3769,6 +3798,898 @@ void tst_QQuickGridView::unaligned()
     delete canvas;
 }
 
+void tst_QQuickGridView::populateTransitions()
+{
+    QFETCH(bool, staticallyPopulate);
+    QFETCH(bool, dynamicallyPopulate);
+    QFETCH(bool, usePopulateTransition);
+
+    QPointF transitionFrom(-50, -50);
+    QPointF transitionVia(100, 100);
+    QaimModel model_transitionFrom;
+    QaimModel model_transitionVia;
+
+    QaimModel model;
+    if (staticallyPopulate) {
+        for (int i = 0; i < 30; i++)
+            model.addItem("item" + QString::number(i), "");
+    }
+
+    QQuickView *canvas = createView();
+    canvas->rootContext()->setContextProperty("testModel", &model);
+    canvas->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition);
+    canvas->rootContext()->setContextProperty("dynamicallyPopulate", dynamicallyPopulate);
+    canvas->rootContext()->setContextProperty("transitionFrom", transitionFrom);
+    canvas->rootContext()->setContextProperty("transitionVia", transitionVia);
+    canvas->rootContext()->setContextProperty("model_transitionFrom", &model_transitionFrom);
+    canvas->rootContext()->setContextProperty("model_transitionVia", &model_transitionVia);
+    canvas->setSource(testFileUrl("populateTransitions.qml"));
+    canvas->show();
+
+    QQuickGridView *gridview = findItem<QQuickGridView>(canvas->rootObject(), "grid");
+    QVERIFY(gridview);
+    QQuickItem *contentItem = gridview->contentItem();
+    QVERIFY(contentItem);
+
+    if (staticallyPopulate || dynamicallyPopulate) {
+        // check the populate transition is run
+        if (usePopulateTransition) {
+            QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), 19);
+        } else {
+            QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+            QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), 0);
+        }
+        QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 0);
+    } else {
+        QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+    }
+
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    if (usePopulateTransition)
+        QCOMPARE(itemCount, gridview->property("countPopulateTransitions").toInt());
+    for (int i=0; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), (i/3)*60.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    // add an item and check this is done with add transition, not populate
+    model.insertItem(0, "another item", "");
+    QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 1);
+    QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(),
+                 (usePopulateTransition && (staticallyPopulate || dynamicallyPopulate)) ? 19 : 0);
+
+    // clear the model
+    canvas->rootContext()->setContextProperty("testModel", QVariant());
+    QTRY_COMPARE(gridview->count(), 0);
+    QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
+    gridview->setProperty("countPopulateTransitions", 0);
+    gridview->setProperty("countAddTransitions", 0);
+
+    // set to a valid model and check populate transition is run a second time
+    model.clear();
+    for (int i = 0; i < 30; i++)
+        model.addItem("item" + QString::number(i), "");
+    canvas->rootContext()->setContextProperty("testModel", &model);
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+
+    QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 19 : 0);
+    QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 0);
+
+    itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    if (usePopulateTransition)
+        QCOMPARE(itemCount, gridview->property("countPopulateTransitions").toInt());
+    for (int i=0; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), (i/3)*60.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    // reset model and check populate transition is run again
+    gridview->setProperty("countPopulateTransitions", 0);
+    gridview->setProperty("countAddTransitions", 0);
+    model.reset();
+    QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 19 : 0);
+    QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 0);
+
+    itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    if (usePopulateTransition)
+        QCOMPARE(itemCount, gridview->property("countPopulateTransitions").toInt());
+    for (int i=0; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), (i/3)*60.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+}
+
+void tst_QQuickGridView::populateTransitions_data()
+{
+    QTest::addColumn<bool>("staticallyPopulate");
+    QTest::addColumn<bool>("dynamicallyPopulate");
+    QTest::addColumn<bool>("usePopulateTransition");
+
+    QTest::newRow("static") << true << false << true;
+    QTest::newRow("static, no populate") << true << false << false;
+
+    QTest::newRow("dynamic") << false << true << true;
+    QTest::newRow("dynamic, no populate") << false << true << false;
+
+    QTest::newRow("empty to start with") << false << false << true;
+    QTest::newRow("empty to start with, no populate") << false << false << false;
+}
+
+void tst_QQuickGridView::addTransitions()
+{
+    QFETCH(int, initialItemCount);
+    QFETCH(bool, shouldAnimateTargets);
+    QFETCH(qreal, contentY);
+    QFETCH(int, insertionIndex);
+    QFETCH(int, insertionCount);
+    QFETCH(ListRange, expectedDisplacedIndexes);
+
+    // added items should start here
+    QPointF targetItems_transitionFrom(-50, -50);
+
+    // displaced items should pass through this point
+    QPointF displacedItems_transitionVia(100, 100);
+
+    QaimModel model;
+    for (int i = 0; i < initialItemCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+    QaimModel model_targetItems_transitionFrom;
+    QaimModel model_displacedItems_transitionVia;
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
+    ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+    ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
+    ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+    canvas->setSource(testFileUrl("addTransitions.qml"));
+    canvas->show();
+
+    QQuickGridView *gridview = findItem<QQuickGridView>(canvas->rootObject(), "grid");
+    QTRY_VERIFY(gridview != 0);
+    QQuickItem *contentItem = gridview->contentItem();
+    QVERIFY(contentItem != 0);
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+
+    if (contentY != 0) {
+        gridview->setContentY(contentY);
+        QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+    }
+
+    QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+    // only target items that will become visible should be animated
+    QList<QPair<QString, QString> > newData;
+    QList<QPair<QString, QString> > expectedTargetData;
+    QList<int> targetIndexes;
+    if (shouldAnimateTargets) {
+        for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
+            newData << qMakePair(QString("New item %1").arg(i), QString(""));
+
+            // last visible item is the first item of the row beneath the view
+            if (i >= (contentY / 60)*3 && i < qCeil((contentY + gridview->height()) / 60.0)*3) {
+                expectedTargetData << newData.last();
+                targetIndexes << i;
+            }
+        }
+        QVERIFY(expectedTargetData.count() > 0);
+    }
+
+    // start animation
+    if (!newData.isEmpty()) {
+        model.insertItems(insertionIndex, newData);
+        QTRY_COMPARE(model.count(), gridview->count());
+    }
+
+    QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+
+    if (shouldAnimateTargets) {
+        QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+        QTRY_COMPARE(gridview->property("displaceTransitionsDone").toInt(),
+                     expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+        // check the target and displaced items were animated
+        model_targetItems_transitionFrom.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+        model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+        // check attached properties
+        matchItemsAndIndexes(gridview->property("targetTrans_items").toMap(), model, targetIndexes);
+        matchIndexLists(gridview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+        matchItemLists(gridview->property("targetTrans_targetItems").toList(), targetItems);
+        if (expectedDisplacedIndexes.isValid()) {
+            // adjust expectedDisplacedIndexes to their final values after the move
+            QList<int> displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount);
+            matchItemsAndIndexes(gridview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+            matchIndexLists(gridview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+            matchItemLists(gridview->property("displacedTrans_targetItems").toList(), targetItems);
+        }
+    } else {
+        QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0);
+        QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
+    }
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    for (int i=0; i<items.count(); i++) {
+        if (items[i]->y() >= contentY) {
+            QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+            firstVisibleIndex = e.evaluate().toInt();
+            break;
+        }
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), (i/3)*60.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QCOMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+}
+
+void tst_QQuickGridView::addTransitions_data()
+{
+    QTest::addColumn<int>("initialItemCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<bool>("shouldAnimateTargets");
+    QTest::addColumn<int>("insertionIndex");
+    QTest::addColumn<int>("insertionCount");
+    QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+    // if inserting a full row before visible index, items don't appear or animate in, even if there are > 1 new items
+    QTest::newRow("insert 1, just before start")
+            << 30 << 20.0 << false
+            << 0 << 1 << ListRange();
+    QTest::newRow("insert 1, way before start")
+            << 30 << 20.0 << false
+            << 0 << 1 << ListRange();
+    QTest::newRow("insert multiple, just before start")
+            << 30 << 100.0 << false
+            << 0 << 3 << ListRange();
+    QTest::newRow("insert multiple (< 1 row), just before start")
+            << 30 << 100.0 << false
+            << 0 << 2 << ListRange();
+    QTest::newRow("insert multiple, way before start")
+            << 30 << 100.0 << false
+            << 0 << 3 << ListRange();
+
+    QTest::newRow("insert 1 at start")
+            << 30 << 0.0 << true
+            << 0 << 1 << ListRange(0, 17);
+    QTest::newRow("insert multiple at start")
+            << 30 << 0.0 << true
+            << 0 << 3 << ListRange(0, 17);
+    QTest::newRow("insert multiple (> 1 row) at start")
+            << 30 << 0.0 << true
+            << 0 << 5 << ListRange(0, 17);
+    QTest::newRow("insert 1 at start, content y not 0")
+            << 30 << 60.0 << true  // first visible is index 3
+            << 3 << 1 << ListRange(0 + 3, 17 + 3);
+    QTest::newRow("insert multiple at start, content y not 0")
+            << 30 << 60.0 << true    // first visible is index 3
+            << 3 << 3 << ListRange(0 + 3, 17 + 3);
+    QTest::newRow("insert multiple (> 1 row) at start, content y not 0")
+            << 30 << 60.0 << true    // first visible is index 3
+            << 3 << 5 << ListRange(0 + 3, 17 + 3);
+
+    QTest::newRow("insert 1 at start, to empty grid")
+            << 0 << 0.0 << true
+            << 0 << 1 << ListRange();
+    QTest::newRow("insert multiple at start, to empty grid")
+            << 0 << 0.0 << true
+            << 0 << 3 << ListRange();
+
+    QTest::newRow("insert 1 at middle")
+            << 30 << 0.0 << true
+            << 7 << 1 << ListRange(7, 17);
+    QTest::newRow("insert multiple at middle")
+            << 30 << 0.0 << true
+            << 7 << 3 << ListRange(7, 17);
+    QTest::newRow("insert multiple (> 1 row) at middle")
+            << 30 << 0.0 << true
+            << 7 << 5 << ListRange(7, 17);
+
+    QTest::newRow("insert 1 at bottom")
+            << 30 << 0.0 << true
+            << 17 << 1 << ListRange(17, 17);
+    QTest::newRow("insert multiple at bottom")
+            << 30 << 0.0 << true
+            << 17 << 3 << ListRange(17, 17);
+    QTest::newRow("insert 1 at bottom, content y not 0")
+            << 30 << 20.0 * 3 << true
+            << 17 + 3 << 1 << ListRange(17 + 3, 17 + 3);
+    QTest::newRow("insert multiple at bottom, content y not 0")
+            << 30 << 20.0 * 3 << true
+            << 17 + 3 << 3 << ListRange(17 + 3, 17 + 3);
+
+
+    // items added after the last visible will not be animated in, since they
+    // do not appear in the final view
+    QTest::newRow("insert 1 after end")
+            << 30 << 0.0 << false
+            << 18 << 1 << ListRange();
+    QTest::newRow("insert multiple after end")
+            << 30 << 0.0 << false
+            << 18 << 3 << ListRange();
+}
+
+void tst_QQuickGridView::moveTransitions()
+{
+    QFETCH(int, initialItemCount);
+    QFETCH(qreal, contentY);
+    QFETCH(qreal, itemsOffsetAfterMove);
+    QFETCH(int, moveFrom);
+    QFETCH(int, moveTo);
+    QFETCH(int, moveCount);
+    QFETCH(ListRange, expectedDisplacedIndexes);
+
+    // target and displaced items should pass through these points
+    QPointF targetItems_transitionVia(-50, 50);
+    QPointF displacedItems_transitionVia(100, 100);
+
+    QaimModel model;
+    for (int i = 0; i < initialItemCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+    QaimModel model_targetItems_transitionVia;
+    QaimModel model_displacedItems_transitionVia;
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia);
+    ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+    ctxt->setContextProperty("targetItems_transitionVia", targetItems_transitionVia);
+    ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+    canvas->setSource(testFileUrl("moveTransitions.qml"));
+    canvas->show();
+
+    QQuickGridView *gridview = findItem<QQuickGridView>(canvas->rootObject(), "grid");
+    QTRY_VERIFY(gridview != 0);
+    QQuickItem *contentItem = gridview->contentItem();
+    QVERIFY(contentItem != 0);
+    QQuickText *name;
+
+    if (contentY != 0) {
+        gridview->setContentY(contentY);
+        QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+    }
+
+    QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+    // Items moving to *or* from visible positions should be animated.
+    // Otherwise, they should not be animated.
+    QList<QPair<QString, QString> > expectedTargetData;
+    QList<int> targetIndexes;
+    for (int i=moveFrom; i<moveFrom+moveCount; i++) {
+        int toIndex = moveTo + (i - moveFrom);
+        int firstVisibleIndex = (contentY / 60) * 3;
+        int lastVisibleIndex = (qCeil((contentY + gridview->height()) / 60.0)*3) - 1;
+        if ((i >= firstVisibleIndex && i <= lastVisibleIndex)
+                || (toIndex >= firstVisibleIndex && toIndex <= lastVisibleIndex)) {
+            expectedTargetData << qMakePair(model.name(i), model.number(i));
+            targetIndexes << i;
+        }
+    }
+    // ViewTransition.index provides the indices that items are moving to, not from
+    targetIndexes = adjustIndexesForMove(targetIndexes, moveFrom, moveTo, moveCount);
+
+    // start animation
+    model.moveItems(moveFrom, moveTo, moveCount);
+
+    QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+    QTRY_COMPARE(gridview->property("displaceTransitionsDone").toInt(),
+                 expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+    QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+
+    // check the target and displaced items were animated
+    model_targetItems_transitionVia.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+    model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+    // check attached properties
+    matchItemsAndIndexes(gridview->property("targetTrans_items").toMap(), model, targetIndexes);
+    matchIndexLists(gridview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+    matchItemLists(gridview->property("targetTrans_targetItems").toList(), targetItems);
+    if (expectedDisplacedIndexes.isValid()) {
+        // adjust expectedDisplacedIndexes to their final values after the move
+        QList<int> displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, moveFrom, moveTo, moveCount);
+        matchItemsAndIndexes(gridview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+        matchIndexLists(gridview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+        matchItemLists(gridview->property("displacedTrans_targetItems").toList(), targetItems);
+    }
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    for (int i=0; i<items.count(); i++) {
+        if (items[i]->y() >= contentY) {
+            QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+            firstVisibleIndex = e.evaluate().toInt();
+            break;
+        }
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), (i/3)*60.0 + itemsOffsetAfterMove);
+        name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+}
+
+void tst_QQuickGridView::moveTransitions_data()
+{
+    QTest::addColumn<int>("initialItemCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<qreal>("itemsOffsetAfterMove");
+    QTest::addColumn<int>("moveFrom");
+    QTest::addColumn<int>("moveTo");
+    QTest::addColumn<int>("moveCount");
+    QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+    QTest::newRow("move from above view, outside visible items, move 1") << 30 << 120.0 << 0.0
+            << 1 << 10 << 1 << ListRange(6, 10);
+    QTest::newRow("move from above view, outside visible items, move 1 (first item)") << 30 << 120.0 << 0.0
+            << 0 << 10 << 1 << ListRange(6, 10);
+    QTest::newRow("move from above view, outside visible items, move multiple") << 30 << 120.0 << 60.0
+            << 1 << 10 << 3 << ListRange(13, 23);
+    QTest::newRow("move from above view, mix of visible/non-visible") << 30 << 120.0 << 60.0
+            << 1 << 10 << 6 << (ListRange(7, 15) + ListRange(16, 23));
+    QTest::newRow("move from above view, mix of visible/non-visible (move first)") << 30 << 120.0 << 120.0
+            << 0 << 10 << 6 << ListRange(16, 23);
+
+    QTest::newRow("move within view, move 1 down") << 30 << 0.0 << 0.0
+            << 1 << 10 << 1 << ListRange(2, 10);
+    QTest::newRow("move within view, move 1 down, move first item") << 30 << 0.0 << 0.0
+            << 0 << 10 << 1 << ListRange(1, 10);
+    QTest::newRow("move within view, move 1 down, move first item, contentY not 0") << 30 << 120.0 << 0.0
+            << 0+6 << 10+6 << 1 << ListRange(1+6, 10+6);
+    QTest::newRow("move within view, move 1 down, to last item") << 30 << 0.0 << 0.0
+            << 10 << 17 << 1 << ListRange(11, 17);
+    QTest::newRow("move within view, move first->last") << 30 << 0.0 << 0.0
+            << 0 << 17 << 1 << ListRange(1, 17);
+
+    QTest::newRow("move within view, move multiple down") << 30 << 0.0 << 0.0
+            << 1 << 10 << 3 << ListRange(4, 12);
+    QTest::newRow("move within view, move multiple down, move first item") << 30 << 0.0 << 0.0
+            << 0 << 10 << 3 << ListRange(3, 12);
+    QTest::newRow("move within view, move multiple down, move first item, contentY not 0") << 30 << 60.0 << 0.0
+            << 0+3 << 10+3 << 3 << ListRange(3+3, 12+3);
+    QTest::newRow("move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0
+            << 5 << 15 << 3 << ListRange(8, 17);
+    QTest::newRow("move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0
+            << 0 << 15 << 3 << ListRange(3, 17);
+
+    QTest::newRow("move within view, move 1 up") << 30 << 0.0 << 0.0
+            << 10 << 1 << 1 << ListRange(1, 9);
+    QTest::newRow("move within view, move 1 up, move to first index") << 30 << 0.0 << 0.0
+            << 10 << 0 << 1 << ListRange(0, 9);
+    QTest::newRow("move within view, move 1 up, move to first index, contentY not 0") << 30 << 120.0 << 0.0
+            << 10+6 << 0+6 << 1 << ListRange(0+6, 9+6);
+    QTest::newRow("move within view, move 1 up, move to first index, contentY not on item border") << 30 << 80.0 << 0.0
+            << 10+3 << 0+3 << 1 << ListRange(0+3, 9+3);
+    QTest::newRow("move within view, move 1 up, move last item") << 30 << 0.0 << 0.0
+            << 17 << 10 << 1 << ListRange(10, 16);
+    QTest::newRow("move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0
+            << 17 << 0 << 1 << ListRange(0, 16);
+
+    QTest::newRow("move within view, move multiple up") << 30 << 0.0 << 0.0
+            << 10 << 1 << 3 << ListRange(1, 9);
+    QTest::newRow("move within view, move multiple (> 1 row) up") << 30 << 0.0 << 0.0
+            << 10 << 1 << 5 << ListRange(1, 9);
+    QTest::newRow("move within view, move multiple up, move to first index") << 30 << 0.0 << 0.0
+            << 10 << 0 << 3 << ListRange(0, 9);
+    QTest::newRow("move within view, move multiple up, move to first index, contentY not 0") << 30 << 60.0 << 0.0
+            << 10+3 << 0+3 << 3 << ListRange(0+3, 9+3);
+    QTest::newRow("move within view, move multiple up (> 1 row), move to first index, contentY not on border") << 30 << 80.0 << 0.0
+            << 10+3 << 0+3 << 5 << ListRange(0+3, 9+3);
+    QTest::newRow("move within view, move multiple up, move last item") << 30 << 0.0 << 0.0
+            << 15 << 5 << 3 << ListRange(5, 14);
+    QTest::newRow("move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0
+            << 15 << 0 << 3 << ListRange(0, 14);
+
+    QTest::newRow("move from below view, move 1 up") << 30 << 0.0 << 0.0
+            << 20 << 5 << 1 << ListRange(5, 17);
+    QTest::newRow("move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0
+            << 20 << 0 << 1 << ListRange(0, 17);
+    QTest::newRow("move from below view, move 1 up, move to top, contentY not 0") << 30 << 60.0 << 0.0
+            << 25 << 3 << 1 << ListRange(0+3, 17+3);
+    QTest::newRow("move from below view, move multiple (> 1 row) up") << 30 << 0.0 << 0.0
+            << 20 << 5 << 5 << ListRange(5, 17);
+    QTest::newRow("move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0
+            << 20 << 0 << 3 << ListRange(0, 17);
+    QTest::newRow("move from below view, move multiple up, move to top, contentY not 0") << 30 << 60.0 << 0.0
+            << 25 << 3 << 3 << ListRange(0+3, 17+3);
+
+    QTest::newRow("move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0
+            << 20 << 17 << 1 << ListRange(17, 17);
+    QTest::newRow("move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 60.0 << 0.0
+            << 25 << 17+3 << 1 << ListRange(17+3, 17+3);
+    QTest::newRow("move from below view, move multiple up, move to to bottom") << 30 << 0.0 << 0.0
+            << 20 << 17 << 3 << ListRange(17, 17);
+    QTest::newRow("move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 60.0 << 0.0
+            << 25 << 17+3 << 3 << ListRange(17+3, 17+3);
+}
+
+void tst_QQuickGridView::removeTransitions()
+{
+    QFETCH(int, initialItemCount);
+    QFETCH(bool, shouldAnimateTargets);
+    QFETCH(qreal, contentY);
+    QFETCH(int, removalIndex);
+    QFETCH(int, removalCount);
+    QFETCH(ListRange, expectedDisplacedIndexes);
+
+    // added items should end here
+    QPointF targetItems_transitionTo(-50, -50);
+
+    // displaced items should pass through this points
+    QPointF displacedItems_transitionVia(100, 100);
+
+    QaimModel model;
+    for (int i = 0; i < initialItemCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+    QaimModel model_targetItems_transitionTo;
+    QaimModel model_displacedItems_transitionVia;
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo);
+    ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+    ctxt->setContextProperty("targetItems_transitionTo", targetItems_transitionTo);
+    ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+    canvas->setSource(testFileUrl("removeTransitions.qml"));
+    canvas->show();
+
+    QQuickGridView *gridview = findItem<QQuickGridView>(canvas->rootObject(), "grid");
+    QTRY_VERIFY(gridview != 0);
+    QQuickItem *contentItem = gridview->contentItem();
+    QVERIFY(contentItem != 0);
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+
+    if (contentY != 0) {
+        gridview->setContentY(contentY);
+        QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+    }
+
+    QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+    // only target items that are visible should be animated
+    QList<QPair<QString, QString> > expectedTargetData;
+    QList<int> targetIndexes;
+    if (shouldAnimateTargets) {
+        for (int i=removalIndex; i<removalIndex+removalCount; i++) {
+            int firstVisibleIndex = (contentY / 60.0)*3;
+            int lastVisibleIndex = (qCeil((contentY + gridview->height()) / 60.0)*3) - 1;
+            if (i >= firstVisibleIndex && i <= lastVisibleIndex) {
+                expectedTargetData << qMakePair(model.name(i), model.number(i));
+                targetIndexes << i;
+            }
+        }
+        QVERIFY(expectedTargetData.count() > 0);
+    }
+
+    // calculate targetItems and expectedTargets before model changes
+    QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+    QVariantMap expectedTargets;
+    for (int i=0; i<targetIndexes.count(); i++)
+        expectedTargets[model.name(targetIndexes[i])] = targetIndexes[i];
+
+    // start animation
+    model.removeItems(removalIndex, removalCount);
+    QTRY_COMPARE(model.count(), gridview->count());
+
+    if (shouldAnimateTargets || expectedDisplacedIndexes.isValid()) {
+        QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+        QTRY_COMPARE(gridview->property("displaceTransitionsDone").toInt(),
+                     expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+        // check the target and displaced items were animated
+        model_targetItems_transitionTo.matchAgainst(expectedTargetData, "wasn't animated to target 'to' pos", "shouldn't have been animated to target 'to' pos");
+        model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+        // check attached properties
+        QCOMPARE(gridview->property("targetTrans_items").toMap(), expectedTargets);
+        matchIndexLists(gridview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+        matchItemLists(gridview->property("targetTrans_targetItems").toList(), targetItems);
+        if (expectedDisplacedIndexes.isValid()) {
+            // adjust expectedDisplacedIndexes to their final values after the move
+            QList<int> displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, removalIndex, removalCount);
+            matchItemsAndIndexes(gridview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+            matchIndexLists(gridview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+            matchItemLists(gridview->property("displacedTrans_targetItems").toList(), targetItems);
+        }
+    } else {
+        QTRY_COMPARE(model_targetItems_transitionTo.count(), 0);
+        QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
+    }
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int itemCount = items.count();
+    int firstVisibleIndex = -1;
+    for (int i=0; i<items.count(); i++) {
+        QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+        int index = e.evaluate().toInt();
+        if (firstVisibleIndex < 0 && items[i]->y() >= contentY)
+            firstVisibleIndex = index;
+        else if (index < 0)
+            itemCount--;    // exclude deleted items
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), contentY + ((i-firstVisibleIndex)/3) * 60.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+}
+
+void tst_QQuickGridView::removeTransitions_data()
+{
+    QTest::addColumn<int>("initialItemCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<bool>("shouldAnimateTargets");
+    QTest::addColumn<int>("removalIndex");
+    QTest::addColumn<int>("removalCount");
+    QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+    // All items that are visible following the remove operation should be animated.
+    // Remove targets that are outside of the view should not be animated.
+
+    // For a GridView, removing any number of items other than a full row before the start
+    // should displace all items in the view
+    QTest::newRow("remove 1 before start")
+            << 30 << 120.0 << false
+            << 2 << 1 << ListRange(6, 24);    // 6-24 are displaced
+    QTest::newRow("remove 1 row, before start")
+            << 30 << 120.0 << false
+            << 3 << 3 << ListRange();
+    QTest::newRow("remove between 1-2 rows, before start")
+            << 30 << 120.0 << false
+            << 0 << 5 << ListRange(6, 25);
+    QTest::newRow("remove 2 rows, before start")
+            << 30 << 120.0 << false
+            << 0 << 6 << ListRange();
+    QTest::newRow("remove mix of before and after start")
+            << 30 << 60.0 << true
+            << 2 << 3 << ListRange(5, 23);  // 5-23 are displaced into view
+
+
+    QTest::newRow("remove 1 from start")
+            << 30 << 0.0 << true
+            << 0 << 1 << ListRange(1, 18);  // 1-18 are displaced into view
+    QTest::newRow("remove multiple from start")
+            << 30 << 0.0 << true
+            << 0 << 3 << ListRange(3, 20);  // 3-18 are displaced into view
+    QTest::newRow("remove 1 from start, content y not 0")
+            << 30 << 60.0 << true
+            << 3 << 1 << ListRange(1 + 3, 18 + 3);
+    QTest::newRow("remove multiple from start, content y not 0")
+            << 30 << 60.0 << true
+            << 3 << 3 << ListRange(3 + 3, 20 + 3);
+
+
+    QTest::newRow("remove 1 from middle")
+            << 30 << 0.0 << true
+            << 5 << 1 << ListRange(6, 18);
+    QTest::newRow("remove multiple from middle")
+            << 30 << 0.0 << true
+            << 5 << 3 << ListRange(8, 20);
+
+
+    QTest::newRow("remove 1 from bottom")
+            << 30 << 0.0 << true
+            << 17 << 1 << ListRange(18, 18);
+    QTest::newRow("remove multiple (1 row) from bottom")
+            << 30 << 0.0 << true
+            << 15 << 3 << ListRange(18, 20);
+    QTest::newRow("remove multiple (> 1 row) from bottom")
+            << 30 << 0.0 << true
+            << 15 << 5 << ListRange(20, 22);
+    QTest::newRow("remove 1 from bottom, content y not 0")
+            << 30 << 60.0 << true
+            << 17 + 3 << 1 << ListRange(18 + 3, 18 + 3);
+    QTest::newRow("remove multiple (1 row) from bottom, content y not 0")
+            << 30 << 60.0 << true
+            << 15 + 3 << 3 << ListRange(18 + 3, 20 + 3);
+
+
+    QTest::newRow("remove 1 after end")
+            << 30 << 0.0 << false
+            << 18 << 1 << ListRange();
+    QTest::newRow("remove multiple after end")
+            << 30 << 0.0 << false
+            << 18 << 3 << ListRange();
+}
+
+void tst_QQuickGridView::multipleTransitions()
+{
+    // Tests that if you interrupt a transition in progress with another action that
+    // cancels the previous transition, the resulting items are still placed correctly.
+
+    QFETCH(int, initialCount);
+    QFETCH(qreal, contentY);
+    QFETCH(QList<ListChange>, changes);
+
+    // add transitions on the left, moves on the right
+    QPointF addTargets_transitionFrom(-50, -50);
+    QPointF addDisplaced_transitionFrom(-50, 50);
+    QPointF moveTargets_transitionFrom(50, -50);
+    QPointF moveDisplaced_transitionFrom(50, 50);
+
+    QmlListModel model;
+    for (int i = 0; i < initialCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom);
+    ctxt->setContextProperty("addDisplaced_transitionFrom", addDisplaced_transitionFrom);
+    ctxt->setContextProperty("moveTargets_transitionFrom", moveTargets_transitionFrom);
+    ctxt->setContextProperty("moveDisplaced_transitionFrom", moveDisplaced_transitionFrom);
+    canvas->setSource(testFileUrl("multipleTransitions.qml"));
+    canvas->show();
+
+    QQuickGridView *gridview = findItem<QQuickGridView>(canvas->rootObject(), "grid");
+    QTRY_VERIFY(gridview != 0);
+    QQuickItem *contentItem = gridview->contentItem();
+    QVERIFY(contentItem != 0);
+    QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+
+    int timeBetweenActions = canvas->rootObject()->property("timeBetweenActions").toInt();
+
+    QList<QPair<QString, QString> > targetItems;
+    for (int i=0; i<changes.count(); i++) {
+        switch (changes[i].type) {
+            case ListChange::Inserted:
+            {
+                for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+                    targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
+                model.insertItems(changes[i].index, targetItems);
+                QTRY_COMPARE(model.count(), gridview->count());
+                QTRY_VERIFY(gridview->property("runningAddTargets").toBool());
+                QTRY_VERIFY(gridview->property("runningAddDisplaced").toBool());
+                if (i == changes.count() - 1) {
+                    QTRY_VERIFY(!gridview->property("runningAddTargets").toBool());
+                    QTRY_VERIFY(!gridview->property("runningAddDisplaced").toBool());
+                } else {
+                    QTest::qWait(timeBetweenActions);
+                }
+                break;
+            }
+            case ListChange::Removed:
+                for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+                    targetItems << qMakePair(model.name(i), model.number(i));
+                model.removeItems(changes[i].index, changes[i].count);
+                QTRY_COMPARE(model.count(), gridview->count());
+                QTRY_VERIFY(gridview->property("runningRemoveTargets").toBool());
+                QTRY_VERIFY(gridview->property("runningRemoveDisplaced").toBool());
+                if (i == changes.count() - 1) {
+                    QTRY_VERIFY(!gridview->property("runningRemoveTargets").toBool());
+                    QTRY_VERIFY(!gridview->property("runningRemoveDisplaced").toBool());
+                } else {
+                    QTest::qWait(timeBetweenActions);
+                }
+                break;
+            case ListChange::Moved:
+                for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+                    targetItems << qMakePair(model.name(i), model.number(i));
+                model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+                QTRY_VERIFY(gridview->property("runningMoveTargets").toBool());
+                QTRY_VERIFY(gridview->property("runningMoveDisplaced").toBool());
+                if (i == changes.count() - 1) {
+                    QTRY_VERIFY(!gridview->property("runningMoveTargets").toBool());
+                    QTRY_VERIFY(!gridview->property("runningMoveDisplaced").toBool());
+                } else {
+                    QTest::qWait(timeBetweenActions);
+                }
+                break;
+            case ListChange::SetCurrent:
+                gridview->setCurrentIndex(changes[i].index);
+                QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                break;
+            case ListChange::SetContentY:
+                gridview->setContentY(changes[i].pos);
+                QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                break;
+        }
+    }
+    QCOMPARE(gridview->count(), model.count());
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    for (int i=0; i<items.count(); i++) {
+        if (items[i]->y() >= contentY) {
+            QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+            firstVisibleIndex = e.evaluate().toInt();
+            break;
+        }
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), (i%3)*80.0);
+        QCOMPARE(item->y(), (i/3)*60.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+}
+
+void tst_QQuickGridView::multipleTransitions_data()
+{
+    QTest::addColumn<int>("initialCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<QList<ListChange> >("changes");
+
+    // the added item and displaced items should move to final dest correctly
+    QTest::newRow("add item, then move it immediately") << 10 << 0.0 << (QList<ListChange>()
+            << ListChange::insert(0, 1)
+            << ListChange::move(0, 3, 1)
+            );
+
+    // items affected by the add should change from move to add transition
+    QTest::newRow("move, then insert item before the moved item") << 20 << 0.0 << (QList<ListChange>()
+            << ListChange::move(1, 10, 3)
+            << ListChange::insert(0, 1)
+            );
+
+    // items should be placed correctly if you trigger a transition then refill for that index
+    QTest::newRow("add at 0, flick down, flick back to top and add at 0 again") << 20 << 0.0 << (QList<ListChange>()
+            << ListChange::insert(0, 1)
+            << ListChange::setContentY(160.0)
+            << ListChange::setContentY(0.0)
+            << ListChange::insert(0, 1)
+            );
+}
+
 void tst_QQuickGridView::cacheBuffer()
 {
     QQuickView *canvas = createView();
@@ -4085,6 +5006,56 @@ void tst_QQuickGridView::unrequestedVisibility()
     delete canvas;
 }
 
+QList<int> tst_QQuickGridView::toIntList(const QVariantList &list)
+{
+    QList<int> ret;
+    bool ok = true;
+    for (int i=0; i<list.count(); i++) {
+        ret << list[i].toInt(&ok);
+        if (!ok)
+            qWarning() << "tst_QQuickGridView::toIntList(): not a number:" << list[i];
+    }
+
+    return ret;
+}
+
+void tst_QQuickGridView::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
+{
+    for (int i=0; i<indexLists.count(); i++) {
+        QSet<int> current = indexLists[i].value<QList<int> >().toSet();
+        if (current != expectedIndexes.toSet())
+            qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
+        QCOMPARE(current, expectedIndexes.toSet());
+    }
+}
+
+void tst_QQuickGridView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
+{
+    for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
+        QVERIFY(it.value().type() == QVariant::Int);
+        QString name = it.key();
+        int itemIndex = it.value().toInt();
+        QVERIFY2(expectedIndexes.contains(itemIndex), QTest::toString(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
+        if (model.name(itemIndex) != name)
+            qDebug() << itemIndex;
+        QCOMPARE(model.name(itemIndex), name);
+    }
+    QCOMPARE(items.count(), expectedIndexes.count());
+}
+
+void tst_QQuickGridView::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
+{
+    for (int i=0; i<itemLists.count(); i++) {
+        QVariantList current = itemLists[i].toList();
+        for (int j=0; j<current.count(); j++) {
+            QQuickItem *o = qobject_cast<QQuickItem*>(current[j].value<QObject*>());
+            QVERIFY2(o, QTest::toString(QString("Invalid actual item at %1").arg(j)));
+            QVERIFY2(expectedItems.contains(o), QTest::toString(QString("Cannot match item %1").arg(j)));
+        }
+        QCOMPARE(current.count(), expectedItems.count());
+    }
+}
+
 QTEST_MAIN(tst_QQuickGridView)
 
 #include "tst_qquickgridview.moc"
diff --git a/tests/auto/qtquick2/qquicklistview/data/addTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/addTransitions.qml
new file mode 100644 (file)
index 0000000..ff90ead
--- /dev/null
@@ -0,0 +1,134 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: list.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+
+            property string nameData: name
+
+            objectName: "wrapper"
+            height: 20
+            width: 240
+            Text { text: index }
+            Text {
+                x: 30
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                x: 200
+                text: wrapper.y
+            }
+            color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == targetItems_transitionFrom)
+                    model_targetItems_transitionFrom.addItem(name, "")
+                if (Qt.point(x, y) == displacedItems_transitionVia)
+                    model_displacedItems_transitionVia.addItem(name, "")
+            }
+        }
+    }
+
+    ListView {
+        id: list
+
+        property int targetTransitionsDone
+        property int displaceTransitionsDone
+
+        property var targetTrans_items: new Object()
+        property var targetTrans_targetIndexes: new Array()
+        property var targetTrans_targetItems: new Array()
+
+        property var displacedTrans_items: new Object()
+        property var displacedTrans_targetIndexes: new Array()
+        property var displacedTrans_targetItems: new Array()
+
+        objectName: "list"
+        focus: true
+        anchors.centerIn: parent
+        width: 240
+        height: 320
+        model: testModel
+        delegate: myDelegate
+
+        // for QDeclarativeListProperty types
+        function copyList(propList) {
+            var temp = new Array()
+            for (var i=0; i<propList.length; i++)
+                temp.push(propList[i])
+            return temp
+        }
+
+        add: Transition {
+            id: targetTransition
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        list.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+                        list.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+                        list.targetTrans_targetItems.push(list.copyList(targetTransition.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: targetItems_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: targetItems_transitionFrom.y; duration: root.duration }
+                }
+
+                ScriptAction { script: list.targetTransitionsDone += 1 }
+            }
+        }
+
+        addDisplaced: Transition {
+            id: displaced
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        list.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+                        list.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+                        list.displacedTrans_targetItems.push(list.copyList(displaced.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; duration: root.duration; to: displacedItems_transitionVia.x }
+                    NumberAnimation { properties: "y"; duration: root.duration; to: displacedItems_transitionVia.y }
+                }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: list.displaceTransitionsDone += 1 }
+            }
+
+        }
+    }
+
+    Rectangle {
+        anchors.fill: list
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    // XXX will it pass without these if I just wait for polish?
+    // check all of these tests - if not, then mark this bit with the bug number!
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+    }
+}
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml
new file mode 100644 (file)
index 0000000..744db31
--- /dev/null
@@ -0,0 +1,141 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: list.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+
+            property string nameData: name
+
+            objectName: "wrapper"
+            height: 20
+            width: 240
+            Text { text: index }
+            Text {
+                x: 30
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                x: 200
+                text: wrapper.y
+            }
+            color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == targetItems_transitionVia)
+                    model_targetItems_transitionVia.addItem(name, "")
+                if (Qt.point(x, y) == displacedItems_transitionVia)
+                    model_displacedItems_transitionVia.addItem(name, "")
+            }
+        }
+    }
+
+    ListView {
+        id: list
+
+        property int targetTransitionsDone
+        property int displaceTransitionsDone
+
+        property var targetTrans_items: new Object()
+        property var targetTrans_targetIndexes: new Array()
+        property var targetTrans_targetItems: new Array()
+
+        property var displacedTrans_items: new Object()
+        property var displacedTrans_targetIndexes: new Array()
+        property var displacedTrans_targetItems: new Array()
+
+        objectName: "list"
+        focus: true
+        anchors.centerIn: parent
+        width: 240
+        height: 320
+        model: testModel
+        delegate: myDelegate
+
+        // for QDeclarativeListProperty types
+        function copyList(propList) {
+            var temp = new Array()
+            for (var i=0; i<propList.length; i++)
+                temp.push(propList[i])
+            return temp
+        }
+
+        move: Transition {
+            id: targetTransition
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        list.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+                        list.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+                        list.targetTrans_targetItems.push(list.copyList(targetTransition.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; to: targetItems_transitionVia.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; to: targetItems_transitionVia.y; duration: root.duration }
+                }
+
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: list.targetTransitionsDone += 1 }
+            }
+        }
+
+        moveDisplaced: Transition {
+            id: displaced
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        list.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+                        list.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+                        list.displacedTrans_targetItems.push(list.copyList(displaced.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation {
+                        properties: "x"; duration: root.duration
+                        to: displacedItems_transitionVia.x
+                    }
+                    NumberAnimation {
+                        properties: "y"; duration: root.duration
+                        to: displacedItems_transitionVia.y
+                    }
+                }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: list.displaceTransitionsDone += 1 }
+            }
+
+        }
+    }
+
+    Rectangle {
+        anchors.fill: list
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 10000 }
+    }
+}
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml
new file mode 100644 (file)
index 0000000..50ffbc5
--- /dev/null
@@ -0,0 +1,121 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    // time to pause between each add, remove, etc.
+    // (obviously, must be less than 'duration' value to actually test that
+    // interrupting transitions will still produce the correct result)
+    property int timeBetweenActions: duration / 2
+
+    property int duration: 100
+
+    property int count: list.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+            objectName: "wrapper"
+            height: 20
+            width: 240
+            Text { text: index }
+            Text {
+                x: 30
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                x: 200
+                text: wrapper.y
+            }
+            color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+        }
+    }
+
+    ListView {
+        id: list
+
+        property bool populateDone
+
+        property bool runningAddTargets: false
+        property bool runningAddDisplaced: false
+        property bool runningMoveTargets: false
+        property bool runningMoveDisplaced: false
+
+        objectName: "list"
+        focus: true
+        anchors.centerIn: parent
+        width: 240
+        height: 320
+        model: testModel
+        delegate: myDelegate
+
+        add: Transition {
+            id: addTargets
+            SequentialAnimation {
+                ScriptAction { script: list.runningAddTargets = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: addTargets_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: addTargets_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: list.runningAddTargets = false }
+            }
+        }
+
+        addDisplaced: Transition {
+            id: addDisplaced
+            SequentialAnimation {
+                ScriptAction { script: list.runningAddDisplaced = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: addDisplaced_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: addDisplaced_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: list.runningAddDisplaced = false }
+            }
+        }
+
+        move: Transition {
+            id: moveTargets
+            SequentialAnimation {
+                ScriptAction { script: list.runningMoveTargets = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: moveTargets_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: moveTargets_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: list.runningMoveTargets = false }
+            }
+        }
+
+        moveDisplaced: Transition {
+            id: moveDisplaced
+            SequentialAnimation {
+                ScriptAction { script: list.runningMoveDisplaced = true }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; from: moveDisplaced_transitionFrom.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; from: moveDisplaced_transitionFrom.y; duration: root.duration }
+                }
+                ScriptAction { script: list.runningMoveDisplaced = false }
+            }
+        }
+    }
+
+    Rectangle {
+        anchors.fill: list
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+    }
+}
+
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml
new file mode 100644 (file)
index 0000000..0994e09
--- /dev/null
@@ -0,0 +1,102 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: list.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+            objectName: "wrapper"
+            height: 20
+            width: 240
+            Text { text: index }
+            Text {
+                x: 30
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                x: 200
+                text: wrapper.y
+            }
+            color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == transitionFrom)
+                    model_transitionFrom.addItem(name, "")
+                if (Qt.point(x, y) == transitionVia) {
+                    model_transitionVia.addItem(name, "")
+                }
+            }
+        }
+    }
+
+    ListView {
+        id: list
+
+        property int countPopulateTransitions
+        property int countAddTransitions
+
+        objectName: "list"
+        focus: true
+        anchors.centerIn: parent
+        width: 240
+        height: 320
+        model: testModel
+        delegate: myDelegate
+
+        populate: usePopulateTransition ? popTransition : null
+
+        add: Transition {
+            SequentialAnimation {
+                ScriptAction { script: list.countAddTransitions += 1 }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+            }
+        }
+    }
+
+    Transition {
+        id: popTransition
+        SequentialAnimation {
+            ParallelAnimation {
+                NumberAnimation { properties: "x"; from: transitionFrom.x; to: transitionVia.x; duration: root.duration }
+                NumberAnimation { properties: "y"; from: transitionFrom.y; to: transitionVia.y; duration: root.duration }
+            }
+            NumberAnimation { properties: "x,y"; duration: root.duration }
+            ScriptAction { script: list.countPopulateTransitions += 1 }
+        }
+    }
+
+
+    Rectangle {
+        anchors.fill: list
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Component.onCompleted: {
+        if (dynamicallyPopulate) {
+            for (var i=0; i<30; i++)
+                testModel.addItem("item " + i, "")
+        }
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+    }
+}
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml
new file mode 100644 (file)
index 0000000..95f76f0
--- /dev/null
@@ -0,0 +1,144 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    width: 500
+    height: 600
+
+    property int duration: 10
+    property int count: list.count
+
+    Component {
+        id: myDelegate
+        Rectangle {
+            id: wrapper
+
+            property string nameData: name
+
+            objectName: "wrapper"
+            height: 20
+            width: 240
+            Text { text: index }
+            Text {
+                x: 30
+                id: textName
+                objectName: "textName"
+                text: name
+            }
+            Text {
+                x: 200
+                text: wrapper.y
+            }
+            color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+            onXChanged: checkPos()
+            onYChanged: checkPos()
+
+            function checkPos() {
+                if (Qt.point(x, y) == targetItems_transitionTo) {
+                    model_targetItems_transitionTo.addItem(nameData, "") // name is invalid once model removes the item
+                }
+                if (Qt.point(x, y) == displacedItems_transitionVia) {
+                    model_displacedItems_transitionVia.addItem(name, "")
+                }
+            }
+        }
+    }
+
+    ListView {
+        id: list
+
+        property int targetTransitionsDone
+        property int displaceTransitionsDone
+
+        property var targetTrans_items: new Object()
+        property var targetTrans_targetIndexes: new Array()
+        property var targetTrans_targetItems: new Array()
+
+        property var displacedTrans_items: new Object()
+        property var displacedTrans_targetIndexes: new Array()
+        property var displacedTrans_targetItems: new Array()
+
+        objectName: "list"
+        focus: true
+        anchors.centerIn: parent
+        width: 240
+        height: 320
+        model: testModel
+        delegate: myDelegate
+
+        // for QDeclarativeListProperty types
+        function copyList(propList) {
+            var temp = new Array()
+            for (var i=0; i<propList.length; i++)
+                temp.push(propList[i])
+            return temp
+        }
+
+        remove: Transition {
+            id: targetTransition
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        list.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+                        list.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+                        list.targetTrans_targetItems.push(list.copyList(targetTransition.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation { properties: "x"; to: targetItems_transitionTo.x; duration: root.duration }
+                    NumberAnimation { properties: "y"; to: targetItems_transitionTo.y; duration: root.duration }
+                }
+                ScriptAction { script: list.targetTransitionsDone += 1 }
+
+                // delay deleting this item so that it stays valid for the tests
+                // (this doesn't delay the test itself)
+                PauseAnimation { duration: 10000 }
+            }
+        }
+
+        removeDisplaced: Transition {
+            id: displaced
+
+            SequentialAnimation {
+                ScriptAction {
+                    script: {
+                        list.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+                        list.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+                        list.displacedTrans_targetItems.push(list.copyList(displaced.ViewTransition.targetItems))
+                    }
+                }
+                ParallelAnimation {
+                    NumberAnimation {
+                        properties: "x"; duration: root.duration
+                        to: displacedItems_transitionVia.x
+                    }
+                    NumberAnimation {
+                        properties: "y"; duration: root.duration
+                        to: displacedItems_transitionVia.y
+                    }
+                }
+                NumberAnimation { properties: "x,y"; duration: root.duration }
+
+                ScriptAction { script: list.displaceTransitionsDone += 1 }
+            }
+
+        }
+    }
+
+    Rectangle {
+        anchors.fill: list
+        color: "lightsteelblue"
+        opacity: 0.2
+    }
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: 20; height: 20
+        color: "white"
+        NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 10000 }
+    }
+}
+
+
index e809f95..393dd09 100644 (file)
@@ -51,7 +51,6 @@
 #include <QtQuick/private/qquicktext_p.h>
 #include <QtQuick/private/qquickvisualitemmodel_p.h>
 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
-#include <QtQuick/private/qdeclarativechangeset_p.h>
 #include "../../shared/util.h"
 #include "../shared/viewtestutil.h"
 #include "../shared/visualtestutil.h"
@@ -171,6 +170,17 @@ private slots:
     void asynchronous();
     void unrequestedVisibility();
 
+    void populateTransitions();
+    void populateTransitions_data();
+    void addTransitions();
+    void addTransitions_data();
+    void moveTransitions();
+    void moveTransitions_data();
+    void removeTransitions();
+    void removeTransitions_data();
+    void multipleTransitions();
+    void multipleTransitions_data();
+
 private:
     template <class T> void items(const QUrl &source, bool forceLayout);
     template <class T> void changed(const QUrl &source, bool forceLayout);
@@ -182,6 +192,11 @@ private:
     template <class T> void clear(const QUrl &source);
     template <class T> void sections(const QUrl &source);
 
+    QList<int> toIntList(const QVariantList &list);
+    void matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes);
+    void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes);
+    void matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems);
+
     void inserted_more_data();
     void removed_more_data();
     void moved_data();
@@ -1356,18 +1371,26 @@ void tst_QQuickListView::multipleChanges()
             {
                 QList<QPair<QString, QString> > items;
                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
-                    items << qMakePair(QString("new item " + j), QString::number(j));
+                    items << qMakePair(QString("new item %1").arg(j), QString::number(j));
                 model.insertItems(changes[i].index, items);
+                QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
                 break;
             }
             case ListChange::Removed:
                 model.removeItems(changes[i].index, changes[i].count);
+                QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
                 break;
             case ListChange::Moved:
                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+                QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
                 break;
             case ListChange::SetCurrent:
                 listview->setCurrentIndex(changes[i].index);
+                QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+                break;
+            case ListChange::SetContentY:
+                listview->setContentY(changes[i].pos);
+                QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
                 break;
         }
     }
@@ -4729,6 +4752,929 @@ void tst_QQuickListView::unrequestedVisibility()
     delete canvas;
 }
 
+void tst_QQuickListView::populateTransitions()
+{
+    QFETCH(bool, staticallyPopulate);
+    QFETCH(bool, dynamicallyPopulate);
+    QFETCH(bool, usePopulateTransition);
+
+    QPointF transitionFrom(-50, -50);
+    QPointF transitionVia(100, 100);
+    QaimModel model_transitionFrom;
+    QaimModel model_transitionVia;
+
+    QaimModel model;
+    if (staticallyPopulate) {
+        for (int i = 0; i < 30; i++)
+            model.addItem("item" + QString::number(i), "");
+    }
+
+    QQuickView *canvas = createView();
+    canvas->rootContext()->setContextProperty("testModel", &model);
+    canvas->rootContext()->setContextProperty("testObject", new TestObject(canvas->rootContext()));
+    canvas->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition);
+    canvas->rootContext()->setContextProperty("dynamicallyPopulate", dynamicallyPopulate);
+    canvas->rootContext()->setContextProperty("transitionFrom", transitionFrom);
+    canvas->rootContext()->setContextProperty("transitionVia", transitionVia);
+    canvas->rootContext()->setContextProperty("model_transitionFrom", &model_transitionFrom);
+    canvas->rootContext()->setContextProperty("model_transitionVia", &model_transitionVia);
+    canvas->setSource(testFileUrl("populateTransitions.qml"));
+    canvas->show();
+
+    QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+    QVERIFY(listview);
+    QQuickItem *contentItem = listview->contentItem();
+    QVERIFY(contentItem);
+
+    if (staticallyPopulate || dynamicallyPopulate) {
+        // check the populate transition is run
+        if (usePopulateTransition) {
+            QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 17);
+        } else {
+            QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+            QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
+        }
+        QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
+    } else {
+        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+    }
+
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    if (usePopulateTransition)
+        QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt());
+    for (int i=0; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QTRY_COMPARE(item->x(), 0.0);
+        QTRY_COMPARE(item->y(), i*20.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    // add an item and check this is done with add trantion, not populate
+    model.insertItem(0, "another item", "");
+    QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 1);
+    QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(),
+                 (usePopulateTransition && (staticallyPopulate || dynamicallyPopulate)) ? 17 : 0);
+
+    // clear the model
+    canvas->rootContext()->setContextProperty("testModel", QVariant());
+    QTRY_COMPARE(listview->count(), 0);
+    QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
+    listview->setProperty("countPopulateTransitions", 0);
+    listview->setProperty("countAddTransitions", 0);
+
+    // set to a valid model and check populate transition is run a second time
+    model.clear();
+    for (int i = 0; i < 30; i++)
+        model.addItem("item" + QString::number(i), "");
+    canvas->rootContext()->setContextProperty("testModel", &model);
+    QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 17 : 0);
+    QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
+
+    itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    if (usePopulateTransition)
+        QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt());
+    for (int i=0; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QTRY_COMPARE(item->x(), 0.0);
+        QTRY_COMPARE(item->y(), i*20.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    // reset model and check populate transition is run again
+    listview->setProperty("countPopulateTransitions", 0);
+    listview->setProperty("countAddTransitions", 0);
+    model.reset();
+    QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 17 : 0);
+    QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
+
+    itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    if (usePopulateTransition)
+        QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt());
+    for (int i=0; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QTRY_COMPARE(item->x(), 0.0);
+        QTRY_COMPARE(item->y(), i*20.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+}
+
+void tst_QQuickListView::populateTransitions_data()
+{
+    QTest::addColumn<bool>("staticallyPopulate");
+    QTest::addColumn<bool>("dynamicallyPopulate");
+    QTest::addColumn<bool>("usePopulateTransition");
+
+    QTest::newRow("static") << true << false << true;
+    QTest::newRow("static, no populate") << true << false << false;
+
+    QTest::newRow("dynamic") << false << true << true;
+    QTest::newRow("dynamic, no populate") << false << true << false;
+
+    QTest::newRow("empty to start with") << false << false << true;
+    QTest::newRow("empty to start with, no populate") << false << false << false;
+}
+
+void tst_QQuickListView::addTransitions()
+{
+    QFETCH(int, initialItemCount);
+    QFETCH(bool, shouldAnimateTargets);
+    QFETCH(qreal, contentY);
+    QFETCH(int, insertionIndex);
+    QFETCH(int, insertionCount);
+    QFETCH(ListRange, expectedDisplacedIndexes);
+
+    // added items should start here
+    QPointF targetItems_transitionFrom(-50, -50);
+
+    // displaced items should pass through this point
+    QPointF displacedItems_transitionVia(100, 100);
+
+    QaimModel model;
+    for (int i = 0; i < initialItemCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+    QaimModel model_targetItems_transitionFrom;
+    QaimModel model_displacedItems_transitionVia;
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    TestObject *testObject = new TestObject;
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
+    ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+    ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
+    ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+    ctxt->setContextProperty("testObject", testObject);
+    canvas->setSource(testFileUrl("addTransitions.qml"));
+    canvas->show();
+
+    QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+    QQuickItem *contentItem = listview->contentItem();
+    QVERIFY(contentItem != 0);
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
+    if (contentY != 0) {
+        listview->setContentY(contentY);
+        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+    }
+
+    QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+    // only target items that will become visible should be animated
+    QList<QPair<QString, QString> > newData;
+    QList<QPair<QString, QString> > expectedTargetData;
+    QList<int> targetIndexes;
+    if (shouldAnimateTargets) {
+        for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
+            newData << qMakePair(QString("New item %1").arg(i), QString(""));
+
+            if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {  // only grab visible items
+                expectedTargetData << newData.last();
+                targetIndexes << i;
+            }
+        }
+        QVERIFY(expectedTargetData.count() > 0);
+    }
+
+    // start animation
+    if (!newData.isEmpty()) {
+        model.insertItems(insertionIndex, newData);
+        QTRY_COMPARE(model.count(), listview->count());
+    }
+
+    QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+
+    if (shouldAnimateTargets) {
+        QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+        QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
+                     expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+        // check the target and displaced items were animated
+        model_targetItems_transitionFrom.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+        model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+        // check attached properties
+        matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
+        matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+        matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
+        if (expectedDisplacedIndexes.isValid()) {
+            // adjust expectedDisplacedIndexes to their final values after the move
+            QList<int> displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount);
+            matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+            matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+            matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
+        }
+
+    } else {
+        QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0);
+        QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
+    }
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    int itemCount = items.count();
+    for (int i=0; i<items.count(); i++) {
+        if (items[i]->y() >= contentY) {
+            QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+            firstVisibleIndex = e.evaluate().toInt();
+            break;
+        }
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QTRY_COMPARE(item->y(), i*20.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+    delete testObject;
+}
+
+void tst_QQuickListView::addTransitions_data()
+{
+    QTest::addColumn<int>("initialItemCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<bool>("shouldAnimateTargets");
+    QTest::addColumn<int>("insertionIndex");
+    QTest::addColumn<int>("insertionCount");
+    QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+    // if inserting before visible index, items should not appear or animate in, even if there are > 1 new items
+    QTest::newRow("insert 1, just before start")
+            << 30 << 20.0 << false
+            << 0 << 1 << ListRange();
+    QTest::newRow("insert 1, way before start")
+            << 30 << 20.0 << false
+            << 0 << 1 << ListRange();
+    QTest::newRow("insert multiple, just before start")
+            << 30 << 100.0 << false
+            << 0 << 3 << ListRange();
+    QTest::newRow("insert multiple, way before start")
+            << 30 << 100.0 << false
+            << 0 << 3 << ListRange();
+
+    QTest::newRow("insert 1 at start")
+            << 30 << 0.0 << true
+            << 0 << 1 << ListRange(0, 15);
+    QTest::newRow("insert multiple at start")
+            << 30 << 0.0 << true
+            << 0 << 3 << ListRange(0, 15);
+    QTest::newRow("insert 1 at start, content y not 0")
+            << 30 << 40.0 << true  // first visible is index 2, so translate the displaced indexes by 2
+            << 2 << 1 << ListRange(0 + 2, 15 + 2);
+    QTest::newRow("insert multiple at start, content y not 0")
+            << 30 << 40.0 << true    // first visible is index 2
+            << 2 << 3 << ListRange(0 + 2, 15 + 2);
+
+    QTest::newRow("insert 1 at start, to empty list")
+            << 0 << 0.0 << true
+            << 0 << 1 << ListRange();
+    QTest::newRow("insert multiple at start, to empty list")
+            << 0 << 0.0 << true
+            << 0 << 3 << ListRange();
+
+    QTest::newRow("insert 1 at middle")
+            << 30 << 0.0 << true
+            << 5 << 1 << ListRange(5, 15);
+    QTest::newRow("insert multiple at middle")
+            << 30 << 0.0 << true
+            << 5 << 3 << ListRange(5, 15);
+
+    QTest::newRow("insert 1 at bottom")
+            << 30 << 0.0 << true
+            << 15 << 1 << ListRange(15, 15);
+    QTest::newRow("insert multiple at bottom")
+            << 30 << 0.0 << true
+            << 15 << 3 << ListRange(15, 15);
+    QTest::newRow("insert 1 at bottom, content y not 0")
+            << 30 << 20.0 * 3 << true
+            << 15 + 3 << 1 << ListRange(15 + 3, 15 + 3);
+    QTest::newRow("insert multiple at bottom, content y not 0")
+            << 30 << 20.0 * 3 << true
+            << 15 + 3 << 3 << ListRange(15 + 3, 15 + 3);
+
+    // items added after the last visible will not be animated in, since they
+    // do not appear in the final view
+    QTest::newRow("insert 1 after end")
+            << 30 << 0.0 << false
+            << 17 << 1 << ListRange();
+    QTest::newRow("insert multiple after end")
+            << 30 << 0.0 << false
+            << 17 << 3 << ListRange();
+}
+
+void tst_QQuickListView::moveTransitions()
+{
+    QFETCH(int, initialItemCount);
+    QFETCH(qreal, contentY);
+    QFETCH(qreal, itemsOffsetAfterMove);
+    QFETCH(int, moveFrom);
+    QFETCH(int, moveTo);
+    QFETCH(int, moveCount);
+    QFETCH(ListRange, expectedDisplacedIndexes);
+
+    // target and displaced items should pass through these points
+    QPointF targetItems_transitionVia(-50, 50);
+    QPointF displacedItems_transitionVia(100, 100);
+
+    QaimModel model;
+    for (int i = 0; i < initialItemCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+    QaimModel model_targetItems_transitionVia;
+    QaimModel model_displacedItems_transitionVia;
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    TestObject *testObject = new TestObject;
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia);
+    ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+    ctxt->setContextProperty("targetItems_transitionVia", targetItems_transitionVia);
+    ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+    ctxt->setContextProperty("testObject", testObject);
+    canvas->setSource(testFileUrl("moveTransitions.qml"));
+    canvas->show();
+
+    QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+    QQuickItem *contentItem = listview->contentItem();
+    QVERIFY(contentItem != 0);
+    QQuickText *name;
+
+    if (contentY != 0) {
+        listview->setContentY(contentY);
+        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+    }
+
+    QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+    // Items moving to *or* from visible positions should be animated.
+    // Otherwise, they should not be animated.
+    QList<QPair<QString, QString> > expectedTargetData;
+    QList<int> targetIndexes;
+    for (int i=moveFrom; i<moveFrom+moveCount; i++) {
+        int toIndex = moveTo + (i - moveFrom);
+        if (i <= (contentY + listview->height()) / 20
+                || toIndex < (contentY + listview->height()) / 20) {
+            expectedTargetData << qMakePair(model.name(i), model.number(i));
+            targetIndexes << i;
+        }
+    }
+    // ViewTransition.index provides the indices that items are moving to, not from
+    targetIndexes = adjustIndexesForMove(targetIndexes, moveFrom, moveTo, moveCount);
+
+    // start animation
+    model.moveItems(moveFrom, moveTo, moveCount);
+
+    QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+    QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
+                 expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+    QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+
+    // check the target and displaced items were animated
+    model_targetItems_transitionVia.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+    model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+    // check attached properties
+    matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
+    matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+    matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
+    if (expectedDisplacedIndexes.isValid()) {
+        // adjust expectedDisplacedIndexes to their final values after the move
+        QList<int> displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, moveFrom, moveTo, moveCount);
+        matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+        matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+        matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
+    }
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    for (int i=0; i<items.count(); i++) {
+        if (items[i]->y() >= contentY) {
+            QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+            firstVisibleIndex = e.evaluate().toInt();
+            break;
+        }
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
+        name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+    delete testObject;
+}
+
+void tst_QQuickListView::moveTransitions_data()
+{
+    QTest::addColumn<int>("initialItemCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<qreal>("itemsOffsetAfterMove");
+    QTest::addColumn<int>("moveFrom");
+    QTest::addColumn<int>("moveTo");
+    QTest::addColumn<int>("moveCount");
+    QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+    // when removing from above the visible, all items shift down depending on how many
+    // items have been removed from above the visible
+    QTest::newRow("move from above view, outside visible items, move 1") << 30 << 4*20.0 << 20.0
+            << 1 << 10 << 1 << ListRange(11, 15+4);
+    QTest::newRow("move from above view, outside visible items, move 1 (first item)") << 30 << 4*20.0 << 20.0
+            << 0 << 10 << 1 << ListRange(11, 15+4);
+    QTest::newRow("move from above view, outside visible items, move multiple") << 30 << 4*20.0 << 2*20.0
+            << 1 << 10 << 2 << ListRange(12, 15+4);
+    QTest::newRow("move from above view, outside visible items, move multiple (first item)") << 30 << 4*20.0 << 3*20.0
+            << 0 << 10 << 3 << ListRange(13, 15+4);
+    QTest::newRow("move from above view, mix of visible/non-visible") << 30 << 4*20.0 << 3*20.0
+            << 1 << 10 << 5 << ListRange(6, 14) + ListRange(15, 15+4);
+    QTest::newRow("move from above view, mix of visible/non-visible (move first)") << 30 << 4*20.0 << 4*20.0
+            << 0 << 10 << 5 << ListRange(5, 14) + ListRange(15, 15+4);
+
+    QTest::newRow("move within view, move 1 down") << 30 << 0.0 << 0.0
+            << 1 << 10 << 1 << ListRange(2, 10);
+    QTest::newRow("move within view, move 1 down, move first item") << 30 << 0.0 << 0.0
+            << 0 << 10 << 1 << ListRange(1, 10);
+    QTest::newRow("move within view, move 1 down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 0+4 << 10+4 << 1 << ListRange(1+4, 10+4);
+    QTest::newRow("move within view, move 1 down, to last item") << 30 << 0.0 << 0.0
+            << 10 << 15 << 1 << ListRange(11, 15);
+    QTest::newRow("move within view, move first->last") << 30 << 0.0 << 0.0
+            << 0 << 15 << 1 << ListRange(1, 15);
+
+    QTest::newRow("move within view, move multiple down") << 30 << 0.0 << 0.0
+            << 1 << 10 << 3 << ListRange(4, 12);
+    QTest::newRow("move within view, move multiple down, move first item") << 30 << 0.0 << 0.0
+            << 0 << 10 << 3 << ListRange(3, 12);
+    QTest::newRow("move within view, move multiple down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 0+4 << 10+4 << 3 << ListRange(3+4, 12+4);
+    QTest::newRow("move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0
+            << 5 << 13 << 3 << ListRange(8, 15);
+    QTest::newRow("move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0
+            << 0 << 13 << 3 << ListRange(3, 15);
+
+    QTest::newRow("move within view, move 1 up") << 30 << 0.0 << 0.0
+            << 10 << 1 << 1 << ListRange(1, 9);
+    QTest::newRow("move within view, move 1 up, move to first index") << 30 << 0.0 << 0.0
+            << 10 << 0 << 1 << ListRange(0, 9);
+    QTest::newRow("move within view, move 1 up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
+    QTest::newRow("move within view, move 1 up, move to first index, contentY not on item border") << 30 << 4*20.0 - 10 << 0.0
+            << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
+    QTest::newRow("move within view, move 1 up, move last item") << 30 << 0.0 << 0.0
+            << 15 << 10 << 1 << ListRange(10, 14);
+    QTest::newRow("move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0
+            << 15 << 0 << 1 << ListRange(0, 14);
+
+    QTest::newRow("move within view, move multiple up") << 30 << 0.0 << 0.0
+            << 10 << 1 << 3 << ListRange(1, 9);
+    QTest::newRow("move within view, move multiple up, move to first index") << 30 << 0.0 << 0.0
+            << 10 << 0 << 3 << ListRange(0, 9);
+    QTest::newRow("move within view, move multiple up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 10+4 << 0+4 << 3 << ListRange(0+4, 9+4);
+    QTest::newRow("move within view, move multiple up, move last item") << 30 << 0.0 << 0.0
+            << 13 << 5 << 3 << ListRange(5, 12);
+    QTest::newRow("move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0
+            << 13 << 0 << 3 << ListRange(0, 12);
+
+    QTest::newRow("move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0
+            << 20 << 0 << 1 << ListRange(0, 15);
+    QTest::newRow("move from below view, move 1 up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 25 << 4 << 1 << ListRange(0+4, 15+4);
+    QTest::newRow("move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0
+            << 20 << 0 << 3 << ListRange(0, 15);
+    QTest::newRow("move from below view, move multiple up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 25 << 4 << 3 << ListRange(0+4, 15+4);
+
+    QTest::newRow("move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0
+            << 20 << 15 << 1 << ListRange(15, 15);
+    QTest::newRow("move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 25 << 15+4 << 1 << ListRange(15+4, 15+4);
+    QTest::newRow("move from below view, move multiple up, move to to bottom") << 30 << 0.0 << 0.0
+            << 20 << 15 << 3 << ListRange(15, 15);
+    QTest::newRow("move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
+            << 25 << 15+4 << 3 << ListRange(15+4, 15+4);
+}
+
+void tst_QQuickListView::removeTransitions()
+{
+    QFETCH(int, initialItemCount);
+    QFETCH(bool, shouldAnimateTargets);
+    QFETCH(qreal, contentY);
+    QFETCH(int, removalIndex);
+    QFETCH(int, removalCount);
+    QFETCH(ListRange, expectedDisplacedIndexes);
+
+    // added items should end here
+    QPointF targetItems_transitionTo(-50, -50);
+
+    // displaced items should pass through this points
+    QPointF displacedItems_transitionVia(100, 100);
+
+    QaimModel model;
+    for (int i = 0; i < initialItemCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+    QaimModel model_targetItems_transitionTo;
+    QaimModel model_displacedItems_transitionVia;
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    TestObject *testObject = new TestObject;
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo);
+    ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+    ctxt->setContextProperty("targetItems_transitionTo", targetItems_transitionTo);
+    ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+    ctxt->setContextProperty("testObject", testObject);
+    canvas->setSource(testFileUrl("removeTransitions.qml"));
+    canvas->show();
+
+    QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+    QQuickItem *contentItem = listview->contentItem();
+    QVERIFY(contentItem != 0);
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
+    if (contentY != 0) {
+        listview->setContentY(contentY);
+        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+    }
+
+    QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+    // only target items that are visible should be animated
+    QList<QPair<QString, QString> > expectedTargetData;
+    QList<int> targetIndexes;
+    if (shouldAnimateTargets) {
+        for (int i=removalIndex; i<removalIndex+removalCount; i++) {
+            if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {
+                expectedTargetData << qMakePair(model.name(i), model.number(i));
+                targetIndexes << i;
+            }
+        }
+        QVERIFY(expectedTargetData.count() > 0);
+    }
+
+    // calculate targetItems and expectedTargets before model changes
+    QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+    QVariantMap expectedTargets;
+    for (int i=0; i<targetIndexes.count(); i++)
+        expectedTargets[model.name(targetIndexes[i])] = targetIndexes[i];
+
+    // start animation
+    model.removeItems(removalIndex, removalCount);
+    QTRY_COMPARE(model.count(), listview->count());
+
+    if (shouldAnimateTargets) {
+        QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+        QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
+                     expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+        // check the target and displaced items were animated
+        model_targetItems_transitionTo.matchAgainst(expectedTargetData, "wasn't animated to target 'to' pos", "shouldn't have been animated to target 'to' pos");
+        model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+        // check attached properties
+        QCOMPARE(listview->property("targetTrans_items").toMap(), expectedTargets);
+        matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+        matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
+        if (expectedDisplacedIndexes.isValid()) {
+            // adjust expectedDisplacedIndexes to their final values after the move
+            QList<int> displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, removalIndex, removalCount);
+            matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+            matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+            matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
+        }
+    } else {
+        QTRY_COMPARE(model_targetItems_transitionTo.count(), 0);
+        QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
+    }
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    int itemCount = items.count();
+
+    for (int i=0; i<items.count(); i++) {
+        QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+        int index = e.evaluate().toInt();
+        if (firstVisibleIndex < 0 && items[i]->y() >= contentY)
+            firstVisibleIndex = index;
+        if (index < 0)
+            itemCount--;    // exclude deleted items
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QCOMPARE(item->x(), 0.0);
+        QCOMPARE(item->y(), contentY + (i-firstVisibleIndex) * 20.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+    delete testObject;
+}
+
+void tst_QQuickListView::removeTransitions_data()
+{
+    QTest::addColumn<int>("initialItemCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<bool>("shouldAnimateTargets");
+    QTest::addColumn<int>("removalIndex");
+    QTest::addColumn<int>("removalCount");
+    QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+    // All items that are visible following the remove operation should be animated.
+    // Remove targets that are outside of the view should not be animated.
+
+    QTest::newRow("remove 1 before start")
+            << 30 << 20.0 * 3 << false
+            << 2 << 1 << ListRange();
+    QTest::newRow("remove multiple, all before start")
+            << 30 << 20.0 * 3 << false
+            << 0 << 3 << ListRange();
+    QTest::newRow("remove mix of before and after start")
+            << 30 << 20.0 * 3 << true
+            << 2 << 3 << ListRange(5, 20);  // 5-20 are visible after the remove
+
+    QTest::newRow("remove 1 from start")
+            << 30 << 0.0 << true
+            << 0 << 1 << ListRange(1, 16);  // 1-16 are visible after the remove
+    QTest::newRow("remove multiple from start")
+            << 30 << 0.0 << true
+            << 0 << 3 << ListRange(3, 18);  // 3-18 are visible after the remove
+    QTest::newRow("remove 1 from start, content y not 0")
+            << 30 << 20.0 * 2 << true  // first visible is index 2, so translate the displaced indexes by 2
+            << 2 << 1 << ListRange(1 + 2, 16 + 2);
+    QTest::newRow("remove multiple from start, content y not 0")
+            << 30 << 20.0 * 2 << true    // first visible is index 2
+            << 2 << 3 << ListRange(3 + 2, 18 + 2);
+
+    QTest::newRow("remove 1 from middle")
+            << 30 << 0.0 << true
+            << 5 << 1 << ListRange(6, 16);
+    QTest::newRow("remove multiple from middle")
+            << 30 << 0.0 << true
+            << 5 << 3 << ListRange(8, 18);
+
+
+    QTest::newRow("remove 1 from bottom")
+            << 30 << 0.0 << true
+            << 15 << 1 << ListRange(16, 16);
+
+    // remove 15, 16, 17
+    // 15 will animate as the target item, 16 & 17 won't be animated since they are outside
+    // the view, and 18 will be animated as the displaced item to replace the last item
+    QTest::newRow("remove multiple from bottom")
+            << 30 << 0.0 << true
+            << 15 << 3 << ListRange(18, 18);
+
+    QTest::newRow("remove 1 from bottom, content y not 0")
+            << 30 << 20.0 * 2 << true
+            << 15 + 2 << 1 << ListRange(16 + 2, 16 + 2);
+    QTest::newRow("remove multiple from bottom, content y not 0")
+            << 30 << 20.0 * 2 << true
+            << 15 + 2 << 3 << ListRange(18 + 2, 18 + 2);
+
+
+    QTest::newRow("remove 1 after end")
+            << 30 << 0.0 << false
+            << 17 << 1 << ListRange();
+    QTest::newRow("remove multiple after end")
+            << 30 << 0.0 << false
+            << 17 << 3 << ListRange();
+}
+
+void tst_QQuickListView::multipleTransitions()
+{
+    // Tests that if you interrupt a transition in progress with another action that
+    // cancels the previous transition, the resulting items are still placed correctly.
+
+    QFETCH(int, initialCount);
+    QFETCH(qreal, contentY);
+    QFETCH(QList<ListChange>, changes);
+
+    // add transitions on the left, moves on the right
+    QPointF addTargets_transitionFrom(-50, -50);
+    QPointF addDisplaced_transitionFrom(-50, 50);
+    QPointF moveTargets_transitionFrom(50, -50);
+    QPointF moveDisplaced_transitionFrom(50, 50);
+
+    QmlListModel model;
+    for (int i = 0; i < initialCount; i++)
+        model.addItem("Original item" + QString::number(i), "");
+
+    QQuickView *canvas = createView();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    TestObject *testObject = new TestObject;
+    ctxt->setContextProperty("testModel", &model);
+    ctxt->setContextProperty("testObject", testObject);
+    ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom);
+    ctxt->setContextProperty("addDisplaced_transitionFrom", addDisplaced_transitionFrom);
+    ctxt->setContextProperty("moveTargets_transitionFrom", moveTargets_transitionFrom);
+    ctxt->setContextProperty("moveDisplaced_transitionFrom", moveDisplaced_transitionFrom);
+    canvas->setSource(testFileUrl("multipleTransitions.qml"));
+    canvas->show();
+
+    QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+    QQuickItem *contentItem = listview->contentItem();
+    QVERIFY(contentItem != 0);
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
+    int timeBetweenActions = canvas->rootObject()->property("timeBetweenActions").toInt();
+
+    QList<QPair<QString, QString> > targetItems;
+    for (int i=0; i<changes.count(); i++) {
+        switch (changes[i].type) {
+            case ListChange::Inserted:
+            {
+                for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+                    targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
+                model.insertItems(changes[i].index, targetItems);
+                QTRY_COMPARE(model.count(), listview->count());
+                QTRY_VERIFY(listview->property("runningAddTargets").toBool());
+                QTRY_VERIFY(listview->property("runningAddDisplaced").toBool());
+                if (i == changes.count() - 1) {
+                    QTRY_VERIFY(!listview->property("runningAddTargets").toBool());
+                    QTRY_VERIFY(!listview->property("runningAddDisplaced").toBool());
+                } else {
+                    QTest::qWait(timeBetweenActions);
+                }
+                break;
+            }
+            case ListChange::Removed:
+                for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+                    targetItems << qMakePair(model.name(i), model.number(i));
+                model.removeItems(changes[i].index, changes[i].count);
+                QTRY_COMPARE(model.count(), listview->count());
+                QTRY_VERIFY(listview->property("runningRemoveTargets").toBool());
+                QTRY_VERIFY(listview->property("runningRemoveDisplaced").toBool());
+                if (i == changes.count() - 1) {
+                    QTRY_VERIFY(!listview->property("runningRemoveTargets").toBool());
+                    QTRY_VERIFY(!listview->property("runningRemoveDisplaced").toBool());
+                } else {
+                    QTest::qWait(timeBetweenActions);
+                }
+                break;
+            case ListChange::Moved:
+                for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+                    targetItems << qMakePair(model.name(i), model.number(i));
+                model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+                QTRY_VERIFY(listview->property("runningMoveTargets").toBool());
+                QTRY_VERIFY(listview->property("runningMoveDisplaced").toBool());
+                if (i == changes.count() - 1) {
+                    QTRY_VERIFY(!listview->property("runningMoveTargets").toBool());
+                    QTRY_VERIFY(!listview->property("runningMoveDisplaced").toBool());
+                } else {
+                    QTest::qWait(timeBetweenActions);
+                }
+                break;
+            case ListChange::SetCurrent:
+                listview->setCurrentIndex(changes[i].index);
+                break;
+            case ListChange::SetContentY:
+                listview->setContentY(changes[i].pos);
+                QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+                break;
+        }
+    }
+    QCOMPARE(listview->count(), model.count());
+
+    QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+    int firstVisibleIndex = -1;
+    for (int i=0; i<items.count(); i++) {
+        if (items[i]->y() >= contentY) {
+            QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+            firstVisibleIndex = e.evaluate().toInt();
+            break;
+        }
+    }
+    QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+    // verify all items moved to the correct final positions
+    int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+    for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+        QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+        QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+        QTRY_COMPARE(item->x(), 0.0);
+        QTRY_COMPARE(item->y(), i*20.0);
+        QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+        QVERIFY(name != 0);
+        QTRY_COMPARE(name->text(), model.name(i));
+    }
+
+    delete canvas;
+    delete testObject;
+}
+
+void tst_QQuickListView::multipleTransitions_data()
+{
+    QTest::addColumn<int>("initialCount");
+    QTest::addColumn<qreal>("contentY");
+    QTest::addColumn<QList<ListChange> >("changes");
+
+    // the added item and displaced items should move to final dest correctly
+    QTest::newRow("add item, then move it immediately") << 10 << 0.0 << (QList<ListChange>()
+            << ListChange::insert(0, 1)
+            << ListChange::move(0, 3, 1)
+            );
+
+    // items affected by the add should change from move to add transition
+    QTest::newRow("move, then insert item before the moved item") << 20 << 0.0 << (QList<ListChange>()
+            << ListChange::move(1, 10, 3)
+            << ListChange::insert(0, 1)
+            );
+
+    // items should be placed correctly if you trigger a transition then refill for that index
+    QTest::newRow("add at 0, flick down, flick back to top and add at 0 again") << 20 << 0.0 << (QList<ListChange>()
+            << ListChange::insert(0, 1)
+            << ListChange::setContentY(80.0)
+            << ListChange::setContentY(0.0)
+            << ListChange::insert(0, 1)
+            );
+}
+
+QList<int> tst_QQuickListView::toIntList(const QVariantList &list)
+{
+    QList<int> ret;
+    bool ok = true;
+    for (int i=0; i<list.count(); i++) {
+        ret << list[i].toInt(&ok);
+        if (!ok)
+            qWarning() << "tst_QQuickListView::toIntList(): not a number:" << list[i];
+    }
+
+    return ret;
+}
+
+void tst_QQuickListView::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
+{
+    for (int i=0; i<indexLists.count(); i++) {
+        QSet<int> current = indexLists[i].value<QList<int> >().toSet();
+        if (current != expectedIndexes.toSet())
+            qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
+        QCOMPARE(current, expectedIndexes.toSet());
+    }
+}
+
+void tst_QQuickListView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
+{
+    for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
+        QVERIFY(it.value().type() == QVariant::Int);
+        QString name = it.key();
+        int itemIndex = it.value().toInt();
+        QVERIFY2(expectedIndexes.contains(itemIndex), QTest::toString(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
+        if (model.name(itemIndex) != name)
+            qDebug() << itemIndex;
+        QCOMPARE(model.name(itemIndex), name);
+    }
+    QCOMPARE(items.count(), expectedIndexes.count());
+}
+
+void tst_QQuickListView::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
+{
+    for (int i=0; i<itemLists.count(); i++) {
+        QVERIFY(itemLists[i].type() == QVariant::List);
+        QVariantList current = itemLists[i].toList();
+        for (int j=0; j<current.count(); j++) {
+            QQuickItem *o = qobject_cast<QQuickItem*>(current[j].value<QObject*>());
+            QVERIFY2(o, QTest::toString(QString("Invalid actual item at %1").arg(j)));
+            QVERIFY2(expectedItems.contains(o), QTest::toString(QString("Cannot match item %1").arg(j)));
+        }
+        QCOMPARE(current.count(), expectedItems.count());
+    }
+}
+
 
 QTEST_MAIN(tst_QQuickListView)
 
index ed2066d..eeef230 100644 (file)
@@ -99,6 +99,53 @@ void QQuickViewTestUtil::flick(QQuickView *canvas, const QPoint &from, const QPo
     QTest::qWait(50);
 }
 
+QList<int> QQuickViewTestUtil::adjustIndexesForAddDisplaced(const QList<int> &indexes, int index, int count)
+{
+    QList<int> result;
+    for (int i=0; i<indexes.count(); i++) {
+        int num = indexes[i];
+        if (num >= index) {
+            num += count;
+        }
+        result << num;
+    }
+    return result;
+}
+
+QList<int> QQuickViewTestUtil::adjustIndexesForMove(const QList<int> &indexes, int from, int to, int count)
+{
+    QList<int> result;
+    for (int i=0; i<indexes.count(); i++) {
+        int num = indexes[i];
+        if (from < to) {
+            if (num >= from && num < from + count)
+                num += (to - from); // target
+            else if (num >= from && num < to + count)
+                num -= count;   // displaced
+        } else if (from > to) {
+            if (num >= from && num < from + count)
+                num -= (from - to);  // target
+            else if (num >= to && num < from + count)
+                num += count;   // displaced
+        }
+        result << num;
+    }
+    return result;
+}
+
+QList<int> QQuickViewTestUtil::adjustIndexesForRemoveDisplaced(const QList<int> &indexes, int index, int count)
+{
+    QList<int> result;
+    for (int i=0; i<indexes.count(); i++) {
+        int num = indexes[i];
+        if (num >= index)
+            num -= count;
+        result << num;
+    }
+    return result;
+}
+
+
 QQuickViewTestUtil::QmlListModel::QmlListModel(QObject *parent)
     : QListModelInterface(parent)
 {
@@ -228,6 +275,17 @@ void QQuickViewTestUtil::QmlListModel::clear() {
     emit itemsRemoved(0, count);
 }
 
+void QQuickViewTestUtil::QmlListModel::matchAgainst(const QList<QPair<QString, QString> > &other, const QString &error1, const QString &error2) {
+    for (int i=0; i<other.count(); i++) {
+        QVERIFY2(list.contains(other[i]),
+                 QTest::toString(other[i].first + " " + other[i].second + " " + error1));
+    }
+    for (int i=0; i<list.count(); i++) {
+        QVERIFY2(other.contains(list[i]),
+                 QTest::toString(list[i].first + " " + list[i].second + " " + error2));
+    }
+}
+
 
 QQuickViewTestUtil::QaimModel::QaimModel(QObject *parent)
     : QAbstractListModel(parent)
@@ -343,3 +401,93 @@ void QQuickViewTestUtil::QaimModel::clear()
     emit endRemoveRows();
 }
 
+void QQuickViewTestUtil::QaimModel::reset()
+{
+    emit beginResetModel();
+    emit endResetModel();
+}
+
+void QQuickViewTestUtil::QaimModel::matchAgainst(const QList<QPair<QString, QString> > &other, const QString &error1, const QString &error2) {
+    for (int i=0; i<other.count(); i++) {
+        QVERIFY2(list.contains(other[i]),
+                 QTest::toString(other[i].first + " " + other[i].second + " " + error1));
+    }
+    for (int i=0; i<list.count(); i++) {
+        QVERIFY2(other.contains(list[i]),
+                 QTest::toString(list[i].first + " " + list[i].second + " " + error2));
+    }
+}
+
+
+
+QQuickViewTestUtil::ListRange::ListRange()
+    : valid(false)
+{
+}
+
+QQuickViewTestUtil::ListRange::ListRange(const ListRange &other)
+    : valid(other.valid)
+{
+    indexes = other.indexes;
+}
+
+QQuickViewTestUtil::ListRange::ListRange(int start, int end)
+    : valid(true)
+{
+    for (int i=start; i<=end; i++)
+        indexes << i;
+}
+
+QQuickViewTestUtil::ListRange::~ListRange()
+{
+}
+
+QQuickViewTestUtil::ListRange QQuickViewTestUtil::ListRange::operator+(const ListRange &other) const
+{
+    if (other == *this)
+        return *this;
+    ListRange a(*this);
+    a.indexes.append(other.indexes);
+    return a;
+}
+
+bool QQuickViewTestUtil::ListRange::operator==(const ListRange &other) const
+{
+    return indexes.toSet() == other.indexes.toSet();
+}
+
+bool QQuickViewTestUtil::ListRange::operator!=(const ListRange &other) const
+{
+    return !(*this == other);
+}
+
+bool QQuickViewTestUtil::ListRange::isValid() const
+{
+    return valid;
+}
+
+int QQuickViewTestUtil::ListRange::count() const
+{
+    return indexes.count();
+}
+
+QList<QPair<QString,QString> > QQuickViewTestUtil::ListRange::getModelDataValues(const QmlListModel &model)
+{
+    QList<QPair<QString,QString> > data;
+    if (!valid)
+        return data;
+    for (int i=0; i<indexes.count(); i++)
+        data.append(qMakePair(model.name(indexes[i]), model.number(indexes[i])));
+    return data;
+}
+
+QList<QPair<QString,QString> > QQuickViewTestUtil::ListRange::getModelDataValues(const QaimModel &model)
+{
+    QList<QPair<QString,QString> > data;
+    if (!valid)
+        return data;
+    for (int i=0; i<indexes.count(); i++)
+        data.append(qMakePair(model.name(indexes[i]), model.number(indexes[i])));
+    return data;
+}
+
index 98b0dbb..71fd506 100644 (file)
@@ -55,16 +55,22 @@ namespace QQuickViewTestUtil
 
     void flick(QQuickView *canvas, const QPoint &from, const QPoint &to, int duration);
 
+    QList<int> adjustIndexesForAddDisplaced(const QList<int> &indexes, int index, int count);
+    QList<int> adjustIndexesForMove(const QList<int> &indexes, int from, int to, int count);
+    QList<int> adjustIndexesForRemoveDisplaced(const QList<int> &indexes, int index, int count);
+
     struct ListChange {
-        enum { Inserted, Removed, Moved, SetCurrent } type;
+        enum { Inserted, Removed, Moved, SetCurrent, SetContentY } type;
         int index;
         int count;
         int to;     // Move
+        qreal pos;  // setContentY
 
-        static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; }
-        static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; }
-        static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; }
-        static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; }
+        static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1, 0.0 }; return c; }
+        static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1, 0.0 }; return c; }
+        static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to, 0.0 }; return c; }
+        static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1, 0.0 }; return c; }
+        static ListChange setContentY(qreal pos) { ListChange c = { SetContentY, -1, -1, -1, pos }; return c; }
     };
 
     class QmlListModel : public QListModelInterface
@@ -87,7 +93,7 @@ namespace QQuickViewTestUtil
         QVariant data(int index, int role) const;
         QHash<int, QVariant> data(int index, const QList<int> &roles) const;
 
-        void addItem(const QString &name, const QString &number);
+        Q_INVOKABLE void addItem(const QString &name, const QString &number);
         void insertItem(int index, const QString &name, const QString &number);
         void insertItems(int index, const QList<QPair<QString, QString> > &items);
 
@@ -101,6 +107,8 @@ namespace QQuickViewTestUtil
 
         void clear();
 
+        void matchAgainst(const QList<QPair<QString, QString> > &other, const QString &error1, const QString &error2);
+
     private:
         QList<QPair<QString,QString> > list;
     };
@@ -120,7 +128,7 @@ namespace QQuickViewTestUtil
         QString name(int index) const;
         QString number(int index) const;
 
-        void addItem(const QString &name, const QString &number);
+        Q_INVOKABLE void addItem(const QString &name, const QString &number);
         void addItems(const QList<QPair<QString, QString> > &items);
         void insertItem(int index, const QString &name, const QString &number);
         void insertItems(int index, const QList<QPair<QString, QString> > &items);
@@ -134,13 +142,39 @@ namespace QQuickViewTestUtil
         void modifyItem(int idx, const QString &name, const QString &number);
 
         void clear();
+        void reset();
+
+        void matchAgainst(const QList<QPair<QString, QString> > &other, const QString &error1, const QString &error2);
 
     private:
         QList<QPair<QString,QString> > list;
     };
 
+    class ListRange
+    {
+    public:
+        ListRange();
+        ListRange(const ListRange &other);
+        ListRange(int start, int end);
+
+        ~ListRange();
+
+        ListRange operator+(const ListRange &other) const;
+        bool operator==(const ListRange &other) const;
+        bool operator!=(const ListRange &other) const;
+
+        bool isValid() const;
+        int count() const;
+
+        QList<QPair<QString,QString> > getModelDataValues(const QmlListModel &model);
+        QList<QPair<QString,QString> > getModelDataValues(const QaimModel &model);
+
+        QList<int> indexes;
+        bool valid;
+    };
 }
 
 Q_DECLARE_METATYPE(QList<QQuickViewTestUtil::ListChange>)
+Q_DECLARE_METATYPE(QQuickViewTestUtil::ListRange)
 
 #endif // QQUICKVIEWTESTUTIL_H
index ceed4b0..09bb03c 100644 (file)
@@ -97,6 +97,16 @@ namespace QQuickVisualTestUtil
 
         return items;
     }
+
+    template<typename T>
+    QList<T*> findItems(QQuickItem *parent, const QString &objectName, const QList<int> &indexes)
+    {
+        QList<T*> items;
+        for (int i=0; i<indexes.count(); i++)
+            items << qobject_cast<QQuickItem*>(findItem<T>(parent, objectName, indexes[i]));
+        return items;
+    }
+
 }
 
 #endif // QQUICKVISUALTESTUTIL_H