Sync from development - prepare for v0.1.5.1

This commit is contained in:
Omni
2025-09-28 12:15:44 +01:00
parent f46ed2c0fe
commit 8661f8963e
11 changed files with 502 additions and 30 deletions

View File

@@ -884,7 +884,12 @@ class JackifyMainWindow(QMainWindow):
self._check_protontricks_on_startup()
def _debug_screen_change(self, index):
"""Debug method to track screen changes"""
"""Handle screen changes - debug logging and state reset"""
# Reset screen state when switching to workflow screens
widget = self.stacked_widget.widget(index)
if widget and hasattr(widget, 'reset_screen_to_defaults'):
widget.reset_screen_to_defaults()
# Only show debug info if debug mode is enabled
from jackify.backend.handlers.config_handler import ConfigHandler
config_handler = ConfigHandler()

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
GUI Mixins Package
Reusable mixins for GUI functionality
"""
from .operation_lock_mixin import OperationLockMixin
__all__ = ['OperationLockMixin']

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Operation Lock Mixin
Provides reliable button state management for GUI operations
"""
from contextlib import contextmanager
class OperationLockMixin:
"""
Mixin that provides reliable button state management.
Ensures controls are always re-enabled after operations, even if exceptions occur.
"""
def operation_lock(self):
"""
Context manager that ensures controls are always re-enabled after operations.
Usage:
with self.operation_lock():
# Perform operation that might fail
risky_operation()
# Controls are guaranteed to be re-enabled here
"""
@contextmanager
def lock_manager():
try:
if hasattr(self, '_disable_controls_during_operation'):
self._disable_controls_during_operation()
yield
finally:
# Ensure controls are re-enabled even if exceptions occur
if hasattr(self, '_enable_controls_after_operation'):
self._enable_controls_after_operation()
return lock_manager()
def safe_operation(self, operation_func, *args, **kwargs):
"""
Execute an operation with automatic button state management.
Args:
operation_func: Function to execute
*args, **kwargs: Arguments to pass to operation_func
Returns:
Result of operation_func or None if exception occurred
"""
try:
with self.operation_lock():
return operation_func(*args, **kwargs)
except Exception as e:
# Log the error but don't re-raise - controls are already re-enabled
if hasattr(self, 'logger'):
self.logger.error(f"Operation failed: {e}", exc_info=True)
# Could also show user error dialog here if needed
return None
def reset_screen_to_defaults(self):
"""
Reset the screen to default state when navigating back from main menu.
Override this method in subclasses to implement screen-specific reset logic.
"""
pass # Default implementation does nothing - subclasses should override

View File

@@ -738,6 +738,30 @@ class ConfigureExistingModlistScreen(QWidget):
else:
return f"{elapsed_seconds_remainder} seconds"
def reset_screen_to_defaults(self):
"""Reset the screen to default state when navigating back from main menu"""
# Clear the shortcut selection
self.shortcut_combo.clear()
self.shortcut_map.clear()
# Auto-refresh modlist list when screen is entered
self.refresh_modlist_list()
# Clear console and process monitor
self.console.clear()
self.process_monitor.clear()
# Reset resolution combo to saved config preference
saved_resolution = self.resolution_service.get_saved_resolution()
if saved_resolution:
combo_items = [self.resolution_combo.itemText(i) for i in range(self.resolution_combo.count())]
resolution_index = self.resolution_service.get_resolution_index(saved_resolution, combo_items)
self.resolution_combo.setCurrentIndex(resolution_index)
elif self.resolution_combo.count() > 0:
self.resolution_combo.setCurrentIndex(0) # Fallback to "Leave unchanged"
# Re-enable controls (in case they were disabled from previous errors)
self._enable_controls_after_operation()
def cleanup(self):
"""Clean up any running threads when the screen is closed"""
debug_print("DEBUG: cleanup called - cleaning up ConfigurationThread")

View File

@@ -1326,6 +1326,27 @@ class ConfigureNewModlistScreen(QWidget):
btn_exit.clicked.connect(on_exit)
dlg.exec()
def reset_screen_to_defaults(self):
"""Reset the screen to default state when navigating back from main menu"""
# Reset form fields
self.install_dir_edit.setText("/path/to/Modlist/ModOrganizer.exe")
# Clear console and process monitor
self.console.clear()
self.process_monitor.clear()
# Reset resolution combo to saved config preference
saved_resolution = self.resolution_service.get_saved_resolution()
if saved_resolution:
combo_items = [self.resolution_combo.itemText(i) for i in range(self.resolution_combo.count())]
resolution_index = self.resolution_service.get_resolution_index(saved_resolution, combo_items)
self.resolution_combo.setCurrentIndex(resolution_index)
elif self.resolution_combo.count() > 0:
self.resolution_combo.setCurrentIndex(0) # Fallback to "Leave unchanged"
# Re-enable controls (in case they were disabled from previous errors)
self._enable_controls_after_operation()
def cleanup(self):
"""Clean up any running threads when the screen is closed"""
debug_print("DEBUG: cleanup called - cleaning up threads")

View File

@@ -2023,7 +2023,10 @@ class InstallModlistScreen(QWidget):
"""Handle configuration error on main thread"""
self._safe_append_text(f"Configuration failed with error: {error_message}")
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}")
# Re-enable all controls on error
self._enable_controls_after_operation()
# 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
@@ -2676,6 +2679,36 @@ https://wiki.scenicroute.games/Somnium/1_Installation.html</i>"""
self.cleanup_processes()
self.go_back()
def reset_screen_to_defaults(self):
"""Reset the screen to default state when navigating back from main menu"""
# Reset form fields
self.modlist_btn.setText("Select Modlist")
self.modlist_btn.setEnabled(False)
self.file_edit.setText("")
self.modlist_name_edit.setText("")
self.install_dir_edit.setText(self.config_handler.get_modlist_install_base_dir())
# Reset game type button
self.game_type_btn.setText("Please Select...")
# Clear console and process monitor
self.console.clear()
self.process_monitor.clear()
# Reset tabs to first tab (Online)
self.source_tabs.setCurrentIndex(0)
# Reset resolution combo to saved config preference
saved_resolution = self.resolution_service.get_saved_resolution()
if saved_resolution:
combo_items = [self.resolution_combo.itemText(i) for i in range(self.resolution_combo.count())]
resolution_index = self.resolution_service.get_resolution_index(saved_resolution, combo_items)
self.resolution_combo.setCurrentIndex(resolution_index)
elif self.resolution_combo.count() > 0:
self.resolution_combo.setCurrentIndex(0) # Fallback to "Leave unchanged"
# Re-enable controls (in case they were disabled from previous errors)
self._enable_controls_after_operation()
def closeEvent(self, event):
"""Handle window close event - clean up processes"""
self.cleanup_processes()