From 487921c79134fd8bbf28f61f58beadcc35a42a82 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Sat, 24 Apr 2021 23:35:43 -0400 Subject: [PATCH] more readme --- .github/workflows/test_and_deploy.yml | 4 +- .gitignore | 1 + README.md | 79 +++++++++++++++++---- examples/basic.py | 3 +- examples/{screenshots.py => demo_widget.py} | 0 qtrangeslider/_qrangeslider.py | 20 ++++-- qtrangeslider/qtcompat/__init__.py | 12 +++- 7 files changed, 94 insertions(+), 25 deletions(-) rename examples/{screenshots.py => demo_widget.py} (100%) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 4ab2004..e2f85e7 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -106,11 +106,11 @@ jobs: if: runner.os == 'Linux' && matrix.screenshot uses: GabrielBB/xvfb-action@v1 with: - run: python examples/screenshots.py + run: python examples/demo_widget.py - name: Screenshots if: runner.os != 'Linux' && matrix.screenshot - run: python examples/screenshots.py + run: python examples/demo_widget.py - uses: actions/upload-artifact@v2 if: matrix.screenshot diff --git a/.gitignore b/.gitignore index 69e5c1b..4d5a7f9 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ target/ # written by setuptools_scm */_version.py +.vscode/settings.json diff --git a/README.md b/README.md index 54b737a..99e4ea2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Version](https://img.shields.io/pypi/pyversions/QtRangeSlider.svg?color=green)]( [![Test](https://github.com/tlambert03/QtRangeSlider/actions/workflows/test_and_deploy.yml/badge.svg)](https://github.com/tlambert03/QtRangeSlider/actions/workflows/test_and_deploy.yml) [![codecov](https://codecov.io/gh/tlambert03/QtRangeSlider/branch/master/graph/badge.svg)](https://codecov.io/gh/tlambert03/QtRangeSlider) -**Multi-handle range slider widget for PyQt/PySide** +**The missing multi-handle range slider widget for PyQt & PySide** ![slider](screenshots/slider.png) @@ -31,24 +31,78 @@ You can install `QtRangeSlider` via pip: ```sh pip install qtrangeslider -# note: you must also install a Qt Backend -# supports PyQt5, PySide2, PyQt6, and PySide6 -# as a convenience you can install via extras: +# NOTE: you must also install a Qt Backend. +# PyQt5, PySide2, PyQt6, and PySide6 are supported +# As a convenience you can install them as extras: pip install qtrangeslider[pyqt5] - ``` -And then to use it: -```python -from qtrangeslider import QRangeSlider -``` +------ ## API -As `QRangeSlider` inherits from `QtWidgets.QSlider`, you can use all of the -same methods available in the [QSlider API](https://doc.qt.io/qt-5/qslider.html) +To create a slider: +```python +from qtrangeslider import QRangeSlider + +# as usual: +# you must create a QApplication before create a widget. +range_slider = QRangeSlider() +``` + +As `QRangeSlider` inherits from `QtWidgets.QSlider`, you can use all of the +same methods available in the [QSlider API](https://doc.qt.io/qt-5/qslider.html). The major difference is that `value` and `sliderPosition` are reimplemented as `tuples` of `int` (where the length of the tuple is equal to the number of handles in the slider.) + +### value: Tuple[int, ...] + +This property holds the current value of all handles in the slider. + +The slider forces all values to be within the legal range: +`minimum <= value <= maximum`. + +Changing the value also changes the sliderPosition. + +##### Access Functions: + +```python +range_slider.value() -> Tuple[int, ...] +``` + +```python +range_slider.setValue(val: Sequence[int]) -> None +``` + +##### Notifier Signal: + +```python +valueChanged(Tuple[int, ...]) +``` + +### sliderPosition: Tuple[int, ...] + +This property holds the current slider positions. It is a `tuple` with length equal to the number of handles. + +If [tracking](https://doc.qt.io/qt-5/qabstractslider.html#tracking-prop) is enabled (the default), this is identical to [`value`](#value--tupleint-). + +##### Access Functions: + +```python +range_slider.sliderPosition() -> Tuple[int, ...] +``` + +```python +range_slider.setSliderPosition(val: Sequence[int]) -> None +``` + +##### Notifier Signal: + +```python +sliderMoved(Tuple[int, ...]) +``` + +------ ## Example @@ -59,6 +113,8 @@ using [Qt Style Sheets](https://doc.qt.io/qt-5/stylesheet-reference.html), then `QRangeSlider` will inherit any styles applied to `QSlider` (since it inherits from QSlider). +> The code for these example widgets is [here](examples/demo_widget.py) +
See style sheet used for this example @@ -112,5 +168,4 @@ QSlider::sub-page:horizontal { If you encounter any problems, please [file an issue] along with a detailed description. - [file an issue]: https://github.com/tlambert03/QtRangeSlider/issues diff --git a/examples/basic.py b/examples/basic.py index d5d9841..a434c7b 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -4,8 +4,7 @@ from qtrangeslider.qtcompat.QtWidgets import QApplication app = QApplication([]) slider = QRangeSlider() -slider.setMinimum(0) -slider.setMaximum(100) + slider.setValue((20, 80)) slider.show() diff --git a/examples/screenshots.py b/examples/demo_widget.py similarity index 100% rename from examples/screenshots.py rename to examples/demo_widget.py diff --git a/qtrangeslider/_qrangeslider.py b/qtrangeslider/_qrangeslider.py index fc5a95a..f362054 100644 --- a/qtrangeslider/_qrangeslider.py +++ b/qtrangeslider/_qrangeslider.py @@ -15,11 +15,6 @@ from .qtcompat.QtWidgets import ( Control = Tuple[str, int] -def _bound(min_: int, max_: int, value: int) -> int: - """Return value bounded by min_ and max_.""" - return max(min_, min(max_, value)) - - class QRangeSlider(QSlider): # Emitted when the slider value has changed, with the new slider values valueChanged = Signal(tuple) @@ -85,6 +80,9 @@ class QRangeSlider(QSlider): return tuple(self._position) def setSliderPosition(self, sld_idx: int, pos: int) -> None: + + # TODO: make it take a tuple, and assert that the length is correct + # only setting `value` is allowed to change the number of handles pos = self._min_max_bound(pos) # prevent sliders from moving beyond their neighbors pos = self._neighbor_bound(pos, sld_idx, self._position) @@ -192,8 +190,11 @@ class QRangeSlider(QSlider): self.update() self.setSliderDown(True) elif self._pressedControl[0] == "bar": + self.setRepeatAction(QSlider.SliderNoAction) # why again? self._clickOffset = self._pixelPosToRangeValue(self._pick(ev.pos())) self._sldPosAtPress = tuple(self._position) + self.update() + self.setSliderDown(True) def mouseMoveEvent(self, ev: QtGui.QMouseEvent) -> None: # TODO: add pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this); @@ -230,7 +231,7 @@ class QRangeSlider(QSlider): old_pressed = self._pressedControl self._pressedControl = self.NULL_CTRL self.setRepeatAction(QSlider.SliderNoAction) - if old_pressed[0] == "handle": + if old_pressed[0] in ("handle", "bar"): self.setSliderDown(False) self.update() # TODO: restrict to the rect of old_pressed @@ -489,6 +490,13 @@ class QRangeSlider(QSlider): setattr(self, f"_bar_{dim}", float(bgrd.groups()[-1])) +def _bound(min_: int, max_: int, value: int) -> int: + """Return value bounded by min_ and max_.""" + return max(min_, min(max_, value)) + + +# Styles Parsing ############## + qlineargrad_pattern = re.compile( r""" qlineargradient\( diff --git a/qtrangeslider/qtcompat/__init__.py b/qtrangeslider/qtcompat/__init__.py index ed73f9e..88faed6 100644 --- a/qtrangeslider/qtcompat/__init__.py +++ b/qtrangeslider/qtcompat/__init__.py @@ -31,13 +31,13 @@ QT_API = "QT_API" # Names of the expected PyQt5 api PYQT5_API = ["pyqt5"] -# Names of the expected PyQt5 api +# Names of the expected PyQt6 api PYQT6_API = ["pyqt6"] # Names of the expected PySide2 api PYSIDE2_API = ["pyside2"] -# Names of the expected PySide2 api +# Names of the expected PySide6 api PYSIDE6_API = ["pyside6"] # Detecting if a binding was specified by the user @@ -142,7 +142,13 @@ if API in PYSIDE6_API: PYSIDE6 = True except ImportError: - raise PythonQtError("No Qt bindings could be found") + API = None + +if API is None: + raise PythonQtError( + "No Qt bindings could be found.\nYou must install one of the following packages " + "to use QtRangeSlider: PyQt5, PyQt6, PySide2, or PySide6" + ) # If a correct API name is passed to QT_API and it could not be found, # switches to another and informs through the warning