1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Controls module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24**   * Redistributions of source code must retain the above copyright
25**     notice, this list of conditions and the following disclaimer.
26**   * Redistributions in binary form must reproduce the above copyright
27**     notice, this list of conditions and the following disclaimer in
28**     the documentation and/or other materials provided with the
29**     distribution.
30**   * Neither the name of The Qt Company Ltd nor the names of its
31**     contributors may be used to endorse or promote products derived
32**     from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51import QtQuick 2.1
52import QtQuick.Controls 1.1
53import QtQuick.Controls.Private 1.0
54
55/*!
56        \qmltype ScrollBar
57        \internal
58        \inqmlmodule QtQuick.Controls.Private
59*/
60Item {
61    id: scrollbar
62
63    property bool isTransient: false
64    property bool active: false
65    property int orientation: Qt.Horizontal
66    property alias minimumValue: slider.minimumValue
67    property alias maximumValue: slider.maximumValue
68    property alias value: slider.value
69    property int singleStep: 20
70
71    activeFocusOnTab: false
72
73    Accessible.role: Accessible.ScrollBar
74    implicitWidth: panelLoader.implicitWidth
75    implicitHeight: panelLoader.implicitHeight
76
77    property bool upPressed
78    property bool downPressed
79    property bool pageUpPressed
80    property bool pageDownPressed
81    property bool handlePressed
82
83
84    property Item __panel: panelLoader.item
85    Loader {
86        id: panelLoader
87        anchors.fill: parent
88        sourceComponent: __style ? __style.__scrollbar : null
89        onStatusChanged: if (status === Loader.Error) console.error("Failed to load Style for", root)
90        property alias __control: scrollbar
91        property QtObject __styleData: QtObject {
92            readonly property alias horizontal: internal.horizontal
93            readonly property alias upPressed: scrollbar.upPressed
94            readonly property alias downPressed: scrollbar.downPressed
95            readonly property alias handlePressed: scrollbar.handlePressed
96        }
97    }
98
99    MouseArea {
100        id: internal
101        property bool horizontal: orientation === Qt.Horizontal
102        property int pageStep: internal.horizontal ? width : height
103        property bool scrollToClickposition: internal.scrollToClickPosition
104        anchors.fill: parent
105        cursorShape: __panel.visible ? Qt.ArrowCursor : Qt.IBeamCursor // forces a cursor change
106
107        property bool autoincrement: false
108        property bool scrollToClickPosition: __style ? __style.scrollToClickedPosition : 0
109
110        // Update hover item
111        onEntered: if (!pressed) __panel.activeControl = __panel.hitTest(mouseX, mouseY)
112        onExited: if (!pressed) __panel.activeControl = "none"
113        onMouseXChanged: if (!pressed) __panel.activeControl = __panel.hitTest(mouseX, mouseY)
114        hoverEnabled: true
115
116        property var pressedX
117        property var pressedY
118        property int oldPosition
119        property int grooveSize
120
121        Timer {
122            running: upPressed || downPressed || pageUpPressed || pageDownPressed
123            interval: 350
124            onTriggered: internal.autoincrement = true
125        }
126
127        Timer {
128            running: internal.autoincrement
129            interval: 60
130            repeat: true
131            onTriggered: {
132                if (upPressed && internal.containsMouse)
133                    internal.decrement();
134                else if (downPressed && internal.containsMouse)
135                    internal.increment();
136                else if (pageUpPressed)
137                    internal.decrementPage();
138                else if (pageDownPressed)
139                    internal.incrementPage();
140            }
141        }
142
143        onPositionChanged: {
144            if (handlePressed) {
145                if (!horizontal)
146                    slider.position = oldPosition + (mouseY - pressedY)
147                else
148                    slider.position = oldPosition + (mouseX - pressedX)
149            }
150        }
151
152        onPressed: {
153            __panel.activeControl = __panel.hitTest(mouseX, mouseY)
154            scrollToClickposition = scrollToClickPosition
155            var handleRect = __panel.subControlRect("handle")
156            var grooveRect = __panel.subControlRect("groove")
157            grooveSize =  horizontal ? grooveRect.width - handleRect.width:
158                                       grooveRect.height - handleRect.height;
159            if (__panel.activeControl === "handle") {
160                pressedX = mouseX;
161                pressedY = mouseY;
162                handlePressed = true;
163                oldPosition = slider.position;
164            } else if (__panel.activeControl === "up") {
165                decrement();
166                upPressed = Qt.binding(function() {return containsMouse});
167            } else if (__panel.activeControl === "down") {
168                increment();
169                downPressed = Qt.binding(function() {return containsMouse});
170            } else if (!scrollToClickposition){
171                if (__panel.activeControl === "upPage") {
172                    decrementPage();
173                    pageUpPressed = true;
174                } else if (__panel.activeControl === "downPage") {
175                    incrementPage();
176                    pageDownPressed = true;
177                }
178            } else { // scroll to click position
179                slider.position = horizontal ? mouseX -  handleRect.width/2 - grooveRect.x
180                                             : mouseY - handleRect.height/2 - grooveRect.y
181                pressedX = mouseX;
182                pressedY = mouseY;
183                handlePressed = true;
184                oldPosition = slider.position;
185            }
186        }
187
188        onReleased: {
189            __panel.activeControl = __panel.hitTest(mouseX, mouseY);
190            autoincrement = false;
191            upPressed = false;
192            downPressed = false;
193            handlePressed = false;
194            pageUpPressed = false;
195            pageDownPressed = false;
196        }
197
198        onWheel: {
199            var stepCount = -(wheel.angleDelta.x ? wheel.angleDelta.x : wheel.angleDelta.y) / 120
200            if (stepCount != 0) {
201                if (wheel.modifiers & Qt.ControlModifier || wheel.modifiers & Qt.ShiftModifier)
202                   incrementPage(stepCount)
203                else
204                   increment(stepCount)
205            }
206        }
207
208        function incrementPage(stepCount) {
209            value = boundValue(value + getSteps(pageStep, stepCount))
210        }
211
212        function decrementPage(stepCount) {
213            value = boundValue(value - getSteps(pageStep, stepCount))
214        }
215
216        function increment(stepCount) {
217            value = boundValue(value + getSteps(singleStep, stepCount))
218        }
219
220        function decrement(stepCount) {
221            value = boundValue(value - getSteps(singleStep, stepCount))
222        }
223
224        function boundValue(val) {
225            return Math.min(Math.max(val, minimumValue), maximumValue)
226        }
227
228        function getSteps(step, count) {
229            if (count)
230                step *= count
231            return step
232        }
233
234        RangeModel {
235            id: slider
236            minimumValue: 0.0
237            maximumValue: 1.0
238            value: 0
239            stepSize: 0.0
240            inverted: false
241            positionAtMaximum: internal.grooveSize
242        }
243    }
244}
245