Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Make QAction.setShortcut and setShortcuts accept many types #461

Merged
merged 16 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion qtpy/QtGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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.4
if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"):
from functools import partialmethod

from ._utils import (
set_shortcut,
set_shortcuts,
)
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved

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!
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
QAction.setShortcut is not _action_set_shortcut
or QAction.setShortcuts is not _action_set_shortcuts
):
QAction = _QAction
51 changes: 43 additions & 8 deletions qtpy/QtWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
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,
Expand All @@ -36,14 +35,15 @@ def __getattr__(name):
from PyQt5.QtWidgets import *
elif PYQT6:
from PyQt6 import QtWidgets
from PyQt6.QtGui import (
from PyQt6.QtWidgets import *

from qtpy.QtGui import (
QAction,
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
QActionGroup,
QFileSystemModel,
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.
Expand Down Expand Up @@ -110,9 +110,10 @@ def __getattr__(name):
elif PYSIDE2:
from PySide2.QtWidgets import *
elif PYSIDE6:
from PySide6.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand
from PySide6.QtWidgets import *

from qtpy.QtGui import QAction, QActionGroup, QShortcut, QUndoCommand
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved

# 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/
Expand Down Expand Up @@ -208,10 +209,44 @@ def __getattr__(name):
"directory",
)

# 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(
# Make `addAction` compatible with Qt6 >= 6.4
if PYQT5 or PYSIDE2 or parse(_qt_version) < parse("6.4"):
from ._utils import add_action
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved

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!
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
QToolBar.addAction is not _toolbar_add_action
):
QToolBar = _QToolBar
60 changes: 47 additions & 13 deletions qtpy/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,57 @@ 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,
],
Expand All @@ -105,16 +138,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,
],
Expand All @@ -123,11 +155,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(
Expand All @@ -136,12 +168,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):
Expand Down
16 changes: 16 additions & 0 deletions qtpy/tests/test_qtgui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_F1)
action.setShortcuts([QtCore.Qt.Key_F1])
# The following line is wrong even for Qt6 == 6.6:
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
# action.setShortcuts(QtCore.Qt.Key_F1)


def test_opengl_imports():
"""
Test for presence of QOpenGL* classes.
Expand Down
6 changes: 3 additions & 3 deletions qtpy/tests/test_qtwidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_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()
Expand Down Expand Up @@ -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_F1)
toolbar.addAction(
QtGui.QIcon(),
"QtPy with an icon and a shortcut",
Expand Down