You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
9.6 KiB
Python
242 lines
9.6 KiB
Python
2 years ago
|
#!/usr/bin/python3
|
||
|
# -*- coding: utf-8 -*-
|
||
|
import sys, os
|
||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||
|
|
||
|
# Originated from
|
||
|
# https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html
|
||
|
# Modification refered from
|
||
|
# https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
|
||
|
# https://github.com/Qt-Widgets/range_slider_for_Qt5_and_PyQt5
|
||
|
|
||
|
class RangeSlider(QtWidgets.QSlider):
|
||
|
sliderMoved = QtCore.pyqtSignal(int, int)
|
||
|
|
||
|
""" 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 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 = QtWidgets.QStyle.SC_None
|
||
|
self.tick_interval = 0
|
||
|
self.tick_position = QtWidgets.QSlider.NoTicks
|
||
|
self.hover_control = QtWidgets.QStyle.SC_None
|
||
|
self.click_offset = 0
|
||
|
|
||
|
# 0 for the low, 1 for the high, -1 for both
|
||
|
self.active_slider = 0
|
||
|
|
||
|
def low(self):
|
||
|
return self._low
|
||
|
|
||
|
def setLow(self, low:int):
|
||
|
self._low = low
|
||
|
self.update()
|
||
|
|
||
|
def high(self):
|
||
|
return self._high
|
||
|
|
||
|
def setHigh(self, high):
|
||
|
self._high = high
|
||
|
self.update()
|
||
|
|
||
|
def paintEvent(self, event):
|
||
|
# based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp
|
||
|
|
||
|
painter = QtGui.QPainter(self)
|
||
|
style = QtWidgets.QApplication.style()
|
||
|
|
||
|
# draw groove
|
||
|
opt = QtWidgets.QStyleOptionSlider()
|
||
|
self.initStyleOption(opt)
|
||
|
opt.siderValue = 0
|
||
|
opt.sliderPosition = 0
|
||
|
opt.subControls = QtWidgets.QStyle.SC_SliderGroove
|
||
|
if self.tickPosition() != self.NoTicks:
|
||
|
opt.subControls |= QtWidgets.QStyle.SC_SliderTickmarks
|
||
|
style.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt, painter, self)
|
||
|
groove = style.subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderGroove, self)
|
||
|
|
||
|
# drawSpan
|
||
|
#opt = QtWidgets.QStyleOptionSlider()
|
||
|
self.initStyleOption(opt)
|
||
|
opt.subControls = QtWidgets.QStyle.SC_SliderGroove
|
||
|
#if self.tickPosition() != self.NoTicks:
|
||
|
# opt.subControls |= QtWidgets.QStyle.SC_SliderTickmarks
|
||
|
opt.siderValue = 0
|
||
|
#print(self._low)
|
||
|
opt.sliderPosition = self._low
|
||
|
low_rect = style.subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self)
|
||
|
opt.sliderPosition = self._high
|
||
|
high_rect = style.subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self)
|
||
|
|
||
|
#print(low_rect, high_rect)
|
||
|
low_pos = self.__pick(low_rect.center())
|
||
|
high_pos = self.__pick(high_rect.center())
|
||
|
|
||
|
min_pos = min(low_pos, high_pos)
|
||
|
max_pos = max(low_pos, high_pos)
|
||
|
|
||
|
c = QtCore.QRect(low_rect.center(), high_rect.center()).center()
|
||
|
#print(min_pos, max_pos, c)
|
||
|
if opt.orientation == QtCore.Qt.Horizontal:
|
||
|
span_rect = QtCore.QRect(QtCore.QPoint(min_pos, c.y()-2), QtCore.QPoint(max_pos, c.y()+1))
|
||
|
else:
|
||
|
span_rect = QtCore.QRect(QtCore.QPoint(c.x()-2, min_pos), QtCore.QPoint(c.x()+1, max_pos))
|
||
|
|
||
|
#self.initStyleOption(opt)
|
||
|
#print(groove.x(), groove.y(), groove.width(), groove.height())
|
||
|
if opt.orientation == QtCore.Qt.Horizontal: groove.adjust(0, 0, -1, 0)
|
||
|
else: groove.adjust(0, 0, 0, -1)
|
||
|
|
||
|
if True: #self.isEnabled():
|
||
|
highlight = self.palette().color(QtGui.QPalette.Highlight)
|
||
|
painter.setBrush(QtGui.QBrush(highlight))
|
||
|
painter.setPen(QtGui.QPen(highlight, 0))
|
||
|
#painter.setPen(QtGui.QPen(self.palette().color(QtGui.QPalette.Dark), 0))
|
||
|
'''
|
||
|
if opt.orientation == QtCore.Qt.Horizontal:
|
||
|
self.setupPainter(painter, opt.orientation, groove.center().x(), groove.top(), groove.center().x(), groove.bottom())
|
||
|
else:
|
||
|
self.setupPainter(painter, opt.orientation, groove.left(), groove.center().y(), groove.right(), groove.center().y())
|
||
|
'''
|
||
|
#spanRect =
|
||
|
painter.drawRect(span_rect.intersected(groove))
|
||
|
#painter.drawRect(groove)
|
||
|
|
||
|
for i, value in enumerate([self._low, self._high]):
|
||
|
opt = QtWidgets.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 i == 0:
|
||
|
opt.subControls = QtWidgets.QStyle.SC_SliderHandle# | QtWidgets.QStyle.SC_SliderGroove
|
||
|
else:
|
||
|
opt.subControls = QtWidgets.QStyle.SC_SliderHandle
|
||
|
|
||
|
if self.tickPosition() != self.NoTicks:
|
||
|
opt.subControls |= QtWidgets.QStyle.SC_SliderTickmarks
|
||
|
|
||
|
if self.pressed_control:
|
||
|
opt.activeSubControls = self.pressed_control
|
||
|
else:
|
||
|
opt.activeSubControls = self.hover_control
|
||
|
|
||
|
opt.sliderPosition = value
|
||
|
opt.sliderValue = value
|
||
|
style.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt, painter, self)
|
||
|
|
||
|
def mousePressEvent(self, event):
|
||
|
event.accept()
|
||
|
|
||
|
style = QtWidgets.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 = QtWidgets.QStyleOptionSlider()
|
||
|
self.initStyleOption(opt)
|
||
|
|
||
|
self.active_slider = -1
|
||
|
|
||
|
for i, value in enumerate([self._low, self._high]):
|
||
|
opt.sliderPosition = value
|
||
|
hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(), self)
|
||
|
if hit == style.SC_SliderHandle:
|
||
|
self.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 = QtWidgets.QStyle.SC_SliderHandle
|
||
|
self.click_offset = self.__pixelPosToRangeValue(self.__pick(event.pos()))
|
||
|
self.triggerAction(self.SliderMove)
|
||
|
self.setRepeatAction(self.SliderNoAction)
|
||
|
else:
|
||
|
event.ignore()
|
||
|
|
||
|
def mouseMoveEvent(self, event):
|
||
|
if self.pressed_control != QtWidgets.QStyle.SC_SliderHandle:
|
||
|
event.ignore()
|
||
|
return
|
||
|
|
||
|
event.accept()
|
||
|
new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos()))
|
||
|
opt = QtWidgets.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.emit(QtCore.SIGNAL('sliderMoved(int)'), new_pos)
|
||
|
self.sliderMoved.emit(self._low, self._high)
|
||
|
|
||
|
def __pick(self, pt):
|
||
|
if self.orientation() == QtCore.Qt.Horizontal:
|
||
|
return pt.x()
|
||
|
else:
|
||
|
return pt.y()
|
||
|
|
||
|
|
||
|
def __pixelPosToRangeValue(self, pos):
|
||
|
opt = QtWidgets.QStyleOptionSlider()
|
||
|
self.initStyleOption(opt)
|
||
|
style = QtWidgets.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)
|