Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-superqt for openSUSE:Factory checked in at 2025-03-12 15:27:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-superqt (Old) and /work/SRC/openSUSE:Factory/.python-superqt.new.19136 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-superqt" Wed Mar 12 15:27:37 2025 rev:3 rq:1252357 version:0.7.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-superqt/python-superqt.changes 2024-12-02 16:59:20.473721958 +0100 +++ /work/SRC/openSUSE:Factory/.python-superqt.new.19136/python-superqt.changes 2025-03-12 15:27:41.415398084 +0100 @@ -1,0 +2,15 @@ +Tue Mar 11 12:35:39 UTC 2025 - Markéta Machová <[email protected]> + +- Update to 0.7.1 + * fix: minimum size hint for QElidingLabel + * fix: End painter when drawing colormap + * build: drop py38 + * fix: KeyError in CodeSyntaxHighlight + * build: support py313 + * feat: allow chaining of QIconifyIcon.addKey + * feat: Improve CodeSyntaxHighlight object + * feat: add QFlowLayout, for variable width widgets + * Lazy-import pyconify +- Skip testing on pyside2, broken with python313 + +------------------------------------------------------------------- Old: ---- superqt-0.6.7.tar.gz New: ---- superqt-0.7.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-superqt.spec ++++++ --- /var/tmp/diff_new_pack.E7fBdw/_old 2025-03-12 15:27:42.523444490 +0100 +++ /var/tmp/diff_new_pack.E7fBdw/_new 2025-03-12 15:27:42.527444657 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-superqt # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-superqt -Version: 0.6.7 +Version: 0.7.1 Release: 0 Summary: Missing widgets and components for PyQt/PySide License: BSD-3-Clause @@ -65,6 +65,8 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} %check +# test_iconify needs an optional module pyconify, which is not in the distribution +rm tests/test_iconify.py # skip flaky tests marked @skip_on_ci by upstream export CI=1 for PYTEST_QT_API in pyqt5 pyqt6; do @@ -72,7 +74,9 @@ %pytest done # The pysides are only for the primary python -for PYTEST_QT_API in pyside2 pyside6; do +# pyside2 is currently broken on python313 +# for PYTEST_QT_API in pyside2 pyside6; do +for PYTEST_QT_API in pyside6; do export PYTEST_QT_API %python3_pytest done ++++++ superqt-0.6.7.tar.gz -> superqt-0.7.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/CHANGELOG.md new/superqt-0.7.1/CHANGELOG.md --- old/superqt-0.6.7/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,53 @@ # Changelog +## [v0.7.1](https://github.com/pyapp-kit/superqt/tree/v0.7.1) (2025-01-05) + +[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.7.0...v0.7.1) + +**Implemented enhancements:** + +- feat: add QFlowLayout, for variable width widgets [\#271](https://github.com/pyapp-kit/superqt/pull/271) ([tlambert03](https://github.com/tlambert03)) +- feat: Improve CodeSyntaxHighlight object [\#268](https://github.com/pyapp-kit/superqt/pull/268) ([tlambert03](https://github.com/tlambert03)) +- feat: allow chaining of QIconifyIcon.addKey [\#267](https://github.com/pyapp-kit/superqt/pull/267) ([tlambert03](https://github.com/tlambert03)) + +**Fixed bugs:** + +- fix: better warning for download error [\#266](https://github.com/pyapp-kit/superqt/pull/266) ([tlambert03](https://github.com/tlambert03)) + +**Merged pull requests:** + +- Lazy-import `pyconify` [\#270](https://github.com/pyapp-kit/superqt/pull/270) ([hanjinliu](https://github.com/hanjinliu)) + +## [v0.7.0](https://github.com/pyapp-kit/superqt/tree/v0.7.0) (2024-12-14) + +[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.6.8...v0.7.0) + +**Fixed bugs:** + +- fix: End painter when drawing colormap [\#262](https://github.com/pyapp-kit/superqt/pull/262) ([gselzer](https://github.com/gselzer)) +- fix: minimum size hint for QElidingLabel [\#260](https://github.com/pyapp-kit/superqt/pull/260) ([gselzer](https://github.com/gselzer)) +- fix: KeyError in CodeSyntaxHighlight [\#258](https://github.com/pyapp-kit/superqt/pull/258) ([hanjinliu](https://github.com/hanjinliu)) + +**Refactors:** + +- chore: Revert "remove stylesheet on sliderLabel \(\#254\)" [\#265](https://github.com/pyapp-kit/superqt/pull/265) ([tlambert03](https://github.com/tlambert03)) +- refactor: remove stylesheet on sliderLabel [\#254](https://github.com/pyapp-kit/superqt/pull/254) ([tlambert03](https://github.com/tlambert03)) + +**Merged pull requests:** + +- build: support py313 [\#264](https://github.com/pyapp-kit/superqt/pull/264) ([tlambert03](https://github.com/tlambert03)) +- build: drop py38 [\#263](https://github.com/pyapp-kit/superqt/pull/263) ([tlambert03](https://github.com/tlambert03)) +- ci: \[pre-commit.ci\] autoupdate [\#257](https://github.com/pyapp-kit/superqt/pull/257) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) +- ci: \[pre-commit.ci\] autoupdate [\#253](https://github.com/pyapp-kit/superqt/pull/253) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) + +## [v0.6.8](https://github.com/pyapp-kit/superqt/tree/v0.6.8) (2024-06-15) + +[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.6.7...v0.6.8) + +**Implemented enhancements:** + +- feat: graceful offline fallback for qiconify [\#251](https://github.com/pyapp-kit/superqt/pull/251) ([tlambert03](https://github.com/tlambert03)) + ## [v0.6.7](https://github.com/pyapp-kit/superqt/tree/v0.6.7) (2024-06-07) [Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.6.6...v0.6.7) @@ -464,13 +512,21 @@ ## [v0.2.1](https://github.com/pyapp-kit/superqt/tree/v0.2.1) (2021-07-10) -[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.2.0...v0.2.1) +[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.2.0rc1...v0.2.1) **Fixed bugs:** - Fix QLabeledRangeSlider API \(fix slider proxy\) [\#10](https://github.com/pyapp-kit/superqt/pull/10) ([tlambert03](https://github.com/tlambert03)) - Fix range slider with negative min range [\#9](https://github.com/pyapp-kit/superqt/pull/9) ([tlambert03](https://github.com/tlambert03)) +## [v0.2.0rc1](https://github.com/pyapp-kit/superqt/tree/v0.2.0rc1) (2021-06-26) + +[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.2.0rc0...v0.2.0rc1) + +## [v0.2.0rc0](https://github.com/pyapp-kit/superqt/tree/v0.2.0rc0) (2021-06-26) + +[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.2.0...v0.2.0rc0) + \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/PKG-INFO new/superqt-0.7.1/PKG-INFO --- old/superqt-0.6.7/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 2.3 +Metadata-Version: 2.4 Name: superqt -Version: 0.6.7 +Version: 0.7.1 Summary: Missing widgets and components for PyQt/PySide Project-URL: Documentation, https://pyapp-kit.github.io/superqt/ Project-URL: Source, https://github.com/pyapp-kit/superqt @@ -17,15 +17,15 @@ Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Desktop Environment Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: Software Development :: Widget Sets -Requires-Python: >=3.8 +Requires-Python: >=3.9 Requires-Dist: pygments>=2.4.0 Requires-Dist: qtpy>=1.1.0 Requires-Dist: typing-extensions!=3.10.0.0,>=3.7.4.3 @@ -63,7 +63,7 @@ Provides-Extra: pyside2 Requires-Dist: pyside2; extra == 'pyside2' Provides-Extra: pyside6 -Requires-Dist: pyside6!=6.5.0,!=6.5.1,!=6.6.2; extra == 'pyside6' +Requires-Dist: pyside6!=6.5.0,!=6.5.1,!=6.6.2,<6.8; extra == 'pyside6' Provides-Extra: quantity Requires-Dist: pint; extra == 'quantity' Provides-Extra: test @@ -93,7 +93,7 @@ Components are tested on: - macOS, Windows, & Linux -- Python 3.8 and above +- Python 3.9 and above - PyQt5 (5.11 and above) & PyQt6 - PySide2 (5.11 and above) & PySide6 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/README.md new/superqt-0.7.1/README.md --- old/superqt-0.6.7/README.md 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/README.md 2020-02-02 01:00:00.000000000 +0100 @@ -15,7 +15,7 @@ Components are tested on: - macOS, Windows, & Linux -- Python 3.8 and above +- Python 3.9 and above - PyQt5 (5.11 and above) & PyQt6 - PySide2 (5.11 and above) & PySide6 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/pyproject.toml new/superqt-0.7.1/pyproject.toml --- old/superqt-0.6.7/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -8,7 +8,7 @@ name = "superqt" description = "Missing widgets and components for PyQt/PySide" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "BSD 3-Clause License" } authors = [{ email = "[email protected]", name = "Talley Lambert" }] keywords = [ @@ -28,11 +28,11 @@ "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Desktop Environment", "Topic :: Software Development :: User Interfaces", "Topic :: Software Development :: Widget Sets", @@ -41,13 +41,21 @@ dependencies = [ "pygments>=2.4.0", "qtpy>=1.1.0", - "typing-extensions >=3.7.4.3,!=3.10.0.0", + "typing-extensions >=3.7.4.3,!=3.10.0.0", # however, pint requires >4.5.0 ] # extras # https://peps.python.org/pep-0621/#dependencies-optional-dependencies [project.optional-dependencies] -test = ["pint", "pytest", "pytest-cov", "pytest-qt", "numpy", "cmap", "pyconify"] +test = [ + "pint", + "pytest", + "pytest-cov", + "pytest-qt", + "numpy", + "cmap", + "pyconify", +] dev = [ "ipython", "ruff", @@ -58,7 +66,13 @@ "rich", "types-Pygments", ] -docs = ["mkdocs-macros-plugin", "mkdocs-material", "mkdocstrings[python]", "pint", "cmap"] +docs = [ + "mkdocs-macros-plugin", + "mkdocs-material", + "mkdocstrings[python]", + "pint", + "cmap", +] quantity = ["pint"] cmap = ["cmap >=0.1.1"] pyside2 = ["pyside2"] @@ -66,7 +80,7 @@ # https://github.com/pyapp-kit/superqt/pull/177 # https://github.com/pyapp-kit/superqt/pull/164 # https://bugreports.qt.io/browse/PYSIDE-2627 -pyside6 = ["pyside6 !=6.5.0,!=6.5.1,!=6.6.2"] +pyside6 = ["pyside6 !=6.5.0,!=6.5.1,!=6.6.2,<6.8"] pyqt5 = ["pyqt5"] pyqt6 = ["pyqt6<6.7"] font-fa5 = ["fonticon-fontawesome5"] @@ -100,21 +114,30 @@ [[tool.hatch.envs.test.matrix]] qt = ["pyside2", "pyqt5", "pyqt5.12"] -python = ["3.8"] +python = ["3.9"] [tool.hatch.envs.test.overrides] matrix.qt.extra-dependencies = [ - {value = "pyside2", if = ["pyside2"]}, - {value = "pyside6", if = ["pyside6"]}, - {value = "pyqt5", if = ["pyqt5"]}, - {value = "pyqt6", if = ["pyqt6"]}, - {value = "pyqt5==5.12", if = ["pyqt5.12"]}, + { value = "pyside2", if = [ + "pyside2", + ] }, + { value = "pyside6", if = [ + "pyside6", + ] }, + { value = "pyqt5", if = [ + "pyqt5", + ] }, + { value = "pyqt6", if = [ + "pyqt6", + ] }, + { value = "pyqt5==5.12", if = [ + "pyqt5.12", + ] }, ] -# https://github.com/charliermarsh/ruff [tool.ruff] line-length = 88 -target-version = "py38" +target-version = "py39" src = ["src", "tests"] # https://docs.astral.sh/ruff/rules @@ -132,7 +155,7 @@ "B", # flake8-bugbear "A001", # flake8-builtins "RUF", # ruff-specific rules - "TCH", # flake8-type-checking + "TC", # flake8-type-checking "TID", # flake8-tidy-imports ] ignore = [ @@ -159,6 +182,7 @@ "ignore:QPixmapCache.find:DeprecationWarning:", "ignore:SelectableGroups dict interface:DeprecationWarning", "ignore:The distutils package is deprecated:DeprecationWarning", + "ignore:.*Skipping callback call set_result", ] # https://mypy.readthedocs.io/en/stable/config_file.html diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/__init__.py new/superqt-0.7.1/src/superqt/__init__.py --- old/superqt-0.6.7/src/superqt/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -11,7 +11,6 @@ from .collapsible import QCollapsible from .combobox import QColorComboBox, QEnumComboBox, QSearchableComboBox from .elidable import QElidingLabel, QElidingLineEdit -from .iconify import QIconifyIcon from .selection import QSearchableListWidget, QSearchableTreeWidget from .sliders import ( QDoubleRangeSlider, @@ -23,11 +22,14 @@ QRangeSlider, ) from .spinbox import QLargeIntSpinBox -from .utils import QMessageHandler, ensure_main_thread, ensure_object_thread +from .utils import ( + QFlowLayout, + QMessageHandler, + ensure_main_thread, + ensure_object_thread, +) __all__ = [ - "ensure_main_thread", - "ensure_object_thread", "QCollapsible", "QColorComboBox", "QColormapComboBox", @@ -36,8 +38,9 @@ "QElidingLabel", "QElidingLineEdit", "QEnumComboBox", - "QLabeledDoubleRangeSlider", + "QFlowLayout", "QIconifyIcon", + "QLabeledDoubleRangeSlider", "QLabeledDoubleSlider", "QLabeledRangeSlider", "QLabeledSlider", @@ -48,20 +51,27 @@ "QSearchableComboBox", "QSearchableListWidget", "QSearchableTreeWidget", + "ensure_main_thread", + "ensure_object_thread", ] if TYPE_CHECKING: - from .combobox import QColormapComboBox # noqa: TCH004 - from .spinbox._quantity import QQuantity # noqa: TCH004 + from .combobox import QColormapComboBox # noqa: TC004 + from .iconify import QIconifyIcon # noqa: TC004 + from .spinbox._quantity import QQuantity # noqa: TC004 def __getattr__(name: str) -> Any: - if name == "QQuantity": - from .spinbox._quantity import QQuantity - - return QQuantity if name == "QColormapComboBox": from .cmap import QColormapComboBox return QColormapComboBox + if name == "QIconifyIcon": + from .iconify import QIconifyIcon + + return QIconifyIcon + if name == "QQuantity": + from .spinbox._quantity import QQuantity + + return QQuantity raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/cmap/__init__.py new/superqt-0.7.1/src/superqt/cmap/__init__.py --- old/superqt-0.6.7/src/superqt/cmap/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/cmap/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -15,9 +15,9 @@ from ._cmap_utils import draw_colormap __all__ = [ - "QColormapItemDelegate", - "draw_colormap", - "QColormapLineEdit", "CmapCatalogComboBox", "QColormapComboBox", + "QColormapItemDelegate", + "QColormapLineEdit", + "draw_colormap", ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/cmap/_catalog_combo.py new/superqt-0.7.1/src/superqt/cmap/_catalog_combo.py --- old/superqt-0.6.7/src/superqt/cmap/_catalog_combo.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/cmap/_catalog_combo.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Container +from typing import TYPE_CHECKING from cmap import Colormap from qtpy.QtCore import Qt, Signal @@ -11,6 +11,8 @@ from ._cmap_utils import try_cast_colormap if TYPE_CHECKING: + from collections.abc import Container + from cmap._catalog import Category, Interpolation from qtpy.QtGui import QKeyEvent diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/cmap/_cmap_combo.py new/superqt-0.7.1/src/superqt/cmap/_cmap_combo.py --- old/superqt-0.6.7/src/superqt/cmap/_cmap_combo.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/cmap/_cmap_combo.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Sequence +from typing import TYPE_CHECKING, Any from cmap import Colormap from qtpy.QtCore import Qt, Signal @@ -23,6 +23,8 @@ from ._cmap_utils import try_cast_colormap if TYPE_CHECKING: + from collections.abc import Sequence + from cmap._colormap import ColorStopsLike diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/cmap/_cmap_utils.py new/superqt-0.7.1/src/superqt/cmap/_cmap_utils.py --- old/superqt-0.6.7/src/superqt/cmap/_cmap_utils.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/cmap/_cmap_utils.py 2020-02-02 01:00:00.000000000 +0100 @@ -121,6 +121,10 @@ painter.setBrush(gradient) painter.drawRect(rect) + # If we created a new Painter, free its resources + if isinstance(painter_or_device, QPaintDevice): + painter.end() + def _draw_checkerboard( painter: QPainter, rect: QRect | QRectF, checker_size: int diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/combobox/__init__.py new/superqt-0.7.1/src/superqt/combobox/__init__.py --- old/superqt-0.6.7/src/superqt/combobox/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/combobox/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -13,7 +13,7 @@ if TYPE_CHECKING: - from superqt.cmap import QColormapComboBox # noqa: TCH004 + from superqt.cmap import QColormapComboBox # noqa: TC004 def __getattr__(name: str) -> Any: # pragma: no cover diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/combobox/_color_combobox.py new/superqt-0.7.1/src/superqt/combobox/_color_combobox.py --- old/superqt-0.6.7/src/superqt/combobox/_color_combobox.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/combobox/_color_combobox.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,7 +3,7 @@ import warnings from contextlib import suppress from enum import IntEnum, auto -from typing import Any, Literal, Sequence, cast +from typing import TYPE_CHECKING, Any, Literal, cast from qtpy.QtCore import QModelIndex, QPersistentModelIndex, QRect, QSize, Qt, Signal from qtpy.QtGui import QColor, QPainter @@ -19,6 +19,9 @@ from superqt.utils import signals_blocked +if TYPE_CHECKING: + from collections.abc import Sequence + _NAME_MAP = {QColor(x).name(): x for x in QColor.colorNames()} COLOR_ROLE = Qt.ItemDataRole.BackgroundRole diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/combobox/_enum_combobox.py new/superqt-0.7.1/src/superqt/combobox/_enum_combobox.py --- old/superqt-0.6.7/src/superqt/combobox/_enum_combobox.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/combobox/_enum_combobox.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,7 +3,7 @@ from functools import reduce from itertools import combinations from operator import or_ -from typing import Optional, Tuple, TypeVar +from typing import Optional, TypeVar from qtpy.QtCore import Signal from qtpy.QtWidgets import QComboBox @@ -47,7 +47,7 @@ return name -def _get_name_with_value(enum_value: Enum) -> Tuple[str, Enum]: +def _get_name_with_value(enum_value: Enum) -> tuple[str, Enum]: return _get_name(enum_value), enum_value diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/elidable/_eliding.py new/superqt-0.7.1/src/superqt/elidable/_eliding.py --- old/superqt-0.6.7/src/superqt/elidable/_eliding.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/elidable/_eliding.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,3 @@ -from typing import List - from qtpy.QtCore import Qt from qtpy.QtGui import QFont, QFontMetrics, QTextLayout @@ -36,7 +34,7 @@ self._ellipses_width = width @staticmethod - def wrapText(text, width, font=None) -> List[str]: + def wrapText(text, width, font=None) -> list[str]: """Returns `text`, split as it would be wrapped for `width`, given `font`. Static method. @@ -74,5 +72,5 @@ # join them return "".join(text[:nlines] + [last_line]) - def _wrappedText(self) -> List[str]: + def _wrappedText(self) -> list[str]: return _GenericEliding.wrapText(self._text, self.width(), self.font()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/elidable/_eliding_label.py new/superqt-0.7.1/src/superqt/elidable/_eliding_label.py --- old/superqt-0.6.7/src/superqt/elidable/_eliding_label.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/elidable/_eliding_label.py 2020-02-02 01:00:00.000000000 +0100 @@ -73,3 +73,10 @@ flags = int(self.alignment() | Qt.TextFlag.TextWordWrap) r = fm.boundingRect(QRect(QPoint(0, 0), self.size()), flags, self._text) return QSize(self.width(), r.height()) + + def minimumSizeHint(self) -> QSize: + # The smallest that self._elidedText can be is just the ellipsis. + fm = QFontMetrics(self.font()) + flags = int(self.alignment() | Qt.TextFlag.TextWordWrap) + r = fm.boundingRect(QRect(QPoint(0, 0), self.size()), flags, "...") + return QSize(r.width(), r.height()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/fonticon/__init__.py new/superqt-0.7.1/src/superqt/fonticon/__init__.py --- old/superqt-0.6.7/src/superqt/fonticon/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/fonticon/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,16 +1,16 @@ from __future__ import annotations __all__ = [ - "addFont", - "Animation", "ENTRY_POINT", - "font", - "icon", + "Animation", "IconFont", "IconFontMeta", "IconOpts", - "pulse", "QIconifyIcon", + "addFont", + "font", + "icon", + "pulse", "setTextIcon", "spin", ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/fonticon/_iconfont.py new/superqt-0.7.1/src/superqt/fonticon/_iconfont.py --- old/superqt-0.6.7/src/superqt/fonticon/_iconfont.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/fonticon/_iconfont.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,4 +1,5 @@ -from typing import Mapping, Type, Union +from collections.abc import Mapping +from typing import Union FONTFILE_ATTR = "__font_file__" @@ -69,7 +70,7 @@ __font_file__ = "..." -def namespace2font(namespace: Union[Mapping, Type], name: str) -> Type[IconFont]: +def namespace2font(namespace: Union[Mapping, type], name: str) -> type[IconFont]: """Convenience to convert a namespace (class, module, dict) into an IconFont.""" if isinstance(namespace, type): if not isinstance(getattr(namespace, FONTFILE_ATTR), str): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/fonticon/_plugins.py new/superqt-0.7.1/src/superqt/fonticon/_plugins.py --- old/superqt-0.6.7/src/superqt/fonticon/_plugins.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/fonticon/_plugins.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,5 @@ import contextlib -from typing import ClassVar, Dict, List, Set, Tuple +from typing import ClassVar from ._iconfont import IconFontMeta, namespace2font @@ -11,9 +11,9 @@ class FontIconManager: ENTRY_POINT: ClassVar[str] = "superqt.fonticon" - _PLUGINS: ClassVar[Dict[str, EntryPoint]] = {} - _LOADED: ClassVar[Dict[str, IconFontMeta]] = {} - _BLOCKED: ClassVar[Set[EntryPoint]] = set() + _PLUGINS: ClassVar[dict[str, EntryPoint]] = {} + _LOADED: ClassVar[dict[str, IconFontMeta]] = {} + _BLOCKED: ClassVar[set[EntryPoint]] = set() def _discover_fonts(self) -> None: self._PLUGINS.clear() @@ -86,15 +86,15 @@ get_font_class = _manager._get_font_class -def discover() -> Tuple[str]: +def discover() -> tuple[str]: _manager._discover_fonts() -def available() -> Tuple[str]: +def available() -> tuple[str]: return tuple(_manager._PLUGINS) -def loaded(load_all=False) -> Dict[str, List[str]]: +def loaded(load_all=False) -> dict[str, list[str]]: if load_all: discover() for x in available(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/fonticon/_qfont_icon.py new/superqt-0.7.1/src/superqt/fonticon/_qfont_icon.py --- old/superqt-0.6.7/src/superqt/fonticon/_qfont_icon.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/fonticon/_qfont_icon.py 2020-02-02 01:00:00.000000000 +0100 @@ -2,9 +2,10 @@ import warnings from collections import abc, defaultdict +from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING, ClassVar, DefaultDict, Sequence, Tuple, Union, cast +from typing import TYPE_CHECKING, ClassVar, Union, cast from qtpy import QT_VERSION from qtpy.QtCore import QObject, QPoint, QRect, QSize, Qt @@ -47,8 +48,8 @@ int, str, Qt.GlobalColor, - Tuple[int, int, int, int], - Tuple[int, int, int], + tuple[int, int, int, int], + tuple[int, int, int], None, ] @@ -159,7 +160,7 @@ def __init__(self, options: _IconOptions): super().__init__() self._opts: defaultdict[QIcon.State, dict[QIcon.Mode, _IconOptions | None]] = ( - DefaultDict(dict) + defaultdict(dict) ) self._opts[QIcon.State.Off][QIcon.Mode.Normal] = options self.update_hash() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/iconify/__init__.py new/superqt-0.7.1/src/superqt/iconify/__init__.py --- old/superqt-0.6.7/src/superqt/iconify/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/iconify/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,9 +1,20 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING -from qtpy.QtCore import QSize -from qtpy.QtGui import QIcon +from qtpy.QtCore import QSize, Qt +from qtpy.QtGui import QIcon, QPainter, QPixmap +from qtpy.QtWidgets import QApplication + +try: + from pyconify import svg_path +except ModuleNotFoundError: # pragma: no cover + raise ModuleNotFoundError( + "pyconify is required to use QIconifyIcon. " + "Please install it with `pip install pyconify` or use the " + "`pip install superqt[iconify]` extra." + ) from None if TYPE_CHECKING: from typing import Literal @@ -11,10 +22,7 @@ Flip = Literal["horizontal", "vertical", "horizontal,vertical"] Rotation = Literal["90", "180", "270", 90, 180, 270, "-90", 1, 2, 3] -try: - from pyconify import svg_path -except ModuleNotFoundError: # pragma: no cover - svg_path = None +__all__ = ["QIconifyIcon"] class QIconifyIcon(QIcon): @@ -72,14 +80,9 @@ rotate: Rotation | None = None, dir: str | None = None, ): - if svg_path is None: # pragma: no cover - raise ModuleNotFoundError( - "pyconify is required to use QIconifyIcon. " - "Please install it with `pip install pyconify` or use the " - "`pip install superqt[iconify]` extra." - ) super().__init__() - self.addKey(*key, color=color, flip=flip, rotate=rotate, dir=dir) + if key: + self.addKey(*key, color=color, flip=flip, rotate=rotate, dir=dir) def addKey( self, @@ -91,7 +94,7 @@ size: QSize | None = None, mode: QIcon.Mode = QIcon.Mode.Normal, state: QIcon.State = QIcon.State.Off, - ) -> None: + ) -> QIconifyIcon: """Add an icon to this QIcon. This is a variant of `QIcon.addFile` that uses an iconify icon keys and @@ -121,6 +124,33 @@ Mode specified for the icon, passed to `QIcon.addFile`. state : QIcon.State, optional State specified for the icon, passed to `QIcon.addFile`. + + Returns + ------- + QIconifyIcon + This QIconifyIcon instance, for chaining. """ - path = svg_path(*key, color=color, flip=flip, rotate=rotate, dir=dir) - self.addFile(str(path), size or QSize(), mode, state) + try: + path = svg_path(*key, color=color, flip=flip, rotate=rotate, dir=dir) + except OSError as e: + warnings.warn( + f"Error fetching icon: {e}.\nIcon {key} not cached. Using fallback.", + stacklevel=2, + ) + self._draw_text_fallback(key) + else: + self.addFile(str(path), size or QSize(), mode, state) + + return self + + def _draw_text_fallback(self, key: tuple[str, ...]) -> None: + if style := QApplication.style(): + pixmap = style.standardPixmap(style.StandardPixmap.SP_MessageBoxQuestion) + else: + pixmap = QPixmap(18, 18) + pixmap.fill(Qt.GlobalColor.transparent) + painter = QPainter(pixmap) + painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "?") + painter.end() + + self.addPixmap(pixmap) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/selection/_searchable_tree_widget.py new/superqt-0.7.1/src/superqt/selection/_searchable_tree_widget.py --- old/superqt-0.6.7/src/superqt/selection/_searchable_tree_widget.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/selection/_searchable_tree_widget.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,6 @@ import logging -from typing import Any, Iterable, Mapping +from collections.abc import Iterable, Mapping +from typing import Any from qtpy.QtCore import QRegularExpression from qtpy.QtWidgets import QLineEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/sliders/__init__.py new/superqt-0.7.1/src/superqt/sliders/__init__.py --- old/superqt-0.6.7/src/superqt/sliders/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/sliders/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -8,6 +8,7 @@ from ._sliders import QDoubleRangeSlider, QDoubleSlider, QRangeSlider __all__ = [ + "MONTEREY_SLIDER_STYLES_FIX", "QDoubleRangeSlider", "QDoubleSlider", "QLabeledDoubleRangeSlider", @@ -15,5 +16,4 @@ "QLabeledRangeSlider", "QLabeledSlider", "QRangeSlider", - "MONTEREY_SLIDER_STYLES_FIX", ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/sliders/_generic_range_slider.py new/superqt-0.7.1/src/superqt/sliders/_generic_range_slider.py --- old/superqt-0.6.7/src/superqt/sliders/_generic_range_slider.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/sliders/_generic_range_slider.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,4 +1,5 @@ -from typing import List, Optional, Sequence, Tuple, TypeVar, Union +from collections.abc import Sequence +from typing import Optional, TypeVar, Union from qtpy import QtGui from qtpy.QtCore import Property, QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal @@ -42,11 +43,11 @@ self.valueChanged = self._valuesChanged self.sliderMoved = self._slidersMoved # list of values - self._value: List[_T] = [20, 80] + self._value: list[_T] = [20, 80] # list of current positions of each handle. same length as _value # If tracking is enabled (the default) this will be identical to _value - self._position: List[_T] = [20, 80] + self._position: list[_T] = [20, 80] # which handle is being pressed/hovered self._pressedIndex = 0 @@ -113,7 +114,7 @@ # ############### QtOverrides ####################### - def value(self) -> Tuple[_T, ...]: + def value(self) -> tuple[_T, ...]: """Get current value of the widget as a tuple of integers.""" return tuple(self._value) @@ -332,7 +333,7 @@ # NOTE: this is very much tied to mousepress... not a generic "get control" def _getControlAtPos( self, pos: QPoint, opt: Optional[QStyleOptionSlider] = None - ) -> Tuple[QStyle.SubControl, int]: + ) -> tuple[QStyle.SubControl, int]: """Update self._pressedControl based on ev.pos().""" opt = opt or self._styleOption diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/sliders/_labeled.py new/superqt-0.7.1/src/superqt/sliders/_labeled.py --- old/superqt-0.6.7/src/superqt/sliders/_labeled.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/sliders/_labeled.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,7 +3,7 @@ import contextlib from enum import IntEnum, IntFlag, auto from functools import partial -from typing import Any, Iterable, overload +from typing import TYPE_CHECKING, Any, overload from qtpy import QtGui from qtpy.QtCore import Property, QPoint, QSize, Qt, Signal @@ -25,6 +25,9 @@ from ._sliders import QDoubleRangeSlider, QDoubleSlider, QRangeSlider +if TYPE_CHECKING: + from collections.abc import Iterable + class LabelPosition(IntEnum): NoLabel = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/utils/__init__.py new/superqt-0.7.1/src/superqt/utils/__init__.py --- old/superqt-0.6.7/src/superqt/utils/__init__.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/utils/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,32 +1,34 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from superqt.cmap import draw_colormap # noqa: TCH004 + from superqt.cmap import draw_colormap # noqa: TC004 __all__ = ( "CodeSyntaxHighlight", + "FunctionWorker", + "GeneratorWorker", + "QFlowLayout", + "QMessageHandler", + "QSignalDebouncer", + "QSignalThrottler", + "WorkerBase", "create_worker", - "qimage_to_array", "draw_colormap", "ensure_main_thread", "ensure_object_thread", "exceptions_as_dialog", - "FunctionWorker", - "GeneratorWorker", "new_worker_qthread", "qdebounced", - "QMessageHandler", - "QSignalDebouncer", - "QSignalThrottler", + "qimage_to_array", "qthrottled", "signals_blocked", "thread_worker", - "WorkerBase", ) from ._code_syntax_highlight import CodeSyntaxHighlight from ._ensure_thread import ensure_main_thread, ensure_object_thread from ._errormsg_context import exceptions_as_dialog +from ._flow_layout import QFlowLayout from ._img_utils import qimage_to_array from ._message_handler import QMessageHandler from ._misc import signals_blocked diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/utils/_code_syntax_highlight.py new/superqt-0.7.1/src/superqt/utils/_code_syntax_highlight.py --- old/superqt-0.6.7/src/superqt/utils/_code_syntax_highlight.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/utils/_code_syntax_highlight.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,75 +1,268 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, cast + from pygments import highlight from pygments.formatter import Formatter from pygments.lexers import find_lexer_class, get_lexer_by_name from pygments.util import ClassNotFound -from qtpy import QtGui +from qtpy.QtGui import ( + QColor, + QFont, + QPalette, + QSyntaxHighlighter, + QTextCharFormat, + QTextDocument, +) + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + from typing import Literal, TypeAlias, TypedDict, Unpack + + import pygments.style + from pygments.style import _StyleDict + from pygments.token import _TokenType + from qtpy.QtCore import QObject + + class SupportsDocumentAndPalette(QObject): + def document(self) -> QTextDocument | None: ... + def palette(self) -> QPalette: ... + def setPalette(self, palette: QPalette) -> None: ... + + KnownStyle: TypeAlias = Literal[ + "abap", + "algol", + "algol_nu", + "arduino", + "autumn", + "bw", + "borland", + "coffee", + "colorful", + "default", + "dracula", + "emacs", + "friendly_grayscale", + "friendly", + "fruity", + "github-dark", + "gruvbox-dark", + "gruvbox-light", + "igor", + "inkpot", + "lightbulb", + "lilypond", + "lovelace", + "manni", + "material", + "monokai", + "murphy", + "native", + "nord-darker", + "nord", + "one-dark", + "paraiso-dark", + "paraiso-light", + "pastie", + "perldoc", + "rainbow_dash", + "rrt", + "sas", + "solarized-dark", + "solarized-light", + "staroffice", + "stata-dark", + "stata-light", + "tango", + "trac", + "vim", + "vs", + "xcode", + "zenburn", + ] + + class FormatterKwargs(TypedDict, total=False): + style: KnownStyle | str + full: bool + title: str + encoding: str + outencoding: str + + +MONO_FAMILIES = [ + "Menlo", + "Courier New", + "Courier", + "Monaco", + "Consolas", + "Andale Mono", + "Source Code Pro", + "Ubuntu Mono", + "monospace", +] + # inspired by https://github.com/Vector35/snippets/blob/master/QCodeEditor.py # (MIT license) and # https://pygments.org/docs/formatterdevelopment/#html-3-2-formatter +def get_text_char_format(style: _StyleDict) -> QTextCharFormat: + """Return a QTextCharFormat object based on the given Pygments `_StyleDict`. - -def get_text_char_format(style): - text_char_format = QtGui.QTextCharFormat() - if hasattr(text_char_format, "setFontFamilies"): - text_char_format.setFontFamilies(["monospace"]) - else: - text_char_format.setFontFamily("monospace") - if style.get("color"): - text_char_format.setForeground(QtGui.QColor(f"#{style['color']}")) - - if style.get("bgcolor"): - text_char_format.setBackground(QtGui.QColor(style["bgcolor"])) - + style will likely have these keys: + - color: str | None + - bold: bool + - italic: bool + - underline: bool + - bgcolor: str | None + - border: str | None + - roman: bool | None + - sans: bool | None + - mono: bool | None + - ansicolor: str | None + - bgansicolor: str | None + """ + text_char_format = QTextCharFormat() + if style.get("mono"): + text_char_format.setFontFamilies(MONO_FAMILIES) + if color := style.get("color"): + text_char_format.setForeground(QColor(f"#{color}")) + if bgcolor := style.get("bgcolor"): + text_char_format.setBackground(QColor(f"#{bgcolor}")) if style.get("bold"): - text_char_format.setFontWeight(QtGui.QFont.Bold) + text_char_format.setFontWeight(QFont.Weight.Bold) if style.get("italic"): text_char_format.setFontItalic(True) if style.get("underline"): text_char_format.setFontUnderline(True) - - # TODO find if it is possible to support border style. - + # if style.get("border"): + # ... return text_char_format class QFormatter(Formatter): - def __init__(self, **kwargs): + def __init__(self, **kwargs: Unpack[FormatterKwargs]) -> None: super().__init__(**kwargs) - self.data = [] - self._style = {name: get_text_char_format(style) for name, style in self.style} - - def format(self, tokensource, outfile): + self.data: list[QTextCharFormat] = [] + style = cast("pygments.style.StyleMeta", self.style) + self._style: Mapping[_TokenType, QTextCharFormat] + self._style = {token: get_text_char_format(style) for token, style in style} + + def format( + self, tokensource: Sequence[tuple[_TokenType, str]], outfile: Any + ) -> None: """Format the given token stream. - `outfile` is argument from parent class, but - in Qt we do not produce string output, but QTextCharFormat, so it needs to be - collected using `self.data`. + When Qt calls the highlightBlock method on a `CodeSyntaxHighlight` object, + `highlight(text, self.lexer, self.formatter)`, which trigger pygments to call + this method. + + Normally, this method puts output into `outfile`, but in Qt we do not produce + string output; instead we collect QTextCharFormat objects in `self.data`, which + can be used to apply formatting in the `highlightBlock` method that triggered + this method. """ self.data = [] - + null = QTextCharFormat() for token, value in tokensource: - self.data.extend([self._style[token]] * len(value)) - + # using get method to workaround not defined style for plain token + # https://github.com/pygments/pygments/issues/2149 + self.data.extend([self._style.get(token, null)] * len(value)) + + +class CodeSyntaxHighlight(QSyntaxHighlighter): + """A syntax highlighter for code using Pygments. + + Parameters + ---------- + parent : QTextDocument | QObject | None + The parent object. Usually a QTextDocument. To use this class with a + QTextArea, pass in `text_area.document()`. + lang : str + The language of the code to highlight. This should be a string that + Pygments recognizes, e.g. 'python', 'pytb', 'cpp', 'java', etc. + theme : KnownStyle | str + The name of the Pygments style to use. For a complete list of available + styles, use `pygments.styles.get_all_styles()`. + + Examples + -------- + ```python + from qtpy.QtWidgets import QTextEdit + from superqt.utils import CodeSyntaxHighlight + + text_area = QTextEdit() + highlighter = CodeSyntaxHighlight(text_area.document(), "python", "monokai") + + # then manually apply the background color to the text area. + palette = text_area.palette() + bgrd_color = QColor(self._highlight.background_color) + palette.setColor(QPalette.ColorRole.Base, bgrd_color) + text_area.setPalette(palette) + ``` + """ + + def __init__( + self, + parent: SupportsDocumentAndPalette | QTextDocument | QObject | None, + lang: str, + theme: KnownStyle | str = "default", + ) -> None: + self._doc_parent: SupportsDocumentAndPalette | None = None + if ( + parent + and not isinstance(parent, QTextDocument) + and hasattr(parent, "document") + and callable(parent.document) + and isinstance(doc := parent.document(), QTextDocument) + ): + if hasattr(parent, "palette") and hasattr(parent, "setPalette"): + self._doc_parent = cast("SupportsDocumentAndPalette", parent) + parent = doc -class CodeSyntaxHighlight(QtGui.QSyntaxHighlighter): - def __init__(self, parent, lang, theme): super().__init__(parent) + self.setLanguage(lang) + self.setTheme(theme) + + def setTheme(self, theme: KnownStyle | str) -> None: + """Set the theme for the syntax highlighting. + + This should be a string that Pygments recognizes, e.g. 'monokai', 'solarized'. + Use `pygments.styles.get_all_styles()` to see a list of available styles. + """ self.formatter = QFormatter(style=theme) + if self._doc_parent is not None: + palette = self._doc_parent.palette() + bgrd = QColor(self.background_color) + palette.setColor(QPalette.ColorRole.Base, bgrd) + self._doc_parent.setPalette(palette) + + self.rehighlight() + + def setLanguage(self, lang: str) -> None: + """Set the language for the syntax highlighting. + + This should be a string that Pygments recognizes, e.g. 'python', 'pytb', 'cpp', + 'java', etc. + """ try: self.lexer = get_lexer_by_name(lang) - except ClassNotFound: - self.lexer = find_lexer_class(lang)() + except ClassNotFound as e: + if cls := find_lexer_class(lang): + self.lexer = cls() + else: + raise ValueError(f"Could not find lexer for language {lang!r}.") from e @property - def background_color(self): - return self.formatter.style.background_color + def background_color(self) -> str: + style = cast("pygments.style.StyleMeta", self.formatter.style) + return style.background_color - def highlightBlock(self, text): + def highlightBlock(self, text: str | None) -> None: # dirty, dirty hack - # The core problem is that pygemnts by default use string streams, + # The core problem is that pygments by default use string streams, # that will not handle QTextCharFormat, so we need use `data` property to # work around this. - highlight(text, self.lexer, self.formatter) - for i in range(len(text)): - self.setFormat(i, 1, self.formatter.data[i]) + if text: + highlight(text, self.lexer, self.formatter) + for i in range(len(text)): + self.setFormat(i, 1, self.formatter.data[i]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/utils/_flow_layout.py new/superqt-0.7.1/src/superqt/utils/_flow_layout.py --- old/superqt-0.6.7/src/superqt/utils/_flow_layout.py 1970-01-01 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/utils/_flow_layout.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,183 @@ +from __future__ import annotations + +from qtpy.QtCore import QPoint, QRect, QSize, Qt +from qtpy.QtWidgets import QLayout, QLayoutItem, QSizePolicy, QStyle, QWidget + + +class QFlowLayout(QLayout): + """Layout that handles different window sizes. + + The widget placement changes depending on the width of the application window. + + Code translated from C++ at: + <https://code.qt.io/cgit/qt/qtbase.git/tree/examples/widgets/layouts/flowlayout> + + described at: <https://doc.qt.io/qt-6/qtwidgets-layouts-flowlayout-example.html> + + See also: <https://doc.qt.io/qt-6/layout.html> + + Parameters + ---------- + parent : QWidget, optional + The parent widget, by default None + """ + + def __init__(self, parent: QWidget | None = None) -> None: + super().__init__(parent) + + self._item_list: list[QLayoutItem] = [] + self._h_space = -1 + self._v_space = -1 + + def __del__(self) -> None: + while item := self.takeAt(0): + del item + + def addItem(self, item: QLayoutItem | None) -> None: + """Add an item to the layout.""" + if item: + self._item_list.append(item) + + def setHorizontalSpacing(self, space: int | None) -> None: + """Set the horizontal spacing. + + If None or -1, the spacing is set to the default value based on the style + of the parent widget. + """ + self._h_space = -1 if space is None else space + + def horizontalSpacing(self) -> int: + """Return the horizontal spacing.""" + if self._h_space >= 0: + return self._h_space + return self._smartSpacing(QStyle.PixelMetric.PM_LayoutHorizontalSpacing) + + def setVerticalSpacing(self, space: int | None) -> None: + """Set the vertical spacing. + + If None or -1, the spacing is set to the default value based on the style + of the parent widget. + """ + self._v_space = -1 if space is None else space + + def verticalSpacing(self) -> int: + """Return the vertical spacing.""" + if self._v_space >= 0: + return self._v_space + return self._smartSpacing(QStyle.PixelMetric.PM_LayoutVerticalSpacing) + + def expandingDirections(self) -> Qt.Orientation: + """Return the expanding directions. + + These are the Qt::Orientations in which the layout can make use of more space + than its sizeHint(). + """ + return Qt.Orientation.Horizontal + + def hasHeightForWidth(self) -> bool: + """Return whether the layout handles height for width.""" + return True + + def heightForWidth(self, width: int) -> int: + """Return the height for a given width. + + `heightForWidth()` passes the width on to _doLayout() which in turn uses the + width as an argument for the layout rect, i.e., the bounds in which the items + are laid out. This rect does not include the layout margin(). + """ + return self._doLayout(QRect(0, 0, width, 0), True) + + def count(self) -> int: + """Return the number of items in the layout.""" + return len(self._item_list) + + def itemAt(self, index: int) -> QLayoutItem | None: + """Return the item at the given index, or None if the index is out of range.""" + try: + return self._item_list[index] + except IndexError: + return None + + def minimumSize(self) -> QSize: + """Return the minimum size of the layout.""" + size = QSize() + for item in self._item_list: + size = size.expandedTo(item.minimumSize()) + + margins = self.contentsMargins() + size += QSize( + margins.left() + margins.right(), margins.top() + margins.bottom() + ) + return size + + def setGeometry(self, rect: QRect) -> None: + """Set the geometry of the layout. + + This triggers a re-layout of the items. + """ + super().setGeometry(rect) + self._doLayout(rect) + + def sizeHint(self) -> QSize: + """Return the size hint of the layout.""" + return self.minimumSize() + + def takeAt(self, index: int) -> QLayoutItem | None: + """Remove and return the item at the given index. Or return None.""" + if 0 <= index < len(self._item_list): + return self._item_list.pop(index) + return None + + def _doLayout(self, rect: QRect, test_only: bool = False) -> int: + """Arrange the items in the layout. + + If test_only is True, the items are not actually laid out, but the height + that the layout would have with the given width is returned. + """ + left, top, right, bottom = self.getContentsMargins() + effective_rect = rect.adjusted(left, top, -right, -bottom) # type: ignore + x = effective_rect.x() + y = effective_rect.y() + line_height = 0 + + for item in self._item_list: + if (wid := item.widget()) and (style := wid.style()): + space_x = self.horizontalSpacing() + space_y = self.verticalSpacing() + if space_x == -1: + space_x = style.layoutSpacing( + QSizePolicy.ControlType.PushButton, + QSizePolicy.ControlType.PushButton, + Qt.Orientation.Horizontal, + ) + if space_y == -1: + space_y = style.layoutSpacing( + QSizePolicy.ControlType.PushButton, + QSizePolicy.ControlType.PushButton, + Qt.Orientation.Vertical, + ) + + # next_x is the x-coordinate of the right edge of the item + next_x = x + item.sizeHint().width() + space_x + # if the item is not the first one in a line, add the spacing + # to the left of it + if next_x - space_x > effective_rect.right() and line_height > 0: + x = effective_rect.x() + y = y + line_height + space_y + next_x = x + item.sizeHint().width() + space_x + line_height = 0 + + # if this is not a test run, move the item to its proper place + if not test_only: + item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) + + x = next_x + line_height = max(line_height, item.sizeHint().height()) + + return y + line_height - rect.y() + bottom + + def _smartSpacing(self, pm: QStyle.PixelMetric) -> int: + """Return the smart spacing based on the style of the parent widget.""" + if isinstance(parent := self.parent(), QWidget) and (style := parent.style()): + return style.pixelMetric(pm, None, parent) + return -1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/utils/_misc.py new/superqt-0.7.1/src/superqt/utils/_misc.py --- old/superqt-0.6.7/src/superqt/utils/_misc.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/utils/_misc.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,6 @@ +from collections.abc import Iterator from contextlib import contextmanager -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING if TYPE_CHECKING: from qtpy.QtCore import QObject diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/src/superqt/utils/_qthreading.py new/superqt-0.7.1/src/superqt/utils/_qthreading.py --- old/superqt-0.6.7/src/superqt/utils/_qthreading.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/src/superqt/utils/_qthreading.py 2020-02-02 01:00:00.000000000 +0100 @@ -9,9 +9,7 @@ Any, Callable, ClassVar, - Generator, Generic, - Sequence, TypeVar, overload, ) @@ -19,6 +17,8 @@ from qtpy.QtCore import QObject, QRunnable, QThread, QThreadPool, QTimer, Signal if TYPE_CHECKING: + from collections.abc import Generator, Sequence + _T = TypeVar("_T") class SigInst(Generic[_T]): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/test_eliding_label.py new/superqt-0.7.1/tests/test_eliding_label.py --- old/superqt-0.6.7/tests/test_eliding_label.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/test_eliding_label.py 2020-02-02 01:00:00.000000000 +0100 @@ -69,3 +69,14 @@ assert isinstance(wrap, list) assert all(isinstance(x, str) for x in wrap) assert 9 <= len(wrap) <= 13 + + +def test_minimum_size_hint(): + # The hint should always just be the space needed for "..." + wdg = QElidingLabel() + size_hint = wdg.minimumSizeHint() + # Regardless of what text is contained + wdg.setText(TEXT) + new_hint = wdg.minimumSizeHint() + assert size_hint.width() == new_hint.width() + assert size_hint.height() == new_hint.height() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/test_ensure_thread.py new/superqt-0.7.1/tests/test_ensure_thread.py --- old/superqt-0.6.7/tests/test_ensure_thread.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/test_ensure_thread.py 2020-02-02 01:00:00.000000000 +0100 @@ -162,7 +162,7 @@ signature = inspect.signature(ob.check_object_thread_return_future) assert len(signature.parameters) == 1 assert next(iter(signature.parameters.values())).name == "a" - assert next(iter(signature.parameters.values())).annotation == int + assert next(iter(signature.parameters.values())).annotation is int assert ob.check_main_thread_return.__name__ == "check_main_thread_return" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/test_flow_layout.py new/superqt-0.7.1/tests/test_flow_layout.py --- old/superqt-0.6.7/tests/test_flow_layout.py 1970-01-01 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/test_flow_layout.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,27 @@ +from typing import Any + +from qtpy.QtWidgets import QPushButton, QWidget + +from superqt import QFlowLayout + + +def test_flow_layout(qtbot: Any) -> None: + wdg = QWidget() + qtbot.addWidget(wdg) + + layout = QFlowLayout(wdg) + layout.addWidget(QPushButton("Short")) + layout.addWidget(QPushButton("Longer")) + layout.addWidget(QPushButton("Different text")) + layout.addWidget(QPushButton("More text")) + layout.addWidget(QPushButton("Even longer button text")) + + wdg.setWindowTitle("Flow Layout") + wdg.show() + + assert layout.expandingDirections() + assert layout.heightForWidth(200) > layout.heightForWidth(400) + assert layout.count() == 5 + assert layout.itemAt(0).widget().text() == "Short" + layout.takeAt(0) + assert layout.count() == 4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/test_searchable_tree.py new/superqt-0.7.1/tests/test_searchable_tree.py --- old/superqt-0.6.7/tests/test_searchable_tree.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/test_searchable_tree.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,3 @@ -from typing import List, Tuple - import pytest from pytestqt.qtbot import QtBot from qtpy.QtCore import Qt @@ -30,15 +28,15 @@ return widget -def columns(item: QTreeWidgetItem) -> Tuple[str, str]: +def columns(item: QTreeWidgetItem) -> tuple[str, str]: return item.text(0), item.text(1) -def all_items(tree: QTreeWidget) -> List[QTreeWidgetItem]: +def all_items(tree: QTreeWidget) -> list[QTreeWidgetItem]: return tree.findItems("", Qt.MatchContains | Qt.MatchRecursive) -def shown_items(tree: QTreeWidget) -> List[QTreeWidgetItem]: +def shown_items(tree: QTreeWidget) -> list[QTreeWidgetItem]: items = all_items(tree) return [item for item in items if not item.isHidden()] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/test_throttler.py new/superqt-0.7.1/tests/test_throttler.py --- old/superqt-0.6.7/tests/test_throttler.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/test_throttler.py 2020-02-02 01:00:00.000000000 +0100 @@ -125,7 +125,7 @@ def test_class_with_slots(qtbot): class A: - __slots__ = ("count", "__weakref__") + __slots__ = ("__weakref__", "count") def __init__(self): self.count = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/zz_test_sliders/test_labeled_slider.py new/superqt-0.7.1/tests/zz_test_sliders/test_labeled_slider.py --- old/superqt-0.6.7/tests/zz_test_sliders/test_labeled_slider.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/zz_test_sliders/test_labeled_slider.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,4 +1,5 @@ -from typing import Any, Iterable +from collections.abc import Iterable +from typing import Any from unittest.mock import Mock import pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/superqt-0.6.7/tests/zz_test_sliders/test_range_slider.py new/superqt-0.7.1/tests/zz_test_sliders/test_range_slider.py --- old/superqt-0.6.7/tests/zz_test_sliders/test_range_slider.py 2020-02-02 01:00:00.000000000 +0100 +++ new/superqt-0.7.1/tests/zz_test_sliders/test_range_slider.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,7 @@ import math +from collections.abc import Iterable from itertools import product -from typing import Any, Iterable +from typing import Any from unittest.mock import Mock import pytest
