pomera-ai-commander 0.1.0 → 1.2.1

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 (191) 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 +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. 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