diff --git a/gui/__init__.py b/gui/__init__.py
new file mode 100644
index 0000000..22a61ff
--- /dev/null
+++ b/gui/__init__.py
@@ -0,0 +1,23 @@
+"""
+DazedMTLTool GUI Package
+"""
+
+__version__ = "1.0.0"
+__author__ = "DazedTranslations"
+
+# Package imports
+from .main import DazedMTLGUI
+from .config_tab import ConfigTab
+from .rpgmaker_tab import RPGMakerTab
+from .other_modules_tab import OtherModulesTab
+from .log_viewer import LogViewer
+from .file_manager import FileManager
+
+__all__ = [
+ "DazedMTLGUI",
+ "ConfigTab",
+ "RPGMakerTab",
+ "OtherModulesTab",
+ "LogViewer",
+ "FileManager"
+]
diff --git a/gui/config_integration.py b/gui/config_integration.py
new file mode 100644
index 0000000..1f02c7c
--- /dev/null
+++ b/gui/config_integration.py
@@ -0,0 +1,220 @@
+"""
+Configuration Integration Helper
+Updates the actual module files with GUI configuration settings
+"""
+
+import re
+import os
+from pathlib import Path
+from typing import Dict, Any
+
+
+class ConfigIntegration:
+ """Helper class to integrate GUI configuration with module files."""
+
+ def __init__(self):
+ self.modules_dir = Path("modules")
+
+ def update_rpgmaker_config(self, config: Dict[str, Any]) -> bool:
+ """Update rpgmakermvmz.py with configuration from GUI."""
+ module_path = self.modules_dir / "rpgmakermvmz.py"
+
+ if not module_path.exists():
+ raise FileNotFoundError(f"Module file not found: {module_path}")
+
+ try:
+ # Read the current module file
+ 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)
+
+ # Write back to file
+ with open(module_path, 'w', encoding='utf-8') as f:
+ f.write(updated_content)
+
+ 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:
+ """Update configuration values in the module content."""
+ lines = content.split('\n')
+ updated_lines = []
+
+ # Track which configurations we found and updated
+ found_configs = set()
+
+ for line in lines:
+ updated_line = line
+
+ # Look for configuration assignments
+ for config_key, config_value in config.items():
+ # Match lines like: CONFIG_NAME = value # comment
+ pattern = rf'^({re.escape(config_key)})\s*=\s*.*?(#.*)?$'
+ match = re.match(pattern, line.strip())
+
+ if match:
+ # Preserve the comment if it exists
+ comment = match.group(2) if match.group(2) else ""
+ if comment:
+ comment = " " + comment
+
+ # Create the new line with proper formatting
+ updated_line = f"{config_key} = {config_value}{comment}"
+ found_configs.add(config_key)
+ break
+
+ updated_lines.append(updated_line)
+
+ # Check if all configurations were found
+ missing_configs = set(config.keys()) - found_configs
+ if missing_configs:
+ print(f"Warning: Could not find these configurations in module: {missing_configs}")
+
+ return '\n'.join(updated_lines)
+
+ def update_env_file(self, config: Dict[str, Any], env_path: Path = None) -> bool:
+ """Update .env file with configuration."""
+ if env_path is None:
+ env_path = Path(".env")
+
+ try:
+ from dotenv import set_key
+
+ for key, value in config.items():
+ set_key(env_path, key, str(value))
+
+ return True
+
+ except Exception as e:
+ print(f"Error updating .env file: {e}")
+ return False
+
+ def read_current_config(self, module_path: Path = None) -> Dict[str, Any]:
+ """Read current configuration from module file."""
+ if module_path is None:
+ module_path = self.modules_dir / "rpgmakermvmz.py"
+
+ if not module_path.exists():
+ return {}
+
+ config = {}
+
+ try:
+ with open(module_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Find configuration lines
+ config_patterns = [
+ r'^(FIRSTLINESPEAKERS|FACENAME101|NAMES|BRFLAG|FIXTEXTWRAP|IGNORETLTEXT)\s*=\s*(True|False)',
+ r'^(CODE\d+)\s*=\s*(True|False)'
+ ]
+
+ for line in content.split('\n'):
+ line = line.strip()
+ for pattern in config_patterns:
+ match = re.match(pattern, line)
+ if match:
+ key = match.group(1)
+ value = match.group(2) == 'True'
+ config[key] = value
+
+ except Exception as e:
+ print(f"Error reading configuration from {module_path}: {e}")
+
+ return config
+
+ def validate_module_syntax(self, module_path: Path = None) -> bool:
+ """Validate that the module file has correct Python syntax."""
+ if module_path is None:
+ module_path = self.modules_dir / "rpgmakermvmz.py"
+
+ try:
+ with open(module_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Try to compile the code
+ compile(content, str(module_path), 'exec')
+ return True
+
+ except SyntaxError as e:
+ print(f"Syntax error in {module_path}: {e}")
+ return False
+ except Exception as e:
+ print(f"Error validating {module_path}: {e}")
+ return False
+
+ def create_config_template(self) -> Dict[str, Any]:
+ """Create a template configuration with default values."""
+ return {
+ # General config
+ "FIRSTLINESPEAKERS": False,
+ "FACENAME101": False,
+ "NAMES": False,
+ "BRFLAG": False,
+ "FIXTEXTWRAP": True,
+ "IGNORETLTEXT": False,
+
+ # Main dialogue codes
+ "CODE401": True,
+ "CODE405": True,
+ "CODE102": True,
+
+ # Optional codes
+ "CODE101": False,
+ "CODE408": False,
+
+ # Variable codes
+ "CODE122": False,
+
+ # Other codes
+ "CODE355655": False,
+ "CODE357": False,
+ "CODE657": False,
+ "CODE356": False,
+ "CODE320": False,
+ "CODE324": False,
+ "CODE111": False,
+ "CODE108": False
+ }
+
+ def get_config_descriptions(self) -> Dict[str, str]:
+ """Get descriptions for all configuration options."""
+ return {
+ "FIRSTLINESPEAKERS": "If 1st line of 401 is a speaker, set to True",
+ "FACENAME101": "Find Speakers in 101 Codes based on Face Name",
+ "NAMES": "Output a list of all the character names found",
+ "BRFLAG": "If the game uses
instead of newlines",
+ "FIXTEXTWRAP": "Overwrites textwrap for better formatting",
+ "IGNORETLTEXT": "Ignores all translated text",
+
+ "CODE401": "Show Text - Main dialogue content",
+ "CODE405": "Show Text (Scrolling) - Longer dialogue",
+ "CODE102": "Show Choices - Player choice options",
+ "CODE101": "Character Names - Turn on when names exist in 101",
+ "CODE408": "Comments - WARNING: Can inflate costs significantly",
+ "CODE122": "Control Variables - Text stored in variables",
+
+ "CODE355655": "Scripts - Text within script commands",
+ "CODE357": "Picture Text - Text displayed on pictures",
+ "CODE657": "Picture Text Extended - Extended picture text",
+ "CODE356": "Plugin Commands - Plugin command parameters",
+ "CODE320": "Change Name Input - Name input prompts",
+ "CODE324": "Change Nickname - Nickname changes",
+ "CODE111": "Conditional Branch - Conditional text",
+ "CODE108": "Comments - Comment blocks"
+ }
diff --git a/gui/config_tab.py b/gui/config_tab.py
new file mode 100644
index 0000000..fc8996b
--- /dev/null
+++ b/gui/config_tab.py
@@ -0,0 +1,503 @@
+"""
+Configuration Tab - Handles environment variables and global settings
+"""
+
+import os
+from pathlib import Path
+from PyQt5.QtWidgets import (
+ QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QLineEdit,
+ QSpinBox, QDoubleSpinBox, QComboBox, QPushButton, QGroupBox,
+ QLabel, QFileDialog, QMessageBox, QScrollArea, QTextEdit,
+ QCheckBox, QApplication
+)
+from PyQt5.QtCore import Qt, pyqtSignal
+from dotenv import load_dotenv, set_key
+
+
+class ConfigTab(QWidget):
+ """Configuration tab for managing environment variables and global settings."""
+
+ config_changed = pyqtSignal()
+
+ def __init__(self):
+ super().__init__()
+ self.env_file_path = Path(".env")
+ self.init_ui()
+ self.load_from_env()
+
+ def init_ui(self):
+ """Initialize the user interface."""
+ main_layout = QVBoxLayout()
+
+ # Create scroll area
+ scroll_area = QScrollArea()
+ scroll_content = QWidget()
+ scroll_layout = QVBoxLayout()
+
+ # API Configuration Group
+ api_group = self.create_api_group()
+ scroll_layout.addWidget(api_group)
+
+ # Translation Settings Group
+ translation_group = self.create_translation_group()
+ scroll_layout.addWidget(translation_group)
+
+ # Performance Settings Group
+ performance_group = self.create_performance_group()
+ scroll_layout.addWidget(performance_group)
+
+ # Text Formatting Group
+ formatting_group = self.create_formatting_group()
+ scroll_layout.addWidget(formatting_group)
+
+ # Custom API Settings Group
+ custom_api_group = self.create_custom_api_group()
+ scroll_layout.addWidget(custom_api_group)
+
+ # UI Settings Group
+ ui_group = self.create_ui_settings_group()
+ scroll_layout.addWidget(ui_group)
+
+ # Buttons
+ button_layout = QHBoxLayout()
+
+ self.save_button = QPushButton("Save Configuration")
+ self.save_button.clicked.connect(self.save_to_env)
+
+ self.load_button = QPushButton("Load from File")
+ self.load_button.clicked.connect(self.load_from_file_dialog)
+
+ self.reset_button = QPushButton("Reset to Defaults")
+ self.reset_button.clicked.connect(self.reset_to_defaults)
+
+ button_layout.addWidget(self.save_button)
+ button_layout.addWidget(self.load_button)
+ button_layout.addWidget(self.reset_button)
+ button_layout.addStretch()
+
+ scroll_layout.addLayout(button_layout)
+
+ scroll_content.setLayout(scroll_layout)
+ scroll_area.setWidget(scroll_content)
+ scroll_area.setWidgetResizable(True)
+
+ main_layout.addWidget(scroll_area)
+ self.setLayout(main_layout)
+
+ def create_api_group(self):
+ """Create API configuration group."""
+ group = QGroupBox("API Configuration")
+ layout = QFormLayout()
+
+ # API URL
+ self.api_url_edit = QLineEdit()
+ self.api_url_edit.setPlaceholderText("Leave blank for OpenAI API")
+ layout.addRow("API URL:", self.api_url_edit)
+
+ # API Key
+ self.api_key_edit = QLineEdit()
+ self.api_key_edit.setEchoMode(QLineEdit.Password)
+ self.api_key_edit.setPlaceholderText("Enter your API key")
+ layout.addRow("API Key:", self.api_key_edit)
+
+ # Organization
+ self.organization_edit = QLineEdit()
+ self.organization_edit.setPlaceholderText("Organization key")
+ layout.addRow("Organization:", self.organization_edit)
+
+ # Model
+ self.model_combo = QComboBox()
+ self.model_combo.setEditable(True)
+ self.model_combo.addItems([
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-1106",
+ "gpt-4",
+ "gpt-4-1106-preview",
+ "gpt-4-turbo",
+ "claude-3-sonnet-20240229",
+ "claude-3-opus-20240229"
+ ])
+ layout.addRow("Model:", self.model_combo)
+
+ group.setLayout(layout)
+ return group
+
+ def create_translation_group(self):
+ """Create translation settings group."""
+ group = QGroupBox("Translation Settings")
+ layout = QFormLayout()
+
+ # Language
+ self.language_combo = QComboBox()
+ self.language_combo.addItems([
+ "English", "Spanish", "French", "German", "Italian",
+ "Portuguese", "Russian", "Chinese", "Korean", "Japanese"
+ ])
+ layout.addRow("Target Language:", self.language_combo)
+
+ # Timeout
+ self.timeout_spin = QSpinBox()
+ self.timeout_spin.setRange(30, 300)
+ self.timeout_spin.setValue(120)
+ self.timeout_spin.setSuffix(" seconds")
+ layout.addRow("Timeout:", self.timeout_spin)
+
+ group.setLayout(layout)
+ return group
+
+ def create_performance_group(self):
+ """Create performance settings group."""
+ group = QGroupBox("Performance Settings")
+ layout = QFormLayout()
+
+ # File Threads
+ self.file_threads_spin = QSpinBox()
+ self.file_threads_spin.setRange(1, 10)
+ self.file_threads_spin.setValue(1)
+ layout.addRow("File Threads:", self.file_threads_spin)
+
+ # Threads per File
+ self.threads_spin = QSpinBox()
+ self.threads_spin.setRange(1, 20)
+ self.threads_spin.setValue(1)
+ layout.addRow("Threads per File:", self.threads_spin)
+
+ # Batch Size
+ self.batch_size_spin = QSpinBox()
+ self.batch_size_spin.setRange(1, 100)
+ self.batch_size_spin.setValue(10)
+ layout.addRow("Batch Size:", self.batch_size_spin)
+
+ # Frequency Penalty
+ self.frequency_penalty_spin = QDoubleSpinBox()
+ self.frequency_penalty_spin.setRange(0.0, 2.0)
+ self.frequency_penalty_spin.setSingleStep(0.1)
+ self.frequency_penalty_spin.setValue(0.2)
+ layout.addRow("Frequency Penalty:", self.frequency_penalty_spin)
+
+ group.setLayout(layout)
+ return group
+
+ def create_formatting_group(self):
+ """Create text formatting settings group."""
+ group = QGroupBox("Text Formatting")
+ layout = QFormLayout()
+
+ # Dialogue Width
+ self.width_spin = QSpinBox()
+ self.width_spin.setRange(20, 200)
+ self.width_spin.setValue(60)
+ self.width_spin.setSuffix(" characters")
+ layout.addRow("Dialogue Width:", self.width_spin)
+
+ # List Width
+ self.list_width_spin = QSpinBox()
+ self.list_width_spin.setRange(20, 200)
+ self.list_width_spin.setValue(100)
+ self.list_width_spin.setSuffix(" characters")
+ layout.addRow("List Width:", self.list_width_spin)
+
+ # Note Width
+ self.note_width_spin = QSpinBox()
+ self.note_width_spin.setRange(20, 200)
+ self.note_width_spin.setValue(75)
+ self.note_width_spin.setSuffix(" characters")
+ layout.addRow("Note Width:", self.note_width_spin)
+
+ group.setLayout(layout)
+ return group
+
+ def create_custom_api_group(self):
+ """Create custom API settings group."""
+ group = QGroupBox("Custom API Pricing")
+ layout = QFormLayout()
+
+ # Input Cost
+ self.input_cost_spin = QDoubleSpinBox()
+ self.input_cost_spin.setRange(0.0, 1.0)
+ self.input_cost_spin.setDecimals(4)
+ self.input_cost_spin.setSingleStep(0.0001)
+ self.input_cost_spin.setValue(0.002)
+ self.input_cost_spin.setSuffix(" per 1K tokens")
+ layout.addRow("Input Cost:", self.input_cost_spin)
+
+ # Output Cost
+ self.output_cost_spin = QDoubleSpinBox()
+ self.output_cost_spin.setRange(0.0, 1.0)
+ self.output_cost_spin.setDecimals(4)
+ self.output_cost_spin.setSingleStep(0.0001)
+ self.output_cost_spin.setValue(0.002)
+ self.output_cost_spin.setSuffix(" per 1K tokens")
+ layout.addRow("Output Cost:", self.output_cost_spin)
+
+ group.setLayout(layout)
+ return group
+
+ def create_ui_settings_group(self):
+ """Create UI settings group."""
+ group = QGroupBox("User Interface Settings")
+ layout = QFormLayout()
+
+ # Font Scale
+ self.font_scale_spin = QDoubleSpinBox()
+ self.font_scale_spin.setRange(0.5, 3.0)
+ self.font_scale_spin.setSingleStep(0.1)
+ self.font_scale_spin.setValue(1.0)
+ self.font_scale_spin.setDecimals(1)
+ self.font_scale_spin.setSuffix("x")
+ self.font_scale_spin.setToolTip("Scale factor for all fonts in the GUI (1.0 = normal, 1.5 = 150% larger)")
+ self.font_scale_spin.valueChanged.connect(self.on_font_scale_changed)
+
+ # Font scale presets
+ font_layout = QHBoxLayout()
+ font_layout.addWidget(self.font_scale_spin)
+
+ # Quick preset buttons
+ small_btn = QPushButton("Small")
+ small_btn.clicked.connect(lambda: self.font_scale_spin.setValue(0.8))
+ small_btn.setMaximumWidth(60)
+
+ normal_btn = QPushButton("Normal")
+ normal_btn.clicked.connect(lambda: self.font_scale_spin.setValue(1.0))
+ normal_btn.setMaximumWidth(60)
+
+ large_btn = QPushButton("Large")
+ large_btn.clicked.connect(lambda: self.font_scale_spin.setValue(1.5))
+ large_btn.setMaximumWidth(60)
+
+ xlarge_btn = QPushButton("X-Large")
+ xlarge_btn.clicked.connect(lambda: self.font_scale_spin.setValue(2.0))
+ xlarge_btn.setMaximumWidth(60)
+
+ font_layout.addWidget(small_btn)
+ font_layout.addWidget(normal_btn)
+ font_layout.addWidget(large_btn)
+ font_layout.addWidget(xlarge_btn)
+
+ layout.addRow("Font Scale:", font_layout)
+
+ # DPI Awareness
+ self.dpi_aware_cb = QCheckBox("High DPI Scaling")
+ self.dpi_aware_cb.setToolTip("Enable automatic scaling for high DPI displays")
+ self.dpi_aware_cb.setChecked(True)
+ layout.addRow("", self.dpi_aware_cb)
+
+ # Theme selection
+ self.theme_combo = QComboBox()
+ self.theme_combo.addItems(["Dark", "Light", "System"])
+ self.theme_combo.setCurrentText("Dark")
+ layout.addRow("Theme:", self.theme_combo)
+
+ # Show font tip
+ self.show_font_tip_cb = QCheckBox("Show font size tip on startup")
+ self.show_font_tip_cb.setToolTip("Display helpful font sizing information when the GUI starts")
+ self.show_font_tip_cb.setChecked(True)
+ layout.addRow("", self.show_font_tip_cb)
+
+ group.setLayout(layout)
+ return group
+
+ def on_font_scale_changed(self, value):
+ """Handle font scale change."""
+ self.config_changed.emit()
+ # Apply font scaling immediately
+ self.apply_font_scaling(value)
+
+ def apply_font_scaling(self, scale_factor):
+ """Apply font scaling to the entire application."""
+ app = QApplication.instance()
+ if app:
+ # Get the default font
+ font = app.font()
+ base_size = 9 # Base font size
+ new_size = int(base_size * scale_factor)
+ font.setPointSize(new_size)
+ app.setFont(font)
+
+ # Emit signal to notify other components
+ self.config_changed.emit()
+
+ def load_from_env(self):
+ """Load configuration from .env file."""
+ if self.env_file_path.exists():
+ load_dotenv(self.env_file_path)
+
+ # Load API settings
+ self.api_url_edit.setText(os.getenv("api", ""))
+ self.api_key_edit.setText(os.getenv("key", ""))
+ self.organization_edit.setText(os.getenv("organization", ""))
+ self.model_combo.setCurrentText(os.getenv("model", "gpt-4"))
+
+ # Load translation settings
+ self.language_combo.setCurrentText(os.getenv("language", "English"))
+ self.timeout_spin.setValue(int(os.getenv("timeout", "120")))
+
+ # Load performance settings
+ self.file_threads_spin.setValue(int(os.getenv("fileThreads", "1")))
+ self.threads_spin.setValue(int(os.getenv("threads", "1")))
+ self.batch_size_spin.setValue(int(os.getenv("batchsize", "10")))
+ self.frequency_penalty_spin.setValue(float(os.getenv("frequency_penalty", "0.2")))
+
+ # Load formatting settings
+ self.width_spin.setValue(int(os.getenv("width", "60")))
+ self.list_width_spin.setValue(int(os.getenv("listWidth", "100")))
+ self.note_width_spin.setValue(int(os.getenv("noteWidth", "75")))
+
+ # Load custom API settings
+ self.input_cost_spin.setValue(float(os.getenv("input_cost", "0.002")))
+ self.output_cost_spin.setValue(float(os.getenv("output_cost", "0.002")))
+
+ # Load UI settings
+ self.font_scale_spin.setValue(float(os.getenv("font_scale", "1.0")))
+ self.dpi_aware_cb.setChecked(os.getenv("dpi_aware", "true").lower() == "true")
+ self.theme_combo.setCurrentText(os.getenv("theme", "Dark"))
+ self.show_font_tip_cb.setChecked(os.getenv("show_font_tip", "true").lower() == "true")
+
+ # Apply font scaling
+ self.apply_font_scaling(self.font_scale_spin.value())
+
+ def save_to_env(self):
+ """Save configuration to .env file."""
+ try:
+ # Ensure .env file exists
+ if not self.env_file_path.exists():
+ self.env_file_path.touch()
+
+ # Save API settings
+ set_key(self.env_file_path, "api", self.api_url_edit.text())
+ set_key(self.env_file_path, "key", self.api_key_edit.text())
+ set_key(self.env_file_path, "organization", self.organization_edit.text())
+ set_key(self.env_file_path, "model", self.model_combo.currentText())
+
+ # Save translation settings
+ set_key(self.env_file_path, "language", self.language_combo.currentText())
+ set_key(self.env_file_path, "timeout", str(self.timeout_spin.value()))
+
+ # Save performance settings
+ set_key(self.env_file_path, "fileThreads", str(self.file_threads_spin.value()))
+ set_key(self.env_file_path, "threads", str(self.threads_spin.value()))
+ set_key(self.env_file_path, "batchsize", str(self.batch_size_spin.value()))
+ set_key(self.env_file_path, "frequency_penalty", str(self.frequency_penalty_spin.value()))
+
+ # Save formatting settings
+ set_key(self.env_file_path, "width", str(self.width_spin.value()))
+ set_key(self.env_file_path, "listWidth", str(self.list_width_spin.value()))
+ set_key(self.env_file_path, "noteWidth", str(self.note_width_spin.value()))
+
+ # Save custom API settings
+ set_key(self.env_file_path, "input_cost", str(self.input_cost_spin.value()))
+ set_key(self.env_file_path, "output_cost", str(self.output_cost_spin.value()))
+
+ # Save UI settings
+ set_key(self.env_file_path, "font_scale", str(self.font_scale_spin.value()))
+ set_key(self.env_file_path, "dpi_aware", str(self.dpi_aware_cb.isChecked()).lower())
+ set_key(self.env_file_path, "theme", self.theme_combo.currentText())
+ set_key(self.env_file_path, "show_font_tip", str(self.show_font_tip_cb.isChecked()).lower())
+
+ QMessageBox.information(self, "Success", "Configuration saved successfully!")
+ self.config_changed.emit()
+
+ except Exception as e:
+ QMessageBox.critical(self, "Error", f"Failed to save configuration:\n{str(e)}")
+
+ def load_from_file_dialog(self):
+ """Load configuration from a file via dialog."""
+ file_path, _ = QFileDialog.getOpenFileName(
+ self, "Load Configuration", "", "Environment Files (*.env);;All Files (*)"
+ )
+
+ if file_path:
+ self.load_from_file(file_path)
+
+ def load_from_file(self, file_path):
+ """Load configuration from a specific file."""
+ try:
+ load_dotenv(file_path, override=True)
+ self.load_from_env()
+ QMessageBox.information(self, "Success", f"Configuration loaded from {Path(file_path).name}")
+ except Exception as e:
+ QMessageBox.critical(self, "Error", f"Failed to load configuration:\n{str(e)}")
+
+ def reset_to_defaults(self):
+ """Reset all settings to default values."""
+ # API settings
+ self.api_url_edit.clear()
+ self.api_key_edit.clear()
+ self.organization_edit.clear()
+ self.model_combo.setCurrentText("gpt-4")
+
+ # Translation settings
+ self.language_combo.setCurrentText("English")
+ self.timeout_spin.setValue(120)
+
+ # Performance settings
+ self.file_threads_spin.setValue(1)
+ self.threads_spin.setValue(1)
+ self.batch_size_spin.setValue(10)
+ self.frequency_penalty_spin.setValue(0.2)
+
+ # Formatting settings
+ self.width_spin.setValue(60)
+ self.list_width_spin.setValue(100)
+ self.note_width_spin.setValue(75)
+
+ # Custom API settings
+ self.input_cost_spin.setValue(0.002)
+ self.output_cost_spin.setValue(0.002)
+
+ # UI settings
+ self.font_scale_spin.setValue(1.0)
+ self.dpi_aware_cb.setChecked(True)
+ self.theme_combo.setCurrentText("Dark")
+ self.show_font_tip_cb.setChecked(True)
+
+ # Apply font scaling
+ self.apply_font_scaling(1.0)
+
+ def get_config(self):
+ """Get current configuration as dictionary."""
+ return {
+ "api": self.api_url_edit.text(),
+ "key": self.api_key_edit.text(),
+ "organization": self.organization_edit.text(),
+ "model": self.model_combo.currentText(),
+ "language": self.language_combo.currentText(),
+ "timeout": self.timeout_spin.value(),
+ "fileThreads": self.file_threads_spin.value(),
+ "threads": self.threads_spin.value(),
+ "batchsize": self.batch_size_spin.value(),
+ "frequency_penalty": self.frequency_penalty_spin.value(),
+ "width": self.width_spin.value(),
+ "listWidth": self.list_width_spin.value(),
+ "noteWidth": self.note_width_spin.value(),
+ "input_cost": self.input_cost_spin.value(),
+ "output_cost": self.output_cost_spin.value(),
+ "font_scale": self.font_scale_spin.value(),
+ "dpi_aware": self.dpi_aware_cb.isChecked(),
+ "theme": self.theme_combo.currentText(),
+ "show_font_tip": self.show_font_tip_cb.isChecked()
+ }
+
+ def validate(self):
+ """Validate the current configuration."""
+ errors = []
+
+ # Check required fields
+ if not self.api_key_edit.text().strip():
+ errors.append("API Key is required")
+
+ if not self.model_combo.currentText().strip():
+ errors.append("Model is required")
+
+ # Check numeric ranges
+ if self.timeout_spin.value() < 30:
+ errors.append("Timeout should be at least 30 seconds")
+
+ if self.threads_spin.value() > 10 and "gpt-4" in self.model_combo.currentText().lower():
+ errors.append("Too many threads for GPT-4 - recommended: 1-2")
+
+ if errors:
+ QMessageBox.warning(self, "Validation Errors", "\n".join(errors))
+ return False
+
+ return True
diff --git a/gui/file_manager.py b/gui/file_manager.py
new file mode 100644
index 0000000..868728d
--- /dev/null
+++ b/gui/file_manager.py
@@ -0,0 +1,405 @@
+"""
+File Manager - Handle input and output files
+"""
+
+from pathlib import Path
+from PyQt5.QtWidgets import (
+ QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem,
+ QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox,
+ QSplitter, QTextEdit, QProgressBar, QCheckBox
+)
+from PyQt5.QtCore import Qt, QThread, pyqtSignal
+import json
+
+
+class FileManager(QWidget):
+ """Widget for managing input and output files."""
+
+ files_changed = pyqtSignal()
+
+ def __init__(self):
+ super().__init__()
+ self.input_files = []
+ self.output_directory = Path("translated")
+ self.init_ui()
+ self.refresh_files()
+
+ def init_ui(self):
+ """Initialize the user interface."""
+ layout = QVBoxLayout()
+
+ # Create splitter for input and output sections
+ splitter = QSplitter(Qt.Horizontal)
+
+ # Input files section
+ input_group = self.create_input_section()
+ splitter.addWidget(input_group)
+
+ # Output files section
+ output_group = self.create_output_section()
+ splitter.addWidget(output_group)
+
+ splitter.setSizes([400, 400])
+ layout.addWidget(splitter)
+
+ # Progress section
+ progress_group = self.create_progress_section()
+ layout.addWidget(progress_group)
+
+ self.setLayout(layout)
+
+ def create_input_section(self):
+ """Create the input files section."""
+ group = QGroupBox("Input Files")
+ layout = QVBoxLayout()
+
+ # Buttons
+ button_layout = QHBoxLayout()
+
+ self.add_files_button = QPushButton("Add Files")
+ self.add_files_button.clicked.connect(self.add_input_files)
+
+ self.add_folder_button = QPushButton("Add Folder")
+ self.add_folder_button.clicked.connect(self.add_input_folder)
+
+ self.remove_files_button = QPushButton("Remove Selected")
+ self.remove_files_button.clicked.connect(self.remove_selected_files)
+
+ self.clear_files_button = QPushButton("Clear All")
+ self.clear_files_button.clicked.connect(self.clear_input_files)
+
+ button_layout.addWidget(self.add_files_button)
+ button_layout.addWidget(self.add_folder_button)
+ button_layout.addWidget(self.remove_files_button)
+ button_layout.addWidget(self.clear_files_button)
+
+ layout.addLayout(button_layout)
+
+ # File tree
+ self.input_tree = QTreeWidget()
+ self.input_tree.setHeaderLabels(["File", "Size", "Type"])
+ self.input_tree.itemSelectionChanged.connect(self.on_input_selection_changed)
+ layout.addWidget(self.input_tree)
+
+ # File info
+ self.input_info = QTextEdit()
+ self.input_info.setMaximumHeight(100)
+ self.input_info.setReadOnly(True)
+ layout.addWidget(self.input_info)
+
+ group.setLayout(layout)
+ return group
+
+ def create_output_section(self):
+ """Create the output files section."""
+ group = QGroupBox("Output Files")
+ layout = QVBoxLayout()
+
+ # Output directory selection
+ dir_layout = QHBoxLayout()
+
+ self.output_dir_label = QLabel(str(self.output_directory))
+ self.output_dir_label.setStyleSheet("border: 1px solid #555; padding: 5px;")
+
+ self.browse_output_button = QPushButton("Browse")
+ self.browse_output_button.clicked.connect(self.browse_output_directory)
+
+ dir_layout.addWidget(QLabel("Output Directory:"))
+ dir_layout.addWidget(self.output_dir_label)
+ dir_layout.addWidget(self.browse_output_button)
+
+ layout.addLayout(dir_layout)
+
+ # Output file tree
+ self.output_tree = QTreeWidget()
+ self.output_tree.setHeaderLabels(["File", "Size", "Modified"])
+ self.output_tree.itemSelectionChanged.connect(self.on_output_selection_changed)
+ layout.addWidget(self.output_tree)
+
+ # Output file preview
+ self.output_preview = QTextEdit()
+ self.output_preview.setMaximumHeight(100)
+ self.output_preview.setReadOnly(True)
+ layout.addWidget(self.output_preview)
+
+ # Output buttons
+ output_button_layout = QHBoxLayout()
+
+ self.open_output_button = QPushButton("Open in Editor")
+ self.open_output_button.clicked.connect(self.open_output_file)
+
+ self.delete_output_button = QPushButton("Delete Selected")
+ self.delete_output_button.clicked.connect(self.delete_output_file)
+
+ output_button_layout.addWidget(self.open_output_button)
+ output_button_layout.addWidget(self.delete_output_button)
+ output_button_layout.addStretch()
+
+ layout.addLayout(output_button_layout)
+
+ group.setLayout(layout)
+ return group
+
+ def create_progress_section(self):
+ """Create the progress monitoring section."""
+ group = QGroupBox("Translation Progress")
+ layout = QVBoxLayout()
+
+ # Progress bar
+ self.progress_bar = QProgressBar()
+ self.progress_bar.setVisible(False)
+ layout.addWidget(self.progress_bar)
+
+ # Status and options
+ status_layout = QHBoxLayout()
+
+ self.status_label = QLabel("Ready")
+ self.backup_cb = QCheckBox("Create Backups")
+ self.backup_cb.setChecked(True)
+
+ status_layout.addWidget(self.status_label)
+ status_layout.addStretch()
+ status_layout.addWidget(self.backup_cb)
+
+ layout.addLayout(status_layout)
+
+ group.setLayout(layout)
+ return group
+
+ def add_input_files(self):
+ """Add input files via dialog."""
+ files, _ = QFileDialog.getOpenFileNames(
+ self,
+ "Select Input Files",
+ "",
+ "JSON Files (*.json);;All Files (*)"
+ )
+
+ for file_path in files:
+ if file_path not in self.input_files:
+ self.input_files.append(file_path)
+
+ self.refresh_input_files()
+
+ def add_input_folder(self):
+ """Add all files from a folder."""
+ folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
+
+ if folder:
+ folder_path = Path(folder)
+ for file_path in folder_path.rglob("*.json"):
+ file_str = str(file_path)
+ if file_str not in self.input_files:
+ self.input_files.append(file_str)
+
+ self.refresh_input_files()
+
+ def remove_selected_files(self):
+ """Remove selected input files."""
+ selected_items = self.input_tree.selectedItems()
+ for item in selected_items:
+ file_path = item.text(0)
+ if file_path in self.input_files:
+ self.input_files.remove(file_path)
+
+ self.refresh_input_files()
+
+ def clear_input_files(self):
+ """Clear all input files."""
+ self.input_files.clear()
+ self.refresh_input_files()
+
+ def refresh_input_files(self):
+ """Refresh the input files display."""
+ self.input_tree.clear()
+
+ for file_path in self.input_files:
+ path_obj = Path(file_path)
+ if path_obj.exists():
+ size = path_obj.stat().st_size
+ file_type = path_obj.suffix
+
+ item = QTreeWidgetItem([
+ path_obj.name,
+ f"{size:,} bytes",
+ file_type
+ ])
+ item.setToolTip(0, str(path_obj))
+ self.input_tree.addTopLevelItem(item)
+ else:
+ # File doesn't exist - show in red
+ item = QTreeWidgetItem([
+ f"{path_obj.name} (Missing)",
+ "0 bytes",
+ "N/A"
+ ])
+ item.setBackground(0, Qt.red)
+ self.input_tree.addTopLevelItem(item)
+
+ # Update info
+ self.update_input_info()
+
+ def refresh_output_files(self):
+ """Refresh the output files display."""
+ self.output_tree.clear()
+
+ if self.output_directory.exists():
+ for file_path in self.output_directory.iterdir():
+ if file_path.is_file():
+ size = file_path.stat().st_size
+ modified = file_path.stat().st_mtime
+
+ import datetime
+ modified_str = datetime.datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M")
+
+ item = QTreeWidgetItem([
+ file_path.name,
+ f"{size:,} bytes",
+ modified_str
+ ])
+ item.setToolTip(0, str(file_path))
+ self.output_tree.addTopLevelItem(item)
+
+ def browse_output_directory(self):
+ """Browse for output directory."""
+ directory = QFileDialog.getExistingDirectory(
+ self, "Select Output Directory", str(self.output_directory)
+ )
+
+ if directory:
+ self.output_directory = Path(directory)
+ self.output_dir_label.setText(str(self.output_directory))
+ self.refresh_output_files()
+
+ def refresh_files(self):
+ """Refresh both input and output file displays."""
+ self.refresh_input_files()
+ self.refresh_output_files()
+
+ def on_input_selection_changed(self):
+ """Handle input file selection change."""
+ selected_items = self.input_tree.selectedItems()
+ if selected_items:
+ item = selected_items[0]
+ file_name = item.text(0)
+
+ # Find the full path
+ full_path = None
+ for file_path in self.input_files:
+ if Path(file_path).name == file_name:
+ full_path = file_path
+ break
+
+ if full_path and Path(full_path).exists():
+ try:
+ with open(full_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Show preview of file structure
+ if full_path.endswith('.json'):
+ data = json.loads(content)
+ if isinstance(data, dict):
+ keys = list(data.keys())[:10] # Show first 10 keys
+ preview = f"JSON Object with {len(data)} keys:\n"
+ preview += "\n".join(keys)
+ if len(data) > 10:
+ preview += f"\n... and {len(data) - 10} more"
+ elif isinstance(data, list):
+ preview = f"JSON Array with {len(data)} items"
+ else:
+ preview = f"JSON: {type(data).__name__}"
+ else:
+ preview = content[:500] + "..." if len(content) > 500 else content
+
+ self.input_info.setPlainText(preview)
+ except Exception as e:
+ self.input_info.setPlainText(f"Error reading file: {str(e)}")
+
+ def on_output_selection_changed(self):
+ """Handle output file selection change."""
+ selected_items = self.output_tree.selectedItems()
+ if selected_items:
+ item = selected_items[0]
+ file_name = item.text(0)
+ file_path = self.output_directory / file_name
+
+ if file_path.exists():
+ try:
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ preview = content[:500] + "..." if len(content) > 500 else content
+ self.output_preview.setPlainText(preview)
+ except Exception as e:
+ self.output_preview.setPlainText(f"Error reading file: {str(e)}")
+
+ def open_output_file(self):
+ """Open selected output file in external editor."""
+ selected_items = self.output_tree.selectedItems()
+ if selected_items:
+ item = selected_items[0]
+ file_name = item.text(0)
+ file_path = self.output_directory / file_name
+
+ if file_path.exists():
+ import subprocess
+ try:
+ subprocess.run(['notepad.exe', str(file_path)], check=True)
+ except subprocess.CalledProcessError:
+ QMessageBox.warning(self, "Warning", "Could not open file in external editor")
+
+ def delete_output_file(self):
+ """Delete selected output file."""
+ selected_items = self.output_tree.selectedItems()
+ if selected_items:
+ item = selected_items[0]
+ file_name = item.text(0)
+ file_path = self.output_directory / file_name
+
+ reply = QMessageBox.question(
+ self,
+ "Confirm Deletion",
+ f"Are you sure you want to delete '{file_name}'?",
+ QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No
+ )
+
+ if reply == QMessageBox.Yes and file_path.exists():
+ try:
+ file_path.unlink()
+ self.refresh_output_files()
+ except Exception as e:
+ QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
+
+ def update_input_info(self):
+ """Update the input files information."""
+ total_files = len(self.input_files)
+ total_size = 0
+
+ for file_path in self.input_files:
+ path_obj = Path(file_path)
+ if path_obj.exists():
+ total_size += path_obj.stat().st_size
+
+ info = f"Total Files: {total_files}\nTotal Size: {total_size:,} bytes"
+
+ # Count by file type
+ extensions = {}
+ for file_path in self.input_files:
+ ext = Path(file_path).suffix
+ extensions[ext] = extensions.get(ext, 0) + 1
+
+ if extensions:
+ info += "\n\nFile Types:"
+ for ext, count in sorted(extensions.items()):
+ info += f"\n{ext or 'No extension'}: {count}"
+
+ self.input_info.setPlainText(info)
+
+ def get_input_files(self):
+ """Get list of input files."""
+ return self.input_files.copy()
+
+ def get_output_directory(self):
+ """Get output directory path."""
+ return self.output_directory
diff --git a/gui/log_viewer.py b/gui/log_viewer.py
new file mode 100644
index 0000000..8985555
--- /dev/null
+++ b/gui/log_viewer.py
@@ -0,0 +1,149 @@
+"""
+Log Viewer - Real-time log display and monitoring
+"""
+
+from PyQt5.QtWidgets import (
+ QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QPushButton,
+ QLabel, QCheckBox, QComboBox, QGroupBox, QSplitter
+)
+from PyQt5.QtCore import Qt, QTimer, pyqtSignal
+from PyQt5.QtGui import QTextCursor, QFont
+from pathlib import Path
+import datetime
+
+
+class LogViewer(QWidget):
+ """Widget for viewing translation logs and monitoring progress."""
+
+ log_updated = pyqtSignal(str)
+
+ def __init__(self):
+ super().__init__()
+ self.log_files = {
+ "Translation History": "log/translationHistory.txt",
+ "Mismatch History": "log/mismatchHistory.txt"
+ }
+ self.auto_refresh_timer = QTimer()
+ self.auto_refresh_timer.timeout.connect(self.refresh_logs)
+ self.init_ui()
+
+ def init_ui(self):
+ """Initialize the user interface."""
+ layout = QVBoxLayout()
+
+ # Control panel
+ control_group = QGroupBox("Log Controls")
+ control_layout = QHBoxLayout()
+
+ # Log file selector
+ self.log_selector = QComboBox()
+ self.log_selector.addItems(list(self.log_files.keys()))
+ self.log_selector.currentTextChanged.connect(self.load_selected_log)
+ control_layout.addWidget(QLabel("Log File:"))
+ control_layout.addWidget(self.log_selector)
+
+ # Auto-refresh checkbox
+ self.auto_refresh_cb = QCheckBox("Auto Refresh")
+ self.auto_refresh_cb.toggled.connect(self.toggle_auto_refresh)
+ control_layout.addWidget(self.auto_refresh_cb)
+
+ # Manual refresh button
+ self.refresh_button = QPushButton("Refresh")
+ self.refresh_button.clicked.connect(self.refresh_logs)
+ control_layout.addWidget(self.refresh_button)
+
+ # Clear button
+ self.clear_button = QPushButton("Clear")
+ self.clear_button.clicked.connect(self.clear_log)
+ control_layout.addWidget(self.clear_button)
+
+ control_layout.addStretch()
+ control_group.setLayout(control_layout)
+ layout.addWidget(control_group)
+
+ # Log display area
+ self.log_display = QTextEdit()
+ self.log_display.setReadOnly(True)
+ self.log_display.setFont(QFont("Consolas", 10))
+ self.log_display.setStyleSheet("""
+ QTextEdit {
+ background-color: #1e1e1e;
+ color: #ffffff;
+ border: 1px solid #555555;
+ }
+ """)
+ layout.addWidget(self.log_display)
+
+ # Status bar
+ self.status_label = QLabel("Ready")
+ self.status_label.setStyleSheet("color: #cccccc; padding: 5px;")
+ layout.addWidget(self.status_label)
+
+ self.setLayout(layout)
+
+ # Initial load
+ self.load_selected_log()
+
+ def toggle_auto_refresh(self, enabled):
+ """Toggle auto-refresh functionality."""
+ if enabled:
+ self.auto_refresh_timer.start(2000) # Refresh every 2 seconds
+ self.status_label.setText("Auto-refresh enabled")
+ else:
+ self.auto_refresh_timer.stop()
+ self.status_label.setText("Auto-refresh disabled")
+
+ def load_selected_log(self):
+ """Load the currently selected log file."""
+ selected_log = self.log_selector.currentText()
+ if selected_log in self.log_files:
+ self.load_log_file(self.log_files[selected_log])
+
+ def load_log_file(self, file_path):
+ """Load content from a specific log file."""
+ try:
+ log_path = Path(file_path)
+ if log_path.exists():
+ with open(log_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+ self.log_display.setPlainText(content)
+
+ # Scroll to bottom
+ cursor = self.log_display.textCursor()
+ cursor.movePosition(QTextCursor.End)
+ self.log_display.setTextCursor(cursor)
+
+ # Update status
+ file_size = log_path.stat().st_size
+ self.status_label.setText(f"Loaded {log_path.name} ({file_size} bytes)")
+ else:
+ self.log_display.setPlainText(f"Log file not found: {file_path}")
+ self.status_label.setText("Log file not found")
+ except Exception as e:
+ self.log_display.setPlainText(f"Error loading log file: {str(e)}")
+ self.status_label.setText(f"Error: {str(e)}")
+
+ def refresh_logs(self):
+ """Refresh the current log display."""
+ self.load_selected_log()
+
+ def clear_log(self):
+ """Clear the log display."""
+ self.log_display.clear()
+ self.status_label.setText("Log cleared")
+
+ def append_log_message(self, message):
+ """Append a message to the log display."""
+ timestamp = datetime.datetime.now().strftime("%H:%M:%S")
+ formatted_message = f"[{timestamp}] {message}"
+
+ self.log_display.append(formatted_message)
+
+ # Scroll to bottom
+ cursor = self.log_display.textCursor()
+ cursor.movePosition(QTextCursor.End)
+ self.log_display.setTextCursor(cursor)
+
+ def get_current_log_content(self):
+ """Get the current log content."""
+ return self.log_display.toPlainText()
diff --git a/gui/main.py b/gui/main.py
new file mode 100644
index 0000000..579f2fc
--- /dev/null
+++ b/gui/main.py
@@ -0,0 +1,623 @@
+#!/usr/bin/env python3
+"""
+DazedMTLTool GUI - Main Application
+A PyQt-based graphical user interface for the DazedMTLTool translation system.
+"""
+
+import sys
+import os
+import json
+from pathlib import Path
+from PyQt5.QtWidgets import (
+ QApplication, QMainWindow, QTabWidget, QVBoxLayout, QHBoxLayout,
+ QWidget, QPushButton, QLabel, QFileDialog, QMessageBox, QProgressBar,
+ QTextEdit, QSplitter, QGroupBox, QStatusBar
+)
+from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
+from PyQt5.QtGui import QIcon, QFont, QPixmap
+
+# Import configuration widgets
+from gui.config_tab import ConfigTab
+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
+
+class DazedMTLGUI(QMainWindow):
+ """Main GUI window for the DazedMTLTool."""
+
+ def __init__(self):
+ super().__init__()
+ self.init_ui()
+ self.setup_status_bar()
+ self.setup_font_scaling()
+
+ def setup_font_scaling(self):
+ """Set up font scaling based on configuration."""
+ try:
+ from dotenv import load_dotenv
+ import os
+
+ # Load environment variables
+ load_dotenv()
+
+ # Get font scale setting
+ font_scale = float(os.getenv("font_scale", "1.0"))
+
+ # Apply font scaling
+ self.apply_font_scaling(font_scale)
+
+ except Exception as e:
+ print(f"Warning: Could not apply font scaling: {e}")
+
+ def apply_font_scaling(self, scale_factor):
+ """Apply font scaling to the entire application."""
+ try:
+ app = QApplication.instance()
+ if app:
+ # Get the default font
+ font = app.font()
+ base_size = 9 # Base font size
+ new_size = int(base_size * scale_factor)
+ font.setPointSize(new_size)
+ app.setFont(font)
+
+ # Also update window title to show scaling
+ if scale_factor != 1.0:
+ self.setWindowTitle(f"DazedMTLTool - Visual Translation Interface (Font: {scale_factor:.1f}x)")
+ else:
+ self.setWindowTitle("DazedMTLTool - Visual Translation Interface")
+
+ except Exception as e:
+ print(f"Warning: Could not apply font scaling: {e}")
+
+ def init_ui(self):
+ """Initialize the user interface."""
+ self.setWindowTitle("DazedMTLTool - Visual Translation Interface")
+ self.setGeometry(100, 100, 1200, 800)
+
+ # Set window icon if available
+ icon_path = Path("screens/tool.png")
+ if icon_path.exists():
+ self.setWindowIcon(QIcon(str(icon_path)))
+
+ # Create central widget and main layout
+ central_widget = QWidget()
+ self.setCentralWidget(central_widget)
+
+ # Create main splitter
+ main_splitter = QSplitter(Qt.Horizontal)
+
+ # Create tab widget
+ self.tab_widget = QTabWidget()
+ self.tab_widget.setTabPosition(QTabWidget.North)
+
+ # Add tabs
+ self.setup_tabs()
+
+ # Create log viewer
+ self.log_viewer = LogViewer()
+
+ # Add widgets to splitter
+ main_splitter.addWidget(self.tab_widget)
+ main_splitter.addWidget(self.log_viewer)
+ main_splitter.setSizes([800, 400])
+
+ # Set main layout
+ layout = QVBoxLayout()
+ layout.addWidget(main_splitter)
+ central_widget.setLayout(layout)
+
+ # Create menu bar
+ self.create_menu_bar()
+
+ def setup_tabs(self):
+ """Set up all the tabs in the interface."""
+ # Configuration Tab
+ self.config_tab = ConfigTab()
+ self.config_tab.config_changed.connect(self.on_config_changed)
+ self.tab_widget.addTab(self.config_tab, "Configuration")
+
+ # RPG Maker MV/MZ Tab
+ self.rpgmaker_tab = RPGMakerTab()
+ self.tab_widget.addTab(self.rpgmaker_tab, "RPG Maker MV/MZ")
+
+ # Other Modules Tab
+ 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
+ try:
+ config = self.config_tab.get_config()
+ font_scale = config.get("font_scale", 1.0)
+ self.apply_font_scaling(font_scale)
+ except Exception as e:
+ print(f"Warning: Could not apply configuration changes: {e}")
+
+ def set_font_scale(self, scale_factor):
+ """Set the font scale and update the configuration."""
+ try:
+ # Apply the scaling immediately
+ self.apply_font_scaling(scale_factor)
+
+ # Update the configuration tab
+ self.config_tab.font_scale_spin.setValue(scale_factor)
+
+ # Save to .env file
+ self.config_tab.save_to_env()
+
+ self.status_label.setText(f"Font scale set to {scale_factor:.1f}x")
+
+ except Exception as e:
+ QMessageBox.warning(self, "Warning", f"Failed to set font scale: {str(e)}")
+
+ def create_menu_bar(self):
+ """Create the menu bar."""
+ menubar = self.menuBar()
+
+ # File menu
+ file_menu = menubar.addMenu('File')
+
+ # Load project action
+ load_action = file_menu.addAction('Load Project')
+ load_action.triggered.connect(self.load_project)
+
+ # Save project action
+ save_action = file_menu.addAction('Save Project')
+ save_action.triggered.connect(self.save_project)
+
+ file_menu.addSeparator()
+
+ # Exit action
+ exit_action = file_menu.addAction('Exit')
+ exit_action.triggered.connect(self.close)
+
+ # Tools menu
+ tools_menu = menubar.addMenu('Tools')
+
+ # Font Size submenu
+ font_menu = tools_menu.addMenu('Font Size')
+
+ # Font size actions
+ small_font_action = font_menu.addAction('Small (0.8x)')
+ small_font_action.triggered.connect(lambda: self.set_font_scale(0.8))
+
+ normal_font_action = font_menu.addAction('Normal (1.0x)')
+ normal_font_action.triggered.connect(lambda: self.set_font_scale(1.0))
+
+ large_font_action = font_menu.addAction('Large (1.5x)')
+ large_font_action.triggered.connect(lambda: self.set_font_scale(1.5))
+
+ xlarge_font_action = font_menu.addAction('Extra Large (2.0x)')
+ xlarge_font_action.triggered.connect(lambda: self.set_font_scale(2.0))
+
+ tools_menu.addSeparator()
+
+ # Reset to defaults
+ reset_action = tools_menu.addAction('Reset to Defaults')
+ reset_action.triggered.connect(self.reset_to_defaults)
+
+ # Validate configuration
+ validate_action = tools_menu.addAction('Validate Configuration')
+ validate_action.triggered.connect(self.validate_configuration)
+
+ # Help menu
+ help_menu = menubar.addMenu('Help')
+
+ # About action
+ about_action = help_menu.addAction('About')
+ about_action.triggered.connect(self.show_about)
+
+ def setup_status_bar(self):
+ """Set up the status bar."""
+ self.status_bar = QStatusBar()
+ self.setStatusBar(self.status_bar)
+
+ # Add status labels
+ self.status_label = QLabel("Ready")
+ self.progress_bar = QProgressBar()
+ self.progress_bar.setVisible(False)
+
+ self.status_bar.addWidget(self.status_label)
+ self.status_bar.addPermanentWidget(self.progress_bar)
+
+ def load_project(self):
+ """Load a project configuration."""
+ file_path, _ = QFileDialog.getOpenFileName(
+ self,
+ "Load Project Configuration",
+ "",
+ "JSON Files (*.json);;All Files (*)"
+ )
+
+ if file_path:
+ try:
+ # Load configuration from file
+ self.config_tab.load_from_file(file_path)
+ self.rpgmaker_tab.load_from_file(file_path)
+ self.status_label.setText(f"Loaded project: {Path(file_path).name}")
+ except Exception as e:
+ QMessageBox.critical(self, "Error", f"Failed to load project:\n{str(e)}")
+
+ def save_project(self):
+ """Save the current project configuration."""
+ file_path, _ = QFileDialog.getSaveFileName(
+ self,
+ "Save Project Configuration",
+ "",
+ "JSON Files (*.json);;All Files (*)"
+ )
+
+ if file_path:
+ try:
+ # Save configuration to file
+ config_data = {}
+ config_data.update(self.config_tab.get_config())
+ config_data.update(self.rpgmaker_tab.get_config())
+
+ import json
+ with open(file_path, 'w', encoding='utf-8') as f:
+ json.dump(config_data, f, indent=2, ensure_ascii=False)
+
+ self.status_label.setText(f"Saved project: {Path(file_path).name}")
+ except Exception as e:
+ QMessageBox.critical(self, "Error", f"Failed to save project:\n{str(e)}")
+
+ def reset_to_defaults(self):
+ """Reset all configurations to default values."""
+ reply = QMessageBox.question(
+ self,
+ "Reset to Defaults",
+ "Are you sure you want to reset all settings to their default values?",
+ QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No
+ )
+
+ if reply == QMessageBox.Yes:
+ self.config_tab.reset_to_defaults()
+ self.rpgmaker_tab.reset_to_defaults()
+ self.status_label.setText("Reset to defaults")
+
+ def validate_configuration(self):
+ """Validate the current configuration."""
+ try:
+ # Validate configuration settings
+ config_valid = self.config_tab.validate()
+ rpgmaker_valid = self.rpgmaker_tab.validate()
+
+ if config_valid and rpgmaker_valid:
+ QMessageBox.information(self, "Validation", "Configuration is valid!")
+ self.status_label.setText("Configuration validated")
+ else:
+ QMessageBox.warning(self, "Validation", "Configuration has issues. Check the log for details.")
+
+ except Exception as e:
+ QMessageBox.critical(self, "Validation Error", f"Failed to validate configuration:\n{str(e)}")
+
+ def show_about(self):
+ """Show the about dialog."""
+ QMessageBox.about(
+ self,
+ "About DazedMTLTool GUI",
+ """
+
A visual interface for the DazedMTLTool translation system.
+This tool helps translate visual novels, RPG games, and other text-based content using AI translation services.
+Features:
+