Source code for cytoflowgui.editors.range_slider

#!/usr/bin/env python3.8
# coding: latin-1

# (c) Massachusetts Institute of Technology 2015-2018
# (c) Brian Teague 2018-2022
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
cytoflowgui.editors.range_slider
--------------------------------

A ``QtGui.QSlider`` with two carets -- allows a range to be specified.
"""

from pyface.qt import QtGui, QtCore

[docs]class RangeSlider(QtGui.QSlider): """ A slider for ranges. This class provides a dual-slider for ranges, where there is a defined maximum and minimum, as is a normal slider, but instead of having a single slider value, there are 2 slider values. This class emits the same signals as the ``QtGui.QSlider`` base class, with the exception of ``valueChanged`` """ def __init__(self, *args): super(RangeSlider, self).__init__(*args) self._low = self.minimum() self._high = self.maximum() self.pressed_control = QtGui.QStyle.SC_None self.hover_control = QtGui.QStyle.SC_None self.click_offset = 0 # 0 for the low, 1 for the high, -1 for both self.active_slider = 0 self.last_active_slider = 0
[docs] def low(self): return self._low
[docs] def setLow(self, low): self._low = low self.last_active_slider = 0 self.update()
[docs] def high(self): return self._high
[docs] def setHigh(self, high): self._high = high self.last_active_slider = 1 self.update()
[docs] def sliderOrderForPaint(self): if self.last_active_slider == 0: return [(1, self._high), (0, self._low)] else: return [(0, self._low), (1, self._high)]
[docs] def sliderOrderForMove(self): if self.last_active_slider == 0: return [(0, self._low), (1, self._high)] else: return [(1, self._high), (0, self._low)]
[docs] def paintEvent(self, event): # based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp painter = QtGui.QPainter(self) style = QtGui.QApplication.style() hasPainted = False for _, value in self.sliderOrderForPaint(): opt = QtGui.QStyleOptionSlider() self.initStyleOption(opt) # Only draw the groove for the first slider so it doesn't get drawn # on top of the existing ones every time if not hasPainted: opt.subControls = QtGui.QStyle.SC_SliderGroove | QtGui.QStyle.SC_SliderHandle hasPainted = True else: opt.subControls = QtGui.QStyle.SC_SliderHandle if self.tickPosition() != self.NoTicks: opt.subControls |= QtGui.QStyle.SC_SliderTickmarks if self.pressed_control: opt.activeSubControls = self.pressed_control opt.state |= QtGui.QStyle.State_Sunken else: opt.activeSubControls = self.hover_control opt.sliderPosition = value opt.sliderValue = value style.drawComplexControl(QtGui.QStyle.CC_Slider, opt, painter, self)
[docs] def mousePressEvent(self, event): event.accept() style = QtGui.QApplication.style() button = event.button() # In a normal slider control, when the user clicks on a point in the # slider's total range, but not on the slider part of the control the # control would jump the slider value to where the user clicked. # For this control, clicks which are not direct hits will slide both # slider parts if button: opt = QtGui.QStyleOptionSlider() self.initStyleOption(opt) self.active_slider = -1 for i, value in self.sliderOrderForMove(): opt.sliderPosition = value hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(), self) if hit == style.SC_SliderHandle: self.active_slider = i self.last_active_slider = i self.pressed_control = hit self.triggerAction(self.SliderMove) self.setRepeatAction(self.SliderNoAction) self.setSliderDown(True) break if self.active_slider < 0: self.pressed_control = QtGui.QStyle.SC_SliderHandle self.click_offset = self.__pixelPosToRangeValue(self.__pick(event.pos())) self.triggerAction(self.SliderMove) self.setRepeatAction(self.SliderNoAction) else: event.ignore()
[docs] def mouseMoveEvent(self, event): if self.pressed_control != QtGui.QStyle.SC_SliderHandle: event.ignore() return event.accept() new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos())) opt = QtGui.QStyleOptionSlider() self.initStyleOption(opt) if self.active_slider < 0: offset = new_pos - self.click_offset self._high += offset self._low += offset if self._low < self.minimum(): diff = self.minimum() - self._low self._low += diff self._high += diff if self._high > self.maximum(): diff = self.maximum() - self._high self._low += diff self._high += diff elif self.active_slider == 0: if new_pos >= self._high: new_pos = self._high - 1 self._low = new_pos else: if new_pos <= self._low: new_pos = self._low + 1 self._high = new_pos self.click_offset = new_pos self.update() self.sliderMoved.emit(new_pos)
[docs] def mouseReleaseEvent(self, event): event.accept() self.sliderReleased.emit()
def __pick(self, pt): if self.orientation() == QtCore.Qt.Horizontal: return pt.x() else: return pt.y() def __pixelPosToRangeValue(self, pos): opt = QtGui.QStyleOptionSlider() self.initStyleOption(opt) style = QtGui.QApplication.style() gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self) sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self) if self.orientation() == QtCore.Qt.Horizontal: slider_length = sr.width() slider_min = gr.x() slider_max = gr.right() - slider_length + 1 else: slider_length = sr.height() slider_min = gr.y() slider_max = gr.bottom() - slider_length + 1 return style.sliderValueFromPosition(self.minimum(), self.maximum(), pos-slider_min, slider_max-slider_min, opt.upsideDown)
if __name__ == "__main__": import sys def echo(value): print(value) app = QtGui.QApplication(sys.argv) slider = RangeSlider() slider.setMinimum(0) slider.setMaximum(10000) slider.setLow(0) slider.setHigh(10000) slider.sliderMoved.connect(echo) slider.show() app.exec_()