supervertaler 1.9.163__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. Supervertaler.py +48473 -0
  2. modules/__init__.py +10 -0
  3. modules/ai_actions.py +964 -0
  4. modules/ai_attachment_manager.py +343 -0
  5. modules/ai_file_viewer_dialog.py +210 -0
  6. modules/autofingers_engine.py +466 -0
  7. modules/cafetran_docx_handler.py +379 -0
  8. modules/config_manager.py +469 -0
  9. modules/database_manager.py +1911 -0
  10. modules/database_migrations.py +417 -0
  11. modules/dejavurtf_handler.py +779 -0
  12. modules/document_analyzer.py +427 -0
  13. modules/docx_handler.py +689 -0
  14. modules/encoding_repair.py +319 -0
  15. modules/encoding_repair_Qt.py +393 -0
  16. modules/encoding_repair_ui.py +481 -0
  17. modules/feature_manager.py +350 -0
  18. modules/figure_context_manager.py +340 -0
  19. modules/file_dialog_helper.py +148 -0
  20. modules/find_replace.py +164 -0
  21. modules/find_replace_qt.py +457 -0
  22. modules/glossary_manager.py +433 -0
  23. modules/image_extractor.py +188 -0
  24. modules/keyboard_shortcuts_widget.py +571 -0
  25. modules/llm_clients.py +1211 -0
  26. modules/llm_leaderboard.py +737 -0
  27. modules/llm_superbench_ui.py +1401 -0
  28. modules/local_llm_setup.py +1104 -0
  29. modules/model_update_dialog.py +381 -0
  30. modules/model_version_checker.py +373 -0
  31. modules/mqxliff_handler.py +638 -0
  32. modules/non_translatables_manager.py +743 -0
  33. modules/pdf_rescue_Qt.py +1822 -0
  34. modules/pdf_rescue_tkinter.py +909 -0
  35. modules/phrase_docx_handler.py +516 -0
  36. modules/project_home_panel.py +209 -0
  37. modules/prompt_assistant.py +357 -0
  38. modules/prompt_library.py +689 -0
  39. modules/prompt_library_migration.py +447 -0
  40. modules/quick_access_sidebar.py +282 -0
  41. modules/ribbon_widget.py +597 -0
  42. modules/sdlppx_handler.py +874 -0
  43. modules/setup_wizard.py +353 -0
  44. modules/shortcut_manager.py +932 -0
  45. modules/simple_segmenter.py +128 -0
  46. modules/spellcheck_manager.py +727 -0
  47. modules/statuses.py +207 -0
  48. modules/style_guide_manager.py +315 -0
  49. modules/superbench_ui.py +1319 -0
  50. modules/superbrowser.py +329 -0
  51. modules/supercleaner.py +600 -0
  52. modules/supercleaner_ui.py +444 -0
  53. modules/superdocs.py +19 -0
  54. modules/superdocs_viewer_qt.py +382 -0
  55. modules/superlookup.py +252 -0
  56. modules/tag_cleaner.py +260 -0
  57. modules/tag_manager.py +351 -0
  58. modules/term_extractor.py +270 -0
  59. modules/termbase_entry_editor.py +842 -0
  60. modules/termbase_import_export.py +488 -0
  61. modules/termbase_manager.py +1060 -0
  62. modules/termview_widget.py +1176 -0
  63. modules/theme_manager.py +499 -0
  64. modules/tm_editor_dialog.py +99 -0
  65. modules/tm_manager_qt.py +1280 -0
  66. modules/tm_metadata_manager.py +545 -0
  67. modules/tmx_editor.py +1461 -0
  68. modules/tmx_editor_qt.py +2784 -0
  69. modules/tmx_generator.py +284 -0
  70. modules/tracked_changes.py +900 -0
  71. modules/trados_docx_handler.py +430 -0
  72. modules/translation_memory.py +715 -0
  73. modules/translation_results_panel.py +2134 -0
  74. modules/translation_services.py +282 -0
  75. modules/unified_prompt_library.py +659 -0
  76. modules/unified_prompt_manager_qt.py +3951 -0
  77. modules/voice_commands.py +920 -0
  78. modules/voice_dictation.py +477 -0
  79. modules/voice_dictation_lite.py +249 -0
  80. supervertaler-1.9.163.dist-info/METADATA +906 -0
  81. supervertaler-1.9.163.dist-info/RECORD +85 -0
  82. supervertaler-1.9.163.dist-info/WHEEL +5 -0
  83. supervertaler-1.9.163.dist-info/entry_points.txt +2 -0
  84. supervertaler-1.9.163.dist-info/licenses/LICENSE +21 -0
  85. supervertaler-1.9.163.dist-info/top_level.txt +2 -0
@@ -0,0 +1,932 @@
1
+ """
2
+ Keyboard Shortcut Manager for Supervertaler Qt
3
+ Centralized management of all keyboard shortcuts
4
+ """
5
+
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional, Tuple
9
+ from PyQt6.QtGui import QKeySequence
10
+ from PyQt6.QtCore import QSettings
11
+
12
+ class ShortcutManager:
13
+ """Manages all keyboard shortcuts for Supervertaler"""
14
+
15
+ # Define all shortcuts with their categories, descriptions, and defaults
16
+ DEFAULT_SHORTCUTS = {
17
+ # File Operations
18
+ "file_new": {
19
+ "category": "File",
20
+ "description": "New Project",
21
+ "default": "",
22
+ "action": "new_project"
23
+ },
24
+ "editor_focus_notes": {
25
+ "category": "Edit",
26
+ "description": "Focus Segment Note Tab (Ctrl+N)",
27
+ "default": "Ctrl+N",
28
+ "action": "focus_segment_notes"
29
+ },
30
+ "file_open": {
31
+ "category": "File",
32
+ "description": "Open Project",
33
+ "default": "Ctrl+O",
34
+ "action": "open_project"
35
+ },
36
+ "file_save": {
37
+ "category": "File",
38
+ "description": "Save Project",
39
+ "default": "Ctrl+S",
40
+ "action": "save_project"
41
+ },
42
+
43
+ "file_quit": {
44
+ "category": "File",
45
+ "description": "Quit Application",
46
+ "default": "Alt+F4",
47
+ "action": "close"
48
+ },
49
+
50
+ # Edit Operations
51
+ "edit_undo": {
52
+ "category": "Edit",
53
+ "description": "Undo",
54
+ "default": "Ctrl+Z",
55
+ "action": "undo"
56
+ },
57
+ "edit_redo": {
58
+ "category": "Edit",
59
+ "description": "Redo",
60
+ "default": "Ctrl+Y",
61
+ "action": "redo"
62
+ },
63
+ "edit_find": {
64
+ "category": "Edit",
65
+ "description": "Find",
66
+ "default": "Ctrl+F",
67
+ "action": "show_find_replace_dialog"
68
+ },
69
+ "edit_replace": {
70
+ "category": "Edit",
71
+ "description": "Replace",
72
+ "default": "Ctrl+H",
73
+ "action": "show_find_replace_dialog"
74
+ },
75
+ "edit_goto": {
76
+ "category": "Edit",
77
+ "description": "Go to Segment",
78
+ "default": "Ctrl+G",
79
+ "action": "show_goto_dialog"
80
+ },
81
+
82
+ # Translation Operations
83
+ "translate_current": {
84
+ "category": "Translation",
85
+ "description": "Translate Current Segment",
86
+ "default": "Ctrl+T",
87
+ "action": "translate_current_segment"
88
+ },
89
+ "translate_batch": {
90
+ "category": "Translation",
91
+ "description": "Translate Multiple Segments",
92
+ "default": "Ctrl+Shift+T",
93
+ "action": "translate_batch"
94
+ },
95
+
96
+ # View Operations
97
+ "view_grid": {
98
+ "category": "View",
99
+ "description": "Switch to Grid View",
100
+ "default": "Ctrl+1",
101
+ "action": "switch_to_grid_view"
102
+ },
103
+ "view_list": {
104
+ "category": "View",
105
+ "description": "Switch to List View",
106
+ "default": "Ctrl+2",
107
+ "action": "switch_to_list_view"
108
+ },
109
+ "view_document": {
110
+ "category": "View",
111
+ "description": "Switch to Document View",
112
+ "default": "Ctrl+3",
113
+ "action": "switch_to_document_view"
114
+ },
115
+ "view_toggle_tags": {
116
+ "category": "View",
117
+ "description": "Toggle Tag View",
118
+ "default": "Ctrl+Alt+T",
119
+ "action": "toggle_tag_view"
120
+ },
121
+
122
+ # Grid Text Zoom
123
+ "grid_zoom_in": {
124
+ "category": "View",
125
+ "description": "Grid Zoom In",
126
+ "default": "Ctrl++",
127
+ "action": "increase_font_size"
128
+ },
129
+ "grid_zoom_out": {
130
+ "category": "View",
131
+ "description": "Grid Zoom Out",
132
+ "default": "Ctrl+-",
133
+ "action": "decrease_font_size"
134
+ },
135
+
136
+ # Results Pane Zoom
137
+ "results_zoom_in": {
138
+ "category": "View",
139
+ "description": "Results Pane Zoom In",
140
+ "default": "Ctrl+Shift+=",
141
+ "action": "results_pane_zoom_in"
142
+ },
143
+ "results_zoom_out": {
144
+ "category": "View",
145
+ "description": "Results Pane Zoom Out",
146
+ "default": "Ctrl+Shift+-",
147
+ "action": "results_pane_zoom_out"
148
+ },
149
+
150
+ # Resources & Tools
151
+ "tools_tm_manager_tab": {
152
+ "category": "Resources",
153
+ "description": "TM Manager (Launch in tab)",
154
+ "default": "Ctrl+M",
155
+ "action": "show_tm_manager_in_tab"
156
+ },
157
+ "tools_tm_manager_window": {
158
+ "category": "Resources",
159
+ "description": "TM Manager (Separate window)",
160
+ "default": "Ctrl+Shift+M",
161
+ "action": "show_tm_manager"
162
+ },
163
+ "tools_concordance_search": {
164
+ "category": "Resources",
165
+ "description": "Quick Concordance Search",
166
+ "default": "Ctrl+K",
167
+ "action": "show_concordance_search"
168
+ },
169
+ "tools_universal_lookup": {
170
+ "category": "Resources",
171
+ "description": "Universal Lookup",
172
+ "default": "Ctrl+Alt+L",
173
+ "action": "show_universal_lookup"
174
+ },
175
+ "tools_autofingers": {
176
+ "category": "Resources",
177
+ "description": "AutoFingers",
178
+ "default": "Ctrl+Shift+A",
179
+ "action": "show_autofingers"
180
+ },
181
+ "tools_force_refresh": {
182
+ "category": "Resources",
183
+ "description": "Force Refresh Matches (clear cache)",
184
+ "default": "F5",
185
+ "action": "force_refresh_matches"
186
+ },
187
+
188
+ # Special
189
+ "voice_dictate": {
190
+ "category": "Special",
191
+ "description": "Voice Dictation",
192
+ "default": "F9",
193
+ "action": "start_voice_dictation"
194
+ },
195
+
196
+ # Match Insertion (Direct)
197
+ "match_insert_1": {
198
+ "category": "Match Insertion",
199
+ "description": "Insert Match #1",
200
+ "default": "",
201
+ "action": "insert_match_1",
202
+ "context": "editor"
203
+ },
204
+ "match_insert_2": {
205
+ "category": "Match Insertion",
206
+ "description": "Insert Match #2",
207
+ "default": "",
208
+ "action": "insert_match_2",
209
+ "context": "editor"
210
+ },
211
+ "match_insert_3": {
212
+ "category": "Match Insertion",
213
+ "description": "Insert Match #3",
214
+ "default": "",
215
+ "action": "insert_match_3",
216
+ "context": "editor"
217
+ },
218
+ "match_insert_4": {
219
+ "category": "Match Insertion",
220
+ "description": "Insert Match #4",
221
+ "default": "",
222
+ "action": "insert_match_4",
223
+ "context": "editor"
224
+ },
225
+ "match_insert_5": {
226
+ "category": "Match Insertion",
227
+ "description": "Insert Match #5",
228
+ "default": "",
229
+ "action": "insert_match_5",
230
+ "context": "editor"
231
+ },
232
+ "match_insert_6": {
233
+ "category": "Match Insertion",
234
+ "description": "Insert Match #6",
235
+ "default": "",
236
+ "action": "insert_match_6",
237
+ "context": "editor"
238
+ },
239
+ "match_insert_7": {
240
+ "category": "Match Insertion",
241
+ "description": "Insert Match #7",
242
+ "default": "",
243
+ "action": "insert_match_7",
244
+ "context": "editor"
245
+ },
246
+ "match_insert_8": {
247
+ "category": "Match Insertion",
248
+ "description": "Insert Match #8",
249
+ "default": "",
250
+ "action": "insert_match_8",
251
+ "context": "editor"
252
+ },
253
+ "match_insert_9": {
254
+ "category": "Match Insertion",
255
+ "description": "Insert Match #9",
256
+ "default": "",
257
+ "action": "insert_match_9",
258
+ "context": "editor"
259
+ },
260
+
261
+ # Compare Panel Insertion
262
+ "compare_insert_alt0": {
263
+ "category": "Compare Panel",
264
+ "description": "Insert Compare Panel MT (Alt+0) / TM Target (Alt+0,0)",
265
+ "default": "Alt+0",
266
+ "action": "insert_compare_panel_alt0",
267
+ "context": "editor"
268
+ },
269
+
270
+ # Compare Panel Navigation
271
+ "compare_nav_mt_prev": {
272
+ "category": "Compare Panel",
273
+ "description": "Compare Panel: Previous MT result",
274
+ "default": "Ctrl+Alt+Left",
275
+ "action": "compare_panel_nav_mt_prev",
276
+ "context": "editor"
277
+ },
278
+ "compare_nav_mt_next": {
279
+ "category": "Compare Panel",
280
+ "description": "Compare Panel: Next MT result",
281
+ "default": "Ctrl+Alt+Right",
282
+ "action": "compare_panel_nav_mt_next",
283
+ "context": "editor"
284
+ },
285
+ "compare_nav_tm_prev": {
286
+ "category": "Compare Panel",
287
+ "description": "Compare Panel: Previous TM match",
288
+ "default": "Ctrl+Alt+Up",
289
+ "action": "compare_panel_nav_tm_prev",
290
+ "context": "editor"
291
+ },
292
+ "compare_nav_tm_next": {
293
+ "category": "Compare Panel",
294
+ "description": "Compare Panel: Next TM match",
295
+ "default": "Ctrl+Alt+Down",
296
+ "action": "compare_panel_nav_tm_next",
297
+ "context": "editor"
298
+ },
299
+
300
+ # TermView Insertion (Alt+0-9, double-tap for 00-99)
301
+ "termview_insert_0": {
302
+ "category": "TermView Insertion",
303
+ "description": "Insert TermView Term [0] (or [00] if double-tap)",
304
+ "default": "",
305
+ "action": "insert_termview_0",
306
+ "context": "editor"
307
+ },
308
+ "termview_insert_1": {
309
+ "category": "TermView Insertion",
310
+ "description": "Insert TermView Term [1] (or [11] if double-tap)",
311
+ "default": "Alt+1",
312
+ "action": "insert_termview_1",
313
+ "context": "editor"
314
+ },
315
+ "termview_insert_2": {
316
+ "category": "TermView Insertion",
317
+ "description": "Insert TermView Term [2] (or [22] if double-tap)",
318
+ "default": "Alt+2",
319
+ "action": "insert_termview_2",
320
+ "context": "editor"
321
+ },
322
+ "termview_insert_3": {
323
+ "category": "TermView Insertion",
324
+ "description": "Insert TermView Term [3] (or [33] if double-tap)",
325
+ "default": "Alt+3",
326
+ "action": "insert_termview_3",
327
+ "context": "editor"
328
+ },
329
+ "termview_insert_4": {
330
+ "category": "TermView Insertion",
331
+ "description": "Insert TermView Term [4] (or [44] if double-tap)",
332
+ "default": "Alt+4",
333
+ "action": "insert_termview_4",
334
+ "context": "editor"
335
+ },
336
+ "termview_insert_5": {
337
+ "category": "TermView Insertion",
338
+ "description": "Insert TermView Term [5] (or [55] if double-tap)",
339
+ "default": "Alt+5",
340
+ "action": "insert_termview_5",
341
+ "context": "editor"
342
+ },
343
+ "termview_insert_6": {
344
+ "category": "TermView Insertion",
345
+ "description": "Insert TermView Term [6] (or [66] if double-tap)",
346
+ "default": "Alt+6",
347
+ "action": "insert_termview_6",
348
+ "context": "editor"
349
+ },
350
+ "termview_insert_7": {
351
+ "category": "TermView Insertion",
352
+ "description": "Insert TermView Term [7] (or [77] if double-tap)",
353
+ "default": "Alt+7",
354
+ "action": "insert_termview_7",
355
+ "context": "editor"
356
+ },
357
+ "termview_insert_8": {
358
+ "category": "TermView Insertion",
359
+ "description": "Insert TermView Term [8] (or [88] if double-tap)",
360
+ "default": "Alt+8",
361
+ "action": "insert_termview_8",
362
+ "context": "editor"
363
+ },
364
+ "termview_insert_9": {
365
+ "category": "TermView Insertion",
366
+ "description": "Insert TermView Term [9] (or [99] if double-tap)",
367
+ "default": "Alt+9",
368
+ "action": "insert_termview_9",
369
+ "context": "editor"
370
+ },
371
+
372
+ # Match Navigation
373
+ "match_next": {
374
+ "category": "Match Navigation",
375
+ "description": "Next Match (in results panel)",
376
+ "default": "Down",
377
+ "action": "next_match",
378
+ "context": "match_panel"
379
+ },
380
+ "match_previous": {
381
+ "category": "Match Navigation",
382
+ "description": "Previous Match (in results panel)",
383
+ "default": "Up",
384
+ "action": "previous_match",
385
+ "context": "match_panel"
386
+ },
387
+ "match_cycle_next": {
388
+ "category": "Match Navigation",
389
+ "description": "Cycle to Next Match (from grid)",
390
+ "default": "Ctrl+Down",
391
+ "action": "select_next_match",
392
+ "context": "grid"
393
+ },
394
+ "match_cycle_previous": {
395
+ "category": "Match Navigation",
396
+ "description": "Cycle to Previous Match (from grid)",
397
+ "default": "Ctrl+Up",
398
+ "action": "select_previous_match",
399
+ "context": "grid"
400
+ },
401
+ "match_insert_selected": {
402
+ "category": "Match Navigation",
403
+ "description": "Insert Selected Match",
404
+ "default": "Space or Enter",
405
+ "action": "insert_selected_match",
406
+ "context": "match_panel"
407
+ },
408
+ "match_insert_selected_ctrl": {
409
+ "category": "Match Navigation",
410
+ "description": "Insert Selected Match (from grid)",
411
+ "default": "Ctrl+Space",
412
+ "action": "insert_selected_match",
413
+ "context": "grid"
414
+ },
415
+
416
+ # Grid Navigation
417
+ "segment_next": {
418
+ "category": "Grid Navigation",
419
+ "description": "Next Segment",
420
+ "default": "Alt+Down",
421
+ "action": "go_to_next_segment"
422
+ },
423
+ "segment_previous": {
424
+ "category": "Grid Navigation",
425
+ "description": "Previous Segment",
426
+ "default": "Alt+Up",
427
+ "action": "go_to_previous_segment"
428
+ },
429
+ "segment_go_to_top": {
430
+ "category": "Grid Navigation",
431
+ "description": "Go to First Segment",
432
+ "default": "Ctrl+Home",
433
+ "action": "go_to_first_segment"
434
+ },
435
+ "segment_go_to_bottom": {
436
+ "category": "Grid Navigation",
437
+ "description": "Go to Last Segment",
438
+ "default": "Ctrl+End",
439
+ "action": "go_to_last_segment"
440
+ },
441
+ "page_prev": {
442
+ "category": "Grid Navigation",
443
+ "description": "Previous Page (pagination)",
444
+ "default": "PgUp",
445
+ "action": "go_to_prev_page"
446
+ },
447
+ "page_next": {
448
+ "category": "Grid Navigation",
449
+ "description": "Next Page (pagination)",
450
+ "default": "PgDown",
451
+ "action": "go_to_next_page"
452
+ },
453
+ "select_range_up": {
454
+ "category": "Grid Navigation",
455
+ "description": "Select Range Upward (one page)",
456
+ "default": "Shift+PgUp",
457
+ "action": "select_range_page_up"
458
+ },
459
+ "select_range_down": {
460
+ "category": "Grid Navigation",
461
+ "description": "Select Range Downward (one page)",
462
+ "default": "Shift+PgDown",
463
+ "action": "select_range_page_down"
464
+ },
465
+
466
+ # Editor Operations
467
+ "editor_save_and_next": {
468
+ "category": "Editor",
469
+ "description": "Save & Next Segment",
470
+ "default": "Ctrl+Enter",
471
+ "action": "save_and_next",
472
+ "context": "editor"
473
+ },
474
+ "editor_confirm_selected": {
475
+ "category": "Editor",
476
+ "description": "Confirm All Selected Segments",
477
+ "default": "Ctrl+Shift+Enter",
478
+ "action": "confirm_selected_segments",
479
+ "context": "editor"
480
+ },
481
+ "editor_line_break": {
482
+ "category": "Editor",
483
+ "description": "Insert Line Break",
484
+ "default": "Ctrl+Enter",
485
+ "action": "insert_line_break",
486
+ "context": "editor_alt"
487
+ },
488
+ "editor_cycle_source_target": {
489
+ "category": "Editor",
490
+ "description": "Cycle between Source/Target cells",
491
+ "default": "Tab",
492
+ "action": "cycle_source_target",
493
+ "context": "grid_editor"
494
+ },
495
+ "editor_insert_tab": {
496
+ "category": "Editor",
497
+ "description": "Insert Tab character",
498
+ "default": "Ctrl+Tab",
499
+ "action": "insert_tab",
500
+ "context": "grid_editor"
501
+ },
502
+ "editor_add_to_termbase": {
503
+ "category": "Editor",
504
+ "description": "Add selected term pair to termbase (with dialog)",
505
+ "default": "Ctrl+E",
506
+ "action": "add_to_termbase",
507
+ "context": "grid_editor"
508
+ },
509
+ "editor_quick_add_to_termbase": {
510
+ "category": "Editor",
511
+ "description": "Quick add term pair to last-used termbase",
512
+ "default": "Alt+Left",
513
+ "action": "quick_add_to_termbase",
514
+ "context": "grid_editor"
515
+ },
516
+ "editor_quick_add_priority_1": {
517
+ "category": "Editor",
518
+ "description": "Quick add term pair with Priority 1",
519
+ "default": "Ctrl+Shift+1",
520
+ "action": "quick_add_term_priority_1",
521
+ "context": "grid_editor"
522
+ },
523
+ "editor_quick_add_priority_2": {
524
+ "category": "Editor",
525
+ "description": "Quick add term pair with Priority 2",
526
+ "default": "Ctrl+Shift+2",
527
+ "action": "quick_add_term_priority_2",
528
+ "context": "grid_editor"
529
+ },
530
+ "editor_add_to_non_translatables": {
531
+ "category": "Editor",
532
+ "description": "Add selected text to non-translatables list",
533
+ "default": "Ctrl+Alt+N",
534
+ "action": "add_to_non_translatables",
535
+ "context": "grid_editor"
536
+ },
537
+ "editor_insert_next_tag": {
538
+ "category": "Editor",
539
+ "description": "Insert next tag (memoQ/CafeTran) or wrap selection",
540
+ "default": "Ctrl+,",
541
+ "action": "insert_next_tag",
542
+ "context": "grid_editor"
543
+ },
544
+ "editor_copy_source_to_target": {
545
+ "category": "Editor",
546
+ "description": "Copy source text to target",
547
+ "default": "Ctrl+Shift+S",
548
+ "action": "copy_source_to_target",
549
+ "context": "grid_editor"
550
+ },
551
+ "editor_add_to_dictionary": {
552
+ "category": "Editor",
553
+ "description": "Add word at cursor to custom dictionary",
554
+ "default": "Alt+D",
555
+ "action": "add_word_to_dictionary",
556
+ "context": "grid_editor"
557
+ },
558
+ "editor_open_quickmenu": {
559
+ "category": "Editor",
560
+ "description": "Open QuickMenu for AI prompt actions",
561
+ "default": "Alt+K",
562
+ "action": "open_quickmenu",
563
+ "context": "grid_editor"
564
+ },
565
+ "editor_show_context_menu_double_shift": {
566
+ "category": "Editor",
567
+ "description": "Show context menu (double-tap Shift)",
568
+ "default": "", # Requires AutoHotkey script: supervertaler_hotkeys.ahk
569
+ "action": "show_context_menu_double_shift",
570
+ "context": "grid_editor",
571
+ "note": "Requires AutoHotkey. Run supervertaler_hotkeys.ahk for this feature."
572
+ },
573
+
574
+ # Filter Operations
575
+ "filter_selected_text": {
576
+ "category": "Filter",
577
+ "description": "Filter on selected text / Clear filter (toggle)",
578
+ "default": "Ctrl+Shift+F",
579
+ "action": "filter_on_selected_text"
580
+ },
581
+ "clear_filter": {
582
+ "category": "Filter",
583
+ "description": "Clear filter (same as above - toggle behavior)",
584
+ "default": "Ctrl+Shift+F",
585
+ "action": "filter_on_selected_text"
586
+ },
587
+ }
588
+
589
+ def __init__(self, settings_file: Optional[Path] = None):
590
+ """
591
+ Initialize ShortcutManager
592
+
593
+ Args:
594
+ settings_file: Path to JSON file for storing custom shortcuts
595
+ """
596
+ self.settings_file = settings_file or Path("user_data/shortcuts.json")
597
+ self.custom_shortcuts = {}
598
+ self.disabled_shortcuts = set() # Set of disabled shortcut IDs
599
+ self.load_shortcuts()
600
+
601
+ def load_shortcuts(self):
602
+ """Load custom shortcuts from file"""
603
+ if self.settings_file.exists():
604
+ try:
605
+ with open(self.settings_file, 'r', encoding='utf-8') as f:
606
+ data = json.load(f)
607
+ # Support both old format (dict of shortcuts) and new format (dict with shortcuts + disabled)
608
+ if isinstance(data, dict):
609
+ if "shortcuts" in data:
610
+ # New format: {"shortcuts": {...}, "disabled": [...]}
611
+ self.custom_shortcuts = data.get("shortcuts", {})
612
+ self.disabled_shortcuts = set(data.get("disabled", []))
613
+ else:
614
+ # Old format: just the shortcuts dict
615
+ self.custom_shortcuts = data
616
+ self.disabled_shortcuts = set()
617
+ else:
618
+ self.custom_shortcuts = {}
619
+ self.disabled_shortcuts = set()
620
+ except Exception as e:
621
+ print(f"Error loading shortcuts: {e}")
622
+ self.custom_shortcuts = {}
623
+ self.disabled_shortcuts = set()
624
+
625
+ def save_shortcuts(self):
626
+ """Save custom shortcuts to file"""
627
+ try:
628
+ self.settings_file.parent.mkdir(parents=True, exist_ok=True)
629
+ # Save in new format that includes both shortcuts and disabled list
630
+ save_data = {
631
+ "shortcuts": self.custom_shortcuts,
632
+ "disabled": list(self.disabled_shortcuts)
633
+ }
634
+ with open(self.settings_file, 'w', encoding='utf-8') as f:
635
+ json.dump(save_data, f, indent=2)
636
+ except Exception as e:
637
+ print(f"Error saving shortcuts: {e}")
638
+
639
+ def get_shortcut(self, shortcut_id: str) -> str:
640
+ """
641
+ Get the current shortcut for a given ID
642
+
643
+ Args:
644
+ shortcut_id: The shortcut identifier
645
+
646
+ Returns:
647
+ The key sequence string (e.g., "Ctrl+T")
648
+ """
649
+ if shortcut_id in self.custom_shortcuts:
650
+ return self.custom_shortcuts[shortcut_id]
651
+
652
+ if shortcut_id in self.DEFAULT_SHORTCUTS:
653
+ return self.DEFAULT_SHORTCUTS[shortcut_id]["default"]
654
+
655
+ return ""
656
+
657
+ def is_enabled(self, shortcut_id: str) -> bool:
658
+ """
659
+ Check if a shortcut is enabled
660
+
661
+ Args:
662
+ shortcut_id: The shortcut identifier
663
+
664
+ Returns:
665
+ True if enabled (not in disabled set), False if disabled
666
+ """
667
+ return shortcut_id not in self.disabled_shortcuts
668
+
669
+ def enable_shortcut(self, shortcut_id: str):
670
+ """
671
+ Enable a previously disabled shortcut
672
+
673
+ Args:
674
+ shortcut_id: The shortcut identifier
675
+ """
676
+ self.disabled_shortcuts.discard(shortcut_id)
677
+
678
+ def disable_shortcut(self, shortcut_id: str):
679
+ """
680
+ Disable a shortcut
681
+
682
+ Args:
683
+ shortcut_id: The shortcut identifier
684
+ """
685
+ self.disabled_shortcuts.add(shortcut_id)
686
+
687
+ def set_shortcut(self, shortcut_id: str, key_sequence: str):
688
+ """
689
+ Set a custom shortcut
690
+
691
+ Args:
692
+ shortcut_id: The shortcut identifier
693
+ key_sequence: The new key sequence string
694
+ """
695
+ if key_sequence:
696
+ self.custom_shortcuts[shortcut_id] = key_sequence
697
+ elif shortcut_id in self.custom_shortcuts:
698
+ del self.custom_shortcuts[shortcut_id]
699
+
700
+ def reset_shortcut(self, shortcut_id: str):
701
+ """Reset a shortcut to its default value"""
702
+ if shortcut_id in self.custom_shortcuts:
703
+ del self.custom_shortcuts[shortcut_id]
704
+
705
+ def reset_all_shortcuts(self):
706
+ """Reset all shortcuts to defaults"""
707
+ self.custom_shortcuts = {}
708
+
709
+ def get_all_shortcuts(self) -> Dict:
710
+ """
711
+ Get all shortcuts with their current values
712
+
713
+ Returns:
714
+ Dictionary of all shortcuts with metadata
715
+ """
716
+ result = {}
717
+ for shortcut_id, data in self.DEFAULT_SHORTCUTS.items():
718
+ result[shortcut_id] = {
719
+ **data,
720
+ "current": self.get_shortcut(shortcut_id),
721
+ "is_custom": shortcut_id in self.custom_shortcuts,
722
+ "is_enabled": self.is_enabled(shortcut_id)
723
+ }
724
+ return result
725
+
726
+ def get_shortcuts_by_category(self) -> Dict[str, List[Tuple[str, Dict]]]:
727
+ """
728
+ Get shortcuts organized by category
729
+
730
+ Returns:
731
+ Dictionary with categories as keys, list of (id, data) tuples as values
732
+ """
733
+ categories = {}
734
+ all_shortcuts = self.get_all_shortcuts()
735
+
736
+ for shortcut_id, data in all_shortcuts.items():
737
+ category = data["category"]
738
+ if category not in categories:
739
+ categories[category] = []
740
+ categories[category].append((shortcut_id, data))
741
+
742
+ return categories
743
+
744
+ def find_conflicts(self, shortcut_id: str, key_sequence: str) -> List[str]:
745
+ """
746
+ Find conflicts with a proposed shortcut
747
+
748
+ Args:
749
+ shortcut_id: The shortcut being changed
750
+ key_sequence: The proposed new key sequence
751
+
752
+ Returns:
753
+ List of conflicting shortcut IDs
754
+ """
755
+ conflicts = []
756
+ for other_id, data in self.get_all_shortcuts().items():
757
+ if other_id != shortcut_id and data["current"] == key_sequence:
758
+ # Check if they're in different contexts (context-specific shortcuts don't conflict)
759
+ this_context = self.DEFAULT_SHORTCUTS.get(shortcut_id, {}).get("context")
760
+ other_context = self.DEFAULT_SHORTCUTS.get(other_id, {}).get("context")
761
+
762
+ # Only conflict if same context or no context specified
763
+ if this_context == other_context or not this_context or not other_context:
764
+ conflicts.append(other_id)
765
+
766
+ return conflicts
767
+
768
+ def export_shortcuts(self, file_path: Path):
769
+ """
770
+ Export shortcuts to a JSON file
771
+
772
+ Args:
773
+ file_path: Path to export file
774
+ """
775
+ export_data = {
776
+ "version": "1.0",
777
+ "shortcuts": self.custom_shortcuts,
778
+ "disabled": list(self.disabled_shortcuts)
779
+ }
780
+
781
+ with open(file_path, 'w', encoding='utf-8') as f:
782
+ json.dump(export_data, f, indent=2)
783
+
784
+ def import_shortcuts(self, file_path: Path) -> bool:
785
+ """
786
+ Import shortcuts from a JSON file
787
+
788
+ Args:
789
+ file_path: Path to import file
790
+
791
+ Returns:
792
+ True if successful, False otherwise
793
+ """
794
+ try:
795
+ with open(file_path, 'r', encoding='utf-8') as f:
796
+ import_data = json.load(f)
797
+
798
+ if "shortcuts" in import_data:
799
+ self.custom_shortcuts = import_data["shortcuts"]
800
+ self.disabled_shortcuts = set(import_data.get("disabled", []))
801
+ return True
802
+ return False
803
+ except Exception as e:
804
+ print(f"Error importing shortcuts: {e}")
805
+ return False
806
+
807
+ def export_html_cheatsheet(self, file_path: Path):
808
+ """
809
+ Export shortcuts as an HTML cheatsheet
810
+
811
+ Args:
812
+ file_path: Path to export HTML file
813
+ """
814
+ categories = self.get_shortcuts_by_category()
815
+
816
+ html = """
817
+ <!DOCTYPE html>
818
+ <html>
819
+ <head>
820
+ <meta charset="UTF-8">
821
+ <title>Supervertaler - Keyboard Shortcuts</title>
822
+ <style>
823
+ body {
824
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
825
+ max-width: 1200px;
826
+ margin: 40px auto;
827
+ padding: 20px;
828
+ background-color: #f5f5f5;
829
+ }
830
+ h1 {
831
+ color: #2196F3;
832
+ text-align: center;
833
+ border-bottom: 3px solid #2196F3;
834
+ padding-bottom: 20px;
835
+ }
836
+ h2 {
837
+ color: #1976D2;
838
+ margin-top: 40px;
839
+ margin-bottom: 20px;
840
+ border-left: 5px solid #2196F3;
841
+ padding-left: 15px;
842
+ }
843
+ table {
844
+ width: 100%;
845
+ border-collapse: collapse;
846
+ background-color: white;
847
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
848
+ margin-bottom: 30px;
849
+ }
850
+ th {
851
+ background-color: #2196F3;
852
+ color: white;
853
+ padding: 12px;
854
+ text-align: left;
855
+ font-weight: 600;
856
+ }
857
+ td {
858
+ padding: 10px 12px;
859
+ border-bottom: 1px solid #e0e0e0;
860
+ }
861
+ tr:hover {
862
+ background-color: #f5f5f5;
863
+ }
864
+ .shortcut {
865
+ font-family: 'Courier New', monospace;
866
+ background-color: #e3f2fd;
867
+ padding: 4px 8px;
868
+ border-radius: 4px;
869
+ font-weight: 600;
870
+ color: #1976D2;
871
+ }
872
+ .custom {
873
+ color: #4CAF50;
874
+ font-weight: 600;
875
+ }
876
+ .footer {
877
+ text-align: center;
878
+ margin-top: 40px;
879
+ color: #666;
880
+ font-size: 0.9em;
881
+ }
882
+ @media print {
883
+ body {
884
+ background-color: white;
885
+ }
886
+ table {
887
+ box-shadow: none;
888
+ page-break-inside: avoid;
889
+ }
890
+ }
891
+ </style>
892
+ </head>
893
+ <body>
894
+ <h1>🌐 Supervertaler - Keyboard Shortcuts</h1>
895
+ <div class="footer" style="text-align: center; margin-bottom: 30px;">
896
+ <p>The Ultimate Translation Workbench</p>
897
+ </div>
898
+ """
899
+
900
+ # Add each category
901
+ for category in sorted(categories.keys()):
902
+ shortcuts = categories[category]
903
+ html += f" <h2>{category}</h2>\n"
904
+ html += " <table>\n"
905
+ html += " <tr><th>Action</th><th>Shortcut</th></tr>\n"
906
+
907
+ for shortcut_id, data in sorted(shortcuts, key=lambda x: x[1]["description"]):
908
+ description = data["description"]
909
+ current = data["current"]
910
+ is_custom = data["is_custom"]
911
+
912
+ custom_mark = " <span class='custom'>(Custom)</span>" if is_custom else ""
913
+
914
+ html += f" <tr>\n"
915
+ html += f" <td>{description}{custom_mark}</td>\n"
916
+ html += f" <td><span class='shortcut'>{current}</span></td>\n"
917
+ html += f" </tr>\n"
918
+
919
+ html += " </table>\n"
920
+
921
+ html += """
922
+ <div class="footer">
923
+ <p>Generated by Supervertaler Qt Edition</p>
924
+ <p>For more information, visit <a href="https://supervertaler.com">supervertaler.com</a></p>
925
+ </div>
926
+ </body>
927
+ </html>
928
+ """
929
+
930
+ with open(file_path, 'w', encoding='utf-8') as f:
931
+ f.write(html)
932
+