Skip to content

Commit

Permalink
Merge pull request #1611 from user202729/first-up-chord-send
Browse files Browse the repository at this point in the history
First-up chord send for keyboard machine
  • Loading branch information
sammdot authored Sep 26, 2023
2 parents d03d154 + c377d96 commit 24b3190
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 23 deletions.
1 change: 1 addition & 0 deletions news.d/feature/1611.core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement first-up chord send for keyboard machine.
34 changes: 31 additions & 3 deletions plover/gui_qt/config_keyboard_widget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,25 @@
<rect>
<x>0</x>
<y>0</y>
<width>117</width>
<height>38</height>
<width>159</width>
<height>66</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<layout class="QGridLayout" name="gridLayout">
<layout class="QGridLayout">
<item row="1" column="0">
<widget class="QCheckBox" name="first_up_chord_send">
<property name="toolTip">
<string>When the first key in a chord is released, the chord is sent.
If the key is pressed and released again, another chord is sent.</string>
</property>
<property name="text">
<string>First-up chord send</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="arpeggiate">
<property name="sizePolicy">
Expand Down Expand Up @@ -47,8 +58,25 @@
</hint>
</hints>
</connection>
<connection>
<sender>first_up_chord_send</sender>
<signal>clicked(bool)</signal>
<receiver>KeyboardWidget</receiver>
<slot>on_first_up_chord_send_changed(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>79</x>
<y>46</y>
</hint>
<hint type="destinationlabel">
<x>79</x>
<y>32</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>on_arpeggiate_changed(bool)</slot>
<slot>on_first_up_chord_send_changed(bool)</slot>
</slots>
</ui>
5 changes: 5 additions & 0 deletions plover/gui_qt/machine_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,12 @@ def __init__(self):
def setValue(self, value):
self._value = copy(value)
self.arpeggiate.setChecked(value['arpeggiate'])
self.first_up_chord_send.setChecked(value['first_up_chord_send'])

def on_arpeggiate_changed(self, value):
self._value['arpeggiate'] = value
self.valueChanged.emit(self._value)

def on_first_up_chord_send_changed(self, value):
self._value['first_up_chord_send'] = value
self.valueChanged.emit(self._value)
52 changes: 38 additions & 14 deletions plover/machine/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,20 @@ def __init__(self, params):
"""Monitor the keyboard's events."""
super().__init__()
self._arpeggiate = params['arpeggiate']
self._first_up_chord_send = params['first_up_chord_send']
if self._arpeggiate and self._first_up_chord_send:
self._error()
raise RuntimeError("Arpeggiate and first-up chord send cannot both be enabled!")
self._is_suppressed = False
# Currently held keys.
self._down_keys = set()
# All keys part of the stroke.
self._stroke_keys = set()
if self._first_up_chord_send:
# If this is True, the first key in a stroke has already been released
# and subsequent key-up events should not send more strokes
self._chord_already_sent = False
else:
# Collect the keys in the stroke, in case first_up_chord_send is False
self._stroke_keys = set()
self._keyboard_capture = None
self._last_stroke_key_down_count = 0
self._stroke_key_down_count = 0
Expand Down Expand Up @@ -109,31 +118,46 @@ def _key_down(self, key):
assert key is not None
self._stroke_key_down_count += 1
self._down_keys.add(key)
self._stroke_keys.add(key)
if self._first_up_chord_send:
self._chord_already_sent = False
else:
self._stroke_keys.add(key)

def _key_up(self, key):
"""Called when a key is released."""
assert key is not None

self._down_keys.discard(key)
# A stroke is complete if all pressed keys have been released,
# and — when arpeggiate mode is enabled — the arpeggiate key
# is part of it.
if (
self._down_keys or
not self._stroke_keys or
(self._arpeggiate and self._arpeggiate_key not in self._stroke_keys)
):
return

if self._first_up_chord_send:
if self._chord_already_sent:
return
else:
# A stroke is complete if all pressed keys have been released,
# and — when arpeggiate mode is enabled — the arpeggiate key
# is part of it.
if (
self._down_keys or
not self._stroke_keys or
(self._arpeggiate and self._arpeggiate_key not in self._stroke_keys)
):
return

self._last_stroke_key_down_count = self._stroke_key_down_count
steno_keys = {self._bindings.get(k) for k in self._stroke_keys}
if self._first_up_chord_send:
steno_keys = {self._bindings.get(k) for k in self._down_keys | {key}}
self._chord_already_sent = True
else:
steno_keys = {self._bindings.get(k) for k in self._stroke_keys}
self._stroke_keys.clear()
steno_keys -= {None}
if steno_keys:
self._notify(steno_keys)
self._stroke_keys.clear()
self._stroke_key_down_count = 0

@classmethod
def get_option_info(cls):
return {
'arpeggiate': (False, boolean),
'first_up_chord_send': (False, boolean),
}
2 changes: 1 addition & 1 deletion test/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def config(self, options):
lambda: ('"log_file_name":"c:/whatever/morestrokes.log"', "c:/whatever/morestrokes.log"),
lambda: ('"enabled_extensions":[]', set()),
lambda: ('"machine_type":"Keyboard"', "Keyboard"),
lambda: ('"machine_specific_options":{"arpeggiate":True}', {"arpeggiate": True}),
lambda: ('"machine_specific_options":{"arpeggiate":True}', {"arpeggiate": True, "first_up_chord_send": False}),
lambda: ('"system_keymap":'+str(DEFAULT_KEYMAP), DEFAULT_KEYMAP),
lambda: ('"dictionaries":("user.json","main.json")', list(map(DictionaryConfig, ("user.json", "main.json")))),
)
Expand Down
4 changes: 3 additions & 1 deletion test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def test_config_dict():
'enabled_extensions': set(),
'auto_start': False,
'machine_type': 'Keyboard',
'machine_specific_options': { 'arpeggiate': False },
'machine_specific_options': { 'arpeggiate': False, 'first_up_chord_send': False },
'system_name': config.DEFAULT_SYSTEM_NAME,
'system_keymap': DEFAULT_KEYMAP,
'dictionaries': [DictionaryConfig(p) for p in english_stenotype.DEFAULT_DICTIONARIES]
Expand Down Expand Up @@ -249,11 +249,13 @@ def test_config_dict():
{
'machine_specific_options': {
'arpeggiate': True,
'first_up_chord_send': False,
}
},
'''
[Keyboard]
arpeggiate = True
first_up_chord_send = False
'''
),

Expand Down
22 changes: 18 additions & 4 deletions test/test_keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,21 @@ def capture():
with mock.patch('plover.machine.keyboard.KeyboardCapture', new=lambda: capture):
yield capture

@pytest.fixture(params=[False])
@pytest.fixture(params=[{'arpeggiate': False, 'first_up_chord_send': False}])
def machine(request, capture):
machine = Keyboard({'arpeggiate': request.param})
machine = Keyboard(request.param)
keymap = Keymap(Keyboard.KEYS_LAYOUT.split(),
system.KEYS + Keyboard.ACTIONS)
keymap.set_mappings(system.KEYMAPS['Keyboard'])
machine.set_keymap(keymap)
return machine

def arpeggiate(func):
return pytest.mark.parametrize('machine', [True], indirect=True)(func)
arpeggiate = pytest.mark.parametrize('machine', [{'arpeggiate': True, 'first_up_chord_send': False}], indirect=True)
first_up_chord_send = pytest.mark.parametrize('machine', [{'arpeggiate': False, 'first_up_chord_send': True}], indirect=True)
"""
These are decorators to be applied on test functions to modify the machine configuration.
Note that at the moment it's not possible to apply both at the same time.
"""

@pytest.fixture
def strokes(machine):
Expand Down Expand Up @@ -97,3 +101,13 @@ def test_arpeggiate_2(capture, machine, strokes):
machine.start_capture()
send_input(capture, 'a +h +space -space -h w')
assert strokes == [{'S-', '*'}]

@first_up_chord_send
def test_first_up_chord_send(capture, machine, strokes):
machine.start_capture()
send_input(capture, '+a +w +l -l +l')
assert strokes == [{'S-', 'T-', '-G'}]
send_input(capture, '-l')
assert strokes == [{'S-', 'T-', '-G'}, {'S-', 'T-', '-G'}]
send_input(capture, '-a -w')
assert strokes == [{'S-', 'T-', '-G'}, {'S-', 'T-', '-G'}]

0 comments on commit 24b3190

Please sign in to comment.