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