add support for PyQt6 and PySide6

This commit is contained in:
Talley Lambert
2021-04-24 11:36:31 -04:00
parent 0f1be2fe89
commit cec8a7f7d0
11 changed files with 331 additions and 15 deletions

View File

@@ -4,11 +4,6 @@ repos:
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/myint/autoflake
rev: v1.4
hooks:
- id: autoflake
args: ["--in-place", "--remove-all-unused-imports"]
- repo: https://github.com/PyCQA/isort
rev: 5.8.0
hooks:

View File

@@ -16,7 +16,7 @@ behave like a standard QSlider... just with multiple handles.
- Uses platform-specific Qt styles
- Supports style sheets
- Supports mouse wheel and keypress (soon) events
- Supports both PyQt5 and PySide2 (via qtpy)
- Supports PyQt5, PyQt6, PySide2 and PySide6
## Installation

14
examples/basic.py Normal file
View File

@@ -0,0 +1,14 @@
from pyqrangeslider import QRangeSlider
from pyqrangeslider.qtcompat import API_NAME
from pyqrangeslider.qtcompat.QtWidgets import QApplication
print(API_NAME)
app = QApplication([])
slider = QRangeSlider()
slider.setMinimum(0)
slider.setMaximum(100)
slider.setValue((20, 80))
slider.show()
app.exec_()

View File

@@ -1,8 +1,8 @@
from typing import List, Sequence, Tuple
from qtpy import QtGui
from qtpy.QtCore import QEvent, QPoint, QRect, QRectF, Qt, Signal
from qtpy.QtWidgets import (
from .qtcompat import QtGui
from .qtcompat.QtCore import QEvent, QPoint, QPointF, QRect, QRectF, Qt, Signal
from .qtcompat.QtWidgets import (
QApplication,
QSlider,
QStyle,
@@ -290,9 +290,11 @@ class QRangeSlider(QSlider):
hdl_idx = 0
dist = float("inf")
if isinstance(pos, QPointF):
pos = QPoint(pos.x(), pos.y())
# TODO: this should be reversed, to prefer higher value handles
for i, hdl in enumerate(self._handleRects(opt)):
if pos in hdl:
if hdl.contains(pos):
return ("handle", i) # TODO: use enum for 'handle'
hdl_center = self._pick(hdl.center())
abs_dist = abs(event_position - hdl_center)
@@ -373,7 +375,7 @@ class QRangeSlider(QSlider):
e.accept()
def _scrollByDelta(
self, orientation: Qt.Orientation, modifiers: Qt.KeyboardModifiers, delta: int
self, orientation, modifiers: Qt.KeyboardModifiers, delta: int
) -> bool:
steps_to_scroll = 0
pg_step = self.pageStep()

View File

@@ -1,7 +1,7 @@
import pytest
from qtpy.QtCore import Qt
from pyqrangeslider import QRangeSlider
from pyqrangeslider.qtcompat.QtCore import Qt
@pytest.mark.parametrize("orientation", ["Horizontal", "Vertical"])

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2014-2015 Colin Duquesnoy
# Copyright © 2009- The Spyder Development Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
"""
Modified from qtpy.QtCore.
Provides QtCore classes and functions.
"""
# flake8: noqa
from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, PythonQtError
if PYQT5:
from PyQt5.QtCore import QT_VERSION_STR as __version__
from PyQt5.QtCore import *
from PyQt5.QtCore import pyqtBoundSignal as SignalInstance
from PyQt5.QtCore import pyqtProperty as Property
from PyQt5.QtCore import pyqtSignal as Signal
from PyQt5.QtCore import pyqtSlot as Slot
# Those are imported from `import *`
del pyqtSignal, pyqtBoundSignal, pyqtSlot, pyqtProperty, QT_VERSION_STR
elif PYQT6:
from PyQt6.QtCore import QT_VERSION_STR as __version__
from PyQt6.QtCore import *
from PyQt6.QtCore import pyqtBoundSignal as SignalInstance
from PyQt6.QtCore import pyqtProperty as Property
from PyQt6.QtCore import pyqtSignal as Signal
from PyQt6.QtCore import pyqtSlot as Slot
# backwards compat with PyQt5
# namespace moves:
for cls in (QEvent, Qt):
for attr in dir(cls):
if not attr[0].isupper():
continue
ns = getattr(cls, attr)
for name, val in vars(ns).items():
if not name.startswith("_"):
setattr(cls, name, val)
# Those are imported from `import *`
del pyqtSignal, pyqtBoundSignal, pyqtSlot, pyqtProperty, QT_VERSION_STR
elif PYSIDE2:
import PySide2.QtCore
__version__ = PySide2.QtCore.__version__
elif PYSIDE6:
import PySide6.QtCore
__version__ = PySide6.QtCore.__version__
else:
raise PythonQtError("No Qt bindings could be found")

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2014-2015 Colin Duquesnoy
# Copyright © 2009- The Spyder Development Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
"""
Modified from qtpy.QtGui
Provides QtGui classes and functions.
"""
from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, PythonQtError
if PYQT5:
from PyQt5.QtGui import *
elif PYSIDE2:
from PySide2.QtGui import *
elif PYQT6:
from PyQt6.QtGui import *
# backwards compat with PyQt5
# namespace moves:
for cls in (QPalette,):
for attr in dir(cls):
if not attr[0].isupper():
continue
ns = getattr(cls, attr)
for name, val in vars(ns).items():
if not name.startswith("_"):
setattr(cls, name, val)
def pos(self, *a):
_pos = self.position(*a)
return _pos.toPoint()
QMouseEvent.pos = pos
elif PYSIDE6:
pass
else:
raise PythonQtError("No Qt bindings could be found")

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2014-2015 Colin Duquesnoy
# Copyright © 2009- The Spyder Developmet Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
"""
Modified from qtpy.QtWidgets
Provides widget classes and functions.
"""
from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, PythonQtError
if PYQT5:
from PyQt5.QtWidgets import *
elif PYSIDE2:
from PySide2.QtWidgets import *
elif PYQT6:
from PyQt6.QtWidgets import *
# backwards compat with PyQt5
# namespace moves:
for cls in (QStyle, QSlider):
for attr in dir(cls):
if not attr[0].isupper():
continue
ns = getattr(cls, attr)
for name, val in vars(ns).items():
if not name.startswith("_"):
setattr(cls, name, val)
def exec_(self):
self.exec()
QApplication.exec_ = exec_
elif PYSIDE6:
pass
else:
raise PythonQtError("No Qt bindings could be found")

View File

@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2009- The Spyder Development Team
# Copyright © 2014-2015 Colin Duquesnoy
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
"""
This file is borrowed from qtpy and modified to support PySide6/PyQt6 (drops PyQt4)
"""
import os
import platform
import sys
import warnings
from distutils.version import LooseVersion
class PythonQtError(RuntimeError):
"""Error raise if no bindings could be selected."""
class PythonQtWarning(Warning):
"""Warning if some features are not implemented in a binding."""
# Qt API environment variable name
QT_API = "QT_API"
# Names of the expected PyQt5 api
PYQT5_API = ["pyqt5"]
# Names of the expected PyQt5 api
PYQT6_API = ["pyqt6"]
# Names of the expected PySide2 api
PYSIDE2_API = ["pyside2"]
# Names of the expected PySide2 api
PYSIDE6_API = ["pyside6"]
# Detecting if a binding was specified by the user
binding_specified = QT_API in os.environ
# Setting a default value for QT_API
os.environ.setdefault(QT_API, "pyqt5")
API = os.environ[QT_API].lower()
initial_api = API
assert API in (PYQT5_API + PYQT6_API + PYSIDE2_API + PYSIDE6_API)
PYQT5 = True
PYSIDE2 = PYQT6 = PYSIDE6 = False
# When `FORCE_QT_API` is set, we disregard
# any previously imported python bindings.
if os.environ.get("FORCE_QT_API") is not None:
if "PyQt5" in sys.modules:
API = initial_api if initial_api in PYQT5_API else "pyqt5"
elif "PySide2" in sys.modules:
API = initial_api if initial_api in PYSIDE2_API else "pyside2"
elif "PyQt6" in sys.modules:
API = initial_api if initial_api in PYQT6_API else "pyqt6"
elif "PySide6" in sys.modules:
API = initial_api if initial_api in PYSIDE6_API else "pyside6"
if API in PYQT5_API:
try:
from PyQt5.QtCore import PYQT_VERSION_STR as PYQT_VERSION # noqa
from PyQt5.QtCore import QT_VERSION_STR as QT_VERSION # noqa
PYSIDE_VERSION = None # noqa
if sys.platform == "darwin":
macos_version = LooseVersion(platform.mac_ver()[0])
if macos_version < LooseVersion("10.10"):
if LooseVersion(QT_VERSION) >= LooseVersion("5.9"):
raise PythonQtError(
"Qt 5.9 or higher only works in "
"macOS 10.10 or higher. Your "
"program will fail in this "
"system."
)
elif macos_version < LooseVersion("10.11"):
if LooseVersion(QT_VERSION) >= LooseVersion("5.11"):
raise PythonQtError(
"Qt 5.11 or higher only works in "
"macOS 10.11 or higher. Your "
"program will fail in this "
"system."
)
del macos_version
except ImportError:
API = os.environ["QT_API"] = "pyside2"
if API in PYSIDE2_API:
try:
from PySide2 import __version__ as PYSIDE_VERSION # noqa
from PySide2.QtCore import __version__ as QT_VERSION # noqa
PYQT_VERSION = None # noqa
PYQT5 = False
PYSIDE2 = True
if sys.platform == "darwin":
macos_version = LooseVersion(platform.mac_ver()[0])
if macos_version < LooseVersion("10.11"):
if LooseVersion(QT_VERSION) >= LooseVersion("5.11"):
raise PythonQtError(
"Qt 5.11 or higher only works in "
"macOS 10.11 or higher. Your "
"program will fail in this "
"system."
)
del macos_version
except ImportError:
API = os.environ["QT_API"] = "pyqt6"
if API in PYQT6_API:
try:
from PyQt6.QtCore import PYQT_VERSION_STR as PYQT_VERSION # noqa
from PyQt6.QtCore import QT_VERSION_STR as QT_VERSION # noqa
PYSIDE_VERSION = None # noqa
PYQT5 = False
PYQT6 = True
except ImportError:
API = os.environ["QT_API"] = "pyside6"
if API in PYSIDE6_API:
try:
from PySide6 import __version__ as PYSIDE_VERSION # noqa
from PySide6.QtCore import __version__ as QT_VERSION # noqa
PYQT_VERSION = None # noqa
PYQT5 = False
PYSIDE6 = True
except ImportError:
raise PythonQtError("No Qt bindings could be found")
# If a correct API name is passed to QT_API and it could not be found,
# switches to another and informs through the warning
if API != initial_api and binding_specified:
warnings.warn(
'Selected binding "{}" could not be found, '
'using "{}"'.format(initial_api, API),
RuntimeWarning,
)
API_NAME = {
"pyqt5": "PyQt5",
"pyqt6": "PyQt6",
"pyside2": "PySide2",
"pyside6": "PySide6",
}[API]

View File

@@ -35,8 +35,6 @@ zip_safe = False
packages = find:
python_requires = >=3.6
setup_requires = setuptools_scm
install_requires =
qtpy
[options.extras_require]
pyside2 = pyside2
@@ -60,7 +58,7 @@ dev =
exclude = _version.py,.eggs,examples
max-line-length = 79
docstring-convention = numpy
ignore = E203,W503,E501,C901
ignore = E203,W503,E501,C901,F403,F405
[isort]
profile=black

View File

@@ -1,6 +1,7 @@
# For more information about tox, see https://tox.readthedocs.io/en/latest/
[tox]
envlist = py{37,38,39}-{linux,macos,windows}-{pyqt,pyside}
toxworkdir=/tmp/.tox
[gh-actions]
python =