source: sasview/src/sas/qtgui/Plotting/rangeSlider.py @ 4a9786d8

Last change on this file since 4a9786d8 was 83eb5208, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Putting files in more ordered fashion

  • Property mode set to 100755
File size: 11.0 KB
Line 
1# -*- coding: utf-8 -*-
2
3"""
4/***************************************************************************
5Name                 : RangeSlider
6Description          : A slider for ranges
7Date                 : Jun 20, 2012
8copyright            : (C) 2012 by Giuseppe Sucameli
9email                : brush.tyler@gmail.com
10
11the code is based on RangeSlider by phil
12(see https://svn.enthought.com/enthought/browser/TraitsBackendQt/trunk/enthought/traits/ui/qt4/extra/range_slider.py) licensed under GPLv2
13 ***************************************************************************/
14
15/***************************************************************************
16 *                                                                         *
17 *   This program is free software; you can redistribute it and/or modify  *
18 *   it under the terms of the GNU General Public License as published by  *
19 *   the Free Software Foundation; either version 2 of the License, or     *
20 *   (at your option) any later version.                                   *
21 *                                                                         *
22 ***************************************************************************/
23"""
24
25from PyQt4 import QtGui, QtCore
26
27class RangeSlider(QtGui.QSlider):
28    """ A slider for ranges.
29   
30        This class provides a dual-slider for ranges, where there is a defined
31        maximum and minimum, as is a normal slider, but instead of having a
32        single slider value, there are 2 slider values.
33       
34        This class emits the same signals as the QSlider base class, with the
35        exception of valueChanged.
36
37        In addition, two new signals are emitted to catch the movement of
38        each handle, lowValueChanged(int) and highValueChanged(int).
39    """
40    highValueChanged = QtCore.pyqtSignal(int)
41    lowValueChanged = QtCore.pyqtSignal(int)
42    def __init__(self, *args):
43        super(RangeSlider, self).__init__(*args)
44
45        self._low = self.minimum()
46        self._high = self.maximum()
47       
48        self.pressed_control = QtGui.QStyle.SC_None
49        self.hover_control = QtGui.QStyle.SC_None
50        self.click_offset = 0
51       
52        # -1 for the low, 1 for the high, 0 for both
53        self.active_slider = -1
54
55    def lowValue(self):
56        return self._low
57
58    def setLowValue(self, low):
59        if low < self.minimum():
60            low = self.minimum()
61        if low == self._low:
62            return
63
64        self._low = low
65        self.update()
66
67        if self.hasTracking():
68            self.lowValueChanged.emit(self._low)
69
70    def highValue(self):
71        return self._high
72
73    def setHighValue(self, high):
74        if high > self.maximum():
75            high = self.maximum()
76        if high == self._high:
77            return
78
79        self._high = high
80        self.update()
81
82        if self.hasTracking():
83            self.lowValueChanged.emit(self._high)
84       
85    def paintEvent(self, event):
86        # based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp
87
88        painter = QtGui.QPainter(self)
89        style = QtGui.QApplication.style() 
90
91        for i, value in enumerate([self._low, self._high]):
92            opt = QtGui.QStyleOptionSlider()
93            self.initStyleOption(opt)
94
95            gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self)
96            sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self)
97
98            if self.orientation() == QtCore.Qt.Horizontal:
99                handle_length = sr.width()
100                slider_length = gr.width()
101                slider_min = gr.x()
102                slider_max = gr.right() - handle_length + 1
103            else:
104                handle_length = sr.height()
105                slider_length = gr.height()
106                slider_min = gr.y()
107                slider_max = gr.bottom() - handle_length + 1
108
109            opt.subControls = QtGui.QStyle.SC_SliderGroove | QtGui.QStyle.SC_SliderHandle
110
111            # draw the first slider with inverted appearance, then the second
112            # slider drawn on top of the existing ones
113            if i == 0:
114                if self.orientation() == QtCore.Qt.Horizontal:
115                    opt.upsideDown = not self.invertedAppearance()
116                else:
117                    opt.upsideDown = self.invertedAppearance()
118
119                if self.orientation() == QtCore.Qt.Horizontal:
120                    opt.sliderValue = value - self.minimum()
121                    opt.sliderPosition = self.minimum() + (self.maximum() - value)
122
123                    opt.rect.setX(slider_min)
124                    opt.rect.setWidth(slider_length)
125
126                else:
127                    opt.rect.setY(slider_min)
128                    opt.rect.setHeight(slider_length)
129
130            else:
131                # do not highlight the second part when has focus to avoid
132                # drawing of partially overlapped semi-transparent backgrounds
133                opt.state &= ~QtGui.QStyle.State_HasFocus
134
135                opt.sliderValue = 0
136                opt.sliderPosition = self.minimum()
137
138                if self.orientation() == QtCore.Qt.Horizontal:
139                    opt.upsideDown = self.invertedAppearance()
140
141                    pos = style.sliderPositionFromValue(0, self.maximum()-self.minimum(),
142                                             value-self.minimum(), slider_max - slider_min - 4,
143                                             opt.upsideDown)
144
145                    opt.rect.setX(pos + 3)
146                    opt.rect.setWidth(slider_max - pos + handle_length - 3)
147
148                else:
149                    opt.upsideDown = not self.invertedAppearance()
150
151                    pos = style.sliderPositionFromValue(0, self.maximum()-self.minimum(),
152                                             self.maximum()-value, slider_max - slider_min - 4,
153                                             opt.upsideDown)
154
155                    opt.rect.setY(slider_min-1)
156                    opt.rect.setHeight(slider_min + slider_length - pos - 2)
157
158            if self.tickPosition() != self.NoTicks:
159                opt.subControls |= QtGui.QStyle.SC_SliderTickmarks
160
161            if self.pressed_control:
162                opt.activeSubControls = self.pressed_control
163                opt.state |= QtGui.QStyle.State_Sunken
164            else:
165                opt.activeSubControls = self.hover_control
166
167            style.drawComplexControl(QtGui.QStyle.CC_Slider, opt, painter, self)
168           
169       
170    def mousePressEvent(self, event):
171        event.accept()
172       
173        style = QtGui.QApplication.style()
174        button = event.button()
175       
176        # In a normal slider control, when the user clicks on a point in the
177        # slider's total range, but not on the slider part of the control the
178        # control would jump the slider value to where the user clicked.
179        # For this control, clicks which are not direct hits will slide both
180        # slider parts
181               
182        if button:
183            opt = QtGui.QStyleOptionSlider()
184            self.initStyleOption(opt)
185
186            self.active_slider = 0
187            mid = (self.maximum() - self.minimum()) / 2.0
188           
189            for i, value in enumerate([self._low, self._high]):
190                opt.sliderPosition = value
191                hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(), self)
192
193                if hit == style.SC_SliderHandle:
194                    self.pressed_control = hit
195
196                    # if both handles are close together, avoid locks
197                    # choosing the one with more empty space near it
198                    if self._low + 2 >= self._high:
199                        self.active_slider = 1 if self._high < mid else -1
200                    else:
201                        self.active_slider = -1 if i == 0 else 1
202                    self.triggerAction(self.SliderMove)
203                    self.setRepeatAction(self.SliderNoAction)
204                    self.setSliderDown(True)
205                    break
206
207            if self.active_slider == 0:
208                self.pressed_control = QtGui.QStyle.SC_SliderHandle
209                self.click_offset = self.__pixelPosToRangeValue(self.__pick(event.pos()))
210                self.triggerAction(self.SliderMove)
211                self.setRepeatAction(self.SliderNoAction)
212                self.setSliderDown(True)
213        else:
214            event.ignore()
215
216    def mouseReleaseEvent(self, event):
217        if self.pressed_control != QtGui.QStyle.SC_SliderHandle:
218            event.ignore()
219            return
220
221        self.setSliderDown(False)
222        return QtGui.QSlider.mouseReleaseEvent(self, event)
223                               
224    def mouseMoveEvent(self, event):
225        if self.pressed_control != QtGui.QStyle.SC_SliderHandle:
226            event.ignore()
227            return
228       
229        event.accept()
230        new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos()))
231        opt = QtGui.QStyleOptionSlider()
232        self.initStyleOption(opt)
233
234        old_click_offset = self.click_offset
235        self.click_offset = new_pos
236       
237        if self.active_slider == 0:
238            offset = new_pos - old_click_offset
239            new_low = self._low + offset
240            new_high = self._high + offset
241
242            if new_low < self.minimum():
243                diff = self.minimum() - new_low
244                new_low += diff
245                new_high += diff
246            if new_high > self.maximum():
247                diff = self.maximum() - new_high
248                new_low += diff
249                new_high += diff
250
251            self.setLowValue( new_low )
252            self.setHighValue( new_high )
253
254        elif self.active_slider < 0:
255            if new_pos > self._high:
256                new_pos = self._high
257            self.setLowValue( new_pos )
258
259        else:
260            if new_pos < self._low:
261                new_pos = self._low
262            self.setHighValue( new_pos )
263
264        if self.hasTracking():
265            self.emit(QtCore.SIGNAL('sliderMoved(int)'), new_pos)
266
267           
268    def __pick(self, pt):
269        if self.orientation() == QtCore.Qt.Horizontal:
270            return pt.x()
271        else:
272            return pt.y()
273           
274           
275    def __pixelPosToRangeValue(self, pos):
276        opt = QtGui.QStyleOptionSlider()
277        self.initStyleOption(opt)
278        style = QtGui.QApplication.style()
279       
280        gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self)
281        sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self)
282       
283        if self.orientation() == QtCore.Qt.Horizontal:
284            handle_length = sr.width()
285            slider_min = gr.x() + handle_length/2
286            slider_max = gr.right() - handle_length/2 + 1
287        else:
288            handle_length = sr.height()
289            slider_min = gr.y() + handle_length/2
290            slider_max = gr.bottom() - handle_length/2 + 1
291           
292        return self.minimum() + style.sliderValueFromPosition(0, self.maximum()-self.minimum(),
293                                            pos-slider_min, slider_max - slider_min,
294                                            opt.upsideDown)
Note: See TracBrowser for help on using the repository browser.