Compare commits

...

63 Commits

Author SHA1 Message Date
Talley Lambert
b79c8e95b7 chore: changelog v0.3.8 2022-10-10 15:37:16 -04:00
Kira Evans
b393c6d039 fix: allow submodule imports (#128)
* fix: allow submodule imports

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-10 15:35:53 -04:00
Talley Lambert
61b8ab30ab chore: changelog v0.3.7 2022-10-10 08:27:33 -04:00
Talley Lambert
abf544cf0e feat: add Quantity widget (using pint) (#126)
* wip

* simplified quantity widget

* fix example

* more docs

* add test

* update docs

* try to avoid overflow

* reduce again
2022-10-10 08:22:52 -04:00
Talley Lambert
9f9dab6f3b fix readme (#125) 2022-10-05 11:16:55 -04:00
Talley Lambert
97bb814451 Docs (#124)
* wip

* wip

* more wip

* progress

* more docs

* more changes

* add link

* more examples and improvements

* fix check-manifest

* sort members

* remove autogen images

* remove _images

* add font docs

* add link to utils
2022-10-05 08:59:27 -04:00
Talley Lambert
d1c056886f chore: changelog v0.3.6 2022-10-03 17:04:14 -04:00
Talley Lambert
a73e56bb83 fix: fix missing labels after setValue (#123) 2022-10-03 17:00:53 -04:00
Talley Lambert
6f71e46914 feat: add editing finished signal to LabeledSliders (#122)
* feat: add editing finished signal to LabeledSliders

* remove extra file
2022-10-03 17:00:29 -04:00
Talley Lambert
fbc67a745c fix: Offer patch for (unstyled) QSliders on macos 12 and Qt <6 (#117)
* wip

* opt-in patch

* finishes

* add patch to demo

* remove demo 2

* extend docs
2022-10-03 15:49:34 -04:00
Talley Lambert
77bd737e13 fix: Fix TypeError on slider rangeChanged signal (#121)
* fix: fix sliders type signals

* test: more tests

* fix for pyside2

* skip type check on py37
2022-10-03 15:10:40 -04:00
pre-commit-ci[bot]
ba626e8786 ci(pre-commit.ci): pre-commit autoupdate (#120)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.971 → v0.981](https://github.com/pre-commit/mirrors-mypy/compare/v0.971...v0.981)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-03 15:09:47 -04:00
Grzegorz Bokota
04efa95511 dummy fix (#119) 2022-10-03 08:24:45 -04:00
pre-commit-ci[bot]
f401d6d59c [pre-commit.ci] pre-commit autoupdate (#116)
updates:
- https://github.com/myint/autoflakehttps://github.com/PyCQA/autoflake
- [github.com/PyCQA/autoflake: v1.5.3 → v1.6.1](https://github.com/PyCQA/autoflake/compare/v1.5.3...v1.6.1)
- [github.com/asottile/pyupgrade: v2.37.3 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.37.3...v2.38.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-02 19:20:26 -04:00
pre-commit-ci[bot]
a3bd0d0edf [pre-commit.ci] pre-commit autoupdate (#114) 2022-09-05 16:06:28 -04:00
pre-commit-ci[bot]
e7e8dfc44c [pre-commit.ci] pre-commit autoupdate (#109)
updates:
- [github.com/PyCQA/flake8: 5.0.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/5.0.2...5.0.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-08-17 12:55:20 -04:00
Talley Lambert
a556f16745 chore: changelog v0.3.5 (#110)
* chore: changelog v0.3.5

* try fix napari test

* again

* try another

* again

* fix again
2022-08-17 12:54:27 -04:00
sfhbarnett
2864058974 fix range slider drag crash on PyQt6 (#108)
* fix range slider drag crash on PyQt6

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-08-04 11:01:19 -04:00
pre-commit-ci[bot]
463332f4fc [pre-commit.ci] pre-commit autoupdate (#104)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/asottile/setup-cfg-fmt: v1.20.2 → v2.0.0](https://github.com/asottile/setup-cfg-fmt/compare/v1.20.2...v2.0.0)
- [github.com/PyCQA/flake8: 4.0.1 → 5.0.2](https://github.com/PyCQA/flake8/compare/4.0.1...5.0.2)
- [github.com/asottile/pyupgrade: v2.37.2 → v2.37.3](https://github.com/asottile/pyupgrade/compare/v2.37.2...v2.37.3)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* version specs

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
2022-08-03 09:36:12 -04:00
Markus Stabrin
f08e2d1720 Fix float value error in pyqt configuration (#106)
Co-authored-by: Markus Stabrin <markus.stabrin@gmail.com>
2022-08-03 09:35:56 -04:00
pre-commit-ci[bot]
39c10aa238 [pre-commit.ci] pre-commit autoupdate (#102)
updates:
- [github.com/asottile/setup-cfg-fmt: v1.20.1 → v1.20.2](https://github.com/asottile/setup-cfg-fmt/compare/v1.20.1...v1.20.2)
- [github.com/asottile/pyupgrade: v2.37.1 → v2.37.2](https://github.com/asottile/pyupgrade/compare/v2.37.1...v2.37.2)
- [github.com/pre-commit/mirrors-mypy: v0.961 → v0.971](https://github.com/pre-commit/mirrors-mypy/compare/v0.961...v0.971)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-31 11:46:56 -04:00
Talley Lambert
d5d40a35f3 changelog 0.3.4 2022-07-24 11:10:01 -04:00
Talley Lambert
5b92a19b82 fix: relax runtime typing extensions requirement (#101) 2022-07-24 11:08:00 -04:00
pre-commit-ci[bot]
a3b0f1b115 [pre-commit.ci] pre-commit autoupdate (#97)
updates:
- [github.com/asottile/pyupgrade: v2.34.0 → v2.37.1](https://github.com/asottile/pyupgrade/compare/v2.34.0...v2.37.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-13 11:57:21 -04:00
Talley Lambert
b1e6d55957 fix: catch qpixmap deprecation (#99) 2022-07-13 11:57:01 -04:00
Talley Lambert
55535b7600 chore: changelog v0.3.3 2022-07-10 10:15:33 -04:00
pre-commit-ci[bot]
31c834053c [pre-commit.ci] pre-commit autoupdate (#96)
updates:
- [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
2022-07-07 17:08:52 -04:00
Talley Lambert
69219c846d Revert "update typing and namespace"
This reverts commit 2edb3c287e.
2022-07-07 16:49:26 -04:00
Talley Lambert
2edb3c287e update typing and namespace 2022-07-07 16:47:04 -04:00
Talley Lambert
218a7b4034 fix: fix deprecation warning on fonticon plugin discovery on python 3.10 (#95)
* fix: fix fonticon

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix entry points API

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-01 11:23:00 -04:00
pre-commit-ci[bot]
9ab24dbcf6 [pre-commit.ci] pre-commit autoupdate (#93)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0)
- [github.com/asottile/pyupgrade: v2.32.1 → v2.34.0](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.34.0)
- [github.com/pre-commit/mirrors-mypy: v0.960 → v0.961](https://github.com/pre-commit/mirrors-mypy/compare/v0.960...v0.961)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-01 11:13:41 -04:00
pre-commit-ci[bot]
35acbbf5e6 [pre-commit.ci] pre-commit autoupdate (#90)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.950 → v0.960](https://github.com/pre-commit/mirrors-mypy/compare/v0.950...v0.960)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-06-11 14:54:37 -04:00
Grzegorz Bokota
0ae3350c57 Add code syntax highlight utils (#88)
* add code syntax highlight code

* add example

* add documentation and fix example

* add tests

* add information about napari theme usage

* clean napari mention
2022-05-18 16:50:51 -04:00
pre-commit-ci[bot]
c7f8780900 [pre-commit.ci] pre-commit autoupdate (#87)
updates:
- [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-05-18 10:05:00 -04:00
Talley Lambert
cc25733ce8 Add changelog for v0.3.2 (#86)
* Add changelog for v0.3.2

* caps
2022-05-03 10:14:15 -04:00
pre-commit-ci[bot]
accb87021f [pre-commit.ci] pre-commit autoupdate (#85)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.942 → v0.950](https://github.com/pre-commit/mirrors-mypy/compare/v0.942...v0.950)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-05-02 14:05:19 -04:00
Talley Lambert
ccad397838 fix crazy animation loop on collapsible (#84) 2022-05-02 14:01:17 -04:00
Talley Lambert
68248c920c reorder label update signal (#83) 2022-04-28 13:31:16 -04:00
Grzegorz Bokota
f8ac85aaf6 feat: Add QSearchableListWidget and QSearchableComboBox widgets (#80)
* implement widgets

* add basic documentation

* Add examples

* try version without packaging

Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
2022-04-25 14:03:24 -04:00
Talley Lambert
bd6fba96ad fix deprecation warnings in tests (#82)
* stub

* update tests

* use util func

* add fallback for older versions

* don't test 3.6
2022-04-24 11:04:50 -04:00
Nekyo
7d31812858 Fix CSS for Collapsible (#79)
The button used for the Collapsible previously showed a black on
archlinux.
This fixes it to display properly.
Closes https://github.com/napari/superqt/issues/78
2022-04-17 10:58:05 -04:00
pre-commit-ci[bot]
f27377ab1b [pre-commit.ci] pre-commit autoupdate (#76)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0)
- [github.com/asottile/setup-cfg-fmt: v1.20.0 → v1.20.1](https://github.com/asottile/setup-cfg-fmt/compare/v1.20.0...v1.20.1)
- [github.com/asottile/pyupgrade: v2.31.1 → v2.32.0](https://github.com/asottile/pyupgrade/compare/v2.31.1...v2.32.0)
- [github.com/psf/black: 22.1.0 → 22.3.0](https://github.com/psf/black/compare/22.1.0...22.3.0)
- [github.com/pre-commit/mirrors-mypy: v0.941 → v0.942](https://github.com/pre-commit/mirrors-mypy/compare/v0.941...v0.942)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-04-17 10:54:09 -04:00
pre-commit-ci[bot]
2052fb8310 [pre-commit.ci] pre-commit autoupdate (#75)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.940 → v0.941](https://github.com/pre-commit/mirrors-mypy/compare/v0.940...v0.941)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-03-22 10:38:06 -04:00
pre-commit-ci[bot]
40d3e20bff [pre-commit.ci] pre-commit autoupdate (#73)
updates:
- [github.com/asottile/pyupgrade: v2.31.0 → v2.31.1](https://github.com/asottile/pyupgrade/compare/v2.31.0...v2.31.1)
- [github.com/pre-commit/mirrors-mypy: v0.931 → v0.940](https://github.com/pre-commit/mirrors-mypy/compare/v0.931...v0.940)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-03-14 16:43:55 -04:00
Talley Lambert
f4d9881b0c Fix height of expanded QCollapsible when child changes size (#72)
* update height when child changes

* return false
2022-03-11 14:16:03 -05:00
Talley Lambert
ba1ae92bcc changelog (#71) 2022-03-02 08:26:05 -05:00
Talley Lambert
8217a1cc71 check min requirements (#70) 2022-03-02 07:54:03 -05:00
Talley Lambert
96de1a261a add signals_blocked util (#69) 2022-02-20 11:25:10 -05:00
Talley Lambert
d8a8328793 add 0.3.0 changelog (#68) 2022-02-16 16:53:51 -05:00
Talley Lambert
2a9f47816a add napari test to CI (#67)
* add napari test

* fix test
2022-02-16 16:39:29 -05:00
Talley Lambert
e06ab4d081 Qthrottler and debouncer (#62)
* initial working throttler

* complete typing and docs

* basic test

* fix future subscript

* touch ups

* Update src/superqt/utils/_throttler.py

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>

* Update src/superqt/utils/_throttler.py

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>

* Update src/superqt/utils/_throttler.py

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>
2022-02-16 16:20:21 -05:00
Talley Lambert
13e092e381 add release action (#65) 2022-02-14 14:24:29 -05:00
Talley Lambert
b2c485bcea fix nested calls (#63) 2022-02-14 14:09:18 -05:00
Talley Lambert
d0d67da377 fix xvfb tests (#61) 2022-02-03 17:06:07 -05:00
pre-commit-ci[bot]
bc98f15ba1 [pre-commit.ci] pre-commit autoupdate (#60)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/psf/black: 21.12b0 → 22.1.0](https://github.com/psf/black/compare/21.12b0...22.1.0)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-02-03 10:10:05 -05:00
Talley Lambert
49bd078012 add edgeLabelMode option to QLabeledSlider (#59) 2022-01-31 13:07:20 -05:00
pre-commit-ci[bot]
d379611491 [pre-commit.ci] pre-commit autoupdate (#55)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.930 → v0.931](https://github.com/pre-commit/mirrors-mypy/compare/v0.930...v0.931)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-01-20 07:31:24 -05:00
Talley Lambert
329eaaa9a0 more proxy signals (#54) 2022-01-07 13:17:12 -05:00
Talley Lambert
d25f4c1cf7 Use qtpy, deprecate superqt.qtcompat, drop support for Qt <5.12 (#39)
* remove qtcompat

* change imports

* change codecov

* add dep

* run tests against dalthviz branch

* update

* more replace

* pin pyside6 for sake of test

* add deprecation

* drop qt 5.11

* unpin pyside6
2022-01-07 12:19:59 -05:00
pre-commit-ci[bot]
a07ee64f8b [pre-commit.ci] pre-commit autoupdate (#52) 2022-01-03 13:21:40 -05:00
Talley Lambert
bbd60eebaf Ugly but functional workaround for pyside6.2.1 breakages (#51)
* working but ugly

* remove signalinstsance type annotation

* change method name

* move line
2022-01-03 10:17:07 -05:00
pre-commit-ci[bot]
9c55c6c657 [pre-commit.ci] pre-commit autoupdate (#47)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0)
- [github.com/psf/black: 21.11b1 → 21.12b0](https://github.com/psf/black/compare/21.11b1...21.12b0)
- [github.com/pre-commit/mirrors-mypy: v0.910-1 → v0.930](https://github.com/pre-commit/mirrors-mypy/compare/v0.910-1...v0.930)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-01-01 17:39:14 -05:00
Talley Lambert
3c217026af fix codecov badge 2021-11-27 10:12:01 -05:00
161 changed files with 3836 additions and 1186 deletions

View File

@@ -70,17 +70,8 @@ jobs:
- python-version: 3.8
platform: ubuntu-18.04
backend: pyside2
- python-version: 3.6
platform: windows-2016
backend: pyqt5
# legacy Qt
- python-version: 3.7
platform: ubuntu-latest
backend: pyside511
- python-version: 3.7
platform: ubuntu-latest
backend: pyqt511
- python-version: 3.7
platform: ubuntu-latest
backend: pyqt512
@@ -91,8 +82,6 @@ jobs:
platform: ubuntu-latest
backend: pyqt514
steps:
- uses: actions/checkout@v2
@@ -110,13 +99,13 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools tox tox-gh-actions
python -m pip install setuptools tox tox-gh-actions
- name: Test with tox
uses: GabrielBB/xvfb-action@v1
timeout-minutes: 3
with:
run: tox
run: python -m tox
env:
PLATFORM: ${{ matrix.platform }}
BACKEND: ${{ matrix.backend }}
@@ -144,6 +133,60 @@ jobs:
name: screenshots ${{ runner.os }}
path: screenshots
test_old_qtpy:
name: qtpy minreq
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: tlambert03/setup-qt-libs@v1
- uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: install
run: |
python -m pip install -U pip
python -m pip install -e .[testing,pyqt5]
python -m pip install qtpy==1.1.0 typing-extensions==3.10.0.0
- name: Test napari magicgui
uses: GabrielBB/xvfb-action@v1
with:
run: python -m pytest --color=yes
test_napari:
name: napari tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
path: superqt
- uses: actions/checkout@v3
with:
repository: napari/napari
path: napari-repo
fetch-depth: 2
- uses: tlambert03/setup-qt-libs@v1
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: install
run: |
python -m pip install -U pip
python -m pip install ./superqt
python -m pip install ./napari-repo[testing,pyqt5]
- name: Test napari
uses: GabrielBB/xvfb-action@v1
with:
working-directory: napari-repo
run: python -m pytest --color=yes napari/_qt
check_manifest:
runs-on: ubuntu-latest
steps:
@@ -183,3 +226,7 @@ jobs:
python -m build
twine check dist/*
twine upload dist/*
- uses: softprops/action-gh-release@v1
with:
generate_release_notes: true

View File

@@ -5,4 +5,5 @@ user=napari
project=superqt
issues=false
since-tag=v0.2.0
add-sections={"documentation":{"prefix":"**Documentation updates:**","labels":["documentation"]}}
exclude-labels=duplicate,question,invalid,wontfix,hide
add-sections={"documentation":{"prefix":"**Documentation updates:**","labels":["documentation"]},"tests":{"prefix":"**Tests & CI:**","labels":["tests"]},"refactor":{"prefix":"**Refactors:**","labels":["refactor"]}}

1
.gitignore vendored
View File

@@ -82,3 +82,4 @@ src/superqt/_version.py
screenshots
.mypy_cache
docs/_auto_images/

View File

@@ -1,21 +1,22 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.3.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.20.0
rev: v2.0.0
hooks:
- id: setup-cfg-fmt
args: ["--include-version-classifiers"]
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
rev: 5.0.4
hooks:
- id: flake8
additional_dependencies: [flake8-typing-imports==1.7.0]
exclude: examples
- repo: https://github.com/myint/autoflake
rev: v1.4
- repo: https://github.com/PyCQA/autoflake
rev: v1.6.1
hooks:
- id: autoflake
args: ["--in-place", "--remove-all-unused-imports"]
@@ -24,16 +25,16 @@ repos:
hooks:
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v2.29.1
rev: v2.38.2
hooks:
- id: pyupgrade
args: [--py37-plus, --keep-runtime-typing]
- repo: https://github.com/psf/black
rev: 21.11b1
rev: 22.8.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.910-1
rev: v0.981
hooks:
- id: mypy
exclude: examples

View File

@@ -1,5 +1,154 @@
# Changelog
## [0.3.8](https://github.com/napari/superqt/tree/0.3.8) (2022-10-10)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.7...0.3.8)
**Fixed bugs:**
- fix: allow submodule imports [\#128](https://github.com/napari/superqt/pull/128) ([kne42](https://github.com/kne42))
## [v0.3.7](https://github.com/napari/superqt/tree/v0.3.7) (2022-10-10)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.6...v0.3.7)
**Implemented enhancements:**
- feat: add Quantity widget \(using pint\) [\#126](https://github.com/napari/superqt/pull/126) ([tlambert03](https://github.com/tlambert03))
## [v0.3.6](https://github.com/napari/superqt/tree/v0.3.6) (2022-10-05)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.6rc0...v0.3.6)
**Documentation updates:**
- minor fix to readme [\#125](https://github.com/napari/superqt/pull/125) ([tlambert03](https://github.com/tlambert03))
- Docs [\#124](https://github.com/napari/superqt/pull/124) ([tlambert03](https://github.com/tlambert03))
## [v0.3.6rc0](https://github.com/napari/superqt/tree/v0.3.6rc0) (2022-10-03)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.5...v0.3.6rc0)
**Implemented enhancements:**
- feat: add editing finished signal to LabeledSliders [\#122](https://github.com/napari/superqt/pull/122) ([tlambert03](https://github.com/tlambert03))
**Fixed bugs:**
- fix: fix missing labels after setValue [\#123](https://github.com/napari/superqt/pull/123) ([tlambert03](https://github.com/tlambert03))
- fix: Fix TypeError on slider rangeChanged signal [\#121](https://github.com/napari/superqt/pull/121) ([tlambert03](https://github.com/tlambert03))
- Simple workaround for pyside 6 [\#119](https://github.com/napari/superqt/pull/119) ([Czaki](https://github.com/Czaki))
- fix: Offer patch for \(unstyled\) QSliders on macos 12 and Qt \<6 [\#117](https://github.com/napari/superqt/pull/117) ([tlambert03](https://github.com/tlambert03))
## [v0.3.5](https://github.com/napari/superqt/tree/v0.3.5) (2022-08-17)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.4...v0.3.5)
**Fixed bugs:**
- fix range slider drag crash on PyQt6 [\#108](https://github.com/napari/superqt/pull/108) ([sfhbarnett](https://github.com/sfhbarnett))
- Fix float value error in pyqt configuration [\#106](https://github.com/napari/superqt/pull/106) ([mstabrin](https://github.com/mstabrin))
**Merged pull requests:**
- chore: changelog v0.3.5 [\#110](https://github.com/napari/superqt/pull/110) ([tlambert03](https://github.com/tlambert03))
## [v0.3.4](https://github.com/napari/superqt/tree/v0.3.4) (2022-07-24)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.3...v0.3.4)
**Fixed bugs:**
- fix: relax runtime typing extensions requirement [\#101](https://github.com/napari/superqt/pull/101) ([tlambert03](https://github.com/tlambert03))
- fix: catch qpixmap deprecation [\#99](https://github.com/napari/superqt/pull/99) ([tlambert03](https://github.com/tlambert03))
## [v0.3.3](https://github.com/napari/superqt/tree/v0.3.3) (2022-07-10)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.2...v0.3.3)
**Implemented enhancements:**
- Add code syntax highlight utils [\#88](https://github.com/napari/superqt/pull/88) ([Czaki](https://github.com/Czaki))
**Fixed bugs:**
- fix: fix deprecation warning on fonticon plugin discovery on python 3.10 [\#95](https://github.com/napari/superqt/pull/95) ([tlambert03](https://github.com/tlambert03))
## [v0.3.2](https://github.com/napari/superqt/tree/v0.3.2) (2022-05-03)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.1...v0.3.2)
**Implemented enhancements:**
- Add QSearchableListWidget and QSearchableComboBox widgets [\#80](https://github.com/napari/superqt/pull/80) ([Czaki](https://github.com/Czaki))
**Fixed bugs:**
- Fix crazy animation loop on Qcollapsible [\#84](https://github.com/napari/superqt/pull/84) ([tlambert03](https://github.com/tlambert03))
- Reorder label update signal [\#83](https://github.com/napari/superqt/pull/83) ([tlambert03](https://github.com/tlambert03))
- Fix height of expanded QCollapsible when child changes size [\#72](https://github.com/napari/superqt/pull/72) ([tlambert03](https://github.com/tlambert03))
**Tests & CI:**
- Fix deprecation warnings in tests [\#82](https://github.com/napari/superqt/pull/82) ([tlambert03](https://github.com/tlambert03))
**Merged pull requests:**
- Add changelog for v0.3.2 [\#86](https://github.com/napari/superqt/pull/86) ([tlambert03](https://github.com/tlambert03))
## [v0.3.1](https://github.com/napari/superqt/tree/v0.3.1) (2022-03-02)
[Full Changelog](https://github.com/napari/superqt/compare/v0.3.0...v0.3.1)
**Implemented enhancements:**
- Add `signals_blocked` util [\#69](https://github.com/napari/superqt/pull/69) ([tlambert03](https://github.com/tlambert03))
**Fixed bugs:**
- put SignalInstance in TYPE\_CHECKING clause, check min requirements [\#70](https://github.com/napari/superqt/pull/70) ([tlambert03](https://github.com/tlambert03))
**Merged pull requests:**
- Add changelog for v0.3.1 [\#71](https://github.com/napari/superqt/pull/71) ([tlambert03](https://github.com/tlambert03))
## [v0.3.0](https://github.com/napari/superqt/tree/v0.3.0) (2022-02-16)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.5-1...v0.3.0)
**Implemented enhancements:**
- Qthrottler and debouncer [\#62](https://github.com/napari/superqt/pull/62) ([tlambert03](https://github.com/tlambert03))
- add edgeLabelMode option to QLabeledSlider [\#59](https://github.com/napari/superqt/pull/59) ([tlambert03](https://github.com/tlambert03))
**Fixed bugs:**
- Fix nested threadworker not starting [\#63](https://github.com/napari/superqt/pull/63) ([tlambert03](https://github.com/tlambert03))
- Add missing signals on proxy sliders [\#54](https://github.com/napari/superqt/pull/54) ([tlambert03](https://github.com/tlambert03))
- Ugly but functional workaround for pyside6.2.1 breakages [\#51](https://github.com/napari/superqt/pull/51) ([tlambert03](https://github.com/tlambert03))
**Tests & CI:**
- add napari test to CI [\#67](https://github.com/napari/superqt/pull/67) ([tlambert03](https://github.com/tlambert03))
- add gh-release action [\#65](https://github.com/napari/superqt/pull/65) ([tlambert03](https://github.com/tlambert03))
- fix xvfb tests [\#61](https://github.com/napari/superqt/pull/61) ([tlambert03](https://github.com/tlambert03))
**Refactors:**
- Use qtpy, deprecate superqt.qtcompat, drop support for Qt \<5.12 [\#39](https://github.com/napari/superqt/pull/39) ([tlambert03](https://github.com/tlambert03))
**Merged pull requests:**
- Add changelog for v0.3.0 [\#68](https://github.com/napari/superqt/pull/68) ([tlambert03](https://github.com/tlambert03))
## [v0.2.5-1](https://github.com/napari/superqt/tree/v0.2.5-1) (2021-11-23)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.5...v0.2.5-1)
**Merged pull requests:**
- typing-extensions version pinning [\#46](https://github.com/napari/superqt/pull/46) ([AhmetCanSolak](https://github.com/AhmetCanSolak))
## [v0.2.5](https://github.com/napari/superqt/tree/v0.2.5) (2021-11-22)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.4...v0.2.5)
@@ -17,10 +166,17 @@
- Use functools.wraps insterad of \_\_wraped\_\_ and manual proxing \_\_name\_\_ [\#29](https://github.com/napari/superqt/pull/29) ([Czaki](https://github.com/Czaki))
- Propagate function name in `ensure_main_thread` and `ensure_object_thread` [\#28](https://github.com/napari/superqt/pull/28) ([Czaki](https://github.com/Czaki))
**Merged pull requests:**
**Tests & CI:**
- reskip test\_object\_thread\_return on ci [\#43](https://github.com/napari/superqt/pull/43) ([tlambert03](https://github.com/tlambert03))
**Refactors:**
- refactoring qtcompat [\#34](https://github.com/napari/superqt/pull/34) ([tlambert03](https://github.com/tlambert03))
**Merged pull requests:**
- Fix-manifest, move font tests [\#44](https://github.com/napari/superqt/pull/44) ([tlambert03](https://github.com/tlambert03))
- update deploy [\#33](https://github.com/napari/superqt/pull/33) ([tlambert03](https://github.com/tlambert03))
- move to src layout [\#32](https://github.com/napari/superqt/pull/32) ([tlambert03](https://github.com/tlambert03))
@@ -61,16 +217,16 @@
## [v0.2.1](https://github.com/napari/superqt/tree/v0.2.1) (2021-07-10)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.0rc1...v0.2.1)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.0rc0...v0.2.1)
**Fixed bugs:**
- Fix QLabeledRangeSlider API \(fix slider proxy\) [\#10](https://github.com/napari/superqt/pull/10) ([tlambert03](https://github.com/tlambert03))
- Fix range slider with negative min range [\#9](https://github.com/napari/superqt/pull/9) ([tlambert03](https://github.com/tlambert03))
## [v0.2.0rc1](https://github.com/napari/superqt/tree/v0.2.0rc1) (2021-06-26)
## [v0.2.0rc0](https://github.com/napari/superqt/tree/v0.2.0rc0) (2021-06-26)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.0...v0.2.0rc1)
[Full Changelog](https://github.com/napari/superqt/compare/v0.2.0...v0.2.0rc0)

View File

@@ -31,8 +31,6 @@ All widgets must be well-tested, and should work on:
- 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) `superqt.qtcompat`.
## Style Guide

View File

@@ -1,12 +1,11 @@
# ![tiny](https://user-images.githubusercontent.com/1609449/120636353-8c3f3800-c43b-11eb-8732-a14dec578897.png) superqt!
[![License](https://img.shields.io/pypi/l/superqt.svg?color=green)](https://github.com/napari/superqt/raw/master/LICENSE)
[![PyPI](https://img.shields.io/pypi/v/superqt.svg?color=green)](https://pypi.org/project/superqt)
[![Python
Version](https://img.shields.io/pypi/pyversions/superqt.svg?color=green)](https://python.org)
[![Test](https://github.com/napari/superqt/actions/workflows/test_and_deploy.yml/badge.svg)](https://github.com/napari/superqt/actions/workflows/test_and_deploy.yml)
[![codecov](https://codecov.io/gh/napari/superqt/branch/master/graph/badge.svg)](https://codecov.io/gh/napari/superqt)
[![codecov](https://codecov.io/gh/napari/superqt/branch/main/graph/badge.svg?token=dcsjgl1sOi)](https://codecov.io/gh/napari/superqt)
### "missing" widgets and components for PyQt/PySide
@@ -20,25 +19,32 @@ Components are tested on:
- PyQt5 (5.11 and above) & PyQt6
- PySide2 (5.11 and above) & PySide6
## Documentation
Documentation is available at https://napari.org/superqt
## Widgets
Widgets include:
superqt provides a variety of widgets that are not included in the native QtWidgets module, including multihandle (range) sliders, comboboxes, and more.
- [Float Slider](docs/sliders.md#float-slider)
See the [widgets documentation](https://napari.org/superqt/widgets) for a full list of widgets.
- [Range Slider](docs/sliders.md#range-slider) (multi-handle slider)
- [Range Slider](https://napari.org/superqt/widgets/qrangeslider/) (multi-handle slider)
<img src="https://raw.githubusercontent.com/napari/superqt/main/docs/images/demo_darwin10.png" alt="range sliders" width=680>
- [Labeled Sliders](docs/sliders.md#labeled-sliders) (sliders with linked
spinboxes)
<img src="https://raw.githubusercontent.com/napari/superqt/main/docs/images/labeled_qslider.png" alt="range sliders" width=680>
<img src="https://raw.githubusercontent.com/napari/superqt/main/docs/images/labeled_range.png" alt="range sliders" width=680>
- Unbound Integer SpinBox (backed by python `int`)
## Utilities
superqt includes a number of utitlities for working with Qt, including:
- tools and decorators for working with threads in qt.
- `superqt.fonticon` for generating icons from font files (such as [Material Design Icons](https://materialdesignicons.com/) and [Font Awesome](https://fontawesome.com/))
See the [utilities documentation](https://napari.org/superqt/utilities/) for a full list of utilities.
## Contributing

View File

@@ -1,6 +1,5 @@
ignore:
- superqt/_version.py
- superqt/qtcompat/*
- '*_tests*'
coverage:
status:

144
docs/_macros.py Normal file
View File

@@ -0,0 +1,144 @@
import sys
from enum import EnumMeta
from importlib import import_module
from pathlib import Path
from textwrap import dedent
from typing import TYPE_CHECKING
from jinja2 import pass_context
from qtpy.QtCore import QObject, Signal
if TYPE_CHECKING:
from mkdocs_macros.plugin import MacrosPlugin
EXAMPLES = Path(__file__).parent.parent / "examples"
IMAGES = Path(__file__).parent / "_auto_images"
IMAGES.mkdir(exist_ok=True, parents=True)
def define_env(env: "MacrosPlugin"):
@env.macro
@pass_context
def show_widget(context, width: int = 500) -> list[Path]:
# extract all fenced code blocks starting with "python"
page = context["page"]
dest = IMAGES / f"{page.title}.png"
if "build" in sys.argv:
dest.unlink(missing_ok=True)
codeblocks = [
b[6:].strip()
for b in page.markdown.split("```")
if b.startswith("python")
]
src = codeblocks[0].strip()
src = src.replace(
"QApplication([])", "QApplication.instance() or QApplication([])"
)
src = src.replace("app.exec_()", "")
exec(src)
_grab(dest, width)
return f"![{page.title}](../{dest.parent.name}/{dest.name}){{ loading=lazy; width={width} }}\n\n"
@env.macro
def show_members(cls: str):
# import class
module, name = cls.rsplit(".", 1)
_cls = getattr(import_module(module), name)
first_q = next(
(
b.__name__
for b in _cls.__mro__
if issubclass(b, QObject) and ".Qt" in b.__module__
),
None,
)
inherited_members = set()
for base in _cls.__mro__:
if issubclass(base, QObject) and ".Qt" in base.__module__:
inherited_members.update(
{k for k in dir(base) if not k.startswith("_")}
)
new_signals = {
k
for k, v in vars(_cls).items()
if not k.startswith("_") and isinstance(v, Signal)
}
self_members = {
k
for k in dir(_cls)
if not k.startswith("_") and k not in inherited_members | new_signals
}
enums = []
for m in list(self_members):
if isinstance(getattr(_cls, m), EnumMeta):
self_members.remove(m)
enums.append(m)
out = ""
if first_q:
url = f"https://doc.qt.io/qt-6/{first_q.lower()}.html"
out += f"## Qt Class\n\n<a href='{url}'>`{first_q}`</a>\n\n"
out += ""
if new_signals:
out += "## Signals\n\n"
for sig in new_signals:
out += f"### `{sig}`\n\n"
if enums:
out += "## Enums\n\n"
for e in enums:
out += f"### `{_cls.__name__}.{e}`\n\n"
for m in getattr(_cls, e):
out += f"- `{m.name}`\n\n"
if self_members:
out += dedent(
f"""
## Methods
::: {cls}
options:
heading_level: 3
show_source: False
show_inherited_members: false
show_signature_annotations: True
members: {sorted(self_members)}
docstring_style: numpy
show_bases: False
show_root_toc_entry: False
show_root_heading: False
"""
)
return out
def _grab(dest: str | Path, width) -> list[Path]:
"""Grab the top widgets of the application."""
from qtpy.QtCore import QTimer
from qtpy.QtWidgets import QApplication
w = QApplication.topLevelWidgets()[-1]
w.setFixedWidth(width)
w.activateWindow()
w.setMinimumHeight(40)
w.grab().save(str(dest))
# hack to make sure the object is truly closed and deleted
while True:
QTimer.singleShot(10, w.deleteLater)
QApplication.processEvents()
try:
w.parent()
except RuntimeError:
return

View File

@@ -1,63 +0,0 @@
# ComboBox
## Enum Combo Box
`QEnumComboBox` is a variant of [`QComboBox`](https://doc.qt.io/qt-5/qcombobox.html)
that populates the items in the combobox based on a python `Enum` class. In addition to all
the methods provided by `QComboBox`, this subclass adds the methods
`enumClass`/`setEnumClass` to get/set the current `Enum` class represented by the combobox,
and `currentEnum`/`setCurrentEnum` to get/set the current `Enum` member in the combobox.
There is also a new signal `currentEnumChanged(enum)` analogous to `currentIndexChanged` and `currentTextChanged`.
Method like `insertItem` and `addItem` are blocked and try of its usage will end with `RuntimeError`
```python
from enum import Enum
from superqt import QEnumComboBox
class SampleEnum(Enum):
first = 1
second = 2
third = 3
# as usual:
# you must create a QApplication before create a widget.
combo = QEnumComboBox()
combo.setEnumClass(SampleEnum)
```
other option is to use optional `enum_class` argument of constructor and change
```python
combo = QEnumComboBox()
combo.setEnumClass(SampleEnum)
```
to
```python
combo = QEnumComboBox(enum_class=SampleEnum)
```
### Allow `None`
`QEnumComboBox` allow using Optional type annotation:
```python
from enum import Enum
from superqt import QEnumComboBox
class SampleEnum(Enum):
first = 1
second = 2
third = 3
# as usual:
# you must create a QApplication before create a widget.
combo = QEnumComboBox()
combo.setEnumClass(SampleEnum, allow_none=True)
```
In this case there is added option `----` and `currentEnum` will return `None` for it.

26
docs/faq.md Normal file
View File

@@ -0,0 +1,26 @@
# FAQ
## Sliders not dragging properly on MacOS 12+
??? details
On MacOS Monterey, with Qt5, there is a bug that causes all sliders
(including native Qt sliders) to not respond properly to drag events. See:
- [https://bugreports.qt.io/browse/QTBUG-98093](https://bugreports.qt.io/browse/QTBUG-98093)
- [https://github.com/napari/superqt/issues/74](https://github.com/napari/superqt/issues/74)
Superqt includes a workaround for this issue, but it is not perfect, and it requires using a custom stylesheet (which may interfere with your own styles). Note that you
may not see this issue if you're already using custom stylesheets.
To opt in to the workaround, do any of the following:
- set the environment variable `USE_MAC_SLIDER_PATCH=1` before importing superqt
(note: this is safe to use even if you're targeting more than just MacOS 12, it will only be applied when needed)
- call the `applyMacStylePatch()` method on any of the superqt slider subclasses (note, this will override your slider styles)
- apply the stylesheet manually:
```python
from superqt.sliders import MONTEREY_SLIDER_STYLES_FIX
slider.setStyleSheet(MONTEREY_SLIDER_STYLES_FIX)
```

View File

29
docs/index.md Normal file
View File

@@ -0,0 +1,29 @@
# superqt
## ![tiny](https://user-images.githubusercontent.com/1609449/120636353-8c3f3800-c43b-11eb-8732-a14dec578897.png) "missing" widgets and components for PyQt/PySide
This repository aims to provide high-quality community-contributed Qt widgets
and components for [PyQt](https://riverbankcomputing.com/software/pyqt/) &
[PySide](https://www.qt.io/qt-for-python) that are not provided in the native
QtWidgets module.
Components are tested on:
- macOS, Windows, & Linux
- Python 3.7 and above
- PyQt5 (5.11 and above) & PyQt6
- PySide2 (5.11 and above) & PySide6
## Installation
```bash
pip install superqt
```
```bash
conda install -c conda-forge superqt
```
## Usage
See the [Widgets](./widgets/) and [Utilities](./utilities/) pages for features offered by superqt.

View File

@@ -1,237 +0,0 @@
# Sliders
![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])`)
------
## Range Slider
```python
from superqt 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` <br>`hideBar` / `showBar` | `bool` | `True` | <small>Whether the bar between handles is visible.</small> |
| `barMovesAllHandles` | `setBarMovesAllHandles` | `bool` | `True` | <small>Whether clicking on the bar moves all handles or just the nearest</small> |
| `barIsRigid` | `setBarIsRigid` | `bool` | `True` | <small>Whether bar length is constant or "elastic" when dragging the bar beyond min/max.</small> |
------
### 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)
<details>
<summary><em>See style sheet used for this example</em></summary>
```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;
}
```
</details>
#### 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 superqt 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 superqt 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/superqt/issues
## Float Slider
just like QSlider, but supports float values
```python
from superqt import QDoubleSlider
```

View File

@@ -0,0 +1,52 @@
# CodeSyntaxHighlight
A code highlighter subclass of `QSyntaxHighlighter`
that can be used to highlight code in a QTextEdit.
Code lexer and available styles are from [`pygments`](https://pygments.org/) python library
List of available languages are available [here](https://pygments.org/languages/).
List of available styles are available [here](https://pygments.org/styles/).
## Example
```python
from qtpy.QtGui import QColor, QPalette
from qtpy.QtWidgets import QApplication, QTextEdit
from superqt.utils import CodeSyntaxHighlight
app = QApplication([])
text_area = QTextEdit()
highlight = CodeSyntaxHighlight(text_area.document(), "python", "monokai")
palette = text_area.palette()
palette.setColor(QPalette.Base, QColor(highlight.background_color))
text_area.setPalette(palette)
text_area.setText(
"""from argparse import ArgumentParser
def main():
parser = ArgumentParser()
parser.add_argument("name", help="Your name")
args = parser.parse_args()
print(f"Hello {args.name}")
if __name__ == "__main__":
main()
"""
)
text_area.show()
text_area.resize(400, 200)
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.utils.CodeSyntaxHighlight') }}

101
docs/utilities/fonticon.md Normal file
View File

@@ -0,0 +1,101 @@
# Font icons
The `superqt.fonticon` module provides a set of utilities for working with font
icons such as [Font Awesome](https://fontawesome.com/) or [Material Design
Icons](https://materialdesignicons.com/).
## Basic Example
```python
from fonticon_fa5 import FA5S
from qtpy.QtCore import QSize
from qtpy.QtWidgets import QApplication, QPushButton
from superqt.fonticon import icon, pulse
app = QApplication([])
btn2 = QPushButton()
btn2.setIcon(icon(FA5S.smile, color="blue"))
btn2.setIconSize(QSize(225, 225))
btn2.show()
app.exec()
```
{{ show_widget(225) }}
## Font Icon plugins
Ready-made fonticon packs are available as plugins:
### [Font Awesome 5](https://fontawesome.com/v5/search)
```bash
pip install fonticon-fontawesome5
```
### [Font Awesome 6](https://fontawesome.com/v6/search)
```bash
pip install fonticon-fontawesome6
```
### [Material Design Icons](https://materialdesignicons.com/)
```bash
pip install fonticon-materialdesignicons6
```
### See also
- <https://github.com/tlambert03/fonticon-bootstrapicons>
- <https://github.com/tlambert03/fonticon-linearicons>
- <https://github.com/tlambert03/fonticon-feather>
`superqt.fonticon` is a pluggable system, and font icon packs may use the `"superqt.fonticon"`
entry point to register themselves with superqt. See [`fonticon-cookiecutter`](https://github.com/tlambert03/fonticon-cookiecutter) for a template, or look through the following repos for examples:
- <https://github.com/tlambert03/fonticon-fontawesome6>
- <https://github.com/tlambert03/fonticon-fontawesome5>
- <https://github.com/tlambert03/fonticon-materialdesignicons6>
## API
::: superqt.fonticon.icon
options:
heading_level: 3
::: superqt.fonticon.setTextIcon
options:
heading_level: 3
::: superqt.fonticon.font
options:
heading_level: 3
::: superqt.fonticon.IconOpts
options:
heading_level: 3
::: superqt.fonticon.addFont
options:
heading_level: 3
## Animations
the `animation` parameter to `icon()` accepts a subclass of
`Animation` that will be
::: superqt.fonticon.Animation
options:
heading_level: 3
::: superqt.fonticon.pulse
options:
heading_level: 3
::: superqt.fonticon.spin
options:
heading_level: 3

31
docs/utilities/index.md Normal file
View File

@@ -0,0 +1,31 @@
# Utilities
## Font Icons
| Object | Description |
| ----------- | --------------------- |
| [`addFont`](./fonticon.md#superqt.fonticon.addFont) | Add an `OTF/TTF` file at to the font registry. |
| [`font`](./fonticon.md#superqt.fonticon.font) | Create `QFont` for a given font-icon font family key |
| [`icon`](./fonticon.md#superqt.fonticon.icon) | Create a `QIcon` for font-con glyph key |
| [`setTextIcon`](./fonticon.md#superqt.fonticon.setTextIcon) | Set text on a `QWidget` to a specific font & glyph. |
| [`IconFont`](./fonticon.md#superqt.fonticon.IconFont) | Helper class that provides a standard way to create an `IconFont`. |
| [`IconOpts`](./fonticon.md#superqt.fonticon.IconOpts) | Options for rendering an icon |
| [`Animation`](./fonticon.md#superqt.fonticon.Animation) | Base class for adding animations to a font-icon. |
## Threading tools
| Object | Description |
| ----------- | --------------------- |
| [`ensure_main_thread`](./thread_decorators.md#ensure_main_thread) | Decorator that ensures a function is called in the main `QApplication` thread. |
| [`ensure_object_thread`](./thread_decorators.md#ensure_object_thread) | Decorator that ensures a `QObject` method is called in the object's thread. |
| [`FunctionWorker`](./threading.md#superqt.utils.FunctionWorker) | `QRunnable` with signals that wraps a simple long-running function. |
| [`GeneratorWorker`](./threading.md#superqt.utils.GeneratorWorker) | `QRunnable` with signals that wraps a long-running generator. |
| [`create_worker`](./threading.md#superqt.utils.create_worker) | Create a worker to run a target function in another thread. |
| [`thread_worker`](./threading.md#superqt.utils.thread_worker) | Decorator for `create_worker`, turn a function into a worker. |
## Miscellaneous
| Object | Description |
| ----------- | --------------------- |
| [`QMessageHandler`](./qmessagehandler.md) | A context manager to intercept messages from Qt. |
| [`CodeSyntaxHighlight`](./code_syntax_highlight.md) | A `QSyntaxHighlighter` for code syntax highlighting. |

View File

@@ -0,0 +1,8 @@
# QMessageHandler
::: superqt.utils.QMessageHandler
options:
heading_level: 3
show_signature_annotations: True
docstring_style: numpy
show_bases: False

View File

@@ -1,22 +1,28 @@
# Decorators
## Move to thread decorators
# Threading decorators
`superqt` provides two decorators that help to ensure that given function is
running in the desired thread:
* `ensure_main_thread` - ensures that the decorated function/method runs in the main thread
* `ensure_object_thread` - ensures that a decorated bound method of a `QObject` runs in the
thread in which the instance lives ([qt
documentation](https://doc.qt.io/qt-5/threads-qobject.html#accessing-qobject-subclasses-from-other-threads)).
## `ensure_main_thread`
`ensure_main_thread` ensures that the decorated function/method runs in the main thread
## `ensure_object_thread`
`ensure_object_thread` ensures that a decorated bound method of a `QObject` runs
in the thread in which the instance lives ([see qt documentation for
details](https://doc.qt.io/qt-5/threads-qobject.html#accessing-qobject-subclasses-from-other-threads)).
## Usage
By default, functions are executed asynchronously (they return immediately with
an instance of
[`concurrent.futures.Future`](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future)).
To block and wait for the result, see [Synchronous mode](#synchronous-mode)
```python
from superqt.qtcompat.QtCore import QObject
from qtpy.QtCore import QObject
from superqt import ensure_main_thread, ensure_object_thread
@ensure_main_thread
@@ -57,12 +63,14 @@ As can be seen in this example these decorators can also be used for setters.
These decorators should not be used as replacement of Qt Signals but rather to
interact with Qt objects from non Qt code.
### Synchronous mode
## Synchronous mode
If you'd like for the program to block and wait for the result of your function
call, use the `await_return=True` parameter, and optionally specify a timeout.
> *Note: Using synchronous mode may significantly impact performance.*
!!! important
Using synchronous mode may significantly impact performance.
```python
from superqt import ensure_main_thread

View File

@@ -0,0 +1,36 @@
# Thread workers
The objects in this module provide utilities for running tasks in a separate
thread. In general (with the exception of `new_worker_qthread`), everything
here wraps Qt's [QRunnable API](https://doc.qt.io/qt-6/qrunnable.html).
The highest level object is the
[`@thread_worker`][superqt.utils.thread_worker] decorator. It was originally
written for `napari`, and was later extracted into `superqt`. You may also be
interested in reading the [napari
documentation](https://napari.org/stable/guides/threading.html#threading-in-napari-with-thread-worker) on this feature,
which provides a more in-depth/introductory usage guide.
For additional control, you can create your own
[`FunctionWorker`][superqt.utils.FunctionWorker] or
[`GeneratorWorker`][superqt.utils.GeneratorWorker] objects.
::: superqt.utils.WorkerBase
::: superqt.utils.FunctionWorker
::: superqt.utils.GeneratorWorker
## Convenience functions
::: superqt.utils.thread_worker
options:
heading_level: 3
::: superqt.utils.create_worker
options:
heading_level: 3
::: superqt.utils.new_worker_qthread
options:
heading_level: 3

View File

@@ -0,0 +1,46 @@
# Throttling & Debouncing
These utilities allow you to throttle or debounce a function. This is useful
when you have a function that is called multiple times in a short period of
time, and you want to make sure it is only "actually" called once (or at least
no more than a certain frequency).
For background on throttling and debouncing, see:
- <https://blog.openreplay.com/forever-functional-debouncing-and-throttling-for-performance>
- <https://css-tricks.com/debouncing-throttling-explained-examples/>
::: superqt.utils.qdebounced
options:
show_source: false
docstring_style: numpy
show_root_toc_entry: True
show_root_heading: True
::: superqt.utils.qthrottled
options:
show_source: false
docstring_style: numpy
show_root_toc_entry: True
show_root_heading: True
::: superqt.utils.QSignalDebouncer
options:
show_source: false
docstring_style: numpy
show_root_toc_entry: True
show_root_heading: True
::: superqt.utils.QSignalThrottler
options:
show_source: false
docstring_style: numpy
show_root_toc_entry: True
show_root_heading: True
::: superqt.utils._throttler.GenericSignalThrottler
options:
show_source: false
docstring_style: numpy
show_root_toc_entry: True
show_root_heading: True

32
docs/widgets/index.md Normal file
View File

@@ -0,0 +1,32 @@
# Widgets
The following are QWidget subclasses:
## Sliders and Numerical Inputs
| Widget | Description |
| ----------- | --------------------- |
| [`QDoubleRangeSlider`](./qdoublerangeslider.md) | Multi-handle slider for float values |
| [`QDoubleSlider`](./qdoubleslider.md) | Slider for float values |
| [`QLabeledDoubleRangeSlider`](./qlabeleddoublerangeslider.md) | `QDoubleRangeSlider` variant with editable labels for each handle |
| [`QLabeledDoubleSlider`](./qlabeleddoubleslider.md) | `QSlider` for float values with editable `QSpinBox` with the current value |
| [`QLabeledRangeSlider`](./qlabeledrangeslider.md) | `QRangeSlider` variant, with editable labels for each handle |
| [`QLabeledSlider`](./qlabeledslider.md) | `QSlider` with editable `QSpinBox` that shows the current value |
| [`QLargeIntSpinBox`](./qlargeintspinbox.md) | `QSpinbox` that accepts arbitrarily large integers |
| [`QRangeSlider`](./qrangeslider.md) | Multi-handle slider |
| [`QQuantity`](./qquantity.md) | Pint-backed quantity widget (magnitude combined with unit dropdown) |
## Labels and categorical inputs
| Widget | Description |
| ----------- | --------------------- |
| [`QElidingLabel`](./qelidinglabel.md) | A `QLabel` variant that will elide text (add `…`) to fit width. |
| [`QEnumComboBox`](./qenumcombobox.md) | `QComboBox` that populates the combobox from a python `Enum` |
| [`QSearchableComboBox`](./qsearchablecombobox.md) | `QComboBox` variant that filters available options based on text input |
| [`QSearchableListWidget`](./qsearchablelistwidget.md) | `QListWidget` variant with search field that filters available options |
## Frames and containers
| Widget | Description |
| ----------- | --------------------- |
| [`QCollapsible`](./qcollapsible.md) | A collapsible widget to hide and unhide child widgets. |

View File

@@ -0,0 +1,24 @@
# QCollapsible
Collapsible `QFrame` that can be expanded or collapsed by clicking on the header.
```python
from qtpy.QtWidgets import QApplication, QLabel, QPushButton
from superqt import QCollapsible
app = QApplication([])
collapsible = QCollapsible("Advanced analysis")
collapsible.addWidget(QLabel("This is the inside of the collapsible frame"))
for i in range(10):
collapsible.addWidget(QPushButton(f"Content button {i + 1}"))
collapsible.expand(animate=False)
collapsible.show()
app.exec_()
```
{{ show_widget(350) }}
{{ show_members('superqt.QCollapsible') }}

View File

@@ -0,0 +1,23 @@
# QDoubleRangeSlider
Float variant of [`QRangeSlider`](qrangeslider.md). (see that page for more details).
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QDoubleRangeSlider
app = QApplication([])
slider = QDoubleRangeSlider(Qt.Orientation.Horizontal)
slider.setRange(0, 1)
slider.setValue((0.2, 0.8))
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QDoubleRangeSlider') }}

View File

@@ -0,0 +1,23 @@
# QDoubleSlider
`QSlider` variant that accepts floating point values.
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QDoubleSlider
app = QApplication([])
slider = QDoubleSlider(Qt.Orientation.Horizontal)
slider.setRange(0, 1)
slider.setValue(0.5)
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QDoubleSlider') }}

View File

@@ -0,0 +1,26 @@
# QElidingLabel
`QLabel` variant that will elide text (i.e. add an ellipsis)
if it is too long to fit in the available space.
```python
from qtpy.QtWidgets import QApplication
from superqt import QElidingLabel
app = QApplication([])
widget = QElidingLabel(
"a skj skjfskfj sdlf sdfl sdlfk jsdf sdlkf jdsf dslfksdl sdlfk sdf sdl "
"fjsdlf kjsdlfk laskdfsal as lsdfjdsl kfjdslf asfd dslkjfldskf sdlkfj"
)
widget.setWordWrap(True)
widget.resize(300, 20)
widget.show()
app.exec_()
```
{{ show_widget(300) }}
{{ show_members('superqt.QElidingLabel') }}

View File

@@ -0,0 +1,72 @@
# QEnumComboBox
`QEnumComboBox` is a variant of
[`QComboBox`](https://doc.qt.io/qt-5/qcombobox.html) that populates the items in
the combobox based on a python `Enum` class. In addition to all the methods
provided by `QComboBox`, this subclass adds the methods
`enumClass`/`setEnumClass` to get/set the current `Enum` class represented by
the combobox, and `currentEnum`/`setCurrentEnum` to get/set the current `Enum`
member in the combobox. There is also a new signal `currentEnumChanged(enum)`
analogous to `currentIndexChanged` and `currentTextChanged`.
Method like `insertItem` and `addItem` are blocked and try of its usage will end
with `RuntimeError`
```python
from enum import Enum
from qtpy.QtWidgets import QApplication
from superqt import QEnumComboBox
class SampleEnum(Enum):
first = 1
second = 2
third = 3
app = QApplication([])
combo = QEnumComboBox()
combo.setEnumClass(SampleEnum)
combo.show()
app.exec_()
```
{{ show_widget() }}
Another option is to use optional `enum_class` argument of constructor and change
```python
# option A:
combo = QEnumComboBox()
combo.setEnumClass(SampleEnum)
# option B:
combo = QEnumComboBox(enum_class=SampleEnum)
```
## Allow `None`
`QEnumComboBox` also allows using `Optional` type annotation:
```python
from enum import Enum
from superqt import QEnumComboBox
class SampleEnum(Enum):
first = 1
second = 2
third = 3
# as usual:
# you must create a QApplication before create a widget.
combo = QEnumComboBox()
combo.setEnumClass(SampleEnum, allow_none=True)
```
In this case there is added option `----` and the `currentEnum()` method will
return `None` when it is selected.
{{ show_members('superqt.QEnumComboBox') }}

View File

@@ -0,0 +1,23 @@
# QLabeledDoubleRangeSlider
Labeled Float variant of [`QRangeSlider`](qrangeslider.md). (see that page for more details).
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QLabeledDoubleRangeSlider
app = QApplication([])
slider = QLabeledDoubleRangeSlider(Qt.Orientation.Horizontal)
slider.setRange(0, 1)
slider.setValue((0.2, 0.8))
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QLabeledDoubleRangeSlider') }}

View File

@@ -0,0 +1,24 @@
# QLabeledDoubleSlider
[`QDoubleSlider`](./qdoubleslider.md) variant that shows an editable (SpinBox) label next to the slider.
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QLabeledDoubleSlider
app = QApplication([])
slider = QLabeledDoubleSlider(Qt.Orientation.Horizontal)
slider.setRange(0, 2.5)
slider.setValue(1.3)
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QLabeledDoubleSlider') }}

View File

@@ -0,0 +1,29 @@
# QLabeledRangeSlider
Labeled variant of [`QRangeSlider`](qrangeslider.md). (see that page for more details).
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QLabeledRangeSlider
app = QApplication([])
slider = QLabeledRangeSlider(Qt.Orientation.Horizontal)
slider.setValue((20, 80))
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QLabeledRangeSlider') }}
----
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

View File

@@ -0,0 +1,22 @@
# QLabeledSlider
`QSlider` variant that shows an editable (SpinBox) label next to the slider.
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QLabeledSlider
app = QApplication([])
slider = QLabeledSlider(Qt.Orientation.Horizontal)
slider.setValue(42)
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QLabeledSlider') }}

View File

@@ -0,0 +1,23 @@
# QLargeIntSpinBox
`QSpinBox` variant that allows to enter large integers, without overflow.
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QLargeIntSpinBox
app = QApplication([])
slider = QLargeIntSpinBox()
slider.setRange(0, 4.53e8)
slider.setValue(4.53e8)
slider.show()
app.exec_()
```
{{ show_widget(150) }}
{{ show_members('superqt.QLargeIntSpinBox') }}

33
docs/widgets/qquantity.md Normal file
View File

@@ -0,0 +1,33 @@
# QQuantity
A widget that allows the user to edit a quantity (a magnitude associated with a unit).
!!! note
This widget requires [`pint`](https://pint.readthedocs.io):
```
pip install pint
```
or
```
pip install superqt[quantity]
```
```python
from qtpy.QtWidgets import QApplication
from superqt import QQuantity
app = QApplication([])
w = QQuantity("1m")
w.show()
app.exec()
```
{{ show_widget(150) }}
{{ show_members('superqt.QQuantity') }}

View File

@@ -0,0 +1,229 @@
# QRangeSlider
A multi-handle slider widget than can be used to
select a range of values.
```python
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QRangeSlider
app = QApplication([])
slider = QRangeSlider(Qt.Orientation.Horizontal)
slider.setValue((20, 80))
slider.show()
app.exec_()
```
{{ show_widget() }}
- `QRangeSlider` inherits from [`QSlider`](https://doc.qt.io/qt-5/qslider.html)
and attempts to match the Qt API as closely as possible
- It uses platform-specific styles (for handle, groove, & ticks) but also supports
QSS style sheets.
- Supports mouse wheel events
- Supports more than 2 handles (e.g. `slider.setValue([0, 10, 60, 80])`)
As `QRangeSlider` inherits from
[`QtWidgets.QSlider`](https://doc.qt.io/qt-5/qslider.html), 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.)
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` <br>`hideBar` / `showBar` | `bool` | `True` | <small>Whether the bar between handles is visible.</small> |
| `barMovesAllHandles` | `setBarMovesAllHandles` | `bool` | `True` | <small>Whether clicking on the bar moves all handles or just the nearest</small> |
| `barIsRigid` | `setBarIsRigid` | `bool` | `True` | <small>Whether bar length is constant or "elastic" when dragging the bar beyond min/max.</small> |
### Screenshots
??? title "code that generates the images below"
```python
import os
from qtpy import QtCore
from qtpy import QtWidgets as QtW
# patch for Qt 5.15 on macos >= 12
os.environ["USE_MAC_SLIDER_PATCH"] = "1"
from superqt import QRangeSlider # noqa
QSS = """
QSlider {
min-height: 20px;
}
QSlider::groove:horizontal {
border: 0px;
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #888, stop:1 #ddd);
height: 20px;
border-radius: 10px;
}
QSlider::handle {
background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.35,
fy:0.3, stop:0 #eef, stop:1 #002);
height: 20px;
width: 20px;
border-radius: 10px;
}
QSlider::sub-page:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #227, stop:1 #77a);
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
QRangeSlider {
qproperty-barColor: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #227, stop:1 #77a);
}
"""
Horizontal = QtCore.Qt.Orientation.Horizontal
class DemoWidget(QtW.QWidget):
def __init__(self) -> None:
super().__init__()
reg_hslider = QtW.QSlider(Horizontal)
reg_hslider.setValue(50)
range_hslider = QRangeSlider(Horizontal)
range_hslider.setValue((20, 80))
multi_range_hslider = QRangeSlider(Horizontal)
multi_range_hslider.setValue((11, 33, 66, 88))
multi_range_hslider.setTickPosition(QtW.QSlider.TickPosition.TicksAbove)
styled_reg_hslider = QtW.QSlider(Horizontal)
styled_reg_hslider.setValue(50)
styled_reg_hslider.setStyleSheet(QSS)
styled_range_hslider = QRangeSlider(Horizontal)
styled_range_hslider.setValue((20, 80))
styled_range_hslider.setStyleSheet(QSS)
reg_vslider = QtW.QSlider(QtCore.Qt.Orientation.Vertical)
reg_vslider.setValue(50)
range_vslider = QRangeSlider(QtCore.Qt.Orientation.Vertical)
range_vslider.setValue((22, 77))
tick_vslider = QtW.QSlider(QtCore.Qt.Orientation.Vertical)
tick_vslider.setValue(55)
tick_vslider.setTickPosition(QtW.QSlider.TicksRight)
range_tick_vslider = QRangeSlider(QtCore.Qt.Orientation.Vertical)
range_tick_vslider.setValue((22, 77))
range_tick_vslider.setTickPosition(QtW.QSlider.TicksLeft)
szp = QtW.QSizePolicy.Maximum
left = QtW.QWidget()
left.setLayout(QtW.QVBoxLayout())
left.setContentsMargins(2, 2, 2, 2)
label1 = QtW.QLabel("Regular QSlider Unstyled")
label2 = QtW.QLabel("QRangeSliders Unstyled")
label3 = QtW.QLabel("Styled Sliders (using same stylesheet)")
label1.setSizePolicy(szp, szp)
label2.setSizePolicy(szp, szp)
label3.setSizePolicy(szp, szp)
left.layout().addWidget(label1)
left.layout().addWidget(reg_hslider)
left.layout().addWidget(label2)
left.layout().addWidget(range_hslider)
left.layout().addWidget(multi_range_hslider)
left.layout().addWidget(label3)
left.layout().addWidget(styled_reg_hslider)
left.layout().addWidget(styled_range_hslider)
right = QtW.QWidget()
right.setLayout(QtW.QHBoxLayout())
right.setContentsMargins(15, 5, 5, 0)
right.layout().setSpacing(30)
right.layout().addWidget(reg_vslider)
right.layout().addWidget(range_vslider)
right.layout().addWidget(tick_vslider)
right.layout().addWidget(range_tick_vslider)
self.setLayout(QtW.QHBoxLayout())
self.layout().addWidget(left)
self.layout().addWidget(right)
self.setGeometry(600, 300, 580, 300)
self.activateWindow()
self.show()
if __name__ == "__main__":
import sys
from pathlib import Path
dest = Path("screenshots")
dest.mkdir(exist_ok=True)
app = QtW.QApplication([])
demo = DemoWidget()
if "-snap" in sys.argv:
import platform
QtW.QApplication.processEvents()
demo.grab().save(str(dest / f"demo_{platform.system().lower()}.png"))
else:
app.exec_()
```
#### macOS
##### Catalina
![mac10](../images/demo_darwin10.png){ width=580; }
##### Big Sur
![mac11](../images/demo_darwin11.png){ width=580; }
#### Windows
![window](../images/demo_windows.png)
#### Linux
![linux](../images/demo_linux.png)
{{ show_members('superqt.sliders._sliders._GenericRangeSlider') }}
## Type changes
Note the following changes in types compared to the `QSlider` API:
```python
value() -> Tuple[int, ...]
```
```python
setValue(val: Sequence[int]) -> None
```
```python
# Signal
valueChanged(Tuple[int, ...])
```
```python
sliderPosition() -> Tuple[int, ...]
```
```python
setSliderPosition(val: Sequence[int]) -> None
```
```python
sliderMoved(Tuple[int, ...])
```

View File

@@ -0,0 +1,25 @@
# QSearchableComboBox
`QSearchableComboBox` is a variant of
[`QComboBox`](https://doc.qt.io/qt-5/qcombobox.html) that allow to filter list
of options by enter part of text. It could be drop in replacement for
`QComboBox`.
```python
from qtpy.QtWidgets import QApplication
from superqt import QSearchableComboBox
app = QApplication([])
combo = QSearchableComboBox()
combo.addItems(["foo", "bar", "baz", "foobar", "foobaz", "barbaz"])
combo.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QSearchableComboBox') }}

View File

@@ -0,0 +1,28 @@
# QSearchableListWidget
`QSearchableListWidget` is a variant of
[`QListWidget`](https://doc.qt.io/qt-5/qlistwidget.html) that add text entry
above list widget that allow to filter list of available options.
Due to implementation details, this widget it does not inherit directly from
[`QListWidget`](https://doc.qt.io/qt-5/qlistwidget.html) but it does fully
satisfy its api. The only limitation is that it cannot be used as argument of
[`QListWidgetItem`](https://doc.qt.io/qt-5/qlistwidgetitem.html) constructor.
```python
from qtpy.QtWidgets import QApplication
from superqt import QSearchableListWidget
app = QApplication([])
slider = QSearchableListWidget()
slider.addItems(["foo", "bar", "baz", "foobar", "foobaz", "barbaz"])
slider.show()
app.exec_()
```
{{ show_widget() }}
{{ show_members('superqt.QSearchableListWidget') }}

View File

@@ -1,6 +1,7 @@
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QRangeSlider
from superqt.qtcompat.QtCore import Qt
from superqt.qtcompat.QtWidgets import QApplication
app = QApplication([])

View File

@@ -0,0 +1,32 @@
from PyQt5.QtGui import QColor, QPalette
from qtpy.QtWidgets import QApplication, QTextEdit
from superqt.utils import CodeSyntaxHighlight
app = QApplication([])
text_area = QTextEdit()
highlight = CodeSyntaxHighlight(text_area.document(), "python", "monokai")
palette = text_area.palette()
palette.setColor(QPalette.Base, QColor(highlight.background_color))
text_area.setPalette(palette)
text_area.setText(
"""from argparse import ArgumentParser
def main():
parser = ArgumentParser()
parser.add_argument("name", help="Your name")
args = parser.parse_args()
print(f"Hello {args.name}")
if __name__ == "__main__":
main()
"""
)
text_area.show()
app.exec_()

View File

@@ -1,6 +1,12 @@
from superqt import QRangeSlider
from superqt.qtcompat import QtCore
from superqt.qtcompat import QtWidgets as QtW
import os
from qtpy import QtCore
from qtpy import QtWidgets as QtW
# patch for Qt 5.15 on macos >= 12
os.environ["USE_MAC_SLIDER_PATCH"] = "1"
from superqt import QRangeSlider # noqa
QSS = """
QSlider {

View File

@@ -1,12 +1,14 @@
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QDoubleSlider
from superqt.qtcompat.QtCore import Qt
from superqt.qtcompat.QtWidgets import QApplication
app = QApplication([])
slider = QDoubleSlider(Qt.Orientation.Horizontal)
slider.setRange(0, 1)
slider.setValue(0.5)
slider.resize(500, 50)
slider.show()
app.exec_()

View File

@@ -1,5 +1,6 @@
from qtpy.QtWidgets import QApplication
from superqt import QElidingLabel
from superqt.qtcompat.QtWidgets import QApplication
app = QApplication([])

View File

@@ -1,6 +1,7 @@
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
from superqt import QDoubleRangeSlider, QDoubleSlider, QRangeSlider
from superqt.qtcompat.QtCore import Qt
from superqt.qtcompat.QtWidgets import QApplication, QVBoxLayout, QWidget
app = QApplication([])

View File

@@ -6,9 +6,10 @@ except ImportError as e:
"pip install git+https://github.com/tlambert03/fonticon-fontawesome5.git"
)
from qtpy.QtCore import QSize
from qtpy.QtWidgets import QApplication, QPushButton
from superqt.fonticon import icon, pulse
from superqt.qtcompat.QtCore import QSize
from superqt.qtcompat.QtWidgets import QApplication, QPushButton
app = QApplication([])

View File

@@ -6,8 +6,9 @@ except ImportError as e:
"pip install git+https://github.com/tlambert03/fonticon-fontawesome5.git"
)
from qtpy.QtWidgets import QApplication, QPushButton
from superqt.fonticon import setTextIcon
from superqt.qtcompat.QtWidgets import QApplication, QPushButton
app = QApplication([])

View File

@@ -6,9 +6,10 @@ except ImportError as e:
"pip install git+https://github.com/tlambert03/fonticon-fontawesome5.git"
)
from qtpy.QtCore import QSize
from qtpy.QtWidgets import QApplication, QPushButton
from superqt.fonticon import IconOpts, icon, pulse, spin
from superqt.qtcompat.QtCore import QSize
from superqt.qtcompat.QtWidgets import QApplication, QPushButton
app = QApplication([])

View File

@@ -1,6 +1,7 @@
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QDoubleSlider
from superqt.qtcompat.QtCore import Qt
from superqt.qtcompat.QtWidgets import QApplication
app = QApplication([])

View File

@@ -1,6 +1,7 @@
from qtpy import QtCore, QtGui, QtWidgets
from qtpy.QtCore import Qt
from superqt.fonticon._plugins import loaded
from superqt.qtcompat import QtCore, QtGui, QtWidgets
from superqt.qtcompat.QtCore import Qt
P = loaded(load_all=True)
if not P:

View File

@@ -1,11 +1,12 @@
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget
from superqt import (
QLabeledDoubleRangeSlider,
QLabeledDoubleSlider,
QLabeledRangeSlider,
QLabeledSlider,
)
from superqt.qtcompat.QtCore import Qt
from superqt.qtcompat.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget
app = QApplication([])

View File

@@ -1,5 +1,6 @@
from qtpy.QtWidgets import QApplication
from superqt import QRangeSlider
from superqt.qtcompat.QtWidgets import QApplication
app = QApplication([])

View File

@@ -1,6 +1,7 @@
"""Example for QCollapsible"""
from qtpy.QtWidgets import QApplication, QLabel, QPushButton
from superqt import QCollapsible
from superqt.qtcompat.QtWidgets import QApplication, QLabel, QPushButton
app = QApplication([])

9
examples/quantity.py Normal file
View File

@@ -0,0 +1,9 @@
from qtpy.QtWidgets import QApplication
from superqt import QQuantity
app = QApplication([])
w = QQuantity("1m")
w.show()
app.exec()

View File

@@ -0,0 +1,11 @@
from qtpy.QtWidgets import QApplication
from superqt import QSearchableComboBox
app = QApplication([])
slider = QSearchableComboBox()
slider.addItems(["foo", "bar", "baz", "foobar", "foobaz", "barbaz"])
slider.show()
app.exec_()

View File

@@ -0,0 +1,11 @@
from qtpy.QtWidgets import QApplication
from superqt import QSearchableListWidget
app = QApplication([])
slider = QSearchableListWidget()
slider.addItems(["foo", "bar", "baz", "foobar", "foobaz", "barbaz"])
slider.show()
app.exec_()

View File

@@ -0,0 +1,29 @@
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QApplication, QWidget
from superqt.utils import qthrottled
class Demo(QWidget):
positionChanged = Signal(int, int)
def __init__(self) -> None:
super().__init__()
self.setMouseTracking(True)
self.positionChanged.connect(self._show_location)
@qthrottled(timeout=400) # call this no more than once every 400ms
def _show_location(self, x, y):
print("Throttled event at", x, y)
def mouseMoveEvent(self, event):
print("real move event at", event.x(), event.y())
self.positionChanged.emit(event.x(), event.y())
if __name__ == "__main__":
app = QApplication([])
w = Demo()
w.resize(600, 600)
w.show()
app.exec_()

282
examples/throttler_demo.py Normal file
View File

@@ -0,0 +1,282 @@
"""Adapted for python from the KDToolBox
https://github.com/KDAB/KDToolBox/tree/master/qt/KDSignalThrottler
MIT License
Copyright (C) 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company,
info@kdab.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from typing import Deque
from qtpy.QtCore import QRect, QSize, Qt, QTimer, Signal
from qtpy.QtGui import QPainter, QPen
from qtpy.QtWidgets import (
QApplication,
QCheckBox,
QComboBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QPushButton,
QSizePolicy,
QSpinBox,
QVBoxLayout,
QWidget,
)
from superqt.utils._throttler import (
GenericSignalThrottler,
QSignalDebouncer,
QSignalThrottler,
)
class DrawSignalsWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
self.setAttribute(Qt.WA_OpaquePaintEvent)
self._scrollTimer = QTimer(self)
self._scrollTimer.setInterval(10)
self._scrollTimer.timeout.connect(self._scroll)
self._scrollTimer.start()
self._signalActivations: Deque[int] = Deque()
self._throttledSignalActivations: Deque[int] = Deque()
def sizeHint(self):
return QSize(400, 200)
def addSignalActivation(self):
self._signalActivations.appendleft(0)
def addThrottledSignalActivation(self):
self._throttledSignalActivations.appendleft(0)
def _scroll(self):
cutoff = self.width()
self.scrollAndCut(self._signalActivations, cutoff)
self.scrollAndCut(self._throttledSignalActivations, cutoff)
self.update()
def scrollAndCut(self, v: Deque[int], cutoff: int):
x = 0
L = len(v)
for p in range(L):
v[p] += 1
if v[p] > cutoff:
x = p
break
# TODO: fix this... delete old ones
def paintEvent(self, event):
p = QPainter(self)
p.fillRect(self.rect(), Qt.white)
h = self.height()
h2 = h // 2
w = self.width()
self._drawSignals(p, self._signalActivations, Qt.red, 0, h2)
self._drawSignals(p, self._throttledSignalActivations, Qt.blue, h2, h)
p.drawText(
QRect(0, 0, w, h2),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
"Source signal",
)
p.drawText(
QRect(0, h2, w, h2),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
"Throttled signal",
)
p.save()
pen = QPen()
pen.setWidthF(2.0)
p.drawLine(0, h2, w, h2)
p.restore()
def _drawSignals(self, p: QPainter, v: Deque[int], color, yStart, yEnd):
p.save()
pen = QPen()
pen.setWidthF(2.0)
pen.setColor(color)
p.setPen(pen)
for i in v:
p.drawLine(i, yStart, i, yEnd)
p.restore()
class DemoWidget(QWidget):
signalToBeThrottled = Signal()
_throttler: GenericSignalThrottler
def __init__(self, parent=None) -> None:
super().__init__(parent)
self._createUi()
self._throttler = None
self._throttlerKindComboBox.currentIndexChanged.connect(self._createThrottler)
self._createThrottler()
self._throttlerTimeoutSpinBox.valueChanged.connect(self.setThrottlerTimeout)
self.setThrottlerTimeout()
self._mainButton.clicked.connect(self.signalToBeThrottled)
self._autoTriggerTimer = QTimer(self)
self._autoTriggerTimer.setTimerType(Qt.TimerType.PreciseTimer)
self._autoTriggerCheckBox.clicked.connect(self._startOrStopAutoTriggerTimer)
self._startOrStopAutoTriggerTimer()
self._autoTriggerIntervalSpinBox.valueChanged.connect(
self._setAutoTriggerTimeout
)
self._setAutoTriggerTimeout()
self._autoTriggerTimer.timeout.connect(self.signalToBeThrottled)
self.signalToBeThrottled.connect(self._drawSignalsWidget.addSignalActivation)
def _createThrottler(self) -> None:
if self._throttler is not None:
self._throttler.deleteLater()
del self._throttler
if self._throttlerKindComboBox.currentIndex() < 2:
cls = QSignalThrottler
else:
cls = QSignalDebouncer
if self._throttlerKindComboBox.currentIndex() % 2:
policy = QSignalThrottler.EmissionPolicy.Leading
else:
policy = QSignalThrottler.EmissionPolicy.Trailing
self._throttler: GenericSignalThrottler = cls(policy, self)
self._throttler.setTimerType(Qt.TimerType.PreciseTimer)
self.signalToBeThrottled.connect(self._throttler.throttle)
self._throttler.triggered.connect(
self._drawSignalsWidget.addThrottledSignalActivation
)
self.setThrottlerTimeout()
def setThrottlerTimeout(self):
self._throttler.setTimeout(self._throttlerTimeoutSpinBox.value())
def _startOrStopAutoTriggerTimer(self):
shouldStart = self._autoTriggerCheckBox.isChecked()
if shouldStart:
self._autoTriggerTimer.start()
else:
self._autoTriggerTimer.stop()
self._autoTriggerIntervalSpinBox.setEnabled(shouldStart)
self._autoTriggerLabel.setEnabled(shouldStart)
def _setAutoTriggerTimeout(self):
timeout = self._autoTriggerIntervalSpinBox.value()
self._autoTriggerTimer.setInterval(timeout)
def _createUi(self):
helpLabel = QLabel(self)
helpLabel.setWordWrap(True)
helpLabel.setText(
"<h2>SignalThrottler example</h2>"
"<p>This example demonstrates the differences between "
"the different kinds of signal throttlers and debouncers."
)
throttlerKindGroupBox = QGroupBox("Throttler configuration", self)
self._throttlerKindComboBox = QComboBox(throttlerKindGroupBox)
self._throttlerKindComboBox.addItems(
(
"Throttler, trailing",
"Throttler, leading",
"Debouncer, trailing",
"Debouncer, leading",
)
)
self._throttlerTimeoutSpinBox = QSpinBox(throttlerKindGroupBox)
self._throttlerTimeoutSpinBox.setRange(1, 5000)
self._throttlerTimeoutSpinBox.setValue(500)
self._throttlerTimeoutSpinBox.setSuffix(" ms")
layout = QFormLayout(throttlerKindGroupBox)
layout.addRow("Kind of throttler:", self._throttlerKindComboBox)
layout.addRow("Timeout:", self._throttlerTimeoutSpinBox)
throttlerKindGroupBox.setLayout(layout)
buttonGroupBox = QGroupBox("Throttler activation")
self._mainButton = QPushButton(("Press me!"), buttonGroupBox)
self._autoTriggerCheckBox = QCheckBox("Trigger automatically")
autoTriggerLayout = QHBoxLayout()
self._autoTriggerLabel = QLabel("Interval", buttonGroupBox)
self._autoTriggerIntervalSpinBox = QSpinBox(buttonGroupBox)
self._autoTriggerIntervalSpinBox.setRange(1, 5000)
self._autoTriggerIntervalSpinBox.setValue(100)
self._autoTriggerIntervalSpinBox.setSuffix(" ms")
autoTriggerLayout.setContentsMargins(0, 0, 0, 0)
autoTriggerLayout.addWidget(self._autoTriggerLabel)
autoTriggerLayout.addWidget(self._autoTriggerIntervalSpinBox)
layout = QVBoxLayout(buttonGroupBox)
layout.addWidget(self._mainButton)
layout.addWidget(self._autoTriggerCheckBox)
layout.addLayout(autoTriggerLayout)
buttonGroupBox.setLayout(layout)
resultGroupBox = QGroupBox("Result")
self._drawSignalsWidget = DrawSignalsWidget(resultGroupBox)
layout = QVBoxLayout(resultGroupBox)
layout.addWidget(self._drawSignalsWidget)
resultGroupBox.setLayout(layout)
layout = QVBoxLayout(self)
layout.addWidget(helpLabel)
layout.addWidget(throttlerKindGroupBox)
layout.addWidget(buttonGroupBox)
layout.addWidget(resultGroupBox)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication([__name__])
w = DemoWidget()
w.resize(600, 600)
w.show()
app.exec_()

55
mkdocs.yml Normal file
View File

@@ -0,0 +1,55 @@
site_name: superqt
site_url: https://github.com/napari/superqt
site_description: >-
missing widgets and components for PyQt/PySide
# Repository
repo_name: napari/superqt
repo_url: https://github.com/napari/superqt
# Copyright
copyright: Copyright &copy; 2021 - 2022 Talley Lambert
extra_css:
- stylesheets/extra.css
watch:
- src
theme:
name: material
features:
- navigation.instant
- navigation.indexes
- navigation.expand
# - navigation.tracking
# - navigation.tabs
- search.highlight
- search.suggest
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- tables
- attr_list
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
plugins:
- search
- autorefs
- mkdocstrings
- macros:
module_name: docs/_macros
- mkdocstrings:
handlers:
python:
import:
- https://docs.python.org/3/objects.inv
options:
show_source: false
docstring_style: numpy
show_root_toc_entry: True
show_root_heading: True

View File

@@ -7,4 +7,4 @@ build-backend = "setuptools.build_meta"
write_to = "src/superqt/_version.py"
[tool.check-manifest]
ignore = ["src/superqt/_version.py"]
ignore = ["src/superqt/_version.py", "mkdocs.yml"]

View File

@@ -35,7 +35,10 @@ project_urls =
[options]
packages = find:
install_requires =
typing-extensions>=3.10.0.0
packaging
pygments>=2.4.0
qtpy>=1.1.0
typing-extensions
python_requires = >=3.7
include_package_data = True
package_dir =
@@ -60,6 +63,10 @@ dev =
pytest-qt
tox
tox-conda
docs =
mkdocs-macros-plugin
mkdocs-material
mkdocstrings[python]
font_fa5 =
fonticon-fontawesome5
font_mi5 =
@@ -72,7 +79,10 @@ pyside2 =
pyside2
pyside6 =
pyside6
quantity =
pint
testing =
pint
pytest
pytest-cov
pytest-qt
@@ -86,11 +96,6 @@ superqt = py.typed
exclude = _version.py,.eggs,examples
docstring-convention = numpy
ignore = E203,W503,E501,C901,F403,F405,D100
per-file-ignores =
src/superqt/qtcompat/QtCore.py:F401
src/superqt/qtcompat/QtGui.py:F401
src/superqt/qtcompat/QtWidgets.py:F401
src/superqt/qtcompat/__init__.py:F401,F811
[pydocstyle]
convention = numpy

View File

@@ -1,13 +1,18 @@
"""superqt is a collection of QtWidgets for python."""
"""superqt is a collection of Qt components for python."""
from typing import TYPE_CHECKING
try:
from ._version import version as __version__
except ImportError:
__version__ = "unknown"
if TYPE_CHECKING:
from .spinbox._quantity import QQuantity
from ._eliding_label import QElidingLabel
from .collapsible import QCollapsible
from .combobox import QEnumComboBox
from .combobox import QEnumComboBox, QSearchableComboBox
from .selection import QSearchableListWidget
from .sliders import (
QDoubleRangeSlider,
QDoubleSlider,
@@ -24,15 +29,26 @@ __all__ = [
"ensure_main_thread",
"ensure_object_thread",
"QDoubleRangeSlider",
"QCollapsible",
"QDoubleSlider",
"QElidingLabel",
"QEnumComboBox",
"QLabeledDoubleRangeSlider",
"QLabeledDoubleSlider",
"QLabeledRangeSlider",
"QLabeledSlider",
"QLargeIntSpinBox",
"QMessageHandler",
"QQuantity",
"QRangeSlider",
"QEnumComboBox",
"QCollapsible",
"QSearchableComboBox",
"QSearchableListWidget",
]
def __getattr__(name):
if name == "QQuantity":
from .spinbox._quantity import QQuantity
return QQuantity
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -1,8 +1,8 @@
from typing import List
from superqt.qtcompat.QtCore import QPoint, QRect, QSize, Qt
from superqt.qtcompat.QtGui import QFont, QFontMetrics, QResizeEvent, QTextLayout
from superqt.qtcompat.QtWidgets import QLabel
from qtpy.QtCore import QPoint, QRect, QSize, Qt
from qtpy.QtGui import QFont, QFontMetrics, QResizeEvent, QTextLayout
from qtpy.QtWidgets import QLabel
class QElidingLabel(QLabel):

View File

@@ -1,20 +1,14 @@
"""A collapsible widget to hide and unhide child widgets"""
from typing import Optional
from ..qtcompat.QtCore import (
QAbstractAnimation,
QEasingCurve,
QMargins,
QPropertyAnimation,
Qt,
)
from ..qtcompat.QtWidgets import QFrame, QPushButton, QVBoxLayout, QWidget
from qtpy.QtCore import QEasingCurve, QEvent, QMargins, QObject, QPropertyAnimation, Qt
from qtpy.QtWidgets import QFrame, QPushButton, QVBoxLayout, QWidget
class QCollapsible(QFrame):
"""A collapsible widget to hide and unhide child widgets.
Based on https://stackoverflow.com/a/68141638
Based on [https://stackoverflow.com/a/68141638](https://stackoverflow.com/a/68141638)
"""
_EXPANDED = ""
@@ -23,10 +17,11 @@ class QCollapsible(QFrame):
def __init__(self, title: str = "", parent: Optional[QWidget] = None):
super().__init__(parent)
self._locked = False
self._is_animating = False
self._toggle_btn = QPushButton(self._COLLAPSED + title)
self._toggle_btn.setCheckable(True)
self._toggle_btn.setStyleSheet("text-align: left; background: transparent;")
self._toggle_btn.setStyleSheet("text-align: left; border: none; outline: none;")
self._toggle_btn.toggled.connect(self._toggle)
# frame layout
@@ -38,6 +33,7 @@ class QCollapsible(QFrame):
self._animation = QPropertyAnimation(self)
self._animation.setPropertyName(b"maximumHeight")
self._animation.setStartValue(0)
self._animation.finished.connect(self._on_animation_done)
self.setDuration(300)
self.setEasingCurve(QEasingCurve.Type.InOutCubic)
@@ -77,19 +73,21 @@ class QCollapsible(QFrame):
def addWidget(self, widget: QWidget):
"""Add a widget to the central content widget's layout."""
widget.installEventFilter(self)
self._content.layout().addWidget(widget)
def removeWidget(self, widget: QWidget):
"""Remove widget from the central content widget's layout."""
self._content.layout().removeWidget(widget)
widget.removeEventFilter(self)
def expand(self, animate: bool = True):
"""Expand (show) the collapsible section"""
self._expand_collapse(QAbstractAnimation.Direction.Forward, animate)
self._expand_collapse(QPropertyAnimation.Direction.Forward, animate)
def collapse(self, animate: bool = True):
"""Collapse (hide) the collapsible section"""
self._expand_collapse(QAbstractAnimation.Direction.Backward, animate)
self._expand_collapse(QPropertyAnimation.Direction.Backward, animate)
def isExpanded(self) -> bool:
"""Return whether the collapsible section is visible"""
@@ -105,12 +103,12 @@ class QCollapsible(QFrame):
return self._locked
def _expand_collapse(
self, direction: QAbstractAnimation.Direction, animate: bool = True
self, direction: QPropertyAnimation.Direction, animate: bool = True
):
if self._locked:
return
forward = direction == QAbstractAnimation.Direction.Forward
forward = direction == QPropertyAnimation.Direction.Forward
text = self._EXPANDED if forward else self._COLLAPSED
self._toggle_btn.setChecked(forward)
@@ -120,9 +118,23 @@ class QCollapsible(QFrame):
if animate:
self._animation.setDirection(direction)
self._animation.setEndValue(_content_height)
self._is_animating = True
self._animation.start()
else:
self._content.setMaximumHeight(_content_height if forward else 0)
def _toggle(self):
self.expand() if self.isExpanded() else self.collapse()
def eventFilter(self, a0: QObject, a1: QEvent) -> bool:
"""If a child widget resizes, we need to update our expanded height."""
if (
a1.type() == QEvent.Type.Resize
and self.isExpanded()
and not self._is_animating
):
self._expand_collapse(QPropertyAnimation.Direction.Forward, animate=False)
return False
def _on_animation_done(self):
self._is_animating = False

View File

@@ -1,3 +1,4 @@
from ._enum_combobox import QEnumComboBox
from ._searchable_combo_box import QSearchableComboBox
__all__ = ("QEnumComboBox",)
__all__ = ("QEnumComboBox", "QSearchableComboBox")

View File

@@ -1,8 +1,8 @@
from enum import Enum, EnumMeta
from typing import Optional, TypeVar
from ..qtcompat.QtCore import Signal
from ..qtcompat.QtWidgets import QComboBox
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QComboBox
EnumType = TypeVar("EnumType", bound=Enum)
@@ -12,7 +12,10 @@ NONE_STRING = "----"
def _get_name(enum_value: Enum):
"""Create human readable name if user does not provide own implementation of __str__"""
if enum_value.__str__.__module__ != "enum":
if (
enum_value.__str__.__module__ != "enum"
and not enum_value.__str__.__module__.startswith("shibokensupport")
):
# check if function was overloaded
name = str(enum_value)
else:

View File

@@ -0,0 +1,48 @@
from qtpy import QT_VERSION
from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import QComboBox, QCompleter
try:
is_qt_bellow_5_14 = tuple(int(x) for x in QT_VERSION.split(".")[:2]) < (5, 14)
except ValueError:
is_qt_bellow_5_14 = False
class QSearchableComboBox(QComboBox):
"""
ComboCox with completer for fast search in multiple options
"""
if is_qt_bellow_5_14:
textActivated = Signal(str) # pragma: no cover
def __init__(self, parent=None):
super().__init__(parent)
self.setEditable(True)
self.completer_object = QCompleter()
self.completer_object.setCaseSensitivity(Qt.CaseInsensitive)
self.completer_object.setCompletionMode(QCompleter.PopupCompletion)
self.completer_object.setFilterMode(Qt.MatchContains)
self.setCompleter(self.completer_object)
self.setInsertPolicy(QComboBox.NoInsert)
if is_qt_bellow_5_14: # pragma: no cover
self.currentIndexChanged.connect(self._text_activated)
def _text_activated(self): # pragma: no cover
self.textActivated.emit(self.currentText())
def addItem(self, *args):
super().addItem(*args)
self.completer_object.setModel(self.model())
def addItems(self, *args):
super().addItems(*args)
self.completer_object.setModel(self.model())
def insertItem(self, *args) -> None:
super().insertItem(*args)
self.completer_object.setModel(self.model())
def insertItems(self, *args) -> None:
super().insertItems(*args)
self.completer_object.setModel(self.model())

View File

@@ -2,14 +2,15 @@ from __future__ import annotations
__all__ = [
"addFont",
"Animation",
"ENTRY_POINT",
"font",
"icon",
"IconFont",
"IconFontMeta",
"IconOpts",
"Animation",
"pulse",
"setTextIcon",
"spin",
]
@@ -22,8 +23,8 @@ from ._qfont_icon import DEFAULT_SCALING_FACTOR, IconOptionDict, IconOpts
from ._qfont_icon import QFontIconStore as _QFIS
if TYPE_CHECKING:
from superqt.qtcompat.QtGui import QFont, QTransform
from superqt.qtcompat.QtWidgets import QWidget
from qtpy.QtGui import QFont, QTransform
from qtpy.QtWidgets import QWidget
from ._qfont_icon import QFontIcon, ValidColor
@@ -49,10 +50,11 @@ def icon(
The `glyph_key` (e.g. 'fa5s.smile') represents a Font-family & style, and a glpyh.
In most cases, the key should be provided by a plugin in the environment, like:
https://github.com/tlambert03/fonticon-fontawesome5 ('fa5s' & 'fa5r' prefixes)
https://github.com/tlambert03/fonticon-materialdesignicons6 ('mdi6' prefix)
...but fonts can also be added manually using :func:`addFont`.
- [fonticon-fontawesome5](https://pypi.org/project/fonticon-fontawesome5/) ('fa5s' & 'fa5r' prefixes)
- [fonticon-materialdesignicons6](https://pypi.org/project/fonticon-materialdesignicons6/) ('mdi6' prefix)
...but fonts can also be added manually using [`addFont`][superqt.fonticon.addFont].
Parameters
----------
@@ -96,19 +98,22 @@ def icon(
Examples
--------
# simple example (assumes the font-awesome5 plugin is installed)
simple example (using the string `'fa5s.smile'` assumes the `fonticon-fontawesome5`
plugin is installed)
>>> btn = QPushButton()
>>> btn.setIcon(icon('fa5s.smile'))
# can also directly import from fonticon_fa5
can also directly import from fonticon_fa5
>>> from fonticon_fa5 import FA5S
>>> btn.setIcon(icon(FA5S.smile))
# with animation
with animation
>>> btn2 = QPushButton()
>>> btn2.setIcon(icon(FA5S.spinner, animation=pulse(btn2)))
# complicated example
complicated example
>>> btn = QPushButton()
>>> btn.setIcon(
... icon(
@@ -152,7 +157,7 @@ def setTextIcon(widget: QWidget, glyph_key: str, size: Optional[float] = None) -
Parameters
----------
wdg : QWidget
widget : QWidget
A widget supporting a `setText` method.
glyph_key : str
String encapsulating a font-family, style, and glyph. e.g. 'fa5s.smile'.
@@ -190,10 +195,12 @@ def addFont(
to their unicode numbers. If a charmap is not provided, glyphs must be directly
accessed with their unicode as something like `key.\uffff`.
NOTE: in most cases, users will not need this.
Instead, they should install a font plugin, like:
https://github.com/tlambert03/fonticon-fontawesome5
https://github.com/tlambert03/fonticon-materialdesignicons6
!!! Note
in most cases, users will not need this. Instead, they should install a
font plugin, like:
- [fonticon-fontawesome5](https://pypi.org/project/fonticon-fontawesome5/)
- [fonticon-materialdesignicons6](https://pypi.org/project/fonticon-materialdesignicons6/)
Parameters
----------

View File

@@ -1,11 +1,13 @@
from abc import ABC, abstractmethod
from superqt.qtcompat.QtCore import QRectF, QTimer
from superqt.qtcompat.QtGui import QPainter
from superqt.qtcompat.QtWidgets import QWidget
from qtpy.QtCore import QRectF, QTimer
from qtpy.QtGui import QPainter
from qtpy.QtWidgets import QWidget
class Animation(ABC):
"""Base icon animation class."""
def __init__(self, parent_widget: QWidget, interval: int = 10, step: int = 1):
self.parent_widget = parent_widget
self.timer = QTimer()
@@ -25,6 +27,8 @@ class Animation(ABC):
class spin(Animation):
"""Animation that smoothly spins an icon."""
def animate(self, painter: QPainter):
if not self.timer.isActive():
self.timer.start()
@@ -36,5 +40,7 @@ class spin(Animation):
class pulse(spin):
"""Animation that spins an icon in slower, discrete steps."""
def __init__(self, parent_widget: QWidget = None):
super().__init__(parent_widget, interval=200, step=45)

View File

@@ -17,7 +17,12 @@ class FontIconManager:
def _discover_fonts(self) -> None:
self._PLUGINS.clear()
for ep in entry_points().get(self.ENTRY_POINT, {}):
entries = entry_points()
if hasattr(entries, "select"): # python>3.10
_entries = entries.select(group=self.ENTRY_POINT) # type: ignore
else:
_entries = entries.get(self.ENTRY_POINT, [])
for ep in _entries:
if ep not in self._BLOCKED:
self._PLUGINS[ep.name] = ep

View File

@@ -6,11 +6,9 @@ from dataclasses import dataclass
from pathlib import Path
from typing import DefaultDict, Dict, Optional, Sequence, Tuple, Type, Union, cast
from typing_extensions import TypedDict
from ..qtcompat import QT_VERSION
from ..qtcompat.QtCore import QObject, QPoint, QRect, QSize, Qt
from ..qtcompat.QtGui import (
from qtpy import QT_VERSION
from qtpy.QtCore import QObject, QPoint, QRect, QSize, Qt
from qtpy.QtGui import (
QColor,
QFont,
QFontDatabase,
@@ -22,7 +20,9 @@ from ..qtcompat.QtGui import (
QPixmapCache,
QTransform,
)
from ..qtcompat.QtWidgets import QApplication, QStyleOption, QWidget
from qtpy.QtWidgets import QApplication, QStyleOption, QWidget
from typing_extensions import TypedDict
from ..utils import QMessageHandler
from ._animations import Animation
@@ -102,6 +102,23 @@ class IconOptionDict(TypedDict, total=False):
# IconOptions are.
@dataclass
class IconOpts:
"""Options for rendering an icon.
Parameters
----------
glyph_key : str, optional
The key of the glyph to use, e.g. `'fa5s.smile'`, by default `None`
scale_factor : float, optional
The scale factor to use, by default `None`
color : ValidColor, optional
The color to use, by default `None`. Colors may be specified as a string,
`QColor`, `Qt.GlobalColor`, or a 3 or 4-tuple of integers.
opacity : float, optional
The opacity to use, by default `None`
animation : Animation, optional
The animation to use, by default `None`
"""
glyph_key: Union[str, Unset] = _Unset
scale_factor: Union[float, Unset] = _Unset
color: Union[ValidColor, Unset] = _Unset
@@ -243,7 +260,9 @@ class _QFontIconEngine(QIconEngine):
def pixmap(self, size: QSize, mode: QIcon.Mode, state: QIcon.State) -> QPixmap:
# first look in cache
pmckey = self._pmcKey(size, mode, state)
pm = QPixmapCache.find(pmckey) if pmckey else None
with warnings.catch_warnings():
warnings.filterwarnings("ignore", "QPixmapCache.find")
pm = QPixmapCache.find(pmckey) if pmckey else None
if pm:
return pm
pixmap = QPixmap(size)
@@ -416,7 +435,7 @@ class QFontIconStore(QObject):
If you'd like to later use a fontkey in the form of `key.some-name`, then
`charmap` must be provided and provide a mapping for all of the glyph names
to their unicode numbers. If a charmap is not provided, glyphs must be directly
accessed with their unicode as something like `key.\uffff`.
accessed with their unicode as something like `key.\\uffff`.
Parameters
----------
@@ -507,7 +526,7 @@ class QFontIconStore(QObject):
) -> None:
"""Sets text on a widget to a specific font & glyph.
This is an alternative to setting a QIcon with a pixmap. It may
This is an alternative to setting a `QIcon` with a pixmap. It may
be easier to combine with dynamic stylesheets.
"""
setText = getattr(widget, "setText", None)

View File

@@ -1,4 +0,0 @@
from PyQt5.Qt3DAnimation import *
from PyQt6.Qt3DAnimation import *
from PySide2.Qt3DAnimation import *
from PySide6.Qt3DAnimation import *

View File

@@ -1,4 +0,0 @@
from PyQt5.Qt3DCore import *
from PyQt6.Qt3DCore import *
from PySide2.Qt3DCore import *
from PySide6.Qt3DCore import *

View File

@@ -1,4 +0,0 @@
from PyQt5.Qt3DExtras import *
from PyQt6.Qt3DExtras import *
from PySide2.Qt3DExtras import *
from PySide6.Qt3DExtras import *

View File

@@ -1,4 +0,0 @@
from PyQt5.Qt3DInput import *
from PyQt6.Qt3DInput import *
from PySide2.Qt3DInput import *
from PySide6.Qt3DInput import *

View File

@@ -1,4 +0,0 @@
from PyQt5.Qt3DLogic import *
from PyQt6.Qt3DLogic import *
from PySide2.Qt3DLogic import *
from PySide6.Qt3DLogic import *

View File

@@ -1,4 +0,0 @@
from PyQt5.Qt3DRender import *
from PyQt6.Qt3DRender import *
from PySide2.Qt3DRender import *
from PySide6.Qt3DRender import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtCharts import *
from PyQt6.QtCharts import *
from PySide2.QtCharts import *
from PySide6.QtCharts import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtConcurrent import *
from PyQt6.QtConcurrent import *
from PySide2.QtConcurrent import *
from PySide6.QtConcurrent import *

View File

@@ -1,12 +0,0 @@
# type: ignore
from . import API_NAME, _get_qtmodule
_QtCore = _get_qtmodule(__name__)
globals().update(_QtCore.__dict__)
if "PyQt" in API_NAME:
Property = _QtCore.pyqtProperty
Signal = _QtCore.pyqtSignal
SignalInstance = getattr(_QtCore, "pyqtBoundSignal", None)
Slot = _QtCore.pyqtSlot
__version__ = _QtCore.QT_VERSION_STR

View File

@@ -1,10 +0,0 @@
from PyQt5.QtCore import *
from PyQt6.QtCore import *
from PySide2.QtCore import *
from PySide6.QtCore import *
Property = pyqtProperty
Signal = pyqtSignal
SignalInstance = pyqtBoundSignal
Slot = pyqtSlot
__version__: str

View File

@@ -1,4 +0,0 @@
from PyQt5.QtDataVisualization import *
from PyQt6.QtDataVisualization import *
from PySide2.QtDataVisualization import *
from PySide6.QtDataVisualization import *

View File

@@ -1,13 +0,0 @@
# type: ignore
from . import API_NAME, _get_qtmodule
_QtGui = _get_qtmodule(__name__)
globals().update(_QtGui.__dict__)
if "6" in API_NAME:
def pos(self, *a):
_pos = self.position(*a)
return _pos.toPoint()
_QtGui.QMouseEvent.pos = pos

View File

@@ -1,4 +0,0 @@
from PyQt5.QtGui import *
from PyQt6.QtGui import *
from PySide2.QtGui import *
from PySide6.QtGui import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtHelp import *
from PyQt6.QtHelp import *
from PySide2.QtHelp import *
from PySide6.QtHelp import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtLocation import *
from PyQt6.QtLocation import *
from PySide2.QtLocation import *
from PySide6.QtLocation import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtMacExtras import *
from PyQt6.QtMacExtras import *
from PySide2.QtMacExtras import *
from PySide6.QtMacExtras import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtMultimedia import *
from PyQt6.QtMultimedia import *
from PySide2.QtMultimedia import *
from PySide6.QtMultimedia import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtMultimediaWidgets import *
from PyQt6.QtMultimediaWidgets import *
from PySide2.QtMultimediaWidgets import *
from PySide6.QtMultimediaWidgets import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtNetwork import *
from PyQt6.QtNetwork import *
from PySide2.QtNetwork import *
from PySide6.QtNetwork import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtOpenGL import *
from PyQt6.QtOpenGL import *
from PySide2.QtOpenGL import *
from PySide6.QtOpenGL import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtOpenGLFunctions import *
from PyQt6.QtOpenGLFunctions import *
from PySide2.QtOpenGLFunctions import *
from PySide6.QtOpenGLFunctions import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtPositioning import *
from PyQt6.QtPositioning import *
from PySide2.QtPositioning import *
from PySide6.QtPositioning import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtPrintSupport import *
from PyQt6.QtPrintSupport import *
from PySide2.QtPrintSupport import *
from PySide6.QtPrintSupport import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtQml import *
from PyQt6.QtQml import *
from PySide2.QtQml import *
from PySide6.QtQml import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtQuick import *
from PyQt6.QtQuick import *
from PySide2.QtQuick import *
from PySide6.QtQuick import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtQuickControls2 import *
from PyQt6.QtQuickControls2 import *
from PySide2.QtQuickControls2 import *
from PySide6.QtQuickControls2 import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtQuickWidgets import *
from PyQt6.QtQuickWidgets import *
from PySide2.QtQuickWidgets import *
from PySide6.QtQuickWidgets import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtRemoteObjects import *
from PyQt6.QtRemoteObjects import *
from PySide2.QtRemoteObjects import *
from PySide6.QtRemoteObjects import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtScript import *
from PyQt6.QtScript import *
from PySide2.QtScript import *
from PySide6.QtScript import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtScriptTools import *
from PyQt6.QtScriptTools import *
from PySide2.QtScriptTools import *
from PySide6.QtScriptTools import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtScxml import *
from PyQt6.QtScxml import *
from PySide2.QtScxml import *
from PySide6.QtScxml import *

View File

@@ -1,4 +0,0 @@
from PyQt5.QtSensors import *
from PyQt6.QtSensors import *
from PySide2.QtSensors import *
from PySide6.QtSensors import *

Some files were not shown because too many files have changed in this diff Show More