mirror of
https://github.com/pyapp-kit/superqt.git
synced 2025-07-21 12:11:07 +02:00
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
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -82,3 +82,4 @@ src/superqt/_version.py
|
||||
screenshots
|
||||
|
||||
.mypy_cache
|
||||
docs/_auto_images/
|
||||
|
21
README.md
21
README.md
@@ -1,6 +1,5 @@
|
||||
#  superqt!
|
||||
|
||||
|
||||
[](https://github.com/napari/superqt/raw/master/LICENSE)
|
||||
[](https://pypi.org/project/superqt)
|
||||
[
|
||||
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)
|
||||
|
||||
<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 widgets.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
144
docs/_macros.py
Normal file
144
docs/_macros.py
Normal 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"{{ 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
|
@@ -1,68 +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.
|
||||
|
||||
## 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`.
|
26
docs/faq.md
Normal file
26
docs/faq.md
Normal 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)
|
||||
```
|
29
docs/index.md
Normal file
29
docs/index.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# superqt
|
||||
|
||||
##  "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.
|
@@ -1,8 +0,0 @@
|
||||
# ListWidget
|
||||
|
||||
## 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.
|
||||
|
||||
Because of implementation it does not inherit directly from `QListWidget` but satisfy it all api. The only limitation is that it cannot be used as argument of `QListWidgetItem` constructor.
|
257
docs/sliders.md
257
docs/sliders.md
@@ -1,257 +0,0 @@
|
||||
# Sliders
|
||||
|
||||
|
||||

|
||||
|
||||
- `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])`)
|
||||
|
||||
*Note: There is a Qt5 Bug that affects sliders in MacOS 12+, see fix at bottom of page.*
|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
##### Big Sur
|
||||
|
||||

|
||||
|
||||
#### Windows
|
||||
|
||||

|
||||
|
||||
#### Linux
|
||||
|
||||

|
||||
|
||||
## Labeled Sliders
|
||||
|
||||
This package also includes two "labeled" slider variants. One for `QRangeSlider`, and one for the native `QSlider`:
|
||||
|
||||
### `QLabeledRangeSlider`
|
||||
|
||||

|
||||
|
||||
```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`
|
||||
|
||||

|
||||
|
||||
```python
|
||||
from superqt import QLabeledSlider
|
||||
```
|
||||
|
||||
(no additional options at this point)
|
||||
|
||||
|
||||
## Float Slider
|
||||
|
||||
just like QSlider, but supports float values
|
||||
|
||||
```python
|
||||
from superqt import QDoubleSlider
|
||||
```
|
||||
|
||||
## Issues
|
||||
|
||||
### MacOS Monterey Slider issue
|
||||
|
||||
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://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)
|
||||
```
|
52
docs/utilities/code_syntax_highlight.md
Normal file
52
docs/utilities/code_syntax_highlight.md
Normal 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
101
docs/utilities/fonticon.md
Normal 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
31
docs/utilities/index.md
Normal 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. |
|
8
docs/utilities/qmessagehandler.md
Normal file
8
docs/utilities/qmessagehandler.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# QMessageHandler
|
||||
|
||||
::: superqt.utils.QMessageHandler
|
||||
options:
|
||||
heading_level: 3
|
||||
show_signature_annotations: True
|
||||
docstring_style: numpy
|
||||
show_bases: False
|
@@ -1,18 +1,24 @@
|
||||
# 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
|
||||
@@ -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
|
36
docs/utilities/threading.md
Normal file
36
docs/utilities/threading.md
Normal 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
|
46
docs/utilities/throttling.md
Normal file
46
docs/utilities/throttling.md
Normal 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
|
@@ -1,10 +0,0 @@
|
||||
# Utils
|
||||
|
||||
## Code highlighting
|
||||
|
||||
`superqt` provides 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/).
|
31
docs/widgets/index.md
Normal file
31
docs/widgets/index.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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 |
|
||||
|
||||
## 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. |
|
24
docs/widgets/qcollapsible.md
Normal file
24
docs/widgets/qcollapsible.md
Normal 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') }}
|
23
docs/widgets/qdoublerangeslider.md
Normal file
23
docs/widgets/qdoublerangeslider.md
Normal 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') }}
|
23
docs/widgets/qdoubleslider.md
Normal file
23
docs/widgets/qdoubleslider.md
Normal 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') }}
|
26
docs/widgets/qelidinglabel.md
Normal file
26
docs/widgets/qelidinglabel.md
Normal 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') }}
|
72
docs/widgets/qenumcombobox.md
Normal file
72
docs/widgets/qenumcombobox.md
Normal 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') }}
|
23
docs/widgets/qlabeleddoublerangeslider.md
Normal file
23
docs/widgets/qlabeleddoublerangeslider.md
Normal 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') }}
|
24
docs/widgets/qlabeleddoubleslider.md
Normal file
24
docs/widgets/qlabeleddoubleslider.md
Normal 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') }}
|
29
docs/widgets/qlabeledrangeslider.md
Normal file
29
docs/widgets/qlabeledrangeslider.md
Normal 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
|
22
docs/widgets/qlabeledslider.md
Normal file
22
docs/widgets/qlabeledslider.md
Normal 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') }}
|
23
docs/widgets/qlargeintspinbox.md
Normal file
23
docs/widgets/qlargeintspinbox.md
Normal 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') }}
|
229
docs/widgets/qrangeslider.md
Normal file
229
docs/widgets/qrangeslider.md
Normal 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
|
||||
|
||||
{ width=580; }
|
||||
|
||||
##### Big Sur
|
||||
|
||||
{ width=580; }
|
||||
|
||||
#### Windows
|
||||
|
||||

|
||||
|
||||
#### Linux
|
||||
|
||||

|
||||
|
||||
|
||||
{{ 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, ...])
|
||||
```
|
25
docs/widgets/qsearchablecombobox.md
Normal file
25
docs/widgets/qsearchablecombobox.md
Normal 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') }}
|
28
docs/widgets/qsearchablelistwidget.md
Normal file
28
docs/widgets/qsearchablelistwidget.md
Normal 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') }}
|
@@ -8,6 +8,7 @@ app = QApplication([])
|
||||
slider = QDoubleSlider(Qt.Orientation.Horizontal)
|
||||
slider.setRange(0, 1)
|
||||
slider.setValue(0.5)
|
||||
slider.resize(500, 50)
|
||||
slider.show()
|
||||
|
||||
app.exec_()
|
55
mkdocs.yml
Normal file
55
mkdocs.yml
Normal 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 © 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
|
@@ -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"]
|
||||
|
@@ -63,6 +63,10 @@ dev =
|
||||
pytest-qt
|
||||
tox
|
||||
tox-conda
|
||||
docs =
|
||||
mkdocs-macros-plugin
|
||||
mkdocs-material
|
||||
mkdocstrings[python]
|
||||
font_fa5 =
|
||||
fonticon-fontawesome5
|
||||
font_mi5 =
|
||||
|
@@ -8,7 +8,7 @@ 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 = "▼ "
|
||||
|
@@ -2,14 +2,15 @@ from __future__ import annotations
|
||||
|
||||
__all__ = [
|
||||
"addFont",
|
||||
"Animation",
|
||||
"ENTRY_POINT",
|
||||
"font",
|
||||
"icon",
|
||||
"IconFont",
|
||||
"IconFontMeta",
|
||||
"IconOpts",
|
||||
"Animation",
|
||||
"pulse",
|
||||
"setTextIcon",
|
||||
"spin",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
----------
|
||||
|
@@ -6,6 +6,8 @@ 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)
|
||||
|
@@ -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
|
||||
@@ -418,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
|
||||
----------
|
||||
@@ -509,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)
|
||||
|
@@ -68,14 +68,14 @@ class _GenericRangeSlider(_GenericSlider[Tuple], Generic[_T]):
|
||||
def barIsRigid(self) -> bool:
|
||||
"""Whether bar length is constant when dragging the bar.
|
||||
|
||||
If False, the bar can shorten when dragged beyond min/max. Default is True.
|
||||
If `False`, the bar can shorten when dragged beyond min/max. Default is `True`.
|
||||
"""
|
||||
return self._bar_is_rigid
|
||||
|
||||
def setBarIsRigid(self, val: bool = True) -> None:
|
||||
"""Whether bar length is constant when dragging the bar.
|
||||
|
||||
If False, the bar can shorten when dragged beyond min/max. Default is True.
|
||||
If `False`, the bar can shorten when dragged beyond min/max. Default is `True`.
|
||||
"""
|
||||
self._bar_is_rigid = bool(val)
|
||||
|
||||
@@ -96,12 +96,18 @@ class _GenericRangeSlider(_GenericSlider[Tuple], Generic[_T]):
|
||||
self._should_draw_bar = bool(val)
|
||||
|
||||
def hideBar(self) -> None:
|
||||
"""Hide the bar between the first and last handle."""
|
||||
self.setBarVisible(False)
|
||||
|
||||
def showBar(self) -> None:
|
||||
"""Show the bar between the first and last handle."""
|
||||
self.setBarVisible(True)
|
||||
|
||||
def applyMacStylePatch(self) -> str:
|
||||
"""Apply a QSS patch to fix sliders on macos>=12 with QT < 6.
|
||||
|
||||
see [FAQ](../faq.md#sliders-not-dragging-properly-on-macos-12) for more details.
|
||||
"""
|
||||
super().applyMacStylePatch()
|
||||
self._style._macpatch = True
|
||||
|
||||
@@ -205,6 +211,7 @@ class _GenericRangeSlider(_GenericSlider[Tuple], Generic[_T]):
|
||||
self._style.brush_active = color
|
||||
|
||||
barColor = Property(QtGui.QBrush, _getBarColor, _setBarColor)
|
||||
"""The color of the bar between the first and last handle."""
|
||||
|
||||
def _offsetAllPositions(self, offset: float, ref=None) -> None:
|
||||
if ref is None:
|
||||
|
@@ -99,6 +99,10 @@ class _GenericSlider(QSlider, Generic[_T]):
|
||||
self.applyMacStylePatch()
|
||||
|
||||
def applyMacStylePatch(self) -> str:
|
||||
"""Apply a QSS patch to fix sliders on macos>=12 with QT < 6.
|
||||
|
||||
see [FAQ](../faq.md#sliders-not-dragging-properly-on-macos-12) for more details.
|
||||
"""
|
||||
self.setStyleSheet(MONTEREY_SLIDER_STYLES_FIX)
|
||||
|
||||
# ############### QtOverrides #######################
|
||||
|
@@ -185,9 +185,11 @@ class QLabeledSlider(_SliderProxy, QAbstractSlider):
|
||||
self.setLayout(layout)
|
||||
|
||||
def edgeLabelMode(self) -> EdgeLabelMode:
|
||||
"""Return current `EdgeLabelMode`."""
|
||||
return self._edge_label_mode
|
||||
|
||||
def setEdgeLabelMode(self, opt: EdgeLabelMode) -> None:
|
||||
"""Set the `EdgeLabelMode`."""
|
||||
if opt is EdgeLabelMode.LabelIsRange:
|
||||
raise ValueError(
|
||||
"mode must be one of 'EdgeLabelMode.NoLabel' or "
|
||||
@@ -283,9 +285,11 @@ class QLabeledRangeSlider(_SliderProxy, QAbstractSlider):
|
||||
self.valueChanged = self._valueChanged
|
||||
|
||||
def handleLabelPosition(self) -> LabelPosition:
|
||||
"""Return where/whether labels are shown adjacent to slider handles."""
|
||||
return self._handle_label_position
|
||||
|
||||
def setHandleLabelPosition(self, opt: LabelPosition) -> LabelPosition:
|
||||
"""Set where/whether labels are shown adjacent to slider handles."""
|
||||
self._handle_label_position = opt
|
||||
for lbl in self._handle_labels:
|
||||
if not opt:
|
||||
@@ -295,9 +299,11 @@ class QLabeledRangeSlider(_SliderProxy, QAbstractSlider):
|
||||
self.setOrientation(self.orientation())
|
||||
|
||||
def edgeLabelMode(self) -> EdgeLabelMode:
|
||||
"""Return current `EdgeLabelMode`."""
|
||||
return self._edge_label_mode
|
||||
|
||||
def setEdgeLabelMode(self, opt: EdgeLabelMode):
|
||||
"""Set `EdgeLabelMode`, controls what is shown at the min/max labels."""
|
||||
self._edge_label_mode = opt
|
||||
if not self._edge_label_mode:
|
||||
self._min_label.hide()
|
||||
|
@@ -92,7 +92,7 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
signal emitter object. To allow identify which worker thread emitted signal.
|
||||
"""
|
||||
|
||||
#: A set of Workers. Add to set using :meth:`WorkerBase.start`
|
||||
#: A set of Workers. Add to set using `WorkerBase.start`
|
||||
_worker_set: Set[WorkerBase] = set()
|
||||
returned: SigInst[_R]
|
||||
errored: SigInst[Exception]
|
||||
@@ -113,11 +113,11 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
def __getattr__(self, name: str) -> SigInst:
|
||||
"""Pass through attr requests to signals to simplify connection API.
|
||||
|
||||
The goal is to enable ``worker.yielded.connect`` instead of
|
||||
``worker.signals.yielded.connect``. Because multiple inheritance of Qt
|
||||
The goal is to enable `worker.yielded.connect` instead of
|
||||
`worker.signals.yielded.connect`. Because multiple inheritance of Qt
|
||||
classes is not well supported in PyQt, we have to use composition here
|
||||
(signals are provided by QObjects, and QRunnable is not a QObject). So
|
||||
this passthrough allows us to connect to signals on the ``_signals``
|
||||
this passthrough allows us to connect to signals on the `_signals`
|
||||
object.
|
||||
"""
|
||||
# the Signal object is actually a class attribute
|
||||
@@ -134,11 +134,10 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
def quit(self) -> None:
|
||||
"""Send a request to abort the worker.
|
||||
|
||||
.. note::
|
||||
|
||||
!!! note
|
||||
It is entirely up to subclasses to honor this method by checking
|
||||
``self.abort_requested`` periodically in their ``worker.work``
|
||||
method, and exiting if ``True``.
|
||||
`self.abort_requested` periodically in their `worker.work`
|
||||
method, and exiting if `True`.
|
||||
"""
|
||||
self._abort_requested = True
|
||||
|
||||
@@ -160,20 +159,20 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
|
||||
The order of method calls when starting a worker is:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
```
|
||||
calls QThreadPool.globalInstance().start(worker)
|
||||
| triggered by the QThreadPool.start() method
|
||||
| | called by worker.run
|
||||
| | |
|
||||
V V V
|
||||
worker.start -> worker.run -> worker.work
|
||||
```
|
||||
|
||||
**This** is the function that actually gets called when calling
|
||||
:func:`QThreadPool.start(worker)`. It simply wraps the :meth:`work`
|
||||
`QThreadPool.start(worker)`. It simply wraps the `work()`
|
||||
method, and emits a few signals. Subclasses should NOT override this
|
||||
method (except with good reason), and instead should implement
|
||||
:meth:`work`.
|
||||
`work()`.
|
||||
"""
|
||||
self.started.emit()
|
||||
self._running = True
|
||||
@@ -208,26 +207,26 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
|
||||
The end-user should never need to call this function.
|
||||
But subclasses must implement this method (See
|
||||
:meth:`GeneratorFunction.work` for an example implementation).
|
||||
Minimally, it should check ``self.abort_requested`` periodically and
|
||||
[`GeneratorFunction.work`][superqt.utils._qthreading.GeneratorWorker.work] for an example implementation).
|
||||
Minimally, it should check `self.abort_requested` periodically and
|
||||
exit if True.
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
```python
|
||||
class MyWorker(WorkerBase):
|
||||
|
||||
class MyWorker(WorkerBase):
|
||||
|
||||
def work(self):
|
||||
i = 0
|
||||
while True:
|
||||
if self.abort_requested:
|
||||
self.aborted.emit()
|
||||
break
|
||||
i += 1
|
||||
if i > max_iters:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
def work(self):
|
||||
i = 0
|
||||
while True:
|
||||
if self.abort_requested:
|
||||
self.aborted.emit()
|
||||
break
|
||||
i += 1
|
||||
if i > max_iters:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
```
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
f'"{self.__class__.__name__}" failed to define work() method'
|
||||
@@ -238,14 +237,14 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
|
||||
The order of method calls when starting a worker is:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
```
|
||||
calls QThreadPool.globalInstance().start(worker)
|
||||
| triggered by the QThreadPool.start() method
|
||||
| | called by worker.run
|
||||
| | |
|
||||
V V V
|
||||
worker.start -> worker.run -> worker.work
|
||||
```
|
||||
"""
|
||||
if self in self._worker_set:
|
||||
raise RuntimeError("This worker is already started!")
|
||||
@@ -271,33 +270,33 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
def await_workers(cls, msecs: int = None) -> None:
|
||||
"""Ask all workers to quit, and wait up to `msec` for quit.
|
||||
|
||||
Attempts to clean up all running workers by calling ``worker.quit()``
|
||||
method. Any workers in the ``WorkerBase._worker_set`` set will have this
|
||||
Attempts to clean up all running workers by calling `worker.quit()`
|
||||
method. Any workers in the `WorkerBase._worker_set` set will have this
|
||||
method.
|
||||
|
||||
By default, this function will block indefinitely, until worker threads
|
||||
finish. If a timeout is provided, a ``RuntimeError`` will be raised if
|
||||
finish. If a timeout is provided, a `RuntimeError` will be raised if
|
||||
the workers do not gracefully exit in the time requests, but the threads
|
||||
will NOT be killed. It is (currently) left to the user to use their OS
|
||||
to force-quit rogue threads.
|
||||
|
||||
.. important::
|
||||
!!! important
|
||||
|
||||
If the user does not put any yields in their function, and the function
|
||||
is super long, it will just hang... For instance, there's no graceful
|
||||
way to kill this thread in python:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@thread_worker
|
||||
def ZZZzzz():
|
||||
time.sleep(10000000)
|
||||
```python
|
||||
@thread_worker
|
||||
def ZZZzzz():
|
||||
time.sleep(10000000)
|
||||
```
|
||||
|
||||
This is why it's always advisable to use a generator that periodically
|
||||
yields for long-running computations in another thread.
|
||||
|
||||
See `this stack-overflow post
|
||||
<https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread>`_
|
||||
See [this stack-overflow
|
||||
post](https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread)
|
||||
for a good discussion on the difficulty of killing a rogue python thread:
|
||||
|
||||
Parameters
|
||||
@@ -326,12 +325,11 @@ class WorkerBase(QRunnable, Generic[_R]):
|
||||
class FunctionWorker(WorkerBase[_R]):
|
||||
"""QRunnable with signals that wraps a simple long-running function.
|
||||
|
||||
.. note::
|
||||
|
||||
``FunctionWorker`` does not provide a way to stop a very long-running
|
||||
function (e.g. ``time.sleep(10000)``). So whenever possible, it is
|
||||
better to implement your long running function as a generator that
|
||||
yields periodically, and use the :class:`GeneratorWorker` instead.
|
||||
!!! note
|
||||
`FunctionWorker` does not provide a way to stop a very long-running
|
||||
function (e.g. `time.sleep(10000)`). So whenever possible, it is better to
|
||||
implement your long running function as a generator that yields periodically,
|
||||
and use the [`GeneratorWorker`][superqt.utils.GeneratorWorker] instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -345,7 +343,7 @@ class FunctionWorker(WorkerBase[_R]):
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If ``func`` is a generator function and not a regular function.
|
||||
If `func` is a generator function and not a regular function.
|
||||
"""
|
||||
|
||||
def __init__(self, func: Callable[_P, _R], *args, **kwargs):
|
||||
@@ -454,7 +452,7 @@ class GeneratorWorker(WorkerBase, Generic[_Y, _S, _R]):
|
||||
return exc.value
|
||||
except RuntimeError as exc:
|
||||
# The worker has probably been deleted. warning will be
|
||||
# emitted in ``WorkerBase.run``
|
||||
# emitted in `WorkerBase.run`
|
||||
return exc
|
||||
return None
|
||||
|
||||
@@ -534,38 +532,39 @@ def create_worker(
|
||||
) -> Union[FunctionWorker, GeneratorWorker]:
|
||||
"""Convenience function to start a function in another thread.
|
||||
|
||||
By default, uses :class:`Worker`, but a custom ``WorkerBase`` subclass may
|
||||
be provided. If so, it must be a subclass of :class:`Worker`, which
|
||||
defines a standard set of signals and a run method.
|
||||
By default, uses `FunctionWorker` for functions and `GeneratorWorker` for
|
||||
generators, but a custom `WorkerBase` subclass may be provided. If so, it must be a
|
||||
subclass of `WorkerBase`, which defines a standard set of signals and a run method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : Callable
|
||||
The function to call in another thread.
|
||||
_start_thread : bool, optional
|
||||
_start_thread : bool
|
||||
Whether to immediaetly start the thread. If False, the returned worker
|
||||
must be manually started with ``worker.start()``. by default it will be
|
||||
``False`` if the ``_connect`` argument is ``None``, otherwise ``True``.
|
||||
must be manually started with `worker.start()`. by default it will be
|
||||
`False` if the `_connect` argument is `None`, otherwise `True`.
|
||||
_connect : Dict[str, Union[Callable, Sequence]], optional
|
||||
A mapping of ``"signal_name"`` -> ``callable`` or list of ``callable``:
|
||||
A mapping of `"signal_name"` -> `callable` or list of `callable`:
|
||||
callback functions to connect to the various signals offered by the
|
||||
worker class. by default None
|
||||
_worker_class : type of GeneratorWorker or FunctionWorker, optional
|
||||
The :class`WorkerBase` to instantiate, by default
|
||||
:class:`FunctionWorker` will be used if ``func`` is a regular function,
|
||||
and :class:`GeneratorWorker` will be used if it is a generator.
|
||||
_ignore_errors : bool, optional
|
||||
If ``False`` (the default), errors raised in the other thread will be
|
||||
worker class. by default `None`
|
||||
_worker_class : type of `GeneratorWorker` or `FunctionWorker`, optional
|
||||
The [`WorkerBase`][superqt.utils.WorkerBase] to instantiate, by default
|
||||
[`FunctionWorker`][superqt.utils.FunctionWorker] will be used if `func` is a
|
||||
regular function, and [`GeneratorWorker`][superqt.utils.GeneratorWorker] will be
|
||||
used if it is a generator.
|
||||
_ignore_errors : bool
|
||||
If `False` (the default), errors raised in the other thread will be
|
||||
reraised in the main thread (makes debugging significantly easier).
|
||||
*args
|
||||
will be passed to ``func``
|
||||
will be passed to `func`
|
||||
**kwargs
|
||||
will be passed to ``func``
|
||||
will be passed to `func`
|
||||
|
||||
Returns
|
||||
-------
|
||||
worker : WorkerBase
|
||||
An instantiated worker. If ``_start_thread`` was ``False``, the worker
|
||||
An instantiated worker. If `_start_thread` was `False`, the worker
|
||||
will have a `.start()` method that can be used to start the thread.
|
||||
|
||||
Raises
|
||||
@@ -573,18 +572,17 @@ def create_worker(
|
||||
TypeError
|
||||
If a worker_class is provided that is not a subclass of WorkerBase.
|
||||
TypeError
|
||||
If _connect is provided and is not a dict of ``{str: callable}``
|
||||
If _connect is provided and is not a dict of `{str: callable}`
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
def long_function(duration):
|
||||
import time
|
||||
time.sleep(duration)
|
||||
|
||||
worker = create_worker(long_function, 10)
|
||||
```python
|
||||
def long_function(duration):
|
||||
import time
|
||||
time.sleep(duration)
|
||||
|
||||
worker = create_worker(long_function, 10)
|
||||
```
|
||||
"""
|
||||
worker: Union[FunctionWorker, GeneratorWorker]
|
||||
|
||||
@@ -616,7 +614,7 @@ def create_worker(
|
||||
getattr(worker, key).connect(v)
|
||||
|
||||
# if the user has not provided a default connection for the "errored"
|
||||
# signal... and they have not explicitly set ``ignore_errors=True``
|
||||
# signal... and they have not explicitly set `ignore_errors=True`
|
||||
# Then rereaise any errors from the thread.
|
||||
if not _ignore_errors and not (_connect or {}).get("errored", False):
|
||||
|
||||
@@ -672,55 +670,55 @@ def thread_worker(
|
||||
):
|
||||
"""Decorator that runs a function in a separate thread when called.
|
||||
|
||||
When called, the decorated function returns a :class:`WorkerBase`. See
|
||||
:func:`create_worker` for additional keyword arguments that can be used
|
||||
When called, the decorated function returns a [`WorkerBase`][superqt.utils.WorkerBase]. See
|
||||
[`create_worker`][superqt.utils.create_worker] for additional keyword arguments that can be used
|
||||
when calling the function.
|
||||
|
||||
The returned worker will have these signals:
|
||||
|
||||
- *started*: emitted when the work is started
|
||||
- *finished*: emitted when the work is finished
|
||||
- *returned*: emitted with return value
|
||||
- *errored*: emitted with error object on Exception
|
||||
- **started**: emitted when the work is started
|
||||
- **finished**: emitted when the work is finished
|
||||
- **returned**: emitted with return value
|
||||
- **errored**: emitted with error object on Exception
|
||||
|
||||
It will also have a ``worker.start()`` method that can be used to start
|
||||
It will also have a `worker.start()` method that can be used to start
|
||||
execution of the function in another thread. (useful if you need to connect
|
||||
callbacks to signals prior to execution)
|
||||
|
||||
If the decorated function is a generator, the returned worker will also
|
||||
provide these signals:
|
||||
|
||||
- *yielded*: emitted with yielded values
|
||||
- *paused*: emitted when a running job has successfully paused
|
||||
- *resumed*: emitted when a paused job has successfully resumed
|
||||
- *aborted*: emitted when a running job is successfully aborted
|
||||
- **yielded**: emitted with yielded values
|
||||
- **paused**: emitted when a running job has successfully paused
|
||||
- **resumed**: emitted when a paused job has successfully resumed
|
||||
- **aborted**: emitted when a running job is successfully aborted
|
||||
|
||||
And these methods:
|
||||
|
||||
- *quit*: ask the thread to quit
|
||||
- *toggle_paused*: toggle the running state of the thread.
|
||||
- *send*: send a value into the generator. (This requires that your
|
||||
decorator function uses the ``value = yield`` syntax)
|
||||
- **quit**: ask the thread to quit
|
||||
- **toggle_paused**: toggle the running state of the thread.
|
||||
- **send**: send a value into the generator. (This requires that your
|
||||
decorator function uses the `value = yield` syntax)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
function : callable
|
||||
Function to call in another thread. For communication between threads
|
||||
may be a generator function.
|
||||
start_thread : bool, optional
|
||||
start_thread : bool
|
||||
Whether to immediaetly start the thread. If False, the returned worker
|
||||
must be manually started with ``worker.start()``. by default it will be
|
||||
``False`` if the ``_connect`` argument is ``None``, otherwise ``True``.
|
||||
connect : Dict[str, Union[Callable, Sequence]], optional
|
||||
A mapping of ``"signal_name"`` -> ``callable`` or list of ``callable``:
|
||||
must be manually started with `worker.start()`. by default it will be
|
||||
`False` if the `_connect` argument is `None`, otherwise `True`.
|
||||
connect : Dict[str, Union[Callable, Sequence]]
|
||||
A mapping of `"signal_name"` -> `callable` or list of `callable`:
|
||||
callback functions to connect to the various signals offered by the
|
||||
worker class. by default None
|
||||
worker_class : Type[WorkerBase], optional
|
||||
The :class`WorkerBase` to instantiate, by default
|
||||
:class:`FunctionWorker` will be used if ``func`` is a regular function,
|
||||
and :class:`GeneratorWorker` will be used if it is a generator.
|
||||
ignore_errors : bool, optional
|
||||
If ``False`` (the default), errors raised in the other thread will be
|
||||
worker_class : Type[WorkerBase]
|
||||
The [`WorkerBase`][superqt.utils.WorkerBase] to instantiate, by default
|
||||
[`FunctionWorker`][superqt.utils.FunctionWorker] will be used if `func` is a regular function,
|
||||
and [`GeneratorWorker`][superqt.utils.GeneratorWorker] will be used if it is a generator.
|
||||
ignore_errors : bool
|
||||
If `False` (the default), errors raised in the other thread will be
|
||||
reraised in the main thread (makes debugging significantly easier).
|
||||
|
||||
Returns
|
||||
@@ -731,25 +729,26 @@ def thread_worker(
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
```python
|
||||
@thread_worker
|
||||
def long_function(start, end):
|
||||
# do work, periodically yielding
|
||||
i = start
|
||||
while i <= end:
|
||||
time.sleep(0.1)
|
||||
yield i
|
||||
|
||||
@thread_worker
|
||||
def long_function(start, end):
|
||||
# do work, periodically yielding
|
||||
i = start
|
||||
while i <= end:
|
||||
time.sleep(0.1)
|
||||
yield i
|
||||
# do teardown
|
||||
return 'anything'
|
||||
|
||||
# do teardown
|
||||
return 'anything'
|
||||
# call the function to start running in another thread.
|
||||
worker = long_function()
|
||||
|
||||
# call the function to start running in another thread.
|
||||
worker = long_function()
|
||||
# connect signals here if desired... or they may be added using the
|
||||
# `connect` argument in the `@thread_worker` decorator... in which
|
||||
# case the worker will start immediately when long_function() is called
|
||||
worker.start()
|
||||
# connect signals here if desired... or they may be added using the
|
||||
# `connect` argument in the `@thread_worker` decorator... in which
|
||||
# case the worker will start immediately when long_function() is called
|
||||
worker.start()
|
||||
```
|
||||
"""
|
||||
|
||||
def _inner(func):
|
||||
@@ -804,36 +803,35 @@ def new_worker_qthread(
|
||||
_connect: Dict[str, Callable] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""This is a convenience function to start a worker in a Qthread.
|
||||
"""This is a convenience function to start a worker in a `QThread`.
|
||||
|
||||
In most cases, the @thread_worker decorator is sufficient and preferable.
|
||||
But this allows the user to completely customize the Worker object.
|
||||
However, they must then maintain control over the thread and clean up
|
||||
In most cases, the [thread_worker][superqt.utils.thread_worker] decorator is
|
||||
sufficient and preferable. But this allows the user to completely customize the
|
||||
Worker object. However, they must then maintain control over the thread and clean up
|
||||
appropriately.
|
||||
|
||||
It follows the pattern described here:
|
||||
https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong
|
||||
and
|
||||
https://doc.qt.io/qt-5/qthread.html#details
|
||||
It follows the pattern described
|
||||
[here](https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong) and in the [qt thread
|
||||
docs](https://doc.qt.io/qt-5/qthread.html#details)
|
||||
|
||||
see also:
|
||||
|
||||
https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
|
||||
|
||||
A QThread object is not a thread! It should be thought of as a class to
|
||||
*manage* a thread, not as the actual code or object that runs in that
|
||||
A QThread object is not a thread! It should be thought of as a class to *manage* a
|
||||
thread, not as the actual code or object that runs in that
|
||||
thread. The QThread object is created on the main thread and lives there.
|
||||
|
||||
Worker objects which derive from QObject are the things that actually do
|
||||
the work. They can be moved to a QThread as is done here.
|
||||
|
||||
.. note:: Mostly ignorable detail
|
||||
??? "Mostly ignorable detail"
|
||||
|
||||
While the signals/slots syntax of the worker looks very similar to
|
||||
standard "single-threaded" signals & slots, note that inter-thread
|
||||
signals and slots (automatically) use an event-based QueuedConnection,
|
||||
while intra-thread signals use a DirectConnection. See `Signals and
|
||||
Slots Across Threads
|
||||
<https://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads>`_
|
||||
signals and slots (automatically) use an event-based QueuedConnection, while
|
||||
intra-thread signals use a DirectConnection. See [Signals and Slots Across
|
||||
Threads](https://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads>)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -843,7 +841,7 @@ def new_worker_qthread(
|
||||
_start_thread : bool
|
||||
If True, thread will be started immediately, otherwise, thread must
|
||||
be manually started with thread.start().
|
||||
_connect : dict, optional
|
||||
_connect : dict
|
||||
Optional dictionary of {signal: function} to connect to the new worker.
|
||||
for instance: _connect = {'incremented': myfunc} will result in:
|
||||
worker.incremented.connect(myfunc)
|
||||
@@ -863,33 +861,33 @@ def new_worker_qthread(
|
||||
--------
|
||||
Create some QObject that has a long-running work method:
|
||||
|
||||
.. code-block:: python
|
||||
```python
|
||||
|
||||
class Worker(QObject):
|
||||
class Worker(QObject):
|
||||
|
||||
finished = Signal()
|
||||
increment = Signal(int)
|
||||
finished = Signal()
|
||||
increment = Signal(int)
|
||||
|
||||
def __init__(self, argument):
|
||||
super().__init__()
|
||||
self.argument = argument
|
||||
def __init__(self, argument):
|
||||
super().__init__()
|
||||
self.argument = argument
|
||||
|
||||
@Slot()
|
||||
def work(self):
|
||||
# some long running task...
|
||||
import time
|
||||
for i in range(10):
|
||||
time.sleep(1)
|
||||
self.increment.emit(i)
|
||||
self.finished.emit()
|
||||
|
||||
worker, thread = new_worker_qthread(
|
||||
Worker,
|
||||
'argument',
|
||||
_start_thread=True,
|
||||
_connect={'increment': print},
|
||||
)
|
||||
@Slot()
|
||||
def work(self):
|
||||
# some long running task...
|
||||
import time
|
||||
for i in range(10):
|
||||
time.sleep(1)
|
||||
self.increment.emit(i)
|
||||
self.finished.emit()
|
||||
|
||||
worker, thread = new_worker_qthread(
|
||||
Worker,
|
||||
'argument',
|
||||
_start_thread=True,
|
||||
_connect={'increment': print},
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
if _connect and not isinstance(_connect, dict):
|
||||
|
@@ -103,7 +103,7 @@ class GenericSignalThrottler(QObject):
|
||||
self.timeoutChanged.emit(timeout)
|
||||
|
||||
def timerType(self) -> Qt.TimerType:
|
||||
"""Return current Qt.TimerType."""
|
||||
"""Return current `Qt.TimerType`."""
|
||||
return self._timer.timerType()
|
||||
|
||||
def setTimerType(self, timerType: Qt.TimerType) -> None:
|
||||
@@ -136,11 +136,11 @@ class GenericSignalThrottler(QObject):
|
||||
assert self._timer.isActive()
|
||||
|
||||
def cancel(self) -> None:
|
||||
""" "Cancel and pending emissions."""
|
||||
"""Cancel any pending emissions."""
|
||||
self._hasPendingEmission = False
|
||||
|
||||
def flush(self) -> None:
|
||||
""" "Force emission of any pending emissions."""
|
||||
"""Force emission of any pending emissions."""
|
||||
self._maybeEmitTriggered()
|
||||
|
||||
def _emitTriggered(self) -> None:
|
||||
|
Reference in New Issue
Block a user