Lemontropia-Suite/ui/tga_converter_dialog.py

384 lines
14 KiB
Python

"""
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
)
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, png_path
conversion_complete = pyqtSignal(int, int) # success_count, total_count
conversion_error = pyqtSignal(str)
def __init__(self, converter: TGAConverter, cache_path: Optional[Path] = None):
super().__init__()
self.converter = converter
self.cache_path = cache_path
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
self.progress_update.emit(f"Found {total} TGA files")
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}")
png_path = self.converter.convert_tga_to_png(tga_path)
if png_path:
success += 1
self.file_converted.emit(tga_path.name, str(png_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."
)
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)
# 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 = []
# Start worker with the selected cache path
cache_path = self.converter._cache_path if self.converter._cache_path else None
self.convert_worker = TGAConvertWorker(self.converter, cache_path)
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;
}
""")