mirror of
https://github.com/pyapp-kit/superqt.git
synced 2025-07-21 12:11:07 +02:00
add support for PyQt6 and PySide6
This commit is contained in:
@@ -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:
|
||||
|
@@ -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
14
examples/basic.py
Normal 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_()
|
@@ -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()
|
||||
|
@@ -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"])
|
||||
|
58
pyqrangeslider/qtcompat/QtCore.py
Normal file
58
pyqrangeslider/qtcompat/QtCore.py
Normal 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")
|
43
pyqrangeslider/qtcompat/QtGui.py
Normal file
43
pyqrangeslider/qtcompat/QtGui.py
Normal 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")
|
44
pyqrangeslider/qtcompat/QtWidgets.py
Normal file
44
pyqrangeslider/qtcompat/QtWidgets.py
Normal 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")
|
161
pyqrangeslider/qtcompat/__init__.py
Normal file
161
pyqrangeslider/qtcompat/__init__.py
Normal 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]
|
@@ -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
|
||||
|
Reference in New Issue
Block a user