diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 3006788..0000000 --- a/.coveragerc +++ /dev/null @@ -1,11 +0,0 @@ -[report] -exclude_lines = - pragma: no cover - if TYPE_CHECKING: - \.\.\. - except ImportError* - raise NotImplementedError() -omit = - qtrangeslider/_version.py - qtrangeslider/qtcompat/* - *_tests* diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index cabe99b..abc8b6d 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -89,6 +89,7 @@ jobs: sudo apt-get install -y libdbus-1-3 libxkbcommon-x11-0 libxcb-icccm4 \ libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \ libxcb-xinerama0 libxcb-xfixes0 + - name: Linux opengl if: runner.os == 'Linux' && ( matrix.backend == 'pyside6' || matrix.backend == 'pyqt6' ) run: sudo apt-get install -y libopengl0 libegl1-mesa libxcb-xinput0 @@ -111,13 +112,13 @@ jobs: if: matrix.screenshot run: pip install . ${{ matrix.backend }} - - name: Screenshots + - name: Screenshots (Linux) if: runner.os == 'Linux' && matrix.screenshot uses: GabrielBB/xvfb-action@v1 with: run: python examples/demo_widget.py -snap - - name: Screenshots + - name: Screenshots (macOS/Win) if: runner.os != 'Linux' && matrix.screenshot run: python examples/demo_widget.py -snap @@ -133,8 +134,8 @@ jobs: # and requires that you have put your twine API key in your # github secrets (see readme for details) needs: [test] + if: ${{ github.repository == 'napari/qwidgets' && contains(github.ref, 'tags') }} runs-on: ubuntu-latest - if: contains(github.ref, 'tags') steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af75e45..9ed556a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,20 +4,32 @@ repos: hooks: - id: end-of-file-fixer - id: trailing-whitespace + - repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.17.0 + hooks: + - id: setup-cfg-fmt + - repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + additional_dependencies: + [flake8-typing-imports==1.7.0] + exclude: examples + - repo: https://github.com/myint/autoflake + rev: v1.4 + hooks: + - id: autoflake + args: ["--in-place", "--remove-all-unused-imports"] - repo: https://github.com/PyCQA/isort rev: 5.8.0 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v2.19.0 + rev: v2.19.1 hooks: - id: pyupgrade + args: [--py37-plus] - repo: https://github.com/psf/black rev: 21.5b2 hooks: - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - pass_filenames: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5881be8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing to this repository + +This repository seeks to accumulate Qt-based widgets for python (PyQt & PySide) +that are not provided in the native QtWidgets module. + +## Clone + +To get started fork this repository, and clone your fork: + +```bash +# clone your fork +git clone https://github.com//qwidgets +cd qwidgets + +# install pre-commit hooks +pre-commit install + +# install in editable mode +pip install -e .[dev] + +# run tests & make sure everything is working! +pytest +``` + +## Targeted platforms + +All widgets must be well-tested, and should work on: + +- Python 3.7 and above +- PyQt5 (5.11 and above) & PyQt6 +- PySide2 (5.11 and above) & PySide6 +- macOS, Windows, & Linux + +Until [qtpy](https://github.com/spyder-ide/qtpy) supports PyQt6/PySide6, imports +should use (and modify if necessary) `qwidgets.qtcompat`. + +## Style Guide + +All widgets should try to match the native Qt API as much as possible: + +- Methods should use `camelCase` naming. +- Getters/setters use the `attribute()/setAttribute()` pattern. +- Private methods should use `_camelCaseNaming`. +- `__init__` methods should be like Qt constructors, meaning they often don't + include parameters for most of the widgets properties. +- When possible, widgets should inherit from the most similar native widget + available. It should strictly match the Qt API where it exists, and attempt to + cover as much of the native API as possible; this includes properties, public + functions, signals, and public slots. + +## Testing + +Tests can be run in the current environment with `pytest`. Or, to run tests +against all supported python & Qt versions, run `tox`. diff --git a/LICENSE b/LICENSE index 28b89bb..80382a5 100644 --- a/LICENSE +++ b/LICENSE @@ -12,7 +12,7 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of QtRangeSlider nor the names of its +* Neither the name of qwidgets nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/README.md b/README.md index d0b6523..f191f99 100644 --- a/README.md +++ b/README.md @@ -1,262 +1,27 @@ -# QtRangeSlider +# qwidgets -[![License](https://img.shields.io/pypi/l/QtRangeSlider.svg?color=green)](https://github.com/tlambert03/QtRangeSlider/raw/master/LICENSE) -[![PyPI](https://img.shields.io/pypi/v/QtRangeSlider.svg?color=green)](https://pypi.org/project/QtRangeSlider) +**Missing widgets for PyQt/PySide** + +[![License](https://img.shields.io/pypi/l/qwidgets.svg?color=green)](https://github.com/napari/qwidgets/raw/master/LICENSE) +[![PyPI](https://img.shields.io/pypi/v/qwidgets.svg?color=green)](https://pypi.org/project/qwidgets) [![Python -Version](https://img.shields.io/pypi/pyversions/QtRangeSlider.svg?color=green)](https://python.org) -[![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) +Version](https://img.shields.io/pypi/pyversions/qwidgets.svg?color=green)](https://python.org) +[![Test](https://github.com/napari/qwidgets/actions/workflows/test_and_deploy.yml/badge.svg)](https://github.com/napari/qwidgets/actions/workflows/test_and_deploy.yml) +[![codecov](https://codecov.io/gh/napari/qwidgets/branch/master/graph/badge.svg)](https://codecov.io/gh/napari/qwidgets) -**The missing multi-handle range slider widget for PyQt & PySide** -![slider](images/slider.png) +## Widgets -The goal of this package is to provide a Range Slider (a slider with 2 or more -handles) that feels as "native" as possible. Styles should match the OS by -default, and the slider should behave like a standard -[`QSlider`](https://doc.qt.io/qt-5/qslider.html)... but with multiple handles! +Widgets include: -- `QRangeSlider` inherits from [`QSlider`](https://doc.qt.io/qt-5/qslider.html) - and attempts to match the Qt API as closely as possible -- Uses platform-specific styles (for handle, groove, & ticks) but also supports - QSS style sheets. -- Supports mouse wheel and keypress (soon) events -- Supports PyQt5, PyQt6, PySide2 and PySide6 -- Supports more than 2 handles (e.g. `slider.setValue([0, 10, 60, 80])`) +- Float Slider +- Range Slider (multi-handle slider) +- Labeled Sliders (sliders with linked spinboxes) +- Unbound Integer SpinBox (backed by python `int`) -## Installation +## Contributing -You can install `QtRangeSlider` via pip: +This repository seeks to accumulate Qt-based widgets for python (PyQt & PySide) +that are not provided in the native QtWidgets module. We welcome contributions! -```sh -pip install qtrangeslider - -# 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] -``` - - ------- - -## API - -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, ...]) -``` - -### Additional properties - -These options are in addition to the Qt QSlider API, and control the behavior of the bar between handles. - -| getter | setter | type | default | description | -| -------------------- | ------------------------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------ | -| `barIsVisible` | `setBarIsVisible`
`hideBar` / `showBar` | `bool` | `True` | Whether the bar between handles is visible. | -| `barMovesAllHandles` | `setBarMovesAllHandles` | `bool` | `True` | Whether clicking on the bar moves all handles or just the nearest | -| `barIsRigid` | `setBarIsRigid` | `bool` | `True` | Whether bar length is constant or "elastic" when dragging the bar beyond min/max. | ------- - -## Examples - -These screenshots show `QRangeSlider` (multiple handles) next to the native `QSlider` -(single handle). With no styles applied, `QRangeSlider` will match the native OS -style of `QSlider` – with or without tick marks. When styles have been applied -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). If you'd like to style `QRangeSlider` differently than `QSlider`, -then you can also target it directly in your style sheet. The one "special" -property for QRangeSlider is `qproperty-barColor`, which sets the color of the -bar between the handles. - -> The code for these example widgets is [here](examples/demo_widget.py) - -
- -See style sheet used for this example - -```css -/* -Because QRangeSlider inherits from QSlider, it will also inherit styles -*/ -QSlider { - min-height: 20px; -} - -QSlider::groove:horizontal { - border: 0px; - background: qlineargradient(x1:0, y1:0, x2:1, y2:1, - stop:0 #777, stop:1 #aaa); - height: 20px; - border-radius: 10px; -} - -QSlider::handle { - background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.5, - fy:0.5, stop:0 #eef, stop:1 #000); - height: 20px; - width: 20px; - border-radius: 10px; -} - -/* -"QSlider::sub-page" is the one exception ... -(it styles the area to the left of the QSlider handle) -*/ -QSlider::sub-page:horizontal { - background: #447; - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; -} - -/* -for QRangeSlider: use "qproperty-barColor". "sub-page" will not work. -*/ -QRangeSlider { - qproperty-barColor: #447; -} -``` - -
- -### macOS - -##### Catalina -![mac10](images/demo_darwin10.png) - -##### Big Sur -![mac11](images/demo_darwin11.png) - -### Windows - -![window](images/demo_windows.png) - -### Linux - -![linux](images/demo_linux.png) - - -## Labeled Sliders - -This package also includes two "labeled" slider variants. One for `QRangeSlider`, and one for the native `QSlider`: - -### `QLabeledRangeSlider` - -![labeled_range](images/labeled_range.png) - -```python -from qtrangeslider import QLabeledRangeSlider -``` - -This has the same API as `QRangeSlider` with the following additional options: - -#### `handleLabelPosition`/`setHandleLabelPosition` - -Where/whether labels are shown adjacent to slider handles. - -**type:** `QLabeledRangeSlider.LabelPosition` - -**default:** `LabelPosition.LabelsAbove` - -*options:* - -- `LabelPosition.NoLabel` (no labels shown adjacent to handles) -- `LabelPosition.LabelsAbove` -- `LabelPosition.LabelsBelow` -- `LabelPosition.LabelsRight` (alias for `LabelPosition.LabelsAbove`) -- `LabelPosition.LabelsLeft` (alias for `LabelPosition.LabelsBelow`) - - -#### `edgeLabelMode`/`setEdgeLabelMode` - -**type:** `QLabeledRangeSlider.EdgeLabelMode` - -**default:** `EdgeLabelMode.LabelIsRange` - -*options:* - -- `EdgeLabelMode.NoLabel`: no labels shown at slider extremes -- `EdgeLabelMode.LabelIsRange`: edge labels shown the min/max values -- `EdgeLabelMode.LabelIsValue`: edge labels shown the slider range - - -#### fine tuning position of labels: - -If you find that you need to fine tune the position of the handle labels: - -- `QLabeledRangeSlider.label_shift_x`: adjust horizontal label position -- `QLabeledRangeSlider.label_shift_y`: adjust vertical label position - -### `QLabeledSlider` - - -![labeled_range](images/labeled_qslider.png) - -```python -from qtrangeslider import QLabeledSlider -``` - -(no additional options at this point) - -## Issues - -If you encounter any problems, please [file an issue] along with a detailed -description. - -[file an issue]: https://github.com/tlambert03/QtRangeSlider/issues +Please see the [Contributing Guide](CONTRIBUTING.md) diff --git a/codecov.yml b/codecov.yml index f88538b..b61ff45 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,6 @@ ignore: - - qtrangeslider/_version.py - - qtrangeslider/qtcompat/* + - qwidgets/_version.py + - qwidgets/qtcompat/* - '*_tests*' coverage: status: diff --git a/docs/RangeSlider.md b/docs/RangeSlider.md new file mode 100644 index 0000000..09880ad --- /dev/null +++ b/docs/RangeSlider.md @@ -0,0 +1,235 @@ +# QRangeSlider + +**The missing multi-handle range slider widget for PyQt & PySide** + +![slider](images/slider.png) + +- `QRangeSlider` inherits from [`QSlider`](https://doc.qt.io/qt-5/qslider.html) + and attempts to match the Qt API as closely as possible +- Uses platform-specific styles (for handle, groove, & ticks) but also supports + QSS style sheets. +- Supports mouse wheel and keypress (soon) events +- Supports more than 2 handles (e.g. `slider.setValue([0, 10, 60, 80])`) + +------ + +## API + +To create a slider: + +```python +from qwidgets 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, ...]) +``` + +### Additional properties + +These options are in addition to the Qt QSlider API, and control the behavior of the bar between handles. + +| getter | setter | type | default | description | +| -------------------- | ------------------------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------ | +| `barIsVisible` | `setBarIsVisible`
`hideBar` / `showBar` | `bool` | `True` | Whether the bar between handles is visible. | +| `barMovesAllHandles` | `setBarMovesAllHandles` | `bool` | `True` | Whether clicking on the bar moves all handles or just the nearest | +| `barIsRigid` | `setBarIsRigid` | `bool` | `True` | Whether bar length is constant or "elastic" when dragging the bar beyond min/max. | +------ + +## Examples + +These screenshots show `QRangeSlider` (multiple handles) next to the native `QSlider` +(single handle). With no styles applied, `QRangeSlider` will match the native OS +style of `QSlider` – with or without tick marks. When styles have been applied +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). If you'd like to style `QRangeSlider` differently than `QSlider`, +then you can also target it directly in your style sheet. The one "special" +property for QRangeSlider is `qproperty-barColor`, which sets the color of the +bar between the handles. + +> The code for these example widgets is [here](examples/demo_widget.py) + +
+ +See style sheet used for this example + +```css +/* +Because QRangeSlider inherits from QSlider, it will also inherit styles +*/ +QSlider { + min-height: 20px; +} + +QSlider::groove:horizontal { + border: 0px; + background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #777, stop:1 #aaa); + height: 20px; + border-radius: 10px; +} + +QSlider::handle { + background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.5, + fy:0.5, stop:0 #eef, stop:1 #000); + height: 20px; + width: 20px; + border-radius: 10px; +} + +/* +"QSlider::sub-page" is the one exception ... +(it styles the area to the left of the QSlider handle) +*/ +QSlider::sub-page:horizontal { + background: #447; + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; +} + +/* +for QRangeSlider: use "qproperty-barColor". "sub-page" will not work. +*/ +QRangeSlider { + qproperty-barColor: #447; +} +``` + +
+ +### macOS + +##### Catalina +![mac10](images/demo_darwin10.png) + +##### Big Sur +![mac11](images/demo_darwin11.png) + +### Windows + +![window](images/demo_windows.png) + +### Linux + +![linux](images/demo_linux.png) + + +## Labeled Sliders + +This package also includes two "labeled" slider variants. One for `QRangeSlider`, and one for the native `QSlider`: + +### `QLabeledRangeSlider` + +![labeled_range](images/labeled_range.png) + +```python +from qwidgets import QLabeledRangeSlider +``` + +This has the same API as `QRangeSlider` with the following additional options: + +#### `handleLabelPosition`/`setHandleLabelPosition` + +Where/whether labels are shown adjacent to slider handles. + +**type:** `QLabeledRangeSlider.LabelPosition` + +**default:** `LabelPosition.LabelsAbove` + +*options:* + +- `LabelPosition.NoLabel` (no labels shown adjacent to handles) +- `LabelPosition.LabelsAbove` +- `LabelPosition.LabelsBelow` +- `LabelPosition.LabelsRight` (alias for `LabelPosition.LabelsAbove`) +- `LabelPosition.LabelsLeft` (alias for `LabelPosition.LabelsBelow`) + + +#### `edgeLabelMode`/`setEdgeLabelMode` + +**type:** `QLabeledRangeSlider.EdgeLabelMode` + +**default:** `EdgeLabelMode.LabelIsRange` + +*options:* + +- `EdgeLabelMode.NoLabel`: no labels shown at slider extremes +- `EdgeLabelMode.LabelIsRange`: edge labels shown the min/max values +- `EdgeLabelMode.LabelIsValue`: edge labels shown the slider range + + +#### fine tuning position of labels: + +If you find that you need to fine tune the position of the handle labels: + +- `QLabeledRangeSlider.label_shift_x`: adjust horizontal label position +- `QLabeledRangeSlider.label_shift_y`: adjust vertical label position + +### `QLabeledSlider` + + +![labeled_range](images/labeled_qslider.png) + +```python +from qwidgets import QLabeledSlider +``` + +(no additional options at this point) + +## Issues + +If you encounter any problems, please [file an issue] along with a detailed +description. + +[file an issue]: https://github.com/napari/qwidgets/issues diff --git a/images/demo_darwin10.png b/docs/images/demo_darwin10.png similarity index 100% rename from images/demo_darwin10.png rename to docs/images/demo_darwin10.png diff --git a/images/demo_darwin11.png b/docs/images/demo_darwin11.png similarity index 100% rename from images/demo_darwin11.png rename to docs/images/demo_darwin11.png diff --git a/images/demo_linux.png b/docs/images/demo_linux.png similarity index 100% rename from images/demo_linux.png rename to docs/images/demo_linux.png diff --git a/images/demo_windows.png b/docs/images/demo_windows.png similarity index 100% rename from images/demo_windows.png rename to docs/images/demo_windows.png diff --git a/images/labeled_qslider.png b/docs/images/labeled_qslider.png similarity index 100% rename from images/labeled_qslider.png rename to docs/images/labeled_qslider.png diff --git a/images/labeled_range.png b/docs/images/labeled_range.png similarity index 100% rename from images/labeled_range.png rename to docs/images/labeled_range.png diff --git a/images/slider.png b/docs/images/slider.png similarity index 100% rename from images/slider.png rename to docs/images/slider.png diff --git a/examples/basic.py b/examples/basic.py index 5807cf2..2178e9f 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,6 +1,6 @@ -from qtrangeslider import QRangeSlider -from qtrangeslider.qtcompat.QtCore import Qt -from qtrangeslider.qtcompat.QtWidgets import QApplication +from qwidgets import QRangeSlider +from qwidgets.qtcompat.QtCore import Qt +from qwidgets.qtcompat.QtWidgets import QApplication app = QApplication([]) diff --git a/examples/basic_float.py b/examples/basic_float.py index 9bfc938..979df20 100644 --- a/examples/basic_float.py +++ b/examples/basic_float.py @@ -1,6 +1,6 @@ -from qtrangeslider import QDoubleSlider -from qtrangeslider.qtcompat.QtCore import Qt -from qtrangeslider.qtcompat.QtWidgets import QApplication +from qwidgets import QDoubleSlider +from qwidgets.qtcompat.QtCore import Qt +from qwidgets.qtcompat.QtWidgets import QApplication app = QApplication([]) diff --git a/examples/demo_widget.py b/examples/demo_widget.py index 414b7c4..a6c63a0 100644 --- a/examples/demo_widget.py +++ b/examples/demo_widget.py @@ -1,6 +1,6 @@ -from qtrangeslider import QRangeSlider -from qtrangeslider.qtcompat import QtCore -from qtrangeslider.qtcompat import QtWidgets as QtW +from qwidgets import QRangeSlider +from qwidgets.qtcompat import QtCore +from qwidgets.qtcompat import QtWidgets as QtW QSS = """ QSlider { diff --git a/examples/float.py b/examples/float.py index 08f814f..03475f3 100644 --- a/examples/float.py +++ b/examples/float.py @@ -1,6 +1,6 @@ -from qtrangeslider import QDoubleRangeSlider, QDoubleSlider, QRangeSlider -from qtrangeslider.qtcompat.QtCore import Qt -from qtrangeslider.qtcompat.QtWidgets import QApplication, QVBoxLayout, QWidget +from qwidgets import QDoubleRangeSlider, QDoubleSlider, QRangeSlider +from qwidgets.qtcompat.QtCore import Qt +from qwidgets.qtcompat.QtWidgets import QApplication, QVBoxLayout, QWidget app = QApplication([]) diff --git a/examples/generic.py b/examples/generic.py index bc37197..bb296e2 100644 --- a/examples/generic.py +++ b/examples/generic.py @@ -1,6 +1,6 @@ -from qtrangeslider import QDoubleSlider -from qtrangeslider.qtcompat.QtCore import Qt -from qtrangeslider.qtcompat.QtWidgets import QApplication +from qwidgets import QDoubleSlider +from qwidgets.qtcompat.QtCore import Qt +from qwidgets.qtcompat.QtWidgets import QApplication app = QApplication([]) diff --git a/examples/labeled.py b/examples/labeled.py index abb8f77..bdeb9a1 100644 --- a/examples/labeled.py +++ b/examples/labeled.py @@ -1,16 +1,11 @@ -from qtrangeslider._labeled import ( +from qwidgets import ( QLabeledDoubleRangeSlider, QLabeledDoubleSlider, QLabeledRangeSlider, QLabeledSlider, ) -from qtrangeslider.qtcompat.QtCore import Qt -from qtrangeslider.qtcompat.QtWidgets import ( - QApplication, - QHBoxLayout, - QVBoxLayout, - QWidget, -) +from qwidgets.qtcompat.QtCore import Qt +from qwidgets.qtcompat.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget app = QApplication([]) diff --git a/examples/multihandle.py b/examples/multihandle.py index e18557c..b51176c 100644 --- a/examples/multihandle.py +++ b/examples/multihandle.py @@ -1,5 +1,5 @@ -from qtrangeslider import QRangeSlider -from qtrangeslider.qtcompat.QtWidgets import QApplication +from qwidgets import QRangeSlider +from qwidgets.qtcompat.QtWidgets import QApplication app = QApplication([]) diff --git a/qwidgets/__init__.py b/qwidgets/__init__.py new file mode 100644 index 0000000..c592327 --- /dev/null +++ b/qwidgets/__init__.py @@ -0,0 +1,26 @@ +"""qwidgets is a collection of QtWidgets for python.""" +try: + from ._version import version as __version__ +except ImportError: + __version__ = "unknown" + + +from .sliders import ( + QDoubleRangeSlider, + QDoubleSlider, + QLabeledDoubleRangeSlider, + QLabeledDoubleSlider, + QLabeledRangeSlider, + QLabeledSlider, + QRangeSlider, +) + +__all__ = [ + "QDoubleRangeSlider", + "QDoubleSlider", + "QLabeledDoubleRangeSlider", + "QLabeledDoubleSlider", + "QLabeledRangeSlider", + "QLabeledSlider", + "QRangeSlider", +] diff --git a/qtrangeslider/qtcompat/QtCore.py b/qwidgets/qtcompat/QtCore.py similarity index 98% rename from qtrangeslider/qtcompat/QtCore.py rename to qwidgets/qtcompat/QtCore.py index 752f173..a755cc2 100644 --- a/qtrangeslider/qtcompat/QtCore.py +++ b/qwidgets/qtcompat/QtCore.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright © 2014-2015 Colin Duquesnoy # Copyright © 2009- The Spyder Development Team diff --git a/qtrangeslider/qtcompat/QtGui.py b/qwidgets/qtcompat/QtGui.py similarity index 97% rename from qtrangeslider/qtcompat/QtGui.py rename to qwidgets/qtcompat/QtGui.py index 595a6ee..fda74f5 100644 --- a/qtrangeslider/qtcompat/QtGui.py +++ b/qwidgets/qtcompat/QtGui.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright © 2014-2015 Colin Duquesnoy # Copyright © 2009- The Spyder Development Team diff --git a/qtrangeslider/qtcompat/QtWidgets.py b/qwidgets/qtcompat/QtWidgets.py similarity index 97% rename from qtrangeslider/qtcompat/QtWidgets.py rename to qwidgets/qtcompat/QtWidgets.py index 1e1ec31..1fa868e 100644 --- a/qtrangeslider/qtcompat/QtWidgets.py +++ b/qwidgets/qtcompat/QtWidgets.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright © 2014-2015 Colin Duquesnoy # Copyright © 2009- The Spyder Developmet Team diff --git a/qtrangeslider/qtcompat/__init__.py b/qwidgets/qtcompat/__init__.py similarity index 98% rename from qtrangeslider/qtcompat/__init__.py rename to qwidgets/qtcompat/__init__.py index 88faed6..97e3088 100644 --- a/qtrangeslider/qtcompat/__init__.py +++ b/qwidgets/qtcompat/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright © 2009- The Spyder Development Team # Copyright © 2014-2015 Colin Duquesnoy @@ -147,7 +146,7 @@ if API in PYSIDE6_API: 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" + "to use qwidgets: PyQt5, PyQt6, PySide2, or PySide6" ) # If a correct API name is passed to QT_API and it could not be found, diff --git a/qtrangeslider/__init__.py b/qwidgets/sliders/__init__.py similarity index 79% rename from qtrangeslider/__init__.py rename to qwidgets/sliders/__init__.py index 10ab0c2..823db8b 100644 --- a/qtrangeslider/__init__.py +++ b/qwidgets/sliders/__init__.py @@ -1,8 +1,3 @@ -try: - from ._version import version as __version__ -except ImportError: - __version__ = "unknown" - from ._labeled import ( QLabeledDoubleRangeSlider, QLabeledDoubleSlider, diff --git a/qtrangeslider/_generic_range_slider.py b/qwidgets/sliders/_generic_range_slider.py similarity index 98% rename from qtrangeslider/_generic_range_slider.py rename to qwidgets/sliders/_generic_range_slider.py index ddbfe06..42e1e1b 100644 --- a/qtrangeslider/_generic_range_slider.py +++ b/qwidgets/sliders/_generic_range_slider.py @@ -1,9 +1,7 @@ from typing import Generic, List, Sequence, Tuple, TypeVar, Union -from ._generic_slider import CC_SLIDER, SC_GROOVE, SC_HANDLE, SC_NONE, _GenericSlider -from ._range_style import RangeSliderStyle, update_styles_from_stylesheet -from .qtcompat import QtGui -from .qtcompat.QtCore import ( +from ..qtcompat import QtGui +from ..qtcompat.QtCore import ( Property, QEvent, QPoint, @@ -13,7 +11,9 @@ from .qtcompat.QtCore import ( Qt, Signal, ) -from .qtcompat.QtWidgets import QSlider, QStyle, QStyleOptionSlider, QStylePainter +from ..qtcompat.QtWidgets import QSlider, QStyle, QStyleOptionSlider, QStylePainter +from ._generic_slider import CC_SLIDER, SC_GROOVE, SC_HANDLE, SC_NONE, _GenericSlider +from ._range_style import RangeSliderStyle, update_styles_from_stylesheet _T = TypeVar("_T") diff --git a/qtrangeslider/_generic_slider.py b/qwidgets/sliders/_generic_slider.py similarity index 99% rename from qtrangeslider/_generic_slider.py rename to qwidgets/sliders/_generic_slider.py index 543b337..72aa122 100644 --- a/qtrangeslider/_generic_slider.py +++ b/qwidgets/sliders/_generic_slider.py @@ -22,9 +22,9 @@ QRangeSlider. from typing import Generic, TypeVar -from .qtcompat import QtGui -from .qtcompat.QtCore import QEvent, QPoint, QPointF, QRect, Qt, Signal -from .qtcompat.QtWidgets import ( +from ..qtcompat import QtGui +from ..qtcompat.QtCore import QEvent, QPoint, QPointF, QRect, Qt, Signal +from ..qtcompat.QtWidgets import ( QApplication, QSlider, QStyle, diff --git a/qtrangeslider/_labeled.py b/qwidgets/sliders/_labeled.py similarity index 98% rename from qtrangeslider/_labeled.py rename to qwidgets/sliders/_labeled.py index cc965f4..b20d9b9 100644 --- a/qtrangeslider/_labeled.py +++ b/qwidgets/sliders/_labeled.py @@ -1,10 +1,9 @@ from enum import IntEnum from functools import partial -from ._sliders import QDoubleRangeSlider, QDoubleSlider, QRangeSlider -from .qtcompat.QtCore import QPoint, QSize, Qt, Signal -from .qtcompat.QtGui import QFontMetrics, QValidator -from .qtcompat.QtWidgets import ( +from ..qtcompat.QtCore import QPoint, QSize, Qt, Signal +from ..qtcompat.QtGui import QFontMetrics, QValidator +from ..qtcompat.QtWidgets import ( QAbstractSlider, QApplication, QDoubleSpinBox, @@ -16,6 +15,7 @@ from .qtcompat.QtWidgets import ( QVBoxLayout, QWidget, ) +from ._sliders import QDoubleRangeSlider, QDoubleSlider, QRangeSlider class LabelPosition(IntEnum): @@ -32,7 +32,7 @@ class EdgeLabelMode(IntEnum): LabelIsValue = 2 -class SliderProxy: +class _SliderProxy: _slider: QSlider def value(self): @@ -112,7 +112,7 @@ def _handle_overloaded_slider_sig(args, kwargs): return parent, orientation -class QLabeledSlider(SliderProxy, QAbstractSlider): +class QLabeledSlider(_SliderProxy, QAbstractSlider): _slider_class = QSlider _slider: QSlider @@ -171,7 +171,7 @@ class QLabeledDoubleSlider(QLabeledSlider): self._label.setDecimals(prec) -class QLabeledRangeSlider(SliderProxy, QAbstractSlider): +class QLabeledRangeSlider(_SliderProxy, QAbstractSlider): valueChanged = Signal(tuple) LabelPosition = LabelPosition EdgeLabelMode = EdgeLabelMode diff --git a/qtrangeslider/_range_style.py b/qwidgets/sliders/_range_style.py similarity index 96% rename from qtrangeslider/_range_style.py rename to qwidgets/sliders/_range_style.py index 4e85352..6fc2764 100644 --- a/qtrangeslider/_range_style.py +++ b/qwidgets/sliders/_range_style.py @@ -5,9 +5,9 @@ import re from dataclasses import dataclass, replace from typing import TYPE_CHECKING -from .qtcompat import PYQT_VERSION -from .qtcompat.QtCore import Qt -from .qtcompat.QtGui import ( +from ..qtcompat import PYQT_VERSION +from ..qtcompat.QtCore import Qt +from ..qtcompat.QtGui import ( QBrush, QColor, QGradient, @@ -15,7 +15,7 @@ from .qtcompat.QtGui import ( QPalette, QRadialGradient, ) -from .qtcompat.QtWidgets import QApplication, QSlider, QStyleOptionSlider +from ..qtcompat.QtWidgets import QApplication, QSlider, QStyleOptionSlider if TYPE_CHECKING: from ._generic_range_slider import _GenericRangeSlider @@ -241,7 +241,7 @@ def parse_color(color: str, default_attr) -> QColor | QGradient: # try linear gradient: match = qlineargrad_pattern.search(color) if match: - grad = QLinearGradient(*[float(i) for i in match.groups()[:4]]) + grad = QLinearGradient(*(float(i) for i in match.groups()[:4])) grad.setColorAt(0, QColor(match.groupdict()["stop0"])) grad.setColorAt(1, QColor(match.groupdict()["stop1"])) return grad @@ -249,7 +249,7 @@ def parse_color(color: str, default_attr) -> QColor | QGradient: # try linear gradient: match = qradial_pattern.search(color) if match: - grad = QRadialGradient(*[float(i) for i in match.groups()[:5]]) + grad = QRadialGradient(*(float(i) for i in match.groups()[:5])) grad.setColorAt(0, QColor(match.groupdict()["stop0"])) grad.setColorAt(1, QColor(match.groupdict()["stop1"])) return grad diff --git a/qtrangeslider/_sliders.py b/qwidgets/sliders/_sliders.py similarity index 96% rename from qtrangeslider/_sliders.py rename to qwidgets/sliders/_sliders.py index 4e2ab54..9eb3a26 100644 --- a/qtrangeslider/_sliders.py +++ b/qwidgets/sliders/_sliders.py @@ -1,6 +1,6 @@ +from ..qtcompat.QtCore import Signal from ._generic_range_slider import _GenericRangeSlider from ._generic_slider import _GenericSlider -from .qtcompat.QtCore import Signal class _IntMixin: diff --git a/qtrangeslider/_tests/__init__.py b/qwidgets/sliders/_tests/__init__.py similarity index 100% rename from qtrangeslider/_tests/__init__.py rename to qwidgets/sliders/_tests/__init__.py diff --git a/qtrangeslider/_tests/_testutil.py b/qwidgets/sliders/_tests/_testutil.py similarity index 89% rename from qtrangeslider/_tests/_testutil.py rename to qwidgets/sliders/_tests/_testutil.py index 47651db..5b6e555 100644 --- a/qtrangeslider/_tests/_testutil.py +++ b/qwidgets/sliders/_tests/_testutil.py @@ -4,9 +4,9 @@ from platform import system import pytest -from qtrangeslider.qtcompat import QT_VERSION -from qtrangeslider.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt -from qtrangeslider.qtcompat.QtGui import QMouseEvent, QWheelEvent +from qwidgets.qtcompat import QT_VERSION +from qwidgets.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt +from qwidgets.qtcompat.QtGui import QMouseEvent, QWheelEvent QT_VERSION = LooseVersion(QT_VERSION) diff --git a/qtrangeslider/_tests/test_float.py b/qwidgets/sliders/_tests/test_float.py similarity index 97% rename from qtrangeslider/_tests/test_float.py rename to qwidgets/sliders/_tests/test_float.py index 43d504e..27a9c53 100644 --- a/qtrangeslider/_tests/test_float.py +++ b/qwidgets/sliders/_tests/test_float.py @@ -2,13 +2,13 @@ import os import pytest -from qtrangeslider import ( +from qwidgets import ( QDoubleRangeSlider, QDoubleSlider, QLabeledDoubleRangeSlider, QLabeledDoubleSlider, ) -from qtrangeslider.qtcompat import API_NAME +from qwidgets.qtcompat import API_NAME range_types = {QDoubleRangeSlider, QLabeledDoubleRangeSlider} diff --git a/qtrangeslider/_tests/test_generic_slider.py b/qwidgets/sliders/_tests/test_generic_slider.py similarity index 95% rename from qtrangeslider/_tests/test_generic_slider.py rename to qwidgets/sliders/_tests/test_generic_slider.py index de7dfda..b6a53bd 100644 --- a/qtrangeslider/_tests/test_generic_slider.py +++ b/qwidgets/sliders/_tests/test_generic_slider.py @@ -2,10 +2,10 @@ import math import pytest -from qtrangeslider._generic_slider import _GenericSlider -from qtrangeslider.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt -from qtrangeslider.qtcompat.QtGui import QHoverEvent -from qtrangeslider.qtcompat.QtWidgets import QStyle, QStyleOptionSlider +from qwidgets.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt +from qwidgets.qtcompat.QtGui import QHoverEvent +from qwidgets.qtcompat.QtWidgets import QStyle, QStyleOptionSlider +from qwidgets.sliders._generic_slider import _GenericSlider from ._testutil import _linspace, _mouse_event, _wheel_event, skip_on_linux_qt6 diff --git a/qtrangeslider/_tests/test_range_slider.py b/qwidgets/sliders/_tests/test_range_slider.py similarity index 95% rename from qtrangeslider/_tests/test_range_slider.py rename to qwidgets/sliders/_tests/test_range_slider.py index a87ffd2..a671214 100644 --- a/qtrangeslider/_tests/test_range_slider.py +++ b/qwidgets/sliders/_tests/test_range_slider.py @@ -2,10 +2,10 @@ import math import pytest -from qtrangeslider import QDoubleRangeSlider, QRangeSlider -from qtrangeslider.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt -from qtrangeslider.qtcompat.QtGui import QHoverEvent -from qtrangeslider.qtcompat.QtWidgets import QStyle, QStyleOptionSlider +from qwidgets import QDoubleRangeSlider, QRangeSlider +from qwidgets.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt +from qwidgets.qtcompat.QtGui import QHoverEvent +from qwidgets.qtcompat.QtWidgets import QStyle, QStyleOptionSlider from ._testutil import _linspace, _mouse_event, _wheel_event, skip_on_linux_qt6 diff --git a/qtrangeslider/_tests/test_single_value_sliders.py b/qwidgets/sliders/_tests/test_single_value_sliders.py similarity index 95% rename from qtrangeslider/_tests/test_single_value_sliders.py rename to qwidgets/sliders/_tests/test_single_value_sliders.py index aff4f03..a0044da 100644 --- a/qtrangeslider/_tests/test_single_value_sliders.py +++ b/qwidgets/sliders/_tests/test_single_value_sliders.py @@ -4,11 +4,11 @@ from distutils.version import LooseVersion import pytest -from qtrangeslider import QDoubleSlider, QLabeledDoubleSlider, QLabeledSlider -from qtrangeslider._generic_slider import _GenericSlider -from qtrangeslider.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt -from qtrangeslider.qtcompat.QtGui import QHoverEvent -from qtrangeslider.qtcompat.QtWidgets import QSlider, QStyle, QStyleOptionSlider +from qwidgets import QDoubleSlider, QLabeledDoubleSlider, QLabeledSlider +from qwidgets.qtcompat.QtCore import QEvent, QPoint, QPointF, Qt +from qwidgets.qtcompat.QtGui import QHoverEvent +from qwidgets.qtcompat.QtWidgets import QSlider, QStyle, QStyleOptionSlider +from qwidgets.sliders._generic_slider import _GenericSlider from ._testutil import ( QT_VERSION, diff --git a/qtrangeslider/_tests/test_slider.py b/qwidgets/sliders/_tests/test_slider.py similarity index 95% rename from qtrangeslider/_tests/test_slider.py rename to qwidgets/sliders/_tests/test_slider.py index 5a0180e..a15ef6e 100644 --- a/qtrangeslider/_tests/test_slider.py +++ b/qwidgets/sliders/_tests/test_slider.py @@ -2,10 +2,10 @@ import platform import pytest -from qtrangeslider import QRangeSlider -from qtrangeslider._generic_range_slider import SC_BAR, SC_HANDLE, SC_NONE -from qtrangeslider.qtcompat import API_NAME -from qtrangeslider.qtcompat.QtCore import Qt +from qwidgets import QRangeSlider +from qwidgets.qtcompat import API_NAME +from qwidgets.qtcompat.QtCore import Qt +from qwidgets.sliders._generic_range_slider import SC_BAR, SC_HANDLE, SC_NONE NOT_LINUX = platform.system() != "Linux" NOT_PYSIDE2 = API_NAME != "PySide2" diff --git a/setup.cfg b/setup.cfg index 71f3c1e..817d7ed 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,18 +1,13 @@ [metadata] -name = QtRangeSlider -url = https://github.com/tlambert03/QtRangeSlider -license = BSD-3 -license_file = LICENSE -description = Multi-handle range slider widget for PyQt/PySide -long_description = file: README.md, CHANGELOG.md +name = qwidgets +description = Missing widgets for PyQt/PySide +long_description = file: README.md long_description_content_type = text/markdown +url = https://github.com/napari/qwidgets author = Talley Lambert author_email = talley.lambert@gmail.com -keywords = qt, range slider, widget -project_urls = - Source = https://github.com/tlambert03/QtRangeSlider - Tracker = https://github.com/tlambert03/QtRangeSlider/issues - Changelog = https://github.com/tlambert03/QtRangeSlider/blob/master/CHANGELOG.md +license = BSD-3-Clause +license_file = LICENSE classifiers = Development Status :: 4 - Beta Environment :: X11 Applications :: Qt @@ -20,49 +15,69 @@ classifiers = License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python + Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython Topic :: Desktop Environment Topic :: Software Development Topic :: Software Development :: User Interfaces Topic :: Software Development :: Widget Sets +keywords = qt, range slider, widget +project_urls = + Source = https://github.com/napari/qwidgets + Tracker = https://github.com/napari/qwidgets/issues + Changelog = https://github.com/napari/qwidgets/blob/master/CHANGELOG.md [options] -zip_safe = False packages = find: -python_requires = >=3.6 -setup_requires = setuptools_scm +python_requires = >=3.7 +setup_requires = + setuptools_scm +zip_safe = False [options.extras_require] -pyside2 = pyside2 -pyqt5 = pyqt5 -pyside6 = pyside6 -pyqt6 = pyqt6 -testing = - tox - tox-conda - pytest - pytest-qt - pytest-cov dev = ipython - jedi<0.18.0 isort + jedi<0.18.0 mypy pre-commit - %(testing)s - %(pyqt5)s + pyside2 + pytest + pytest-cov + pytest-qt + tox + tox-conda +pyqt5 = + pyqt5 +pyqt6 = + pyqt6 +pyside2 = + pyside2 +pyside6 = + pyside6 +testing = + pytest + pytest-cov + pytest-qt + tox + tox-conda [flake8] exclude = _version.py,.eggs,examples docstring-convention = numpy -ignore = E203,W503,E501,C901,F403,F405 +ignore = E203,W503,E501,C901,F403,F405,D100 + +[pydocstyle] +convention = numpy +add_select = D402,D415,D417 +ignore = D100 [isort] -profile=black +profile = black [tool:pytest] addopts = -W error diff --git a/setup.py b/setup.py index 336090f..c45171e 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,6 @@ -""" -PEP 517 doesn’t support editable installs -so this file is currently here to support "pip install -e ." -""" from setuptools import setup setup( - use_scm_version={"write_to": "qtrangeslider/_version.py"}, + use_scm_version={"write_to": "qwidgets/_version.py"}, setup_requires=["setuptools_scm"], ) diff --git a/tox.ini b/tox.ini index 0dc0f67..ea0c2fc 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,18 @@ envlist = py{37,38,39}-{linux,macos,windows}-{pyqt5,pyside2,pyqt6,pyside6},py37-{linux,macos,windows}-{pyqt511,pyside511} toxworkdir=/tmp/.tox +[coverage:report] +exclude_lines = + pragma: no cover + if TYPE_CHECKING: + \.\.\. + except ImportError* + raise NotImplementedError() +omit = + qwidgets/_version.py + qwidgets/qtcompat/* + *_tests* + [gh-actions] python = 3.6: py36 @@ -45,4 +57,4 @@ extras = pyside6: pyside6 commands_pre = pyqt6,pyside6: pip install -U pytest-qt@git+https://github.com/pytest-dev/pytest-qt.git -commands = pytest --color=yes --cov=qtrangeslider --cov-report=xml {posargs} +commands = pytest --color=yes --cov=qwidgets --cov-report=xml {posargs}