mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Sync from development - prepare for v0.1.2
This commit is contained in:
@@ -34,8 +34,7 @@ class ConfigureExistingModlistScreen(QWidget):
|
||||
self.stacked_widget = stacked_widget
|
||||
self.main_menu_index = main_menu_index
|
||||
self.debug = DEBUG_BORDERS
|
||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Configure_Existing_Modlist_workflow.log')
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
self.refresh_paths()
|
||||
|
||||
# --- Detect Steam Deck ---
|
||||
steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower()
|
||||
@@ -297,6 +296,41 @@ class ConfigureExistingModlistScreen(QWidget):
|
||||
|
||||
# Time tracking for workflow completion
|
||||
self._workflow_start_time = None
|
||||
|
||||
# Initialize empty controls list - will be populated after UI is built
|
||||
self._actionable_controls = []
|
||||
|
||||
# Now collect all actionable controls after UI is fully built
|
||||
self._collect_actionable_controls()
|
||||
|
||||
def _collect_actionable_controls(self):
|
||||
"""Collect all actionable controls that should be disabled during operations (except Cancel)"""
|
||||
self._actionable_controls = [
|
||||
# Main action button
|
||||
self.start_btn,
|
||||
# Form fields
|
||||
self.shortcut_combo,
|
||||
# Resolution controls
|
||||
self.resolution_combo,
|
||||
]
|
||||
|
||||
def _disable_controls_during_operation(self):
|
||||
"""Disable all actionable controls during configure operations (except Cancel)"""
|
||||
for control in self._actionable_controls:
|
||||
if control:
|
||||
control.setEnabled(False)
|
||||
|
||||
def _enable_controls_after_operation(self):
|
||||
"""Re-enable all actionable controls after configure operations complete"""
|
||||
for control in self._actionable_controls:
|
||||
if control:
|
||||
control.setEnabled(True)
|
||||
|
||||
def refresh_paths(self):
|
||||
"""Refresh cached paths when config changes."""
|
||||
from jackify.shared.paths import get_jackify_logs_dir
|
||||
self.modlist_log_path = get_jackify_logs_dir() / 'Configure_Existing_Modlist_workflow.log'
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""Handle window resize to prioritize form over console"""
|
||||
@@ -382,17 +416,22 @@ class ConfigureExistingModlistScreen(QWidget):
|
||||
log_handler = LoggingHandler()
|
||||
log_handler.rotate_log_file_per_run(Path(self.modlist_log_path), backup_count=5)
|
||||
|
||||
# Disable controls during configuration
|
||||
self._disable_controls_during_operation()
|
||||
|
||||
# Get selected shortcut
|
||||
idx = self.shortcut_combo.currentIndex() - 1 # Account for 'Please Select...'
|
||||
from jackify.frontends.gui.services.message_service import MessageService
|
||||
if idx < 0 or idx >= len(self.shortcut_map):
|
||||
MessageService.critical(self, "No Shortcut Selected", "Please select a ModOrganizer.exe Steam shortcut to configure.", safety_level="medium")
|
||||
self._enable_controls_after_operation()
|
||||
return
|
||||
shortcut = self.shortcut_map[idx]
|
||||
modlist_name = shortcut.get('AppName', '')
|
||||
install_dir = shortcut.get('StartDir', '')
|
||||
if not modlist_name or not install_dir:
|
||||
MessageService.critical(self, "Invalid Shortcut", "The selected shortcut is missing required information.", safety_level="medium")
|
||||
self._enable_controls_after_operation()
|
||||
return
|
||||
resolution = self.resolution_combo.currentText()
|
||||
# Handle resolution saving
|
||||
@@ -505,6 +544,9 @@ class ConfigureExistingModlistScreen(QWidget):
|
||||
|
||||
def on_configuration_complete(self, success, message, modlist_name):
|
||||
"""Handle configuration completion"""
|
||||
# Re-enable all controls when workflow completes
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
if success:
|
||||
# Calculate time taken
|
||||
time_taken = self._calculate_time_taken()
|
||||
@@ -525,6 +567,9 @@ class ConfigureExistingModlistScreen(QWidget):
|
||||
|
||||
def on_configuration_error(self, error_message):
|
||||
"""Handle configuration error"""
|
||||
# Re-enable all controls on error
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
self._safe_append_text(f"Configuration error: {error_message}")
|
||||
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}", safety_level="medium")
|
||||
|
||||
@@ -559,8 +604,8 @@ class ConfigureExistingModlistScreen(QWidget):
|
||||
if self.config_process and self.config_process.state() == QProcess.Running:
|
||||
self.config_process.terminate()
|
||||
self.config_process.waitForFinished(2000)
|
||||
# Reset button states
|
||||
self.start_btn.setEnabled(True)
|
||||
# Re-enable all controls
|
||||
self._enable_controls_after_operation()
|
||||
self.cancel_btn.setVisible(True)
|
||||
|
||||
def show_next_steps_dialog(self, message):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
ConfigureNewModlistScreen for Jackify GUI
|
||||
"""
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QLineEdit, QPushButton, QGridLayout, QFileDialog, QTextEdit, QSizePolicy, QTabWidget, QDialog, QListWidget, QListWidgetItem, QMessageBox, QProgressDialog
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QLineEdit, QPushButton, QGridLayout, QFileDialog, QTextEdit, QSizePolicy, QTabWidget, QDialog, QListWidget, QListWidgetItem, QMessageBox, QProgressDialog, QCheckBox
|
||||
from PySide6.QtCore import Qt, QSize, QThread, Signal, QTimer, QProcess, QMetaObject
|
||||
from PySide6.QtGui import QPixmap, QTextCursor
|
||||
from ..shared_theme import JACKIFY_COLOR_BLUE, DEBUG_BORDERS
|
||||
@@ -106,8 +106,7 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
self.protontricks_service = ProtontricksDetectionService()
|
||||
|
||||
# Path for workflow log
|
||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Configure_New_Modlist_workflow.log')
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
self.refresh_paths()
|
||||
|
||||
# Scroll tracking for professional auto-scroll behavior
|
||||
self._user_manually_scrolled = False
|
||||
@@ -211,7 +210,6 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
"7680x4320"
|
||||
])
|
||||
form_grid.addWidget(resolution_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||
form_grid.addWidget(self.resolution_combo, 2, 1)
|
||||
|
||||
# Load saved resolution if available
|
||||
saved_resolution = self.resolution_service.get_saved_resolution()
|
||||
@@ -236,6 +234,27 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
else:
|
||||
self.resolution_combo.setCurrentIndex(0)
|
||||
# Otherwise, default is 'Leave unchanged' (index 0)
|
||||
|
||||
# Horizontal layout for resolution dropdown and auto-restart checkbox
|
||||
resolution_and_restart_layout = QHBoxLayout()
|
||||
resolution_and_restart_layout.setSpacing(12)
|
||||
|
||||
# Resolution dropdown (made smaller)
|
||||
self.resolution_combo.setMaximumWidth(280) # Constrain width but keep aesthetically pleasing
|
||||
resolution_and_restart_layout.addWidget(self.resolution_combo)
|
||||
|
||||
# Add stretch to push checkbox to the right
|
||||
resolution_and_restart_layout.addStretch()
|
||||
|
||||
# Auto-accept Steam restart checkbox (right-aligned)
|
||||
self.auto_restart_checkbox = QCheckBox("Auto-accept Steam restart")
|
||||
self.auto_restart_checkbox.setChecked(False) # Always default to unchecked per session
|
||||
self.auto_restart_checkbox.setToolTip("When checked, Steam restart dialog will be automatically accepted, allowing unattended configuration")
|
||||
resolution_and_restart_layout.addWidget(self.auto_restart_checkbox)
|
||||
|
||||
# Update the form grid to use the combined layout
|
||||
form_grid.addLayout(resolution_and_restart_layout, 2, 1)
|
||||
|
||||
form_section_widget = QWidget()
|
||||
form_section_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
form_section_widget.setLayout(form_grid)
|
||||
@@ -338,6 +357,44 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
self.start_btn.clicked.connect(self.validate_and_start_configure)
|
||||
# --- Connect steam_restart_finished signal ---
|
||||
self.steam_restart_finished.connect(self._on_steam_restart_finished)
|
||||
|
||||
# Initialize empty controls list - will be populated after UI is built
|
||||
self._actionable_controls = []
|
||||
|
||||
# Now collect all actionable controls after UI is fully built
|
||||
self._collect_actionable_controls()
|
||||
|
||||
def _collect_actionable_controls(self):
|
||||
"""Collect all actionable controls that should be disabled during operations (except Cancel)"""
|
||||
self._actionable_controls = [
|
||||
# Main action button
|
||||
self.start_btn,
|
||||
# Form fields
|
||||
self.modlist_name_edit,
|
||||
self.install_dir_edit,
|
||||
# Resolution controls
|
||||
self.resolution_combo,
|
||||
# Checkboxes
|
||||
self.auto_restart_checkbox,
|
||||
]
|
||||
|
||||
def _disable_controls_during_operation(self):
|
||||
"""Disable all actionable controls during configure operations (except Cancel)"""
|
||||
for control in self._actionable_controls:
|
||||
if control:
|
||||
control.setEnabled(False)
|
||||
|
||||
def _enable_controls_after_operation(self):
|
||||
"""Re-enable all actionable controls after configure operations complete"""
|
||||
for control in self._actionable_controls:
|
||||
if control:
|
||||
control.setEnabled(True)
|
||||
|
||||
def refresh_paths(self):
|
||||
"""Refresh cached paths when config changes."""
|
||||
from jackify.shared.paths import get_jackify_logs_dir
|
||||
self.modlist_log_path = get_jackify_logs_dir() / 'Configure_New_Modlist_workflow.log'
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""Handle window resize to prioritize form over console"""
|
||||
@@ -522,23 +579,38 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
# Start time tracking
|
||||
self._workflow_start_time = time.time()
|
||||
|
||||
# Disable controls during configuration (after validation passes)
|
||||
self._disable_controls_during_operation()
|
||||
|
||||
# Validate modlist name
|
||||
modlist_name = self.modlist_name_edit.text().strip()
|
||||
if not modlist_name:
|
||||
MessageService.warning(self, "Missing Name", "Please specify a name for your modlist", safety_level="low")
|
||||
self._enable_controls_after_operation()
|
||||
return
|
||||
# --- Shortcut creation will be handled by automated workflow ---
|
||||
from jackify.backend.handlers.shortcut_handler import ShortcutHandler
|
||||
steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower()
|
||||
shortcut_handler = ShortcutHandler(steamdeck=steamdeck) # Still needed for Steam restart
|
||||
# --- User confirmation before restarting Steam ---
|
||||
reply = MessageService.question(
|
||||
self, "Ready to Configure Modlist",
|
||||
"Would you like to restart Steam and begin post-install configuration now? Restarting Steam could close any games you have open!",
|
||||
safety_level="medium"
|
||||
)
|
||||
print(f"DEBUG: Steam restart dialog returned: {reply!r}")
|
||||
|
||||
# Check if auto-restart is enabled
|
||||
auto_restart_enabled = hasattr(self, 'auto_restart_checkbox') and self.auto_restart_checkbox.isChecked()
|
||||
|
||||
if auto_restart_enabled:
|
||||
# Auto-accept Steam restart - proceed without dialog
|
||||
self._safe_append_text("Auto-accepting Steam restart (unattended mode enabled)")
|
||||
reply = QMessageBox.Yes # Simulate user clicking Yes
|
||||
else:
|
||||
# --- User confirmation before restarting Steam ---
|
||||
reply = MessageService.question(
|
||||
self, "Ready to Configure Modlist",
|
||||
"Would you like to restart Steam and begin post-install configuration now? Restarting Steam could close any games you have open!",
|
||||
safety_level="medium"
|
||||
)
|
||||
|
||||
debug_print(f"DEBUG: Steam restart dialog returned: {reply!r}")
|
||||
if reply not in (QMessageBox.Yes, QMessageBox.Ok, QMessageBox.AcceptRole):
|
||||
self._enable_controls_after_operation()
|
||||
if self.stacked_widget:
|
||||
self.stacked_widget.setCurrentIndex(0)
|
||||
return
|
||||
@@ -562,7 +634,6 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
progress.setMinimumDuration(0)
|
||||
progress.setValue(0)
|
||||
progress.show()
|
||||
self.setEnabled(False)
|
||||
def do_restart():
|
||||
try:
|
||||
ok = shortcut_handler.secure_steam_restart()
|
||||
@@ -579,7 +650,7 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
if hasattr(self, '_steam_restart_progress'):
|
||||
self._steam_restart_progress.close()
|
||||
del self._steam_restart_progress
|
||||
self.setEnabled(True)
|
||||
self._enable_controls_after_operation()
|
||||
if success:
|
||||
self._safe_append_text("Steam restarted successfully.")
|
||||
|
||||
@@ -722,7 +793,7 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
"""Handle error from the automated prefix workflow"""
|
||||
self._safe_append_text(f"Error during automated Steam setup: {error_message}")
|
||||
self._safe_append_text("Please check the logs for details.")
|
||||
self.start_btn.setEnabled(True)
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
def show_shortcut_conflict_dialog(self, conflicts):
|
||||
"""Show dialog to resolve shortcut name conflicts"""
|
||||
@@ -1162,8 +1233,8 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
|
||||
def on_configuration_complete(self, success, message, modlist_name):
|
||||
"""Handle configuration completion (same as Tuxborn)"""
|
||||
# Always re-enable the start button when workflow completes
|
||||
self.start_btn.setEnabled(True)
|
||||
# Re-enable all controls when workflow completes
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
if success:
|
||||
# Calculate time taken
|
||||
@@ -1185,8 +1256,8 @@ class ConfigureNewModlistScreen(QWidget):
|
||||
|
||||
def on_configuration_error(self, error_message):
|
||||
"""Handle configuration error"""
|
||||
# Re-enable the start button on error
|
||||
self.start_btn.setEnabled(True)
|
||||
# Re-enable all controls on error
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
self._safe_append_text(f"Configuration error: {error_message}")
|
||||
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}", safety_level="medium")
|
||||
|
||||
@@ -355,9 +355,8 @@ class InstallModlistScreen(QWidget):
|
||||
self.online_modlists = {} # {game_type: [modlist_dict, ...]}
|
||||
self.modlist_details = {} # {modlist_name: modlist_dict}
|
||||
|
||||
# Path for workflow log
|
||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Modlist_Install_workflow.log')
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
# Initialize log path (can be refreshed via refresh_paths method)
|
||||
self.refresh_paths()
|
||||
|
||||
# Initialize services early
|
||||
from jackify.backend.services.api_key_service import APIKeyService
|
||||
@@ -459,11 +458,11 @@ class InstallModlistScreen(QWidget):
|
||||
file_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.file_edit = QLineEdit()
|
||||
self.file_edit.setMinimumWidth(400)
|
||||
file_btn = QPushButton("Browse")
|
||||
file_btn.clicked.connect(self.browse_wabbajack_file)
|
||||
self.file_btn = QPushButton("Browse")
|
||||
self.file_btn.clicked.connect(self.browse_wabbajack_file)
|
||||
file_layout.addWidget(QLabel(".wabbajack File:"))
|
||||
file_layout.addWidget(self.file_edit)
|
||||
file_layout.addWidget(file_btn)
|
||||
file_layout.addWidget(self.file_btn)
|
||||
self.file_group.setLayout(file_layout)
|
||||
file_tab_vbox.addWidget(self.file_group)
|
||||
file_tab.setLayout(file_tab_vbox)
|
||||
@@ -484,22 +483,22 @@ class InstallModlistScreen(QWidget):
|
||||
install_dir_label = QLabel("Install Directory:")
|
||||
self.install_dir_edit = QLineEdit(self.config_handler.get_modlist_install_base_dir())
|
||||
self.install_dir_edit.setMaximumHeight(25) # Force compact height
|
||||
browse_install_btn = QPushButton("Browse")
|
||||
browse_install_btn.clicked.connect(self.browse_install_dir)
|
||||
self.browse_install_btn = QPushButton("Browse")
|
||||
self.browse_install_btn.clicked.connect(self.browse_install_dir)
|
||||
install_dir_hbox = QHBoxLayout()
|
||||
install_dir_hbox.addWidget(self.install_dir_edit)
|
||||
install_dir_hbox.addWidget(browse_install_btn)
|
||||
install_dir_hbox.addWidget(self.browse_install_btn)
|
||||
form_grid.addWidget(install_dir_label, 1, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||
form_grid.addLayout(install_dir_hbox, 1, 1)
|
||||
# Downloads Dir
|
||||
downloads_dir_label = QLabel("Downloads Directory:")
|
||||
self.downloads_dir_edit = QLineEdit(self.config_handler.get_modlist_downloads_base_dir())
|
||||
self.downloads_dir_edit.setMaximumHeight(25) # Force compact height
|
||||
browse_downloads_btn = QPushButton("Browse")
|
||||
browse_downloads_btn.clicked.connect(self.browse_downloads_dir)
|
||||
self.browse_downloads_btn = QPushButton("Browse")
|
||||
self.browse_downloads_btn.clicked.connect(self.browse_downloads_dir)
|
||||
downloads_dir_hbox = QHBoxLayout()
|
||||
downloads_dir_hbox.addWidget(self.downloads_dir_edit)
|
||||
downloads_dir_hbox.addWidget(browse_downloads_btn)
|
||||
downloads_dir_hbox.addWidget(self.browse_downloads_btn)
|
||||
form_grid.addWidget(downloads_dir_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||
form_grid.addLayout(downloads_dir_hbox, 2, 1)
|
||||
# Nexus API Key
|
||||
@@ -603,7 +602,25 @@ class InstallModlistScreen(QWidget):
|
||||
self.resolution_combo.setCurrentIndex(0)
|
||||
# Otherwise, default is 'Leave unchanged' (index 0)
|
||||
form_grid.addWidget(resolution_label, 5, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||
form_grid.addWidget(self.resolution_combo, 5, 1)
|
||||
|
||||
# Horizontal layout for resolution dropdown and auto-restart checkbox
|
||||
resolution_and_restart_layout = QHBoxLayout()
|
||||
resolution_and_restart_layout.setSpacing(12)
|
||||
|
||||
# Resolution dropdown (made smaller)
|
||||
self.resolution_combo.setMaximumWidth(280) # Constrain width but keep aesthetically pleasing
|
||||
resolution_and_restart_layout.addWidget(self.resolution_combo)
|
||||
|
||||
# Add stretch to push checkbox to the right
|
||||
resolution_and_restart_layout.addStretch()
|
||||
|
||||
# Auto-accept Steam restart checkbox (right-aligned)
|
||||
self.auto_restart_checkbox = QCheckBox("Auto-accept Steam restart")
|
||||
self.auto_restart_checkbox.setChecked(False) # Always default to unchecked per session
|
||||
self.auto_restart_checkbox.setToolTip("When checked, Steam restart dialog will be automatically accepted, allowing unattended installation")
|
||||
resolution_and_restart_layout.addWidget(self.auto_restart_checkbox)
|
||||
|
||||
form_grid.addLayout(resolution_and_restart_layout, 5, 1)
|
||||
form_section_widget = QWidget()
|
||||
form_section_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
form_section_widget.setLayout(form_grid)
|
||||
@@ -723,6 +740,57 @@ class InstallModlistScreen(QWidget):
|
||||
|
||||
# Initialize process tracking
|
||||
self.process = None
|
||||
|
||||
# Initialize empty controls list - will be populated after UI is built
|
||||
self._actionable_controls = []
|
||||
|
||||
# Now collect all actionable controls after UI is fully built
|
||||
self._collect_actionable_controls()
|
||||
|
||||
def _collect_actionable_controls(self):
|
||||
"""Collect all actionable controls that should be disabled during operations (except Cancel)"""
|
||||
self._actionable_controls = [
|
||||
# Main action button
|
||||
self.start_btn,
|
||||
# Game/modlist selection
|
||||
self.game_type_btn,
|
||||
self.modlist_btn,
|
||||
# Source tabs (entire tab widget)
|
||||
self.source_tabs,
|
||||
# Form fields
|
||||
self.modlist_name_edit,
|
||||
self.install_dir_edit,
|
||||
self.downloads_dir_edit,
|
||||
self.api_key_edit,
|
||||
self.file_edit,
|
||||
# Browse buttons
|
||||
self.browse_install_btn,
|
||||
self.browse_downloads_btn,
|
||||
self.file_btn,
|
||||
# Resolution controls
|
||||
self.resolution_combo,
|
||||
# Checkboxes
|
||||
self.save_api_key_checkbox,
|
||||
self.auto_restart_checkbox,
|
||||
]
|
||||
|
||||
def _disable_controls_during_operation(self):
|
||||
"""Disable all actionable controls during install/configure operations (except Cancel)"""
|
||||
for control in self._actionable_controls:
|
||||
if control:
|
||||
control.setEnabled(False)
|
||||
|
||||
def _enable_controls_after_operation(self):
|
||||
"""Re-enable all actionable controls after install/configure operations complete"""
|
||||
for control in self._actionable_controls:
|
||||
if control:
|
||||
control.setEnabled(True)
|
||||
|
||||
def refresh_paths(self):
|
||||
"""Refresh cached paths when config changes."""
|
||||
from jackify.shared.paths import get_jackify_logs_dir
|
||||
self.modlist_log_path = get_jackify_logs_dir() / 'Modlist_Install_workflow.log'
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
|
||||
def _open_url_safe(self, url):
|
||||
"""Safely open URL using subprocess to avoid Qt library conflicts in PyInstaller"""
|
||||
@@ -1121,6 +1189,9 @@ class InstallModlistScreen(QWidget):
|
||||
if not self._check_protontricks():
|
||||
return
|
||||
|
||||
# Disable all controls during installation (except Cancel)
|
||||
self._disable_controls_during_operation()
|
||||
|
||||
try:
|
||||
tab_index = self.source_tabs.currentIndex()
|
||||
install_mode = 'online'
|
||||
@@ -1128,12 +1199,14 @@ class InstallModlistScreen(QWidget):
|
||||
modlist = self.file_edit.text().strip()
|
||||
if not modlist or not os.path.isfile(modlist) or not modlist.endswith('.wabbajack'):
|
||||
MessageService.warning(self, "Invalid Modlist", "Please select a valid .wabbajack file.")
|
||||
self._enable_controls_after_operation()
|
||||
return
|
||||
install_mode = 'file'
|
||||
else:
|
||||
modlist = self.modlist_btn.text().strip()
|
||||
if not modlist or modlist in ("Select Modlist", "Fetching modlists...", "No modlists found.", "Error fetching modlists."):
|
||||
MessageService.warning(self, "Invalid Modlist", "Please select a valid modlist.")
|
||||
self._enable_controls_after_operation()
|
||||
return
|
||||
|
||||
# For online modlists, use machine_url instead of display name
|
||||
@@ -1159,6 +1232,7 @@ class InstallModlistScreen(QWidget):
|
||||
missing_fields.append("Nexus API Key")
|
||||
if missing_fields:
|
||||
MessageService.warning(self, "Missing Required Fields", f"Please fill in all required fields before starting the install:\n- " + "\n- ".join(missing_fields))
|
||||
self._enable_controls_after_operation()
|
||||
return
|
||||
validation_handler = ValidationHandler()
|
||||
from pathlib import Path
|
||||
@@ -1324,14 +1398,11 @@ class InstallModlistScreen(QWidget):
|
||||
debug_print(f"DEBUG: Exception in validate_and_start_install: {e}")
|
||||
import traceback
|
||||
debug_print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||
# Re-enable the button in case of exception
|
||||
self.start_btn.setEnabled(True)
|
||||
# Re-enable all controls after exception
|
||||
self._enable_controls_after_operation()
|
||||
self.cancel_btn.setVisible(True)
|
||||
self.cancel_install_btn.setVisible(False)
|
||||
# Also re-enable the entire widget
|
||||
self.setEnabled(True)
|
||||
debug_print(f"DEBUG: Widget re-enabled in exception handler, widget enabled: {self.isEnabled()}")
|
||||
print(f"DEBUG: Widget re-enabled in exception handler, widget enabled: {self.isEnabled()}") # Always print
|
||||
debug_print(f"DEBUG: Controls re-enabled in exception handler")
|
||||
|
||||
def run_modlist_installer(self, modlist, install_dir, downloads_dir, api_key, install_mode='online'):
|
||||
debug_print('DEBUG: run_modlist_installer called - USING THREADED BACKEND WRAPPER')
|
||||
@@ -1501,12 +1572,21 @@ class InstallModlistScreen(QWidget):
|
||||
self._safe_append_text(f"\nModlist installation completed successfully.")
|
||||
self._safe_append_text(f"\nWarning: Post-install configuration skipped for unsupported game: {game_name or game_type}")
|
||||
else:
|
||||
# Show the normal install complete dialog for supported games
|
||||
reply = MessageService.question(
|
||||
self, "Modlist Install Complete!",
|
||||
"Modlist install complete!\n\nWould you like to add this modlist to Steam and configure it now? Steam will restart, closing any game you have open!",
|
||||
critical=False # Non-critical, won't steal focus
|
||||
)
|
||||
# Check if auto-restart is enabled
|
||||
auto_restart_enabled = hasattr(self, 'auto_restart_checkbox') and self.auto_restart_checkbox.isChecked()
|
||||
|
||||
if auto_restart_enabled:
|
||||
# Auto-accept Steam restart - proceed without dialog
|
||||
self._safe_append_text("\nAuto-accepting Steam restart (unattended mode enabled)")
|
||||
reply = QMessageBox.Yes # Simulate user clicking Yes
|
||||
else:
|
||||
# Show the normal install complete dialog for supported games
|
||||
reply = MessageService.question(
|
||||
self, "Modlist Install Complete!",
|
||||
"Modlist install complete!\n\nWould you like to add this modlist to Steam and configure it now? Steam will restart, closing any game you have open!",
|
||||
critical=False # Non-critical, won't steal focus
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
# --- Create Steam shortcut BEFORE restarting Steam ---
|
||||
# Proceed directly to automated prefix creation
|
||||
@@ -1522,6 +1602,8 @@ class InstallModlistScreen(QWidget):
|
||||
"You can manually add the modlist to Steam later if desired.",
|
||||
safety_level="medium"
|
||||
)
|
||||
# Re-enable controls since operation is complete
|
||||
self._enable_controls_after_operation()
|
||||
else:
|
||||
# Check for user cancellation first
|
||||
last_output = self.console.toPlainText()
|
||||
@@ -1611,9 +1693,6 @@ class InstallModlistScreen(QWidget):
|
||||
progress.setMinimumDuration(0)
|
||||
progress.setValue(0)
|
||||
progress.show()
|
||||
self.setEnabled(False)
|
||||
debug_print(f"DEBUG: Widget disabled in restart_steam_and_configure, widget enabled: {self.isEnabled()}")
|
||||
print(f"DEBUG: Widget disabled in restart_steam_and_configure, widget enabled: {self.isEnabled()}") # Always print
|
||||
|
||||
def do_restart():
|
||||
debug_print("DEBUG: do_restart thread started - using direct backend service")
|
||||
@@ -1651,9 +1730,7 @@ class InstallModlistScreen(QWidget):
|
||||
finally:
|
||||
self._steam_restart_progress = None
|
||||
|
||||
self.setEnabled(True)
|
||||
debug_print(f"DEBUG: Widget re-enabled in _on_steam_restart_finished, widget enabled: {self.isEnabled()}")
|
||||
print(f"DEBUG: Widget re-enabled in _on_steam_restart_finished, widget enabled: {self.isEnabled()}") # Always print
|
||||
# Controls are managed by the proper control management system
|
||||
if success:
|
||||
self._safe_append_text("Steam restarted successfully.")
|
||||
|
||||
@@ -1676,6 +1753,8 @@ class InstallModlistScreen(QWidget):
|
||||
def start_automated_prefix_workflow(self):
|
||||
"""Start the automated prefix creation workflow"""
|
||||
try:
|
||||
# Disable controls during installation
|
||||
self._disable_controls_during_operation()
|
||||
modlist_name = self.modlist_name_edit.text().strip()
|
||||
install_dir = self.install_dir_edit.text().strip()
|
||||
final_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
|
||||
@@ -1784,33 +1863,43 @@ class InstallModlistScreen(QWidget):
|
||||
import traceback
|
||||
debug_print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||
self._safe_append_text(f"ERROR: Failed to start automated workflow: {e}")
|
||||
# Re-enable controls on exception
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
def on_automated_prefix_finished(self, success, prefix_path, new_appid_str, last_timestamp=None):
|
||||
"""Handle completion of automated prefix creation"""
|
||||
if success:
|
||||
debug_print(f"SUCCESS: Automated prefix creation completed!")
|
||||
debug_print(f"Prefix created at: {prefix_path}")
|
||||
if new_appid_str and new_appid_str != "0":
|
||||
debug_print(f"AppID: {new_appid_str}")
|
||||
|
||||
# Convert string AppID back to integer for configuration
|
||||
new_appid = int(new_appid_str) if new_appid_str and new_appid_str != "0" else None
|
||||
|
||||
# Continue with configuration using the new AppID and timestamp
|
||||
modlist_name = self.modlist_name_edit.text().strip()
|
||||
install_dir = self.install_dir_edit.text().strip()
|
||||
self.continue_configuration_after_automated_prefix(new_appid, modlist_name, install_dir, last_timestamp)
|
||||
else:
|
||||
self._safe_append_text(f"ERROR: Automated prefix creation failed")
|
||||
self._safe_append_text("Please check the logs for details")
|
||||
MessageService.critical(self, "Automated Setup Failed",
|
||||
"Automated prefix creation failed. Please check the console output for details.")
|
||||
try:
|
||||
if success:
|
||||
debug_print(f"SUCCESS: Automated prefix creation completed!")
|
||||
debug_print(f"Prefix created at: {prefix_path}")
|
||||
if new_appid_str and new_appid_str != "0":
|
||||
debug_print(f"AppID: {new_appid_str}")
|
||||
|
||||
# Convert string AppID back to integer for configuration
|
||||
new_appid = int(new_appid_str) if new_appid_str and new_appid_str != "0" else None
|
||||
|
||||
# Continue with configuration using the new AppID and timestamp
|
||||
modlist_name = self.modlist_name_edit.text().strip()
|
||||
install_dir = self.install_dir_edit.text().strip()
|
||||
self.continue_configuration_after_automated_prefix(new_appid, modlist_name, install_dir, last_timestamp)
|
||||
else:
|
||||
self._safe_append_text(f"ERROR: Automated prefix creation failed")
|
||||
self._safe_append_text("Please check the logs for details")
|
||||
MessageService.critical(self, "Automated Setup Failed",
|
||||
"Automated prefix creation failed. Please check the console output for details.")
|
||||
# Re-enable controls on failure
|
||||
self._enable_controls_after_operation()
|
||||
finally:
|
||||
# Always ensure controls are re-enabled when workflow truly completes
|
||||
pass
|
||||
|
||||
def on_automated_prefix_error(self, error_msg):
|
||||
"""Handle error in automated prefix creation"""
|
||||
self._safe_append_text(f"ERROR: Error during automated prefix creation: {error_msg}")
|
||||
MessageService.critical(self, "Automated Setup Error",
|
||||
f"Error during automated prefix creation: {error_msg}")
|
||||
# Re-enable controls on error
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
def on_automated_prefix_progress(self, progress_msg):
|
||||
"""Handle progress updates from automated prefix creation"""
|
||||
@@ -1831,7 +1920,6 @@ class InstallModlistScreen(QWidget):
|
||||
self.steam_restart_progress.setMinimumDuration(0)
|
||||
self.steam_restart_progress.setValue(0)
|
||||
self.steam_restart_progress.show()
|
||||
self.setEnabled(False)
|
||||
|
||||
def hide_steam_restart_progress(self):
|
||||
"""Hide Steam restart progress dialog"""
|
||||
@@ -1843,45 +1931,53 @@ class InstallModlistScreen(QWidget):
|
||||
pass
|
||||
finally:
|
||||
self.steam_restart_progress = None
|
||||
self.setEnabled(True)
|
||||
# Controls are managed by the proper control management system
|
||||
|
||||
def on_configuration_complete(self, success, message, modlist_name):
|
||||
"""Handle configuration completion on main thread"""
|
||||
if success:
|
||||
# Show celebration SuccessDialog after the entire workflow
|
||||
from ..dialogs import SuccessDialog
|
||||
import time
|
||||
if not hasattr(self, '_install_workflow_start_time'):
|
||||
self._install_workflow_start_time = time.time()
|
||||
time_taken = int(time.time() - self._install_workflow_start_time)
|
||||
mins, secs = divmod(time_taken, 60)
|
||||
time_str = f"{mins} minutes, {secs} seconds" if mins else f"{secs} seconds"
|
||||
display_names = {
|
||||
'skyrim': 'Skyrim',
|
||||
'fallout4': 'Fallout 4',
|
||||
'falloutnv': 'Fallout New Vegas',
|
||||
'oblivion': 'Oblivion',
|
||||
'starfield': 'Starfield',
|
||||
'oblivion_remastered': 'Oblivion Remastered',
|
||||
'enderal': 'Enderal'
|
||||
}
|
||||
game_name = display_names.get(self._current_game_type, self._current_game_name)
|
||||
success_dialog = SuccessDialog(
|
||||
modlist_name=modlist_name,
|
||||
workflow_type="install",
|
||||
time_taken=time_str,
|
||||
game_name=game_name,
|
||||
parent=self
|
||||
)
|
||||
success_dialog.show()
|
||||
elif hasattr(self, '_manual_steps_retry_count') and self._manual_steps_retry_count >= 3:
|
||||
# Max retries reached - show failure message
|
||||
MessageService.critical(self, "Manual Steps Failed",
|
||||
"Manual steps validation failed after multiple attempts.")
|
||||
else:
|
||||
# Configuration failed for other reasons
|
||||
MessageService.critical(self, "Configuration Failed",
|
||||
"Post-install configuration failed. Please check the console output.")
|
||||
try:
|
||||
# Re-enable controls now that installation/configuration is complete
|
||||
self._enable_controls_after_operation()
|
||||
|
||||
if success:
|
||||
# Show celebration SuccessDialog after the entire workflow
|
||||
from ..dialogs import SuccessDialog
|
||||
import time
|
||||
if not hasattr(self, '_install_workflow_start_time'):
|
||||
self._install_workflow_start_time = time.time()
|
||||
time_taken = int(time.time() - self._install_workflow_start_time)
|
||||
mins, secs = divmod(time_taken, 60)
|
||||
time_str = f"{mins} minutes, {secs} seconds" if mins else f"{secs} seconds"
|
||||
display_names = {
|
||||
'skyrim': 'Skyrim',
|
||||
'fallout4': 'Fallout 4',
|
||||
'falloutnv': 'Fallout New Vegas',
|
||||
'oblivion': 'Oblivion',
|
||||
'starfield': 'Starfield',
|
||||
'oblivion_remastered': 'Oblivion Remastered',
|
||||
'enderal': 'Enderal'
|
||||
}
|
||||
game_name = display_names.get(self._current_game_type, self._current_game_name)
|
||||
success_dialog = SuccessDialog(
|
||||
modlist_name=modlist_name,
|
||||
workflow_type="install",
|
||||
time_taken=time_str,
|
||||
game_name=game_name,
|
||||
parent=self
|
||||
)
|
||||
success_dialog.show()
|
||||
elif hasattr(self, '_manual_steps_retry_count') and self._manual_steps_retry_count >= 3:
|
||||
# Max retries reached - show failure message
|
||||
MessageService.critical(self, "Manual Steps Failed",
|
||||
"Manual steps validation failed after multiple attempts.")
|
||||
else:
|
||||
# Configuration failed for other reasons
|
||||
MessageService.critical(self, "Configuration Failed",
|
||||
"Post-install configuration failed. Please check the console output.")
|
||||
except Exception as e:
|
||||
# Ensure controls are re-enabled even on unexpected errors
|
||||
self._enable_controls_after_operation()
|
||||
raise
|
||||
# Clean up thread
|
||||
if hasattr(self, 'config_thread') and self.config_thread is not None:
|
||||
# Disconnect all signals to prevent "Internal C++ object already deleted" errors
|
||||
@@ -1940,8 +2036,8 @@ class InstallModlistScreen(QWidget):
|
||||
else:
|
||||
# User clicked Cancel or closed the dialog - cancel the workflow
|
||||
self._safe_append_text("\n🛑 Manual steps cancelled by user. Workflow stopped.")
|
||||
# Reset button states
|
||||
self.start_btn.setEnabled(True)
|
||||
# Re-enable all controls when workflow is cancelled
|
||||
self._enable_controls_after_operation()
|
||||
self.cancel_btn.setVisible(True)
|
||||
self.cancel_install_btn.setVisible(False)
|
||||
|
||||
@@ -2513,8 +2609,8 @@ class InstallModlistScreen(QWidget):
|
||||
# Cleanup any remaining processes
|
||||
self.cleanup_processes()
|
||||
|
||||
# Reset button states
|
||||
self.start_btn.setEnabled(True)
|
||||
# Reset button states and re-enable all controls
|
||||
self._enable_controls_after_operation()
|
||||
self.cancel_btn.setVisible(True)
|
||||
self.cancel_install_btn.setVisible(False)
|
||||
|
||||
|
||||
@@ -106,8 +106,7 @@ class TuxbornInstallerScreen(QWidget):
|
||||
self.modlist_details = {} # {modlist_name: modlist_dict}
|
||||
|
||||
# Path for workflow log
|
||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Tuxborn_Installer_workflow.log')
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
self.refresh_paths()
|
||||
|
||||
# Initialize services early
|
||||
from jackify.backend.services.api_key_service import APIKeyService
|
||||
@@ -440,6 +439,12 @@ class TuxbornInstallerScreen(QWidget):
|
||||
self.start_btn.clicked.connect(self.validate_and_start_install)
|
||||
self.steam_restart_finished.connect(self._on_steam_restart_finished)
|
||||
|
||||
def refresh_paths(self):
|
||||
"""Refresh cached paths when config changes."""
|
||||
from jackify.shared.paths import get_jackify_logs_dir
|
||||
self.modlist_log_path = get_jackify_logs_dir() / 'Tuxborn_Installer_workflow.log'
|
||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||
|
||||
def _open_url_safe(self, url):
|
||||
"""Safely open URL using subprocess to avoid Qt library conflicts in PyInstaller"""
|
||||
import subprocess
|
||||
|
||||
Reference in New Issue
Block a user