icon glory
This commit is contained in:
parent
d870a395fd
commit
ff5aefc82f
1 changed files with 402 additions and 369 deletions
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Reference in a new issue