--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 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 1.1
+import Qt.labs.components 1.1
+
+Item {
+ id: slider
+
+ //
+ // Common API
+ //
+ property int orientation: Qt.Horizontal
+ property alias minimumValue: range.minimumValue
+ property alias maximumValue: range.maximumValue
+ property alias pressed: mouseArea.pressed
+ property alias stepSize: range.stepSize
+ property alias platformMouseAnchors: mouseArea.anchors
+
+ // NOTE: this property is in/out, the user can set it, create bindings to it, and
+ // at the same time the slider wants to update. There's no way in QML to do this kind
+ // of updates AND allow the user bind it (without a Binding object). That's the
+ // reason this is an alias to a C++ property in range model.
+ property alias value: range.value
+
+ //
+ // Public extensions
+ //
+ property alias inverted: range.inverted
+
+ // Value indicator displays the current value near the slider
+ // if valueIndicatorText == "", a default formating will be applied
+ property string valueIndicatorText: formatValue(range.value)
+ property bool valueIndicatorVisible: false
+
+ property int valueIndicatorMargin: 1
+ property string valueIndicatorPosition: __isVertical ? "Left" : "Top"
+
+ // The default implementation for label hides decimals until it hits a
+ // floating point value at which point it keeps decimals
+ property bool __useDecimals: false
+ function formatValue(v) {
+ return __useDecimals ? (v.toFixed(2)) : v;
+ }
+
+ //
+ // "Protected" properties
+ //
+
+ // Hooks for customizing the pieces of the slider
+ property Item __grooveItem
+ property Item __valueTrackItem
+ property Item __handleItem
+ property Item __valueIndicatorItem
+
+ property bool __isVertical: orientation == Qt.Vertical
+
+ implicitWidth: 400
+ implicitHeight: handle.height
+
+ width: __isVertical ? implicitHeight : implicitWidth
+ height: __isVertical ? implicitWidth : implicitHeight
+
+ // This is a template slider, so every piece can be modified by passing a
+ // different Component. The main elements in the implementation are
+ //
+ // - the 'range' does the calculations to map position to/from value,
+ // it also serves as a data storage for both properties;
+ //
+ // - the 'fakeHandle' is what the mouse area drags on the screen, it feeds
+ // the 'range' position and also reads it when convenient;
+ //
+ // - the real 'handle' it is the visual representation of the handle, that
+ // just follows the 'fakeHandle' position.
+ //
+ // Everything is encapsulated in a contents Item, so for the
+ // vertical slider, we just swap the height/width, make it
+ // horizontal, and then use rotation to make it vertical again.
+
+ Component.onCompleted: {
+ __grooveItem.parent = groove;
+ __valueTrackItem.parent = valueTrack;
+ __handleItem.parent = handle;
+ __valueIndicatorItem.parent = valueIndicator;
+ }
+
+ Item {
+ id: contents
+
+ width: __isVertical ? slider.height : slider.width
+ height: __isVertical ? slider.width : slider.height
+ rotation: __isVertical ? -90 : 0
+
+ anchors.centerIn: slider
+
+ RangeModel {
+ id: range
+ minimumValue: 0.0
+ maximumValue: 1.0
+ value: 0
+ stepSize: 0.0
+ onValueChanged: {
+ // XXX: Moved that outside formatValue to get rid of binding loop warnings
+ var v = range.value
+ if (parseInt(v) != v)
+ __useDecimals = true;
+ }
+ positionAtMinimum: handle.width / 2
+ positionAtMaximum: contents.width - handle.width / 2
+ onMaximumChanged: __useDecimals = false;
+ onMinimumChanged: __useDecimals = false;
+ onStepSizeChanged: __useDecimals = false;
+ }
+
+ Item {
+ id: groove
+ anchors.fill: parent
+ anchors.leftMargin: handle.width / 2
+ anchors.rightMargin: handle.width / 2
+ }
+
+ Item {
+ id: valueTrack
+
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: groove.left
+ anchors.right: handle.horizontalCenter
+ anchors.rightMargin: handle.width / 2
+
+ states: State {
+ when: slider.inverted
+ PropertyChanges {
+ target: valueTrack
+ anchors.rightMargin: 0
+ anchors.leftMargin: - handle.width / 2
+ }
+ AnchorChanges {
+ target: valueTrack
+ anchors.left: handle.horizontalCenter
+ anchors.right: groove.right
+ }
+ }
+ }
+
+ Item {
+ id: handle
+ transform: Translate { x: - handle.width / 2 }
+ rotation: __isVertical ? 90 : 0
+
+ anchors.verticalCenter: parent.verticalCenter
+
+ width: __handleItem.width
+ height: __handleItem.height
+
+ x: fakeHandle.x
+ Behavior on x {
+ id: behavior
+ enabled: !mouseArea.drag.active
+
+ PropertyAnimation {
+ duration: behavior.enabled ? 150 : 0
+ easing.type: Easing.OutSine
+ }
+ }
+ }
+
+ Item {
+ id: fakeHandle
+ width: handle.width
+ height: handle.height
+ transform: Translate { x: - handle.width / 2 }
+ }
+
+ MouseArea {
+ id: mouseArea
+ property real oldPosition: 0
+ anchors {
+ fill: parent
+ rightMargin: platformStyle.mouseMarginRight
+ leftMargin: platformStyle.mouseMarginLeft
+ topMargin: platformStyle.mouseMarginTop
+ bottomMargin: platformStyle.mouseMarginBottom
+ }
+
+ drag.target: fakeHandle
+ drag.axis: Drag.XAxis
+ drag.minimumX: range.positionAtMinimum
+ drag.maximumX: range.positionAtMaximum
+
+ onPressed: {
+ oldPosition = range.position;
+ // Clamp the value
+ var newX = Math.max(mouse.x + anchors.leftMargin, drag.minimumX);
+ newX = Math.min(newX, drag.maximumX);
+
+ // Debounce the press: a press event inside the handler will not
+ // change its position, the user needs to drag it.
+ if (Math.abs(newX - fakeHandle.x) > handle.width / 2)
+ range.position = newX;
+ }
+
+ onCanceled: {
+ range.position = oldPosition;
+ }
+ }
+
+ Item {
+ id: valueIndicator
+
+ transform: Translate {
+ x: - handle.width / 2;
+ y: __isVertical? -(__valueIndicatorItem.width/2)+20 : y ;
+ }
+
+ rotation: __isVertical ? 90 : 0
+ visible: valueIndicatorVisible
+
+ width: __valueIndicatorItem.width //+ (__isVertical? (handle.width/2) : 0 )
+ height: __valueIndicatorItem.height
+
+ state: {
+ if (!__isVertical)
+ return slider.valueIndicatorPosition;
+
+ if (valueIndicatorPosition == "Right")
+ return "Bottom";
+ if (valueIndicatorPosition == "Top")
+ return "Right";
+ if (valueIndicatorPosition == "Bottom")
+ return "Left";
+
+ return "Top";
+ }
+
+ anchors.margins: valueIndicatorMargin
+
+ states: [
+ State {
+ name: "Top"
+ AnchorChanges {
+ target: valueIndicator
+ anchors.bottom: handle.top
+ anchors.horizontalCenter: handle.horizontalCenter
+ }
+ },
+ State {
+ name: "Bottom"
+ AnchorChanges {
+ target: valueIndicator
+ anchors.top: handle.bottom
+ anchors.horizontalCenter: handle.horizontalCenter
+ }
+ },
+ State {
+ name: "Right"
+ AnchorChanges {
+ target: valueIndicator
+ anchors.left: handle.right
+ anchors.verticalCenter: handle.verticalCenter
+ }
+ },
+ State {
+ name: "Left"
+ AnchorChanges {
+ target: valueIndicator
+ anchors.right: handle.left
+ anchors.verticalCenter: handle.verticalCenter
+ }
+ }
+ ]
+ }
+ }
+
+ // when there is no mouse interaction, the handle's position binds to the value
+ Binding {
+ when: !mouseArea.drag.active
+ target: fakeHandle
+ property: "x"
+ value: range.position
+ }
+
+ // when the slider is dragged, the value binds to the handle's position
+ Binding {
+ when: mouseArea.drag.active
+ target: range
+ property: "position"
+ value: fakeHandle.x
+ }
+}