icon glory

This commit is contained in:
dazedanon 2025-10-25 17:45:56 -05:00
parent d870a395fd
commit ff5aefc82f

View file

@ -20,25 +20,50 @@ from dotenv import load_dotenv
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGroupBox,
QTextEdit, QMessageBox, QListWidget, QListWidgetItem,
QSplitter, QFileDialog, QComboBox, QCheckBox, QProgressBar
QSplitter, QFileDialog, QComboBox, QCheckBox, QProgressBar, QFrame, QFormLayout
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QThread, QMutex, QProcess
from PyQt5.QtGui import QFont
from gui.log_viewer import LogViewer
def create_section_header(title):
"""Create a clean section header without boxes."""
label = QLabel(title)
label.setStyleSheet("""
QLabel {
font-size: 13px;
font-weight: bold;
color: #007acc;
padding: 8px 0px 5px 0px;
background-color: transparent;
}
""")
return label
def create_horizontal_line():
"""Create a horizontal separator line."""
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
line.setStyleSheet("QFrame { color: #555555; margin: 5px 0px; }")
return line
class TranslationWorker(QThread):
"""Worker thread for running translations without blocking the UI."""
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int, int, str) # current_file, total_files, filename
item_progress_signal = pyqtSignal(int, int) # current_item, total_items (for tqdm within file)
finished_signal = pyqtSignal(bool, str)
def __init__(self, project_root, module_info, estimate_only=False):
def __init__(self, project_root, module_info, estimate_only=False, selected_files=None):
super().__init__()
self.project_root = project_root
self.module_info = module_info # [name, extensions, handler_function]
self.estimate_only = estimate_only
self.selected_files = selected_files # List of files to process
self.should_stop = False
self.mutex = QMutex() # For thread safety
self.executor = None # Store reference to executor for proper shutdown
@ -308,14 +333,18 @@ except Exception as e:
self.finished_signal.emit(False, "Files directory missing")
return
# Find files matching the selected module's extensions
matching_files = []
for file_path in files_dir.iterdir():
if file_path.is_file() and file_path.name != '.gitkeep':
for ext in self.module_info[1]:
if file_path.name.endswith(ext):
matching_files.append(file_path.name)
break
# Use selected files or find all matching files
if self.selected_files:
matching_files = self.selected_files
else:
# Find files matching the selected module's extensions
matching_files = []
for file_path in files_dir.iterdir():
if file_path.is_file() and file_path.name != '.gitkeep':
for ext in self.module_info[1]:
if file_path.name.endswith(ext):
matching_files.append(file_path.name)
break
if not matching_files:
self.emit_log(f"❌ No files found matching extensions: {', '.join(self.module_info[1])}")
@ -466,13 +495,13 @@ class TranslationTab(QWidget):
self.files_dir.mkdir(exist_ok=True)
self.translated_dir.mkdir(exist_ok=True)
self.setup_ui()
self.refresh_file_lists()
# Initialize tracking variables
self.files_completed = 0
self.files_total = 0
# Auto-refresh timer for file lists (less frequent during translation)
self.refresh_timer = QTimer()
self.refresh_timer.timeout.connect(self.refresh_file_lists)
self.refresh_timer.start(3000) # Refresh every 3 seconds
self.setup_ui()
self.setup_module_list()
self.refresh_file_lists()
def setup_ui(self):
"""Set up the user interface."""
@ -482,52 +511,220 @@ class TranslationTab(QWidget):
# Left side - translation controls
left_widget = QWidget()
layout = QVBoxLayout()
layout.setSpacing(15)
layout.setSpacing(8)
layout.setContentsMargins(15, 15, 15, 15)
# Title
title_label = QLabel("Translation")
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #007acc; margin-bottom: 5px;")
layout.addWidget(title_label)
# Translation Settings Section
layout.addWidget(create_section_header("🌐 Translation Settings"))
# Description
desc_label = QLabel(
"Manage your files and run translations. Add files to 'files' folder, select module, then click translate."
)
desc_label.setWordWrap(True)
desc_label.setStyleSheet("color: #cccccc; margin-bottom: 15px;")
layout.addWidget(desc_label)
trans_form = QFormLayout()
trans_form.setSpacing(6)
trans_form.setContentsMargins(0, 0, 0, 12)
trans_form.setFieldGrowthPolicy(QFormLayout.FieldsStayAtSizeHint)
trans_form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
# Module selection
module_group = QGroupBox("Translation Settings")
module_group.setStyleSheet("QGroupBox { font-weight: bold; color: #007acc; margin-top: 10px; }")
module_layout = QVBoxLayout()
selector_layout = QHBoxLayout()
selector_layout.addWidget(QLabel("Game Engine:"))
engine_label = QLabel("Game Engine:")
engine_label.setFixedWidth(150)
engine_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.module_combo = QComboBox()
self.module_combo.currentTextChanged.connect(self._on_module_changed)
selector_layout.addWidget(self.module_combo)
self.estimate_checkbox = QCheckBox("Estimate only (don't translate)")
selector_layout.addWidget(self.estimate_checkbox)
module_layout.addLayout(selector_layout)
module_group.setLayout(module_layout)
layout.addWidget(module_group)
self.module_combo.setFixedWidth(300)
trans_form.addRow(engine_label, self.module_combo)
# File management splitter
file_splitter = QSplitter(Qt.Horizontal)
input_widget = self.create_input_files_widget()
file_splitter.addWidget(input_widget)
output_widget = self.create_output_files_widget()
file_splitter.addWidget(output_widget)
file_splitter.setStretchFactor(0, 1)
file_splitter.setStretchFactor(1, 1)
layout.addWidget(file_splitter)
mode_label = QLabel("Mode:")
mode_label.setFixedWidth(150)
mode_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.mode_combo = QComboBox()
self.mode_combo.addItem("Translate")
self.mode_combo.addItem("Estimate")
self.mode_combo.setFixedWidth(200)
self.mode_combo.currentTextChanged.connect(self._on_mode_changed)
trans_form.addRow(mode_label, self.mode_combo)
# Log
log_widget = self.create_log_widget()
layout.addWidget(log_widget)
layout.addLayout(trans_form)
layout.addWidget(create_horizontal_line())
# Modules list
self.setup_module_list()
# Files Section
layout.addWidget(create_section_header("📁 Input Files"))
# Files Section with side buttons
files_container = QHBoxLayout()
files_container.setSpacing(0) # No spacing between list and buttons
# File list with checkboxes
self.file_list = QListWidget()
self.file_list.setMinimumHeight(150)
self.file_list.setMaximumHeight(250)
self.file_list.setSelectionMode(QListWidget.NoSelection) # Disable selection highlighting
self.file_list.itemClicked.connect(self._toggle_file_checkbox)
self.file_list.setFocusPolicy(Qt.NoFocus) # Remove focus outline
self.file_list.setStyleSheet("""
QListWidget {
outline: none;
border: 1px solid #555555;
border-right: none;
}
QListWidget::item {
border: none;
outline: none;
}
QListWidget::item:hover {
background-color: #3e3e42;
}
""")
files_container.addWidget(self.file_list)
# File management buttons (icon-based, vertical on the side)
file_buttons = QVBoxLayout()
file_buttons.setSpacing(0)
file_buttons.setContentsMargins(0, 0, 0, 0)
# Button style for all icon buttons - all same size
icon_button_style = """
QPushButton {
font-size: 13px;
padding: 0px;
min-width: 32px;
max-width: 32px;
min-height: 32px;
max-height: 32px;
border: 1px solid #555555;
border-top: none;
border-radius: 0px;
background-color: #2d2d30;
}
QPushButton:hover {
background-color: #3e3e42;
border-left-color: #007acc;
}
QPushButton:pressed {
background-color: #007acc;
}
"""
# First button style - same size but with top border
first_button_style = """
QPushButton {
font-size: 13px;
padding: 0px;
min-width: 32px;
max-width: 32px;
min-height: 32px;
max-height: 32px;
border: 1px solid #555555;
border-radius: 0px;
background-color: #2d2d30;
}
QPushButton:hover {
background-color: #3e3e42;
border-left-color: #007acc;
}
QPushButton:pressed {
background-color: #007acc;
}
"""
select_all_btn = QPushButton("")
select_all_btn.setToolTip("Select all files")
select_all_btn.clicked.connect(self.select_all_files)
select_all_btn.setStyleSheet(first_button_style)
file_buttons.addWidget(select_all_btn)
deselect_all_btn = QPushButton("")
deselect_all_btn.setToolTip("Deselect all files")
deselect_all_btn.clicked.connect(self.deselect_all_files)
deselect_all_btn.setStyleSheet(icon_button_style)
file_buttons.addWidget(deselect_all_btn)
add_files_btn = QPushButton("")
add_files_btn.setToolTip("Add files to translate")
add_files_btn.clicked.connect(self.add_input_files)
add_files_btn.setStyleSheet(icon_button_style)
file_buttons.addWidget(add_files_btn)
remove_files_btn = QPushButton("🗑️")
remove_files_btn.setToolTip("Remove selected files")
remove_files_btn.clicked.connect(self.remove_selected_files)
remove_files_btn.setStyleSheet(icon_button_style)
file_buttons.addWidget(remove_files_btn)
open_folder_btn = QPushButton("📁")
open_folder_btn.setToolTip("Open files folder in explorer")
open_folder_btn.clicked.connect(self.open_input_folder)
open_folder_btn.setStyleSheet(icon_button_style)
file_buttons.addWidget(open_folder_btn)
refresh_btn = QPushButton("🔄")
refresh_btn.setToolTip("Refresh file list")
refresh_btn.clicked.connect(self.refresh_file_lists)
refresh_btn.setStyleSheet(icon_button_style)
file_buttons.addWidget(refresh_btn)
# Add stretch to push buttons to top
file_buttons.addStretch()
# Add button column to the container
files_container.addLayout(file_buttons)
# Add the entire container to main layout
layout.addLayout(files_container)
layout.addWidget(create_horizontal_line())
# Progress Section
layout.addWidget(create_section_header("📊 Translation Progress"))
progress_layout = QVBoxLayout()
progress_layout.setSpacing(8)
progress_layout.setContentsMargins(0, 0, 0, 12)
# Files Translated counter
files_layout = QHBoxLayout()
files_layout.addWidget(QLabel("Files Translated:"))
self.files_translated_label = QLabel("0/0")
self.files_translated_label.setStyleSheet("font-weight: bold; color: #007acc;")
files_layout.addWidget(self.files_translated_label)
files_layout.addStretch()
progress_layout.addLayout(files_layout)
# Currently translating
translating_layout = QHBoxLayout()
translating_layout.addWidget(QLabel("Translating:"))
self.translating_label = QLabel("")
self.translating_label.setStyleSheet("font-weight: bold; color: #cccccc;")
translating_layout.addWidget(self.translating_label)
translating_layout.addStretch()
progress_layout.addLayout(translating_layout)
# Progress bar with label
item_progress_layout = QHBoxLayout()
item_progress_layout.addWidget(QLabel("Progress:"))
self.item_progress_label = QLabel("0/0")
self.item_progress_label.setStyleSheet("font-weight: bold; color: #cccccc;")
item_progress_layout.addWidget(self.item_progress_label)
item_progress_layout.addStretch()
progress_layout.addLayout(item_progress_layout)
self.item_progress_bar = QProgressBar()
self.item_progress_bar.setStyleSheet("""
QProgressBar {
border: 1px solid #555555;
border-radius: 3px;
text-align: center;
background-color: #2b2b2b;
color: white;
height: 20px;
}
QProgressBar::chunk {
background-color: #007acc;
border-radius: 2px;
}
""")
progress_layout.addWidget(self.item_progress_bar)
layout.addLayout(progress_layout)
# Spacer to push buttons to bottom
layout.addStretch()
# Buttons
button_layout = QHBoxLayout()
@ -550,7 +747,7 @@ class TranslationTab(QWidget):
button_layout.addWidget(self.translate_button)
self.stop_button = QPushButton("Stop Translation")
self.stop_button.clicked.connect(self.stop_translation)
self.stop_button.setEnabled(False)
self.stop_button.setVisible(False) # Hidden by default
self.stop_button.setStyleSheet("""
QPushButton {
background-color: #cc0000;
@ -650,142 +847,82 @@ class TranslationTab(QWidget):
elif "mv/mz" in lowered:
self.engine_changed.emit("mvmz")
def create_input_files_widget(self):
"""Create the input files widget."""
input_group = QGroupBox("Input Files (files folder)")
input_group.setStyleSheet("QGroupBox { font-weight: bold; color: #007acc; margin-top: 10px; }")
input_layout = QVBoxLayout()
# Update mode dropdown based on engine
current_mode = self.mode_combo.currentText()
self.mode_combo.clear()
self.mode_combo.addItem("Translate")
self.mode_combo.addItem("Estimate")
self.input_list = QListWidget()
self.input_list.setMinimumHeight(80)
self.input_list.setMaximumHeight(150)
input_layout.addWidget(self.input_list)
# Add Parse Speakers for RPG Maker MV/MZ
if "mv/mz" in lowered:
self.mode_combo.addItem("Parse Speakers")
input_buttons = QHBoxLayout()
add_input_btn = QPushButton("Add Files")
add_input_btn.clicked.connect(self.add_input_files)
input_buttons.addWidget(add_input_btn)
# Restore previous selection if it still exists
index = self.mode_combo.findText(current_mode)
if index >= 0:
self.mode_combo.setCurrentIndex(index)
else:
self.mode_combo.setCurrentIndex(0)
remove_input_btn = QPushButton("Remove Selected")
remove_input_btn.clicked.connect(self.remove_input_file)
input_buttons.addWidget(remove_input_btn)
def _toggle_file_checkbox(self, item):
"""Toggle checkbox when clicking anywhere on the item."""
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
open_input_btn = QPushButton("Open Folder")
open_input_btn.clicked.connect(self.open_input_folder)
input_buttons.addWidget(open_input_btn)
input_layout.addLayout(input_buttons)
input_group.setLayout(input_layout)
return input_group
def create_output_files_widget(self):
"""Create the output files widget."""
output_group = QGroupBox("Output Files (translated folder)")
output_group.setStyleSheet("QGroupBox { font-weight: bold; color: #007acc; margin-top: 10px; }")
output_layout = QVBoxLayout()
self.output_list = QListWidget()
self.output_list.setMinimumHeight(80)
self.output_list.setMaximumHeight(150)
output_layout.addWidget(self.output_list)
output_buttons = QHBoxLayout()
remove_output_btn = QPushButton("Remove Selected")
remove_output_btn.clicked.connect(self.remove_output_file)
output_buttons.addWidget(remove_output_btn)
clear_output_btn = QPushButton("Clear All")
clear_output_btn.clicked.connect(self.clear_output_files)
output_buttons.addWidget(clear_output_btn)
open_output_btn = QPushButton("Open Folder")
open_output_btn.clicked.connect(self.open_output_folder)
output_buttons.addWidget(open_output_btn)
output_layout.addLayout(output_buttons)
output_group.setLayout(output_layout)
return output_group
def create_log_widget(self):
"""Create the console log widget."""
log_group = QGroupBox("Console Output")
log_group.setStyleSheet("QGroupBox { font-weight: bold; color: #007acc; margin-top: 10px; }")
layout = QVBoxLayout()
# Progress bar for file translation
progress_layout = QHBoxLayout()
self.progress_label = QLabel("Ready")
self.progress_label.setStyleSheet("color: #cccccc; font-weight: bold;")
progress_layout.addWidget(self.progress_label)
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setStyleSheet("""
QProgressBar {
border: 1px solid #555555;
border-radius: 3px;
text-align: center;
background-color: #2b2b2b;
color: white;
}
QProgressBar::chunk {
background-color: #007acc;
border-radius: 2px;
}
""")
progress_layout.addWidget(self.progress_bar)
layout.addLayout(progress_layout)
# Cost display on its own row (compact)
cost_layout = QHBoxLayout()
cost_layout.setContentsMargins(0, 0, 0, 0) # Remove margins
cost_layout.setSpacing(0) # Remove spacing
cost_layout.addStretch() # Push cost to center
self.cost_label = QLabel("")
self.cost_label.setStyleSheet("color: #00ff00; font-weight: bold; font-size: 12px;")
self.cost_label.setVisible(False)
self.cost_label.setAlignment(Qt.AlignCenter)
cost_layout.addWidget(self.cost_label)
cost_layout.addStretch() # Push cost to center
layout.addLayout(cost_layout)
self.log_display = QTextEdit()
self.log_display.setReadOnly(True)
self.log_display.setFont(QFont("Consolas", 9))
self.log_display.setMinimumHeight(150)
self.log_display.setMaximumHeight(300)
self.log_display.setStyleSheet("""
QTextEdit {
background-color: #1e1e1e;
color: #ffffff;
border: 1px solid #555555;
font-family: 'Consolas', 'Courier New', monospace;
}
""")
layout.addWidget(self.log_display)
clear_log_btn = QPushButton("Clear Log")
clear_log_btn.clicked.connect(self.clear_log)
layout.addWidget(clear_log_btn)
log_group.setLayout(layout)
return log_group
def _on_mode_changed(self, mode_text):
"""Update the translate button text based on selected mode."""
if mode_text == "Translate":
self.translate_button.setText("Start Translation")
elif mode_text == "Estimate":
self.translate_button.setText("Start Estimation")
elif mode_text == "Parse Speakers":
self.translate_button.setText("Parse Speakers")
def refresh_file_lists(self):
"""Refresh both input and output file lists."""
# Refresh input files
self.input_list.clear()
"""Refresh the file list with checkboxes, preserving checked states."""
# Save current check states
checked_files = set()
for i in range(self.file_list.count()):
item = self.file_list.item(i)
if item.checkState() == Qt.Checked:
checked_files.add(item.text())
# Rebuild the list
self.file_list.clear()
if self.files_dir.exists():
for file_path in self.files_dir.iterdir():
if file_path.is_file() and file_path.name != '.gitkeep':
self.input_list.addItem(file_path.name)
item = QListWidgetItem(file_path.name)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
# Restore checked state if it was previously checked
if file_path.name in checked_files:
item.setCheckState(Qt.Checked)
else:
item.setCheckState(Qt.Unchecked)
self.file_list.addItem(item)
# Refresh output files
self.output_list.clear()
if self.translated_dir.exists():
for file_path in self.translated_dir.iterdir():
if file_path.is_file() and file_path.name != '.gitkeep':
self.output_list.addItem(file_path.name)
def select_all_files(self):
"""Select all files in the list."""
for i in range(self.file_list.count()):
item = self.file_list.item(i)
item.setCheckState(Qt.Checked)
def deselect_all_files(self):
"""Deselect all files in the list."""
for i in range(self.file_list.count()):
item = self.file_list.item(i)
item.setCheckState(Qt.Unchecked)
def get_selected_files(self):
"""Get list of checked files."""
selected = []
for i in range(self.file_list.count()):
item = self.file_list.item(i)
if item.checkState() == Qt.Checked:
selected.append(item.text())
return selected
def add_input_files(self):
"""Add files to the input directory."""
@ -824,78 +961,30 @@ class TranslationTab(QWidget):
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to add files:\n{str(e)}")
def remove_input_file(self):
"""Remove selected input file."""
current_item = self.input_list.currentItem()
if not current_item:
QMessageBox.information(self, "No Selection", "Please select a file to remove.")
return
def remove_selected_files(self):
"""Remove selected (checked) files from the input directory."""
selected_files = self.get_selected_files()
file_path = self.files_dir / current_item.text()
if not selected_files:
QMessageBox.information(self, "No Selection", "Please check files to remove.")
return
reply = QMessageBox.question(
self,
"Confirm Deletion",
f"Are you sure you want to delete '{current_item.text()}'?",
f"Are you sure you want to delete {len(selected_files)} file(s)?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
try:
file_path.unlink()
for filename in selected_files:
file_path = self.files_dir / filename
file_path.unlink()
self.refresh_file_lists()
QMessageBox.information(self, "Files Removed", f"Successfully removed {len(selected_files)} file(s).")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
def remove_output_file(self):
"""Remove selected output file."""
current_item = self.output_list.currentItem()
if not current_item:
QMessageBox.information(self, "No Selection", "Please select a file to remove.")
return
file_path = self.translated_dir / current_item.text()
reply = QMessageBox.question(
self,
"Confirm Deletion",
f"Are you sure you want to delete '{current_item.text()}'?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
try:
file_path.unlink()
self.refresh_file_lists()
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
def clear_output_files(self):
"""Clear all output files."""
if self.output_list.count() == 0:
QMessageBox.information(self, "No Files", "No output files to clear.")
return
reply = QMessageBox.question(
self,
"Confirm Clear",
"Are you sure you want to delete ALL translated files?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
try:
count = 0
for file_path in self.translated_dir.iterdir():
if file_path.is_file() and file_path.name != '.gitkeep':
file_path.unlink()
count += 1
self.refresh_file_lists()
QMessageBox.information(self, "Files Cleared", f"Deleted {count} files.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to clear files:\n{str(e)}")
QMessageBox.critical(self, "Error", f"Failed to delete files:\n{str(e)}")
def open_input_folder(self):
"""Open the input directory."""
@ -920,8 +1009,11 @@ class TranslationTab(QWidget):
def start_translation(self):
"""Start the translation process."""
if self.input_list.count() == 0:
QMessageBox.warning(self, "No Files", "No files found in the input folder to translate.")
# Get checked files
selected_files = self.get_selected_files()
if not selected_files:
QMessageBox.warning(self, "No Files Selected", "Please check at least one file to translate.")
return
# Get selected module
@ -931,138 +1023,93 @@ class TranslationTab(QWidget):
return
selected_module = self.modules[selected_index]
estimate_only = self.estimate_checkbox.isChecked()
# Get mode from dropdown
mode = self.mode_combo.currentText()
estimate_only = (mode == "Estimate")
parse_speakers = (mode == "Parse Speakers")
# Confirm start
action = "estimate" if estimate_only else "translate"
action_ing = "estimation of" if estimate_only else "translating"
action = mode.lower()
reply = QMessageBox.question(
self,
f"Start {action.title()}",
f"Start {action_ing} {self.input_list.count()} files using {selected_module[0]}?",
f"Start {mode}",
f"Start {action} for {len(selected_files)} file(s) using {selected_module[0]}?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
self.translate_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.clear_log()
# Toggle button visibility
self.translate_button.setVisible(False)
self.stop_button.setVisible(True)
# Initialize progress tracking
self.files_completed = 0
self.files_total = len(selected_files)
self.files_translated_label.setText(f"0/{self.files_total}")
self.translating_label.setText("Starting...")
self.item_progress_label.setText("0/0")
self.item_progress_bar.setValue(0)
self.item_progress_bar.setMaximum(100)
# Create and start translation worker
self.translation_worker = TranslationWorker(
self.project_root,
selected_module,
estimate_only
estimate_only,
selected_files # Pass selected files
)
# Connect signals
self.translation_worker.log_signal.connect(self.append_log)
self.translation_worker.progress_signal.connect(self.update_progress)
self.translation_worker.progress_signal.connect(self.update_file_progress)
self.translation_worker.item_progress_signal.connect(self.update_item_progress)
self.translation_worker.finished_signal.connect(self.on_translation_finished)
# Show progress bar and initialize
self.progress_bar.setVisible(True)
self.progress_bar.setValue(0)
self.progress_label.setText("Starting...")
# Hide cost display initially
self.cost_label.setVisible(False)
self.cost_label.setText("")
# Reduce file refresh frequency during translation
self.refresh_timer.stop()
# Start the worker
self.translation_worker.start()
def append_log(self, message):
"""Append a message to the log buffer for batched display."""
# Strip ANSI color codes from colorama
import re
clean_message = re.sub(r'\x1b\[[0-9;]*m', '', message)
"""Append a message to the log - now just for internal tracking."""
# We no longer display logs in the UI, but we can keep this for debugging
# or future use with the log viewer
pass
# Check if this is a cost message and update the cost display
if "Cost: $" in clean_message:
# Extract cost for display
cost_match = re.search(r'Cost: \$([0-9,\.]+)', clean_message)
if cost_match:
cost_value = cost_match.group(1)
if "TOTAL:" in clean_message:
# This is the final total cost
self.update_cost_display(f"Total Cost: ${cost_value}")
else:
# This is an individual file cost - extract filename
filename_match = re.search(r'^([^:]+):', clean_message)
if filename_match:
filename = filename_match.group(1).strip()
self.update_cost_display(f"{filename}: ${cost_value}")
else:
# Fallback - just show the cost
self.update_cost_display(f"File Cost: ${cost_value}")
def update_file_progress(self, current_file, total_files, filename):
"""Update the file-level progress."""
self.files_completed = current_file
self.files_translated_label.setText(f"{current_file}/{total_files}")
self.translating_label.setText(filename)
# Also check for the final cost display format (with emoji)
elif "💰" in message:
# Extract cost from the emoji format
cost_match = re.search(r'💰\s*(.+)', message)
if cost_match:
cost_text = cost_match.group(1).strip()
self.update_cost_display(f"Total: {cost_text}")
self.log_buffer.append(clean_message)
# Start timer if not already running
if not self.log_timer.isActive():
self.log_timer.start(100) # Flush every 100ms
def update_cost_display(self, cost_text):
"""Update the cost display label."""
self.cost_label.setText(cost_text)
self.cost_label.setVisible(True)
def update_item_progress(self, current_item, total_items):
"""Update the item-level progress (from tqdm)."""
self.item_progress_label.setText(f"{current_item}/{total_items}")
self.item_progress_bar.setMaximum(total_items if total_items > 0 else 100)
self.item_progress_bar.setValue(current_item)
def flush_log_buffer(self):
"""Flush the log buffer to the display."""
if self.log_buffer:
# Add all buffered messages at once
for message in self.log_buffer:
self.log_display.append(message)
self.log_buffer.clear()
# Auto-scroll to bottom
scrollbar = self.log_display.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
# Stop timer if buffer is empty
"""No longer needed - kept for compatibility."""
self.log_buffer.clear()
self.log_timer.stop()
def update_progress(self, current_file, total_files, filename):
"""Update the progress bar and label."""
self.progress_bar.setMaximum(total_files)
self.progress_bar.setValue(current_file)
self.progress_label.setText(f"Processing {filename} ({current_file}/{total_files})")
"""Legacy method - redirect to new method."""
self.update_file_progress(current_file, total_files, filename)
def on_translation_finished(self, success, message):
"""Handle translation completion."""
self.translate_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.progress_bar.setVisible(False)
self.progress_label.setText("Ready")
# Flush any remaining log messages
self.flush_log_buffer()
# Resume file refresh
self.refresh_timer.start(3000)
self.refresh_file_lists()
# Toggle button visibility
self.translate_button.setVisible(True)
self.stop_button.setVisible(False)
# Update progress display
if success:
self.append_log("")
self.append_log("🎉 Process completed successfully!")
self.translating_label.setText("Completed!")
else:
self.append_log("")
self.append_log(f"❌ Process failed: {message}")
self.translating_label.setText(f"Failed: {message}")
# Force flush the final messages
self.flush_log_buffer()
# Refresh file list to show any new translated files
self.refresh_file_lists()
def stop_translation(self):
"""Stop the translation process."""
@ -1071,30 +1118,16 @@ class TranslationTab(QWidget):
# Wait for the worker to stop gracefully
if not self.translation_worker.wait(5000): # Wait up to 5 seconds
self.append_log("⚠️ Worker didn't stop gracefully, forcing termination...")
self.translation_worker.terminate()
self.translation_worker.wait(2000) # Wait for termination
self.translate_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.progress_bar.setVisible(False)
self.progress_label.setText("Ready")
# Resume file refresh
self.refresh_timer.start(3000)
# Flush any remaining messages
self.flush_log_buffer()
def clear_log(self):
"""Clear the log display."""
self.log_buffer.clear() # Clear buffer too
self.log_display.clear()
# Toggle button visibility
self.translate_button.setVisible(True)
self.stop_button.setVisible(False)
self.translating_label.setText("Stopped")
def closeEvent(self, event):
"""Handle widget close event."""
if hasattr(self, 'refresh_timer'):
self.refresh_timer.stop()
if hasattr(self, 'log_timer'):
self.log_timer.stop()
if hasattr(self, 'translation_worker') and self.translation_worker.isRunning():