mirror of
https://github.com/pyapp-kit/superqt.git
synced 2025-08-30 22:30:06 +02:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f76cf6d126 |
28
examples/float.py
Normal file
28
examples/float.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from qtrangeslider import QRangeSlider
|
||||
from qtrangeslider._float_slider import QDoubleRangeSlider, QDoubleSlider
|
||||
from qtrangeslider.qtcompat.QtCore import Qt
|
||||
from qtrangeslider.qtcompat.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
|
||||
app = QApplication([])
|
||||
|
||||
w = QWidget()
|
||||
|
||||
sld1 = QDoubleSlider(Qt.Horizontal)
|
||||
sld2 = QDoubleRangeSlider(Qt.Horizontal)
|
||||
rs = QRangeSlider(Qt.Horizontal)
|
||||
|
||||
sld1.valueChanged.connect(lambda e: print("doubslider valuechanged", e))
|
||||
|
||||
sld2.setMaximum(1)
|
||||
sld2.setValue((0.2, 0.8))
|
||||
sld2.valueChanged.connect(lambda e: print("valueChanged", e))
|
||||
sld2.sliderMoved.connect(lambda e: print("sliderMoved", e))
|
||||
sld2.rangeChanged.connect(lambda e, f: print("rangeChanged", (e, f)))
|
||||
|
||||
w.setLayout(QVBoxLayout())
|
||||
w.layout().addWidget(sld1)
|
||||
w.layout().addWidget(sld2)
|
||||
w.layout().addWidget(rs)
|
||||
w.show()
|
||||
w.resize(500, 150)
|
||||
app.exec_()
|
@@ -1,17 +1,49 @@
|
||||
from qtrangeslider._labeled import QLabeledRangeSlider, QLabeledSlider
|
||||
from qtrangeslider._labeled import (
|
||||
QLabeledDoubleRangeSlider,
|
||||
QLabeledDoubleSlider,
|
||||
QLabeledRangeSlider,
|
||||
QLabeledSlider,
|
||||
)
|
||||
from qtrangeslider.qtcompat.QtCore import Qt
|
||||
from qtrangeslider.qtcompat.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
from qtrangeslider.qtcompat.QtWidgets import (
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
app = QApplication([])
|
||||
|
||||
w = QWidget()
|
||||
sld = QLabeledRangeSlider()
|
||||
ORIENTATION = Qt.Horizontal
|
||||
|
||||
sld.setRange(0, 500)
|
||||
sld.setValue((100, 400))
|
||||
w.setLayout(QVBoxLayout())
|
||||
w.layout().addWidget(sld)
|
||||
w.layout().addWidget(QLabeledSlider(Qt.Horizontal))
|
||||
w = QWidget()
|
||||
qls = QLabeledSlider(ORIENTATION)
|
||||
qls.valueChanged.connect(lambda e: print("qls valueChanged", e))
|
||||
qls.setRange(0, 500)
|
||||
qls.setValue(300)
|
||||
|
||||
|
||||
qlds = QLabeledDoubleSlider(ORIENTATION)
|
||||
qlds.valueChanged.connect(lambda e: print("qlds valueChanged", e))
|
||||
qlds.setRange(0, 1)
|
||||
qlds.setValue(0.5)
|
||||
qlds.setSingleStep(0.1)
|
||||
|
||||
qlrs = QLabeledRangeSlider(ORIENTATION)
|
||||
qlrs.valueChanged.connect(lambda e: print("qlrs valueChanged", e))
|
||||
qlrs.setValue((20, 60))
|
||||
|
||||
qldrs = QLabeledDoubleRangeSlider(ORIENTATION)
|
||||
qldrs.valueChanged.connect(lambda e: print("qlrs valueChanged", e))
|
||||
qldrs.setRange(0, 1)
|
||||
qldrs.setValue((0.2, 0.7))
|
||||
|
||||
|
||||
w.setLayout(QVBoxLayout() if ORIENTATION == Qt.Horizontal else QHBoxLayout())
|
||||
w.layout().addWidget(qls)
|
||||
w.layout().addWidget(qlds)
|
||||
w.layout().addWidget(qlrs)
|
||||
w.layout().addWidget(qldrs)
|
||||
w.show()
|
||||
w.resize(500, 150)
|
||||
app.exec_()
|
||||
|
@@ -3,7 +3,21 @@ try:
|
||||
except ImportError:
|
||||
__version__ = "unknown"
|
||||
|
||||
from ._labeled import QLabeledRangeSlider, QLabeledSlider
|
||||
from ._float_slider import QDoubleRangeSlider, QDoubleSlider
|
||||
from ._labeled import (
|
||||
QLabeledDoubleRangeSlider,
|
||||
QLabeledDoubleSlider,
|
||||
QLabeledRangeSlider,
|
||||
QLabeledSlider,
|
||||
)
|
||||
from ._qrangeslider import QRangeSlider
|
||||
|
||||
__all__ = ["QRangeSlider", "QLabeledRangeSlider", "QLabeledSlider"]
|
||||
__all__ = [
|
||||
"QDoubleRangeSlider",
|
||||
"QDoubleSlider",
|
||||
"QLabeledDoubleRangeSlider",
|
||||
"QLabeledDoubleSlider",
|
||||
"QLabeledRangeSlider",
|
||||
"QLabeledSlider",
|
||||
"QRangeSlider",
|
||||
]
|
||||
|
96
qtrangeslider/_float_slider.py
Normal file
96
qtrangeslider/_float_slider.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import math
|
||||
from typing import Tuple
|
||||
|
||||
from ._hooked import _HookedSlider
|
||||
from ._qrangeslider import QRangeSlider
|
||||
from .qtcompat.QtCore import Signal
|
||||
|
||||
|
||||
class QDoubleSlider(_HookedSlider):
|
||||
|
||||
valueChanged = Signal(float)
|
||||
rangeChanged = Signal(float, float)
|
||||
sliderMoved = Signal(float)
|
||||
_multiplier = 1
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self._multiplier = 10 ** 2
|
||||
self.setMinimum(0)
|
||||
self.setMaximum(99)
|
||||
self.setSingleStep(1)
|
||||
self.setPageStep(10)
|
||||
super().sliderMoved.connect(
|
||||
lambda e: self.sliderMoved.emit(self._post_get_hook(e))
|
||||
)
|
||||
|
||||
def decimals(self) -> int:
|
||||
"""This property holds the precision of the slider, in decimals."""
|
||||
return int(math.log10(self._multiplier))
|
||||
|
||||
def setDecimals(self, prec: int):
|
||||
"""This property holds the precision of the slider, in decimals
|
||||
|
||||
Sets how many decimals the slider uses for displaying and interpreting doubles.
|
||||
"""
|
||||
previous = self._multiplier
|
||||
self._multiplier = 10 ** int(prec)
|
||||
ratio = self._multiplier / previous
|
||||
|
||||
if ratio != 1:
|
||||
self.blockSignals(True)
|
||||
try:
|
||||
newmin = self.minimum() * ratio
|
||||
newmax = self.maximum() * ratio
|
||||
newval = self._scale_value(ratio)
|
||||
newstep = self.singleStep() * ratio
|
||||
newpage = self.pageStep() * ratio
|
||||
self.setRange(newmin, newmax)
|
||||
self.setValue(newval)
|
||||
self.setSingleStep(newstep)
|
||||
self.setPageStep(newpage)
|
||||
except OverflowError as err:
|
||||
self._multiplier = previous
|
||||
raise OverflowError(
|
||||
f"Cannot use {prec} decimals with a range of {newmin}-"
|
||||
f"{newmax}. If you need this feature, please open a feature"
|
||||
" request at github."
|
||||
) from err
|
||||
self.blockSignals(False)
|
||||
|
||||
def _scale_value(self, p):
|
||||
# for subclasses
|
||||
return self.value() * p
|
||||
|
||||
def _post_get_hook(self, value: int) -> float:
|
||||
return value / self._multiplier
|
||||
|
||||
def _pre_set_hook(self, value: float) -> int:
|
||||
return int(value * self._multiplier)
|
||||
|
||||
def sliderChange(self, change) -> None:
|
||||
if change == self.SliderValueChange:
|
||||
self.valueChanged.emit(self.value())
|
||||
if change == self.SliderRangeChange:
|
||||
self.rangeChanged.emit(self.minimum(), self.maximum())
|
||||
return super().sliderChange(self.SliderChange(change))
|
||||
|
||||
|
||||
class QDoubleRangeSlider(QRangeSlider, QDoubleSlider):
|
||||
rangeChanged = Signal(float, float)
|
||||
|
||||
def value(self) -> Tuple[float, ...]:
|
||||
"""Get current value of the widget as a tuple of integers."""
|
||||
return tuple(float(i) for i in self._value)
|
||||
|
||||
def _min_max_bound(self, val: int) -> float:
|
||||
return round(super()._min_max_bound(val), self.decimals())
|
||||
|
||||
def _scale_value(self, p):
|
||||
# This function is called during setDecimals...
|
||||
# but because QRangeSlider has a private nonQt `_value`
|
||||
# we don't actually need to scale
|
||||
return self._value
|
||||
|
||||
def setDecimals(self, prec: int):
|
||||
return super().setDecimals(prec)
|
49
qtrangeslider/_hooked.py
Normal file
49
qtrangeslider/_hooked.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from .qtcompat.QtWidgets import QSlider
|
||||
|
||||
|
||||
class _HookedSlider(QSlider):
|
||||
def _post_get_hook(self, value):
|
||||
return value
|
||||
|
||||
def _pre_set_hook(self, value):
|
||||
return value
|
||||
|
||||
def value(self) -> float: # type: ignore[override]
|
||||
return float(self._post_get_hook(super().value()))
|
||||
|
||||
def setValue(self, value: float) -> None:
|
||||
super().setValue(self._pre_set_hook(value))
|
||||
|
||||
def minimum(self) -> float: # type: ignore[override]
|
||||
return self._post_get_hook(super().minimum())
|
||||
|
||||
def setMinimum(self, minimum: float):
|
||||
super().setMinimum(self._pre_set_hook(minimum))
|
||||
|
||||
def maximum(self) -> float: # type: ignore[override]
|
||||
return self._post_get_hook(super().maximum())
|
||||
|
||||
def setMaximum(self, maximum: float):
|
||||
super().setMaximum(self._pre_set_hook(maximum))
|
||||
|
||||
def singleStep(self) -> float: # type: ignore[override]
|
||||
return self._post_get_hook(super().singleStep())
|
||||
|
||||
def setSingleStep(self, step: float):
|
||||
super().setSingleStep(self._pre_set_hook(step))
|
||||
|
||||
def pageStep(self) -> float: # type: ignore[override]
|
||||
return self._post_get_hook(super().pageStep())
|
||||
|
||||
def setPageStep(self, step: float) -> None:
|
||||
super().setPageStep(self._pre_set_hook(step))
|
||||
|
||||
def setRange(self, min: float, max: float) -> None:
|
||||
super().setRange(self._pre_set_hook(min), self._pre_set_hook(max))
|
||||
|
||||
# def sliderChange(self, change) -> None:
|
||||
# if change == QSlider.SliderValueChange:
|
||||
# self.valueChanged.emit(self.value())
|
||||
# if change == QSlider.SliderRangeChange:
|
||||
# self.rangeChanged.emit(self.minimum(), self.maximum())
|
||||
# return super().sliderChange(change)
|
@@ -1,12 +1,14 @@
|
||||
from enum import IntEnum
|
||||
from functools import partial
|
||||
|
||||
from ._float_slider import QDoubleRangeSlider, QDoubleSlider
|
||||
from ._qrangeslider import QRangeSlider
|
||||
from .qtcompat.QtCore import QPoint, QSize, Qt, Signal
|
||||
from .qtcompat.QtGui import QFontMetrics
|
||||
from .qtcompat.QtGui import QFontMetrics, QValidator
|
||||
from .qtcompat.QtWidgets import (
|
||||
QAbstractSlider,
|
||||
QApplication,
|
||||
QDoubleSpinBox,
|
||||
QHBoxLayout,
|
||||
QSlider,
|
||||
QSpinBox,
|
||||
@@ -31,7 +33,47 @@ class EdgeLabelMode(IntEnum):
|
||||
LabelIsValue = 2
|
||||
|
||||
|
||||
class QLabeledSlider(QAbstractSlider):
|
||||
class SliderProxy:
|
||||
_slider: QAbstractSlider
|
||||
|
||||
def value(self):
|
||||
return self._slider.value()
|
||||
|
||||
def setValue(self, value) -> None:
|
||||
self._slider.setValue(value)
|
||||
|
||||
def minimum(self):
|
||||
return self._slider.minimum()
|
||||
|
||||
def setMinimum(self, minimum):
|
||||
self._slider.setMinimum(minimum)
|
||||
|
||||
def maximum(self):
|
||||
return self._slider.maximum()
|
||||
|
||||
def setMaximum(self, maximum):
|
||||
self._slider.setMaximum(maximum)
|
||||
|
||||
def singleStep(self):
|
||||
return self._slider.singleStep()
|
||||
|
||||
def setSingleStep(self, step):
|
||||
self._slider.setSingleStep(step)
|
||||
|
||||
def pageStep(self):
|
||||
return self._slider.pageStep()
|
||||
|
||||
def setPageStep(self, step) -> None:
|
||||
self._slider.setPageStep(step)
|
||||
|
||||
def setRange(self, min, max) -> None:
|
||||
self._slider.setRange(min, max)
|
||||
|
||||
|
||||
class QLabeledSlider(SliderProxy, QAbstractSlider):
|
||||
_slider_class = QSlider
|
||||
_slider: QSlider
|
||||
|
||||
def __init__(self, *args) -> None:
|
||||
parent = None
|
||||
orientation = Qt.Horizontal
|
||||
@@ -45,8 +87,9 @@ class QLabeledSlider(QAbstractSlider):
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
self._slider = QSlider()
|
||||
self._slider = self._slider_class()
|
||||
self._slider.valueChanged.connect(self.valueChanged.emit)
|
||||
self._slider.rangeChanged.connect(self.rangeChanged.emit)
|
||||
self._label = SliderLabel(self._slider, connect=self.setValue)
|
||||
|
||||
self.valueChanged.connect(self._label.setValue)
|
||||
@@ -80,10 +123,30 @@ class QLabeledSlider(QAbstractSlider):
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class QLabeledRangeSlider(QAbstractSlider):
|
||||
class QLabeledDoubleSlider(QLabeledSlider):
|
||||
_slider_class = QDoubleSlider
|
||||
_slider: QDoubleSlider
|
||||
valueChanged = Signal(float)
|
||||
rangeChanged = Signal(float, float)
|
||||
|
||||
def __init__(self, *args) -> None:
|
||||
super().__init__(*args)
|
||||
self.setDecimals(2)
|
||||
|
||||
def decimals(self) -> int:
|
||||
return self._slider.decimals()
|
||||
|
||||
def setDecimals(self, prec: int):
|
||||
self._slider.setDecimals(prec)
|
||||
self._label.setDecimals(prec)
|
||||
|
||||
|
||||
class QLabeledRangeSlider(SliderProxy, QAbstractSlider):
|
||||
valueChanged = Signal(tuple)
|
||||
LabelPosition = LabelPosition
|
||||
EdgeLabelMode = EdgeLabelMode
|
||||
_slider_class = QRangeSlider
|
||||
_slider: QRangeSlider
|
||||
|
||||
def __init__(self, *args) -> None:
|
||||
parent = None
|
||||
@@ -105,8 +168,9 @@ class QLabeledRangeSlider(QAbstractSlider):
|
||||
self.label_shift_x = 0
|
||||
self.label_shift_y = 0
|
||||
|
||||
self._slider = QRangeSlider()
|
||||
self._slider = self._slider_class()
|
||||
self._slider.valueChanged.connect(self.valueChanged.emit)
|
||||
self._slider.rangeChanged.connect(self.rangeChanged.emit)
|
||||
|
||||
self._min_label = SliderLabel(
|
||||
self._slider, alignment=Qt.AlignLeft, connect=self._min_label_edited
|
||||
@@ -117,7 +181,7 @@ class QLabeledRangeSlider(QAbstractSlider):
|
||||
self.setEdgeLabelMode(EdgeLabelMode.LabelIsRange)
|
||||
|
||||
self._slider.valueChanged.connect(self._on_value_changed)
|
||||
self.rangeChanged.connect(self._on_range_changed)
|
||||
self._slider.rangeChanged.connect(self._on_range_changed)
|
||||
|
||||
self._on_value_changed(self._slider.value())
|
||||
self._on_range_changed(self._slider.minimum(), self._slider.maximum())
|
||||
@@ -180,7 +244,7 @@ class QLabeledRangeSlider(QAbstractSlider):
|
||||
else:
|
||||
dx *= 3
|
||||
pos = self._slider.mapToParent(rect.center())
|
||||
pos += QPoint(dx + self.label_shift_x, dy + self.label_shift_y)
|
||||
pos += QPoint(int(dx + self.label_shift_x), int(dy + self.label_shift_y))
|
||||
label.move(pos)
|
||||
label.clearFocus()
|
||||
|
||||
@@ -231,12 +295,12 @@ class QLabeledRangeSlider(QAbstractSlider):
|
||||
self._max_label.setValue(max)
|
||||
self._reposition_labels()
|
||||
|
||||
def value(self):
|
||||
return self._slider.value()
|
||||
# def setValue(self, value) -> None:
|
||||
# super().setValue(value)
|
||||
# self.sliderChange(QSlider.SliderValueChange)
|
||||
|
||||
def setValue(self, v: int) -> None:
|
||||
self._slider.setValue(v)
|
||||
self.sliderChange(QSlider.SliderValueChange)
|
||||
def setRange(self, min, max) -> None:
|
||||
self._on_range_changed(min, max)
|
||||
|
||||
def setOrientation(self, orientation):
|
||||
"""Set orientation, value will be 'horizontal' or 'vertical'."""
|
||||
@@ -285,7 +349,27 @@ class QLabeledRangeSlider(QAbstractSlider):
|
||||
self._reposition_labels()
|
||||
|
||||
|
||||
class SliderLabel(QSpinBox):
|
||||
class QLabeledDoubleRangeSlider(QLabeledRangeSlider):
|
||||
_slider_class = QDoubleRangeSlider
|
||||
_slider: QDoubleRangeSlider
|
||||
rangeChanged = Signal(float, float)
|
||||
|
||||
def __init__(self, *args) -> None:
|
||||
super().__init__(*args)
|
||||
self.setDecimals(2)
|
||||
|
||||
def decimals(self) -> int:
|
||||
return self._slider.decimals()
|
||||
|
||||
def setDecimals(self, prec: int):
|
||||
self._slider.setDecimals(prec)
|
||||
self._min_label.setDecimals(prec)
|
||||
self._max_label.setDecimals(prec)
|
||||
for lbl in self._handle_labels:
|
||||
lbl.setDecimals(prec)
|
||||
|
||||
|
||||
class SliderLabel(QDoubleSpinBox):
|
||||
def __init__(
|
||||
self, slider: QSlider, parent=None, alignment=Qt.AlignCenter, connect=None
|
||||
) -> None:
|
||||
@@ -293,6 +377,7 @@ class SliderLabel(QSpinBox):
|
||||
self._slider = slider
|
||||
self.setFocusPolicy(Qt.ClickFocus)
|
||||
self.setMode(EdgeLabelMode.LabelIsValue)
|
||||
self.setDecimals(0)
|
||||
|
||||
self.setRange(slider.minimum(), slider.maximum())
|
||||
slider.rangeChanged.connect(self._update_size)
|
||||
@@ -304,6 +389,10 @@ class SliderLabel(QSpinBox):
|
||||
self.editingFinished.connect(self.clearFocus)
|
||||
self._update_size()
|
||||
|
||||
def setDecimals(self, prec: int) -> None:
|
||||
super().setDecimals(prec)
|
||||
self._update_size()
|
||||
|
||||
def _update_size(self):
|
||||
# fontmetrics to measure the width of text
|
||||
fm = QFontMetrics(self.font())
|
||||
@@ -360,3 +449,9 @@ class SliderLabel(QSpinBox):
|
||||
self.setMaximum(self._slider.maximum())
|
||||
self._slider.rangeChanged.connect(self.setRange)
|
||||
self._update_size()
|
||||
|
||||
def validate(self, input: str, pos: int):
|
||||
# fake like an integer spinbox
|
||||
if "." in input and self.decimals() < 1:
|
||||
return QValidator.Invalid, input, len(input)
|
||||
return super().validate(input, pos)
|
||||
|
@@ -2,6 +2,7 @@ import textwrap
|
||||
from collections import abc
|
||||
from typing import List, Sequence, Tuple
|
||||
|
||||
from ._hooked import _HookedSlider
|
||||
from ._style import RangeSliderStyle, update_styles_from_stylesheet
|
||||
from .qtcompat import QtGui
|
||||
from .qtcompat.QtCore import (
|
||||
@@ -25,7 +26,7 @@ from .qtcompat.QtWidgets import (
|
||||
ControlType = Tuple[str, int]
|
||||
|
||||
|
||||
class QRangeSlider(QSlider):
|
||||
class QRangeSlider(_HookedSlider, QSlider):
|
||||
"""MultiHandle Range Slider widget.
|
||||
|
||||
Same API as QSlider, but `value`, `setValue`, `sliderPosition`, and
|
||||
@@ -93,12 +94,11 @@ class QRangeSlider(QSlider):
|
||||
|
||||
The number of handles will be equal to the length of the sequence
|
||||
"""
|
||||
if not isinstance(val, abc.Sequence) and len(val) >= 2:
|
||||
if not (isinstance(val, abc.Sequence) and len(val) >= 2):
|
||||
raise ValueError("value must be iterable of len >= 2")
|
||||
val = [self._min_max_bound(v) for v in val]
|
||||
if self._value == val and self._position == val:
|
||||
return
|
||||
|
||||
self._value[:] = val[:]
|
||||
if self._position != val:
|
||||
self._position = val
|
||||
@@ -106,7 +106,7 @@ class QRangeSlider(QSlider):
|
||||
self.sliderMoved.emit(tuple(self._position))
|
||||
|
||||
self.sliderChange(QSlider.SliderValueChange)
|
||||
self.valueChanged.emit(tuple(self._value))
|
||||
self.valueChanged.emit(self.value())
|
||||
|
||||
def sliderPosition(self) -> Tuple[int, ...]:
|
||||
"""Get current value of the widget as a tuple of integers.
|
||||
@@ -173,6 +173,7 @@ class QRangeSlider(QSlider):
|
||||
pos = self._neighbor_bound(pos, index, self._position)
|
||||
if pos == self._position[index]:
|
||||
return
|
||||
|
||||
self._position[index] = pos
|
||||
if _update:
|
||||
self._updateSliderMove()
|
||||
@@ -256,7 +257,8 @@ class QRangeSlider(QSlider):
|
||||
elif self._hoverControl[0] == "handle":
|
||||
hidx = self._hoverControl[1]
|
||||
for idx, pos in enumerate(self._position):
|
||||
opt.sliderPosition = pos
|
||||
opt.sliderPosition = self._pre_set_hook(pos)
|
||||
|
||||
if idx == pidx: # make pressed handles appear sunken
|
||||
opt.state |= QStyle.State_Sunken
|
||||
else:
|
||||
@@ -361,14 +363,14 @@ class QRangeSlider(QSlider):
|
||||
style = self.style().proxy()
|
||||
|
||||
if handle_index is not None: # get specific handle rect
|
||||
opt.sliderPosition = self._position[handle_index]
|
||||
opt.sliderPosition = self._pre_set_hook(self._position[handle_index])
|
||||
return style.subControlRect(
|
||||
QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self
|
||||
)
|
||||
else:
|
||||
rects = []
|
||||
for p in self._position:
|
||||
opt.sliderPosition = p
|
||||
opt.sliderPosition = self._pre_set_hook(p)
|
||||
r = style.subControlRect(
|
||||
QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self
|
||||
)
|
||||
@@ -454,7 +456,6 @@ class QRangeSlider(QSlider):
|
||||
def _pixelPosToRangeValue(self, pos: int, opt: QStyleOptionSlider = None) -> int:
|
||||
if not opt:
|
||||
opt = self._getStyleOption()
|
||||
|
||||
groove_rect = self._grooveRect(opt)
|
||||
handle_rect = self._handleRects(opt, 0)
|
||||
if self.orientation() == Qt.Horizontal:
|
||||
@@ -465,13 +466,14 @@ class QRangeSlider(QSlider):
|
||||
sliderLength = handle_rect.height()
|
||||
sliderMin = groove_rect.y()
|
||||
sliderMax = groove_rect.bottom() - sliderLength + 1
|
||||
return QStyle.sliderValueFromPosition(
|
||||
self.minimum(),
|
||||
self.maximum(),
|
||||
v = QStyle.sliderValueFromPosition(
|
||||
opt.minimum,
|
||||
opt.maximum,
|
||||
pos - sliderMin,
|
||||
sliderMax - sliderMin,
|
||||
opt.upsideDown,
|
||||
)
|
||||
return self._post_get_hook(v)
|
||||
|
||||
def _pick(self, pt: QPoint) -> int:
|
||||
return pt.x() if self.orientation() == Qt.Horizontal else pt.y()
|
||||
@@ -550,10 +552,9 @@ class QRangeSlider(QSlider):
|
||||
_prev_value = self.value()
|
||||
|
||||
if modifiers & Qt.AltModifier:
|
||||
|
||||
self._spreadAllPositions(shrink=steps_to_scroll < 0)
|
||||
else:
|
||||
self._offsetAllPositions(steps_to_scroll)
|
||||
self._offsetAllPositions(self._post_get_hook(steps_to_scroll))
|
||||
self.triggerAction(QSlider.SliderMove)
|
||||
|
||||
if _prev_value == self.value():
|
||||
|
134
qtrangeslider/_tests/test_float.py
Normal file
134
qtrangeslider/_tests/test_float.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from qtrangeslider import (
|
||||
QDoubleRangeSlider,
|
||||
QDoubleSlider,
|
||||
QLabeledDoubleRangeSlider,
|
||||
QLabeledDoubleSlider,
|
||||
)
|
||||
from qtrangeslider.qtcompat import API_NAME
|
||||
|
||||
range_types = {QDoubleRangeSlider, QLabeledDoubleRangeSlider}
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
QDoubleSlider,
|
||||
QLabeledDoubleSlider,
|
||||
QDoubleRangeSlider,
|
||||
QLabeledDoubleRangeSlider,
|
||||
]
|
||||
)
|
||||
def ds(qtbot, request):
|
||||
# convenience fixture that converts value() and setValue()
|
||||
# to let us use setValue((a, b)) for both range and non-range sliders
|
||||
cls = request.param
|
||||
wdg = cls()
|
||||
qtbot.addWidget(wdg)
|
||||
|
||||
def assert_val_type():
|
||||
type_ = float
|
||||
if cls in range_types:
|
||||
assert all([isinstance(i, type_) for i in wdg.value()]) # sourcery skip
|
||||
else:
|
||||
assert isinstance(wdg.value(), type_)
|
||||
|
||||
def assert_val_eq(val):
|
||||
assert wdg.value() == val if cls is QDoubleRangeSlider else val[0]
|
||||
|
||||
wdg.assert_val_type = assert_val_type
|
||||
wdg.assert_val_eq = assert_val_eq
|
||||
|
||||
if cls not in range_types:
|
||||
superset = wdg.setValue
|
||||
|
||||
def _safe_set(val):
|
||||
superset(val[0] if isinstance(val, tuple) else val)
|
||||
|
||||
wdg.setValue = _safe_set
|
||||
|
||||
return wdg
|
||||
|
||||
|
||||
def test_double_sliders(ds):
|
||||
ds.setMinimum(10)
|
||||
ds.setMaximum(99)
|
||||
ds.setValue((20, 40))
|
||||
ds.setSingleStep(1)
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
ds.assert_val_eq((20, 40))
|
||||
assert ds.singleStep() == 1
|
||||
|
||||
ds.setDecimals(2)
|
||||
ds.assert_val_eq((20, 40))
|
||||
ds.assert_val_type()
|
||||
|
||||
ds.setValue((20.23435, 40.2342))
|
||||
ds.assert_val_eq((20.23, 40.23)) # because of decimals
|
||||
ds.assert_val_type()
|
||||
|
||||
ds.setDecimals(4)
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
assert ds.singleStep() == 1
|
||||
ds.assert_val_eq((20.23, 40.23))
|
||||
ds.setValue((20.2343, 40.2342))
|
||||
ds.assert_val_eq((20.2343, 40.2342))
|
||||
|
||||
ds.setDecimals(6)
|
||||
ds.assert_val_eq((20.2343, 40.2342))
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
assert ds.singleStep() == 1
|
||||
|
||||
with pytest.raises(OverflowError) as err:
|
||||
ds.setDecimals(8)
|
||||
assert "open a feature request" in str(err)
|
||||
|
||||
ds.assert_val_eq((20.2343, 40.2342))
|
||||
assert ds.minimum() == 10
|
||||
assert ds.maximum() == 99
|
||||
assert ds.singleStep() == 1
|
||||
|
||||
|
||||
def test_double_sliders_small(ds):
|
||||
ds.setMaximum(1)
|
||||
ds.setDecimals(8)
|
||||
ds.setValue((0.5, 0.9))
|
||||
assert ds.minimum() == 0
|
||||
assert ds.maximum() == 1
|
||||
ds.assert_val_eq((0.5, 0.9))
|
||||
|
||||
ds.setValue((0.122233, 0.72644353))
|
||||
ds.assert_val_eq((0.122233, 0.72644353))
|
||||
|
||||
|
||||
def test_double_sliders_big(ds):
|
||||
ds.setValue((20, 80))
|
||||
ds.setDecimals(-6)
|
||||
assert ds.decimals() == -6
|
||||
ds.setMaximum(5e14)
|
||||
assert ds.minimum() == 0
|
||||
assert ds.maximum() == 5e14
|
||||
ds.setValue((1.74e9, 1.432e10))
|
||||
ds.assert_val_eq((1.74e9, 1.432e10))
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
os.name == "nt" and API_NAME == "PyQt6", reason="Not ready for pyqt6"
|
||||
)
|
||||
def test_signals(ds, qtbot):
|
||||
with qtbot.waitSignal(ds.valueChanged):
|
||||
ds.setValue((10, 20))
|
||||
|
||||
with qtbot.waitSignal(ds.rangeChanged):
|
||||
ds.setMinimum(0.5)
|
||||
|
||||
with qtbot.waitSignal(ds.rangeChanged):
|
||||
ds.setMaximum(3.7)
|
||||
|
||||
with qtbot.waitSignal(ds.rangeChanged):
|
||||
ds.setRange(1.2, 3.3)
|
Reference in New Issue
Block a user