Files
Jackify/jackify/frontends/gui/screens/install_modlist_selection.py
2026-02-25 20:54:28 +00:00

196 lines
9.1 KiB
Python

"""Modlist selection methods for InstallModlistScreen (Mixin)."""
from pathlib import Path
from PySide6.QtWidgets import QFileDialog, QMessageBox, QApplication, QDialog
from PySide6.QtCore import QTimer, Qt
import logging
import os
import re
# Runtime imports to avoid circular dependencies
from .install_modlist_dialogs import SelectionDialog, ModlistFetchThread # Runtime import
from jackify.frontends.gui.screens.modlist_gallery import ModlistGalleryDialog # Runtime import
logger = logging.getLogger(__name__)
class ModlistSelectionMixin:
"""Mixin providing modlist selection methods for InstallModlistScreen."""
def open_game_type_dialog(self):
dlg = SelectionDialog("Select Game Type", self.game_types, self, show_search=False)
if dlg.exec() == QDialog.Accepted and dlg.selected_item:
self.game_type_btn.setText(dlg.selected_item)
# Store game type for gallery filter
self.current_game_type = dlg.selected_item
# Enable modlist button immediately - gallery will fetch its own data
self.modlist_btn.setEnabled(True)
self.modlist_btn.setText("Select Modlist")
# No need to fetch modlists here - gallery does it when opened
def fetch_modlists_for_game_type(self, game_type):
self.current_game_type = game_type # Store for display formatting
self.modlist_btn.setText("Fetching modlists...")
self.modlist_btn.setEnabled(False)
game_type_map = {
"Skyrim": "skyrim",
"Fallout 4": "fallout4",
"Fallout New Vegas": "falloutnv",
"Oblivion": "oblivion",
"Starfield": "starfield",
"Oblivion Remastered": "oblivion_remastered",
"Enderal": "enderal",
"Other": "other"
}
cli_game_type = game_type_map.get(game_type, "other")
log_path = self.modlist_log_path
# Use backend service directly - NO CLI CALLS
self.fetch_thread = ModlistFetchThread(
cli_game_type, log_path, mode='list-modlists')
self.fetch_thread.result.connect(self.on_modlists_fetched)
self.fetch_thread.start()
def on_modlists_fetched(self, modlist_infos, error):
# Handle the case where modlist_infos might be strings (backward compatibility)
if modlist_infos and isinstance(modlist_infos[0], str):
filtered = [m for m in modlist_infos if m and not m.startswith('DEBUG:')]
self.current_modlists = filtered
self.current_modlist_display = filtered
else:
# New format - full modlist objects with enhanced metadata
filtered_modlists = [m for m in modlist_infos if m and hasattr(m, 'id')]
filtered = filtered_modlists # Set filtered for the condition check below
self.current_modlists = [m.id for m in filtered_modlists] # Keep IDs for selection
# Create enhanced display strings with size info and status indicators
display_strings = []
for modlist in filtered_modlists:
# Get enhanced metadata
download_size = getattr(modlist, 'download_size', '')
install_size = getattr(modlist, 'install_size', '')
total_size = getattr(modlist, 'total_size', '')
status_down = getattr(modlist, 'status_down', False)
status_nsfw = getattr(modlist, 'status_nsfw', False)
# Format display string without redundant game type: "Modlist Name - Download|Install|Total"
# For "Other" category, include game type in brackets for clarity
# Use padding to create alignment: left-aligned name, right-aligned sizes
if hasattr(self, 'current_game_type') and self.current_game_type == "Other":
name_part = f"{modlist.name} [{modlist.game}]"
else:
name_part = modlist.name
size_part = f"{download_size}|{install_size}|{total_size}"
# Create aligned display using string formatting (approximate alignment)
display_str = f"{name_part:<50} {size_part:>15}"
# Add status indicators at the beginning if present
if status_down or status_nsfw:
status_parts = []
if status_down:
status_parts.append("[DOWN]")
if status_nsfw:
status_parts.append("[NSFW]")
display_str = " ".join(status_parts) + " " + display_str
display_strings.append(display_str)
self.current_modlist_display = display_strings
# Create mapping from display string back to modlist ID for selection
self._modlist_id_map = {}
if len(self.current_modlist_display) == len(self.current_modlists):
self._modlist_id_map = {display: modlist_id for display, modlist_id in
zip(self.current_modlist_display, self.current_modlists)}
else:
# Fallback for backward compatibility
self._modlist_id_map = {mid: mid for mid in self.current_modlists}
if error:
self.modlist_btn.setText("Error fetching modlists.")
self.modlist_btn.setEnabled(False)
# Don't write to log file before workflow starts - just show error in UI
elif filtered:
self.modlist_btn.setText("Select Modlist")
self.modlist_btn.setEnabled(True)
else:
self.modlist_btn.setText("No modlists found.")
self.modlist_btn.setEnabled(False)
def open_modlist_dialog(self):
# CRITICAL: Prevent opening gallery without game type selected
# Prevent engine path resolution / subprocess issues
if not hasattr(self, 'current_game_type') or not self.current_game_type:
QMessageBox.warning(
self,
"Game Type Required",
"Please select a game type before opening the modlist gallery."
)
return
self.modlist_btn.setEnabled(False)
cursor_overridden = False
try:
QApplication.setOverrideCursor(Qt.WaitCursor)
cursor_overridden = True
game_type_to_human_friendly = {
"Skyrim": "Skyrim Special Edition",
"Fallout 4": "Fallout 4",
"Fallout New Vegas": "Fallout New Vegas",
"Oblivion": "Oblivion",
"Starfield": "Starfield",
"Oblivion Remastered": "Oblivion",
"Enderal": "Enderal Special Edition",
"Other": None
}
game_filter = None
if hasattr(self, 'current_game_type'):
game_filter = game_type_to_human_friendly.get(self.current_game_type)
dlg = ModlistGalleryDialog(game_filter=game_filter, parent=self)
if cursor_overridden:
QApplication.restoreOverrideCursor()
cursor_overridden = False
if dlg.exec() == QDialog.Accepted and dlg.selected_metadata:
metadata = dlg.selected_metadata
self.modlist_btn.setText(metadata.title)
self.selected_modlist_info = {
'machine_url': metadata.namespacedName,
'title': metadata.title,
'author': metadata.author,
'game': metadata.gameHumanFriendly,
'description': metadata.description,
'nsfw': metadata.nsfw,
'force_down': metadata.forceDown
}
self.modlist_name_edit.setText(metadata.title)
# Auto-append modlist name to install directory
base_install_dir = self.config_handler.get_modlist_install_base_dir()
if base_install_dir:
# Sanitize modlist title for filesystem use
safe_title = re.sub(r'[<>:"/\\|?*]', '', metadata.title)
safe_title = safe_title.strip()
modlist_install_path = os.path.join(base_install_dir, safe_title)
self.install_dir_edit.setText(modlist_install_path)
finally:
if cursor_overridden:
QApplication.restoreOverrideCursor()
self.modlist_btn.setEnabled(True)
def browse_wabbajack_file(self):
file, _ = QFileDialog.getOpenFileName(self, "Select .wabbajack File", os.path.expanduser("~"), "Wabbajack Files (*.wabbajack)")
if file:
self.file_edit.setText(os.path.realpath(file))
def browse_install_dir(self):
dir = QFileDialog.getExistingDirectory(self, "Select Install Directory", self.install_dir_edit.text())
if dir:
self.install_dir_edit.setText(os.path.realpath(dir))
def browse_downloads_dir(self):
dir = QFileDialog.getExistingDirectory(self, "Select Downloads Directory", self.downloads_dir_edit.text())
if dir:
self.downloads_dir_edit.setText(os.path.realpath(dir))