From df81709742c9d44d214f56686a67fdf58154f919 Mon Sep 17 00:00:00 2001 From: Anton Yablokov Date: Wed, 18 Oct 2023 20:34:32 +0300 Subject: [PATCH 01/15] Test `QMenu.addAction` with various shortcut types Test `QMenu.addAction` with a shortcut of `Qt.Key` type as well. The argument type causes the following error on `PySide2`: ```plaintext TypeError: 'PySide2.QtWidgets.QAction.setShortcut' called with wrong argument types: PySide2.QtWidgets.QAction.setShortcut(Key) Supported signatures: PySide2.QtWidgets.QAction.setShortcut(PySide2.QtGui.QKeySequence) ``` --- qtpy/tests/test_qtwidgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qtpy/tests/test_qtwidgets.py b/qtpy/tests/test_qtwidgets.py index 70c937a8..e6249d9a 100644 --- a/qtpy/tests/test_qtwidgets.py +++ b/qtpy/tests/test_qtwidgets.py @@ -117,10 +117,10 @@ def test_QMenu_functions(qtbot): window = QtWidgets.QMainWindow() menu = QtWidgets.QMenu(window) menu.addAction("QtPy") - menu.addAction("QtPy with a shortcut", QtGui.QKeySequence.UnknownKey) + menu.addAction("QtPy with a Qt.Key shortcut", QtCore.Qt.Key.Key_F1) menu.addAction( QtGui.QIcon(), - "QtPy with an icon and a shortcut", + "QtPy with an icon and a QKeySequence shortcut", QtGui.QKeySequence.UnknownKey, ) window.show() From 49f0b7511abbc0eb3c2ef5e94ac9b171901b4c54 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Thu, 19 Oct 2023 19:25:52 +0300 Subject: [PATCH 02/15] Make `QAction.setShortcut`(`s`) accept many types Fix the omission of `Qt.Key` check. Fix a crash when `QAction.setShortcuts` gets a single shortcut. Add tests for the cases. Refactor a little. --- qtpy/QtWidgets.py | 77 +++++++++++++++++++++++++++++++++++- qtpy/_utils.py | 59 +++++++++++++++++++++------ qtpy/tests/test_qtwidgets.py | 18 ++++++++- 3 files changed, 138 insertions(+), 16 deletions(-) diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index 5e8e0886..d2b13708 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -17,6 +17,8 @@ add_action, getattr_missing_optional_dep, possibly_static_exec, + set_shortcut, + set_shortcuts, static_method_kwargs_wrapper, ) @@ -210,8 +212,79 @@ def __getattr__(name): # Make `addAction` compatible with Qt6 >= 6.3 if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): - QMenu.addAction = partialmethod(add_action, old_add_action=QMenu.addAction) - QToolBar.addAction = partialmethod( + + class _QMenu(QMenu): + old_add_action = QMenu.addAction + + def addAction(self, *args): + return add_action( + self, + *args, + old_add_action=_QMenu.old_add_action, + ) + + _menu_add_action = partialmethod( + add_action, + old_add_action=QMenu.addAction, + ) + QMenu.addAction = _menu_add_action + if QMenu.addAction is not _menu_add_action: # despite the previous line! + QMenu = _QMenu + + class _QToolBar(QToolBar): + old_add_action = QToolBar.addAction + + def addAction(self, *args): + return add_action( + self, + *args, + old_add_action=_QToolBar.old_add_action, + ) + + _toolbar_add_action = partialmethod( add_action, old_add_action=QToolBar.addAction, ) + QToolBar.addAction = _toolbar_add_action + if ( # despite the previous line! + QToolBar.addAction is not _toolbar_add_action + ): + QToolBar = _QToolBar + + +# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.3 +if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): + + class _QAction(QAction): + old_set_shortcut = QAction.setShortcut + old_set_shortcuts = QAction.setShortcuts + + def setShortcut(self, shortcut): + return set_shortcut( + self, + shortcut, + old_set_shortcut=_QAction.old_set_shortcut, + ) + + def setShortcuts(self, shortcuts): + return set_shortcuts( + self, + shortcuts, + old_set_shortcuts=_QAction.old_set_shortcuts, + ) + + _action_set_shortcut = partialmethod( + set_shortcut, + old_set_shortcut=QAction.setShortcut, + ) + _action_set_shortcuts = partialmethod( + set_shortcuts, + old_set_shortcuts=QAction.setShortcuts, + ) + QAction.setShortcut = _action_set_shortcut + QAction.setShortcuts = _action_set_shortcuts + if ( # despite the two previous lines! + QAction.setShortcut is not _action_set_shortcut + or QAction.setShortcuts is not _action_set_shortcuts + ): + QAction = _QAction diff --git a/qtpy/_utils.py b/qtpy/_utils.py index ec851fab..3f08f4fd 100644 --- a/qtpy/_utils.py +++ b/qtpy/_utils.py @@ -70,24 +70,56 @@ def possibly_static_exec_(cls, *args, **kwargs): return cls.exec_(*args, **kwargs) +def set_shortcut(self, shortcut, old_set_shortcut): + """Ensure that the type of `shortcut` is compatible to `QAction.setShortcut`.""" + from qtpy.QtCore import Qt + from qtpy.QtGui import QKeySequence + + if isinstance(shortcut, (QKeySequence.StandardKey, Qt.Key, int)): + shortcut = QKeySequence(shortcut) + old_set_shortcut(self, shortcut) + + +def set_shortcuts(self, shortcuts, old_set_shortcuts): + """Ensure that the type of `shortcuts` is compatible to `QAction.setShortcuts`.""" + from qtpy.QtCore import Qt + from qtpy.QtGui import QKeySequence + + if isinstance( + shortcuts, (QKeySequence, QKeySequence.StandardKey, Qt.Key, int, str) + ): + shortcuts = (shortcuts,) + + shortcuts = tuple( + ( + QKeySequence(shortcut) + if isinstance(shortcut, (QKeySequence.StandardKey, Qt.Key, int)) + else shortcut + ) + for shortcut in shortcuts + ) + old_set_shortcuts(self, shortcuts) + + def add_action(self, *args, old_add_action): """Re-order arguments of `addAction` to backport compatibility with Qt>=6.3.""" - from qtpy.QtCore import QObject + from qtpy.QtCore import QObject, Qt from qtpy.QtGui import QIcon, QKeySequence action: QAction icon: QIcon text: str - shortcut: QKeySequence | QKeySequence.StandardKey | str | int + shortcut: QKeySequence | QKeySequence.StandardKey | Qt.Key | str | int receiver: QObject member: bytes + if all( isinstance(arg, t) for arg, t in zip( args, [ str, - (QKeySequence, QKeySequence.StandardKey, str, int), + (QKeySequence, QKeySequence.StandardKey, Qt.Key, str, int), QObject, bytes, ], @@ -105,16 +137,15 @@ def add_action(self, *args, old_add_action): text, shortcut, receiver, member = args action = old_add_action(self, text, receiver, member, shortcut) else: - return old_add_action(self, *args) - return action - if all( + action = old_add_action(self, *args) + elif all( isinstance(arg, t) for arg, t in zip( args, [ QIcon, str, - (QKeySequence, QKeySequence.StandardKey, str, int), + (QKeySequence, QKeySequence.StandardKey, Qt.Key, str, int), QObject, bytes, ], @@ -123,11 +154,11 @@ def add_action(self, *args, old_add_action): if len(args) == 3: icon, text, shortcut = args action = old_add_action(self, icon, text) - action.setShortcut(QKeySequence(shortcut)) + action.setShortcut(shortcut) elif len(args) == 4: icon, text, shortcut, receiver = args action = old_add_action(self, icon, text, receiver) - action.setShortcut(QKeySequence(shortcut)) + action.setShortcut(shortcut) elif len(args) == 5: icon, text, shortcut, receiver, member = args action = old_add_action( @@ -136,12 +167,14 @@ def add_action(self, *args, old_add_action): text, receiver, member, - QKeySequence(shortcut), + shortcut, ) else: - return old_add_action(self, *args) - return action - return old_add_action(self, *args) + action = old_add_action(self, *args) + else: + action = old_add_action(self, *args) + + return action def static_method_kwargs_wrapper(func, from_kwarg_name, to_kwarg_name): diff --git a/qtpy/tests/test_qtwidgets.py b/qtpy/tests/test_qtwidgets.py index e6249d9a..abd5b0a0 100644 --- a/qtpy/tests/test_qtwidgets.py +++ b/qtpy/tests/test_qtwidgets.py @@ -148,7 +148,7 @@ def test_QMenu_functions(qtbot): def test_QToolBar_functions(qtbot): """Test `QtWidgets.QToolBar.addAction` compatibility with Qt6 arguments' order.""" toolbar = QtWidgets.QToolBar() - toolbar.addAction("QtPy with a shortcut", QtGui.QKeySequence.UnknownKey) + toolbar.addAction("QtPy with a shortcut", QtCore.Qt.Key.Key_F1) toolbar.addAction( QtGui.QIcon(), "QtPy with an icon and a shortcut", @@ -156,6 +156,22 @@ def test_QToolBar_functions(qtbot): ) +@pytest.mark.skipif( + sys.platform == "darwin" and sys.version_info[:2] == (3, 7), + reason="Stalls on macOS CI with Python 3.7", +) +def test_QAction_functions(qtbot): + """Test `QtWidgets.QAction.setShortcut` compatibility with Qt6 types.""" + action = QtWidgets.QAction("QtPy", None) + action.setShortcut(QtGui.QKeySequence.UnknownKey) + action.setShortcuts([QtGui.QKeySequence.UnknownKey]) + action.setShortcuts(QtGui.QKeySequence.UnknownKey) + action.setShortcut(QtCore.Qt.Key.Key_F1) + action.setShortcuts([QtCore.Qt.Key.Key_F1]) + # The following line is wrong even for Qt6 == 6.6: + # action.setShortcuts(QtCore.Qt.Key.Key_F1) + + @pytest.mark.skipif( PYQT5 and PYQT_VERSION.startswith("5.9"), reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*" From d6f273950fa1aebe4e944b89b728e2fa9b3b7d67 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:27:14 +0000 Subject: [PATCH 03/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtpy/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtpy/_utils.py b/qtpy/_utils.py index 3f08f4fd..a366cb97 100644 --- a/qtpy/_utils.py +++ b/qtpy/_utils.py @@ -86,7 +86,7 @@ def set_shortcuts(self, shortcuts, old_set_shortcuts): from qtpy.QtGui import QKeySequence if isinstance( - shortcuts, (QKeySequence, QKeySequence.StandardKey, Qt.Key, int, str) + shortcuts, (QKeySequence, QKeySequence.StandardKey, Qt.Key, int, str), ): shortcuts = (shortcuts,) From bc7cc975a22628b05361367804535dad775ea986 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Thu, 19 Oct 2023 19:40:29 +0300 Subject: [PATCH 04/15] Move the `QAction.setShortcut` patch to `QtGui` --- qtpy/QtGui.py | 50 +++++++++++++++++++++++++++++++++++- qtpy/QtWidgets.py | 42 ++---------------------------- qtpy/tests/test_qtgui.py | 16 ++++++++++++ qtpy/tests/test_qtwidgets.py | 16 ------------ 4 files changed, 67 insertions(+), 57 deletions(-) diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index 8b3a0ba9..cede6793 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -8,7 +8,11 @@ """Provides QtGui classes and functions.""" -from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, QtModuleNotInstalledError + +from packaging.version import parse + +from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6 +from . import QT_VERSION as _qt_version from ._utils import getattr_missing_optional_dep, possibly_static_exec _missing_optional_names = {} @@ -252,3 +256,47 @@ def movePositionPatched( # Follow similar approach for `QDropEvent` and child classes QDropEvent.pos = lambda self: self.position().toPoint() QDropEvent.posF = lambda self: self.position() + + +# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.3 +if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): + from functools import partialmethod + + from ._utils import ( + set_shortcut, + set_shortcuts, + ) + + class _QAction(QAction): + old_set_shortcut = QAction.setShortcut + old_set_shortcuts = QAction.setShortcuts + + def setShortcut(self, shortcut): + return set_shortcut( + self, + shortcut, + old_set_shortcut=_QAction.old_set_shortcut, + ) + + def setShortcuts(self, shortcuts): + return set_shortcuts( + self, + shortcuts, + old_set_shortcuts=_QAction.old_set_shortcuts, + ) + + _action_set_shortcut = partialmethod( + set_shortcut, + old_set_shortcut=QAction.setShortcut, + ) + _action_set_shortcuts = partialmethod( + set_shortcuts, + old_set_shortcuts=QAction.setShortcuts, + ) + QAction.setShortcut = _action_set_shortcut + QAction.setShortcuts = _action_set_shortcuts + if ( # despite the two previous lines! + QAction.setShortcut is not _action_set_shortcut + or QAction.setShortcuts is not _action_set_shortcuts + ): + QAction = _QAction diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index d2b13708..11b2ea11 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -38,7 +38,7 @@ def __getattr__(name): from PyQt5.QtWidgets import * elif PYQT6: from PyQt6 import QtWidgets - from PyQt6.QtGui import ( + from qtpy.QtGui import ( QAction, QActionGroup, QFileSystemModel, @@ -112,7 +112,7 @@ def __getattr__(name): elif PYSIDE2: from PySide2.QtWidgets import * elif PYSIDE6: - from PySide6.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand + from qtpy.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand from PySide6.QtWidgets import * # Attempt to import QOpenGLWidget, but if that fails, @@ -250,41 +250,3 @@ def addAction(self, *args): QToolBar.addAction is not _toolbar_add_action ): QToolBar = _QToolBar - - -# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.3 -if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): - - class _QAction(QAction): - old_set_shortcut = QAction.setShortcut - old_set_shortcuts = QAction.setShortcuts - - def setShortcut(self, shortcut): - return set_shortcut( - self, - shortcut, - old_set_shortcut=_QAction.old_set_shortcut, - ) - - def setShortcuts(self, shortcuts): - return set_shortcuts( - self, - shortcuts, - old_set_shortcuts=_QAction.old_set_shortcuts, - ) - - _action_set_shortcut = partialmethod( - set_shortcut, - old_set_shortcut=QAction.setShortcut, - ) - _action_set_shortcuts = partialmethod( - set_shortcuts, - old_set_shortcuts=QAction.setShortcuts, - ) - QAction.setShortcut = _action_set_shortcut - QAction.setShortcuts = _action_set_shortcuts - if ( # despite the two previous lines! - QAction.setShortcut is not _action_set_shortcut - or QAction.setShortcuts is not _action_set_shortcuts - ): - QAction = _QAction diff --git a/qtpy/tests/test_qtgui.py b/qtpy/tests/test_qtgui.py index 1aa4f47e..1def1617 100644 --- a/qtpy/tests/test_qtgui.py +++ b/qtpy/tests/test_qtgui.py @@ -177,6 +177,22 @@ def test_qtextcursor_moveposition(): assert cursor.selectedText() == "foo bar baz" +@pytest.mark.skipif( + sys.platform == "darwin" and sys.version_info[:2] == (3, 7), + reason="Stalls on macOS CI with Python 3.7", +) +def test_QAction_functions(qtbot): + """Test `QtGui.QAction.setShortcut` compatibility with Qt6 types.""" + action = QtGui.QAction("QtPy", None) + action.setShortcut(QtGui.QKeySequence.UnknownKey) + action.setShortcuts([QtGui.QKeySequence.UnknownKey]) + action.setShortcuts(QtGui.QKeySequence.UnknownKey) + action.setShortcut(QtCore.Qt.Key.Key_F1) + action.setShortcuts([QtCore.Qt.Key.Key_F1]) + # The following line is wrong even for Qt6 == 6.6: + # action.setShortcuts(QtCore.Qt.Key.Key_F1) + + def test_opengl_imports(): """ Test for presence of QOpenGL* classes. diff --git a/qtpy/tests/test_qtwidgets.py b/qtpy/tests/test_qtwidgets.py index abd5b0a0..ef7c9de3 100644 --- a/qtpy/tests/test_qtwidgets.py +++ b/qtpy/tests/test_qtwidgets.py @@ -156,22 +156,6 @@ def test_QToolBar_functions(qtbot): ) -@pytest.mark.skipif( - sys.platform == "darwin" and sys.version_info[:2] == (3, 7), - reason="Stalls on macOS CI with Python 3.7", -) -def test_QAction_functions(qtbot): - """Test `QtWidgets.QAction.setShortcut` compatibility with Qt6 types.""" - action = QtWidgets.QAction("QtPy", None) - action.setShortcut(QtGui.QKeySequence.UnknownKey) - action.setShortcuts([QtGui.QKeySequence.UnknownKey]) - action.setShortcuts(QtGui.QKeySequence.UnknownKey) - action.setShortcut(QtCore.Qt.Key.Key_F1) - action.setShortcuts([QtCore.Qt.Key.Key_F1]) - # The following line is wrong even for Qt6 == 6.6: - # action.setShortcuts(QtCore.Qt.Key.Key_F1) - - @pytest.mark.skipif( PYQT5 and PYQT_VERSION.startswith("5.9"), reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*" From b4d3e1d4b22d93a26c5edeb4072b0ffc5d1e4e23 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Thu, 19 Oct 2023 19:45:27 +0300 Subject: [PATCH 05/15] Fix AttributeError: type object 'Key' has no attribute 'Key_F1' on old PyQt5 --- qtpy/tests/test_qtgui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qtpy/tests/test_qtgui.py b/qtpy/tests/test_qtgui.py index 1def1617..ff864526 100644 --- a/qtpy/tests/test_qtgui.py +++ b/qtpy/tests/test_qtgui.py @@ -187,10 +187,10 @@ def test_QAction_functions(qtbot): action.setShortcut(QtGui.QKeySequence.UnknownKey) action.setShortcuts([QtGui.QKeySequence.UnknownKey]) action.setShortcuts(QtGui.QKeySequence.UnknownKey) - action.setShortcut(QtCore.Qt.Key.Key_F1) - action.setShortcuts([QtCore.Qt.Key.Key_F1]) + action.setShortcut(QtCore.Qt.Key_F1) + action.setShortcuts([QtCore.Qt.Key_F1]) # The following line is wrong even for Qt6 == 6.6: - # action.setShortcuts(QtCore.Qt.Key.Key_F1) + # action.setShortcuts(QtCore.Qt.Key_F1) def test_opengl_imports(): From 80bb648e39e2e61b87ae591227db455d3e88ed29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:46:17 +0000 Subject: [PATCH 06/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtpy/QtWidgets.py | 6 ++++-- qtpy/_utils.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index 11b2ea11..e60b332c 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -38,6 +38,8 @@ def __getattr__(name): from PyQt5.QtWidgets import * elif PYQT6: from PyQt6 import QtWidgets + from PyQt6.QtWidgets import * + from qtpy.QtGui import ( QAction, QActionGroup, @@ -45,7 +47,6 @@ def __getattr__(name): QShortcut, QUndoCommand, ) - from PyQt6.QtWidgets import * # Attempt to import QOpenGLWidget, but if that fails, # don't raise an exception until the name is explicitly accessed. @@ -112,9 +113,10 @@ def __getattr__(name): elif PYSIDE2: from PySide2.QtWidgets import * elif PYSIDE6: - from qtpy.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand from PySide6.QtWidgets import * + from qtpy.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand + # Attempt to import QOpenGLWidget, but if that fails, # don't raise an exception until the name is explicitly accessed. # See https://github.com/spyder-ide/qtpy/pull/387/ diff --git a/qtpy/_utils.py b/qtpy/_utils.py index a366cb97..5207b554 100644 --- a/qtpy/_utils.py +++ b/qtpy/_utils.py @@ -86,7 +86,8 @@ def set_shortcuts(self, shortcuts, old_set_shortcuts): from qtpy.QtGui import QKeySequence if isinstance( - shortcuts, (QKeySequence, QKeySequence.StandardKey, Qt.Key, int, str), + shortcuts, + (QKeySequence, QKeySequence.StandardKey, Qt.Key, int, str), ): shortcuts = (shortcuts,) From 7c5100d924c1c4a9ce122373d43826741eb1b522 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Thu, 19 Oct 2023 20:00:49 +0300 Subject: [PATCH 07/15] Patch `QAction.setShortcut`(`s`) for Qt < 6.4 --- qtpy/QtGui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index cede6793..64ec3f10 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -258,8 +258,8 @@ def movePositionPatched( QDropEvent.posF = lambda self: self.position() -# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.3 -if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): +# Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.4 +if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"): from functools import partialmethod from ._utils import ( From 849a349d01bd59cc59836a209972e3bcec2c8b95 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Thu, 19 Oct 2023 20:01:50 +0300 Subject: [PATCH 08/15] Fix AttributeError: type object 'Key' has no attribute 'Key_F1' on old PyQt5 --- qtpy/tests/test_qtwidgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qtpy/tests/test_qtwidgets.py b/qtpy/tests/test_qtwidgets.py index ef7c9de3..2e6cb9b4 100644 --- a/qtpy/tests/test_qtwidgets.py +++ b/qtpy/tests/test_qtwidgets.py @@ -117,7 +117,7 @@ def test_QMenu_functions(qtbot): window = QtWidgets.QMainWindow() menu = QtWidgets.QMenu(window) menu.addAction("QtPy") - menu.addAction("QtPy with a Qt.Key shortcut", QtCore.Qt.Key.Key_F1) + menu.addAction("QtPy with a Qt.Key shortcut", QtCore.Qt.Key_F1) menu.addAction( QtGui.QIcon(), "QtPy with an icon and a QKeySequence shortcut", @@ -148,7 +148,7 @@ def test_QMenu_functions(qtbot): def test_QToolBar_functions(qtbot): """Test `QtWidgets.QToolBar.addAction` compatibility with Qt6 arguments' order.""" toolbar = QtWidgets.QToolBar() - toolbar.addAction("QtPy with a shortcut", QtCore.Qt.Key.Key_F1) + toolbar.addAction("QtPy with a shortcut", QtCore.Qt.Key_F1) toolbar.addAction( QtGui.QIcon(), "QtPy with an icon and a shortcut", From d8afeb0c1da830093c0a24eece37a3a0542c5d20 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Thu, 19 Oct 2023 20:21:01 +0300 Subject: [PATCH 09/15] Patch `addAction` for Qt < 6.4 Some 6.3.2 builds still seem to lack the flexibility. --- qtpy/QtWidgets.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index e60b332c..319c5831 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -14,11 +14,8 @@ from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6 from . import QT_VERSION as _qt_version from ._utils import ( - add_action, getattr_missing_optional_dep, possibly_static_exec, - set_shortcut, - set_shortcuts, static_method_kwargs_wrapper, ) @@ -212,8 +209,9 @@ def __getattr__(name): "directory", ) -# Make `addAction` compatible with Qt6 >= 6.3 -if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.3"): +# Make `addAction` compatible with Qt6 >= 6.4 +if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"): + from ._utils import add_action class _QMenu(QMenu): old_add_action = QMenu.addAction From a2451e1dcdf26cb3539047b80e57e9659a23ef5c Mon Sep 17 00:00:00 2001 From: StSav012 Date: Tue, 24 Oct 2023 20:08:54 +0300 Subject: [PATCH 10/15] Apply code style suggestions by @ccordoba12 --- qtpy/QtGui.py | 3 ++- qtpy/QtWidgets.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index 64ec3f10..7ae5ad92 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -295,7 +295,8 @@ def setShortcuts(self, shortcuts): ) QAction.setShortcut = _action_set_shortcut QAction.setShortcuts = _action_set_shortcuts - if ( # despite the two previous lines! + # Despite the two previous lines! + if ( QAction.setShortcut is not _action_set_shortcut or QAction.setShortcuts is not _action_set_shortcuts ): diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index 319c5831..c49bf066 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -228,7 +228,8 @@ def addAction(self, *args): old_add_action=QMenu.addAction, ) QMenu.addAction = _menu_add_action - if QMenu.addAction is not _menu_add_action: # despite the previous line! + # Despite the previous line! + if QMenu.addAction is not _menu_add_action: QMenu = _QMenu class _QToolBar(QToolBar): @@ -246,7 +247,6 @@ def addAction(self, *args): old_add_action=QToolBar.addAction, ) QToolBar.addAction = _toolbar_add_action - if ( # despite the previous line! - QToolBar.addAction is not _toolbar_add_action - ): + # Despite the previous line! + if QToolBar.addAction is not _toolbar_add_action: QToolBar = _QToolBar From 674183d5f22468c1173d39c3bb5643d77d71c2ed Mon Sep 17 00:00:00 2001 From: StSav012 Date: Tue, 24 Oct 2023 20:11:41 +0300 Subject: [PATCH 11/15] Move imports to be at the beginning of the file as suggested by @ccordoba12 --- qtpy/QtGui.py | 15 ++++++++------- qtpy/QtWidgets.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index 7ae5ad92..514b2ae0 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -8,12 +8,19 @@ """Provides QtGui classes and functions.""" +from functools import partialmethod from packaging.version import parse from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6 from . import QT_VERSION as _qt_version -from ._utils import getattr_missing_optional_dep, possibly_static_exec +from ._utils import ( + getattr_missing_optional_dep, + possibly_static_exec, + set_shortcut, + set_shortcuts, +) + _missing_optional_names = {} @@ -260,12 +267,6 @@ def movePositionPatched( # Make `QAction.setShortcut` and `QAction.setShortcuts` compatible with Qt>=6.4 if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"): - from functools import partialmethod - - from ._utils import ( - set_shortcut, - set_shortcuts, - ) class _QAction(QAction): old_set_shortcut = QAction.setShortcut diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index c49bf066..a7525826 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -14,6 +14,7 @@ from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6 from . import QT_VERSION as _qt_version from ._utils import ( + add_action, getattr_missing_optional_dep, possibly_static_exec, static_method_kwargs_wrapper, @@ -211,7 +212,6 @@ def __getattr__(name): # Make `addAction` compatible with Qt6 >= 6.4 if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"): - from ._utils import add_action class _QMenu(QMenu): old_add_action = QMenu.addAction From 855e8324392b4267057dbd03cc41450d9b450be6 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Tue, 24 Oct 2023 20:13:21 +0300 Subject: [PATCH 12/15] Clarify the comment as suggested by @ccordoba12 --- qtpy/tests/test_qtgui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qtpy/tests/test_qtgui.py b/qtpy/tests/test_qtgui.py index ff864526..851d00ad 100644 --- a/qtpy/tests/test_qtgui.py +++ b/qtpy/tests/test_qtgui.py @@ -189,7 +189,8 @@ def test_QAction_functions(qtbot): action.setShortcuts(QtGui.QKeySequence.UnknownKey) action.setShortcut(QtCore.Qt.Key_F1) action.setShortcuts([QtCore.Qt.Key_F1]) - # The following line is wrong even for Qt6 == 6.6: + # The following line is wrong even for Qt6 == 6.6. + # Don't test the function with a single `QtCore.Qt.Key` argument. # action.setShortcuts(QtCore.Qt.Key_F1) From 429fd3df039f59cf48847cf0d1190f46315c0722 Mon Sep 17 00:00:00 2001 From: StSav012 Date: Tue, 24 Oct 2023 20:21:10 +0300 Subject: [PATCH 13/15] Import only `QAction` separately as suggested by @ccordoba12 --- qtpy/QtWidgets.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index a7525826..820dcf5b 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -37,15 +37,15 @@ def __getattr__(name): elif PYQT6: from PyQt6 import QtWidgets from PyQt6.QtWidgets import * - - from qtpy.QtGui import ( - QAction, + from PyQt6.QtGui import ( QActionGroup, QFileSystemModel, QShortcut, QUndoCommand, ) + from qtpy.QtGui import QAction # See spyder-ide/qtpy#461 + # Attempt to import QOpenGLWidget, but if that fails, # don't raise an exception until the name is explicitly accessed. # See https://github.com/spyder-ide/qtpy/pull/387/ @@ -112,8 +112,9 @@ def __getattr__(name): from PySide2.QtWidgets import * elif PYSIDE6: from PySide6.QtWidgets import * + from PySide6.QtGui import QActionGroup, QShortcut, QUndoCommand - from qtpy.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand + from qtpy.QtGui import QAction # See spyder-ide/qtpy#461 # Attempt to import QOpenGLWidget, but if that fails, # don't raise an exception until the name is explicitly accessed. From 2c299ff9bfca1a8b7384493453af2b797ab7154d Mon Sep 17 00:00:00 2001 From: StSav012 Date: Tue, 24 Oct 2023 20:34:57 +0300 Subject: [PATCH 14/15] Test whether `QAction.setShortcuts` doesn't support a `Qt.Key` argument as suggested by @ccordoba12 The test is expected to fail on Qt6 >= 6.5. The earlier versions of PyQt/PySide are patched against the fail, so don't test them. --- qtpy/tests/test_qtgui.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/qtpy/tests/test_qtgui.py b/qtpy/tests/test_qtgui.py index 851d00ad..27e3d170 100644 --- a/qtpy/tests/test_qtgui.py +++ b/qtpy/tests/test_qtgui.py @@ -3,12 +3,14 @@ import sys import pytest +from packaging.version import parse from qtpy import ( PYQT5, PYQT_VERSION, PYSIDE2, PYSIDE6, + QT_VERSION, QtCore, QtGui, QtWidgets, @@ -189,11 +191,28 @@ def test_QAction_functions(qtbot): action.setShortcuts(QtGui.QKeySequence.UnknownKey) action.setShortcut(QtCore.Qt.Key_F1) action.setShortcuts([QtCore.Qt.Key_F1]) - # The following line is wrong even for Qt6 == 6.6. + # The following line fails even for Qt6 == 6.6. # Don't test the function with a single `QtCore.Qt.Key` argument. + # See the following test. # action.setShortcuts(QtCore.Qt.Key_F1) +@pytest.mark.skipif( + parse(QT_VERSION) < parse('6.5.0'), + reason="Qt6 >= 6.5 specific test", +) +@pytest.mark.skipif( + sys.platform == "darwin" and sys.version_info[:2] == (3, 7), + reason="Stalls on macOS CI with Python 3.7", +) +@pytest.mark.xfail(strict=True) +def test_QAction_functions_fail(qtbot): + """Test `QtGui.QAction.setShortcuts` compatibility with `QtCore.Qt.Key` type.""" + action = QtGui.QAction("QtPy", None) + # The following line is wrong even for Qt6 == 6.6. + action.setShortcuts(QtCore.Qt.Key_F1) + + def test_opengl_imports(): """ Test for presence of QOpenGL* classes. From c8d1144dda8893d6a442caadfadcd1c199779f18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:35:16 +0000 Subject: [PATCH 15/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- qtpy/QtGui.py | 1 - qtpy/QtWidgets.py | 4 ++-- qtpy/tests/test_qtgui.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qtpy/QtGui.py b/qtpy/QtGui.py index 514b2ae0..3e8a4fb3 100644 --- a/qtpy/QtGui.py +++ b/qtpy/QtGui.py @@ -21,7 +21,6 @@ set_shortcuts, ) - _missing_optional_names = {} _QTOPENGL_NAMES = { diff --git a/qtpy/QtWidgets.py b/qtpy/QtWidgets.py index 820dcf5b..1b225421 100644 --- a/qtpy/QtWidgets.py +++ b/qtpy/QtWidgets.py @@ -36,13 +36,13 @@ def __getattr__(name): from PyQt5.QtWidgets import * elif PYQT6: from PyQt6 import QtWidgets - from PyQt6.QtWidgets import * from PyQt6.QtGui import ( QActionGroup, QFileSystemModel, QShortcut, QUndoCommand, ) + from PyQt6.QtWidgets import * from qtpy.QtGui import QAction # See spyder-ide/qtpy#461 @@ -111,8 +111,8 @@ def __getattr__(name): elif PYSIDE2: from PySide2.QtWidgets import * elif PYSIDE6: - from PySide6.QtWidgets import * from PySide6.QtGui import QActionGroup, QShortcut, QUndoCommand + from PySide6.QtWidgets import * from qtpy.QtGui import QAction # See spyder-ide/qtpy#461 diff --git a/qtpy/tests/test_qtgui.py b/qtpy/tests/test_qtgui.py index 27e3d170..23fc0158 100644 --- a/qtpy/tests/test_qtgui.py +++ b/qtpy/tests/test_qtgui.py @@ -198,7 +198,7 @@ def test_QAction_functions(qtbot): @pytest.mark.skipif( - parse(QT_VERSION) < parse('6.5.0'), + parse(QT_VERSION) < parse("6.5.0"), reason="Qt6 >= 6.5 specific test", ) @pytest.mark.skipif(