pomera-ai-commander 1.1.1 → 1.2.2

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 (213) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1199 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_directory.py +549 -0
  12. package/core/data_validator.py +1066 -1066
  13. package/core/database_connection_manager.py +744 -744
  14. package/core/database_curl_settings_manager.py +608 -608
  15. package/core/database_promera_ai_settings_manager.py +446 -446
  16. package/core/database_schema.py +411 -411
  17. package/core/database_schema_manager.py +395 -395
  18. package/core/database_settings_manager.py +1507 -1507
  19. package/core/database_settings_manager_interface.py +456 -456
  20. package/core/dialog_manager.py +734 -734
  21. package/core/diff_utils.py +239 -0
  22. package/core/efficient_line_numbers.py +540 -510
  23. package/core/error_handler.py +746 -746
  24. package/core/error_service.py +431 -431
  25. package/core/event_consolidator.py +511 -511
  26. package/core/mcp/__init__.py +43 -43
  27. package/core/mcp/find_replace_diff.py +334 -0
  28. package/core/mcp/protocol.py +288 -288
  29. package/core/mcp/schema.py +251 -251
  30. package/core/mcp/server_stdio.py +299 -299
  31. package/core/mcp/tool_registry.py +2699 -2345
  32. package/core/memento.py +275 -0
  33. package/core/memory_efficient_text_widget.py +711 -711
  34. package/core/migration_manager.py +914 -914
  35. package/core/migration_test_suite.py +1085 -1085
  36. package/core/migration_validator.py +1143 -1143
  37. package/core/optimized_find_replace.py +714 -714
  38. package/core/optimized_pattern_engine.py +424 -424
  39. package/core/optimized_search_highlighter.py +552 -552
  40. package/core/performance_monitor.py +674 -674
  41. package/core/persistence_manager.py +712 -712
  42. package/core/progressive_stats_calculator.py +632 -632
  43. package/core/regex_pattern_cache.py +529 -529
  44. package/core/regex_pattern_library.py +350 -350
  45. package/core/search_operation_manager.py +434 -434
  46. package/core/settings_defaults_registry.py +1087 -1087
  47. package/core/settings_integrity_validator.py +1111 -1111
  48. package/core/settings_serializer.py +557 -557
  49. package/core/settings_validator.py +1823 -1823
  50. package/core/smart_stats_calculator.py +709 -709
  51. package/core/statistics_update_manager.py +619 -619
  52. package/core/stats_config_manager.py +858 -858
  53. package/core/streaming_text_handler.py +723 -723
  54. package/core/task_scheduler.py +596 -596
  55. package/core/update_pattern_library.py +168 -168
  56. package/core/visibility_monitor.py +596 -596
  57. package/core/widget_cache.py +498 -498
  58. package/mcp.json +51 -61
  59. package/migrate_data.py +127 -0
  60. package/package.json +64 -57
  61. package/pomera.py +7883 -7482
  62. package/pomera_mcp_server.py +183 -144
  63. package/requirements.txt +33 -0
  64. package/scripts/Dockerfile.alpine +43 -0
  65. package/scripts/Dockerfile.gui-test +54 -0
  66. package/scripts/Dockerfile.linux +43 -0
  67. package/scripts/Dockerfile.test-linux +80 -0
  68. package/scripts/Dockerfile.ubuntu +39 -0
  69. package/scripts/README.md +53 -0
  70. package/scripts/build-all.bat +113 -0
  71. package/scripts/build-docker.bat +53 -0
  72. package/scripts/build-docker.sh +55 -0
  73. package/scripts/build-optimized.bat +101 -0
  74. package/scripts/build.sh +78 -0
  75. package/scripts/docker-compose.test.yml +27 -0
  76. package/scripts/docker-compose.yml +32 -0
  77. package/scripts/postinstall.js +62 -0
  78. package/scripts/requirements-minimal.txt +33 -0
  79. package/scripts/test-linux-simple.bat +28 -0
  80. package/scripts/validate-release-workflow.py +450 -0
  81. package/tools/__init__.py +4 -4
  82. package/tools/ai_tools.py +2891 -2891
  83. package/tools/ascii_art_generator.py +352 -352
  84. package/tools/base64_tools.py +183 -183
  85. package/tools/base_tool.py +511 -511
  86. package/tools/case_tool.py +308 -308
  87. package/tools/column_tools.py +395 -395
  88. package/tools/cron_tool.py +884 -884
  89. package/tools/curl_history.py +600 -600
  90. package/tools/curl_processor.py +1207 -1207
  91. package/tools/curl_settings.py +502 -502
  92. package/tools/curl_tool.py +5467 -5467
  93. package/tools/diff_viewer.py +1817 -1072
  94. package/tools/email_extraction_tool.py +248 -248
  95. package/tools/email_header_analyzer.py +425 -425
  96. package/tools/extraction_tools.py +250 -250
  97. package/tools/find_replace.py +2289 -1750
  98. package/tools/folder_file_reporter.py +1463 -1463
  99. package/tools/folder_file_reporter_adapter.py +480 -480
  100. package/tools/generator_tools.py +1216 -1216
  101. package/tools/hash_generator.py +255 -255
  102. package/tools/html_tool.py +656 -656
  103. package/tools/jsonxml_tool.py +729 -729
  104. package/tools/line_tools.py +419 -419
  105. package/tools/markdown_tools.py +561 -561
  106. package/tools/mcp_widget.py +1417 -1417
  107. package/tools/notes_widget.py +978 -973
  108. package/tools/number_base_converter.py +372 -372
  109. package/tools/regex_extractor.py +571 -571
  110. package/tools/slug_generator.py +310 -310
  111. package/tools/sorter_tools.py +458 -458
  112. package/tools/string_escape_tool.py +392 -392
  113. package/tools/text_statistics_tool.py +365 -365
  114. package/tools/text_wrapper.py +430 -430
  115. package/tools/timestamp_converter.py +421 -421
  116. package/tools/tool_loader.py +710 -710
  117. package/tools/translator_tools.py +522 -522
  118. package/tools/url_link_extractor.py +261 -261
  119. package/tools/url_parser.py +204 -204
  120. package/tools/whitespace_tools.py +355 -355
  121. package/tools/word_frequency_counter.py +146 -146
  122. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  123. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  124. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  125. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  126. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  127. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  128. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  129. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  131. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  132. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  134. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  135. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  136. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  137. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  138. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  139. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  140. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  141. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  142. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  143. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  144. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  145. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  146. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  147. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  148. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  149. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  150. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  151. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  152. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  153. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  154. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  155. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  156. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  157. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  158. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  159. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  160. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  161. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  162. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  163. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  164. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  165. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  166. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  167. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  168. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  169. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  170. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  192. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  193. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  194. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  195. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  196. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  197. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  198. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  199. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  200. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  201. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  202. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  203. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  204. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  205. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  206. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  207. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  208. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  209. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  210. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  211. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  212. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  213. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,523 +1,523 @@
1
- """
2
- Translator Tools Module - Binary and Morse code translation utilities
3
-
4
- This module provides comprehensive translation functionality with a tabbed UI interface
5
- for the Promera AI Commander application.
6
- """
7
-
8
- import tkinter as tk
9
- from tkinter import ttk, messagebox
10
- import base64
11
- import threading
12
- import time
13
-
14
- # Try to import NumPy for Morse code audio generation
15
- try:
16
- import numpy as np
17
- NUMPY_AVAILABLE = True
18
- except ImportError:
19
- NUMPY_AVAILABLE = False
20
-
21
- # Try to import PyAudio for Morse code audio
22
- try:
23
- import pyaudio
24
- PYAUDIO_AVAILABLE = True
25
- except ImportError:
26
- PYAUDIO_AVAILABLE = False
27
-
28
-
29
- class TranslatorToolsProcessor:
30
- """Translator tools processor with binary and Morse code translation capabilities."""
31
-
32
- # Morse code dictionary
33
- MORSE_CODE_DICT = {
34
- 'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....',
35
- 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.',
36
- 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
37
- 'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....',
38
- '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----', ' ': '/',
39
- ',': '--..--', '.': '.-.-.-', '?': '..--..', '/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-'
40
- }
41
-
42
- REVERSED_MORSE_DICT = {v: k for k, v in MORSE_CODE_DICT.items()}
43
-
44
- # Audio constants
45
- MORSE_DOT_DURATION = 0.080
46
- MORSE_DASH_DURATION = 0.080 * 3
47
- SAMPLE_RATE = 44100
48
- TONE_FREQUENCY = 700
49
-
50
- @staticmethod
51
- def morse_translator(text, mode):
52
- """Translates text to or from Morse code."""
53
- if mode == "morse":
54
- return ' '.join(TranslatorToolsProcessor.MORSE_CODE_DICT.get(char.upper(), '') for char in text)
55
- else: # mode == "text"
56
- return ''.join(TranslatorToolsProcessor.REVERSED_MORSE_DICT.get(code, '') for code in text.split(' '))
57
-
58
- @staticmethod
59
- def binary_translator(text):
60
- """Translates text to or from binary."""
61
- # Detect if input is binary or text
62
- if all(c in ' 01' for c in text): # Binary to Text
63
- try:
64
- return ''.join(chr(int(b, 2)) for b in text.split())
65
- except (ValueError, TypeError):
66
- return "Error: Invalid binary sequence."
67
- else: # Text to Binary
68
- return ' '.join(format(ord(char), '08b') for char in text)
69
-
70
- @staticmethod
71
- def process_text(input_text, tool_type, settings):
72
- """Process text using the specified translator tool and settings."""
73
- if tool_type == "Morse Code Translator":
74
- return TranslatorToolsProcessor.morse_translator(
75
- input_text,
76
- settings.get("mode", "morse")
77
- )
78
- elif tool_type == "Binary Code Translator":
79
- return TranslatorToolsProcessor.binary_translator(input_text)
80
- else:
81
- return f"Unknown translator tool: {tool_type}"
82
-
83
-
84
- class TranslatorToolsWidget(ttk.Frame):
85
- """Tabbed interface widget for translator tools, similar to Sorter Tools."""
86
-
87
- def __init__(self, parent, app, dialog_manager=None):
88
- super().__init__(parent)
89
- self.app = app
90
- self.dialog_manager = dialog_manager
91
- self.processor = TranslatorToolsProcessor()
92
-
93
- # Initialize UI variables for Morse Code Translator
94
- self.morse_mode = tk.StringVar(value="morse")
95
-
96
- # Audio-related variables
97
- self.morse_thread = None
98
- self.stop_morse_playback = threading.Event()
99
- self.audio_stream = None
100
- self.pyaudio_instance = None
101
-
102
- # Initialize PyAudio if available
103
- self.setup_audio()
104
-
105
- self.create_widgets()
106
- self.load_settings()
107
-
108
- def _show_error(self, title, message):
109
- """Show error dialog using DialogManager if available, otherwise use messagebox."""
110
- if self.dialog_manager:
111
- return self.dialog_manager.show_error(title, message, parent=self.winfo_toplevel())
112
- else:
113
- from tkinter import messagebox
114
- messagebox.showerror(title, message, parent=self.winfo_toplevel())
115
- return True
116
-
117
- def _show_audio_setup_instructions(self, missing_library):
118
- """Show detailed instructions for setting up audio features."""
119
- title = "Audio Feature Setup Required"
120
-
121
- if missing_library == "NumPy":
122
- message = """Morse Code Audio is not available in this optimized build.
123
-
124
- To enable audio features, you have two options:
125
-
126
- OPTION 1: Run from Source (Recommended)
127
- 1. Download Python 3.8+ from python.org
128
- 2. Install required libraries:
129
- pip install numpy pyaudio
130
- 3. Download source code from GitHub
131
- 4. Run: python pomera.py
132
-
133
- OPTION 2: Use Text-Only Mode
134
- • Morse code text translation works perfectly
135
- • Copy the Morse code and use external audio tools
136
- • This keeps the executable small (40MB vs 400MB+)
137
-
138
- The current executable was optimized for size by excluding
139
- audio libraries. Text-based Morse code works fully!"""
140
-
141
- else: # PyAudio
142
- message = """Audio playback is not available.
143
-
144
- To enable Morse code audio:
145
-
146
- OPTION 1: Install Audio Libraries
147
- 1. Install Python 3.8+ from python.org
148
- 2. Install audio libraries:
149
- pip install pyaudio numpy
150
- 3. Run from source: python pomera.py
151
-
152
- OPTION 2: Use Text-Only Mode
153
- • All Morse code translation features work
154
- • Copy output to external audio tools if needed
155
-
156
- Note: Audio libraries were excluded to keep the
157
- executable small and portable."""
158
-
159
- if self.dialog_manager:
160
- return self.dialog_manager.show_info(title, message, parent=self.winfo_toplevel())
161
- else:
162
- from tkinter import messagebox
163
- messagebox.showinfo(title, message, parent=self.winfo_toplevel())
164
- return True
165
-
166
- def setup_audio(self):
167
- """Initialize PyAudio for Morse code audio playback."""
168
- if not PYAUDIO_AVAILABLE:
169
- return
170
-
171
- try:
172
- self.pyaudio_instance = pyaudio.PyAudio()
173
- self.audio_stream = self.pyaudio_instance.open(
174
- format=pyaudio.paFloat32,
175
- channels=1,
176
- rate=TranslatorToolsProcessor.SAMPLE_RATE,
177
- output=True
178
- )
179
- except Exception as e:
180
- print(f"Failed to initialize PyAudio: {e}")
181
- self.pyaudio_instance = None
182
- self.audio_stream = None
183
-
184
- def create_widgets(self):
185
- """Creates the tabbed interface for translator tools."""
186
- # Create notebook for tabs
187
- self.notebook = ttk.Notebook(self)
188
- self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
189
-
190
- # Create Morse Code Translator tab
191
- self.morse_frame = ttk.Frame(self.notebook)
192
- self.notebook.add(self.morse_frame, text="Morse Code Translator")
193
- self.create_morse_translator_widgets()
194
-
195
- # Create Binary Code Translator tab
196
- self.binary_frame = ttk.Frame(self.notebook)
197
- self.notebook.add(self.binary_frame, text="Binary Code Translator")
198
- self.create_binary_translator_widgets()
199
-
200
- def create_morse_translator_widgets(self):
201
- """Creates widgets for the Morse Code Translator tab."""
202
- # Mode selection
203
- mode_frame = ttk.LabelFrame(self.morse_frame, text="Translation Mode", padding=10)
204
- mode_frame.pack(fill=tk.X, padx=5, pady=5)
205
-
206
- ttk.Radiobutton(
207
- mode_frame,
208
- text="Text to Morse",
209
- variable=self.morse_mode,
210
- value="morse",
211
- command=self._on_setting_change
212
- ).pack(side=tk.LEFT, padx=5)
213
-
214
- ttk.Radiobutton(
215
- mode_frame,
216
- text="Morse to Text",
217
- variable=self.morse_mode,
218
- value="text",
219
- command=self._on_setting_change
220
- ).pack(side=tk.LEFT, padx=5)
221
-
222
- # Buttons
223
- button_frame = ttk.Frame(self.morse_frame)
224
- button_frame.pack(fill=tk.X, padx=5, pady=5)
225
-
226
- ttk.Button(
227
- button_frame,
228
- text="Translate",
229
- command=self._apply_morse_translator
230
- ).pack(side=tk.LEFT, padx=5)
231
-
232
- # Audio button (only if PyAudio is available)
233
- if PYAUDIO_AVAILABLE and self.audio_stream:
234
- self.play_morse_button = ttk.Button(
235
- button_frame,
236
- text="Play Morse Audio",
237
- command=self._play_morse_audio
238
- )
239
- self.play_morse_button.pack(side=tk.LEFT, padx=5)
240
-
241
- def create_binary_translator_widgets(self):
242
- """Creates widgets for the Binary Code Translator tab."""
243
- # Info label
244
- info_frame = ttk.LabelFrame(self.binary_frame, text="Information", padding=10)
245
- info_frame.pack(fill=tk.X, padx=5, pady=5)
246
-
247
- info_label = ttk.Label(
248
- info_frame,
249
- text="Automatically detects input type:\n• Text → Binary (8-bit per character)\n• Binary → Text (space-separated binary)"
250
- )
251
- info_label.pack()
252
-
253
- # Button
254
- button_frame = ttk.Frame(self.binary_frame)
255
- button_frame.pack(fill=tk.X, padx=5, pady=5)
256
-
257
- ttk.Button(
258
- button_frame,
259
- text="Translate",
260
- command=self._apply_binary_translator
261
- ).pack(side=tk.LEFT, padx=5)
262
-
263
- def _on_setting_change(self):
264
- """Handle setting changes."""
265
- self.save_settings()
266
- if hasattr(self.app, 'on_tool_setting_change'):
267
- self.app.on_tool_setting_change()
268
-
269
- def _apply_morse_translator(self):
270
- """Apply the Morse Code Translator tool."""
271
- self._apply_tool("Morse Code Translator")
272
-
273
- def _apply_binary_translator(self):
274
- """Apply the Binary Code Translator tool."""
275
- self._apply_tool("Binary Code Translator")
276
-
277
- def _apply_tool(self, tool_type):
278
- """Apply the specified translator tool."""
279
- try:
280
- # Get input text from the active input tab
281
- active_input_tab = self.app.input_tabs[self.app.input_notebook.index(self.app.input_notebook.select())]
282
- input_text = active_input_tab.text.get("1.0", tk.END).strip()
283
-
284
- if not input_text:
285
- # Show a message if no input text
286
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
287
- active_output_tab.text.config(state="normal")
288
- active_output_tab.text.delete("1.0", tk.END)
289
- active_output_tab.text.insert("1.0", f"Please enter text to translate in the input area.\n\nFor {tool_type}:\n" +
290
- ("- Enter text to convert to Morse code, or Morse code to convert to text" if tool_type == "Morse Code Translator"
291
- else "- Enter text to convert to binary, or binary code to convert to text"))
292
- active_output_tab.text.config(state="disabled")
293
- return
294
-
295
- # Get settings for the tool
296
- settings = self.get_tool_settings(tool_type)
297
-
298
- # Process the text
299
- result = self.processor.process_text(input_text, tool_type, settings)
300
-
301
- # Update output
302
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
303
- active_output_tab.text.config(state="normal")
304
- active_output_tab.text.delete("1.0", tk.END)
305
- active_output_tab.text.insert("1.0", result)
306
- active_output_tab.text.config(state="disabled")
307
-
308
- # Update statistics
309
- if hasattr(self.app, 'update_all_stats'):
310
- self.app.after(10, self.app.update_all_stats)
311
-
312
- except Exception as e:
313
- # Show error in output if something goes wrong
314
- try:
315
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
316
- active_output_tab.text.config(state="normal")
317
- active_output_tab.text.delete("1.0", tk.END)
318
- active_output_tab.text.insert("1.0", f"Error: {str(e)}")
319
- active_output_tab.text.config(state="disabled")
320
- except:
321
- print(f"Translator Tools Error: {str(e)}") # Fallback to console
322
-
323
- def _play_morse_audio(self):
324
- """Play Morse code audio from the output area."""
325
- if self.morse_thread and self.morse_thread.is_alive():
326
- print("Morse playback is already in progress.")
327
- return
328
-
329
- if not PYAUDIO_AVAILABLE or not self.audio_stream:
330
- self._show_audio_setup_instructions("PyAudio")
331
- return
332
-
333
- if not NUMPY_AVAILABLE:
334
- self._show_audio_setup_instructions("NumPy")
335
- return
336
-
337
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
338
- morse_code = active_output_tab.text.get("1.0", tk.END).strip()
339
- if not morse_code:
340
- print("No Morse code to play.")
341
- return
342
-
343
- self.stop_morse_playback.clear()
344
- self.morse_thread = threading.Thread(target=self._play_morse_thread, args=(morse_code,), daemon=True)
345
- self.morse_thread.start()
346
-
347
- def _stop_morse_audio(self):
348
- """Stop the currently playing Morse code audio."""
349
- self.stop_morse_playback.set()
350
-
351
- def _play_morse_thread(self, morse_code):
352
- """The actual playback logic that runs in a thread."""
353
- if hasattr(self, 'play_morse_button'):
354
- self.play_morse_button.config(text="Stop Playing", command=self._stop_morse_audio)
355
- print("Starting Morse code playback.")
356
-
357
- try:
358
- for char in morse_code:
359
- if self.stop_morse_playback.is_set():
360
- print("Morse playback stopped by user.")
361
- break
362
- if char == '.':
363
- tone = self._generate_morse_tone(TranslatorToolsProcessor.MORSE_DOT_DURATION)
364
- self.audio_stream.write(tone.tobytes())
365
- time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION)
366
- elif char == '-':
367
- tone = self._generate_morse_tone(TranslatorToolsProcessor.MORSE_DASH_DURATION)
368
- self.audio_stream.write(tone.tobytes())
369
- time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION)
370
- elif char == ' ':
371
- time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION * 3 - TranslatorToolsProcessor.MORSE_DOT_DURATION)
372
- elif char == '/':
373
- time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION * 7 - TranslatorToolsProcessor.MORSE_DOT_DURATION)
374
- except Exception as e:
375
- print(f"Error during morse playback: {e}")
376
- finally:
377
- if hasattr(self, 'play_morse_button'):
378
- self.play_morse_button.config(text="Play Morse Audio", command=self._play_morse_audio)
379
- print("Morse code playback finished.")
380
- self.stop_morse_playback.clear()
381
-
382
- def _generate_morse_tone(self, duration):
383
- """Generate a sine wave for a given duration for Morse code."""
384
- if not NUMPY_AVAILABLE:
385
- # Return silence if numpy is not available
386
- import array
387
- sample_count = int(TranslatorToolsProcessor.SAMPLE_RATE * duration)
388
- return array.array('f', [0.0] * sample_count)
389
-
390
- tone_freq = TranslatorToolsProcessor.TONE_FREQUENCY
391
- t = np.linspace(0, duration, int(TranslatorToolsProcessor.SAMPLE_RATE * duration), False)
392
- tone = np.sin(tone_freq * t * 2 * np.pi)
393
- return (0.5 * tone).astype(np.float32)
394
-
395
- def get_tool_settings(self, tool_type):
396
- """Get settings for the specified tool."""
397
- if tool_type == "Morse Code Translator":
398
- return {
399
- "mode": self.morse_mode.get()
400
- }
401
- elif tool_type == "Binary Code Translator":
402
- return {} # Binary translator doesn't have settings
403
- return {}
404
-
405
- def get_all_settings(self):
406
- """Get all settings for both translator tools."""
407
- return {
408
- "Morse Code Translator": self.get_tool_settings("Morse Code Translator"),
409
- "Binary Code Translator": self.get_tool_settings("Binary Code Translator")
410
- }
411
-
412
- def load_settings(self):
413
- """Load settings from the main application."""
414
- if not hasattr(self.app, 'settings'):
415
- return
416
-
417
- # Load Morse Code Translator settings
418
- morse_settings = self.app.settings.get("tool_settings", {}).get("Morse Code Translator", {})
419
- self.morse_mode.set(morse_settings.get("mode", "morse"))
420
-
421
- def save_settings(self):
422
- """Save settings to the main application."""
423
- if not hasattr(self.app, 'settings'):
424
- return
425
-
426
- # Save Morse Code Translator settings
427
- if "Morse Code Translator" not in self.app.settings["tool_settings"]:
428
- self.app.settings["tool_settings"]["Morse Code Translator"] = {}
429
- self.app.settings["tool_settings"]["Morse Code Translator"]["mode"] = self.morse_mode.get()
430
-
431
- def __del__(self):
432
- """Cleanup audio resources when widget is destroyed."""
433
- if self.audio_stream:
434
- try:
435
- self.audio_stream.stop_stream()
436
- self.audio_stream.close()
437
- except:
438
- pass
439
- if self.pyaudio_instance:
440
- try:
441
- self.pyaudio_instance.terminate()
442
- except:
443
- pass
444
-
445
-
446
- class TranslatorTools:
447
- """Main Translator Tools class that provides the interface for the main application."""
448
-
449
- def __init__(self):
450
- self.processor = TranslatorToolsProcessor()
451
- self.widget = None
452
-
453
- def create_widget(self, parent, app, dialog_manager=None):
454
- """Create and return the tabbed widget component."""
455
- self.widget = TranslatorToolsWidget(parent, app, dialog_manager)
456
- return self.widget
457
-
458
- def process_text(self, input_text, tool_type, settings):
459
- """Process text using the specified translator tool and settings."""
460
- return self.processor.process_text(input_text, tool_type, settings)
461
-
462
- def get_default_settings(self):
463
- """Get default settings for both translator tools."""
464
- return {
465
- "Morse Code Translator": {"mode": "morse", "tone": TranslatorToolsProcessor.TONE_FREQUENCY},
466
- "Binary Code Translator": {}
467
- }
468
-
469
-
470
- # Convenience functions for backward compatibility
471
- def morse_translator(text, mode, morse_dict=None, reversed_morse_dict=None):
472
- """Translate text to/from Morse code."""
473
- return TranslatorToolsProcessor.morse_translator(text, mode)
474
-
475
-
476
- def binary_translator(text):
477
- """Translate text to/from binary code."""
478
- return TranslatorToolsProcessor.binary_translator(text)
479
-
480
-
481
- # BaseTool-compatible wrapper
482
- try:
483
- from tools.base_tool import ToolWithOptions
484
- from typing import Dict, Any
485
-
486
- class TranslatorToolsV2(ToolWithOptions):
487
- """
488
- BaseTool-compatible version of TranslatorTools.
489
- """
490
-
491
- TOOL_NAME = "Translator Tools"
492
- TOOL_DESCRIPTION = "Translate text to/from Morse code and binary"
493
- TOOL_VERSION = "2.0.0"
494
-
495
- OPTIONS = [
496
- ("Text to Morse", "to_morse"),
497
- ("Morse to Text", "from_morse"),
498
- ("Text to Binary", "to_binary"),
499
- ("Binary to Text", "from_binary"),
500
- ]
501
- OPTIONS_LABEL = "Translation"
502
- USE_DROPDOWN = True
503
- DEFAULT_OPTION = "to_morse"
504
-
505
- def __init__(self):
506
- super().__init__()
507
- self._processor = TranslatorToolsProcessor()
508
-
509
- def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
510
- """Process text using the specified translation mode."""
511
- mode = settings.get("mode", "to_morse")
512
-
513
- if mode == "to_morse":
514
- return TranslatorToolsProcessor.morse_translator(input_text, "morse")
515
- elif mode == "from_morse":
516
- return TranslatorToolsProcessor.morse_translator(input_text, "text")
517
- elif mode in ("to_binary", "from_binary"):
518
- return TranslatorToolsProcessor.binary_translator(input_text)
519
- else:
520
- return input_text
521
-
522
- except ImportError:
1
+ """
2
+ Translator Tools Module - Binary and Morse code translation utilities
3
+
4
+ This module provides comprehensive translation functionality with a tabbed UI interface
5
+ for the Promera AI Commander application.
6
+ """
7
+
8
+ import tkinter as tk
9
+ from tkinter import ttk, messagebox
10
+ import base64
11
+ import threading
12
+ import time
13
+
14
+ # Try to import NumPy for Morse code audio generation
15
+ try:
16
+ import numpy as np
17
+ NUMPY_AVAILABLE = True
18
+ except ImportError:
19
+ NUMPY_AVAILABLE = False
20
+
21
+ # Try to import PyAudio for Morse code audio
22
+ try:
23
+ import pyaudio
24
+ PYAUDIO_AVAILABLE = True
25
+ except ImportError:
26
+ PYAUDIO_AVAILABLE = False
27
+
28
+
29
+ class TranslatorToolsProcessor:
30
+ """Translator tools processor with binary and Morse code translation capabilities."""
31
+
32
+ # Morse code dictionary
33
+ MORSE_CODE_DICT = {
34
+ 'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....',
35
+ 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.',
36
+ 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
37
+ 'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....',
38
+ '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----', ' ': '/',
39
+ ',': '--..--', '.': '.-.-.-', '?': '..--..', '/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-'
40
+ }
41
+
42
+ REVERSED_MORSE_DICT = {v: k for k, v in MORSE_CODE_DICT.items()}
43
+
44
+ # Audio constants
45
+ MORSE_DOT_DURATION = 0.080
46
+ MORSE_DASH_DURATION = 0.080 * 3
47
+ SAMPLE_RATE = 44100
48
+ TONE_FREQUENCY = 700
49
+
50
+ @staticmethod
51
+ def morse_translator(text, mode):
52
+ """Translates text to or from Morse code."""
53
+ if mode == "morse":
54
+ return ' '.join(TranslatorToolsProcessor.MORSE_CODE_DICT.get(char.upper(), '') for char in text)
55
+ else: # mode == "text"
56
+ return ''.join(TranslatorToolsProcessor.REVERSED_MORSE_DICT.get(code, '') for code in text.split(' '))
57
+
58
+ @staticmethod
59
+ def binary_translator(text):
60
+ """Translates text to or from binary."""
61
+ # Detect if input is binary or text
62
+ if all(c in ' 01' for c in text): # Binary to Text
63
+ try:
64
+ return ''.join(chr(int(b, 2)) for b in text.split())
65
+ except (ValueError, TypeError):
66
+ return "Error: Invalid binary sequence."
67
+ else: # Text to Binary
68
+ return ' '.join(format(ord(char), '08b') for char in text)
69
+
70
+ @staticmethod
71
+ def process_text(input_text, tool_type, settings):
72
+ """Process text using the specified translator tool and settings."""
73
+ if tool_type == "Morse Code Translator":
74
+ return TranslatorToolsProcessor.morse_translator(
75
+ input_text,
76
+ settings.get("mode", "morse")
77
+ )
78
+ elif tool_type == "Binary Code Translator":
79
+ return TranslatorToolsProcessor.binary_translator(input_text)
80
+ else:
81
+ return f"Unknown translator tool: {tool_type}"
82
+
83
+
84
+ class TranslatorToolsWidget(ttk.Frame):
85
+ """Tabbed interface widget for translator tools, similar to Sorter Tools."""
86
+
87
+ def __init__(self, parent, app, dialog_manager=None):
88
+ super().__init__(parent)
89
+ self.app = app
90
+ self.dialog_manager = dialog_manager
91
+ self.processor = TranslatorToolsProcessor()
92
+
93
+ # Initialize UI variables for Morse Code Translator
94
+ self.morse_mode = tk.StringVar(value="morse")
95
+
96
+ # Audio-related variables
97
+ self.morse_thread = None
98
+ self.stop_morse_playback = threading.Event()
99
+ self.audio_stream = None
100
+ self.pyaudio_instance = None
101
+
102
+ # Initialize PyAudio if available
103
+ self.setup_audio()
104
+
105
+ self.create_widgets()
106
+ self.load_settings()
107
+
108
+ def _show_error(self, title, message):
109
+ """Show error dialog using DialogManager if available, otherwise use messagebox."""
110
+ if self.dialog_manager:
111
+ return self.dialog_manager.show_error(title, message, parent=self.winfo_toplevel())
112
+ else:
113
+ from tkinter import messagebox
114
+ messagebox.showerror(title, message, parent=self.winfo_toplevel())
115
+ return True
116
+
117
+ def _show_audio_setup_instructions(self, missing_library):
118
+ """Show detailed instructions for setting up audio features."""
119
+ title = "Audio Feature Setup Required"
120
+
121
+ if missing_library == "NumPy":
122
+ message = """Morse Code Audio is not available in this optimized build.
123
+
124
+ To enable audio features, you have two options:
125
+
126
+ OPTION 1: Run from Source (Recommended)
127
+ 1. Download Python 3.8+ from python.org
128
+ 2. Install required libraries:
129
+ pip install numpy pyaudio
130
+ 3. Download source code from GitHub
131
+ 4. Run: python pomera.py
132
+
133
+ OPTION 2: Use Text-Only Mode
134
+ • Morse code text translation works perfectly
135
+ • Copy the Morse code and use external audio tools
136
+ • This keeps the executable small (40MB vs 400MB+)
137
+
138
+ The current executable was optimized for size by excluding
139
+ audio libraries. Text-based Morse code works fully!"""
140
+
141
+ else: # PyAudio
142
+ message = """Audio playback is not available.
143
+
144
+ To enable Morse code audio:
145
+
146
+ OPTION 1: Install Audio Libraries
147
+ 1. Install Python 3.8+ from python.org
148
+ 2. Install audio libraries:
149
+ pip install pyaudio numpy
150
+ 3. Run from source: python pomera.py
151
+
152
+ OPTION 2: Use Text-Only Mode
153
+ • All Morse code translation features work
154
+ • Copy output to external audio tools if needed
155
+
156
+ Note: Audio libraries were excluded to keep the
157
+ executable small and portable."""
158
+
159
+ if self.dialog_manager:
160
+ return self.dialog_manager.show_info(title, message, parent=self.winfo_toplevel())
161
+ else:
162
+ from tkinter import messagebox
163
+ messagebox.showinfo(title, message, parent=self.winfo_toplevel())
164
+ return True
165
+
166
+ def setup_audio(self):
167
+ """Initialize PyAudio for Morse code audio playback."""
168
+ if not PYAUDIO_AVAILABLE:
169
+ return
170
+
171
+ try:
172
+ self.pyaudio_instance = pyaudio.PyAudio()
173
+ self.audio_stream = self.pyaudio_instance.open(
174
+ format=pyaudio.paFloat32,
175
+ channels=1,
176
+ rate=TranslatorToolsProcessor.SAMPLE_RATE,
177
+ output=True
178
+ )
179
+ except Exception as e:
180
+ print(f"Failed to initialize PyAudio: {e}")
181
+ self.pyaudio_instance = None
182
+ self.audio_stream = None
183
+
184
+ def create_widgets(self):
185
+ """Creates the tabbed interface for translator tools."""
186
+ # Create notebook for tabs
187
+ self.notebook = ttk.Notebook(self)
188
+ self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
189
+
190
+ # Create Morse Code Translator tab
191
+ self.morse_frame = ttk.Frame(self.notebook)
192
+ self.notebook.add(self.morse_frame, text="Morse Code Translator")
193
+ self.create_morse_translator_widgets()
194
+
195
+ # Create Binary Code Translator tab
196
+ self.binary_frame = ttk.Frame(self.notebook)
197
+ self.notebook.add(self.binary_frame, text="Binary Code Translator")
198
+ self.create_binary_translator_widgets()
199
+
200
+ def create_morse_translator_widgets(self):
201
+ """Creates widgets for the Morse Code Translator tab."""
202
+ # Mode selection
203
+ mode_frame = ttk.LabelFrame(self.morse_frame, text="Translation Mode", padding=10)
204
+ mode_frame.pack(fill=tk.X, padx=5, pady=5)
205
+
206
+ ttk.Radiobutton(
207
+ mode_frame,
208
+ text="Text to Morse",
209
+ variable=self.morse_mode,
210
+ value="morse",
211
+ command=self._on_setting_change
212
+ ).pack(side=tk.LEFT, padx=5)
213
+
214
+ ttk.Radiobutton(
215
+ mode_frame,
216
+ text="Morse to Text",
217
+ variable=self.morse_mode,
218
+ value="text",
219
+ command=self._on_setting_change
220
+ ).pack(side=tk.LEFT, padx=5)
221
+
222
+ # Buttons
223
+ button_frame = ttk.Frame(self.morse_frame)
224
+ button_frame.pack(fill=tk.X, padx=5, pady=5)
225
+
226
+ ttk.Button(
227
+ button_frame,
228
+ text="Translate",
229
+ command=self._apply_morse_translator
230
+ ).pack(side=tk.LEFT, padx=5)
231
+
232
+ # Audio button (only if PyAudio is available)
233
+ if PYAUDIO_AVAILABLE and self.audio_stream:
234
+ self.play_morse_button = ttk.Button(
235
+ button_frame,
236
+ text="Play Morse Audio",
237
+ command=self._play_morse_audio
238
+ )
239
+ self.play_morse_button.pack(side=tk.LEFT, padx=5)
240
+
241
+ def create_binary_translator_widgets(self):
242
+ """Creates widgets for the Binary Code Translator tab."""
243
+ # Info label
244
+ info_frame = ttk.LabelFrame(self.binary_frame, text="Information", padding=10)
245
+ info_frame.pack(fill=tk.X, padx=5, pady=5)
246
+
247
+ info_label = ttk.Label(
248
+ info_frame,
249
+ text="Automatically detects input type:\n• Text → Binary (8-bit per character)\n• Binary → Text (space-separated binary)"
250
+ )
251
+ info_label.pack()
252
+
253
+ # Button
254
+ button_frame = ttk.Frame(self.binary_frame)
255
+ button_frame.pack(fill=tk.X, padx=5, pady=5)
256
+
257
+ ttk.Button(
258
+ button_frame,
259
+ text="Translate",
260
+ command=self._apply_binary_translator
261
+ ).pack(side=tk.LEFT, padx=5)
262
+
263
+ def _on_setting_change(self):
264
+ """Handle setting changes."""
265
+ self.save_settings()
266
+ if hasattr(self.app, 'on_tool_setting_change'):
267
+ self.app.on_tool_setting_change()
268
+
269
+ def _apply_morse_translator(self):
270
+ """Apply the Morse Code Translator tool."""
271
+ self._apply_tool("Morse Code Translator")
272
+
273
+ def _apply_binary_translator(self):
274
+ """Apply the Binary Code Translator tool."""
275
+ self._apply_tool("Binary Code Translator")
276
+
277
+ def _apply_tool(self, tool_type):
278
+ """Apply the specified translator tool."""
279
+ try:
280
+ # Get input text from the active input tab
281
+ active_input_tab = self.app.input_tabs[self.app.input_notebook.index(self.app.input_notebook.select())]
282
+ input_text = active_input_tab.text.get("1.0", tk.END).strip()
283
+
284
+ if not input_text:
285
+ # Show a message if no input text
286
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
287
+ active_output_tab.text.config(state="normal")
288
+ active_output_tab.text.delete("1.0", tk.END)
289
+ active_output_tab.text.insert("1.0", f"Please enter text to translate in the input area.\n\nFor {tool_type}:\n" +
290
+ ("- Enter text to convert to Morse code, or Morse code to convert to text" if tool_type == "Morse Code Translator"
291
+ else "- Enter text to convert to binary, or binary code to convert to text"))
292
+ active_output_tab.text.config(state="disabled")
293
+ return
294
+
295
+ # Get settings for the tool
296
+ settings = self.get_tool_settings(tool_type)
297
+
298
+ # Process the text
299
+ result = self.processor.process_text(input_text, tool_type, settings)
300
+
301
+ # Update output
302
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
303
+ active_output_tab.text.config(state="normal")
304
+ active_output_tab.text.delete("1.0", tk.END)
305
+ active_output_tab.text.insert("1.0", result)
306
+ active_output_tab.text.config(state="disabled")
307
+
308
+ # Update statistics
309
+ if hasattr(self.app, 'update_all_stats'):
310
+ self.app.after(10, self.app.update_all_stats)
311
+
312
+ except Exception as e:
313
+ # Show error in output if something goes wrong
314
+ try:
315
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
316
+ active_output_tab.text.config(state="normal")
317
+ active_output_tab.text.delete("1.0", tk.END)
318
+ active_output_tab.text.insert("1.0", f"Error: {str(e)}")
319
+ active_output_tab.text.config(state="disabled")
320
+ except:
321
+ print(f"Translator Tools Error: {str(e)}") # Fallback to console
322
+
323
+ def _play_morse_audio(self):
324
+ """Play Morse code audio from the output area."""
325
+ if self.morse_thread and self.morse_thread.is_alive():
326
+ print("Morse playback is already in progress.")
327
+ return
328
+
329
+ if not PYAUDIO_AVAILABLE or not self.audio_stream:
330
+ self._show_audio_setup_instructions("PyAudio")
331
+ return
332
+
333
+ if not NUMPY_AVAILABLE:
334
+ self._show_audio_setup_instructions("NumPy")
335
+ return
336
+
337
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
338
+ morse_code = active_output_tab.text.get("1.0", tk.END).strip()
339
+ if not morse_code:
340
+ print("No Morse code to play.")
341
+ return
342
+
343
+ self.stop_morse_playback.clear()
344
+ self.morse_thread = threading.Thread(target=self._play_morse_thread, args=(morse_code,), daemon=True)
345
+ self.morse_thread.start()
346
+
347
+ def _stop_morse_audio(self):
348
+ """Stop the currently playing Morse code audio."""
349
+ self.stop_morse_playback.set()
350
+
351
+ def _play_morse_thread(self, morse_code):
352
+ """The actual playback logic that runs in a thread."""
353
+ if hasattr(self, 'play_morse_button'):
354
+ self.play_morse_button.config(text="Stop Playing", command=self._stop_morse_audio)
355
+ print("Starting Morse code playback.")
356
+
357
+ try:
358
+ for char in morse_code:
359
+ if self.stop_morse_playback.is_set():
360
+ print("Morse playback stopped by user.")
361
+ break
362
+ if char == '.':
363
+ tone = self._generate_morse_tone(TranslatorToolsProcessor.MORSE_DOT_DURATION)
364
+ self.audio_stream.write(tone.tobytes())
365
+ time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION)
366
+ elif char == '-':
367
+ tone = self._generate_morse_tone(TranslatorToolsProcessor.MORSE_DASH_DURATION)
368
+ self.audio_stream.write(tone.tobytes())
369
+ time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION)
370
+ elif char == ' ':
371
+ time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION * 3 - TranslatorToolsProcessor.MORSE_DOT_DURATION)
372
+ elif char == '/':
373
+ time.sleep(TranslatorToolsProcessor.MORSE_DOT_DURATION * 7 - TranslatorToolsProcessor.MORSE_DOT_DURATION)
374
+ except Exception as e:
375
+ print(f"Error during morse playback: {e}")
376
+ finally:
377
+ if hasattr(self, 'play_morse_button'):
378
+ self.play_morse_button.config(text="Play Morse Audio", command=self._play_morse_audio)
379
+ print("Morse code playback finished.")
380
+ self.stop_morse_playback.clear()
381
+
382
+ def _generate_morse_tone(self, duration):
383
+ """Generate a sine wave for a given duration for Morse code."""
384
+ if not NUMPY_AVAILABLE:
385
+ # Return silence if numpy is not available
386
+ import array
387
+ sample_count = int(TranslatorToolsProcessor.SAMPLE_RATE * duration)
388
+ return array.array('f', [0.0] * sample_count)
389
+
390
+ tone_freq = TranslatorToolsProcessor.TONE_FREQUENCY
391
+ t = np.linspace(0, duration, int(TranslatorToolsProcessor.SAMPLE_RATE * duration), False)
392
+ tone = np.sin(tone_freq * t * 2 * np.pi)
393
+ return (0.5 * tone).astype(np.float32)
394
+
395
+ def get_tool_settings(self, tool_type):
396
+ """Get settings for the specified tool."""
397
+ if tool_type == "Morse Code Translator":
398
+ return {
399
+ "mode": self.morse_mode.get()
400
+ }
401
+ elif tool_type == "Binary Code Translator":
402
+ return {} # Binary translator doesn't have settings
403
+ return {}
404
+
405
+ def get_all_settings(self):
406
+ """Get all settings for both translator tools."""
407
+ return {
408
+ "Morse Code Translator": self.get_tool_settings("Morse Code Translator"),
409
+ "Binary Code Translator": self.get_tool_settings("Binary Code Translator")
410
+ }
411
+
412
+ def load_settings(self):
413
+ """Load settings from the main application."""
414
+ if not hasattr(self.app, 'settings'):
415
+ return
416
+
417
+ # Load Morse Code Translator settings
418
+ morse_settings = self.app.settings.get("tool_settings", {}).get("Morse Code Translator", {})
419
+ self.morse_mode.set(morse_settings.get("mode", "morse"))
420
+
421
+ def save_settings(self):
422
+ """Save settings to the main application."""
423
+ if not hasattr(self.app, 'settings'):
424
+ return
425
+
426
+ # Save Morse Code Translator settings
427
+ if "Morse Code Translator" not in self.app.settings["tool_settings"]:
428
+ self.app.settings["tool_settings"]["Morse Code Translator"] = {}
429
+ self.app.settings["tool_settings"]["Morse Code Translator"]["mode"] = self.morse_mode.get()
430
+
431
+ def __del__(self):
432
+ """Cleanup audio resources when widget is destroyed."""
433
+ if self.audio_stream:
434
+ try:
435
+ self.audio_stream.stop_stream()
436
+ self.audio_stream.close()
437
+ except:
438
+ pass
439
+ if self.pyaudio_instance:
440
+ try:
441
+ self.pyaudio_instance.terminate()
442
+ except:
443
+ pass
444
+
445
+
446
+ class TranslatorTools:
447
+ """Main Translator Tools class that provides the interface for the main application."""
448
+
449
+ def __init__(self):
450
+ self.processor = TranslatorToolsProcessor()
451
+ self.widget = None
452
+
453
+ def create_widget(self, parent, app, dialog_manager=None):
454
+ """Create and return the tabbed widget component."""
455
+ self.widget = TranslatorToolsWidget(parent, app, dialog_manager)
456
+ return self.widget
457
+
458
+ def process_text(self, input_text, tool_type, settings):
459
+ """Process text using the specified translator tool and settings."""
460
+ return self.processor.process_text(input_text, tool_type, settings)
461
+
462
+ def get_default_settings(self):
463
+ """Get default settings for both translator tools."""
464
+ return {
465
+ "Morse Code Translator": {"mode": "morse", "tone": TranslatorToolsProcessor.TONE_FREQUENCY},
466
+ "Binary Code Translator": {}
467
+ }
468
+
469
+
470
+ # Convenience functions for backward compatibility
471
+ def morse_translator(text, mode, morse_dict=None, reversed_morse_dict=None):
472
+ """Translate text to/from Morse code."""
473
+ return TranslatorToolsProcessor.morse_translator(text, mode)
474
+
475
+
476
+ def binary_translator(text):
477
+ """Translate text to/from binary code."""
478
+ return TranslatorToolsProcessor.binary_translator(text)
479
+
480
+
481
+ # BaseTool-compatible wrapper
482
+ try:
483
+ from tools.base_tool import ToolWithOptions
484
+ from typing import Dict, Any
485
+
486
+ class TranslatorToolsV2(ToolWithOptions):
487
+ """
488
+ BaseTool-compatible version of TranslatorTools.
489
+ """
490
+
491
+ TOOL_NAME = "Translator Tools"
492
+ TOOL_DESCRIPTION = "Translate text to/from Morse code and binary"
493
+ TOOL_VERSION = "2.0.0"
494
+
495
+ OPTIONS = [
496
+ ("Text to Morse", "to_morse"),
497
+ ("Morse to Text", "from_morse"),
498
+ ("Text to Binary", "to_binary"),
499
+ ("Binary to Text", "from_binary"),
500
+ ]
501
+ OPTIONS_LABEL = "Translation"
502
+ USE_DROPDOWN = True
503
+ DEFAULT_OPTION = "to_morse"
504
+
505
+ def __init__(self):
506
+ super().__init__()
507
+ self._processor = TranslatorToolsProcessor()
508
+
509
+ def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
510
+ """Process text using the specified translation mode."""
511
+ mode = settings.get("mode", "to_morse")
512
+
513
+ if mode == "to_morse":
514
+ return TranslatorToolsProcessor.morse_translator(input_text, "morse")
515
+ elif mode == "from_morse":
516
+ return TranslatorToolsProcessor.morse_translator(input_text, "text")
517
+ elif mode in ("to_binary", "from_binary"):
518
+ return TranslatorToolsProcessor.binary_translator(input_text)
519
+ else:
520
+ return input_text
521
+
522
+ except ImportError:
523
523
  pass