Skip to content

Commit

Permalink
categorical slider
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Oct 15, 2024
1 parent 952ac33 commit 6d5814e
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 119 deletions.
4 changes: 4 additions & 0 deletions examples/labeled_sliders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
QLabeledRangeSlider,
QLabeledSlider,
)
from superqt.sliders._labeled import QLabeledCategoricalSlider

app = QApplication([])

Expand Down Expand Up @@ -36,6 +37,8 @@
qldrs.setSingleStep(0.01)
qldrs.setValue((0.2, 0.7))

qlcs = QLabeledCategoricalSlider()
qlcs.setCategories(["dog", "cat", "elephant", "bird", "fish"])

w.setLayout(
QVBoxLayout() if ORIENTATION == Qt.Orientation.Horizontal else QHBoxLayout()
Expand All @@ -44,6 +47,7 @@
w.layout().addWidget(qlds)
w.layout().addWidget(qlrs)
w.layout().addWidget(qldrs)
w.layout().addWidget(qlcs)
w.show()
w.resize(500, 150)
app.exec_()
2 changes: 2 additions & 0 deletions src/superqt/sliders/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ._labeled import (
QLabeledCategoricalSlider,
QLabeledDoubleRangeSlider,
QLabeledDoubleSlider,
QLabeledRangeSlider,
Expand All @@ -10,6 +11,7 @@
__all__ = [
"QDoubleRangeSlider",
"QDoubleSlider",
"QLabeledCategoricalSlider",
"QLabeledDoubleRangeSlider",
"QLabeledDoubleSlider",
"QLabeledRangeSlider",
Expand Down
79 changes: 79 additions & 0 deletions src/superqt/sliders/_categorical_slider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from typing import Generic, Iterable, Sequence, TypeVar, overload

from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import QSlider, QWidget

T = TypeVar("T")


class QCategoricalSlider(QSlider, Generic[T]):
"""A Slider that can only take on a finite number of values."""

categoryChanged = Signal(object)
categoriesChanged = Signal(tuple)

@overload
def __init__(
self,
parent: QWidget | None = ...,
/,
*,
categories: Iterable[T] = ...,
) -> None: ...
@overload
def __init__(
self,
orientation: Qt.Orientation,
parent: QWidget | None = ...,
/,
*,
categories: Iterable[T] = ...,
) -> None: ...

def __init__(
self, *args: Qt.Orientation | QWidget | None, categories: Iterable[T] = ()
) -> None:
# default to horizontal orientation
if len(args) == 0:
args = (Qt.Orientation.Horizontal, None)
elif len(args) == 1:
args = (Qt.Orientation.Horizontal, args[0])
super().__init__(*args) # type: ignore [arg-type]

Check warning on line 41 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L37-L41

Added lines #L37 - L41 were not covered by tests

self._categories: Sequence[T] = ()
self.setCategories(categories)
self.setTickPosition(QSlider.TickPosition.TicksAbove)
self.setTickInterval(1)
self.valueChanged.connect(self._on_value_changed)

Check warning on line 47 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L43-L47

Added lines #L43 - L47 were not covered by tests

def categories(self) -> Sequence[T]:
"""Return the categories of the slider."""
return self._categories

Check warning on line 51 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L51

Added line #L51 was not covered by tests

def setCategories(self, categories: Iterable[T]) -> None:
"""Set the categories of the slider."""
self._categories = tuple(categories)
self.setRange(0, len(self._categories) - 1)
self.categoriesChanged.emit(self._categories)

Check warning on line 57 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L55-L57

Added lines #L55 - L57 were not covered by tests

def category(self) -> T:
"""Return the current categorical value of the slider."""
try:
return self._categories[super().value()]
except IndexError:
return None

Check warning on line 64 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L61-L64

Added lines #L61 - L64 were not covered by tests

def setCategory(self, value: T) -> None:
"""Set the current categorical value of the slider."""
try:

Check warning on line 68 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L68

Added line #L68 was not covered by tests
# we could consider indexing this up-front during setCategories
# to save .index() calls here
idx = self._categories.index(value)
except ValueError:

Check warning on line 72 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L71-L72

Added lines #L71 - L72 were not covered by tests
# the behavior of the original QSlider is to (quietly) pin to the nearest
# value when the value is out of range. Here we do nothing.
return None
super().setValue(idx)

Check warning on line 76 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L75-L76

Added lines #L75 - L76 were not covered by tests

def _on_value_changed(self, value: int) -> None:
self.categoryChanged.emit(self._categories[value])

Check warning on line 79 in src/superqt/sliders/_categorical_slider.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_categorical_slider.py#L79

Added line #L79 was not covered by tests
Loading

0 comments on commit 6d5814e

Please sign in to comment.