diff options
Diffstat (limited to 'qutebrowser/keyinput/keyutils.py')
-rw-r--r-- | qutebrowser/keyinput/keyutils.py | 483 |
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': |