Lemontropia-Suite/ui/tga_converter_dialog.py

406 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, 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,
output_format: str = 'png'):
super().__init__()
self.converter = converter
self.cache_path = cache_path
self.output_format = output_format
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 to convert to {self.output_format.upper()}")
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, output_format=self.output_format)
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 or WebP format.\n"
"The game stores item icons in the cache folder as .tga files.\n"
"WebP format is recommended for web uploads (smaller file size, better quality)."
)
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)
output_layout.addSpacing(20)
# Format selector
output_layout.addWidget(QLabel("Format:"))
self.format_combo = QComboBox()
self.format_combo.addItem("PNG", "png")
self.format_combo.addItem("WebP (better for web)", "webp")
self.format_combo.currentIndexChanged.connect(self._on_format_changed)
output_layout.addWidget(self.format_combo)
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 _on_format_changed(self):
"""Handle output format change."""
output_format = self.format_combo.currentData()
if output_format == 'webp':
self.convert_btn.setText("🚀 Convert All to WebP")
else:
self.convert_btn.setText("🚀 Convert All to PNG")
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 and format
cache_path = self.converter._cache_path if self.converter._cache_path else None
output_format = self.format_combo.currentData()
self.convert_worker = TGAConvertWorker(self.converter, cache_path, output_format)
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;
}
""")