Beginning of gui
This commit is contained in:
parent
8606a5c7b8
commit
b44633f13c
10 changed files with 2639 additions and 0 deletions
23
gui/__init__.py
Normal file
23
gui/__init__.py
Normal file
|
|
@ -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"
|
||||
]
|
||||
220
gui/config_integration.py
Normal file
220
gui/config_integration.py
Normal file
|
|
@ -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 <br> 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"
|
||||
}
|
||||
503
gui/config_tab.py
Normal file
503
gui/config_tab.py
Normal file
|
|
@ -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
|
||||
405
gui/file_manager.py
Normal file
405
gui/file_manager.py
Normal file
|
|
@ -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
|
||||
149
gui/log_viewer.py
Normal file
149
gui/log_viewer.py
Normal file
|
|
@ -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()
|
||||
623
gui/main.py
Normal file
623
gui/main.py
Normal file
|
|
@ -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",
|
||||
"""
|
||||
<h3>DazedMTLTool GUI</h3>
|
||||
<p>A visual interface for the DazedMTLTool translation system.</p>
|
||||
<p>This tool helps translate visual novels, RPG games, and other text-based content using AI translation services.</p>
|
||||
<p><b>Features:</b></p>
|
||||
<ul>
|
||||
<li>Visual configuration management</li>
|
||||
<li>Module-specific settings</li>
|
||||
<li>Real-time translation monitoring</li>
|
||||
<li>File management and organization</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
|
||||
def update_status(self, message):
|
||||
"""Update the status bar message."""
|
||||
self.status_label.setText(message)
|
||||
|
||||
def show_progress(self, show=True):
|
||||
"""Show or hide the progress bar."""
|
||||
self.progress_bar.setVisible(show)
|
||||
|
||||
def set_progress(self, value):
|
||||
"""Set the progress bar value (0-100)."""
|
||||
self.progress_bar.setValue(value)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the GUI application."""
|
||||
try:
|
||||
# Enable high DPI scaling before creating QApplication
|
||||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
||||
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
except Exception as e:
|
||||
print(f"Failed to create QApplication: {e}")
|
||||
print("Make sure PyQt5 is properly installed:")
|
||||
print(" pip install PyQt5>=5.15.0")
|
||||
return 1
|
||||
|
||||
# Set application properties
|
||||
app.setApplicationName("DazedMTLTool")
|
||||
app.setApplicationVersion("1.0")
|
||||
app.setOrganizationName("DazedTranslations")
|
||||
|
||||
# Apply dark theme (optional)
|
||||
app.setStyleSheet("""
|
||||
QMainWindow {
|
||||
background-color: #2b2b2b;
|
||||
color: #ffffff;
|
||||
}
|
||||
QWidget {
|
||||
background-color: #2b2b2b;
|
||||
color: #ffffff;
|
||||
}
|
||||
QTabWidget::pane {
|
||||
border: 1px solid #555555;
|
||||
background-color: #3c3c3c;
|
||||
}
|
||||
QTabBar::tab {
|
||||
background-color: #555555;
|
||||
color: #ffffff;
|
||||
padding: 8px 12px;
|
||||
margin-right: 2px;
|
||||
border: 1px solid #666666;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
background-color: #007acc;
|
||||
color: #ffffff;
|
||||
}
|
||||
QTabBar::tab:hover {
|
||||
background-color: #666666;
|
||||
color: #ffffff;
|
||||
}
|
||||
QGroupBox {
|
||||
font-weight: bold;
|
||||
border: 2px solid #555555;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0px;
|
||||
padding-top: 10px;
|
||||
color: #ffffff;
|
||||
background-color: #3c3c3c;
|
||||
}
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
left: 10px;
|
||||
padding: 0 5px 0 5px;
|
||||
color: #007acc;
|
||||
background-color: transparent;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #0078d4;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #106ebe;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
QCheckBox {
|
||||
color: #ffffff;
|
||||
spacing: 5px;
|
||||
background-color: transparent;
|
||||
}
|
||||
QCheckBox::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
QCheckBox::indicator:unchecked {
|
||||
background-color: #404040;
|
||||
border: 1px solid #555555;
|
||||
}
|
||||
QCheckBox::indicator:checked {
|
||||
background-color: #0078d4;
|
||||
border: 1px solid #0078d4;
|
||||
}
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: transparent;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
border: 1px solid #555555;
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border: 2px solid #007acc;
|
||||
}
|
||||
QSpinBox, QDoubleSpinBox {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
border: 1px solid #555555;
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
QSpinBox:focus, QDoubleSpinBox:focus {
|
||||
border: 2px solid #007acc;
|
||||
}
|
||||
QComboBox {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
border: 1px solid #555555;
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
QComboBox:focus {
|
||||
border: 2px solid #007acc;
|
||||
}
|
||||
QComboBox::drop-down {
|
||||
border: none;
|
||||
background-color: #555555;
|
||||
}
|
||||
QComboBox::down-arrow {
|
||||
image: none;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid #ffffff;
|
||||
}
|
||||
QComboBox QAbstractItemView {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
selection-background-color: #007acc;
|
||||
border: 1px solid #555555;
|
||||
}
|
||||
QTextEdit {
|
||||
background-color: #1e1e1e;
|
||||
color: #ffffff;
|
||||
border: 1px solid #555555;
|
||||
selection-background-color: #007acc;
|
||||
}
|
||||
QScrollArea {
|
||||
background-color: #2b2b2b;
|
||||
border: none;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
background-color: #404040;
|
||||
width: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background-color: #666666;
|
||||
border-radius: 6px;
|
||||
margin: 2px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background-color: #007acc;
|
||||
}
|
||||
QScrollBar:horizontal {
|
||||
background-color: #404040;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QScrollBar::handle:horizontal {
|
||||
background-color: #666666;
|
||||
border-radius: 6px;
|
||||
margin: 2px;
|
||||
}
|
||||
QScrollBar::handle:horizontal:hover {
|
||||
background-color: #007acc;
|
||||
}
|
||||
QTreeWidget {
|
||||
background-color: #1e1e1e;
|
||||
color: #ffffff;
|
||||
border: 1px solid #555555;
|
||||
selection-background-color: #007acc;
|
||||
}
|
||||
QTreeWidget::item {
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid #333333;
|
||||
}
|
||||
QTreeWidget::item:selected {
|
||||
background-color: #007acc;
|
||||
color: #ffffff;
|
||||
}
|
||||
QTreeWidget::item:hover {
|
||||
background-color: #404040;
|
||||
}
|
||||
QHeaderView::section {
|
||||
background-color: #555555;
|
||||
color: #ffffff;
|
||||
padding: 6px;
|
||||
border: 1px solid #666666;
|
||||
}
|
||||
QStatusBar {
|
||||
background-color: #2b2b2b;
|
||||
color: #ffffff;
|
||||
border-top: 1px solid #555555;
|
||||
}
|
||||
QProgressBar {
|
||||
background-color: #404040;
|
||||
border: 1px solid #555555;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #007acc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
QMenuBar {
|
||||
background-color: #2b2b2b;
|
||||
color: #ffffff;
|
||||
border-bottom: 1px solid #555555;
|
||||
}
|
||||
QMenuBar::item {
|
||||
padding: 6px 12px;
|
||||
background-color: transparent;
|
||||
}
|
||||
QMenuBar::item:selected {
|
||||
background-color: #007acc;
|
||||
}
|
||||
QMenu {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
border: 1px solid #555555;
|
||||
}
|
||||
QMenu::item {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
QMenu::item:selected {
|
||||
background-color: #007acc;
|
||||
}
|
||||
QFrame[frameShape="4"] {
|
||||
color: #555555;
|
||||
}
|
||||
QFrame[frameShape="5"] {
|
||||
color: #555555;
|
||||
}
|
||||
""")
|
||||
|
||||
try:
|
||||
# Create and show the main window
|
||||
window = DazedMTLGUI()
|
||||
window.show()
|
||||
|
||||
# Show font adjustment tip on first run (optional)
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
show_font_tip = os.getenv("show_font_tip", "true").lower() == "true"
|
||||
|
||||
if show_font_tip:
|
||||
QMessageBox.information(
|
||||
window,
|
||||
"Font Size Adjustment",
|
||||
"💡 Font too small?\n\n"
|
||||
"• Use Tools → Font Size menu for quick adjustment\n"
|
||||
"• Or go to Configuration tab → UI Settings\n"
|
||||
"• Try 'Large (1.5x)' or 'Extra Large (2.0x)' for high DPI displays\n\n"
|
||||
"This tip can be disabled in the Configuration tab."
|
||||
)
|
||||
|
||||
# Set flag to not show again
|
||||
from dotenv import set_key
|
||||
set_key(".env", "show_font_tip", "false")
|
||||
|
||||
except Exception:
|
||||
pass # Ignore if .env operations fail
|
||||
|
||||
# Start the application
|
||||
return app.exec_()
|
||||
except Exception as e:
|
||||
print(f"Error starting GUI: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
182
gui/other_modules_tab.py
Normal file
182
gui/other_modules_tab.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
"""
|
||||
Other Modules Tab - Configuration for other translation modules
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton,
|
||||
QGroupBox, QLabel, QScrollArea, QListWidget, QListWidgetItem,
|
||||
QMessageBox, QTextEdit, QFrame
|
||||
)
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
||||
|
||||
class OtherModulesTab(QWidget):
|
||||
"""Tab for configuring other translation modules."""
|
||||
|
||||
config_changed = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.modules_info = self.get_modules_info()
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
"""Initialize the user interface."""
|
||||
main_layout = QVBoxLayout()
|
||||
|
||||
# Create scroll area
|
||||
scroll_area = QScrollArea()
|
||||
scroll_content = QWidget()
|
||||
scroll_layout = QVBoxLayout()
|
||||
|
||||
# Title
|
||||
title_label = QLabel("Other Translation Modules")
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #007acc;")
|
||||
scroll_layout.addWidget(title_label)
|
||||
|
||||
# Description
|
||||
description_label = QLabel(
|
||||
"Configure settings for other game engines and file formats supported by DazedMTLTool."
|
||||
)
|
||||
description_label.setWordWrap(True)
|
||||
description_label.setStyleSheet("color: #cccccc; margin-bottom: 10px;")
|
||||
scroll_layout.addWidget(description_label)
|
||||
|
||||
# Create module groups
|
||||
for category, modules in self.modules_info.items():
|
||||
group = self.create_module_group(category, modules)
|
||||
scroll_layout.addWidget(group)
|
||||
|
||||
# Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
self.refresh_button = QPushButton("Refresh Modules")
|
||||
self.refresh_button.clicked.connect(self.refresh_modules)
|
||||
|
||||
self.enable_all_button = QPushButton("Enable All")
|
||||
self.enable_all_button.clicked.connect(self.enable_all_modules)
|
||||
|
||||
self.disable_all_button = QPushButton("Disable All")
|
||||
self.disable_all_button.clicked.connect(self.disable_all_modules)
|
||||
|
||||
button_layout.addWidget(self.refresh_button)
|
||||
button_layout.addWidget(self.enable_all_button)
|
||||
button_layout.addWidget(self.disable_all_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 get_modules_info(self):
|
||||
"""Get information about available modules."""
|
||||
return {
|
||||
"Visual Novel Engines": {
|
||||
"kirikiri": "Kirikiri engine games (.ks, .tjs files)",
|
||||
"renpy": "Ren'Py visual novels (.rpy files)",
|
||||
"tyrano": "TyranoBuilder games",
|
||||
"nscript": "NScripter games",
|
||||
"lune": "Lune engine games"
|
||||
},
|
||||
"RPG Engines": {
|
||||
"rpgmakerace": "RPG Maker Ace (legacy)",
|
||||
"rpgmakerplugin": "RPG Maker plugins",
|
||||
"unity": "Unity engine games",
|
||||
"wolf": "Wolf RPG Editor games",
|
||||
"wolf2": "Wolf RPG Editor 2 games"
|
||||
},
|
||||
"File Formats": {
|
||||
"json": "Generic JSON files",
|
||||
"csv": "CSV/Excel files",
|
||||
"text": "Plain text files",
|
||||
"regex": "Custom regex patterns",
|
||||
"images": "Image text extraction"
|
||||
}
|
||||
}
|
||||
|
||||
def create_module_group(self, category, modules):
|
||||
"""Create a group for a category of modules."""
|
||||
group = QGroupBox(category)
|
||||
layout = QVBoxLayout()
|
||||
|
||||
for module_name, description in modules.items():
|
||||
# Check if module file exists
|
||||
module_path = Path(f"modules/{module_name}.py")
|
||||
exists = module_path.exists()
|
||||
|
||||
checkbox = QCheckBox(f"{module_name.upper()}")
|
||||
checkbox.setEnabled(exists)
|
||||
checkbox.setToolTip(description)
|
||||
|
||||
if not exists:
|
||||
checkbox.setText(f"{module_name.upper()} (Not Found)")
|
||||
checkbox.setStyleSheet("color: #ff6b6b;")
|
||||
|
||||
# Add description label
|
||||
desc_label = QLabel(description)
|
||||
desc_label.setStyleSheet("color: #aaaaaa; font-size: 11px; margin-left: 20px;")
|
||||
desc_label.setWordWrap(True)
|
||||
|
||||
layout.addWidget(checkbox)
|
||||
layout.addWidget(desc_label)
|
||||
|
||||
# Store reference for later use
|
||||
setattr(self, f"{module_name}_cb", checkbox)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def refresh_modules(self):
|
||||
"""Refresh the modules list."""
|
||||
# Clear current layout and rebuild
|
||||
self.modules_info = self.get_modules_info()
|
||||
|
||||
# Find all Python files in modules directory
|
||||
modules_dir = Path("modules")
|
||||
if modules_dir.exists():
|
||||
found_modules = [f.stem for f in modules_dir.glob("*.py") if f.stem != "__init__"]
|
||||
|
||||
info_text = f"Found {len(found_modules)} module files:\n"
|
||||
info_text += ", ".join(sorted(found_modules))
|
||||
|
||||
QMessageBox.information(self, "Modules Refreshed", info_text)
|
||||
else:
|
||||
QMessageBox.warning(self, "Warning", "Modules directory not found!")
|
||||
|
||||
def enable_all_modules(self):
|
||||
"""Enable all available modules."""
|
||||
for category, modules in self.modules_info.items():
|
||||
for module_name in modules.keys():
|
||||
checkbox = getattr(self, f"{module_name}_cb", None)
|
||||
if checkbox and checkbox.isEnabled():
|
||||
checkbox.setChecked(True)
|
||||
|
||||
def disable_all_modules(self):
|
||||
"""Disable all modules."""
|
||||
for category, modules in self.modules_info.items():
|
||||
for module_name in modules.keys():
|
||||
checkbox = getattr(self, f"{module_name}_cb", None)
|
||||
if checkbox:
|
||||
checkbox.setChecked(False)
|
||||
|
||||
def get_enabled_modules(self):
|
||||
"""Get list of enabled modules."""
|
||||
enabled = []
|
||||
for category, modules in self.modules_info.items():
|
||||
for module_name in modules.keys():
|
||||
checkbox = getattr(self, f"{module_name}_cb", None)
|
||||
if checkbox and checkbox.isChecked():
|
||||
enabled.append(module_name)
|
||||
return enabled
|
||||
|
||||
def get_config(self):
|
||||
"""Get current configuration."""
|
||||
return {
|
||||
"enabled_modules": self.get_enabled_modules()
|
||||
}
|
||||
473
gui/rpgmaker_tab.py
Normal file
473
gui/rpgmaker_tab.py
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
"""
|
||||
RPG Maker MV/MZ Tab - Configuration for RPG Maker specific settings
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QCheckBox,
|
||||
QPushButton, QGroupBox, QLabel, QMessageBox, QScrollArea,
|
||||
QTextEdit, QSpinBox, QFrame
|
||||
)
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
|
||||
try:
|
||||
from .config_integration import ConfigIntegration
|
||||
except ImportError:
|
||||
# Fallback if config_integration is not available
|
||||
ConfigIntegration = None
|
||||
|
||||
|
||||
class RPGMakerTab(QWidget):
|
||||
"""RPG Maker MV/MZ configuration tab."""
|
||||
|
||||
config_changed = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.config_integration = ConfigIntegration() if ConfigIntegration else None
|
||||
self.init_ui()
|
||||
self.reset_to_defaults()
|
||||
|
||||
def init_ui(self):
|
||||
"""Initialize the user interface."""
|
||||
main_layout = QVBoxLayout()
|
||||
|
||||
# Create scroll area
|
||||
scroll_area = QScrollArea()
|
||||
scroll_content = QWidget()
|
||||
scroll_layout = QVBoxLayout()
|
||||
|
||||
# Title and description
|
||||
title_label = QLabel("RPG Maker MV/MZ Translation Settings")
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #007acc;")
|
||||
scroll_layout.addWidget(title_label)
|
||||
|
||||
description_label = QLabel(
|
||||
"Configure which RPG Maker event codes to translate and additional options. "
|
||||
"Each code represents a specific type of game content."
|
||||
)
|
||||
description_label.setWordWrap(True)
|
||||
description_label.setStyleSheet("color: #cccccc; margin-bottom: 10px;")
|
||||
scroll_layout.addWidget(description_label)
|
||||
|
||||
# General Config Group
|
||||
general_group = self.create_general_config_group()
|
||||
scroll_layout.addWidget(general_group)
|
||||
|
||||
# Main Dialogue Codes Group
|
||||
dialogue_group = self.create_dialogue_codes_group()
|
||||
scroll_layout.addWidget(dialogue_group)
|
||||
|
||||
# Optional Codes Group
|
||||
optional_group = self.create_optional_codes_group()
|
||||
scroll_layout.addWidget(optional_group)
|
||||
|
||||
# Variable Codes Group
|
||||
variable_group = self.create_variable_codes_group()
|
||||
scroll_layout.addWidget(variable_group)
|
||||
|
||||
# Other Codes Group
|
||||
other_group = self.create_other_codes_group()
|
||||
scroll_layout.addWidget(other_group)
|
||||
|
||||
# Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
self.reset_button = QPushButton("Reset to Defaults")
|
||||
self.reset_button.clicked.connect(self.reset_to_defaults)
|
||||
|
||||
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)
|
||||
|
||||
# Add separator
|
||||
separator = QFrame()
|
||||
separator.setFrameShape(QFrame.HLine)
|
||||
separator.setFrameShadow(QFrame.Sunken)
|
||||
scroll_layout.addWidget(separator)
|
||||
|
||||
# Add note about performance
|
||||
note_text = QTextEdit()
|
||||
note_text.setMaximumHeight(100)
|
||||
note_text.setReadOnly(True)
|
||||
note_text.setHtml("""
|
||||
<b>Performance Notes:</b><br>
|
||||
• Enable only the codes you need to reduce translation time and costs<br>
|
||||
• CODE408 can significantly increase costs as it translates comments<br>
|
||||
• Main dialogue codes (401, 405, 102) are typically required for story content<br>
|
||||
• Test with a small file first to verify settings work correctly
|
||||
""")
|
||||
scroll_layout.addWidget(note_text)
|
||||
|
||||
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_general_config_group(self):
|
||||
"""Create general configuration group."""
|
||||
group = QGroupBox("General Configuration")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# FIRSTLINESPEAKERS
|
||||
self.first_line_speakers_cb = QCheckBox("First Line Speakers")
|
||||
self.first_line_speakers_cb.setToolTip(
|
||||
"If the first line of event code 401 contains a speaker name, enable this option"
|
||||
)
|
||||
layout.addWidget(self.first_line_speakers_cb)
|
||||
|
||||
# FACENAME101
|
||||
self.facename_101_cb = QCheckBox("Find Speakers in 101 Codes based on Face Name")
|
||||
self.facename_101_cb.setToolTip(
|
||||
"Use character face names to identify speakers in event code 101"
|
||||
)
|
||||
layout.addWidget(self.facename_101_cb)
|
||||
|
||||
# NAMES
|
||||
self.names_cb = QCheckBox("Output Character Names List")
|
||||
self.names_cb.setToolTip(
|
||||
"Generate a list of all character names found during translation"
|
||||
)
|
||||
layout.addWidget(self.names_cb)
|
||||
|
||||
# BRFLAG
|
||||
self.br_flag_cb = QCheckBox("Use <br> Tags")
|
||||
self.br_flag_cb.setToolTip(
|
||||
"Use <br> HTML tags instead of newlines for line breaks"
|
||||
)
|
||||
layout.addWidget(self.br_flag_cb)
|
||||
|
||||
# FIXTEXTWRAP
|
||||
self.fix_textwrap_cb = QCheckBox("Fix Text Wrapping")
|
||||
self.fix_textwrap_cb.setToolTip(
|
||||
"Automatically fix text wrapping for better display"
|
||||
)
|
||||
layout.addWidget(self.fix_textwrap_cb)
|
||||
|
||||
# IGNORETLTEXT
|
||||
self.ignore_tl_text_cb = QCheckBox("Ignore Already Translated Text")
|
||||
self.ignore_tl_text_cb.setToolTip(
|
||||
"Skip text that appears to already be translated"
|
||||
)
|
||||
layout.addWidget(self.ignore_tl_text_cb)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_dialogue_codes_group(self):
|
||||
"""Create dialogue codes group."""
|
||||
group = QGroupBox("Main Dialogue Codes (Recommended)")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# CODE401
|
||||
self.code_401_cb = QCheckBox("CODE 401 - Show Text")
|
||||
self.code_401_cb.setToolTip(
|
||||
"Translate dialogue text displayed in message windows. Essential for story content."
|
||||
)
|
||||
layout.addWidget(self.code_401_cb)
|
||||
|
||||
# CODE405
|
||||
self.code_405_cb = QCheckBox("CODE 405 - Show Text (Scrolling)")
|
||||
self.code_405_cb.setToolTip(
|
||||
"Translate scrolling text messages. Used for longer dialogue passages."
|
||||
)
|
||||
layout.addWidget(self.code_405_cb)
|
||||
|
||||
# CODE102
|
||||
self.code_102_cb = QCheckBox("CODE 102 - Show Choices")
|
||||
self.code_102_cb.setToolTip(
|
||||
"Translate choice options presented to the player."
|
||||
)
|
||||
layout.addWidget(self.code_102_cb)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_optional_codes_group(self):
|
||||
"""Create optional codes group."""
|
||||
group = QGroupBox("Optional Codes")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# CODE101
|
||||
self.code_101_cb = QCheckBox("CODE 101 - Character Names")
|
||||
self.code_101_cb.setToolTip(
|
||||
"Translate character names. Enable only when names are stored in code 101."
|
||||
)
|
||||
layout.addWidget(self.code_101_cb)
|
||||
|
||||
# CODE408
|
||||
self.code_408_cb = QCheckBox("CODE 408 - Comments (Warning: High Cost)")
|
||||
self.code_408_cb.setToolTip(
|
||||
"Translate comment text. WARNING: This can significantly increase translation costs!"
|
||||
)
|
||||
self.code_408_cb.setStyleSheet("QCheckBox { color: #ff6b6b; }")
|
||||
layout.addWidget(self.code_408_cb)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_variable_codes_group(self):
|
||||
"""Create variable codes group."""
|
||||
group = QGroupBox("Variable Codes")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# CODE122
|
||||
self.code_122_cb = QCheckBox("CODE 122 - Control Variables")
|
||||
self.code_122_cb.setToolTip(
|
||||
"Translate text stored in game variables."
|
||||
)
|
||||
layout.addWidget(self.code_122_cb)
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
def create_other_codes_group(self):
|
||||
"""Create other codes group."""
|
||||
group = QGroupBox("Other Event Codes")
|
||||
layout = QVBoxLayout()
|
||||
|
||||
codes_info = [
|
||||
("355655", "Scripts", "Translate text within script commands"),
|
||||
("357", "Picture Text", "Translate text displayed on pictures"),
|
||||
("657", "Picture Text Extended", "Extended picture text translation"),
|
||||
("356", "Plugin Commands", "Translate plugin command parameters"),
|
||||
("320", "Change Name Input", "Translate name input prompts"),
|
||||
("324", "Change Nickname", "Translate nickname changes"),
|
||||
("111", "Conditional Branch", "Translate conditional text"),
|
||||
("108", "Comments", "Translate comment blocks")
|
||||
]
|
||||
|
||||
self.other_code_checkboxes = {}
|
||||
|
||||
for code, name, tooltip in codes_info:
|
||||
cb = QCheckBox(f"CODE {code} - {name}")
|
||||
cb.setToolTip(tooltip)
|
||||
layout.addWidget(cb)
|
||||
self.other_code_checkboxes[code] = cb
|
||||
|
||||
group.setLayout(layout)
|
||||
return group
|
||||
|
||||
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)
|
||||
|
||||
# Main dialogue codes (enabled by default)
|
||||
self.code_401_cb.setChecked(True)
|
||||
self.code_405_cb.setChecked(True)
|
||||
self.code_102_cb.setChecked(True)
|
||||
|
||||
# Optional codes (disabled by default)
|
||||
self.code_101_cb.setChecked(False)
|
||||
self.code_408_cb.setChecked(False)
|
||||
|
||||
# Variable codes (disabled by default)
|
||||
self.code_122_cb.setChecked(False)
|
||||
|
||||
# Other codes (all disabled by default)
|
||||
for cb in self.other_code_checkboxes.values():
|
||||
cb.setChecked(False)
|
||||
|
||||
def get_config(self):
|
||||
"""Get current configuration as dictionary."""
|
||||
config = {
|
||||
# General settings
|
||||
"FIRSTLINESPEAKERS": self.first_line_speakers_cb.isChecked(),
|
||||
"FACENAME101": self.facename_101_cb.isChecked(),
|
||||
"NAMES": self.names_cb.isChecked(),
|
||||
"BRFLAG": self.br_flag_cb.isChecked(),
|
||||
"FIXTEXTWRAP": self.fix_textwrap_cb.isChecked(),
|
||||
"IGNORETLTEXT": self.ignore_tl_text_cb.isChecked(),
|
||||
|
||||
# Main dialogue codes
|
||||
"CODE401": self.code_401_cb.isChecked(),
|
||||
"CODE405": self.code_405_cb.isChecked(),
|
||||
"CODE102": self.code_102_cb.isChecked(),
|
||||
|
||||
# Optional codes
|
||||
"CODE101": self.code_101_cb.isChecked(),
|
||||
"CODE408": self.code_408_cb.isChecked(),
|
||||
|
||||
# Variable codes
|
||||
"CODE122": self.code_122_cb.isChecked(),
|
||||
}
|
||||
|
||||
# Other codes
|
||||
for code, cb in self.other_code_checkboxes.items():
|
||||
config[f"CODE{code}"] = cb.isChecked()
|
||||
|
||||
return config
|
||||
|
||||
def set_config(self, config):
|
||||
"""Set configuration from dictionary."""
|
||||
# General settings
|
||||
self.first_line_speakers_cb.setChecked(config.get("FIRSTLINESPEAKERS", False))
|
||||
self.facename_101_cb.setChecked(config.get("FACENAME101", False))
|
||||
self.names_cb.setChecked(config.get("NAMES", False))
|
||||
self.br_flag_cb.setChecked(config.get("BRFLAG", False))
|
||||
self.fix_textwrap_cb.setChecked(config.get("FIXTEXTWRAP", True))
|
||||
self.ignore_tl_text_cb.setChecked(config.get("IGNORETLTEXT", False))
|
||||
|
||||
# Main dialogue codes
|
||||
self.code_401_cb.setChecked(config.get("CODE401", True))
|
||||
self.code_405_cb.setChecked(config.get("CODE405", True))
|
||||
self.code_102_cb.setChecked(config.get("CODE102", True))
|
||||
|
||||
# Optional codes
|
||||
self.code_101_cb.setChecked(config.get("CODE101", False))
|
||||
self.code_408_cb.setChecked(config.get("CODE408", False))
|
||||
|
||||
# Variable codes
|
||||
self.code_122_cb.setChecked(config.get("CODE122", False))
|
||||
|
||||
# Other codes
|
||||
for code, cb in self.other_code_checkboxes.items():
|
||||
cb.setChecked(config.get(f"CODE{code}", False))
|
||||
|
||||
def load_from_file(self, file_path):
|
||||
"""Load configuration from file."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
self.set_config(config)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Failed to load RPG Maker config:\n{str(e)}")
|
||||
|
||||
def validate(self):
|
||||
"""Validate current configuration."""
|
||||
warnings = []
|
||||
errors = []
|
||||
|
||||
# Check if any main dialogue codes are enabled
|
||||
main_codes_enabled = (
|
||||
self.code_401_cb.isChecked() or
|
||||
self.code_405_cb.isChecked() or
|
||||
self.code_102_cb.isChecked()
|
||||
)
|
||||
|
||||
if not main_codes_enabled:
|
||||
warnings.append("No main dialogue codes are enabled. You may not get any translated content.")
|
||||
|
||||
# Check for high-cost options
|
||||
if self.code_408_cb.isChecked():
|
||||
warnings.append("CODE 408 (Comments) is enabled. This can significantly increase translation costs!")
|
||||
|
||||
# Check for conflicting options
|
||||
if self.br_flag_cb.isChecked() and self.fix_textwrap_cb.isChecked():
|
||||
warnings.append("Both BR tags and text wrapping are enabled. This might cause formatting issues.")
|
||||
|
||||
return len(errors) == 0, warnings, errors
|
||||
|
||||
def validate_and_show_report(self):
|
||||
"""Validate and show a detailed report."""
|
||||
is_valid, warnings, errors = self.validate()
|
||||
|
||||
report = []
|
||||
|
||||
if errors:
|
||||
report.append("<b>Errors:</b>")
|
||||
for error in errors:
|
||||
report.append(f"• {error}")
|
||||
report.append("")
|
||||
|
||||
if warnings:
|
||||
report.append("<b>Warnings:</b>")
|
||||
for warning in warnings:
|
||||
report.append(f"• {warning}")
|
||||
report.append("")
|
||||
|
||||
# Add enabled codes summary
|
||||
enabled_codes = []
|
||||
config = self.get_config()
|
||||
for key, value in config.items():
|
||||
if key.startswith("CODE") and value:
|
||||
enabled_codes.append(key)
|
||||
|
||||
if enabled_codes:
|
||||
report.append("<b>Enabled Codes:</b>")
|
||||
report.append(", ".join(enabled_codes))
|
||||
else:
|
||||
report.append("<b>No codes enabled!</b>")
|
||||
|
||||
if not report:
|
||||
report.append("Configuration is valid with no issues.")
|
||||
|
||||
msg_box = QMessageBox(self)
|
||||
msg_box.setWindowTitle("Validation Report")
|
||||
msg_box.setTextFormat(Qt.RichText)
|
||||
msg_box.setText("<br>".join(report))
|
||||
|
||||
if errors:
|
||||
msg_box.setIcon(QMessageBox.Critical)
|
||||
elif warnings:
|
||||
msg_box.setIcon(QMessageBox.Warning)
|
||||
else:
|
||||
msg_box.setIcon(QMessageBox.Information)
|
||||
|
||||
msg_box.exec_()
|
||||
|
||||
return is_valid
|
||||
|
||||
def apply_to_module(self):
|
||||
"""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:
|
||||
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)}")
|
||||
|
||||
def load_from_module(self):
|
||||
"""Load configuration from the actual module file."""
|
||||
if not self.config_integration:
|
||||
return
|
||||
|
||||
try:
|
||||
config = self.config_integration.read_current_config()
|
||||
if config:
|
||||
self.set_config(config)
|
||||
QMessageBox.information(self, "Success", "Configuration loaded from module file")
|
||||
else:
|
||||
QMessageBox.warning(self, "Warning", "No configuration found in module file")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Failed to load from module:\n{str(e)}")
|
||||
|
|
@ -6,3 +6,4 @@ ruamel.yaml==0.17.32
|
|||
tiktoken==0.8.0
|
||||
tqdm==4.65.0
|
||||
pillow==10.4.0
|
||||
PyQt5>=5.15.0
|
||||
|
|
|
|||
60
start_gui.py
Normal file
60
start_gui.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Launch script for DazedMTLTool GUI
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add the project root to Python path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
def check_dependencies():
|
||||
"""Check if required dependencies are installed."""
|
||||
missing_deps = []
|
||||
|
||||
try:
|
||||
import PyQt5
|
||||
except ImportError:
|
||||
missing_deps.append("PyQt5")
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
missing_deps.append("python-dotenv")
|
||||
|
||||
if missing_deps:
|
||||
print("Missing dependencies:")
|
||||
for dep in missing_deps:
|
||||
print(f" - {dep}")
|
||||
print("\nPlease install them using:")
|
||||
print(" pip install -r requirements_gui.txt")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
print("DazedMTLTool GUI Launcher")
|
||||
print("=" * 40)
|
||||
|
||||
# Check dependencies
|
||||
if not check_dependencies():
|
||||
sys.exit(1)
|
||||
|
||||
# Import and run GUI
|
||||
try:
|
||||
from gui.main import main as gui_main
|
||||
gui_main()
|
||||
except ImportError as e:
|
||||
print(f"Error importing GUI modules: {e}")
|
||||
print("Make sure all GUI files are in the 'gui' directory")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error starting GUI: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue