Files
qt-creator/lib/qtcreator/qtcomponents/TableView.qml
hjk 2931a499e6 Long live the king!
Change-Id: I2b72b34c0cfeafc8bdbaf49b83ff723544f2b6e2
Reviewed-by: Daniel Teske <daniel.teske@nokia.com>
2012-01-26 19:55:36 +01:00

605 lines
23 KiB
QML

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
import QtQuick 1.0
/*
*
* TableView
*
* This component provides an item-view with resizable
* header sections.
*
* You can style the drawn delegate by overriding the itemDelegate
* property. The following properties are supported for custom
* delegates:
*
* Note: Currently only row selection is available for this component
*
* itemheight - default platform size of item
* itemwidth - default platform width of item
* itemselected - if the row is currently selected
* itemvalue - The text for this item
* itemforeground - The default text color for an item
*
* For example:
* itemDelegate: Item {
* Text {
* anchors.verticalCenter: parent.verticalCenter
* color: itemForeground
* elide: Text.ElideRight
* text: itemValue
* }
* }
*
* Data for each row is provided through a model:
*
* ListModel {
* ListElement{ column1: "value 1"; column2: "value 2"}
* ListElement{ column1: "value 3"; column2: "value 4"}
* }
*
* You provide title and size properties on TableColumns
* by setting the default header property :
*
* TableView {
* TableColumn{ property: "column1" ; caption: "Column 1" ; width:100}
* TableColumn{ property: "column2" ; caption: "Column 2" ; width:200}
* model: datamodel
* }
*
* The header sections are attached to values in the datamodel by defining
* the listmodel property they attach to. Each property in the model, will
* then be shown in each column section.
*
* 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.
*
* sortColumn - The index of the currently selected sort header
* sortIndicatorVisible - If sort indicators should be enabled
* sortIndicatorDirection - "up" or "down" depending on state
*
*/
FocusScope{
id: root
property variant model
property int frameWidth: frame ? styleitem.pixelMetric("defaultframewidth") : 0;
property alias contentHeight : tree.contentHeight
property alias contentWidth: tree.contentWidth
property bool frame: true
property bool highlightOnFocus: false
property bool frameAroundContents: styleitem.styleHint("framearoundcontents")
property int sortColumn // Index of currently selected sort column
property bool sortIndicatorVisible: false // enables or disables sort indicator
property string sortIndicatorDirection: "down" // "up" or "down" depending on current state
property bool alternateRowColor: true
property alias contentX: tree.contentX
property alias contentY: tree.contentY
property alias currentIndex: tree.currentIndex // Should this be currentRowIndex?
property int headerHeight: headerrow.height
property Component itemDelegate: standardDelegate
property Component rowDelegate: rowDelegate
property Component headerDelegate: headerDelegate
property alias cacheBuffer: tree.cacheBuffer
property bool headerVisible: true
default property alias header: tree.header
signal activated
Component {
id: standardDelegate
Item {
property int implicitWidth: sizehint.paintedWidth + 4
Text {
width: parent.width
anchors.margins: 4
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
elide: itemElideMode
text: itemValue ? itemValue : ""
color: itemForeground
}
Text {
id: sizehint
text: itemValue ? itemValue : ""
visible:false
}
}
}
Component {
id: nativeDelegate
// This gives more native styling, but might be less performant
QStyleItem {
elementType: "item"
text: itemValue
selected: itemSelected
}
}
Component {
id: headerDelegate
QStyleItem {
elementType: "header"
activeControl: itemSort
raised: true
sunken: itemPressed
text: itemValue
hover: itemContainsMouse
}
}
Component {
id: rowDelegate
QStyleItem {
id: rowstyle
elementType: "itemrow"
activeControl: itemAlternateBackground ? "alternate" : ""
selected: itemSelected ? "true" : "false"
}
}
Rectangle {
id: colorRect
color: "white"
anchors.fill: frameitem
anchors.margins: frameWidth
anchors.rightMargin: (!frameAroundContents && vscrollbar.visible ? vscrollbar.width : 0) + frameWidth
anchors.bottomMargin: (!frameAroundContents && hscrollbar.visible ? hscrollbar.height : 0) +frameWidth
}
QStyleItem {
id: frameitem
elementType: "frame"
onElementTypeChanged: scrollarea.frameWidth = styleitem.pixelMetric("defaultframewidth");
sunken: true
visible: frame
anchors.fill: parent
anchors.rightMargin: frame ? (frameAroundContents ? (vscrollbar.visible ? vscrollbar.width + 2 * frameMargins : 0) : -frameWidth) : 0
anchors.bottomMargin: frame ? (frameAroundContents ? (hscrollbar.visible ? hscrollbar.height + 2 * frameMargins : 0) : -frameWidth) : 0
anchors.topMargin: frame ? (frameAroundContents ? 0 : -frameWidth) : 0
property int scrollbarspacing: styleitem.pixelMetric("scrollbarspacing");
property int frameMargins : frame ? scrollbarspacing : 0
}
MouseArea {
id: mousearea
anchors.fill: tree
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; repeat: true; interval: 30 ; onTriggered: tree.incrementCurrentIndex()}
Timer { running: mousearea.autodecrement; repeat: true; interval: 30 ; onTriggered: tree.decrementCurrentIndex()}
onMousePositionChanged: {
if (mouseY > tree.height) {
autodecrement = false
autoincrement = true
} else if (mouseY < 0) {
autoincrement = false
autodecrement = true
} else {
autoincrement = false
autodecrement = false
}
var y = Math.min(contentY + tree.height - 5, Math.max(mouseY + contentY, contentY))
var newIndex = tree.indexAt(0, y)
if (newIndex > 0)
tree.currentIndex = tree.indexAt(0, y)
}
onPressed: {
tree.forceActiveFocus()
var x = Math.min(contentWidth - 5, Math.max(mouseX + contentX, 0))
var y = Math.min(contentHeight - 5, Math.max(mouseY + contentY, 0))
tree.currentIndex = tree.indexAt(x, y)
}
onDoubleClicked: {
parent.activated()
}
}
ListView {
id: tree
property list<TableColumn> header
property bool blockUpdates: false
highlightFollowsCurrentItem: true
model: root.model
interactive: false
anchors.top: tableColumn.bottom
anchors.topMargin: -frameWidth
anchors.left: frameitem.left
anchors.right: frameitem.right
anchors.bottom: frameitem.bottom
anchors.margins: frameWidth
anchors.rightMargin: (!frameAroundContents && vscrollbar.visible ? vscrollbar.width: 0) + frameWidth
anchors.bottomMargin: (!frameAroundContents && hscrollbar.visible ? hscrollbar.height : 0) + frameWidth
focus: true
clip: true
Keys.onUpPressed: {
blockUpdates = true
if (currentIndex > 0) currentIndex = currentIndex - 1
wheelarea.verticalValue = contentY/wheelarea.scale
blockUpdates = false
}
Keys.onDownPressed: {
blockUpdates = true
if (currentIndex< count - 1) currentIndex = currentIndex + 1
wheelarea.verticalValue = contentY/wheelarea.scale
blockUpdates = false
}
Keys.onPressed: {
if (event.key == Qt.Key_PageUp) {
vscrollbar.value = vscrollbar.value - tree.height
} else if (event.key == Qt.Key_PageDown)
vscrollbar.value = vscrollbar.value + tree.height
}
onContentYChanged: {
// positionViewAtIndex(currentIndex, ListView.Visible)
// highlight follows item
blockUpdates = true
vscrollbar.value = tree.contentY
blockUpdates = false
}
delegate: Item {
id: rowitem
width: row.width
height: row.height
anchors.margins: frameWidth
property int rowIndex: model.index
property bool itemAlternateBackground: alternateRowColor && rowIndex % 2 == 1
Loader {
id: rowstyle
// row delegate
sourceComponent: root.rowDelegate
// Row fills the tree width regardless of item size
// But scrollbar should not adjust to it
width: frameitem.width
height: row.height
x: contentX
property bool itemAlternateBackground: rowitem.itemAlternateBackground
property bool itemSelected: rowitem.ListView.isCurrentItem
}
Row {
id: row
anchors.left: parent.left
Repeater {
id: repeater
model: root.header.length
Loader {
id: itemDelegateLoader
visible: header[index].visible
sourceComponent: itemDelegate
property variant model: tree.model
property variant itemProperty: header[index].property
width: header[index].width
height: item ? item.height : Math.max(16, styleitem.sizeFromContents(16, 16).height)
function getValue() {
if (index < header.length &&
root.model.get(rowIndex).hasOwnProperty(header[index].property))
return root.model.get(rowIndex)[ header[index].property]
}
property variant itemValue: root.model.get(rowIndex)[ header[index].property]
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
}
}
onWidthChanged: tree.contentWidth = width
}
}
}
Text{ id:text }
Item {
id: tableColumn
clip: true
anchors.top: frameitem.top
anchors.left: frameitem.left
anchors.right: frameitem.right
anchors.margins: frameWidth
visible: headerVisible
Behavior on height { NumberAnimation{duration:80}}
height: headerVisible ? styleitem.sizeFromContents(text.font.pixelSize, styleitem.fontHeight).height : frameWidth
Row {
id: headerrow
anchors.top: parent.top
height:parent.height
x: -tree.contentX
Repeater {
id: repeater
model: header.length
property int targetIndex: -1
property int dragIndex: -1
delegate: Item {
z:-index
width: header[index].width
visible: header[index].visible
height: headerrow.height
Loader {
sourceComponent: root.headerDelegate
anchors.fill: parent
property string itemValue: header[index].caption
property string itemSort: (sortIndicatorVisible && index == sortColumn) ? (sortIndicatorDirection == "up" ? "up" : "down") : "";
property bool itemPressed: headerClickArea.pressed
property bool itemContainsMouse: headerClickArea.containsMouse
}
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
onMousePositionChanged: {
if (pressed) { // only do this while dragging
for (var h = 0 ; h < header.length ; ++h) {
if (drag.target.x > headerrow.children[h].x - 10) {
repeater.targetIndex = header.length - h - 1
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
parent: tableColumn
sourceComponent: root.headerDelegate
width: header[index].width
height: parent.height
property string itemValue: header[index].caption
property string itemSort: (sortIndicatorVisible && index == sortColumn) ? (sortIndicatorDirection == "up" ? "up" : "down") : "";
property bool itemPressed: headerClickArea.pressed
property bool itemContainsMouse: headerClickArea.containsMouse
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 = tree.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
QStyleItem {
anchors.fill: parent
cursor: "splithcursor"
}
}
}
}
}
Loader {
id: loader
z:-1
sourceComponent: root.headerDelegate
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: headerrow.bottom
anchors.rightMargin: -2
width: root.width - headerrow.width
property string itemValue
property string itemSort
property bool itemPressed
property bool itemContainsMouse
}
}
WheelArea {
id: wheelarea
anchors.fill: parent
property int scale: 5
horizontalMinimumValue: hscrollbar.minimumValue/scale
horizontalMaximumValue: hscrollbar.maximumValue/scale
verticalMinimumValue: vscrollbar.minimumValue/scale
verticalMaximumValue: vscrollbar.maximumValue/scale
verticalValue: contentY/scale
horizontalValue: contentX/scale
onVerticalValueChanged: {
if(!tree.blockUpdates) {
contentY = verticalValue * scale
vscrollbar.value = contentY
}
}
onHorizontalValueChanged: {
if(!tree.blockUpdates) {
contentX = horizontalValue * scale
hscrollbar.value = contentX
}
}
}
ScrollBar {
id: hscrollbar
orientation: Qt.Horizontal
property int availableWidth: root.width - vscrollbar.width
visible: contentWidth > availableWidth
maximumValue: contentWidth > availableWidth ? tree.contentWidth - availableWidth : 0
minimumValue: 0
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: frameWidth
anchors.bottomMargin: styleitem.frameoffset
anchors.rightMargin: vscrollbar.visible ? scrollbarExtent : (frame ? 1 : 0)
onValueChanged: {
if (!tree.blockUpdates)
contentX = value
}
property int scrollbarExtent : styleitem.pixelMetric("scrollbarExtent");
}
ScrollBar {
id: vscrollbar
orientation: Qt.Vertical
// We cannot bind directly to tree.height due to binding loops so we have to redo the calculation here
property int availableHeight : root.height - (hscrollbar.visible ? hscrollbar.height : 0) - tableColumn.height
visible: contentHeight > availableHeight
maximumValue: contentHeight > availableHeight ? tree.contentHeight - availableHeight : 0
minimumValue: 0
anchors.rightMargin: styleitem.frameoffset
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: styleitem.style == "mac" ? tableColumn.height : 0
onValueChanged: {
if(!tree.blockUpdates)
contentY = value
}
anchors.bottomMargin: hscrollbar.visible ? hscrollbar.height : styleitem.frameoffset
Keys.onUpPressed: if (tree.currentIndex > 0) tree.currentIndex = tree.currentIndex - 1
Keys.onDownPressed: if (tree.currentIndex< tree.count - 1) tree.currentIndex = tree.currentIndex + 1
}
QStyleItem {
z: 2
anchors.fill: parent
anchors.margins: -4
visible: highlightOnFocus && parent.activeFocus && styleitem.styleHint("focuswidget")
elementType: "focusframe"
}
QStyleItem {
id: styleitem
elementType: "header"
visible:false
property int frameoffset: style === "mac" ? -1 : 0
}
QStyleItem {
id: rowstyleitem
elementType: "item"
visible:false
property color textColor: styleHint("textColor")
property color highlightedTextColor: styleHint("highlightedTextColor")
}
SystemPalette{id:palette}
}