1
1
import threading
2
2
from PySide6 .QtWidgets import (
3
- QWidget , QGridLayout , QTextEdit , QPushButton ,
4
- QComboBox , QFileDialog , QMessageBox , QHBoxLayout
3
+ QMainWindow ,
4
+ QWidget ,
5
+ QGridLayout ,
6
+ QTextEdit ,
7
+ QComboBox ,
8
+ QFileDialog ,
9
+ QMessageBox ,
10
+ QHBoxLayout ,
5
11
)
12
+ from PySide6 .QtGui import QAction
6
13
from PySide6 .QtCore import QTimer , Qt
7
14
from charset_normalizer import from_bytes
8
15
from translation_worker import TranslationWorker
9
16
10
- TRANSLATION_PROVIDERS = {
11
- "Google" , "KoboldCPP"
12
- }
17
+ TRANSLATION_PROVIDERS = {"Google" , "KoboldCPP" }
13
18
14
19
15
- class TranslatorApp (QWidget ):
16
-
20
+ class TranslatorApp (QMainWindow ):
17
21
def __init__ (self , llm_url ):
18
22
super ().__init__ ()
19
23
self .llm_url = llm_url
20
24
self .thread = None
21
25
self .current_file_path = None
22
26
self .language_combo = None
23
- self .open_button = None
24
- self .save_button = None
25
27
self .translation_output = None
26
28
self .text_edit = None
27
29
self .worker = None
@@ -33,72 +35,113 @@ def __init__(self, llm_url):
33
35
self .selection_timer .setSingleShot (True )
34
36
self .selection_timer .timeout .connect (self .process_selection )
35
37
self .translator_combo = None
36
- self .init_ui ()
37
38
39
+ # Create central widget and main layout
40
+ central_widget = QWidget ()
41
+ self .setCentralWidget (central_widget )
42
+ self .main_layout = QGridLayout (central_widget )
43
+ self .main_layout .setContentsMargins (10 , 10 , 10 , 10 )
38
44
45
+ self .init_ui ()
46
+ self .create_menus ()
47
+
48
+ def create_menus (self ):
49
+ menu_bar = self .menuBar ()
50
+
51
+ # File menu
52
+ file_menu = menu_bar .addMenu ("&File" )
53
+ open_action = QAction ("&Open" , self )
54
+ open_action .triggered .connect (self .open_file )
55
+ open_action .setShortcut ("Ctrl+O" )
56
+ file_menu .addAction (open_action )
57
+
58
+ save_action = QAction ("&Save" , self )
59
+ save_action .triggered .connect (self .save_file )
60
+ save_action .setShortcut ("Ctrl+S" )
61
+ file_menu .addAction (save_action )
62
+
63
+ file_menu .addSeparator ()
64
+ exit_action = QAction ("&Exit" , self )
65
+ exit_action .triggered .connect (self .close )
66
+ file_menu .addAction (exit_action )
67
+
68
+ # Edit menu
69
+ edit_menu = menu_bar .addMenu ("&Edit" )
70
+ undo_action = QAction ("&Undo" , self )
71
+ undo_action .triggered .connect (self .text_edit .undo )
72
+ undo_action .setShortcut ("Ctrl+Z" )
73
+ edit_menu .addAction (undo_action )
74
+
75
+ redo_action = QAction ("&Redo" , self )
76
+ redo_action .triggered .connect (self .text_edit .redo )
77
+ redo_action .setShortcut ("Ctrl+Y" )
78
+ edit_menu .addAction (redo_action )
79
+
80
+ # Help menu
81
+ help_menu = menu_bar .addMenu ("&Help" )
82
+ about_action = QAction ("&About" , self )
83
+ about_action .triggered .connect (self .show_about )
84
+ help_menu .addAction (about_action )
85
+
86
+ def show_about (self ):
87
+ about_box = QMessageBox (self )
88
+ about_box .setWindowTitle ("About TextAutoTranslate" )
89
+ about_box .setTextFormat (Qt .TextFormat .RichText )
90
+ about_box .setText (
91
+ "A translation tool with multiple providers<br><br>"
92
+ "Version 0.1<br>"
93
+ "Copyright © 2025 Ati1707<br><br>"
94
+ "<a href='https://github.com/Ati1707/TextAutoTranslate'>GitHub Repository</a>"
95
+ )
96
+ about_box .exec ()
39
97
40
98
def init_ui (self ):
41
- layout = QGridLayout (self )
42
- layout .setContentsMargins (10 , 10 , 10 , 10 )
43
-
44
99
# Text Editor
45
100
self .text_edit = QTextEdit ()
46
101
self .text_edit .setAcceptRichText (False )
47
102
self .text_edit .setFontFamily ("Courier New" )
48
103
self .text_edit .setFontPointSize (12 )
49
- layout .addWidget (self .text_edit , 0 , 0 , 1 , 2 )
104
+ self . main_layout .addWidget (self .text_edit , 0 , 0 , 1 , 2 )
50
105
51
106
# Translation Output
52
107
self .translation_output = QTextEdit ()
53
108
self .translation_output .setFontFamily ("Courier New" )
54
109
self .translation_output .setFontPointSize (12 )
55
110
self .translation_output .setReadOnly (True )
56
- layout .addWidget (self .translation_output , 1 , 0 , 1 , 2 )
57
-
58
- # Buttons Layout
59
- buttons_layout = QHBoxLayout ()
60
-
61
- # Open File Button
62
- self .open_button = QPushButton ("Open File" )
63
- self .open_button .clicked .connect (self .open_file )
64
- buttons_layout .addWidget (self .open_button )
65
-
66
- # Save File Button
67
- self .save_button = QPushButton ("Save File" )
68
- self .save_button .clicked .connect (self .save_file )
69
- buttons_layout .addWidget (self .save_button )
111
+ self .main_layout .addWidget (self .translation_output , 1 , 0 , 1 , 2 )
70
112
71
113
# Translator Selection Combo Box
72
114
self .translator_combo = QComboBox ()
73
115
self .translator_combo .addItems (list (TRANSLATION_PROVIDERS ))
74
116
self .translator_combo .setCurrentIndex (0 )
75
117
76
- # Combine buttons and translator combo in a horizontal layout
118
+ # Header layout (left side)
77
119
left_header_layout = QHBoxLayout ()
78
- left_header_layout .addLayout (buttons_layout )
79
120
left_header_layout .addWidget (self .translator_combo )
80
- left_header_layout .addStretch () # Push elements to the left
121
+ left_header_layout .addStretch ()
81
122
82
123
# Add combined layout to grid
83
- layout .addLayout (left_header_layout , 2 , 0 )
124
+ self . main_layout .addLayout (left_header_layout , 2 , 0 )
84
125
85
126
# Language Combo Box
86
127
self .language_combo = QComboBox ()
87
128
self .language_combo .addItems (self .languages )
88
129
self .language_combo .setCurrentIndex (- 1 )
89
130
self .language_combo .setPlaceholderText ("Select Target Language" )
90
- layout .addWidget (self .language_combo , 2 , 1 , alignment = Qt .AlignmentFlag .AlignRight )
131
+ self .main_layout .addWidget (
132
+ self .language_combo , 2 , 1 , alignment = Qt .AlignmentFlag .AlignRight
133
+ )
91
134
92
- # Update language combo label when translator changes
93
- self .translator_combo .currentTextChanged .connect (self .update_language_combo_label )
135
+ # Connect signals
136
+ self .translator_combo .currentTextChanged .connect (
137
+ self .update_language_combo_label
138
+ )
139
+ self .text_edit .selectionChanged .connect (self .handle_selection )
94
140
95
141
# Set row stretch factors
96
- layout .setRowStretch (0 , 7 )
97
- layout .setRowStretch (1 , 2 )
98
- layout .setRowStretch (2 , 1 )
99
-
100
- # Connect selection change handler
101
- self .text_edit .selectionChanged .connect (self .handle_selection )
142
+ self .main_layout .setRowStretch (0 , 7 )
143
+ self .main_layout .setRowStretch (1 , 2 )
144
+ self .main_layout .setRowStretch (2 , 1 )
102
145
103
146
def update_window_title (self ):
104
147
"""Update window title with current file path if available"""
@@ -115,10 +158,7 @@ def update_language_combo_label(self, translator):
115
158
116
159
def open_file (self ):
117
160
file_path , _ = QFileDialog .getOpenFileName (
118
- self ,
119
- "Open File" ,
120
- "" ,
121
- "All Files (*)"
161
+ self , "Open File" , "" , "All Files (*)"
122
162
)
123
163
124
164
if file_path :
@@ -127,14 +167,14 @@ def open_file(self):
127
167
with open (file_path , "rb" ) as f :
128
168
raw_data = f .read ()
129
169
result = from_bytes (raw_data ).best ()
130
- encoding = result .encoding if result else ' utf-8'
170
+ encoding = result .encoding if result else " utf-8"
131
171
132
172
try :
133
173
with open (file_path , "r" , encoding = encoding ) as file :
134
174
content = file .read ()
135
175
except (UnicodeDecodeError , LookupError ):
136
176
try :
137
- with open (file_path , "r" , encoding = ' utf-16' ) as file :
177
+ with open (file_path , "r" , encoding = " utf-16" ) as file :
138
178
content = file .read ()
139
179
except Exception as e :
140
180
content = f"Error: Failed to decode file - { str (e )} "
@@ -147,11 +187,14 @@ def save_file(self):
147
187
# Overwrite existing file
148
188
content = self .text_edit .toPlainText ()
149
189
try :
150
- reply = QMessageBox .question (self , "Confirm File Overwrite" ,
151
- f"You are about to overwrite:{ self .current_file_path } "
152
- f"This will permanently replace the existing file. Are you sure you want to continue?" ,
153
- QMessageBox .StandardButton .Yes ,
154
- QMessageBox .StandardButton .No )
190
+ reply = QMessageBox .question (
191
+ self ,
192
+ "Confirm File Overwrite" ,
193
+ f"You are about to overwrite:{ self .current_file_path } "
194
+ f"This will permanently replace the existing file. Are you sure you want to continue?" ,
195
+ QMessageBox .StandardButton .Yes ,
196
+ QMessageBox .StandardButton .No ,
197
+ )
155
198
if reply == QMessageBox .StandardButton .Yes :
156
199
with open (self .current_file_path , "w" , encoding = "utf-8" ) as file :
157
200
file .write (content )
@@ -160,10 +203,7 @@ def save_file(self):
160
203
else :
161
204
# Save as new file
162
205
file_path , _ = QFileDialog .getSaveFileName (
163
- self ,
164
- "Save File" ,
165
- "" ,
166
- "All Files (*)"
206
+ self , "Save File" , "" , "All Files (*)"
167
207
)
168
208
if file_path :
169
209
content = self .text_edit .toPlainText ()
@@ -173,7 +213,9 @@ def save_file(self):
173
213
self .current_file_path = file_path
174
214
self .update_window_title ()
175
215
except Exception as e :
176
- QMessageBox .critical (self , "Error" , f"Failed to save file: { str (e )} " )
216
+ QMessageBox .critical (
217
+ self , "Error" , f"Failed to save file: { str (e )} "
218
+ )
177
219
178
220
def handle_selection (self ):
179
221
if self .selection_timer .isActive ():
@@ -199,4 +241,4 @@ def start_translation_thread(self, text):
199
241
self .worker = TranslationWorker (text , translator , language , self .llm_url )
200
242
self .thread = threading .Thread (target = self .worker .run )
201
243
self .worker .finished .connect (self .update_translation_output )
202
- self .thread .start ()
244
+ self .thread .start ()
0 commit comments