Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions qtpy/QtWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ def __getattr__(name):
"directory",
)

if PYQT5 or PYSIDE2 or _parse_version(_qt_version) < _parse_version("6.4"):
# Make `addAction` compatible with Qt6 >= 6.4
if PYQT5 or PYSIDE2 or _parse_version(_qt_version) < _parse_version("6.3"):
# Make `addAction` compatible with Qt6 >= 6.3
_menu_add_action = partialmethod(
add_action,
old_add_action=QMenu.addAction,
Expand Down
110 changes: 40 additions & 70 deletions qtpy/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,85 +102,55 @@ def set_shortcuts(self, shortcuts, old_set_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 .QtCore import QObject, Qt
def add_action(self, *args, old_add_action, **kwargs):
"""
Re-order arguments when calling the `addAction` function with the signature
introduced in Qt 6.3.
"""
import warnings
from collections.abc import Callable
from .QtCore import Qt
from .QtGui import QIcon, QKeySequence

action: QAction
icon: QIcon
text: str
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, Qt.Key, str, int),
QObject,
bytes,
],
)
):
if len(args) == 2:
text, shortcut = args
action = old_add_action(self, text)
action.setShortcut(shortcut)
elif len(args) == 3:
text, shortcut, receiver = args
action = old_add_action(self, text, receiver)
action.setShortcut(shortcut)
elif len(args) == 4:
text, shortcut, receiver, member = args
action = old_add_action(self, text, receiver, member, shortcut)
else:
action = old_add_action(self, *args)
elif all(
isinstance(arg, t)
for arg, t in zip(
args,
[
QIcon,
str,
(QKeySequence, QKeySequence.StandardKey, Qt.Key, str, int),
QObject,
bytes,
],
)
):
if len(args) == 3:
icon, text, shortcut = args
action = old_add_action(self, icon, text)
action.setShortcut(shortcut)
elif len(args) == 4:
icon, text, shortcut, receiver = args
action = old_add_action(self, icon, text, receiver)
action.setShortcut(shortcut)
elif len(args) == 5:
icon, text, shortcut, receiver, member = args
action = old_add_action(
self,
icon,
text,
receiver,
member,
shortcut,
)
else:
action = old_add_action(self, *args)
new_args = list(args)
if new_args and isinstance(new_args[0], QIcon):
icon = new_args.pop(0)
else:
action = old_add_action(self, *args)
icon = None
shortcut = kwargs.pop("shortcut", None)
connection_type = kwargs.pop("type", None)
if connection_type:
warnings.warn("type argument is not supported in Qt<6.3")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These signatures got added in 6.3 and won't error in Qt5 but type is not supported. How should this be handled?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showing a warning if someone uses the argument with a binding that doesn't support it makes sense to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave the AttributeError, should we then also change this warning to an exception?

AttributeError: QtWidgets.QMenu.addAction(): unsupported keyword 'type'


shortcut_types = (QKeySequence, QKeySequence.StandardKey, Qt.Key, str, int)
if len(new_args) > 1 and isinstance(new_args[1], shortcut_types):
# Qt6.3 signature (text, shortcut, receiver, member)
shortcut = new_args.pop(1)
elif (
len(new_args) > 2
and isinstance(new_args[1], Callable)
and isinstance(new_args[2], shortcut_types)
):
# Qt5 signature (arg__1, arg__2, arg__3)
shortcut = new_args.pop(2)
elif len(new_args) > 3 and isinstance(new_args[3], shortcut_types):
# Qt5 signature (text, receiver, member, shortcut)
shortcut = new_args.pop(3)

if icon is not None:
new_args.insert(0, icon)
action = old_add_action(self, *new_args, **kwargs)

if shortcut is not None:
action.setShortcut(shortcut)

return action


def static_method_kwargs_wrapper(func, from_kwarg_name, to_kwarg_name):
"""
Helper function to manage `from_kwarg_name` to `to_kwarg_name` kwargs name changes in static methods.
Helper function to manage `from_kwarg_name` to `to_kwarg_name` kwargs name changes
in static methods.

Makes static methods accept the `from_kwarg_name` kwarg as `to_kwarg_name`.
"""
Expand Down
52 changes: 46 additions & 6 deletions tests/test_qtwidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,53 @@ def test_QMenu_functions(qtbot):
# A window is required for static calls
window = QtWidgets.QMainWindow()
menu = QtWidgets.QMenu(window)
menu.addAction("QtPy")
menu.addAction("QtPy with a Qt.Key shortcut", QtCore.Qt.Key_F1)
menu.addAction(
QtGui.QIcon(),
"QtPy with an icon and a QKeySequence shortcut",
QtGui.QKeySequence.UnknownKey,

# Actions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests have only been added to QMenu, in theory qtpy supports these for QToolBar as well.
But in Qt6 these also are valid for QWidget. But in Qt5 they don't exist at all.
Does QtPy provide backwards compatibility for everything? I'm a bit confused with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a little bit of context the original PR from where this patch approach comes is #437 The idea shown there only takes care of the more simple cases of compatibility for QMenu and QToolbar so I will say we currently only support backwards compatibility for QMenu and QToolbar addAction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added tests for QToolbar.


class Receiver(QtCore.QObject):
triggered = QtCore.Signal

func = lambda: print('Action')
icon = QtGui.QIcon()
text = "QtPy"
shortcuts = (
QtGui.QKeySequence.StandardKey.HelpContents,
QtCore.Qt.Key_F1,
'F1',
1,
)
receiver = Receiver(parent=window)
member = 'triggered'
connection_type = QtCore.Qt.ConnectionType.DirectConnection
action = QtWidgets.QAction()

menu.addAction(text)
menu.addAction(text, func)
menu.addAction(icon, text)
menu.addAction(icon, text, func)

menu.addAction(action)

for shortcut in shortcuts:
menu.addAction(text, func, shortcut)
menu.addAction(text, func, shortcut=shortcut)
menu.addAction(text, receiver, member, shortcut)
menu.addAction(icon, text, func, shortcut)
menu.addAction(icon, text, func, shortcut=shortcut)
menu.addAction(icon, text, receiver, member, shortcut)

# Qt 5
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These signatures are valid in Qt5 but not Qt6, do we still need to support them?
They won't error with Qt5 but will error with Qt6.

Copy link
Member

@dalthviz dalthviz Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they should still be available to ensure things with Qt5 bindings work. If no Qt6 bindings signature is usable with the passed arguments we should detect that case and probably at the very least show a warning explaining the situation (something like no Qt6 compatible signature is available please use one of the following to keep compatibility between Qt versions...).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we'd have to also change this section here:

if PYQT5 or PYSIDE2 or _parse_version(_qt_version) < _parse_version("6.4"):

I feel like the error is pretty clear already:
AttributeError: PySide6.QtWidgets.QMenu.addAction(): unsupported keyword 'shortcut'

This way we're also ensuring that we're not adding unnecessary code for when the user uses a modern Qt6 binding.

Are you okay, if we just leave it as is?

menu.addAction(text, receiver, member, shortcut=shortcut)
menu.addAction(icon, text, receiver, member, shortcut=shortcut)

# Qt>=6.3 signature
menu.addAction(text, shortcut)
menu.addAction(text, shortcut, receiver, member, type=connection_type)
menu.addAction(icon, text, shortcut)
menu.addAction(
icon, text, shortcut, receiver, member, type=connection_type
)

window.show()

with qtbot.waitExposed(window):
Expand Down
Loading