Fall back to non-keypad keys without any keypad bindings

Fixes #3701
This commit is contained in:
Florian Bruhin 2018-03-13 14:10:22 +01:00
parent a7b6d179d4
commit b88ac51d25
4 changed files with 67 additions and 3 deletions

View File

@ -147,10 +147,18 @@ class BaseKeyParser(QObject):
return QKeySequence.NoMatch
# First, try a straightforward match
self._debug_log("Trying simple match")
match, binding = self._match_key(sequence)
# Then try without optional modifiers
if match == QKeySequence.NoMatch:
self._debug_log("Trying match without modifiers")
sequence = sequence.strip_modifiers()
match, binding = self._match_key(sequence)
# If that doesn't match, try a key_mapping
if match == QKeySequence.NoMatch:
self._debug_log("Trying match with key_mappings")
mapped = sequence.with_mappings(config.val.bindings.key_mappings)
if sequence != mapped:
self._debug_log("Mapped {} -> {}".format(
@ -159,10 +167,12 @@ class BaseKeyParser(QObject):
sequence = mapped
# If that doesn't match either, try treating it as count.
txt = str(sequence[-1]) # To account for sequences changed above.
if (match == QKeySequence.NoMatch and
txt.isdigit() and
self._supports_count and
not (not self._count and txt == '0')):
self._debug_log("Trying match as count")
assert len(txt) == 1, txt
if not dry_run:
self._count += txt

View File

@ -510,6 +510,12 @@ class KeySequence:
return self.__class__(*keys)
def strip_modifiers(self):
"""Strip optional modifiers from keys."""
modifiers = Qt.KeypadModifier
keys = [key & ~modifiers for key in self._iter_keys()]
return self.__class__(*keys)
def with_mappings(self, mappings):
"""Get a new KeySequence with the given mappings applied."""
keys = []

View File

@ -198,13 +198,34 @@ class TestHandle:
keyparser.execute.assert_called_with('message-info ba', None)
assert not keyparser._sequence
@pytest.mark.parametrize('key, number', [(Qt.Key_0, 0), (Qt.Key_1, 1)])
def test_number_press(self, handle_text, keyparser, key, number):
handle_text(key)
@pytest.mark.parametrize('key, modifiers, number', [
(Qt.Key_0, Qt.NoModifier, 0),
(Qt.Key_1, Qt.NoModifier, 1),
(Qt.Key_1, Qt.KeypadModifier, 1),
])
def test_number_press(self, fake_keyevent, keyparser,
key, modifiers, number):
keyparser.handle(fake_keyevent(key, modifiers))
command = 'message-info {}'.format(number)
keyparser.execute.assert_called_once_with(command, None)
assert not keyparser._sequence
@pytest.mark.parametrize('modifiers, text', [
(Qt.NoModifier, '2'),
(Qt.KeypadModifier, 'num-2'),
])
def test_number_press_keypad(self, fake_keyevent, keyparser, config_stub,
modifiers, text):
"""Make sure a <Num+2> binding overrides the 2 binding."""
config_stub.val.bindings.commands = {'normal': {
'2': 'message-info 2',
'<Num+2>': 'message-info num-2'}}
keyparser._read_config('normal')
keyparser.handle(fake_keyevent(Qt.Key_2, modifiers))
command = 'message-info {}'.format(text)
keyparser.execute.assert_called_once_with(command, None)
assert not keyparser._sequence
def test_umlauts(self, handle_text, keyparser, config_stub):
config_stub.val.bindings.commands = {'normal': {'ü': 'message-info ü'}}
keyparser._read_config('normal')
@ -215,6 +236,15 @@ class TestHandle:
handle_text(Qt.Key_X)
keyparser.execute.assert_called_once_with('message-info a', None)
def test_mapping_keypad(self, config_stub, fake_keyevent, keyparser):
"""Make sure falling back to non-numpad keys works with mappings."""
config_stub.val.bindings.commands = {'normal': {'a': 'nop'}}
config_stub.val.bindings.key_mappings = {'1': 'a'}
keyparser._read_config('normal')
keyparser.handle(fake_keyevent(Qt.Key_1, Qt.KeypadModifier))
keyparser.execute.assert_called_once_with('nop', None)
def test_binding_and_mapping(self, config_stub, handle_text, keyparser):
"""with a conflicting binding/mapping, the binding should win."""
handle_text(Qt.Key_B)
@ -296,6 +326,15 @@ class TestCount:
assert sig1.args == ('4',)
assert sig2.args == ('42',)
def test_numpad(self, fake_keyevent, keyparser):
"""Make sure we can enter a count via numpad."""
for key, modifiers in [(Qt.Key_4, Qt.KeypadModifier),
(Qt.Key_2, Qt.KeypadModifier),
(Qt.Key_B, Qt.NoModifier),
(Qt.Key_A, Qt.NoModifier)]:
keyparser.handle(fake_keyevent(key, modifiers))
keyparser.execute.assert_called_once_with('message-info ba', 42)
def test_clear_keystring(qtbot, keyparser):
"""Test that the keystring is cleared and the signal is emitted."""

View File

@ -377,6 +377,15 @@ class TestKeySequence:
with pytest.raises(keyutils.KeyParseError):
seq.append_event(event)
def test_strip_modifiers(self):
seq = keyutils.KeySequence(Qt.Key_0,
Qt.Key_1 | Qt.KeypadModifier,
Qt.Key_A | Qt.ControlModifier)
expected = keyutils.KeySequence(Qt.Key_0,
Qt.Key_1,
Qt.Key_A | Qt.ControlModifier)
assert seq.strip_modifiers() == expected
def test_with_mappings(self):
seq = keyutils.KeySequence.parse('foobar')
mappings = {keyutils.KeySequence('b'): keyutils.KeySequence('t')}