Get mv config working

This commit is contained in:
dazedanon 2025-07-18 15:44:26 -05:00
parent b44633f13c
commit e7b41c9f44
7 changed files with 1245 additions and 72 deletions

View file

@ -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
View 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()

View file

@ -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():

View file

@ -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
View 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")

View file

@ -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

View file

@ -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