summaryrefslogtreecommitdiff
path: root/qutebrowser/keyinput/keyutils.py
diff options
context:
space:
mode:
Diffstat (limited to 'qutebrowser/keyinput/keyutils.py')
-rw-r--r--qutebrowser/keyinput/keyutils.py483
1 files changed, 281 insertions, 202 deletions
diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py
index 0362e09f3..9a0deffc5 100644
--- a/qutebrowser/keyinput/keyutils.py
+++ b/qutebrowser/keyinput/keyutils.py
@@ -24,8 +24,8 @@ comes to keys/modifiers. Many places (such as QKeyEvent::key()) don't actually
return a Qt::Key, they return an int.
To make things worse, when talking about a "key", sometimes Qt means a Qt::Key
-member. However, sometimes it means a Qt::Key member ORed with
-Qt.KeyboardModifiers...
+member. However, sometimes it means a Qt::Key member ORed with a
+Qt.KeyboardModifier...
Because of that, _assert_plain_key() and _assert_plain_modifier() make sure we
handle what we actually think we do.
@@ -33,27 +33,52 @@ handle what we actually think we do.
import itertools
import dataclasses
-from typing import cast, overload, Iterable, Iterator, List, Mapping, Optional, Union
+from typing import Iterator, Iterable, List, Mapping, Optional, Union, overload, cast
-from PyQt5.QtCore import Qt, QEvent
-from PyQt5.QtGui import QKeySequence, QKeyEvent
+from qutebrowser.qt import machinery
+from qutebrowser.qt.core import Qt, QEvent
+from qutebrowser.qt.gui import QKeySequence, QKeyEvent
+if machinery.IS_QT6:
+ # FIXME:qt6 (lint) how come pylint isn't picking this up with both backends
+ # installed?
+ from qutebrowser.qt.core import QKeyCombination # pylint: disable=no-name-in-module
+else:
+ QKeyCombination = None # QKeyCombination was added in Qt 6
-from qutebrowser.utils import utils
+from qutebrowser.utils import utils, qtutils, debug
+
+
+class InvalidKeyError(Exception):
+
+ """Raised when a key can't be represented by PyQt.
+
+ WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2022-April/044607.html
+ Should be fixed in PyQt 6.3.1 (or 6.4.0?).
+ """
# Map Qt::Key values to their Qt::KeyboardModifier value.
_MODIFIER_MAP = {
- Qt.Key_Shift: Qt.ShiftModifier,
- Qt.Key_Control: Qt.ControlModifier,
- Qt.Key_Alt: Qt.AltModifier,
- Qt.Key_Meta: Qt.MetaModifier,
- Qt.Key_AltGr: Qt.GroupSwitchModifier,
- Qt.Key_Mode_switch: Qt.GroupSwitchModifier,
+ Qt.Key.Key_Shift: Qt.KeyboardModifier.ShiftModifier,
+ Qt.Key.Key_Control: Qt.KeyboardModifier.ControlModifier,
+ Qt.Key.Key_Alt: Qt.KeyboardModifier.AltModifier,
+ Qt.Key.Key_Meta: Qt.KeyboardModifier.MetaModifier,
+ Qt.Key.Key_AltGr: Qt.KeyboardModifier.GroupSwitchModifier,
+ Qt.Key.Key_Mode_switch: Qt.KeyboardModifier.GroupSwitchModifier,
}
-_NIL_KEY = Qt.Key(0)
+try:
+ _NIL_KEY: Union[Qt.Key, int] = Qt.Key(0)
+except ValueError:
+ # WORKAROUND for
+ # https://www.riverbankcomputing.com/pipermail/pyqt/2022-April/044607.html
+ _NIL_KEY = 0
-_ModifierType = Union[Qt.KeyboardModifier, Qt.KeyboardModifiers]
+_ModifierType = Qt.KeyboardModifier
+if machinery.IS_QT6:
+ _KeyInfoType = QKeyCombination
+else:
+ _KeyInfoType = int
_SPECIAL_NAMES = {
@@ -63,123 +88,101 @@ _SPECIAL_NAMES = {
# For dead/combining keys, we return the corresponding non-combining
# key, as that's easier to add to the config.
- Qt.Key_Super_L: 'Super L',
- Qt.Key_Super_R: 'Super R',
- Qt.Key_Hyper_L: 'Hyper L',
- Qt.Key_Hyper_R: 'Hyper R',
- Qt.Key_Direction_L: 'Direction L',
- Qt.Key_Direction_R: 'Direction R',
-
- Qt.Key_Shift: 'Shift',
- Qt.Key_Control: 'Control',
- Qt.Key_Meta: 'Meta',
- Qt.Key_Alt: 'Alt',
-
- Qt.Key_AltGr: 'AltGr',
- Qt.Key_Multi_key: 'Multi key',
- Qt.Key_SingleCandidate: 'Single Candidate',
- Qt.Key_Mode_switch: 'Mode switch',
- Qt.Key_Dead_Grave: '`',
- Qt.Key_Dead_Acute: '´',
- Qt.Key_Dead_Circumflex: '^',
- Qt.Key_Dead_Tilde: '~',
- Qt.Key_Dead_Macron: '¯',
- Qt.Key_Dead_Breve: '˘',
- Qt.Key_Dead_Abovedot: '˙',
- Qt.Key_Dead_Diaeresis: '¨',
- Qt.Key_Dead_Abovering: '˚',
- Qt.Key_Dead_Doubleacute: '˝',
- Qt.Key_Dead_Caron: 'ˇ',
- Qt.Key_Dead_Cedilla: '¸',
- Qt.Key_Dead_Ogonek: '˛',
- Qt.Key_Dead_Iota: 'Iota',
- Qt.Key_Dead_Voiced_Sound: 'Voiced Sound',
- Qt.Key_Dead_Semivoiced_Sound: 'Semivoiced Sound',
- Qt.Key_Dead_Belowdot: 'Belowdot',
- Qt.Key_Dead_Hook: 'Hook',
- Qt.Key_Dead_Horn: 'Horn',
-
- Qt.Key_Dead_Stroke: '\u0335', # '̵'
- Qt.Key_Dead_Abovecomma: '\u0313', # '̓'
- Qt.Key_Dead_Abovereversedcomma: '\u0314', # '̔'
- Qt.Key_Dead_Doublegrave: '\u030f', # '̏'
- Qt.Key_Dead_Belowring: '\u0325', # '̥'
- Qt.Key_Dead_Belowmacron: '\u0331', # '̱'
- Qt.Key_Dead_Belowcircumflex: '\u032d', # '̭'
- Qt.Key_Dead_Belowtilde: '\u0330', # '̰'
- Qt.Key_Dead_Belowbreve: '\u032e', # '̮'
- Qt.Key_Dead_Belowdiaeresis: '\u0324', # '̤'
- Qt.Key_Dead_Invertedbreve: '\u0311', # '̑'
- Qt.Key_Dead_Belowcomma: '\u0326', # '̦'
- Qt.Key_Dead_Currency: '¤',
- Qt.Key_Dead_a: 'a',
- Qt.Key_Dead_A: 'A',
- Qt.Key_Dead_e: 'e',
- Qt.Key_Dead_E: 'E',
- Qt.Key_Dead_i: 'i',
- Qt.Key_Dead_I: 'I',
- Qt.Key_Dead_o: 'o',
- Qt.Key_Dead_O: 'O',
- Qt.Key_Dead_u: 'u',
- Qt.Key_Dead_U: 'U',
- Qt.Key_Dead_Small_Schwa: 'ə',
- Qt.Key_Dead_Capital_Schwa: 'Ə',
- Qt.Key_Dead_Greek: 'Greek',
- Qt.Key_Dead_Lowline: '\u0332', # '̲'
- Qt.Key_Dead_Aboveverticalline: '\u030d', # '̍'
- Qt.Key_Dead_Belowverticalline: '\u0329',
- Qt.Key_Dead_Longsolidusoverlay: '\u0338', # '̸'
-
- Qt.Key_Memo: 'Memo',
- Qt.Key_ToDoList: 'To Do List',
- Qt.Key_Calendar: 'Calendar',
- Qt.Key_ContrastAdjust: 'Contrast Adjust',
- Qt.Key_LaunchG: 'Launch (G)',
- Qt.Key_LaunchH: 'Launch (H)',
-
- Qt.Key_MediaLast: 'Media Last',
-
- Qt.Key_unknown: 'Unknown',
+ Qt.Key.Key_Super_L: 'Super L',
+ Qt.Key.Key_Super_R: 'Super R',
+ Qt.Key.Key_Hyper_L: 'Hyper L',
+ Qt.Key.Key_Hyper_R: 'Hyper R',
+ Qt.Key.Key_Direction_L: 'Direction L',
+ Qt.Key.Key_Direction_R: 'Direction R',
+
+ Qt.Key.Key_Shift: 'Shift',
+ Qt.Key.Key_Control: 'Control',
+ Qt.Key.Key_Meta: 'Meta',
+ Qt.Key.Key_Alt: 'Alt',
+ Qt.Key.Key_AltGr: 'AltGr',
+
+ Qt.Key.Key_Multi_key: 'Multi key',
+ Qt.Key.Key_SingleCandidate: 'Single Candidate',
+ Qt.Key.Key_Mode_switch: 'Mode switch',
+
+ Qt.Key.Key_Dead_Grave: '`',
+ Qt.Key.Key_Dead_Acute: '´',
+ Qt.Key.Key_Dead_Circumflex: '^',
+ Qt.Key.Key_Dead_Tilde: '~',
+ Qt.Key.Key_Dead_Macron: '¯',
+ Qt.Key.Key_Dead_Breve: '˘',
+ Qt.Key.Key_Dead_Abovedot: '˙',
+ Qt.Key.Key_Dead_Diaeresis: '¨',
+ Qt.Key.Key_Dead_Abovering: '˚',
+ Qt.Key.Key_Dead_Doubleacute: '˝',
+ Qt.Key.Key_Dead_Caron: 'ˇ',
+ Qt.Key.Key_Dead_Cedilla: '¸',
+ Qt.Key.Key_Dead_Ogonek: '˛',
+ Qt.Key.Key_Dead_Iota: 'Iota',
+ Qt.Key.Key_Dead_Voiced_Sound: 'Voiced Sound',
+ Qt.Key.Key_Dead_Semivoiced_Sound: 'Semivoiced Sound',
+ Qt.Key.Key_Dead_Belowdot: 'Belowdot',
+ Qt.Key.Key_Dead_Hook: 'Hook',
+ Qt.Key.Key_Dead_Horn: 'Horn',
+ Qt.Key.Key_Dead_Stroke: '\u0335', # '̵'
+ Qt.Key.Key_Dead_Abovecomma: '\u0313', # '̓'
+ Qt.Key.Key_Dead_Abovereversedcomma: '\u0314', # '̔'
+ Qt.Key.Key_Dead_Doublegrave: '\u030f', # '̏'
+ Qt.Key.Key_Dead_Belowring: '\u0325', # '̥'
+ Qt.Key.Key_Dead_Belowmacron: '\u0331', # '̱'
+ Qt.Key.Key_Dead_Belowcircumflex: '\u032d', # '̭'
+ Qt.Key.Key_Dead_Belowtilde: '\u0330', # '̰'
+ Qt.Key.Key_Dead_Belowbreve: '\u032e', # '̮'
+ Qt.Key.Key_Dead_Belowdiaeresis: '\u0324', # '̤'
+ Qt.Key.Key_Dead_Invertedbreve: '\u0311', # '̑'
+ Qt.Key.Key_Dead_Belowcomma: '\u0326', # '̦'
+ Qt.Key.Key_Dead_Currency: '¤',
+ Qt.Key.Key_Dead_a: 'a',
+ Qt.Key.Key_Dead_A: 'A',
+ Qt.Key.Key_Dead_e: 'e',
+ Qt.Key.Key_Dead_E: 'E',
+ Qt.Key.Key_Dead_i: 'i',
+ Qt.Key.Key_Dead_I: 'I',
+ Qt.Key.Key_Dead_o: 'o',
+ Qt.Key.Key_Dead_O: 'O',
+ Qt.Key.Key_Dead_u: 'u',
+ Qt.Key.Key_Dead_U: 'U',
+ Qt.Key.Key_Dead_Small_Schwa: 'ə',
+ Qt.Key.Key_Dead_Capital_Schwa: 'Ə',
+ Qt.Key.Key_Dead_Greek: 'Greek',
+ Qt.Key.Key_Dead_Lowline: '\u0332', # '̲'
+ Qt.Key.Key_Dead_Aboveverticalline: '\u030d', # '̍'
+ Qt.Key.Key_Dead_Belowverticalline: '\u0329',
+ Qt.Key.Key_Dead_Longsolidusoverlay: '\u0338', # '̸'
+
+ Qt.Key.Key_MediaLast: 'Media Last',
+
+ Qt.Key.Key_unknown: 'Unknown',
# For some keys, we just want a different name
- Qt.Key_Escape: 'Escape',
+ Qt.Key.Key_Escape: 'Escape',
_NIL_KEY: 'nil',
}
def _assert_plain_key(key: Qt.Key) -> None:
- """Make sure this is a key without KeyboardModifiers mixed in."""
- assert not key & Qt.KeyboardModifierMask, hex(key)
+ """Make sure this is a key without KeyboardModifier mixed in."""
+ key_int = qtutils.extract_enum_val(key)
+ mask = qtutils.extract_enum_val(Qt.KeyboardModifier.KeyboardModifierMask)
+ assert not key_int & mask, hex(key_int)
def _assert_plain_modifier(key: _ModifierType) -> None:
"""Make sure this is a modifier without a key mixed in."""
- mask = Qt.KeyboardModifierMask
- assert not key & ~mask, hex(key)
+ key_int = qtutils.extract_enum_val(key)
+ mask = qtutils.extract_enum_val(Qt.KeyboardModifier.KeyboardModifierMask)
+ assert not key_int & ~mask, hex(key_int)
def _is_printable(key: Qt.Key) -> bool:
_assert_plain_key(key)
- return key <= 0xff and key not in [Qt.Key_Space, _NIL_KEY]
-
-
-def is_special(key: Qt.Key, modifiers: _ModifierType) -> bool:
- """Check whether this key requires special key syntax."""
- _assert_plain_key(key)
- _assert_plain_modifier(modifiers)
- return not (_is_printable(key) and
- modifiers in [Qt.ShiftModifier, Qt.NoModifier])
-
-
-def is_modifier_key(key: Qt.Key) -> bool:
- """Test whether the given key is a modifier.
-
- This only considers keys which are part of Qt::KeyboardModifiers, i.e.
- which would interrupt a key chain like "yY" when handled.
- """
- _assert_plain_key(key)
- return key in _MODIFIER_MAP
+ return key <= 0xff and key not in [Qt.Key.Key_Space, _NIL_KEY]
def _is_surrogate(key: Qt.Key) -> bool:
@@ -248,20 +251,20 @@ def _key_to_string(key: Qt.Key) -> str:
def _modifiers_to_string(modifiers: _ModifierType) -> str:
- """Convert the given Qt::KeyboardModifiers to a string.
+ """Convert the given Qt::KeyboardModifier to a string.
- Handles Qt.GroupSwitchModifier because Qt doesn't handle that as a
+ Handles Qt.KeyboardModifier.GroupSwitchModifier because Qt doesn't handle that as a
modifier.
"""
_assert_plain_modifier(modifiers)
- altgr = cast(Qt.KeyboardModifiers, Qt.GroupSwitchModifier)
+ altgr = Qt.KeyboardModifier.GroupSwitchModifier
if modifiers & altgr:
- modifiers &= ~altgr
+ modifiers &= ~altgr # type: ignore[assignment]
result = 'AltGr+'
else:
result = ''
- result += QKeySequence(modifiers).toString()
+ result += QKeySequence(qtutils.extract_enum_val(modifiers)).toString()
_check_valid_utf8(result, modifiers)
return result
@@ -344,11 +347,29 @@ class KeyInfo:
Attributes:
key: A Qt::Key member.
- modifiers: A Qt::KeyboardModifiers enum value.
+ modifiers: A Qt::KeyboardModifier enum value.
"""
key: Qt.Key
- modifiers: _ModifierType
+ modifiers: _ModifierType = Qt.KeyboardModifier.NoModifier
+
+ def __post_init__(self) -> None:
+ """Run some validation on the key/modifier values."""
+ # This is mainly useful while porting from Qt 5 to 6.
+ # FIXME:qt6 do we want to remove or keep this (and fix the remaining
+ # issues) when done?
+ # assert isinstance(self.key, Qt.Key), self.key
+ # assert isinstance(self.modifiers, Qt.KeyboardModifier), self.modifiers
+ _assert_plain_key(self.key)
+ _assert_plain_modifier(self.modifiers)
+
+ def __repr__(self) -> str:
+ return utils.get_repr(
+ self,
+ key=debug.qenum_key(Qt, self.key, klass=Qt.Key),
+ modifiers=debug.qflags_key(Qt, self.modifiers, klass=Qt.KeyboardModifier),
+ text=str(self),
+ )
@classmethod
def from_event(cls, e: QKeyEvent) -> 'KeyInfo':
@@ -357,11 +378,35 @@ class KeyInfo:
This makes sure that key/modifiers are never mixed and also remaps
UTF-16 surrogates to work around QTBUG-72776.
"""
- key = _remap_unicode(Qt.Key(e.key()), e.text())
- modifiers = e.modifiers()
- _assert_plain_key(key)
- _assert_plain_modifier(modifiers)
- return cls(key, cast(Qt.KeyboardModifier, modifiers))
+ try:
+ key = Qt.Key(e.key())
+ except ValueError as ex:
+ raise InvalidKeyError(str(ex))
+ key = _remap_unicode(key, e.text())
+ modifiers = cast(Qt.KeyboardModifier, e.modifiers())
+ return cls(key, modifiers)
+
+ @classmethod
+ def from_qt(cls, combination: _KeyInfoType) -> 'KeyInfo':
+ """Construct a KeyInfo from a Qt5-style int or Qt6-style QKeyCombination."""
+ if machinery.IS_QT5:
+ assert isinstance(combination, int)
+ key = Qt.Key(
+ int(combination) & ~Qt.KeyboardModifier.KeyboardModifierMask)
+ modifiers = Qt.KeyboardModifier(
+ int(combination) & Qt.KeyboardModifier.KeyboardModifierMask)
+ return cls(key, modifiers)
+ else:
+ # QKeyCombination is now guaranteed to be available here
+ assert isinstance(combination, QKeyCombination)
+ try:
+ key = combination.key()
+ except ValueError as e:
+ raise InvalidKeyError(str(e))
+ return cls(
+ key=key,
+ modifiers=combination.keyboardModifiers(),
+ )
def __str__(self) -> str:
"""Convert this KeyInfo to a meaningful name.
@@ -370,11 +415,11 @@ class KeyInfo:
A name of the key (combination) as a string.
"""
key_string = _key_to_string(self.key)
- modifiers = int(self.modifiers)
+ modifiers = self.modifiers
if self.key in _MODIFIER_MAP:
# Don't return e.g. <Shift+Shift>
- modifiers &= ~_MODIFIER_MAP[self.key]
+ modifiers &= ~_MODIFIER_MAP[self.key] # type: ignore[assignment]
elif _is_printable(self.key):
# "normal" binding
if not key_string: # pragma: no cover
@@ -382,11 +427,11 @@ class KeyInfo:
.format(self.key))
assert len(key_string) == 1, key_string
- if self.modifiers == Qt.ShiftModifier:
- assert not is_special(self.key, self.modifiers)
+ if self.modifiers == Qt.KeyboardModifier.ShiftModifier:
+ assert not self.is_special()
return key_string.upper()
- elif self.modifiers == Qt.NoModifier:
- assert not is_special(self.key, self.modifiers)
+ elif self.modifiers == Qt.KeyboardModifier.NoModifier:
+ assert not self.is_special()
return key_string.lower()
else:
# Use special binding syntax, but <Ctrl-a> instead of <Ctrl-A>
@@ -395,19 +440,19 @@ class KeyInfo:
modifiers = Qt.KeyboardModifier(modifiers)
# "special" binding
- assert is_special(self.key, self.modifiers)
+ assert self.is_special()
modifier_string = _modifiers_to_string(modifiers)
return '<{}{}>'.format(modifier_string, key_string)
def text(self) -> str:
"""Get the text which would be displayed when pressing this key."""
control = {
- Qt.Key_Space: ' ',
- Qt.Key_Tab: '\t',
- Qt.Key_Backspace: '\b',
- Qt.Key_Return: '\r',
- Qt.Key_Enter: '\r',
- Qt.Key_Escape: '\x1b',
+ Qt.Key.Key_Space: ' ',
+ Qt.Key.Key_Tab: '\t',
+ Qt.Key.Key_Backspace: '\b',
+ Qt.Key.Key_Return: '\r',
+ Qt.Key.Key_Enter: '\r',
+ Qt.Key.Key_Escape: '\x1b',
}
if self.key in control:
@@ -416,17 +461,51 @@ class KeyInfo:
return ''
text = QKeySequence(self.key).toString()
- if not self.modifiers & Qt.ShiftModifier:
+ if not self.modifiers & Qt.KeyboardModifier.ShiftModifier:
text = text.lower()
return text
- def to_event(self, typ: QEvent.Type = QEvent.KeyPress) -> QKeyEvent:
+ def to_event(self, typ: QEvent.Type = QEvent.Type.KeyPress) -> QKeyEvent:
"""Get a QKeyEvent from this KeyInfo."""
return QKeyEvent(typ, self.key, self.modifiers, self.text())
- def to_int(self) -> int:
- """Get the key as an integer (with key/modifiers)."""
- return int(self.key) | int(self.modifiers)
+ def to_qt(self) -> _KeyInfoType:
+ """Get something suitable for a QKeySequence."""
+ if machinery.IS_QT5:
+ return int(self.key) | int(self.modifiers)
+ else:
+ try:
+ # FIXME:qt6 We might want to consider only supporting KeyInfo to be
+ # instanciated with a real Qt.Key, not with ints. See __post_init__.
+ key = Qt.Key(self.key)
+ except ValueError as e:
+ # WORKAROUND for
+ # https://www.riverbankcomputing.com/pipermail/pyqt/2022-April/044607.html
+ raise InvalidKeyError(e)
+
+ return QKeyCombination(self.modifiers, key)
+
+ def with_stripped_modifiers(self, modifiers: Qt.KeyboardModifier) -> "KeyInfo":
+ mods = self.modifiers & ~modifiers
+ return KeyInfo(key=self.key, modifiers=mods) # type: ignore[arg-type]
+
+ def is_special(self) -> bool:
+ """Check whether this key requires special key syntax."""
+ return not (
+ _is_printable(self.key) and
+ self.modifiers in [
+ Qt.KeyboardModifier.ShiftModifier,
+ Qt.KeyboardModifier.NoModifier,
+ ]
+ )
+
+ def is_modifier_key(self) -> bool:
+ """Test whether the given key is a modifier.
+
+ This only considers keys which are part of Qt::KeyboardModifier, i.e.
+ which would interrupt a key chain like "yY" when handled.
+ """
+ return self.key in _MODIFIER_MAP
class KeySequence:
@@ -448,21 +527,20 @@ class KeySequence:
_MAX_LEN = 4
- def __init__(self, *keys: int) -> None:
+ def __init__(self, *keys: KeyInfo) -> None:
self._sequences: List[QKeySequence] = []
for sub in utils.chunk(keys, self._MAX_LEN):
- args = [self._convert_key(key) for key in sub]
+ try:
+ args = [info.to_qt() for info in sub]
+ except InvalidKeyError as e:
+ raise KeyParseError(keystr=None, error=f"Got invalid key: {e}")
+
sequence = QKeySequence(*args)
self._sequences.append(sequence)
if keys:
assert self
self._validate()
- def _convert_key(self, key: Union[int, Qt.KeyboardModifiers]) -> int:
- """Convert a single key for QKeySequence."""
- assert isinstance(key, (int, Qt.KeyboardModifiers)), key
- return int(key)
-
def __str__(self) -> str:
parts = []
for info in self:
@@ -471,11 +549,11 @@ class KeySequence:
def __iter__(self) -> Iterator[KeyInfo]:
"""Iterate over KeyInfo objects."""
- for key_and_modifiers in self._iter_keys():
- key = Qt.Key(int(key_and_modifiers) & ~Qt.KeyboardModifierMask)
- modifiers = Qt.KeyboardModifiers(
- int(key_and_modifiers) & Qt.KeyboardModifierMask)
- yield KeyInfo(key=key, modifiers=modifiers)
+ # FIXME:mypy Stubs seem to be unaware that iterating a QKeySequence produces
+ # _KeyInfoType
+ sequences = cast(List[Iterable[_KeyInfoType]], self._sequences)
+ for combination in itertools.chain.from_iterable(sequences):
+ yield KeyInfo.from_qt(combination)
def __repr__(self) -> str:
return utils.get_repr(self, keys=str(self))
@@ -520,21 +598,19 @@ class KeySequence:
...
def __getitem__(self, item: Union[int, slice]) -> Union[KeyInfo, 'KeySequence']:
+ infos = list(self)
if isinstance(item, slice):
- keys = list(self._iter_keys())
- return self.__class__(*keys[item])
+ return self.__class__(*infos[item])
else:
- infos = list(self)
return infos[item]
- def _iter_keys(self) -> Iterator[int]:
- sequences = cast(Iterable[Iterable[int]], self._sequences)
- return itertools.chain.from_iterable(sequences)
-
def _validate(self, keystr: str = None) -> None:
- for info in self:
- if info.key < Qt.Key_Space or info.key >= Qt.Key_unknown:
- raise KeyParseError(keystr, "Got invalid key!")
+ try:
+ for info in self:
+ if info.key < Qt.Key.Key_Space or info.key >= Qt.Key.Key_unknown:
+ raise KeyParseError(keystr, "Got invalid key!")
+ except InvalidKeyError as e:
+ raise KeyParseError(keystr, f"Got invalid key: {e}")
for seq in self._sequences:
if not seq:
@@ -552,12 +628,12 @@ class KeySequence:
if len(self._sequences) > len(other._sequences):
# If we entered more sequences than there are in the config,
# there's no way there can be a match.
- return QKeySequence.NoMatch
+ return QKeySequence.SequenceMatch.NoMatch
for entered, configured in zip(self._sequences, other._sequences):
# If we get NoMatch/PartialMatch in a sequence, we can abort there.
match = entered.matches(configured)
- if match != QKeySequence.ExactMatch:
+ if match != QKeySequence.SequenceMatch.ExactMatch:
return match
# We checked all common sequences and they had an ExactMatch.
@@ -569,33 +645,36 @@ class KeySequence:
# If there's the same amount of sequences configured and entered,
# that's an EqualMatch.
if len(self._sequences) == len(other._sequences):
- return QKeySequence.ExactMatch
+ return QKeySequence.SequenceMatch.ExactMatch
elif len(self._sequences) < len(other._sequences):
- return QKeySequence.PartialMatch
+ return QKeySequence.SequenceMatch.PartialMatch
else:
raise utils.Unreachable("self={!r} other={!r}".format(self, other))
def append_event(self, ev: QKeyEvent) -> 'KeySequence':
"""Create a new KeySequence object with the given QKeyEvent added."""
- key = Qt.Key(ev.key())
+ try:
+ key = Qt.Key(ev.key())
+ except ValueError as e:
+ raise KeyParseError(None, f"Got invalid key: {e}")
_assert_plain_key(key)
- _assert_plain_modifier(ev.modifiers())
+ _assert_plain_modifier(cast(Qt.KeyboardModifier, ev.modifiers()))
key = _remap_unicode(key, ev.text())
- modifiers = int(ev.modifiers())
+ modifiers = ev.modifiers()
if key == _NIL_KEY:
raise KeyParseError(None, "Got nil key!")
- # We always remove Qt.GroupSwitchModifier because QKeySequence has no
+ # We always remove Qt.KeyboardModifier.GroupSwitchModifier because QKeySequence has no
# way to mention that in a binding anyways...
- modifiers &= ~Qt.GroupSwitchModifier
+ modifiers &= ~Qt.KeyboardModifier.GroupSwitchModifier
- # We change Qt.Key_Backtab to Key_Tab here because nobody would
+ # We change Qt.Key.Key_Backtab to Key_Tab here because nobody would
# configure "Shift-Backtab" in their config.
- if modifiers & Qt.ShiftModifier and key == Qt.Key_Backtab:
- key = Qt.Key_Tab
+ if modifiers & Qt.KeyboardModifier.ShiftModifier and key == Qt.Key.Key_Backtab:
+ key = Qt.Key.Key_Tab
# We don't care about a shift modifier with symbols (Shift-: should
# match a : binding even though we typed it with a shift on an
@@ -607,52 +686,52 @@ class KeySequence:
#
# In addition, Shift also *is* relevant when other modifiers are
# involved. Shift-Ctrl-X should not be equivalent to Ctrl-X.
- if (modifiers == Qt.ShiftModifier and
+ shift_modifier = Qt.KeyboardModifier.ShiftModifier
+ if (modifiers == shift_modifier and # type: ignore[comparison-overlap]
_is_printable(key) and
not ev.text().isupper()):
- modifiers = Qt.KeyboardModifiers() # type: ignore[assignment]
+ modifiers = Qt.KeyboardModifier.NoModifier # type: ignore[assignment]
# On macOS, swap Ctrl and Meta back
#
- # We don't use Qt.AA_MacDontSwapCtrlAndMeta because that also affects
+ # We don't use Qt.ApplicationAttribute.AA_MacDontSwapCtrlAndMeta because that also affects
# Qt/QtWebEngine's own shortcuts. However, we do want "Ctrl" and "Meta"
# (or "Cmd") in a key binding name to actually represent what's on the
# keyboard.
if utils.is_mac:
- # FIXME:qt6 Reevaluate the type ignores below
- if modifiers & Qt.ControlModifier and modifiers & Qt.MetaModifier:
+ if modifiers & Qt.KeyboardModifier.ControlModifier and modifiers & Qt.KeyboardModifier.MetaModifier:
pass
- elif modifiers & Qt.ControlModifier:
- modifiers &= ~Qt.ControlModifier
- modifiers |= Qt.MetaModifier # type: ignore[assignment]
- elif modifiers & Qt.MetaModifier:
- modifiers &= ~Qt.MetaModifier
- modifiers |= Qt.ControlModifier # type: ignore[assignment]
+ elif modifiers & Qt.KeyboardModifier.ControlModifier:
+ modifiers &= ~Qt.KeyboardModifier.ControlModifier
+ modifiers |= Qt.KeyboardModifier.MetaModifier
+ elif modifiers & Qt.KeyboardModifier.MetaModifier:
+ modifiers &= ~Qt.KeyboardModifier.MetaModifier
+ modifiers |= Qt.KeyboardModifier.ControlModifier
- keys = list(self._iter_keys())
- keys.append(key | int(modifiers))
+ infos = list(self)
+ infos.append(KeyInfo(key, cast(Qt.KeyboardModifier, modifiers)))
- return self.__class__(*keys)
+ return self.__class__(*infos)
def strip_modifiers(self) -> 'KeySequence':
"""Strip optional modifiers from keys."""
- modifiers = Qt.KeypadModifier
- keys = [key & ~modifiers for key in self._iter_keys()]
- return self.__class__(*keys)
+ modifiers = Qt.KeyboardModifier.KeypadModifier
+ infos = [info.with_stripped_modifiers(modifiers) for info in self]
+ return self.__class__(*infos)
def with_mappings(
self,
mappings: Mapping['KeySequence', 'KeySequence']
) -> 'KeySequence':
"""Get a new KeySequence with the given mappings applied."""
- keys = []
- for key in self._iter_keys():
- key_seq = KeySequence(key)
+ infos: List[KeyInfo] = []
+ for info in self:
+ key_seq = KeySequence(info)
if key_seq in mappings:
- keys += [info.to_int() for info in mappings[key_seq]]
+ infos += mappings[key_seq]
else:
- keys.append(key)
- return self.__class__(*keys)
+ infos.append(info)
+ return self.__class__(*infos)
@classmethod
def parse(cls, keystr: str) -> 'KeySequence':