summaryrefslogtreecommitdiffstats
path: root/src/controls/TableView.qml
diff options
context:
space:
mode:
Diffstat (limited to 'src/controls/TableView.qml')
-rw-r--r--src/controls/TableView.qml676
1 files changed, 676 insertions, 0 deletions
diff --git a/src/controls/TableView.qml b/src/controls/TableView.qml
new file mode 100644
index 000000000..bcfc8d8b1
--- /dev/null
+++ b/src/controls/TableView.qml
@@ -0,0 +1,676 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Components project.
+**
+** $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.0
+import QtQuick.Controls 1.0
+import QtQuick.Controls.Private 1.0
+
+/*!
+ \qmltype TableView
+ \inqmlmodule QtQuick.Controls 1.0
+ \ingroup views
+ \brief Provides a list view with scroll bars, styling and header sections.
+
+ \image tableview.png
+
+ A TableView is similar to \l ListView and adds scroll bars, selection and
+ resizable header sections. As with \l ListView, data for each row is provided through a \l model:
+
+ \code
+ ListModel {
+ id: libraryModel
+ ListElement{ title: "A Masterpiece" ; author: "Gabriel" }
+ ListElement{ title: "Brilliance" ; author: "Jens" }
+ ListElement{ title: "Outstanding" ; author: "Frederik" }
+ }
+ \endcode
+
+ You provide title and size of a column header
+ by adding a \l TableViewColumn to the default \l header property
+ as demonstrated below.
+ \code
+
+ TableView {
+ TableViewColumn{ role: "title" ; title: "Title" ; width: 100 }
+ TableViewColumn{ role: "author" ; title: "Author" ; width: 200 }
+ model: libraryModel
+ }
+ \endcode
+
+ The header sections are attached to values in the \l model by defining
+ the model role they attach to. Each property in the model, will
+ then be shown in their corresponding column.
+
+ You can customize the look by overriding the \l itemDelegate,
+ \l rowDelegate or \l headerDelegate properties.
+
+ The view itself does not provide sorting. This has to
+ be done on the model itself. However you can provide sorting
+ on the model and enable sort indicators on headers.
+
+\list
+ \li sortColumn - The index of the currently selected sort header
+ \li sortIndicatorVisible - If sort indicators should be enabled
+ \li sortIndicatorDirection - "up" or "down" depending on state
+\endlist
+*/
+
+ScrollArea {
+ id: root
+
+ /*! This property holds the model providing data for the list.
+
+ The model provides the set of data that is used to create the items in the view.
+ Models can be created directly in QML using ListModel, XmlListModel or VisualItemModel,
+ or provided by C++ model classes. \sa ListView::model
+
+ Example model:
+
+ \code
+ model: ListModel {
+ ListElement{ column1: "value 1" ; column2: "value 2" }
+ ListElement{ column1: "value 3" ; column2: "value 4" }
+ }
+ \endcode */
+ property variant model
+
+ width: 200
+ height: 200
+
+ /*! \internal */
+ __scrollBarTopMargin: styleitem.style == "mac" ? headerrow.height : 0
+
+ /*! This property sets if the frame should paint the focus frame around its contents.
+ The default value is \c false.
+ \note Only certain platforms such as Mac OS X will be affected by this property */
+ property bool highlightOnFocus: false
+
+ /*! This property is set to \c true if the view alternates the row color.
+ The default value is \c true. */
+ property bool alternateRowColor: true
+
+ /*! This property determines if the header is visible.
+ The default value is \c true. */
+ property bool headerVisible: true
+
+ /*! This property defines a delegate to draw a specific cell.
+
+ In the item delegate you have access to the following special properties:
+ \list
+ \li itemHeight - default platform size of item
+ \li itemWidth - default platform width of item
+ \li itemSelected - if the row is currently selected
+ \li itemValue - The text for this item
+ \li itemForeground - The default text color for an item
+ \endlist
+ Example:
+ \code
+ itemDelegate: Item {
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ color: itemForeground
+ elide: Text.ElideRight
+ text: itemValue
+ }
+ }
+ \endcode */
+ property Component itemDelegate: standardDelegate
+
+ /*! This property defines a delegate to draw a row. */
+ property Component rowDelegate: rowDelegate
+
+ /*! This property defines a delegate to draw a header. */
+ property Component headerDelegate: headerDelegate
+
+ /*! \qmlproperty color TableView::backgroundColor
+
+ This property sets the background color of the viewport.
+ The default value is the base color of the SystemPalette. */
+ property alias backgroundColor: colorRect.color
+
+ /*! This property sets if the frame should be visible.
+ The default value is \c true. */
+ frame: true
+
+ /*! Index of the currently selected sort column
+ The default value is \c 0. */
+ property int sortColumn
+
+ /*! This property shows or hides the sort indicator
+ The default value is \c false.
+ \note The view itself does not sort the data. */
+ property bool sortIndicatorVisible: false
+
+ /*! This sets the sorting direction of the sort indicator
+ The allowed values are:
+ \list
+ \li "up"
+ \li "down" - the default
+ \endlist */
+ property string sortIndicatorDirection: "down"
+
+ /*! \qmlproperty Component TableView::header
+ This property contains the TableViewHeader items */
+ default property alias header: listView.columnheader
+
+ /*! \qmlproperty Component TableView::contentHeader
+ This is the content header of the TableView */
+ property alias contentHeader: listView.header
+
+ /*! \qmlproperty Component TableView::contentFooter
+ This is the content footer of the TableView */
+ property alias contentFooter: listView.footer
+
+ /*! \qmlproperty Item TableView::currentItem
+ This is the current item of the TableView */
+ property alias currentItem: listView.currentItem
+
+ /*! \qmlproperty int TableView::count
+ The current number of rows */
+ property alias count: listView.count
+
+ /*! \qmlproperty string TableView::section
+ The section of the view. \sa ListView::section */
+ readonly property alias section: listView.section
+
+ /*! \qmlproperty int TableView::currentIndex
+ The current row index of the view. */
+ property alias currentIndex: listView.currentIndex
+
+ Accessible.role: Accessible.Table
+
+ /*! \qmlsignal TableView::activated()
+ Emitted when a new row is selected by the user. */
+ signal activated
+
+ /*! \internal */
+ function __decrementCurrentIndex() {
+ __scroller.blockUpdates = true;
+ listView.decrementCurrentIndex();
+ __scroller.blockUpdates = false;
+ }
+
+ /*! \internal */
+ function __incrementCurrentIndex() {
+ __scroller.blockUpdates = true;
+ listView.incrementCurrentIndex();
+ __scroller.blockUpdates = false;
+ }
+
+ ListView {
+ id: listView
+ anchors.topMargin: tableHeader.height
+ anchors.fill: parent
+
+ flickableDirection: Flickable.HorizontalFlick
+ SystemPalette {
+ id: palette
+ colorGroup: enabled ? SystemPalette.Active : SystemPalette.Disabled
+ }
+
+ Rectangle {
+ id: colorRect
+ parent: viewport
+ anchors.fill: parent
+ color: palette.base
+ z: -1
+ }
+
+ StyleItem {
+ id: itemstyle
+ elementType: "item"
+ visible: false
+ }
+
+ MouseArea {
+ id: mousearea
+
+ anchors.fill: listView
+
+ property bool autoincrement: false
+ property bool autodecrement: false
+
+ onReleased: {
+ autoincrement = false
+ autodecrement = false
+ }
+
+ // Handle vertical scrolling whem dragging mouse outside boundraries
+ Timer { running: mousearea.autoincrement && __scroller.verticalScrollBar.visible; repeat: true; interval: 20 ; onTriggered: __incrementCurrentIndex()}
+ Timer { running: mousearea.autodecrement && __scroller.verticalScrollBar.visible; repeat: true; interval: 20 ; onTriggered: __decrementCurrentIndex()}
+
+ onPositionChanged: {
+ if (mouseY > listView.height && pressed) {
+ if (autoincrement) return;
+ autodecrement = false;
+ autoincrement = true;
+ } else if (mouseY < 0 && pressed) {
+ if (autodecrement) return;
+ autoincrement = false;
+ autodecrement = true;
+ } else {
+ autoincrement = false;
+ autodecrement = false;
+ }
+ var y = Math.min(flickableItem.contentY + listView.height - 5, Math.max(mouseY + flickableItem.contentY, flickableItem.contentY));
+ var newIndex = listView.indexAt(0, y);
+ if (newIndex >= 0)
+ listView.currentIndex = listView.indexAt(0, y);
+ }
+
+ onPressed: {
+ listView.forceActiveFocus()
+ var x = Math.min(flickableItem.contentWidth - 5, Math.max(mouseX + flickableItem.contentX, 0))
+ var y = Math.min(flickableItem.contentHeight - 5, Math.max(mouseY + flickableItem.contentY, 0))
+ listView.currentIndex = listView.indexAt(x, y)
+ }
+
+ onDoubleClicked: { root.activated() }
+
+ // Note by prevent stealing we are keeping the flickable from
+ // eating our mouse press events
+ preventStealing: true
+ }
+
+ // Fills extra rows with alternate color
+ Column {
+ id: rowfiller
+ property int rowHeight: flickableItem.contentHeight/count
+ property int paddedRowCount: height/rowHeight
+ property int count: flickableItem.count
+ y: flickableItem.contentHeight
+ width: parent.width
+ visible: flickableItem.contentHeight > 0 && alternateRowColor
+ height: viewport.height - flickableItem.contentHeight
+ Repeater {
+ model: visible ? parent.paddedRowCount : 0
+ Loader {
+ width: rowfiller.width
+ height: rowfiller.rowHeight
+ sourceComponent: root.rowDelegate
+ property bool itemAlternateBackground: (index + count) % 2 === 1
+ property bool itemSelected: false
+ property variant model: listView.model
+ property variant modelData: null
+ }
+ }
+ }
+
+ property list<TableViewColumn> columnheader
+ highlightFollowsCurrentItem: true
+ model: root.model
+
+ Keys.onUpPressed: root.decrementCurrentIndex()
+ Keys.onDownPressed: root.incrementCurrentIndex()
+
+ Keys.onPressed: {
+ if (event.key === Qt.Key_PageUp) {
+ verticalScrollBar.value = __scroller.verticalScrollBar.value - listView.height
+ } else if (event.key === Qt.Key_PageDown)
+ verticalScrollBar.value = __scroller.verticalScrollBar.value + listView.height
+ }
+
+ Keys.onReturnPressed: root.activated();
+
+ delegate: Item {
+ id: rowitem
+ width: row.width
+ height: rowstyle.height
+
+ property int rowIndex: model.index
+ property bool itemAlternateBackground: alternateRowColor && rowIndex % 2 == 1
+ property variant itemModelData: typeof modelData == "undefined" ? null : modelData
+ property variant itemModel: model
+
+ Loader {
+ id: rowstyle
+ // row delegate
+ sourceComponent: root.rowDelegate
+ // Row fills the view width regardless of item size
+ // But scrollbar should not adjust to it
+ width: parent.width + __scroller.horizontalScrollBar.width
+ x: flickableItem.contentX
+
+ property bool itemAlternateBackground: rowitem.itemAlternateBackground
+ property bool itemSelected: rowitem.ListView.isCurrentItem
+ property int index: rowitem.rowIndex
+ property variant model: listView.model
+ property variant modelData: rowitem.itemModelData
+ property variant itemModel: rowitem.itemModel
+ }
+ Row {
+ id: row
+ anchors.left: parent.left
+ height: parent.height
+ Repeater {
+ id: repeater
+ model: root.header.length
+ Loader {
+ id: itemDelegateLoader
+ height: parent.height
+ visible: header[index].visible
+ sourceComponent: header[index].delegate ? header[index].delegate : itemDelegate
+ property variant model: listView.model
+ property variant role: header[index].role
+ property variant modelData: itemModelData
+
+ width: header[index].width
+
+ function getValue() {
+ if (header[index].role.length && itemModel.hasOwnProperty(header[index].role))
+ return itemModel[header[index].role] // Qml ListModel and QAbstractItemModel
+ else if (modelData != undefined && modelData.hasOwnProperty(header[index].role))
+ return modelData[header[index].role] // QObjectList / QObject
+ else if (modelData != undefined)
+ return modelData // Models without role
+ else
+ return ""
+ }
+ property variant itemValue: getValue()
+ property bool itemSelected: rowitem.ListView.isCurrentItem
+ property color itemForeground: itemSelected ? rowstyleitem.highlightedTextColor : rowstyleitem.textColor
+ property int rowIndex: rowitem.rowIndex
+ property int columnIndex: index
+ property int itemElideMode: header[index].elideMode
+ property int itemTextAlignment: header[index].horizontalAlignment
+ }
+ }
+ onWidthChanged: listView.contentWidth = width
+ }
+ }
+
+ Text{ id:text }
+
+ Item {
+ id: tableHeader
+ clip: true
+ parent: __scroller
+ visible: headerVisible
+ anchors.top: parent.top
+ anchors.margins: viewport.anchors.margins
+ anchors.rightMargin: __scroller.frameWidth +
+ (__scroller.outerFrame && __scrollBarTopMargin ? 0 : __scroller.verticalScrollBar.width
+ + __scroller.scrollBarSpacing)
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ height: headerVisible ? headerrow.height : 0
+
+ Behavior on height { NumberAnimation{ duration: 80 } }
+
+ Row {
+ id: headerrow
+ x: -listView.contentX
+
+ Repeater {
+ id: repeater
+
+ property int targetIndex: -1
+ property int dragIndex: -1
+
+ model: header.length
+
+ delegate: Item {
+ z:-index
+ width: header[index].width
+ visible: header[index].visible
+ height: headerStyle.height
+
+ Loader {
+ id: headerStyle
+ sourceComponent: root.headerDelegate
+ anchors.left: parent.left
+ anchors.right: parent.right
+ property string itemValue: header[index].title
+ property string itemSort: (sortIndicatorVisible && index == sortColumn) ? (sortIndicatorDirection == "up" ? "up" : "down") : "";
+ property bool itemPressed: headerClickArea.pressed
+ property bool itemContainsMouse: headerClickArea.containsMouse
+ property string itemPosition: header.length === 1 ? "only" :
+ index===header.length-1 ? "end" :
+ index===0 ? "beginning" : ""
+ }
+ Rectangle{
+ id: targetmark
+ width: parent.width
+ height:parent.height
+ opacity: (index == repeater.targetIndex && repeater.targetIndex != repeater.dragIndex) ? 0.5 : 0
+ Behavior on opacity { NumberAnimation{duration:160}}
+ color: palette.highlight
+ }
+
+ MouseArea{
+ id: headerClickArea
+ drag.axis: Qt.YAxis
+ hoverEnabled: true
+ anchors.fill: parent
+ onClicked: {
+ if (sortColumn == index)
+ sortIndicatorDirection = sortIndicatorDirection === "up" ? "down" : "up"
+ sortColumn = index
+ }
+ // Here we handle moving header sections
+ // NOTE: the direction is different from the master branch
+ // so this indicates that I am using an invalid assumption on item ordering
+ onPositionChanged: {
+ if (pressed) { // only do this while dragging
+ for (var h = header.length-1 ; h >= 0 ; --h) {
+ if (drag.target.x > headerrow.children[h].x) {
+ repeater.targetIndex = h
+ break
+ }
+ }
+ }
+ }
+
+ onPressed: {
+ repeater.dragIndex = index
+ draghandle.x = parent.x
+ }
+
+ onReleased: {
+ if (repeater.targetIndex >= 0 && repeater.targetIndex != index ) {
+ // Rearrange the header sections
+ var items = new Array
+ for (var i = 0 ; i< header.length ; ++i)
+ items.push(header[i])
+ items.splice(index, 1);
+ items.splice(repeater.targetIndex, 0, header[index]);
+ header = items
+ if (sortColumn == index)
+ sortColumn = repeater.targetIndex
+ }
+ repeater.targetIndex = -1
+ }
+ drag.maximumX: 1000
+ drag.minimumX: -1000
+ drag.target: draghandle
+ }
+
+ Loader {
+ id: draghandle
+ property string itemValue: header[index].title
+ property string itemSort: (sortIndicatorVisible && index == sortColumn) ? (sortIndicatorDirection == "up" ? "up" : "down") : "";
+ property bool itemPressed: headerClickArea.pressed
+ property bool itemContainsMouse: headerClickArea.containsMouse
+ property string itemPosition
+
+ parent: tableHeader
+ width: header[index].width
+ height: parent.height
+ sourceComponent: root.headerDelegate
+ visible: headerClickArea.pressed
+ opacity: 0.5
+ }
+
+
+ MouseArea {
+ id: headerResizeHandle
+ property int offset: 0
+ property int minimumSize: 20
+ anchors.rightMargin: -width/2
+ width: 16 ; height: parent.height
+ anchors.right: parent.right
+ onPositionChanged: {
+ var newHeaderWidth = header[index].width + (mouseX - offset)
+ header[index].width = Math.max(minimumSize, newHeaderWidth)
+ }
+ property bool found:false
+
+ onDoubleClicked: {
+ var row
+ var minWidth = 0
+ var listdata = listView.children[0]
+ for (row = 0 ; row < listdata.children.length ; ++row){
+ var item = listdata.children[row+1]
+ if (item && item.children[1] && item.children[1].children[index] &&
+ item.children[1].children[index].children[0].hasOwnProperty("implicitWidth"))
+ minWidth = Math.max(minWidth, item.children[1].children[index].children[0].implicitWidth)
+ }
+ if (minWidth)
+ header[index].width = minWidth
+ }
+ onPressedChanged: if (pressed) offset=mouseX
+ cursorShape: Qt.SplitHCursor
+ }
+ }
+ }
+ }
+ Loader {
+ id: loader
+ property string itemValue
+ property string itemSort
+ property bool itemPressed
+ property bool itemContainsMouse
+ property string itemPosition
+
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.bottom: headerrow.bottom
+ anchors.rightMargin: -2
+ sourceComponent: root.headerDelegate
+ width: root.width - headerrow.width + 2
+ visible: root.header.length
+ z:-1
+ }
+
+ Component {
+ id: standardDelegate
+ Item {
+ height: Math.max(16, styleitem.implicitHeight)
+ property int implicitWidth: sizehint.paintedWidth + 4
+ Text {
+ id: label
+ objectName: "label"
+ width: parent.width
+ anchors.margins: 6
+ font: itemstyle.font
+ anchors.left: parent.left
+ anchors.right: parent.right
+ horizontalAlignment: itemTextAlignment
+ anchors.verticalCenter: parent.verticalCenter
+ elide: itemElideMode
+ text: itemValue != undefined ? itemValue : ""
+ color: itemForeground
+ renderType: Text.NativeRendering
+ }
+ Text {
+ id: sizehint
+ font: label.font
+ text: itemValue ? itemValue : ""
+ visible: false
+ }
+ }
+ }
+
+ Component {
+ id: nativeDelegate
+ // This gives more native styling, but might be less performant
+ StyleItem {
+ elementType: "item"
+ text: itemValue
+ selected: itemSelected
+ active: root.activeFocus
+ }
+ }
+
+ Component {
+ id: headerDelegate
+ StyleItem {
+ elementType: "header"
+ activeControl: itemSort
+ raised: true
+ sunken: itemPressed
+ text: itemValue
+ hover: itemContainsMouse
+ hints: itemPosition
+ }
+ }
+
+ Component {
+ id: rowDelegate
+ StyleItem {
+ id: rowstyle
+ elementType: "itemrow"
+ activeControl: itemAlternateBackground ? "alternate" : ""
+ selected: itemSelected ? true : false
+ height: Math.max(16, styleitem.implicitHeight)
+ active: root.activeFocus
+ }
+ }
+
+ StyleItem {
+ id: styleitem
+ elementType: "header"
+ visible:false
+ contentWidth: 16
+ contentHeight: font.pixelSize
+ }
+
+ StyleItem {
+ id: rowstyleitem
+ property color textColor: styleHint("textColor")
+ property color highlightedTextColor: styleHint("highlightedTextColor")
+ elementType: "item"
+ visible: false
+ }
+ }
+ }
+}