summaryrefslogtreecommitdiff
path: root/src/extras/Tumbler.qml
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@theqtcompany.com>2015-02-12 13:55:22 +0100
committerJ-P Nurmi <jpnurmi@theqtcompany.com>2015-02-13 12:01:33 +0000
commit6f15c206b069ed0fcf48a285bfcc4ad636927df0 (patch)
tree6150e262bc2d049219981a4cb1e1deeeeed5d636 /src/extras/Tumbler.qml
parentd28a02aec9a1632f2263d9276099454b33fb6741 (diff)
downloadqtquickcontrols-6f15c206b069ed0fcf48a285bfcc4ad636927df0.tar.gz
Import Qt Quick Extras (the former Qt Quick Enterprise Controls)
Change-Id: I59c5c97c564f707da4ce617e25e13ff8124f7d4b Reviewed-by: J-P Nurmi <jpnurmi@theqtcompany.com>
Diffstat (limited to 'src/extras/Tumbler.qml')
-rw-r--r--src/extras/Tumbler.qml464
1 files changed, 464 insertions, 0 deletions
diff --git a/src/extras/Tumbler.qml b/src/extras/Tumbler.qml
new file mode 100644
index 00000000..cdf21c4d
--- /dev/null
+++ b/src/extras/Tumbler.qml
@@ -0,0 +1,464 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Extras module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.2
+import QtQuick.Controls 1.0
+import QtQuick.Controls.Private 1.0
+import QtQuick.Extras 1.3
+import QtQuick.Extras.Private 1.0
+import QtQuick.Extras.Styles 1.3
+import QtQuick.Layouts 1.0
+
+/*!
+ \qmltype Tumbler
+ \inqmlmodule QtQuick.Extras
+ \since QtQuick.Extras 1.2
+ \ingroup extras
+ \ingroup extras-interactive
+ \brief A control that can have several spinnable wheels, each with items
+ that can be selected.
+
+ \image tumbler.png A Tumbler
+
+ \note Tumbler requires Qt 5.3.2 or later.
+
+ The Tumbler control is used with one or more TumblerColumn items, which
+ define the content of each column:
+
+ \code
+ Tumbler {
+ TumblerColumn {
+ model: 5
+ }
+ TumblerColumn {
+ model: [0, 1, 2, 3, 4]
+ }
+ TumblerColumn {
+ model: ["A", "B", "C", "D", "E]
+ }
+ }
+ \endcode
+
+ You can also use a traditional model with roles:
+
+ \code
+ Rectangle {
+ width: 220
+ height: 350
+ color: "#494d53"
+
+ ListModel {
+ id: listModel
+
+ ListElement {
+ foo: "A"
+ bar: "B"
+ baz: "C"
+ }
+ ListElement {
+ foo: "A"
+ bar: "B"
+ baz: "C"
+ }
+ ListElement {
+ foo: "A"
+ bar: "B"
+ baz: "C"
+ }
+ }
+
+ Tumbler {
+ anchors.centerIn: parent
+
+ TumblerColumn {
+ model: listModel
+ role: "foo"
+ }
+ TumblerColumn {
+ model: listModel
+ role: "bar"
+ }
+ TumblerColumn {
+ model: listModel
+ role: "baz"
+ }
+ }
+ }
+ \endcode
+
+ \section1 Limitations
+
+ For technical reasons, the model count must be equal to or greater than
+ \l {QtQuick.Extras.Styles::TumblerStyle}{visibleItemCount}
+ plus one. The
+ \l {QtQuick.Extras.Styles::TumblerStyle::}{visibleItemCount}
+ must also be an odd number.
+
+ You can create a custom appearance for a Tumbler by assigning a
+ \l {QtQuick.Extras.Styles::}{TumblerStyle}. To style
+ individual columns, use the \l {TumblerColumn::delegate}{delegate} and
+ \l {TumblerColumn::highlight}{highlight} properties of TumblerColumn.
+*/
+
+Control {
+ id: tumbler
+
+ style: Qt.createComponent(StyleSettings.style + "/TumblerStyle.qml", tumbler)
+
+ ListModel {
+ id: columnModel
+ }
+
+ /*!
+ \qmlproperty int Tumbler::columnCount
+
+ The number of columns in the Tumbler.
+ */
+ readonly property alias columnCount: columnModel.count
+
+ /*! \internal */
+ function __isValidColumnIndex(index) {
+ return index >= 0 && index < columnCount/* && columnRepeater.children.length === columnCount*/;
+ }
+
+ /*! \internal */
+ function __isValidColumnAndItemIndex(columnIndex, itemIndex) {
+ return __isValidColumnIndex(columnIndex) && itemIndex >= 0 && itemIndex < __viewAt(columnIndex).count;
+ }
+
+ /*!
+ Returns the current index of the column at \a columnIndex, or \c null
+ if the \a index is invalid.
+ */
+ function currentIndexAt(columnIndex) {
+ if (!__isValidColumnIndex(columnIndex))
+ return -1;
+
+ return columnModel.get(columnIndex).columnObject.currentIndex;
+ }
+
+ /*!
+ Sets the current index of the column at \a columnIndex to \a itemIndex.
+
+ Does nothing if \a columnIndex or \a itemIndex are invalid.
+ */
+ function setCurrentIndexAt(columnIndex, itemIndex) {
+ if (!__isValidColumnAndItemIndex(columnIndex, itemIndex))
+ return;
+
+ var view = columnRepeater.itemAt(columnIndex).view;
+ if (view.currentIndex !== itemIndex) {
+ // Hack to work around the pathview jumping when the index is changed.
+ // TODO: doesn't seem to be necessary anymore?
+ view.highlightMoveDuration = 0;
+ view.currentIndex = itemIndex;
+ view.highlightMoveDuration = Qt.binding(function(){ return __highlightMoveDuration; });
+ }
+ }
+
+ /*!
+ Returns the column at \a columnIndex or \c null if the \a index is
+ invalid.
+ */
+ function getColumn(columnIndex) {
+ if (!__isValidColumnIndex(columnIndex))
+ return null;
+
+ return columnModel.get(columnIndex).columnObject;
+ }
+
+ /*!
+ Adds a \a column and returns the added column.
+
+ The \a column argument can be an instance of TumblerColumn,
+ or a Component. The component has to contain a TumblerColumn.
+ 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 TumblerColumn,
+ or a Component. The component has to contain a TumblerColumn.
+ Otherwise, \c null is returned.
+ */
+ function insertColumn(index, column) {
+ var object = column;
+ if (typeof column["createObject"] === "function") {
+ object = column.createObject(root);
+ } else if (object.__tumbler) {
+ console.warn("Tumbler::insertColumn(): you cannot add a column to multiple Tumblers")
+ return null;
+ }
+ if (index >= 0 && index <= columnCount && object.Accessible.role === Accessible.ColumnHeader) {
+ object.__tumbler = tumbler;
+ object.__index = index;
+ columnModel.insert(index, { columnObject: object });
+ return object;
+ }
+
+ if (object !== column)
+ object.destroy();
+ console.warn("Tumbler::insertColumn(): invalid argument");
+ return null;
+ }
+
+ /*
+ Try making one selection bar by invisible highlight item hack, so that bars go across separators
+ */
+
+ Component.onCompleted: {
+ for (var i = 0; i < data.length; ++i) {
+ var column = data[i];
+ if (column.Accessible.role === Accessible.ColumnHeader)
+ addColumn(column);
+ }
+ }
+
+ /*! \internal */
+ readonly property alias __columnRow: columnRow
+ /*! \internal */
+ property int __highlightMoveDuration: 300
+
+ /*! \internal */
+ function __viewAt(index) {
+ if (!__isValidColumnIndex(index))
+ return null;
+
+ return columnRepeater.itemAt(index).view;
+ }
+
+ /*! \internal */
+ readonly property alias __movementDelayTimer: movementDelayTimer
+
+ // When the up/down arrow keys are held down on a PathView,
+ // the movement of the items is limited to the highlightMoveDuration,
+ // but there is no built-in guard against trying to move the items at
+ // the speed of the auto-repeat key presses. This results in sluggish
+ // movement, so we enforce a delay with a timer to avoid this.
+ Timer {
+ id: movementDelayTimer
+ interval: __highlightMoveDuration
+ }
+
+ Loader {
+ id: backgroundLoader
+ sourceComponent: __style.background
+ anchors.fill: columnRow
+ }
+
+ Loader {
+ id: frameLoader
+ sourceComponent: __style.frame
+ anchors.fill: columnRow
+ anchors.leftMargin: -__style.padding.left
+ anchors.rightMargin: -__style.padding.right
+ anchors.topMargin: -__style.padding.top
+ anchors.bottomMargin: -__style.padding.bottom
+ }
+
+ Row {
+ id: columnRow
+ x: __style.padding.left
+ y: __style.padding.top
+
+ Repeater {
+ id: columnRepeater
+ model: columnModel
+ delegate: Item {
+ id: columnItem
+ width: columnPathView.width + separatorDelegateLoader.width
+ height: columnPathView.height
+
+ readonly property int __columnIndex: index
+ // For index-related functions and tests.
+ readonly property alias view: columnPathView
+ readonly property alias separator: separatorDelegateLoader.item
+
+ PathView {
+ id: columnPathView
+ width: columnObject.width
+ height: tumbler.height - tumbler.__style.padding.top - tumbler.__style.padding.bottom
+ visible: columnObject.visible
+ clip: true
+
+ Binding {
+ target: columnObject
+ property: "__currentIndex"
+ value: columnPathView.currentIndex
+ }
+
+ // We add one here so that the delegate's don't just appear in the view instantly,
+ // but rather come from the top/bottom. To account for this adjustment elsewhere,
+ // we extend the path height by half an item's height at the top and bottom.
+ pathItemCount: tumbler.__style.visibleItemCount + 1
+ preferredHighlightBegin: 0.5
+ preferredHighlightEnd: 0.5
+ highlightMoveDuration: tumbler.__highlightMoveDuration
+ highlight: Loader {
+ id: highlightLoader
+ objectName: "highlightLoader"
+ sourceComponent: columnObject.highlight ? columnObject.highlight : __style.highlight
+ width: columnPathView.width
+
+ readonly property int __index: index
+
+ property QtObject styleData: QtObject {
+ readonly property alias index: highlightLoader.__index
+ readonly property int column: columnItem.__columnIndex
+ readonly property bool activeFocus: columnPathView.activeFocus
+ }
+ }
+ dragMargin: width / 2
+
+ activeFocusOnTab: true
+ Keys.onDownPressed: {
+ if (!movementDelayTimer.running) {
+ columnPathView.incrementCurrentIndex();
+ movementDelayTimer.start();
+ }
+ }
+ Keys.onUpPressed: {
+ if (!movementDelayTimer.running) {
+ columnPathView.decrementCurrentIndex();
+ movementDelayTimer.start();
+ }
+ }
+
+ path: Path {
+ startX: columnPathView.width / 2
+ startY: -tumbler.__style.__delegateHeight / 2
+ PathLine {
+ x: columnPathView.width / 2
+ y: columnPathView.pathItemCount * tumbler.__style.__delegateHeight - tumbler.__style.__delegateHeight / 2
+ }
+ }
+
+ model: columnObject.model
+
+ delegate: Item {
+ id: delegateRootItem
+ property var itemModel: model
+
+ implicitWidth: itemDelegateLoader.width
+ implicitHeight: itemDelegateLoader.height
+
+ Loader {
+ id: itemDelegateLoader
+ sourceComponent: columnObject.delegate ? columnObject.delegate : __style.delegate
+ width: columnObject.width
+
+ onHeightChanged: tumbler.__style.__delegateHeight = height;
+
+ property var model: itemModel
+
+ readonly property var __modelData: modelData
+ readonly property int __columnDelegateIndex: index
+ property QtObject styleData: QtObject {
+ readonly property var modelData: itemDelegateLoader.__modelData
+ readonly property alias index: itemDelegateLoader.__columnDelegateIndex
+ readonly property int column: columnItem.__columnIndex
+ readonly property bool activeFocus: columnPathView.activeFocus
+ readonly property real displacement: {
+ var count = delegateRootItem.PathView.view.count;
+ var offset = delegateRootItem.PathView.view.offset;
+
+ var d = count - index - offset;
+ var halfVisibleItems = Math.floor(tumbler.__style.visibleItemCount / 2) + 1;
+ if (d > halfVisibleItems)
+ d -= count;
+ else if (d < -halfVisibleItems)
+ d += count;
+ return d;
+ }
+ readonly property bool current: delegateRootItem.PathView.isCurrentItem
+ readonly property string role: columnObject.role
+ readonly property var value: (itemModel && itemModel.hasOwnProperty(role))
+ ? itemModel[role] // Qml ListModel and QAbstractItemModel
+ : modelData && modelData.hasOwnProperty(role)
+ ? modelData[role] // QObjectList/QObject
+ : modelData != undefined ? modelData : "" // Models without role
+ }
+ }
+ }
+ }
+
+ Loader {
+ anchors.fill: columnPathView
+ sourceComponent: columnObject.columnForeground ? columnObject.columnForeground : __style.columnForeground
+
+ property QtObject styleData: QtObject {
+ readonly property int column: columnItem.__columnIndex
+ readonly property bool activeFocus: columnPathView.activeFocus
+ }
+ }
+
+ Loader {
+ id: separatorDelegateLoader
+ objectName: "separatorDelegateLoader"
+ sourceComponent: __style.separator
+ // Don't need a separator after the last delegate.
+ active: __columnIndex < tumbler.columnCount - 1
+ anchors.left: columnPathView.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ visible: columnObject.visible
+
+ // Use the width of the first separator to help us
+ // determine the default separator width.
+ onWidthChanged: {
+ if (__columnIndex == 0) {
+ tumbler.__style.__separatorWidth = width;
+ }
+ }
+
+ property QtObject styleData: QtObject {
+ readonly property int index: __columnIndex
+ }
+ }
+ }
+ }
+ }
+
+ Loader {
+ id: foregroundLoader
+ sourceComponent: __style.foreground
+ anchors.fill: backgroundLoader
+ }
+}