From c47c502e794f7ae37c516a1db123139d9dc1270d Mon Sep 17 00:00:00 2001
From: J-P Nurmi <jpnurmi@digia.com>
Date: Tue, 28 May 2013 13:39:00 +0200
Subject: [PATCH] Revise TableView API for managing the columns

Replace "property list<TableViewColumn> columns" with:
- addColumn(column)
- insertColumn(index, column)
- moveColumn(from, to)
- removeColumn(index)
- getColumn(index)

Exposing list<TableViewColumn> type of property in the public API was
problematic for several reasons. First of all, it limited the internal
implementation too much. Secondly, modifying the list programmatically
did not work as expected, and it also threw nasty warnings while
reordering the columns interactively.

Task-number: QTBUG-30319
Task-number: QTBUG-31028
Change-Id: I0039f7e4be2d6ee9303a4118bdf84146b6a96a05
Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
---
 src/controls/TableView.qml                    | 112 +++++++++++----
 src/controls/TableViewColumn.qml              |   2 +
 .../data/tableview/table_dynamiccolumns.qml   |  65 +++++++++
 tests/auto/controls/data/tst_tableview.qml    | 136 +++++++++++++++++-
 4 files changed, 290 insertions(+), 25 deletions(-)
 create mode 100644 tests/auto/controls/data/tableview/table_dynamiccolumns.qml

diff --git a/src/controls/TableView.qml b/src/controls/TableView.qml
index 4cde5d3e3..93d75e92c 100644
--- a/src/controls/TableView.qml
+++ b/src/controls/TableView.qml
@@ -200,9 +200,8 @@ ScrollView {
     */
     property int sortIndicatorOrder: Qt.AscendingOrder
 
-    /*! \qmlproperty list<TableViewColumn> TableView::columns
-    This property contains the TableViewColumn items */
-    default property alias columns: listView.columnheader
+    /*! \internal */
+    default property alias __columns: root.data
 
     /*! \qmlproperty Component TableView::contentHeader
     This is the content header of the TableView */
@@ -216,8 +215,9 @@ ScrollView {
     The current number of rows */
     readonly property alias rowCount: listView.count
 
-    /*! The current number of columns */
-    readonly property int columnCount: columns.length
+    /*! \qmlproperty int TableView::columnCount
+    The current number of columns */
+    readonly property alias columnCount: columnModel.count
 
     /*! \qmlproperty string TableView::section.property
         \qmlproperty enumeration TableView::section.criteria
@@ -288,6 +288,73 @@ ScrollView {
         return listView.indexAt(obj.x, obj.y)
     }
 
+    /*! Adds a \a column and returns the added column.
+
+        The \a column argument can be an instance of TableViewColumn,
+        or a Component. The component has to contain a TableViewColumn.
+        Otherwise  \c null is returned.
+    */
+    function addColumn(column) {
+        return insertColumn(columnCount, column)
+    }
+
+    /*! Inserts a \a column at the given \a index and returns the inserted column.
+
+        The \a column argument can be an instance of TableViewColumn,
+        or a Component. The component has to contain a TableViewColumn.
+        Otherwise  \c null is returned.
+    */
+    function insertColumn(index, column) {
+        var object = column
+        if (typeof column['createObject'] === 'function')
+            object = column.createObject(root)
+
+        if (index >= 0 && index <= columnCount && object.Accessible.role === Accessible.ColumnHeader) {
+            columnModel.insert(index, {columnItem: object})
+            return object
+        }
+
+        if (object !== column)
+            object.destroy()
+        console.warn("TableView::insertColumn(): invalid argument")
+        return null
+    }
+
+    /*! Removes and destroys a column at the given \a index. */
+    function removeColumn(index) {
+        if (index < 0 || index >= columnCount) {
+            console.warn("TableView::removeColumn(): invalid argument")
+            return
+        }
+        var column = columnModel.get(index).columnItem
+        columnModel.remove(index, 1)
+        column.destroy()
+    }
+
+    /*! Moves a column \a from index \a to another. */
+    function moveColumn(from, to) {
+        if (from < 0 || from >= columnCount || to < 0 || to >= columnCount) {
+            console.warn("TableView::moveColumn(): invalid argument")
+            return
+        }
+        columnModel.move(from, to, 1)
+    }
+
+    /*! Returns the column at the given \a index
+        or \c null if the \a index is invalid. */
+    function getColumn(index) {
+        if (index < 0 || index >= columnCount)
+            return null
+        return columnModel.get(index).columnItem
+    }
+
+    Component.onCompleted: {
+        for (var i = 0; i < __columns.length; ++i) {
+            var column = __columns[i]
+            if (column.Accessible.role === Accessible.ColumnHeader)
+                addColumn(column)
+        }
+    }
 
     style: Qt.createComponent(Settings.style + "/TableViewStyle.qml", root)
 
@@ -429,7 +496,10 @@ ScrollView {
             }
         }
 
-        property list<TableViewColumn> columnheader
+        ListModel {
+            id: columnModel
+        }
+
         highlightFollowsCurrentItem: true
         model: root.model
 
@@ -483,7 +553,7 @@ ScrollView {
                 height: parent.height
                 Repeater {
                     id: repeater
-                    model: root.columnCount
+                    model: columnModel
 
                     Loader {
                         id: itemDelegateLoader
@@ -509,7 +579,7 @@ ScrollView {
                             readonly property string role: __column.role
                         }
 
-                        readonly property TableViewColumn __column: columns[index]
+                        readonly property TableViewColumn __column: columnItem
                         readonly property bool __hasModelRole: styleData.role && itemModel.hasOwnProperty(styleData.role)
                         readonly property bool __hasModelDataRole: styleData.role && modelData && modelData.hasOwnProperty(styleData.role)
                     }
@@ -548,12 +618,12 @@ ScrollView {
                     property int targetIndex: -1
                     property int dragIndex: -1
 
-                    model: columnCount
+                    model: columnModel
 
                     delegate: Item {
                         z:-index
-                        width: columns[index].width
-                        visible: columns[index].visible
+                        width: modelData.width
+                        visible: modelData.visible
                         height: headerVisible ? headerStyle.height : 0
 
                         Loader {
@@ -562,7 +632,7 @@ ScrollView {
                             anchors.left: parent.left
                             anchors.right: parent.right
                             property QtObject styleData: QtObject {
-                                readonly property string value: columns[index].title
+                                readonly property string value: modelData.title
                                 readonly property bool pressed: headerClickArea.pressed
                                 readonly property bool containsMouse: headerClickArea.containsMouse
                                 readonly property int column: index
@@ -608,13 +678,7 @@ ScrollView {
 
                             onReleased: {
                                 if (repeater.targetIndex >= 0 && repeater.targetIndex != index ) {
-                                    // Rearrange the header sections
-                                    var items = new Array
-                                    for (var i = 0 ; i< columnCount ; ++i)
-                                        items.push(columns[i])
-                                    items.splice(index, 1);
-                                    items.splice(repeater.targetIndex, 0, columns[index]);
-                                    columns = items
+                                    columnModel.move(index, repeater.targetIndex, 1)
                                     if (sortIndicatorColumn == index)
                                         sortIndicatorColumn = repeater.targetIndex
                                 }
@@ -628,14 +692,14 @@ ScrollView {
                         Loader {
                             id: draghandle
                             property QtObject styleData: QtObject{
-                                readonly property string value: columns[index].title
+                                readonly property string value: modelData.title
                                 readonly property bool pressed: headerClickArea.pressed
                                 readonly property bool containsMouse: headerClickArea.containsMouse
                                 readonly property int column: index
                             }
 
                             parent: tableHeader
-                            width: columns[index].width
+                            width: modelData.width
                             height: parent.height
                             sourceComponent: root.headerDelegate
                             visible: headerClickArea.pressed
@@ -651,8 +715,8 @@ ScrollView {
                             width: 16 ; height: parent.height
                             anchors.right: parent.right
                             onPositionChanged:  {
-                                var newHeaderWidth = columns[index].width + (mouseX - offset)
-                                columns[index].width = Math.max(minimumSize, newHeaderWidth)
+                                var newHeaderWidth = modelData.width + (mouseX - offset)
+                                modelData.width = Math.max(minimumSize, newHeaderWidth)
                             }
                             property bool found:false
 
@@ -667,7 +731,7 @@ ScrollView {
                                         minWidth = Math.max(minWidth, item.children[1].children[index].children[0].implicitWidth)
                                 }
                                 if (minWidth)
-                                    columns[index].width = minWidth
+                                    modelData.width = minWidth
                             }
                             onPressedChanged: if (pressed) offset=mouseX
                             cursorShape: Qt.SplitHCursor
diff --git a/src/controls/TableViewColumn.qml b/src/controls/TableViewColumn.qml
index 919f30de4..2c629dfbd 100644
--- a/src/controls/TableViewColumn.qml
+++ b/src/controls/TableViewColumn.qml
@@ -89,4 +89,6 @@ QtObject {
     /*! The delegate of the column. This can be used to set the
     \l TableView::itemDelegate for a specific column. */
     property Component delegate
+
+    Accessible.role: Accessible.ColumnHeader
 }
diff --git a/tests/auto/controls/data/tableview/table_dynamiccolumns.qml b/tests/auto/controls/data/tableview/table_dynamiccolumns.qml
new file mode 100644
index 000000000..ce2cb93c4
--- /dev/null
+++ b/tests/auto/controls/data/tableview/table_dynamiccolumns.qml
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module 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 Digia Plc 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.1
+import QtQuick.Controls 1.0
+
+TableView {
+    id: tableView
+
+    TableViewColumn { title: "static" }
+
+    Component {
+        id: component
+        TableViewColumn { }
+    }
+
+    Component.onCompleted: {
+        addColumn(component.createObject(tableView, {title: "added item"}))
+
+        var col1 = addColumn(component)
+        col1.title = "added component"
+
+        insertColumn(0, component.createObject(tableView, {title: "inserted item"}))
+
+        var col2 = insertColumn(0, component)
+        col2.title = "inserted component"
+    }
+}
diff --git a/tests/auto/controls/data/tst_tableview.qml b/tests/auto/controls/data/tst_tableview.qml
index 6de8d5779..d092ae9d5 100644
--- a/tests/auto/controls/data/tst_tableview.qml
+++ b/tests/auto/controls/data/tst_tableview.qml
@@ -55,6 +55,11 @@ TestCase {
     width:400
     height:400
 
+    Component {
+        id: newColumn
+        TableViewColumn { }
+    }
+
     function test_usingqmlmodel_data() {
         return [
                     {tag: "listmodel", a: "tableview/table5_listmodel.qml", expected: "A"},
@@ -173,7 +178,14 @@ TestCase {
         compare(component.status, Component.Ready)
         var table =  component.createObject(container);
         verify(table !== null, "table created is null")
-        table.forceActiveFocus();
+
+        // wait for items to be created
+        var timeout = 2000
+        while (timeout >= 0 && table.rowAt(15, 55) === -1) {
+            timeout -= 50
+            wait(50)
+        }
+
         compare(table.test, 0)
         mouseClick(table, 15 , 55, Qt.LeftButton)
         compare(table.test, 1)
@@ -228,6 +240,128 @@ TestCase {
         table.destroy()
     }
 
+    function test_dynamicColumns() {
+        var component = Qt.createComponent("tableview/table_dynamiccolumns.qml")
+        compare(component.status, Component.Ready)
+        var table = component.createObject(container)
+
+        // insertColumn(component), insertColumn(item),
+        // addColumn(component), addColumn(item), and static TableViewColumn {}
+        compare(table.columnCount, 5)
+        compare(table.getColumn(0).title, "inserted component")
+        compare(table.getColumn(1).title, "inserted item")
+        table.destroy()
+    }
+
+    function test_addRemoveColumn() {
+        var tableView = Qt.createQmlObject('import QtQuick 2.1; import QtQuick.Controls 1.0; TableView { }', testCase, '');
+        compare(tableView.columnCount, 0)
+        tableView.addColumn(newColumn.createObject(testCase, {title: "title 1"}))
+        compare(tableView.columnCount, 1)
+        tableView.addColumn(newColumn.createObject(testCase, {title: "title 2"}))
+        compare(tableView.columnCount, 2)
+        compare(tableView.getColumn(0).title, "title 1")
+        compare(tableView.getColumn(1).title, "title 2")
+
+        tableView.insertColumn(1, newColumn.createObject(testCase, {title: "title 3"}))
+        compare(tableView.columnCount, 3)
+        compare(tableView.getColumn(0).title, "title 1")
+        compare(tableView.getColumn(1).title, "title 3")
+        compare(tableView.getColumn(2).title, "title 2")
+
+        tableView.insertColumn(0, newColumn.createObject(testCase, {title: "title 4"}))
+        compare(tableView.columnCount, 4)
+        compare(tableView.getColumn(0).title, "title 4")
+        compare(tableView.getColumn(1).title, "title 1")
+        compare(tableView.getColumn(2).title, "title 3")
+        compare(tableView.getColumn(3).title, "title 2")
+
+        tableView.removeColumn(0)
+        compare(tableView.columnCount, 3)
+        compare(tableView.getColumn(0).title, "title 1")
+        compare(tableView.getColumn(1).title, "title 3")
+        compare(tableView.getColumn(2).title, "title 2")
+
+        tableView.removeColumn(1)
+        compare(tableView.columnCount, 2)
+        compare(tableView.getColumn(0).title, "title 1")
+        compare(tableView.getColumn(1).title, "title 2")
+
+        tableView.removeColumn(1)
+        compare(tableView.columnCount, 1)
+        compare(tableView.getColumn(0).title, "title 1")
+
+        tableView.removeColumn(0)
+        compare(tableView.columnCount, 0)
+        tableView.destroy()
+    }
+
+    function test_moveColumn_data() {
+        return [
+            {tag:"0->1 (0)", from: 0, to: 1},
+            {tag:"0->1 (1)", from: 0, to: 1},
+            {tag:"0->1 (2)", from: 0, to: 1},
+
+            {tag:"0->2 (0)", from: 0, to: 2},
+            {tag:"0->2 (1)", from: 0, to: 2},
+            {tag:"0->2 (2)", from: 0, to: 2},
+
+            {tag:"1->0 (0)", from: 1, to: 0},
+            {tag:"1->0 (1)", from: 1, to: 0},
+            {tag:"1->0 (2)", from: 1, to: 0},
+
+            {tag:"1->2 (0)", from: 1, to: 2},
+            {tag:"1->2 (1)", from: 1, to: 2},
+            {tag:"1->2 (2)", from: 1, to: 2},
+
+            {tag:"2->0 (0)", from: 2, to: 0},
+            {tag:"2->0 (1)", from: 2, to: 0},
+            {tag:"2->0 (2)", from: 2, to: 0},
+
+            {tag:"2->1 (0)", from: 2, to: 1},
+            {tag:"2->1 (1)", from: 2, to: 1},
+            {tag:"2->1 (2)", from: 2, to: 1},
+
+            {tag:"0->0", from: 0, to: 0},
+            {tag:"-1->0", from: -1, to: 0},
+            {tag:"0->-1", from: 0, to: -1},
+            {tag:"1->10", from: 1, to: 10},
+            {tag:"10->2", from: 10, to: 2},
+            {tag:"10->-1", from: 10, to: -1}
+        ]
+    }
+
+    function test_moveColumn(data) {
+        var tableView = Qt.createQmlObject('import QtQuick 2.1; import QtQuick.Controls 1.0; TableView { }', testCase, '');
+        compare(tableView.columnCount, 0)
+
+        var titles = ["title 1", "title 2", "title 3"]
+
+        var i = 0;
+        for (i = 0; i < titles.length; ++i)
+            tableView.addColumn(newColumn.createObject(testCase, {title: titles[i]}))
+
+        compare(tableView.columnCount, titles.length)
+        for (i = 0; i < tableView.columnCount; ++i)
+            compare(tableView.getColumn(i).title, titles[i])
+
+        tableView.moveColumn(data.from, data.to)
+
+        compare(tableView.columnCount, titles.length)
+
+        if (data.from >= 0 && data.from < tableView.columnCount && data.to >= 0 && data.to < tableView.columnCount) {
+            var title = titles[data.from]
+            titles.splice(data.from, 1)
+            titles.splice(data.to, 0, title)
+        }
+
+        compare(tableView.columnCount, titles.length)
+        for (i = 0; i < tableView.columnCount; ++i)
+            compare(tableView.getColumn(i).title, titles[i])
+
+        tableView.destroy()
+    }
+
     // In TableView, drawn text = table.__currentRowItem.children[1].children[1].itemAt(0).children[0].children[0].text
 
     function findAChild(item, name)
-- 
GitLab