|
1 | 1 | from __future__ import annotations |
| 2 | +from typing import TYPE_CHECKING |
2 | 3 | from abc import ABCMeta, abstractmethod |
3 | 4 | from typing import Literal |
4 | 5 | from collections.abc import Generator, Sequence |
5 | 6 | import sys |
6 | 7 |
|
7 | 8 | from tqdm import tqdm |
8 | 9 | from tqdm.contrib import DummyTqdmFile |
| 10 | +if TYPE_CHECKING: |
| 11 | + from PySide6.QtWidgets import QProgressBar, QLabel |
9 | 12 |
|
10 | 13 |
|
11 | | -__all__ = ["Progress"] |
| 14 | +__all__ = ["Progress", "TqdmProgressImpl", "QtWidgetsProgressImpl"] |
12 | 15 |
|
13 | 16 |
|
14 | 17 | class Progress: |
@@ -234,6 +237,12 @@ def update(self, progress: Progress, count: int): |
234 | 237 |
|
235 | 238 |
|
236 | 239 | class TqdmProgressImpl(AbstractProgressImpl): |
| 240 | + """Progress implementation for terminals based on `تقدّم <https://tqdm.github.io/>`_. |
| 241 | +
|
| 242 | + This implementation is automatically registered by :meth:`glasgow.main.run_async`, but may |
| 243 | + be used explicitly if needed. |
| 244 | + """ |
| 245 | + |
237 | 246 | def __init__(self): |
238 | 247 | self._bars = dict[Progress, tqdm]() |
239 | 248 |
|
@@ -269,3 +278,70 @@ def close(self, progress: Progress): |
269 | 278 |
|
270 | 279 | def update(self, progress: Progress, delta: int): |
271 | 280 | self._bars[progress].update(delta) |
| 281 | + |
| 282 | + |
| 283 | +class QtWidgetsProgressImpl(AbstractProgressImpl): |
| 284 | + """Progress implementation for Qt applications based on :mod:`~PySide6.QtWidgets`. |
| 285 | +
|
| 286 | + This implementation must be registered manually. It requires |
| 287 | + a :class:`~PySide6.QtWidgets.QProgressBar` and/or |
| 288 | + a :class:`~PySide6.QtWidgets.QLabel` to be provided; |
| 289 | + these widgets will be updated to display progress. For example: |
| 290 | +
|
| 291 | + .. code:: py |
| 292 | +
|
| 293 | + class MainWindow(QMainWindow, Ui_MainWindow): |
| 294 | + def __init__(self): |
| 295 | + super().__init__() |
| 296 | +
|
| 297 | + self.setupUi(self) |
| 298 | + QtWidgetsProgressImpl(self.progressBar, self.statusLabel).register() |
| 299 | +
|
| 300 | + ... |
| 301 | +
|
| 302 | + .. note:: |
| 303 | +
|
| 304 | + The limitation of this implementation is that it cannot display multiple progress bars. |
| 305 | + The last opened progress indicator is always used when updating progress information. |
| 306 | + """ |
| 307 | + |
| 308 | + def __init__(self, progressBar: QProgressBar | None, statusLabel: QLabel | None): |
| 309 | + self.progressBar = progressBar |
| 310 | + self.statusLabel = statusLabel |
| 311 | + |
| 312 | + self._bars = list[Progress]() |
| 313 | + |
| 314 | + def register(self): |
| 315 | + self._orig_impl, Progress._impl = Progress._impl, self |
| 316 | + |
| 317 | + def unregister(self): |
| 318 | + Progress._impl = self._orig_impl |
| 319 | + |
| 320 | + def open(self, progress: Progress): |
| 321 | + self._bars.append(progress) |
| 322 | + self._refresh() |
| 323 | + |
| 324 | + def close(self, progress: Progress): |
| 325 | + self._bars.remove(progress) |
| 326 | + self._refresh() |
| 327 | + |
| 328 | + def update(self, progress: Progress, delta: int): |
| 329 | + self._refresh() |
| 330 | + |
| 331 | + def _refresh(self): |
| 332 | + if self._bars: |
| 333 | + progress = self._bars[-1] |
| 334 | + if self.progressBar is not None: |
| 335 | + # From QProgressBar documentation: |
| 336 | + # > If minimum and maximum both are set to 0, the bar shows a busy indicator instead |
| 337 | + # > of a percentage of steps. |
| 338 | + self.progressBar.setRange(0, progress.total or 0) |
| 339 | + self.progressBar.setValue(progress.done) |
| 340 | + if self.statusLabel is not None: |
| 341 | + statusText = f"{progress.action} [{progress.done}" |
| 342 | + if progress.total is not None: |
| 343 | + statusText += f"/{progress.total}" |
| 344 | + if progress.item is not None: |
| 345 | + statusText += f" {progress.item}" |
| 346 | + statusText += "]" |
| 347 | + self.statusLabel.setText(statusText) |
0 commit comments