""" Lemontropia Suite - TGA Icon Converter Dialog UI for converting Entropia Universe cached .tga icons to PNG. """ import logging from pathlib import Path from typing import Optional from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QGroupBox, QProgressBar, QTextEdit, QFileDialog, QMessageBox, QSpinBox, QCheckBox, QComboBox ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QPixmap from modules.tga_converter import TGAConverter logger = logging.getLogger(__name__) class TGAConvertWorker(QThread): """Background worker for TGA conversion.""" progress_update = pyqtSignal(str) file_converted = pyqtSignal(str, str) # tga_name, output_path conversion_complete = pyqtSignal(int, int) # success_count, total_count conversion_error = pyqtSignal(str) def __init__(self, converter: TGAConverter, cache_path: Optional[Path] = None, canvas_size=None, upscale: bool = False, upscale_method: str = 'nearest'): super().__init__() self.converter = converter self.cache_path = cache_path self.canvas_size = canvas_size self.upscale = upscale self.upscale_method = upscale_method self._is_running = True def run(self): """Run the conversion.""" try: # Use provided cache path or find it if self.cache_path and self.cache_path.exists(): cache_path = self.cache_path self.progress_update.emit(f"Using cache folder: {cache_path}") else: self.progress_update.emit("Finding cache folder...") cache_path = self.converter.find_cache_folder() if not cache_path: self.conversion_error.emit("Cache folder not found") return # Get list of TGA files tga_files = list(cache_path.glob("*.tga")) total = len(tga_files) success = 0 canvas_info = f" ({self.canvas_size[0]}x{self.canvas_size[1]} canvas)" if self.canvas_size else "" self.progress_update.emit(f"Found {total} TGA files to convert{canvas_info}") for i, tga_path in enumerate(tga_files): if not self._is_running: break self.progress_update.emit(f"[{i+1}/{total}] Converting: {tga_path.name}") output_path = self.converter.convert_tga_to_png( tga_path, canvas_size=self.canvas_size, upscale=self.upscale, upscale_method=self.upscale_method ) if output_path: success += 1 self.file_converted.emit(tga_path.name, str(output_path)) self.conversion_complete.emit(success, total) except Exception as e: self.conversion_error.emit(str(e)) def stop(self): """Stop the conversion.""" self._is_running = False class TGAConverterDialog(QDialog): """ Dialog for converting Entropia Universe TGA icons to PNG. """ def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("🔧 TGA Icon Converter") self.setMinimumSize(700, 600) self.resize(800, 700) self.converter = TGAConverter() self.convert_worker: Optional[TGAConvertWorker] = None self.converted_files = [] self._setup_ui() self._apply_dark_theme() # Auto-scan on open self._scan_cache() def _setup_ui(self): """Setup the dialog UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) layout.setSpacing(10) # Header header = QLabel("🔧 TGA Icon Converter") header.setStyleSheet("font-size: 18px; font-weight: bold; color: #4caf50;") layout.addWidget(header) desc = QLabel( "Convert Entropia Universe cached .tga icon files to PNG format.\n" "The game stores item icons in the cache folder as .tga files.\n" "Icons are centered on a canvas of your chosen size with transparent background." ) desc.setStyleSheet("color: #888; padding: 5px;") desc.setWordWrap(True) layout.addWidget(desc) # Cache folder info cache_group = QGroupBox("📁 Cache Folder") cache_layout = QVBoxLayout(cache_group) self.cache_path_label = QLabel("Searching...") self.cache_path_label.setStyleSheet("font-family: Consolas; color: #888;") cache_layout.addWidget(self.cache_path_label) cache_buttons = QHBoxLayout() self.scan_btn = QPushButton("🔍 Scan Cache") self.scan_btn.clicked.connect(self._scan_cache) cache_buttons.addWidget(self.scan_btn) browse_cache_btn = QPushButton("Browse...") browse_cache_btn.clicked.connect(self._browse_cache_folder) cache_buttons.addWidget(browse_cache_btn) cache_buttons.addStretch() cache_layout.addLayout(cache_buttons) layout.addWidget(cache_group) # TGA Files list files_group = QGroupBox("📄 TGA Files Found") files_layout = QVBoxLayout(files_group) self.files_count_label = QLabel("No files found") files_layout.addWidget(self.files_count_label) self.files_list = QListWidget() self.files_list.setMaximumHeight(150) files_layout.addWidget(self.files_list) layout.addWidget(files_group) # Output settings output_group = QGroupBox("💾 Output Settings") output_layout = QHBoxLayout(output_group) output_layout.addWidget(QLabel("Output folder:")) self.output_path_label = QLabel(str(self.converter.output_dir)) self.output_path_label.setStyleSheet("font-family: Consolas; color: #888;") output_layout.addWidget(self.output_path_label, 1) output_browse = QPushButton("Browse...") output_browse.clicked.connect(self._browse_output_folder) output_layout.addWidget(output_browse) layout.addWidget(output_group) # Canvas size settings canvas_group = QGroupBox("🖼️ Canvas Size") canvas_layout = QHBoxLayout(canvas_group) canvas_layout.addWidget(QLabel("Size:")) self.canvas_combo = QComboBox() self.canvas_combo.addItem("Original (no canvas)", None) self.canvas_combo.addItem("64x64", (64, 64)) self.canvas_combo.addItem("128x128", (128, 128)) self.canvas_combo.addItem("256x256", (256, 256)) self.canvas_combo.addItem("280x280", (280, 280)) self.canvas_combo.addItem("320x320", (320, 320)) self.canvas_combo.addItem("512x512", (512, 512)) self.canvas_combo.setCurrentIndex(4) # Default to 320x320 canvas_layout.addWidget(self.canvas_combo) canvas_layout.addSpacing(20) canvas_layout.addWidget(QLabel("Upscale:")) self.upscale_method_combo = QComboBox() self.upscale_method_combo.addItem("Sharp Pixels (NEAREST)", "nearest") self.upscale_method_combo.addItem("Smooth (HQ4x-style)", "hq4x") self.upscale_method_combo.addItem("Photorealistic (LANCZOS)", "lanczos") self.upscale_method_combo.setToolTip( "NEAREST: Sharp pixel edges (best for pixel art)\n" "HQ4x: Smooth but keeps details (best for game icons)\n" "LANCZOS: Very smooth (best for photos)" ) canvas_layout.addWidget(self.upscale_method_combo) canvas_layout.addStretch() layout.addWidget(canvas_group) # Convert button self.convert_btn = QPushButton("🚀 Convert All to PNG") self.convert_btn.setMinimumHeight(50) self.convert_btn.setStyleSheet(""" QPushButton { background-color: #0d47a1; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #1565c0; } QPushButton:disabled { background-color: #333; } """) self.convert_btn.clicked.connect(self._start_conversion) layout.addWidget(self.convert_btn) # Progress self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 0) # Indeterminate self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) self.status_label = QLabel("Ready") self.status_label.setStyleSheet("color: #888; padding: 5px;") layout.addWidget(self.status_label) # Results results_group = QGroupBox("✅ Converted Files") results_layout = QVBoxLayout(results_group) self.results_list = QListWidget() results_layout.addWidget(self.results_list) results_buttons = QHBoxLayout() open_folder_btn = QPushButton("📂 Open Output Folder") open_folder_btn.clicked.connect(self._open_output_folder) results_buttons.addWidget(open_folder_btn) results_buttons.addStretch() results_layout.addLayout(results_buttons) layout.addWidget(results_group) # Close button close_btn = QPushButton("Close") close_btn.clicked.connect(self.accept) layout.addWidget(close_btn) def _scan_cache(self): """Scan for TGA files in cache.""" self.files_list.clear() self.files_count_label.setText("Scanning...") # Use manually set path if available, otherwise find it if self.converter._cache_path and self.converter._cache_path.exists(): cache_path = self.converter._cache_path else: cache_path = self.converter.find_cache_folder() if cache_path: self.cache_path_label.setText(str(cache_path)) tga_files = list(cache_path.glob("*.tga")) self.files_count_label.setText(f"Found {len(tga_files)} TGA files") for tga_file in tga_files: item = QListWidgetItem(tga_file.name) # Try to get TGA info tga_info = self.converter.read_tga_header(tga_file) if tga_info: item.setToolTip(f"{tga_info.width}x{tga_info.height}, {tga_info.pixel_depth}bpp") self.files_list.addItem(item) self.convert_btn.setEnabled(len(tga_files) > 0) else: self.cache_path_label.setText("❌ Not found") self.files_count_label.setText("Cache folder not found") self.convert_btn.setEnabled(False) def _browse_cache_folder(self): """Browse for cache folder.""" dir_path = QFileDialog.getExistingDirectory( self, "Select Entropia Universe Cache Folder", str(Path.home() / "Documents" / "Entropia Universe"), QFileDialog.Option.ShowDirsOnly ) if dir_path: self.converter._cache_path = Path(dir_path) self._scan_cache() def _browse_output_folder(self): """Browse for output folder.""" dir_path = QFileDialog.getExistingDirectory( self, "Select Output Folder", str(self.converter.output_dir), QFileDialog.Option.ShowDirsOnly ) if dir_path: self.converter.output_dir = Path(dir_path) self.output_path_label.setText(dir_path) def _start_conversion(self): """Start TGA to PNG conversion.""" if self.convert_worker and self.convert_worker.isRunning(): QMessageBox.warning(self, "Busy", "Conversion already in progress") return self.convert_btn.setEnabled(False) self.progress_bar.setVisible(True) self.results_list.clear() self.converted_files = [] # Get canvas settings canvas_size = self.canvas_combo.currentData() upscale_method = self.upscale_method_combo.currentData() # Start worker with settings cache_path = self.converter._cache_path if self.converter._cache_path else None self.convert_worker = TGAConvertWorker( self.converter, cache_path, canvas_size, upscale=True, upscale_method=upscale_method ) self.convert_worker.progress_update.connect(self._on_progress) self.convert_worker.file_converted.connect(self._on_file_converted) self.convert_worker.conversion_complete.connect(self._on_conversion_complete) self.convert_worker.conversion_error.connect(self._on_conversion_error) self.convert_worker.start() def _on_progress(self, message: str): """Handle progress update.""" self.status_label.setText(message) def _on_file_converted(self, tga_name: str, png_path: str): """Handle file converted.""" self.converted_files.append(png_path) item = QListWidgetItem(f"✅ {tga_name} → {Path(png_path).name}") self.results_list.addItem(item) def _on_conversion_complete(self, success: int, total: int): """Handle conversion complete.""" self.progress_bar.setVisible(False) self.convert_btn.setEnabled(True) self.status_label.setText(f"Complete: {success}/{total} files converted") QMessageBox.information( self, "Conversion Complete", f"Successfully converted {success} of {total} files\n\n" f"Output: {self.converter.output_dir}" ) def _on_conversion_error(self, error: str): """Handle conversion error.""" self.progress_bar.setVisible(False) self.convert_btn.setEnabled(True) self.status_label.setText(f"Error: {error}") QMessageBox.critical(self, "Error", error) def _open_output_folder(self): """Open output folder in file explorer.""" import os import platform output_path = str(self.converter.output_dir) if platform.system() == "Windows": os.startfile(output_path) elif platform.system() == "Darwin": # macOS import subprocess subprocess.run(["open", output_path]) else: # Linux import subprocess subprocess.run(["xdg-open", output_path]) def closeEvent(self, event): """Handle dialog close.""" if self.convert_worker and self.convert_worker.isRunning(): self.convert_worker.stop() self.convert_worker.wait(1000) event.accept() def _apply_dark_theme(self): """Apply dark theme.""" self.setStyleSheet(""" QDialog { background-color: #1e1e1e; color: #e0e0e0; } QGroupBox { font-weight: bold; border: 1px solid #444; border-radius: 6px; margin-top: 10px; padding-top: 10px; } QListWidget { background-color: #252525; border: 1px solid #444; color: #e0e0e0; } QPushButton { background-color: #0d47a1; border: 1px solid #1565c0; border-radius: 4px; padding: 6px 12px; color: white; } QPushButton:hover { background-color: #1565c0; } QLabel { color: #e0e0e0; } """)