Get mv config working
This commit is contained in:
parent
b44633f13c
commit
e7b41c9f44
7 changed files with 1245 additions and 72 deletions
|
|
@ -27,11 +27,6 @@ class ConfigIntegration:
|
|||
with open(module_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Create backup
|
||||
backup_path = module_path.with_suffix('.py.backup')
|
||||
with open(backup_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
# Update configuration values
|
||||
updated_content = self._update_config_values(content, config)
|
||||
|
||||
|
|
@ -42,12 +37,6 @@ class ConfigIntegration:
|
|||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Restore from backup if something went wrong
|
||||
if backup_path.exists():
|
||||
with open(backup_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
with open(module_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
raise e
|
||||
|
||||
def _update_config_values(self, content: str, config: Dict[str, Any]) -> str:
|
||||
|
|
|
|||
466
gui/file_manager_tab.py
Normal file
466
gui/file_manager_tab.py
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
File Manager Tab for DazedMTLTool GUI
|
||||
|
||||
Manages input files from 'files' directory and output to 'translated' directory.
|
||||
Users can add files through GUI or by copying them to the files folder.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTreeWidget, QTreeWidgetItem,
|
||||
QPushButton, QGroupBox, QSplitter, QTextEdit, QFileDialog, QMessageBox,
|
||||
QProgressBar, QFrame, QHeaderView
|
||||
)
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
|
||||
class FileManagerTab(QWidget):
|
||||
"""File Manager tab for managing input and output files."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.project_root = Path(__file__).parent.parent
|
||||
self.files_dir = self.project_root / "files"
|
||||
self.translated_dir = self.project_root / "translated"
|
||||
|
||||
# Ensure directories exist
|
||||
self.files_dir.mkdir(exist_ok=True)
|
||||
self.translated_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.setup_ui()
|
||||
self.refresh_file_lists()
|
||||
|
||||
# Auto-refresh timer
|
||||
self.refresh_timer = QTimer()
|
||||
self.refresh_timer.timeout.connect(self.refresh_file_lists)
|
||||
self.refresh_timer.start(3000) # Refresh every 3 seconds
|
||||
|
||||
def setup_ui(self):
|
||||
"""Set up the user interface."""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Title
|
||||
title_label = QLabel("File Manager")
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #007acc;")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Description
|
||||
desc_label = QLabel(
|
||||
"Input files are loaded from the 'files' directory. "
|
||||
"Translated files will be saved to the 'translated' directory. "
|
||||
"You can add files using the GUI or by copying them to the files folder."
|
||||
)
|
||||
desc_label.setWordWrap(True)
|
||||
desc_label.setStyleSheet("color: #cccccc; margin-bottom: 10px;")
|
||||
layout.addWidget(desc_label)
|
||||
|
||||
# Main splitter
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# Input files section
|
||||
input_group = self.create_input_files_section()
|
||||
splitter.addWidget(input_group)
|
||||
|
||||
# Output files section
|
||||
output_group = self.create_output_files_section()
|
||||
splitter.addWidget(output_group)
|
||||
|
||||
# Set equal sizes
|
||||
splitter.setSizes([400, 400])
|
||||
layout.addWidget(splitter)
|
||||
|
||||
# Action buttons
|
||||
button_layout = self.create_action_buttons()
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# Status bar
|
||||
self.status_label = QLabel("Ready")
|
||||
self.status_label.setStyleSheet("color: #cccccc; padding: 5px;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def create_input_files_section(self):
|
||||
"""Create the input files section."""
|
||||
group = QGroupBox("Input Files (files directory)")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# File tree
|
||||
self.input_tree = QTreeWidget()
|
||||
self.input_tree.setHeaderLabels(["Filename", "Size", "Modified"])
|
||||
self.input_tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
self.input_tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||||
self.input_tree.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
||||
self.input_tree.itemSelectionChanged.connect(self.on_input_file_selected)
|
||||
layout.addWidget(self.input_tree)
|
||||
|
||||
# Input buttons
|
||||
input_button_layout = QHBoxLayout()
|
||||
|
||||
self.add_file_btn = QPushButton("Add Files")
|
||||
self.add_file_btn.clicked.connect(self.add_files)
|
||||
input_button_layout.addWidget(self.add_file_btn)
|
||||
|
||||
self.remove_file_btn = QPushButton("Remove Selected")
|
||||
self.remove_file_btn.clicked.connect(self.remove_input_file)
|
||||
self.remove_file_btn.setEnabled(False)
|
||||
input_button_layout.addWidget(self.remove_file_btn)
|
||||
|
||||
self.open_files_folder_btn = QPushButton("Open Files Folder")
|
||||
self.open_files_folder_btn.clicked.connect(self.open_files_folder)
|
||||
input_button_layout.addWidget(self.open_files_folder_btn)
|
||||
|
||||
input_button_layout.addStretch()
|
||||
layout.addLayout(input_button_layout)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_output_files_section(self):
|
||||
"""Create the output files section."""
|
||||
group = QGroupBox("Output Files (translated directory)")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# File tree
|
||||
self.output_tree = QTreeWidget()
|
||||
self.output_tree.setHeaderLabels(["Filename", "Size", "Modified"])
|
||||
self.output_tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
self.output_tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||||
self.output_tree.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
||||
self.output_tree.itemSelectionChanged.connect(self.on_output_file_selected)
|
||||
layout.addWidget(self.output_tree)
|
||||
|
||||
# Output buttons
|
||||
output_button_layout = QHBoxLayout()
|
||||
|
||||
self.view_output_btn = QPushButton("View Selected")
|
||||
self.view_output_btn.clicked.connect(self.view_output_file)
|
||||
self.view_output_btn.setEnabled(False)
|
||||
output_button_layout.addWidget(self.view_output_btn)
|
||||
|
||||
self.delete_output_btn = QPushButton("Delete Selected")
|
||||
self.delete_output_btn.clicked.connect(self.delete_output_file)
|
||||
self.delete_output_btn.setEnabled(False)
|
||||
output_button_layout.addWidget(self.delete_output_btn)
|
||||
|
||||
self.open_translated_folder_btn = QPushButton("Open Translated Folder")
|
||||
self.open_translated_folder_btn.clicked.connect(self.open_translated_folder)
|
||||
output_button_layout.addWidget(self.open_translated_folder_btn)
|
||||
|
||||
output_button_layout.addStretch()
|
||||
layout.addLayout(output_button_layout)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_action_buttons(self):
|
||||
"""Create main action buttons."""
|
||||
layout = QHBoxLayout()
|
||||
|
||||
self.refresh_btn = QPushButton("Refresh File Lists")
|
||||
self.refresh_btn.clicked.connect(self.refresh_file_lists)
|
||||
layout.addWidget(self.refresh_btn)
|
||||
|
||||
self.clear_translated_btn = QPushButton("Clear All Translated Files")
|
||||
self.clear_translated_btn.clicked.connect(self.clear_translated_files)
|
||||
layout.addWidget(self.clear_translated_btn)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Auto-refresh indicator
|
||||
self.auto_refresh_label = QLabel("Auto-refresh: ON")
|
||||
self.auto_refresh_label.setStyleSheet("color: #00aa00; font-size: 10px;")
|
||||
layout.addWidget(self.auto_refresh_label)
|
||||
|
||||
return layout
|
||||
|
||||
def refresh_file_lists(self):
|
||||
"""Refresh both input and output file lists."""
|
||||
self.refresh_input_files()
|
||||
self.refresh_output_files()
|
||||
self.update_status()
|
||||
|
||||
def refresh_input_files(self):
|
||||
"""Refresh the input files tree."""
|
||||
self.input_tree.clear()
|
||||
|
||||
if not self.files_dir.exists():
|
||||
return
|
||||
|
||||
supported_extensions = {'.json', '.txt', '.csv', '.js', '.py', '.xml', '.html', '.md'}
|
||||
|
||||
for file_path in self.files_dir.iterdir():
|
||||
if (file_path.is_file() and
|
||||
file_path.suffix.lower() in supported_extensions and
|
||||
file_path.name != '.gitkeep'):
|
||||
item = QTreeWidgetItem()
|
||||
item.setText(0, file_path.name)
|
||||
|
||||
# File size
|
||||
size = file_path.stat().st_size
|
||||
if size < 1024:
|
||||
size_str = f"{size} B"
|
||||
elif size < 1024 * 1024:
|
||||
size_str = f"{size / 1024:.1f} KB"
|
||||
else:
|
||||
size_str = f"{size / (1024 * 1024):.1f} MB"
|
||||
item.setText(1, size_str)
|
||||
|
||||
# Modified time
|
||||
import datetime
|
||||
mtime = datetime.datetime.fromtimestamp(file_path.stat().st_mtime)
|
||||
item.setText(2, mtime.strftime("%Y-%m-%d %H:%M"))
|
||||
|
||||
# Store file path
|
||||
item.setData(0, Qt.UserRole, str(file_path))
|
||||
|
||||
self.input_tree.addTopLevelItem(item)
|
||||
|
||||
def refresh_output_files(self):
|
||||
"""Refresh the output files tree."""
|
||||
self.output_tree.clear()
|
||||
|
||||
if not self.translated_dir.exists():
|
||||
return
|
||||
|
||||
for file_path in self.translated_dir.iterdir():
|
||||
if file_path.is_file() and file_path.name != '.gitkeep':
|
||||
item = QTreeWidgetItem()
|
||||
item.setText(0, file_path.name)
|
||||
|
||||
# File size
|
||||
size = file_path.stat().st_size
|
||||
if size < 1024:
|
||||
size_str = f"{size} B"
|
||||
elif size < 1024 * 1024:
|
||||
size_str = f"{size / 1024:.1f} KB"
|
||||
else:
|
||||
size_str = f"{size / (1024 * 1024):.1f} MB"
|
||||
item.setText(1, size_str)
|
||||
|
||||
# Modified time
|
||||
import datetime
|
||||
mtime = datetime.datetime.fromtimestamp(file_path.stat().st_mtime)
|
||||
item.setText(2, mtime.strftime("%Y-%m-%d %H:%M"))
|
||||
|
||||
# Store file path
|
||||
item.setData(0, Qt.UserRole, str(file_path))
|
||||
|
||||
self.output_tree.addTopLevelItem(item)
|
||||
|
||||
def update_status(self):
|
||||
"""Update the status label."""
|
||||
input_count = self.input_tree.topLevelItemCount()
|
||||
output_count = self.output_tree.topLevelItemCount()
|
||||
self.status_label.setText(f"Input files: {input_count} | Output files: {output_count}")
|
||||
|
||||
def on_input_file_selected(self):
|
||||
"""Handle input file selection."""
|
||||
selected = self.input_tree.selectedItems()
|
||||
self.remove_file_btn.setEnabled(len(selected) > 0)
|
||||
|
||||
def on_output_file_selected(self):
|
||||
"""Handle output file selection."""
|
||||
selected = self.output_tree.selectedItems()
|
||||
self.view_output_btn.setEnabled(len(selected) > 0)
|
||||
self.delete_output_btn.setEnabled(len(selected) > 0)
|
||||
|
||||
def add_files(self):
|
||||
"""Add files to the input directory."""
|
||||
file_paths, _ = QFileDialog.getOpenFileNames(
|
||||
self,
|
||||
"Select Files to Add",
|
||||
"",
|
||||
"All Supported Files (*.json *.txt *.csv *.js *.py *.xml *.html *.md);;All Files (*.*)"
|
||||
)
|
||||
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
copied_count = 0
|
||||
for file_path in file_paths:
|
||||
source_path = Path(file_path)
|
||||
dest_path = self.files_dir / source_path.name
|
||||
|
||||
try:
|
||||
# Check if file already exists
|
||||
if dest_path.exists():
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"File Exists",
|
||||
f"'{source_path.name}' already exists. Overwrite?",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
)
|
||||
if reply != QMessageBox.Yes:
|
||||
continue
|
||||
|
||||
shutil.copy2(source_path, dest_path)
|
||||
copied_count += 1
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Copy Error",
|
||||
f"Failed to copy '{source_path.name}':\n{str(e)}"
|
||||
)
|
||||
|
||||
if copied_count > 0:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Files Added",
|
||||
f"Successfully added {copied_count} file(s) to the input directory."
|
||||
)
|
||||
self.refresh_input_files()
|
||||
|
||||
def remove_input_file(self):
|
||||
"""Remove selected input file."""
|
||||
selected = self.input_tree.selectedItems()
|
||||
if not selected:
|
||||
return
|
||||
|
||||
item = selected[0]
|
||||
file_path = Path(item.data(0, Qt.UserRole))
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Confirm Removal",
|
||||
f"Are you sure you want to remove '{file_path.name}' from the input directory?",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
try:
|
||||
file_path.unlink()
|
||||
self.refresh_input_files()
|
||||
QMessageBox.information(self, "File Removed", f"'{file_path.name}' has been removed.")
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to remove file:\n{str(e)}")
|
||||
|
||||
def view_output_file(self):
|
||||
"""View selected output file."""
|
||||
selected = self.output_tree.selectedItems()
|
||||
if not selected:
|
||||
return
|
||||
|
||||
item = selected[0]
|
||||
file_path = Path(item.data(0, Qt.UserRole))
|
||||
|
||||
try:
|
||||
# Open file with default system application
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
os.startfile(str(file_path))
|
||||
elif platform.system() == 'Darwin': # macOS
|
||||
subprocess.run(['open', str(file_path)])
|
||||
else: # Linux
|
||||
subprocess.run(['xdg-open', str(file_path)])
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to open file:\n{str(e)}")
|
||||
|
||||
def delete_output_file(self):
|
||||
"""Delete selected output file."""
|
||||
selected = self.output_tree.selectedItems()
|
||||
if not selected:
|
||||
return
|
||||
|
||||
item = selected[0]
|
||||
file_path = Path(item.data(0, Qt.UserRole))
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Confirm Deletion",
|
||||
f"Are you sure you want to delete '{file_path.name}' from the output directory?",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
try:
|
||||
file_path.unlink()
|
||||
self.refresh_output_files()
|
||||
QMessageBox.information(self, "File Deleted", f"'{file_path.name}' has been deleted.")
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to delete file:\n{str(e)}")
|
||||
|
||||
def clear_translated_files(self):
|
||||
"""Clear all files from the translated directory."""
|
||||
if self.output_tree.topLevelItemCount() == 0:
|
||||
QMessageBox.information(self, "No Files", "There are no files to clear.")
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Confirm Clear All",
|
||||
"Are you sure you want to delete ALL files from the translated directory?",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
try:
|
||||
deleted_count = 0
|
||||
for file_path in self.translated_dir.iterdir():
|
||||
if file_path.is_file() and file_path.name != '.gitkeep':
|
||||
file_path.unlink()
|
||||
deleted_count += 1
|
||||
|
||||
self.refresh_output_files()
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Files Cleared",
|
||||
f"Successfully deleted {deleted_count} file(s) from the translated directory."
|
||||
)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to clear files:\n{str(e)}")
|
||||
|
||||
def open_files_folder(self):
|
||||
"""Open the files folder in the system file manager."""
|
||||
try:
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
subprocess.run(['explorer', str(self.files_dir)])
|
||||
elif platform.system() == 'Darwin': # macOS
|
||||
subprocess.run(['open', str(self.files_dir)])
|
||||
else: # Linux
|
||||
subprocess.run(['xdg-open', str(self.files_dir)])
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to open folder:\n{str(e)}")
|
||||
|
||||
def open_translated_folder(self):
|
||||
"""Open the translated folder in the system file manager."""
|
||||
try:
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
subprocess.run(['explorer', str(self.translated_dir)])
|
||||
elif platform.system() == 'Darwin': # macOS
|
||||
subprocess.run(['open', str(self.translated_dir)])
|
||||
else: # Linux
|
||||
subprocess.run(['xdg-open', str(self.translated_dir)])
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to open folder:\n{str(e)}")
|
||||
|
||||
def get_input_files(self):
|
||||
"""Get list of input files for processing."""
|
||||
input_files = []
|
||||
for i in range(self.input_tree.topLevelItemCount()):
|
||||
item = self.input_tree.topLevelItem(i)
|
||||
file_path = item.data(0, Qt.UserRole)
|
||||
input_files.append(Path(file_path))
|
||||
return input_files
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Handle widget close event."""
|
||||
if hasattr(self, 'refresh_timer'):
|
||||
self.refresh_timer.stop()
|
||||
event.accept()
|
||||
22
gui/main.py
22
gui/main.py
|
|
@ -22,6 +22,8 @@ from gui.rpgmaker_tab import RPGMakerTab
|
|||
from gui.other_modules_tab import OtherModulesTab
|
||||
from gui.log_viewer import LogViewer
|
||||
from gui.file_manager import FileManager
|
||||
from gui.file_manager_tab import FileManagerTab
|
||||
from gui.translation_tab import TranslationTab
|
||||
|
||||
class DazedMTLGUI(QMainWindow):
|
||||
"""Main GUI window for the DazedMTLTool."""
|
||||
|
|
@ -118,6 +120,14 @@ class DazedMTLGUI(QMainWindow):
|
|||
self.config_tab.config_changed.connect(self.on_config_changed)
|
||||
self.tab_widget.addTab(self.config_tab, "Configuration")
|
||||
|
||||
# File Manager Tab (New)
|
||||
self.file_manager_tab = FileManagerTab()
|
||||
self.tab_widget.addTab(self.file_manager_tab, "File Manager")
|
||||
|
||||
# Translation Execution Tab
|
||||
self.translation_tab = TranslationTab(self)
|
||||
self.tab_widget.addTab(self.translation_tab, "Translation")
|
||||
|
||||
# RPG Maker MV/MZ Tab
|
||||
self.rpgmaker_tab = RPGMakerTab()
|
||||
self.tab_widget.addTab(self.rpgmaker_tab, "RPG Maker MV/MZ")
|
||||
|
|
@ -126,10 +136,6 @@ class DazedMTLGUI(QMainWindow):
|
|||
self.other_modules_tab = OtherModulesTab()
|
||||
self.tab_widget.addTab(self.other_modules_tab, "Other Modules")
|
||||
|
||||
# File Manager Tab
|
||||
self.file_manager = FileManager()
|
||||
self.tab_widget.addTab(self.file_manager, "File Manager")
|
||||
|
||||
def on_config_changed(self):
|
||||
"""Handle configuration changes."""
|
||||
# This will be called when font scale or other settings change
|
||||
|
|
@ -330,6 +336,14 @@ class DazedMTLGUI(QMainWindow):
|
|||
def set_progress(self, value):
|
||||
"""Set the progress bar value (0-100)."""
|
||||
self.progress_bar.setValue(value)
|
||||
|
||||
def get_input_files(self):
|
||||
"""Get list of input files from the file manager."""
|
||||
try:
|
||||
return self.file_manager_tab.get_input_files()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not get input files: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
|
|
@ -21,12 +21,131 @@ except ImportError:
|
|||
class RPGMakerTab(QWidget):
|
||||
"""RPG Maker MV/MZ configuration tab."""
|
||||
|
||||
# Default configuration values for RPG Maker MV/MZ
|
||||
DEFAULT_CONFIG = {
|
||||
# General settings
|
||||
"FIRSTLINESPEAKERS": False,
|
||||
"FACENAME101": False,
|
||||
"NAMES": False,
|
||||
"BRFLAG": False,
|
||||
"FIXTEXTWRAP": True,
|
||||
"IGNORETLTEXT": False,
|
||||
|
||||
# Main dialogue codes (enabled by default)
|
||||
"CODE401": True, # Show Text
|
||||
"CODE405": True, # Show Text (line 4+)
|
||||
"CODE102": True, # Show Choices
|
||||
|
||||
# Optional codes (disabled by default)
|
||||
"CODE101": False, # Show Text (face)
|
||||
"CODE408": False, # Show Text (line continuation)
|
||||
|
||||
# Variable codes (disabled by default)
|
||||
"CODE122": False, # Control Variables
|
||||
|
||||
# Other codes (all disabled by default)
|
||||
"CODE103": False, # Input Number
|
||||
"CODE104": False, # Select Item
|
||||
"CODE111": False, # Conditional Branch
|
||||
"CODE117": False, # Common Event
|
||||
"CODE119": False, # Set Movement Route
|
||||
"CODE121": False, # Control Switches
|
||||
"CODE125": False, # Change Gold
|
||||
"CODE126": False, # Change Items
|
||||
"CODE127": False, # Change Weapons
|
||||
"CODE128": False, # Change Armors
|
||||
"CODE129": False, # Change Party Member
|
||||
"CODE132": False, # Change Battle BGM
|
||||
"CODE133": False, # Change Victory ME
|
||||
"CODE134": False, # Change Save Access
|
||||
"CODE135": False, # Change Menu Access
|
||||
"CODE136": False, # Change Encounter
|
||||
"CODE137": False, # Change Formation Access
|
||||
"CODE138": False, # Change Window Color
|
||||
"CODE139": False, # Change Defeat ME
|
||||
"CODE140": False, # Change Vehicle BGM
|
||||
"CODE201": False, # Transfer Player
|
||||
"CODE202": False, # Set Vehicle Location
|
||||
"CODE203": False, # Set Event Location
|
||||
"CODE204": False, # Scroll Map
|
||||
"CODE205": False, # Set Move Route
|
||||
"CODE206": False, # Get On/Off Vehicle
|
||||
"CODE211": False, # Change Transparency
|
||||
"CODE212": False, # Show Animation
|
||||
"CODE213": False, # Show Balloon Icon
|
||||
"CODE214": False, # Erase Event
|
||||
"CODE216": False, # Change Player Followers
|
||||
"CODE217": False, # Gather Followers
|
||||
"CODE221": False, # Fadeout Screen
|
||||
"CODE222": False, # Fadein Screen
|
||||
"CODE223": False, # Tint Screen
|
||||
"CODE224": False, # Flash Screen
|
||||
"CODE225": False, # Shake Screen
|
||||
"CODE230": False, # Wait
|
||||
"CODE231": False, # Show Picture
|
||||
"CODE232": False, # Move Picture
|
||||
"CODE233": False, # Rotate Picture
|
||||
"CODE234": False, # Tint Picture
|
||||
"CODE235": False, # Erase Picture
|
||||
"CODE236": False, # Set Weather Effect
|
||||
"CODE241": False, # Play BGM
|
||||
"CODE242": False, # Fadeout BGM
|
||||
"CODE243": False, # Save BGM
|
||||
"CODE244": False, # Resume BGM
|
||||
"CODE245": False, # Play BGS
|
||||
"CODE246": False, # Fadeout BGS
|
||||
"CODE249": False, # Play ME
|
||||
"CODE250": False, # Play SE
|
||||
"CODE251": False, # Stop SE
|
||||
"CODE261": False, # Show Movie
|
||||
"CODE281": False, # Change Map Name Display
|
||||
"CODE282": False, # Change Tileset
|
||||
"CODE283": False, # Change Battle Background
|
||||
"CODE284": False, # Change Parallax
|
||||
"CODE285": False, # Get Location Info
|
||||
"CODE301": False, # Battle Processing
|
||||
"CODE302": False, # Shop Processing
|
||||
"CODE303": False, # Name Input Processing
|
||||
"CODE311": False, # Change HP
|
||||
"CODE312": False, # Change MP
|
||||
"CODE313": False, # Change State
|
||||
"CODE314": False, # Recover All
|
||||
"CODE315": False, # Change EXP
|
||||
"CODE316": False, # Change Level
|
||||
"CODE317": False, # Change Parameters
|
||||
"CODE318": False, # Change Skills
|
||||
"CODE319": False, # Change Equipment
|
||||
"CODE320": False, # Change Name
|
||||
"CODE321": False, # Change Class
|
||||
"CODE322": False, # Change Actor Graphic
|
||||
"CODE323": False, # Change Vehicle Graphic
|
||||
"CODE324": False, # Change Nickname
|
||||
"CODE325": False, # Change Profile
|
||||
"CODE326": False, # Change TP
|
||||
"CODE331": False, # Change Enemy HP
|
||||
"CODE332": False, # Change Enemy MP
|
||||
"CODE333": False, # Change Enemy State
|
||||
"CODE334": False, # Enemy Recover All
|
||||
"CODE335": False, # Enemy Appear
|
||||
"CODE336": False, # Enemy Transform
|
||||
"CODE337": False, # Show Battle Animation
|
||||
"CODE339": False, # Force Action
|
||||
"CODE340": False, # Abort Battle
|
||||
"CODE351": False, # Open Menu Screen
|
||||
"CODE352": False, # Open Save Screen
|
||||
"CODE353": False, # Game Over
|
||||
"CODE354": False, # Return to Title Screen
|
||||
"CODE355": False, # Script
|
||||
"CODE356": False, # Plugin Command
|
||||
}
|
||||
|
||||
config_changed = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.config_integration = ConfigIntegration() if ConfigIntegration else None
|
||||
self.init_ui()
|
||||
self.connect_auto_apply() # Connect auto-apply before setting defaults
|
||||
self.reset_to_defaults()
|
||||
|
||||
def init_ui(self):
|
||||
|
|
@ -75,18 +194,13 @@ class RPGMakerTab(QWidget):
|
|||
button_layout = QHBoxLayout()
|
||||
|
||||
self.reset_button = QPushButton("Reset to Defaults")
|
||||
self.reset_button.clicked.connect(self.reset_to_defaults)
|
||||
self.reset_button.clicked.connect(self.reset_to_defaults_with_message)
|
||||
|
||||
self.validate_button = QPushButton("Validate Settings")
|
||||
self.validate_button.clicked.connect(self.validate_and_show_report)
|
||||
|
||||
self.apply_button = QPushButton("Apply to Module")
|
||||
self.apply_button.clicked.connect(self.apply_to_module)
|
||||
self.apply_button.setToolTip("Apply current settings to the rpgmakermvmz.py module file")
|
||||
|
||||
button_layout.addWidget(self.reset_button)
|
||||
button_layout.addWidget(self.validate_button)
|
||||
button_layout.addWidget(self.apply_button)
|
||||
button_layout.addStretch()
|
||||
|
||||
scroll_layout.addLayout(button_layout)
|
||||
|
|
@ -263,29 +377,105 @@ class RPGMakerTab(QWidget):
|
|||
|
||||
def reset_to_defaults(self):
|
||||
"""Reset all settings to default values."""
|
||||
# General config defaults
|
||||
self.first_line_speakers_cb.setChecked(False)
|
||||
self.facename_101_cb.setChecked(False)
|
||||
self.names_cb.setChecked(False)
|
||||
self.br_flag_cb.setChecked(False)
|
||||
self.fix_textwrap_cb.setChecked(True)
|
||||
self.ignore_tl_text_cb.setChecked(False)
|
||||
# Temporarily disconnect signals to avoid multiple apply calls
|
||||
self.disconnect_auto_apply()
|
||||
|
||||
# Main dialogue codes (enabled by default)
|
||||
self.code_401_cb.setChecked(True)
|
||||
self.code_405_cb.setChecked(True)
|
||||
self.code_102_cb.setChecked(True)
|
||||
# Apply default values from the DEFAULT_CONFIG constant
|
||||
self.first_line_speakers_cb.setChecked(self.DEFAULT_CONFIG["FIRSTLINESPEAKERS"])
|
||||
self.facename_101_cb.setChecked(self.DEFAULT_CONFIG["FACENAME101"])
|
||||
self.names_cb.setChecked(self.DEFAULT_CONFIG["NAMES"])
|
||||
self.br_flag_cb.setChecked(self.DEFAULT_CONFIG["BRFLAG"])
|
||||
self.fix_textwrap_cb.setChecked(self.DEFAULT_CONFIG["FIXTEXTWRAP"])
|
||||
self.ignore_tl_text_cb.setChecked(self.DEFAULT_CONFIG["IGNORETLTEXT"])
|
||||
|
||||
# Optional codes (disabled by default)
|
||||
self.code_101_cb.setChecked(False)
|
||||
self.code_408_cb.setChecked(False)
|
||||
# Main dialogue codes
|
||||
self.code_401_cb.setChecked(self.DEFAULT_CONFIG["CODE401"])
|
||||
self.code_405_cb.setChecked(self.DEFAULT_CONFIG["CODE405"])
|
||||
self.code_102_cb.setChecked(self.DEFAULT_CONFIG["CODE102"])
|
||||
|
||||
# Variable codes (disabled by default)
|
||||
self.code_122_cb.setChecked(False)
|
||||
# Optional codes
|
||||
self.code_101_cb.setChecked(self.DEFAULT_CONFIG["CODE101"])
|
||||
self.code_408_cb.setChecked(self.DEFAULT_CONFIG["CODE408"])
|
||||
|
||||
# Other codes (all disabled by default)
|
||||
# Variable codes
|
||||
self.code_122_cb.setChecked(self.DEFAULT_CONFIG["CODE122"])
|
||||
|
||||
# Other codes - apply defaults from the constant
|
||||
for code, cb in self.other_code_checkboxes.items():
|
||||
default_key = f"CODE{code}"
|
||||
if default_key in self.DEFAULT_CONFIG:
|
||||
cb.setChecked(self.DEFAULT_CONFIG[default_key])
|
||||
else:
|
||||
cb.setChecked(False) # Fallback to False if not in defaults
|
||||
|
||||
# Reconnect signals and apply changes once
|
||||
self.connect_auto_apply()
|
||||
self.apply_to_module(show_messages=False)
|
||||
|
||||
def reset_to_defaults_with_message(self):
|
||||
"""Reset to defaults and show confirmation message (for button clicks)."""
|
||||
self.reset_to_defaults()
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Reset Complete",
|
||||
"All settings have been reset to their default values and applied to the module."
|
||||
)
|
||||
|
||||
def disconnect_auto_apply(self):
|
||||
"""Disconnect all checkboxes from auto-apply to prevent multiple calls."""
|
||||
try:
|
||||
# General settings checkboxes
|
||||
self.first_line_speakers_cb.stateChanged.disconnect()
|
||||
self.facename_101_cb.stateChanged.disconnect()
|
||||
self.names_cb.stateChanged.disconnect()
|
||||
self.br_flag_cb.stateChanged.disconnect()
|
||||
self.fix_textwrap_cb.stateChanged.disconnect()
|
||||
self.ignore_tl_text_cb.stateChanged.disconnect()
|
||||
|
||||
# Main dialogue codes
|
||||
self.code_401_cb.stateChanged.disconnect()
|
||||
self.code_405_cb.stateChanged.disconnect()
|
||||
self.code_102_cb.stateChanged.disconnect()
|
||||
|
||||
# Optional codes
|
||||
self.code_101_cb.stateChanged.disconnect()
|
||||
self.code_408_cb.stateChanged.disconnect()
|
||||
|
||||
# Variable codes
|
||||
self.code_122_cb.stateChanged.disconnect()
|
||||
|
||||
# Other codes
|
||||
for cb in self.other_code_checkboxes.values():
|
||||
cb.stateChanged.disconnect()
|
||||
except TypeError:
|
||||
# Ignore if signals are not connected
|
||||
pass
|
||||
|
||||
def connect_auto_apply(self):
|
||||
"""Connect all checkboxes to auto-apply changes when modified."""
|
||||
# General settings checkboxes
|
||||
self.first_line_speakers_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.facename_101_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.names_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.br_flag_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.fix_textwrap_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.ignore_tl_text_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
|
||||
# Main dialogue codes
|
||||
self.code_401_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.code_405_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.code_102_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
|
||||
# Optional codes
|
||||
self.code_101_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
self.code_408_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
|
||||
# Variable codes
|
||||
self.code_122_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
|
||||
# Other codes
|
||||
for cb in self.other_code_checkboxes.values():
|
||||
cb.setChecked(False)
|
||||
cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||
|
||||
def get_config(self):
|
||||
"""Get current configuration as dictionary."""
|
||||
|
|
@ -427,35 +617,60 @@ class RPGMakerTab(QWidget):
|
|||
|
||||
return is_valid
|
||||
|
||||
def apply_to_module(self):
|
||||
def apply_to_module(self, show_messages=False):
|
||||
"""Apply current configuration to the rpgmakermvmz.py module."""
|
||||
if not self.config_integration:
|
||||
QMessageBox.warning(self, "Warning", "Configuration integration not available")
|
||||
return
|
||||
|
||||
try:
|
||||
config = self.get_config()
|
||||
success = self.config_integration.update_rpgmaker_config(config)
|
||||
|
||||
if success:
|
||||
# Direct file modification approach (completely silent)
|
||||
module_path = Path(__file__).parent.parent / "modules" / "rpgmakermvmz.py"
|
||||
|
||||
if not module_path.exists():
|
||||
if show_messages:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
"modules/rpgmakermvmz.py not found!\n\n"
|
||||
"Make sure you're running the GUI from the correct directory."
|
||||
)
|
||||
return
|
||||
|
||||
# Read the current file
|
||||
with open(module_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Update each configuration value
|
||||
for key, value in config.items():
|
||||
# Convert boolean to Python boolean string
|
||||
value_str = str(value)
|
||||
|
||||
# Find and replace the line with this configuration
|
||||
import re
|
||||
pattern = rf'^{key}\s*=\s*.*$'
|
||||
replacement = f'{key} = {value_str}'
|
||||
|
||||
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
|
||||
|
||||
# Write the updated content back
|
||||
with open(module_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
# Emit signal for other components (silent)
|
||||
self.config_changed.emit()
|
||||
|
||||
if show_messages:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Success",
|
||||
"Configuration has been applied to modules/rpgmakermvmz.py\n\n"
|
||||
"The module will now use these settings when running translations."
|
||||
)
|
||||
else:
|
||||
QMessageBox.warning(self, "Warning", "Failed to apply configuration to module")
|
||||
|
||||
except FileNotFoundError:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
"modules/rpgmakermvmz.py not found!\n\n"
|
||||
"Make sure you're running the GUI from the correct directory."
|
||||
)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Failed to apply configuration:\n{str(e)}")
|
||||
if show_messages:
|
||||
QMessageBox.critical(self, "Error", f"Failed to apply configuration:\n{str(e)}")
|
||||
# Silent failure for auto-apply - just emit signal anyway
|
||||
self.config_changed.emit()
|
||||
|
||||
def load_from_module(self):
|
||||
"""Load configuration from the actual module file."""
|
||||
|
|
|
|||
489
gui/translation_tab.py
Normal file
489
gui/translation_tab.py
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Translation Execution Tab for DazedMTLTool GUI
|
||||
|
||||
Handles running the translation process with configured settings.
|
||||
Each module has its own default settings in its folder without backups.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGroupBox,
|
||||
QComboBox, QTextEdit, QProgressBar, QMessageBox, QListWidget,
|
||||
QListWidgetItem, QSplitter, QCheckBox
|
||||
)
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
|
||||
from PyQt5.QtGui import QFont, QColor
|
||||
|
||||
|
||||
class TranslationWorker(QThread):
|
||||
"""Worker thread for translation process."""
|
||||
|
||||
progress_updated = pyqtSignal(int)
|
||||
status_updated = pyqtSignal(str)
|
||||
log_updated = pyqtSignal(str)
|
||||
finished = pyqtSignal(bool) # True if successful
|
||||
|
||||
def __init__(self, config, files_to_translate):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.files_to_translate = files_to_translate
|
||||
self.is_cancelled = False
|
||||
|
||||
def run(self):
|
||||
"""Run the translation process."""
|
||||
try:
|
||||
total_files = len(self.files_to_translate)
|
||||
if total_files == 0:
|
||||
self.status_updated.emit("No files to translate")
|
||||
self.finished.emit(False)
|
||||
return
|
||||
|
||||
self.status_updated.emit(f"Starting translation of {total_files} files...")
|
||||
|
||||
for i, file_path in enumerate(self.files_to_translate):
|
||||
if self.is_cancelled:
|
||||
self.status_updated.emit("Translation cancelled")
|
||||
self.finished.emit(False)
|
||||
return
|
||||
|
||||
progress = int((i / total_files) * 100)
|
||||
self.progress_updated.emit(progress)
|
||||
self.status_updated.emit(f"Translating: {file_path.name}")
|
||||
self.log_updated.emit(f"Processing file: {file_path}")
|
||||
|
||||
# Here we would call the actual translation module
|
||||
success = self.translate_file(file_path)
|
||||
|
||||
if not success:
|
||||
self.log_updated.emit(f"Failed to translate: {file_path.name}")
|
||||
self.status_updated.emit(f"Error translating {file_path.name}")
|
||||
self.finished.emit(False)
|
||||
return
|
||||
else:
|
||||
self.log_updated.emit(f"Successfully translated: {file_path.name}")
|
||||
|
||||
self.progress_updated.emit(100)
|
||||
self.status_updated.emit(f"Translation completed successfully!")
|
||||
self.finished.emit(True)
|
||||
|
||||
except Exception as e:
|
||||
self.log_updated.emit(f"Translation error: {str(e)}")
|
||||
self.status_updated.emit(f"Translation failed: {str(e)}")
|
||||
self.finished.emit(False)
|
||||
|
||||
def translate_file(self, file_path):
|
||||
"""Translate a single file using the appropriate module."""
|
||||
try:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# Import the main translation module
|
||||
from modules.main import translate_file
|
||||
|
||||
# Create module-specific config for this file
|
||||
self.create_module_config(file_path)
|
||||
|
||||
# Call the translation function
|
||||
result = translate_file(str(file_path))
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.log_updated.emit(f"Error in translate_file: {str(e)}")
|
||||
return False
|
||||
|
||||
def create_module_config(self, file_path):
|
||||
"""Create module-specific configuration without backups."""
|
||||
try:
|
||||
# Determine the module based on file extension and content
|
||||
module_name = self.determine_module(file_path)
|
||||
|
||||
if module_name:
|
||||
# Create module directory if it doesn't exist
|
||||
module_dir = Path(__file__).parent.parent / "modules" / module_name
|
||||
module_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Create default settings file
|
||||
settings_file = module_dir / "settings.json"
|
||||
|
||||
# Only create if it doesn't exist (no overwriting)
|
||||
if not settings_file.exists():
|
||||
default_settings = self.get_default_settings(module_name)
|
||||
with open(settings_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_settings, f, indent=2, ensure_ascii=False)
|
||||
|
||||
self.log_updated.emit(f"Created default settings for {module_name}")
|
||||
|
||||
except Exception as e:
|
||||
self.log_updated.emit(f"Error creating module config: {str(e)}")
|
||||
|
||||
def determine_module(self, file_path):
|
||||
"""Determine which module to use based on file type."""
|
||||
suffix = file_path.suffix.lower()
|
||||
|
||||
if suffix == '.json':
|
||||
# Check if it's RPG Maker format
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if '"code":' in content or '"parameters":' in content:
|
||||
return "rpgmakermvmz"
|
||||
else:
|
||||
return "json"
|
||||
except:
|
||||
return "json"
|
||||
elif suffix == '.txt':
|
||||
return "text"
|
||||
elif suffix == '.csv':
|
||||
return "csv"
|
||||
elif suffix in ['.js', '.py']:
|
||||
return "regex"
|
||||
else:
|
||||
return "text" # Default fallback
|
||||
|
||||
def get_default_settings(self, module_name):
|
||||
"""Get default settings for a module."""
|
||||
base_settings = {
|
||||
"enabled": True,
|
||||
"backup": False, # No backups as requested
|
||||
"output_directory": "translated",
|
||||
"preserve_formatting": True
|
||||
}
|
||||
|
||||
if module_name == "rpgmakermvmz":
|
||||
# Use the same defaults as defined in RPG Maker tab
|
||||
rpg_defaults = {
|
||||
# Main dialogue codes (enabled by default)
|
||||
"CODE401": True, # Show Text
|
||||
"CODE405": True, # Show Text (line 4+)
|
||||
"CODE102": True, # Show Choices
|
||||
|
||||
# Optional codes (disabled by default)
|
||||
"CODE101": False, # Show Text (face)
|
||||
"CODE408": False, # Show Text (line continuation)
|
||||
|
||||
# Variable codes (disabled by default)
|
||||
"CODE122": False, # Control Variables
|
||||
|
||||
# General settings
|
||||
"FIRSTLINESPEAKERS": False,
|
||||
"FACENAME101": False,
|
||||
"NAMES": False,
|
||||
"BRFLAG": False,
|
||||
"FIXTEXTWRAP": True,
|
||||
"IGNORETLTEXT": False,
|
||||
|
||||
# All other codes disabled by default
|
||||
"translate_dialogue": True,
|
||||
"translate_choices": True,
|
||||
"translate_items": False,
|
||||
"translate_skills": False,
|
||||
"translate_states": False,
|
||||
"translate_actors": False,
|
||||
"translate_classes": False,
|
||||
"translate_weapons": False,
|
||||
"translate_armors": False,
|
||||
"translate_enemies": False,
|
||||
"event_codes": ["401", "405", "102"] # Only main dialogue codes enabled
|
||||
}
|
||||
base_settings.update(rpg_defaults)
|
||||
elif module_name == "json":
|
||||
base_settings.update({
|
||||
"translate_strings": True,
|
||||
"skip_keys": ["id", "index", "type"],
|
||||
"nested_translation": True
|
||||
})
|
||||
elif module_name == "text":
|
||||
base_settings.update({
|
||||
"line_by_line": True,
|
||||
"skip_empty_lines": True,
|
||||
"preserve_whitespace": True
|
||||
})
|
||||
elif module_name == "csv":
|
||||
base_settings.update({
|
||||
"has_header": True,
|
||||
"delimiter": ",",
|
||||
"columns_to_translate": [] # Empty means translate all text columns
|
||||
})
|
||||
|
||||
return base_settings
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel the translation process."""
|
||||
self.is_cancelled = True
|
||||
|
||||
|
||||
class TranslationTab(QWidget):
|
||||
"""Translation execution tab."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent_window = parent
|
||||
self.worker = None
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Set up the user interface."""
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Title
|
||||
title_label = QLabel("Translation Execution")
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #007acc;")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Description
|
||||
desc_label = QLabel(
|
||||
"Execute the translation process on selected files. "
|
||||
"Each module will use its default settings stored in its own folder."
|
||||
)
|
||||
desc_label.setWordWrap(True)
|
||||
desc_label.setStyleSheet("color: #cccccc; margin-bottom: 10px;")
|
||||
layout.addWidget(desc_label)
|
||||
|
||||
# Main content
|
||||
content_splitter = QSplitter(Qt.Vertical)
|
||||
|
||||
# File selection and controls
|
||||
controls_group = self.create_controls_section()
|
||||
content_splitter.addWidget(controls_group)
|
||||
|
||||
# Progress and log section
|
||||
progress_group = self.create_progress_section()
|
||||
content_splitter.addWidget(progress_group)
|
||||
|
||||
content_splitter.setSizes([300, 200])
|
||||
layout.addWidget(content_splitter)
|
||||
|
||||
# Status bar
|
||||
self.status_label = QLabel("Ready to translate")
|
||||
self.status_label.setStyleSheet("color: #cccccc; padding: 5px;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def create_controls_section(self):
|
||||
"""Create the controls section."""
|
||||
group = QGroupBox("Translation Controls")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# File selection
|
||||
file_layout = QHBoxLayout()
|
||||
|
||||
file_label = QLabel("Files to translate:")
|
||||
file_label.setStyleSheet("color: #ffffff;")
|
||||
file_layout.addWidget(file_label)
|
||||
|
||||
self.refresh_files_btn = QPushButton("Refresh File List")
|
||||
self.refresh_files_btn.clicked.connect(self.refresh_file_list)
|
||||
file_layout.addWidget(self.refresh_files_btn)
|
||||
|
||||
file_layout.addStretch()
|
||||
layout.addLayout(file_layout)
|
||||
|
||||
# File list
|
||||
self.file_list = QListWidget()
|
||||
self.file_list.setSelectionMode(QListWidget.ExtendedSelection)
|
||||
layout.addWidget(self.file_list)
|
||||
|
||||
# Options
|
||||
options_layout = QHBoxLayout()
|
||||
|
||||
self.select_all_cb = QCheckBox("Select All Files")
|
||||
self.select_all_cb.stateChanged.connect(self.toggle_select_all)
|
||||
options_layout.addWidget(self.select_all_cb)
|
||||
|
||||
self.dry_run_cb = QCheckBox("Dry Run (test without translating)")
|
||||
options_layout.addWidget(self.dry_run_cb)
|
||||
|
||||
options_layout.addStretch()
|
||||
layout.addLayout(options_layout)
|
||||
|
||||
# Action buttons
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
self.start_btn = QPushButton("Start Translation")
|
||||
self.start_btn.clicked.connect(self.start_translation)
|
||||
self.start_btn.setStyleSheet("QPushButton { background-color: #0a7a0a; }")
|
||||
button_layout.addWidget(self.start_btn)
|
||||
|
||||
self.stop_btn = QPushButton("Stop Translation")
|
||||
self.stop_btn.clicked.connect(self.stop_translation)
|
||||
self.stop_btn.setEnabled(False)
|
||||
self.stop_btn.setStyleSheet("QPushButton { background-color: #aa0000; }")
|
||||
button_layout.addWidget(self.stop_btn)
|
||||
|
||||
button_layout.addStretch()
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_progress_section(self):
|
||||
"""Create the progress and log section."""
|
||||
group = QGroupBox("Translation Progress")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Progress bar
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setRange(0, 100)
|
||||
self.progress_bar.setValue(0)
|
||||
layout.addWidget(self.progress_bar)
|
||||
|
||||
# Log output
|
||||
log_label = QLabel("Translation Log:")
|
||||
log_label.setStyleSheet("color: #ffffff; margin-top: 10px;")
|
||||
layout.addWidget(log_label)
|
||||
|
||||
self.log_output = QTextEdit()
|
||||
self.log_output.setReadOnly(True)
|
||||
self.log_output.setMaximumHeight(150)
|
||||
layout.addWidget(self.log_output)
|
||||
|
||||
# Clear log button
|
||||
clear_layout = QHBoxLayout()
|
||||
clear_layout.addStretch()
|
||||
|
||||
self.clear_log_btn = QPushButton("Clear Log")
|
||||
self.clear_log_btn.clicked.connect(self.clear_log)
|
||||
clear_layout.addWidget(self.clear_log_btn)
|
||||
|
||||
layout.addLayout(clear_layout)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def refresh_file_list(self):
|
||||
"""Refresh the list of available files."""
|
||||
self.file_list.clear()
|
||||
|
||||
try:
|
||||
# Get files from the file manager tab
|
||||
if hasattr(self.parent_window, 'file_manager_tab'):
|
||||
input_files = self.parent_window.file_manager_tab.get_input_files()
|
||||
|
||||
for file_path in input_files:
|
||||
item = QListWidgetItem(file_path.name)
|
||||
item.setData(Qt.UserRole, str(file_path))
|
||||
item.setCheckState(Qt.Checked) # Default to checked
|
||||
self.file_list.addItem(item)
|
||||
|
||||
self.status_label.setText(f"Found {len(input_files)} files ready for translation")
|
||||
else:
|
||||
self.status_label.setText("File manager not available")
|
||||
|
||||
except Exception as e:
|
||||
self.status_label.setText(f"Error loading files: {str(e)}")
|
||||
|
||||
def toggle_select_all(self, state):
|
||||
"""Toggle selection of all files."""
|
||||
check_state = Qt.Checked if state == Qt.Checked else Qt.Unchecked
|
||||
|
||||
for i in range(self.file_list.count()):
|
||||
item = self.file_list.item(i)
|
||||
item.setCheckState(check_state)
|
||||
|
||||
def get_selected_files(self):
|
||||
"""Get list of selected files."""
|
||||
selected_files = []
|
||||
|
||||
for i in range(self.file_list.count()):
|
||||
item = self.file_list.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
file_path = Path(item.data(Qt.UserRole))
|
||||
selected_files.append(file_path)
|
||||
|
||||
return selected_files
|
||||
|
||||
def start_translation(self):
|
||||
"""Start the translation process."""
|
||||
selected_files = self.get_selected_files()
|
||||
|
||||
if not selected_files:
|
||||
QMessageBox.warning(self, "No Files Selected", "Please select at least one file to translate.")
|
||||
return
|
||||
|
||||
# Get configuration from other tabs
|
||||
try:
|
||||
config = {}
|
||||
if hasattr(self.parent_window, 'config_tab'):
|
||||
config.update(self.parent_window.config_tab.get_config())
|
||||
if hasattr(self.parent_window, 'rpgmaker_tab'):
|
||||
config.update(self.parent_window.rpgmaker_tab.get_config())
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Configuration Error", f"Failed to get configuration:\n{str(e)}")
|
||||
return
|
||||
|
||||
# Confirm start
|
||||
if not self.dry_run_cb.isChecked():
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Confirm Translation",
|
||||
f"Start translation of {len(selected_files)} files?\n\nThis will create translated files in the 'translated' directory.",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
)
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
# Start worker thread
|
||||
self.worker = TranslationWorker(config, selected_files)
|
||||
self.worker.progress_updated.connect(self.progress_bar.setValue)
|
||||
self.worker.status_updated.connect(self.status_label.setText)
|
||||
self.worker.log_updated.connect(self.add_log_message)
|
||||
self.worker.finished.connect(self.on_translation_finished)
|
||||
|
||||
# Update UI state
|
||||
self.start_btn.setEnabled(False)
|
||||
self.stop_btn.setEnabled(True)
|
||||
self.progress_bar.setValue(0)
|
||||
|
||||
# Start translation
|
||||
self.worker.start()
|
||||
self.add_log_message(f"Starting translation of {len(selected_files)} files...")
|
||||
|
||||
def stop_translation(self):
|
||||
"""Stop the translation process."""
|
||||
if self.worker and self.worker.isRunning():
|
||||
self.worker.cancel()
|
||||
self.add_log_message("Cancelling translation...")
|
||||
self.status_label.setText("Stopping translation...")
|
||||
|
||||
def on_translation_finished(self, success):
|
||||
"""Handle translation completion."""
|
||||
# Update UI state
|
||||
self.start_btn.setEnabled(True)
|
||||
self.stop_btn.setEnabled(False)
|
||||
|
||||
if success:
|
||||
self.add_log_message("Translation completed successfully!")
|
||||
QMessageBox.information(self, "Translation Complete", "All files have been translated successfully!")
|
||||
|
||||
# Refresh file manager if available
|
||||
if hasattr(self.parent_window, 'file_manager_tab'):
|
||||
self.parent_window.file_manager_tab.refresh_file_lists()
|
||||
else:
|
||||
self.add_log_message("Translation failed or was cancelled.")
|
||||
|
||||
self.worker = None
|
||||
|
||||
def add_log_message(self, message):
|
||||
"""Add a message to the log output."""
|
||||
import datetime
|
||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
formatted_message = f"[{timestamp}] {message}"
|
||||
self.log_output.append(formatted_message)
|
||||
|
||||
# Auto-scroll to bottom
|
||||
scrollbar = self.log_output.verticalScrollBar()
|
||||
scrollbar.setValue(scrollbar.maximum())
|
||||
|
||||
def clear_log(self):
|
||||
"""Clear the log output."""
|
||||
self.log_output.clear()
|
||||
self.add_log_message("Log cleared")
|
||||
|
|
@ -87,12 +87,12 @@ TRANSLATION_CONFIG = TranslationConfig(
|
|||
LEAVE = False
|
||||
|
||||
# Config (Default)
|
||||
FIRSTLINESPEAKERS = False # If 1st line of 401 is a speaker, set to True (False)
|
||||
FACENAME101 = False # Find Speakers in 101 Codes based on Face Name (False)
|
||||
NAMES = False # Output a list of all the character names found (False)
|
||||
BRFLAG = False # If the game uses <br> instead (False)
|
||||
FIXTEXTWRAP = True # Overwrites textwrap (True)
|
||||
IGNORETLTEXT = False # Ignores all translated text. (False)
|
||||
FIRSTLINESPEAKERS = False
|
||||
FACENAME101 = False
|
||||
NAMES = False
|
||||
BRFLAG = False
|
||||
FIXTEXTWRAP = True
|
||||
IGNORETLTEXT = False
|
||||
|
||||
# Dialogue / Scroll / Choices (Main Codes)
|
||||
CODE401 = True
|
||||
|
|
@ -100,8 +100,8 @@ CODE405 = True
|
|||
CODE102 = True
|
||||
|
||||
# Optional
|
||||
CODE101 = False # Turn this one on when names exist in 101
|
||||
CODE408 = False # Warning, translates comments and can inflate costs.
|
||||
CODE101 = False
|
||||
CODE408 = False
|
||||
|
||||
# Variables
|
||||
CODE122 = False
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@ def set_defaults(file_path):
|
|||
|
||||
# Define the default values with comments
|
||||
defaults = {
|
||||
'FIRSTLINESPEAKERS': 'False # If 1st line of 401 is a speaker, set to True (False)',
|
||||
'FACENAME101': 'False # Find Speakers in 101 Codes based on Face Name (False)',
|
||||
'NAMES': 'False # Output a list of all the character names found (False)',
|
||||
'BRFLAG': 'False # If the game uses <br> instead (False)',
|
||||
'FIXTEXTWRAP': 'True # Overwrites textwrap (True)',
|
||||
'IGNORETLTEXT': 'False # Ignores all translated text. (False)',
|
||||
'FIRSTLINESPEAKERS': 'False',
|
||||
'FACENAME101': 'False',
|
||||
'NAMES': 'False',
|
||||
'BRFLAG': 'False',
|
||||
'FIXTEXTWRAP': 'True',
|
||||
'IGNORETLTEXT': 'False',
|
||||
# Dialogue / Scroll / Choices (Main Codes)
|
||||
'CODE401': 'True',
|
||||
'CODE405': 'True',
|
||||
'CODE102': 'True',
|
||||
# Optional
|
||||
'CODE101': 'False # Turn this one on when names exist in 101',
|
||||
'CODE408': 'False # Warning, translates comments and can inflate costs.',
|
||||
'CODE101': 'False',
|
||||
'CODE408': 'False',
|
||||
# Variables
|
||||
'CODE122': 'False',
|
||||
# Other
|
||||
|
|
|
|||
Loading…
Reference in a new issue