import math import platform from contextlib import suppress import pytest from qtpy.QtCore import QEvent, QPoint, QPointF, Qt from qtpy.QtWidgets import QSlider, QStyle, QStyleOptionSlider from superqt import QDoubleSlider, QLabeledDoubleSlider, QLabeledSlider from superqt.sliders._generic_slider import _GenericSlider from ._testutil import ( QT_VERSION, _hover_event, _linspace, _mouse_event, _wheel_event, skip_on_linux_qt6, ) @pytest.fixture( params=[Qt.Orientation.Horizontal, Qt.Orientation.Vertical], ids=["horizontal", "vertical"], ) def orientation(request): return request.param START_MI_MAX_VAL = (0, 99, 0) TEST_SLIDERS = [QDoubleSlider, QLabeledSlider, QLabeledDoubleSlider] def _assert_value_in_range(sld): val = sld.value() if isinstance(val, (int, float)): val = (val,) assert all(sld.minimum() <= v <= sld.maximum() for v in val) @pytest.fixture(params=TEST_SLIDERS) def sld(request, qtbot, orientation): Cls = request.param slider = Cls(orientation) slider.setRange(*START_MI_MAX_VAL[:2]) slider.setValue(START_MI_MAX_VAL[2]) qtbot.addWidget(slider) assert (slider.minimum(), slider.maximum(), slider.value()) == START_MI_MAX_VAL _assert_value_in_range(slider) yield slider _assert_value_in_range(slider) with suppress(AttributeError): slider.initStyleOption(QStyleOptionSlider()) def called_with(*expected_result): """Use in check_params_cbs to assert that a callback is called as expected. e.g. `called_with(20, 50)` returns a callback that checks that the callback is called with the arguments (20, 50) """ def check_emitted_values(*values): return values == expected_result return check_emitted_values def test_change_floatslider_range(sld: _GenericSlider, qtbot): BOTH = [sld.rangeChanged, sld.valueChanged] for signals, checks, funcname, args in [ (BOTH, [called_with(10, 99), called_with(10)], "setMinimum", (10,)), ([sld.rangeChanged], [called_with(10, 90)], "setMaximum", (90,)), (BOTH, [called_with(20, 40), called_with(20)], "setRange", (20, 40)), ([sld.valueChanged], [called_with(30)], "setValue", (30,)), (BOTH, [called_with(20, 25), called_with(25)], "setMaximum", (25,)), ([sld.valueChanged], [called_with(23)], "setValue", (23,)), ]: with qtbot.waitSignals(signals, check_params_cbs=checks, timeout=500): getattr(sld, funcname)(*args) _assert_value_in_range(sld) def test_float_values(sld: _GenericSlider, qtbot): if type(sld) is QLabeledSlider: pytest.skip() for signals, checks, funcname, args in [ (sld.rangeChanged, called_with(0.1, 0.9), "setRange", (0.1, 0.9)), (sld.valueChanged, called_with(0.4), "setValue", (0.4,)), (sld.valueChanged, called_with(0.1), "setValue", (0,)), (sld.valueChanged, called_with(0.9), "setValue", (1.9,)), ]: with qtbot.waitSignal(signals, check_params_cb=checks, timeout=400): getattr(sld, funcname)(*args) _assert_value_in_range(sld) def test_ticks(sld: _GenericSlider, qtbot): sld.setTickInterval(3) assert sld.tickInterval() == 3 sld.setTickPosition(QSlider.TickPosition.TicksAbove) sld.show() @pytest.mark.skipif(platform.system() != "Darwin", reason="cross-platform is tricky") def test_press_move_release(sld: _GenericSlider, qtbot): if hasattr(sld, "_slider") and sld._slider.orientation() == Qt.Orientation.Vertical: pytest.xfail("test failing for vertical at the moment") # this fail on vertical came with pyside6.2 ... need to debug # still works in practice, but test fails to catch signals if sld.orientation() == Qt.Orientation.Vertical: pytest.xfail() _real_sld = getattr(sld, "_slider", sld) with suppress(AttributeError): # for QSlider assert _real_sld._pressedControl == QStyle.SubControl.SC_None opt = QStyleOptionSlider() _real_sld.initStyleOption(opt) style = _real_sld.style() hrect = style.subControlRect( QStyle.ComplexControl.CC_Slider, opt, QStyle.SubControl.SC_SliderHandle ) handle_pos = _real_sld.mapToGlobal(hrect.center()) with qtbot.waitSignal(_real_sld.sliderPressed, timeout=300): qtbot.mousePress(_real_sld, Qt.MouseButton.LeftButton, pos=handle_pos) with suppress(AttributeError): assert sld._pressedControl == QStyle.SubControl.SC_SliderHandle with qtbot.waitSignals( [_real_sld.sliderMoved, _real_sld.valueChanged], timeout=300 ): shift = ( QPoint(0, -8) if _real_sld.orientation() == Qt.Orientation.Vertical else QPoint(8, 0) ) _real_sld.mouseMoveEvent(_mouse_event(handle_pos + shift)) with qtbot.waitSignal(_real_sld.sliderReleased, timeout=300): qtbot.mouseRelease(_real_sld, Qt.MouseButton.LeftButton, pos=handle_pos) with suppress(AttributeError): assert _real_sld._pressedControl == QStyle.SubControl.SC_None sld.show() with qtbot.waitSignal(_real_sld.sliderPressed, timeout=300): qtbot.mousePress(_real_sld, Qt.MouseButton.LeftButton, pos=handle_pos) @skip_on_linux_qt6 def test_hover(sld: _GenericSlider): _real_sld = getattr(sld, "_slider", sld) opt = QStyleOptionSlider() _real_sld.initStyleOption(opt) hrect = _real_sld.style().subControlRect( QStyle.ComplexControl.CC_Slider, opt, QStyle.SubControl.SC_SliderHandle ) handle_pos = QPointF(sld.mapToGlobal(hrect.center())) with suppress(AttributeError): # for QSlider assert _real_sld._hoverControl == QStyle.SubControl.SC_None _real_sld.event(_hover_event(QEvent.Type.HoverEnter, handle_pos, QPointF(), sld)) with suppress(AttributeError): # for QSlider assert _real_sld._hoverControl == QStyle.SubControl.SC_SliderHandle _real_sld.event( _hover_event(QEvent.Type.HoverLeave, QPointF(-1000, -1000), handle_pos, sld) ) with suppress(AttributeError): # for QSlider assert _real_sld._hoverControl == QStyle.SubControl.SC_None def test_wheel(sld: _GenericSlider, qtbot): if type(sld) is QLabeledSlider and QT_VERSION < (5, 12): pytest.skip() _real_sld = getattr(sld, "_slider", sld) with qtbot.waitSignal(sld.valueChanged, timeout=400): _real_sld.wheelEvent(_wheel_event(120)) _real_sld.wheelEvent(_wheel_event(0)) def test_position(sld: _GenericSlider, qtbot): sld.setSliderPosition(21) assert sld.sliderPosition() == 21 if type(sld) is not QLabeledSlider: sld.setSliderPosition(21.5) assert sld.sliderPosition() == 21.5 def test_steps(sld: _GenericSlider, qtbot): sld.setSingleStep(11) assert sld.singleStep() == 11 sld.setPageStep(16) assert sld.pageStep() == 16 if type(sld) is not QLabeledSlider: sld.setSingleStep(0.1) assert sld.singleStep() == 0.1 sld.setSingleStep(1.5e20) assert sld.singleStep() == 1.5e20 sld.setPageStep(0.2) assert sld.pageStep() == 0.2 sld.setPageStep(1.5e30) assert sld.pageStep() == 1.5e30 @pytest.mark.parametrize("mag", list(range(4, 37, 4)) + list(range(-4, -37, -4))) def test_slider_extremes(sld: _GenericSlider, mag, qtbot): if type(sld) is QLabeledSlider: pytest.skip() _mag = 10**mag with qtbot.waitSignal(sld.rangeChanged, timeout=400): sld.setRange(-_mag, _mag) for i in _linspace(-_mag, _mag, 10): sld.setValue(i) assert math.isclose(sld.value(), i, rel_tol=1e-8)