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.
- package/LICENSE +21 -21
- package/README.md +105 -680
- package/bin/pomera-ai-commander.js +62 -62
- package/core/__init__.py +65 -65
- package/core/app_context.py +482 -482
- package/core/async_text_processor.py +421 -421
- package/core/backup_manager.py +655 -655
- package/core/backup_recovery_manager.py +1033 -1033
- package/core/content_hash_cache.py +508 -508
- package/core/context_menu.py +313 -313
- package/core/data_validator.py +1066 -1066
- package/core/database_connection_manager.py +744 -744
- package/core/database_curl_settings_manager.py +608 -608
- package/core/database_promera_ai_settings_manager.py +446 -446
- package/core/database_schema.py +411 -411
- package/core/database_schema_manager.py +395 -395
- package/core/database_settings_manager.py +1507 -1507
- package/core/database_settings_manager_interface.py +456 -456
- package/core/dialog_manager.py +734 -734
- package/core/efficient_line_numbers.py +510 -510
- package/core/error_handler.py +746 -746
- package/core/error_service.py +431 -431
- package/core/event_consolidator.py +511 -511
- package/core/mcp/__init__.py +43 -43
- package/core/mcp/protocol.py +288 -288
- package/core/mcp/schema.py +251 -251
- package/core/mcp/server_stdio.py +299 -299
- package/core/mcp/tool_registry.py +2372 -2345
- package/core/memory_efficient_text_widget.py +711 -711
- package/core/migration_manager.py +914 -914
- package/core/migration_test_suite.py +1085 -1085
- package/core/migration_validator.py +1143 -1143
- package/core/optimized_find_replace.py +714 -714
- package/core/optimized_pattern_engine.py +424 -424
- package/core/optimized_search_highlighter.py +552 -552
- package/core/performance_monitor.py +674 -674
- package/core/persistence_manager.py +712 -712
- package/core/progressive_stats_calculator.py +632 -632
- package/core/regex_pattern_cache.py +529 -529
- package/core/regex_pattern_library.py +350 -350
- package/core/search_operation_manager.py +434 -434
- package/core/settings_defaults_registry.py +1087 -1087
- package/core/settings_integrity_validator.py +1111 -1111
- package/core/settings_serializer.py +557 -557
- package/core/settings_validator.py +1823 -1823
- package/core/smart_stats_calculator.py +709 -709
- package/core/statistics_update_manager.py +619 -619
- package/core/stats_config_manager.py +858 -858
- package/core/streaming_text_handler.py +723 -723
- package/core/task_scheduler.py +596 -596
- package/core/update_pattern_library.py +168 -168
- package/core/visibility_monitor.py +596 -596
- package/core/widget_cache.py +498 -498
- package/mcp.json +51 -61
- package/package.json +61 -57
- package/pomera.py +7482 -7482
- package/pomera_mcp_server.py +183 -144
- package/requirements.txt +32 -0
- package/tools/__init__.py +4 -4
- package/tools/ai_tools.py +2891 -2891
- package/tools/ascii_art_generator.py +352 -352
- package/tools/base64_tools.py +183 -183
- package/tools/base_tool.py +511 -511
- package/tools/case_tool.py +308 -308
- package/tools/column_tools.py +395 -395
- package/tools/cron_tool.py +884 -884
- package/tools/curl_history.py +600 -600
- package/tools/curl_processor.py +1207 -1207
- package/tools/curl_settings.py +502 -502
- package/tools/curl_tool.py +5467 -5467
- package/tools/diff_viewer.py +1071 -1071
- package/tools/email_extraction_tool.py +248 -248
- package/tools/email_header_analyzer.py +425 -425
- package/tools/extraction_tools.py +250 -250
- package/tools/find_replace.py +1750 -1750
- package/tools/folder_file_reporter.py +1463 -1463
- package/tools/folder_file_reporter_adapter.py +480 -480
- package/tools/generator_tools.py +1216 -1216
- package/tools/hash_generator.py +255 -255
- package/tools/html_tool.py +656 -656
- package/tools/jsonxml_tool.py +729 -729
- package/tools/line_tools.py +419 -419
- package/tools/markdown_tools.py +561 -561
- package/tools/mcp_widget.py +1417 -1417
- package/tools/notes_widget.py +973 -973
- package/tools/number_base_converter.py +372 -372
- package/tools/regex_extractor.py +571 -571
- package/tools/slug_generator.py +310 -310
- package/tools/sorter_tools.py +458 -458
- package/tools/string_escape_tool.py +392 -392
- package/tools/text_statistics_tool.py +365 -365
- package/tools/text_wrapper.py +430 -430
- package/tools/timestamp_converter.py +421 -421
- package/tools/tool_loader.py +710 -710
- package/tools/translator_tools.py +522 -522
- package/tools/url_link_extractor.py +261 -261
- package/tools/url_parser.py +204 -204
- package/tools/whitespace_tools.py +355 -355
- package/tools/word_frequency_counter.py +146 -146
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/app_context.cpython-313.pyc +0 -0
- package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
- package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
- package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
- package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/error_service.cpython-313.pyc +0 -0
- package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
- package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
- package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
- package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
- package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
- package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
- package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
- package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
- package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
- package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
- package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
- package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
- 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
|