Skip to content

Commit 51017c1

Browse files
committed
Merge branch 'issue_541_use_event_code_for_shortcuts' into develop
2 parents 5d12302 + c32ed7f commit 51017c1

File tree

11 files changed

+163
-68
lines changed

11 files changed

+163
-68
lines changed

lute/app_factory.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@
3535

3636
from lute.models.book import Book
3737
from lute.models.language import Language
38-
from lute.settings.current import refresh_global_settings, current_settings
38+
from lute.settings.current import (
39+
refresh_global_settings,
40+
current_settings,
41+
current_hotkeys,
42+
)
3943
from lute.models.repositories import UserSettingRepository
4044
from lute.book.stats import Service as StatsService
4145

@@ -123,6 +127,7 @@ def inject_menu_bar_vars():
123127
"backup_last_display_date": bs.last_backup_display_date,
124128
"backup_time_since": bs.time_since_last_backup,
125129
"user_settings": json.dumps(current_settings),
130+
"user_hotkeys": json.dumps(current_hotkeys),
126131
}
127132
return ret
128133

lute/db/management.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,27 @@ def add_default_user_settings(session, default_user_backup_path):
9393
# Keyboard shortcuts. These have default values assigned
9494
# as they were the hotkeys defined in the initial Lute
9595
# release.
96-
"hotkey_StartHover": "escape",
97-
"hotkey_PrevWord": "arrowleft",
98-
"hotkey_NextWord": "arrowright",
99-
"hotkey_StatusUp": "arrowup",
100-
"hotkey_StatusDown": "arrowdown",
101-
"hotkey_Bookmark": "b",
102-
"hotkey_CopySentence": "c",
103-
"hotkey_CopyPara": "shift+c",
104-
"hotkey_TranslateSentence": "t",
105-
"hotkey_TranslatePara": "shift+t",
106-
"hotkey_NextTheme": "m",
107-
"hotkey_ToggleHighlight": "h",
108-
"hotkey_ToggleFocus": "f",
109-
"hotkey_Status1": "1",
110-
"hotkey_Status2": "2",
111-
"hotkey_Status3": "3",
112-
"hotkey_Status4": "4",
113-
"hotkey_Status5": "5",
114-
"hotkey_StatusIgnore": "i",
115-
"hotkey_StatusWellKnown": "w",
116-
"hotkey_SaveTerm": "ctrl+enter",
96+
"hotkey_Bookmark": "KeyB",
97+
"hotkey_CopyPara": "shift+KeyC",
98+
"hotkey_CopySentence": "KeyC",
99+
"hotkey_NextTheme": "KeyM",
100+
"hotkey_NextWord": "ArrowRight",
101+
"hotkey_PrevWord": "ArrowLeft",
102+
"hotkey_SaveTerm": "ctrl+Enter",
103+
"hotkey_StartHover": "Escape",
104+
"hotkey_Status1": "Digit1",
105+
"hotkey_Status2": "Digit2",
106+
"hotkey_Status3": "Digit3",
107+
"hotkey_Status4": "Digit4",
108+
"hotkey_Status5": "Digit5",
109+
"hotkey_StatusDown": "ArrowDown",
110+
"hotkey_StatusIgnore": "KeyI",
111+
"hotkey_StatusUp": "ArrowUp",
112+
"hotkey_StatusWellKnown": "KeyW",
113+
"hotkey_ToggleFocus": "KeyF",
114+
"hotkey_ToggleHighlight": "KeyH",
115+
"hotkey_TranslatePara": "shift+KeyT",
116+
"hotkey_TranslateSentence": "KeyT",
117117
# New hotkeys. These must have empty values, because
118118
# users may have already setup their hotkeys, and we can't
119119
# assume that a given key combination is free:

lute/settings/current.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,27 @@
1212
# The current user settings, key/value dict.
1313
current_settings = {}
1414

15+
# Current user hotkey mappings, mapping to mapping_name dict.
16+
current_hotkeys = {}
17+
1518

1619
def refresh_global_settings(session):
1720
"Refresh all settings dictionary."
1821
# Have to reload to not mess up any references
1922
# (e.g. during testing).
2023
current_settings.clear()
24+
current_hotkeys.clear()
2125

2226
settings = session.query(UserSetting).all()
2327
for s in settings:
2428
current_settings[s.key] = s.value
2529

30+
hotkeys = [
31+
s for s in settings if s.key.startswith("hotkey_") and (s.value or "") != ""
32+
]
33+
for h in hotkeys:
34+
current_hotkeys[h.value] = h.key
35+
2636
# Convert some ints into bools.
2737
boolkeys = [
2838
"open_popup_in_new_tab",

lute/static/js/lute-hotkey-utils.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/**
2-
* Get the pressed keys as a string, eg 'meta-c', 'shift-a'.
2+
* Get the pressed keys as a string, eg 'meta-KeyC', 'shift-KeyA'.
33
*
4-
* Note that there _must_ be a "regular" key pressed as well.
5-
* If only meta/alt/ctl/shift are pressed, returns null.
4+
* If only meta/alt/ctl/shift are pressed, returns something like 'meta-MetaLeft'.
65
*/
76
function get_pressed_keys_as_string(event) {
87
const keys = [];
@@ -13,13 +12,42 @@ function get_pressed_keys_as_string(event) {
1312
if (event.altKey) keys.push('alt');
1413
if (event.metaKey) keys.push('meta');
1514

15+
let code = event.code ?? event.originalEvent?.code;
16+
// console.log(`event.code = ${code}`);
17+
// console.log('event = ', event)
18+
keys.push(code);
19+
const ret = keys.join('+');
20+
// console.log(`got hotkey = ${ret}`);
21+
return ret;
22+
}
23+
24+
25+
/**
26+
* Function for legacy get_pressed_keys_as_string
27+
*
28+
* Per https://github.com/LuteOrg/lute-v3/issues/541, Lute used to use
29+
* the event.key for keyboard shortcuts, but that caused problems for
30+
* changing keyboard layouts.
31+
*
32+
* This function uses the old way of "event.key" to find the key
33+
* pressed, as a fallback for users who have the old keyboard
34+
* mappings.
35+
*/
36+
function _legacy_pressed_key_string(event) {
37+
const keys = [];
38+
39+
// Check for modifier keys
40+
if (event.ctrlKey) keys.push('ctrl');
41+
if (event.shiftKey) keys.push('shift');
42+
if (event.altKey) keys.push('alt');
43+
if (event.metaKey) keys.push('meta');
44+
1645
// Map special keys to names if needed
1746
const keyMap = {
1847
' ': 'space'
1948
};
2049

2150
if (event.key == null) {
22-
// window.alert("no key for event?");
2351
return null;
2452
}
2553

@@ -29,6 +57,27 @@ function get_pressed_keys_as_string(event) {
2957

3058
keys.push(actual_key);
3159
const ret = keys.join('+');
32-
// console.log(`got hotkey = ${ret}`);
60+
// console.log(`got legacy hotkey = ${ret}`);
3361
return ret;
3462
}
63+
64+
65+
/**
66+
* get the "hotkey name" (e.g. "hotkey_Status5") for the given event
67+
* from LUTE_USER_HOTKEYS.
68+
*
69+
* First try to get the name using the pressed key string, then use
70+
* the legacy event.
71+
*
72+
* Returns null if no match found.
73+
*/
74+
function get_hotkey_name(event) {
75+
const s = get_pressed_keys_as_string(event);
76+
if (s in LUTE_USER_HOTKEYS)
77+
return LUTE_USER_HOTKEYS[s];
78+
const legacy_s = _legacy_pressed_key_string(event);
79+
if (legacy_s in LUTE_USER_HOTKEYS)
80+
return LUTE_USER_HOTKEYS[legacy_s];
81+
// console.log(`No match for hotkey ${s} or legacy hotkey ${legacy_s}`);
82+
return null;
83+
}

lute/static/js/lute.js

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -893,54 +893,53 @@ function handle_keydown (e) {
893893
return; // Nothing to do.
894894
}
895895

896-
// User hotkeys are stored in LUTE_USER_SETTINGS
897-
// hash in global space.
898-
const k = LUTE_USER_SETTINGS; // shorthand varname.
896+
const hotkey_name = get_hotkey_name(e);
897+
if (hotkey_name == null)
898+
return;
899899

900900
const next_incr = _lang_is_left_to_right() ? 1 : -1;
901901
const prev_incr = -1 * next_incr;
902902

903903
// Map of shortcuts to lambdas:
904904
let map = {
905-
[k.hotkey_StartHover]: () => start_hover_mode(),
906-
[k.hotkey_PrevWord]: () => _move_cursor('span.word', prev_incr),
907-
[k.hotkey_NextWord]: () => _move_cursor('span.word', next_incr),
908-
[k.hotkey_PrevUnknownWord]: () => _move_cursor('span.word.status0', prev_incr),
909-
[k.hotkey_NextUnknownWord]: () => _move_cursor('span.word.status0', next_incr),
910-
[k.hotkey_PrevSentence]: () => _move_cursor('span.word.sentencestart', prev_incr),
911-
[k.hotkey_NextSentence]: () => _move_cursor('span.word.sentencestart', next_incr),
912-
[k.hotkey_StatusUp]: () => increment_status_for_selected_elements(+1),
913-
[k.hotkey_StatusDown]: () => increment_status_for_selected_elements(-1),
914-
[k.hotkey_Bookmark]: () => handle_bookmark(),
915-
[k.hotkey_CopySentence]: () => handle_copy('sentence-id'),
916-
[k.hotkey_CopyPara]: () => handle_copy('paragraph-id'),
917-
[k.hotkey_CopyPage]: () => handle_copy(null),
918-
[k.hotkey_EditPage]: () => handle_edit_page(),
919-
[k.hotkey_TranslateSentence]: () => handle_translate('sentence-id'),
920-
[k.hotkey_TranslatePara]: () => handle_translate('paragraph-id'),
921-
[k.hotkey_TranslatePage]: () => handle_translate(null),
922-
[k.hotkey_NextTheme]: () => next_theme(),
923-
[k.hotkey_ToggleHighlight]: () => toggle_highlight(),
924-
[k.hotkey_ToggleFocus]: () => toggleFocus(),
925-
[k.hotkey_Status1]: () => update_status_for_marked_elements(1),
926-
[k.hotkey_Status2]: () => update_status_for_marked_elements(2),
927-
[k.hotkey_Status3]: () => update_status_for_marked_elements(3),
928-
[k.hotkey_Status4]: () => update_status_for_marked_elements(4),
929-
[k.hotkey_Status5]: () => update_status_for_marked_elements(5),
930-
[k.hotkey_StatusIgnore]: () => update_status_for_marked_elements(98),
931-
[k.hotkey_StatusWellKnown]: () => update_status_for_marked_elements(99),
932-
[k.hotkey_DeleteTerm]: () => update_status_for_marked_elements(0),
905+
"hotkey_StartHover": () => start_hover_mode(),
906+
"hotkey_PrevWord": () => _move_cursor('span.word', prev_incr),
907+
"hotkey_NextWord": () => _move_cursor('span.word', next_incr),
908+
"hotkey_PrevUnknownWord": () => _move_cursor('span.word.status0', prev_incr),
909+
"hotkey_NextUnknownWord": () => _move_cursor('span.word.status0', next_incr),
910+
"hotkey_PrevSentence": () => _move_cursor('span.word.sentencestart', prev_incr),
911+
"hotkey_NextSentence": () => _move_cursor('span.word.sentencestart', next_incr),
912+
"hotkey_StatusUp": () => increment_status_for_selected_elements(+1),
913+
"hotkey_StatusDown": () => increment_status_for_selected_elements(-1),
914+
"hotkey_Bookmark": () => handle_bookmark(),
915+
"hotkey_CopySentence": () => handle_copy('sentence-id'),
916+
"hotkey_CopyPara": () => handle_copy('paragraph-id'),
917+
"hotkey_CopyPage": () => handle_copy(null),
918+
"hotkey_EditPage": () => handle_edit_page(),
919+
"hotkey_TranslateSentence": () => handle_translate('sentence-id'),
920+
"hotkey_TranslatePara": () => handle_translate('paragraph-id'),
921+
"hotkey_TranslatePage": () => handle_translate(null),
922+
"hotkey_NextTheme": () => next_theme(),
923+
"hotkey_ToggleHighlight": () => toggle_highlight(),
924+
"hotkey_ToggleFocus": () => toggleFocus(),
925+
"hotkey_Status1": () => update_status_for_marked_elements(1),
926+
"hotkey_Status2": () => update_status_for_marked_elements(2),
927+
"hotkey_Status3": () => update_status_for_marked_elements(3),
928+
"hotkey_Status4": () => update_status_for_marked_elements(4),
929+
"hotkey_Status5": () => update_status_for_marked_elements(5),
930+
"hotkey_StatusIgnore": () => update_status_for_marked_elements(98),
931+
"hotkey_StatusWellKnown": () => update_status_for_marked_elements(99),
932+
"hotkey_DeleteTerm": () => update_status_for_marked_elements(0),
933933
}
934934

935-
const ks = get_pressed_keys_as_string(e);
936-
if (ks in map) {
935+
if (hotkey_name in map) {
937936
// Override any existing event - e.g., if "up" arrow is in the map,
938937
// don't scroll screen.
939938
e.preventDefault();
940-
map[ks]();
939+
map[hotkey_name]();
941940
}
942941
else {
943-
// console.log('unhandled key ' + ks);
942+
// console.log(`hotkey "${hotkey_name}" not found in map`);
944943
}
945944
}
946945

lute/templates/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
<script type="text/javascript">
4848
const LUTE_USER_SETTINGS = {{ user_settings | safe }}
49+
const LUTE_USER_HOTKEYS = {{ user_hotkeys | safe }}
4950
</script>
5051
</head>
5152
<body>

lute/templates/read/term_bulk_edit_form.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<script type="text/javascript" src="{{ url_for('static', filename='js/lute-hotkey-utils.js') }}" charset="utf-8"></script>
55
<script type="text/javascript">
66
const LUTE_USER_SETTINGS = {{ user_settings | safe }}
7+
const LUTE_USER_HOTKEYS = {{ user_hotkeys | safe }}
78
</script>
89

910
<div id="term-form-container">
@@ -47,8 +48,7 @@
4748

4849
// "Save" shortcut
4950
$(document).keydown(function(event) {
50-
const s = get_pressed_keys_as_string(event);
51-
if (s == LUTE_USER_SETTINGS.hotkey_SaveTerm) {
51+
if (get_hotkey_name(event) == "hotkey_SaveTerm") {
5252
$("#btnsubmit").click();
5353
}
5454
});

lute/templates/settings/shortcuts.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141

4242
<script>
4343

44+
// Check any duplicates. All of the keyboard shortcuts are rendered
45+
// into javascript as a dict, shortcut_string => shortcut_name, so
46+
// each non-blank shortcut must be unique.
4447
let _check_dups = function() {
4548
let values = {};
4649

lute/templates/term/_form.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<script type="text/javascript" src="{{ url_for('static', filename='js/lute-hotkey-utils.js') }}" charset="utf-8"></script>
55
<script type="text/javascript">
66
const LUTE_USER_SETTINGS = {{ user_settings | safe }}
7+
const LUTE_USER_HOTKEYS = {{ user_hotkeys | safe }}
78
</script>
89

910

@@ -317,8 +318,7 @@
317318

318319
// "Save" shortcut
319320
$(document).keydown(function(event) {
320-
const s = get_pressed_keys_as_string(event);
321-
if (s == LUTE_USER_SETTINGS.hotkey_SaveTerm) {
321+
if (get_hotkey_name(event) == "hotkey_SaveTerm") {
322322
$("#btnsubmit").click();
323323
}
324324
});

tests/acceptance/lute_test_client.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,24 @@ def fill_reading_bulk_edit_form(self, updates=None):
366366

367367
def press_hotkey(self, hotkey):
368368
"Send a hotkey."
369+
key_to_code_map = {
370+
"1": "Digit1",
371+
"2": "Digit2",
372+
"3": "Digit3",
373+
"4": "Digit4",
374+
"5": "Digit5",
375+
"arrowdown": "ArrowDown",
376+
"arrowup": "ArrowUp",
377+
"h": "KeyH",
378+
"i": "KeyI",
379+
"m": "KeyM",
380+
"w": "KeyW",
381+
}
382+
if hotkey not in key_to_code_map:
383+
raise RuntimeError(f"Missing {hotkey} in acceptance test map")
369384
event_parts = [
370385
"type: 'keydown'",
371-
f"key: '{hotkey.lower()}'",
386+
f"code: '{key_to_code_map[hotkey]}'",
372387
]
373388
if hotkey in ["C", "T"]:
374389
event_parts.append("shiftKey: true")

0 commit comments

Comments
 (0)