Sync from development - prepare for v0.3.0

This commit is contained in:
Omni
2026-02-07 18:26:54 +00:00
parent b55e1cf768
commit 12294d3186
169 changed files with 31749 additions and 33649 deletions

View File

@@ -0,0 +1,73 @@
"""
Main window backend initialization mixin.
System info, config, modlist service, protontricks service, resource limits.
"""
import os
from jackify.backend.models.configuration import SystemInfo
from jackify.backend.services.modlist_service import ModlistService
def _debug_print(message):
from jackify.backend.handlers.config_handler import ConfigHandler
ch = ConfigHandler()
if ch.get('debug_mode', False):
print(message)
class MainWindowBackendMixin:
"""Mixin for backend service initialization."""
def _initialize_backend(self):
from jackify.shared.steam_utils import detect_steam_installation_types
is_flatpak, is_native = detect_steam_installation_types()
self.system_info = SystemInfo(
is_steamdeck=self._is_steamdeck(),
is_flatpak_steam=is_flatpak,
is_native_steam=is_native
)
self._apply_resource_limits()
from jackify.backend.handlers.config_handler import ConfigHandler
self.config_handler = ConfigHandler()
self.backend_services = {'modlist_service': ModlistService(self.system_info)}
self.gui_services = {}
from jackify.backend.services.protontricks_detection_service import ProtontricksDetectionService
self.protontricks_service = ProtontricksDetectionService(steamdeck=self.system_info.is_steamdeck)
from jackify.backend.services.update_service import UpdateService
from jackify import __version__
self.update_service = UpdateService(__version__)
_debug_print(f"GUI Backend initialized - Steam Deck: {self.system_info.is_steamdeck}")
def _is_steamdeck(self):
try:
if os.path.exists("/etc/os-release"):
with open("/etc/os-release", "r") as f:
content = f.read()
if "steamdeck" in content:
return True
return False
except Exception:
return False
def _apply_resource_limits(self):
try:
from jackify.backend.services.resource_manager import ResourceManager
resource_manager = ResourceManager()
success = resource_manager.apply_recommended_limits()
if success:
status = resource_manager.get_limit_status()
if status['target_achieved']:
_debug_print(f"Resource limits optimized: file descriptors set to {status['current_soft']}")
else:
print(f"Resource limits improved: file descriptors increased to {status['current_soft']} (target: {status['target_limit']})")
else:
status = resource_manager.get_limit_status()
print(f"Warning: Could not optimize resource limits: current file descriptors={status['current_soft']}, target={status['target_limit']}")
from jackify.backend.handlers.config_handler import ConfigHandler
config_handler = ConfigHandler()
if config_handler.get('debug_mode', False):
instructions = resource_manager.get_manual_increase_instructions()
print(f"Manual increase instructions available for {instructions['distribution']}")
except Exception as e:
print(f"Warning: Error applying resource limits: {e}")

View File

@@ -0,0 +1,117 @@
"""
Main window dialogs and cleanup mixin.
Settings, About, open URL, cleanup_processes, closeEvent.
"""
import os
import subprocess
from jackify.frontends.gui.dialogs.settings_dialog import SettingsDialog
class MainWindowDialogsMixin:
"""Mixin for settings/about dialogs, open URL, and cleanup."""
def open_settings_dialog(self):
try:
if self._settings_dialog is not None:
try:
if self._settings_dialog.isVisible():
self._settings_dialog.raise_()
self._settings_dialog.activateWindow()
return
else:
self._settings_dialog = None
except RuntimeError:
self._settings_dialog = None
dlg = SettingsDialog(self)
self._settings_dialog = dlg
def on_dialog_finished():
self._settings_dialog = None
dlg.finished.connect(on_dialog_finished)
dlg.exec()
except Exception as e:
print(f"[ERROR] Exception in open_settings_dialog: {e}")
import traceback
traceback.print_exc()
self._settings_dialog = None
def open_about_dialog(self):
try:
from jackify.frontends.gui.dialogs.about_dialog import AboutDialog
if self._about_dialog is not None:
try:
if self._about_dialog.isVisible():
self._about_dialog.raise_()
self._about_dialog.activateWindow()
return
else:
self._about_dialog = None
except RuntimeError:
self._about_dialog = None
dlg = AboutDialog(self.system_info, self)
self._about_dialog = dlg
def on_dialog_finished():
self._about_dialog = None
dlg.finished.connect(on_dialog_finished)
dlg.exec()
except Exception as e:
print(f"[ERROR] Exception in open_about_dialog: {e}")
import traceback
traceback.print_exc()
self._about_dialog = None
def _open_url(self, url: str):
env = os.environ.copy()
appimage_vars = [
'LD_LIBRARY_PATH', 'PYTHONPATH', 'PYTHONHOME',
'QT_PLUGIN_PATH', 'QML2_IMPORT_PATH',
]
if 'APPIMAGE' in env or 'APPDIR' in env:
for var in appimage_vars:
env.pop(var, None)
subprocess.Popen(
['xdg-open', url],
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
def cleanup_processes(self):
try:
if hasattr(self, '_update_thread') and self._update_thread is not None:
if self._update_thread.isRunning():
self._update_thread.quit()
self._update_thread.wait(2000)
self._update_thread = None
if hasattr(self, '_gallery_cache_preload_thread') and self._gallery_cache_preload_thread is not None:
if self._gallery_cache_preload_thread.isRunning():
self._gallery_cache_preload_thread.quit()
self._gallery_cache_preload_thread.wait(2000)
self._gallery_cache_preload_thread = None
for service in self.gui_services.values():
if hasattr(service, 'cleanup'):
service.cleanup()
screens = [
self.modlist_tasks_screen, self.install_modlist_screen,
self.configure_new_modlist_screen, self.configure_existing_modlist_screen,
]
for screen in screens:
if hasattr(screen, 'cleanup_processes'):
screen.cleanup_processes()
elif hasattr(screen, 'cleanup'):
screen.cleanup()
try:
subprocess.run(['pkill', '-f', 'jackify-engine'], timeout=5, capture_output=True)
except Exception:
pass
except Exception as e:
print(f"Error during cleanup: {e}")
def closeEvent(self, event):
self._save_geometry_on_quit()
self.cleanup_processes()
event.accept()

View File

@@ -0,0 +1,207 @@
"""
Main window geometry and resize mixin.
Window flags, save/restore geometry, compact mode, responsive minimum, resize handling.
"""
from PySide6.QtWidgets import QMainWindow, QApplication
from PySide6.QtCore import Qt, QTimer, QRect
from jackify.frontends.gui.utils import get_screen_geometry, set_responsive_minimum
ENABLE_WINDOW_HEIGHT_ANIMATION = False
def _debug_print(message):
from jackify.backend.handlers.config_handler import ConfigHandler
ch = ConfigHandler()
if ch.get('debug_mode', False):
print(message)
class MainWindowGeometryMixin:
"""Mixin for window geometry, save/restore, compact mode, and resize behavior."""
def _apply_standard_window_flags(self):
window_flags = self.windowFlags()
window_flags |= (
Qt.Window
| Qt.WindowTitleHint
| Qt.WindowSystemMenuHint
| Qt.WindowMinimizeButtonHint
| Qt.WindowMaximizeButtonHint
| Qt.WindowCloseButtonHint
)
window_flags &= ~Qt.CustomizeWindowHint
self.setWindowFlags(window_flags)
def _restore_geometry(self):
width, height = self._calculate_initial_window_size()
height = min(height, self._compact_height)
self.resize(width, height)
self._center_on_screen(width, height)
def _save_geometry_on_quit(self):
if self._is_compact_mode():
self._save_geometry()
else:
from PySide6.QtCore import QSettings
settings = QSettings("Jackify", "Jackify")
settings.remove("windowGeometry")
def _is_compact_mode(self) -> bool:
try:
if hasattr(self, 'install_modlist_screen') and hasattr(self.install_modlist_screen, 'show_details_checkbox'):
if self.install_modlist_screen.show_details_checkbox.isChecked():
return False
if hasattr(self, 'install_ttw_screen') and hasattr(self.install_ttw_screen, 'show_details_checkbox'):
if self.install_ttw_screen.show_details_checkbox.isChecked():
return False
if hasattr(self, 'configure_new_modlist_screen') and hasattr(self.configure_new_modlist_screen, 'show_details_checkbox'):
if self.configure_new_modlist_screen.show_details_checkbox.isChecked():
return False
if hasattr(self, 'configure_existing_modlist_screen') and hasattr(self.configure_existing_modlist_screen, 'show_details_checkbox'):
if self.configure_existing_modlist_screen.show_details_checkbox.isChecked():
return False
except Exception:
pass
return True
def _save_geometry(self):
from PySide6.QtCore import QSettings
settings = QSettings("Jackify", "Jackify")
settings.setValue("windowGeometry", self.saveGeometry())
def apply_responsive_minimum(self, min_width: int = 1100, min_height: int = 600):
set_responsive_minimum(self, min_width=min_width, min_height=min_height, margin=self._window_margin)
def _calculate_initial_window_size(self):
_, _, screen_width, screen_height = get_screen_geometry(self)
if not screen_width or not screen_height:
return (self._base_min_width, self._base_min_height)
width = min(
max(self._base_min_width, int(screen_width * 0.85)),
screen_width - self._window_margin
)
height = min(
max(self._base_min_height, int(screen_height * 0.75)),
screen_height - self._window_margin
)
return (width, height)
def _center_on_screen(self, width: int, height: int):
_, _, screen_width, screen_height = get_screen_geometry(self)
if not screen_width or not screen_height:
return
x = max(0, (screen_width - width) // 2)
y = max(0, (screen_height - height) // 2)
self.move(x, y)
def _ensure_within_available_geometry(self):
from PySide6.QtCore import QRect
_, _, screen_width, screen_height = get_screen_geometry(self)
if not screen_width or not screen_height:
return
current_geometry = self.geometry()
new_width = min(current_geometry.width(), screen_width - self._window_margin)
new_height = min(current_geometry.height(), screen_height - self._window_margin)
new_width = max(new_width, self.minimumWidth())
new_height = max(new_height, self.minimumHeight())
new_x = min(max(current_geometry.x(), 0), screen_width - new_width)
new_y = min(max(current_geometry.y(), 0), screen_height - new_height)
self.setGeometry(new_x, new_y, new_width, new_height)
def _on_resize_event_geometry(self, event):
super().resizeEvent(event)
if self._is_compact_mode():
if not hasattr(self, '_geometry_save_timer'):
self._geometry_save_timer = QTimer()
self._geometry_save_timer.setSingleShot(True)
self._geometry_save_timer.timeout.connect(self._save_geometry)
self._geometry_save_timer.stop()
self._geometry_save_timer.start(500)
def _geometry_show_event(self, event):
super().showEvent(event)
if not self._initial_show_adjusted:
self._initial_show_adjusted = True
if not (hasattr(self, 'system_info') and self.system_info.is_steamdeck):
self.setWindowState(Qt.WindowNoState)
self.apply_responsive_minimum(self._base_min_width, self._base_min_height)
self._ensure_within_available_geometry()
def _maintain_fullscreen_on_deck(self, index):
if hasattr(self, 'system_info') and self.system_info.is_steamdeck:
if not self.isMaximized():
self.showMaximized()
def _on_child_resize_request(self, mode: str):
_debug_print(f"DEBUG: _on_child_resize_request called with mode='{mode}', current_size={self.size()}")
try:
if self.system_info and self.system_info.is_steamdeck:
_debug_print("DEBUG: Steam Deck detected, ignoring resize request")
try:
if hasattr(self, 'install_ttw_screen') and self.install_ttw_screen.show_details_checkbox:
self.install_ttw_screen.show_details_checkbox.setVisible(False)
except Exception:
pass
return
except Exception:
pass
if mode == "expand":
target_height = self._compact_height + self._details_extra_height
self._resize_height(target_height)
elif mode == "collapse" or mode == "compact":
self._resize_height(self._compact_height)
else:
self.apply_responsive_minimum(self._base_min_width, self._base_min_height)
def _resize_height(self, requested_height: int):
target_height = self._clamp_height_to_screen(requested_height)
self.apply_responsive_minimum(self._base_min_width, self._base_min_height)
if ENABLE_WINDOW_HEIGHT_ANIMATION:
self._animate_height(target_height)
return
geom = self.geometry()
new_y = geom.y()
_, _, _, screen_height = get_screen_geometry(self)
max_bottom = max(self._base_min_height, screen_height - self._window_margin)
if new_y + target_height > max_bottom:
new_y = max(0, max_bottom - target_height)
self._programmatic_resize = True
self.setGeometry(geom.x(), new_y, geom.width(), target_height)
QTimer.singleShot(100, lambda: setattr(self, '_programmatic_resize', False))
def _clamp_height_to_screen(self, requested_height: int) -> int:
_, _, _, screen_height = get_screen_geometry(self)
available = max(self._base_min_height, screen_height - self._window_margin)
return max(self._base_min_height, min(requested_height, available))
def _animate_height(self, target_height: int, duration_ms: int = 180):
try:
from PySide6.QtCore import QEasingCurve, QPropertyAnimation, QRect
except Exception:
before = self.size()
self._programmatic_resize = True
self.resize(self.size().width(), target_height)
_debug_print(f"DEBUG: Animated fallback resize from {before} to {self.size()}")
QTimer.singleShot(100, lambda: setattr(self, '_programmatic_resize', False))
return
start_rect = self.geometry()
end_rect = QRect(start_rect.x(), start_rect.y(), start_rect.width(), self._clamp_height_to_screen(target_height))
screen = QApplication.primaryScreen()
if screen:
screen_geometry = screen.availableGeometry()
would_be_bottom = start_rect.y() + target_height
if would_be_bottom > screen_geometry.bottom():
new_y = screen_geometry.bottom() - target_height
if new_y < screen_geometry.top():
new_y = screen_geometry.top()
end_rect.moveTop(new_y)
self._resize_anim = QPropertyAnimation(self, b"geometry")
self._resize_anim.setDuration(duration_ms)
self._resize_anim.setEasingCurve(QEasingCurve.OutCubic)
self._resize_anim.setStartValue(start_rect)
self._resize_anim.setEndValue(end_rect)
self._programmatic_resize = True
self._resize_anim.finished.connect(lambda: setattr(self, '_programmatic_resize', False))
self._resize_anim.start()

View File

@@ -0,0 +1,102 @@
"""
Main window startup and background tasks mixin.
Gallery cache preload, protontricks check, update check.
"""
import sys
from PySide6.QtCore import QThread, Signal, QTimer
from PySide6.QtWidgets import QDialog
def _debug_print(message):
from jackify.backend.handlers.config_handler import ConfigHandler
ch = ConfigHandler()
if ch.get('debug_mode', False):
print(message)
class MainWindowStartupMixin:
"""Mixin for startup and background tasks."""
def _start_gallery_cache_preload(self):
from PySide6.QtCore import QThread, Signal
class GalleryCachePreloadThread(QThread):
finished_signal = Signal(bool, str)
def run(self):
try:
from jackify.backend.services.modlist_gallery_service import ModlistGalleryService
service = ModlistGalleryService()
metadata = service.fetch_modlist_metadata(
include_validation=False,
include_search_index=True,
sort_by="title",
force_refresh=False
)
if metadata:
modlists_with_mods = sum(1 for m in metadata.modlists if hasattr(m, 'mods') and m.mods)
if modlists_with_mods > 0:
_debug_print(f"Gallery cache ready ({modlists_with_mods} modlists with mods)")
else:
_debug_print("Gallery cache updated")
else:
_debug_print("Failed to load gallery cache")
except Exception as e:
_debug_print(f"Gallery cache preload error: {str(e)}")
self._gallery_cache_preload_thread = GalleryCachePreloadThread()
self._gallery_cache_preload_thread.start()
_debug_print("Started background gallery cache preload")
def _check_protontricks_on_startup(self):
try:
method = self.config_handler.get('component_installation_method', 'winetricks')
if method != 'system_protontricks':
_debug_print(f"Skipping protontricks check (current method: {method}).")
return
is_installed, installation_type, details = self.protontricks_service.detect_protontricks()
if not is_installed:
print(f"Protontricks not found: {details}")
from jackify.frontends.gui.dialogs.protontricks_error_dialog import ProtontricksErrorDialog
dialog = ProtontricksErrorDialog(self.protontricks_service, self)
result = dialog.exec()
if result == QDialog.Rejected:
print("User chose to exit due to missing protontricks")
sys.exit(1)
else:
_debug_print(f"Protontricks detected: {details}")
except Exception as e:
print(f"Error checking protontricks: {e}")
def _check_for_updates_on_startup(self):
try:
_debug_print("Checking for updates on startup...")
class UpdateCheckThread(QThread):
update_available = Signal(object)
def __init__(self, update_service):
super().__init__()
self.update_service = update_service
def run(self):
update_info = self.update_service.check_for_updates()
if update_info:
self.update_available.emit(update_info)
def on_update_available(update_info):
_debug_print(f"Update available: v{update_info.version}")
def show_update_dialog():
from jackify.frontends.gui.dialogs.update_dialog import UpdateDialog
dialog = UpdateDialog(update_info, self.update_service, self)
dialog.exec()
QTimer.singleShot(1000, show_update_dialog)
self._update_thread = UpdateCheckThread(self.update_service)
self._update_thread.update_available.connect(on_update_available)
self._update_thread.start()
except Exception as e:
_debug_print(f"Error setting up update check: {e}")

View File

@@ -0,0 +1,187 @@
"""
Main window UI setup mixin.
Stacked widget, screens, bottom bar, screen change handling.
"""
import sys
from PySide6.QtWidgets import (
QWidget, QLabel, QVBoxLayout, QHBoxLayout,
QStackedWidget, QSizePolicy,
)
from PySide6.QtCore import Qt
from jackify import __version__
from jackify.frontends.gui.shared_theme import DEBUG_BORDERS
from jackify.frontends.gui.widgets.feature_placeholder import FeaturePlaceholder
def _debug_print(message):
from jackify.backend.handlers.config_handler import ConfigHandler
ch = ConfigHandler()
if ch.get('debug_mode', False):
print(message)
class MainWindowUIMixin:
"""Mixin for main window UI: stacked widget, screens, bottom bar."""
def _setup_ui(self, dev_mode=False):
self.stacked_widget = QStackedWidget()
from jackify.frontends.gui.screens import (
MainMenu, ModlistTasksScreen, AdditionalTasksScreen,
InstallModlistScreen, ConfigureNewModlistScreen, ConfigureExistingModlistScreen,
)
from jackify.frontends.gui.screens.install_ttw import InstallTTWScreen
from jackify.frontends.gui.screens.wabbajack_installer import WabbajackInstallerScreen
self.main_menu = MainMenu(stacked_widget=self.stacked_widget, dev_mode=dev_mode)
self.feature_placeholder = FeaturePlaceholder(stacked_widget=self.stacked_widget)
self.modlist_tasks_screen = ModlistTasksScreen(
stacked_widget=self.stacked_widget, main_menu_index=0, dev_mode=dev_mode
)
self.additional_tasks_screen = AdditionalTasksScreen(
stacked_widget=self.stacked_widget, main_menu_index=0, system_info=self.system_info
)
self.install_modlist_screen = InstallModlistScreen(
stacked_widget=self.stacked_widget, main_menu_index=0, system_info=self.system_info
)
self.configure_new_modlist_screen = ConfigureNewModlistScreen(
stacked_widget=self.stacked_widget, main_menu_index=0, system_info=self.system_info
)
self.configure_existing_modlist_screen = ConfigureExistingModlistScreen(
stacked_widget=self.stacked_widget, main_menu_index=0, system_info=self.system_info
)
self.install_ttw_screen = InstallTTWScreen(
stacked_widget=self.stacked_widget, main_menu_index=0, system_info=self.system_info
)
self.wabbajack_installer_screen = WabbajackInstallerScreen(
stacked_widget=self.stacked_widget, additional_tasks_index=3, system_info=self.system_info
)
try:
self.install_ttw_screen.resize_request.connect(self._on_child_resize_request)
except Exception:
pass
try:
self.install_modlist_screen.resize_request.connect(self._on_child_resize_request)
except Exception:
pass
try:
self.configure_new_modlist_screen.resize_request.connect(self._on_child_resize_request)
except Exception:
pass
try:
self.configure_existing_modlist_screen.resize_request.connect(self._on_child_resize_request)
except Exception:
pass
try:
self.wabbajack_installer_screen.resize_request.connect(self._on_child_resize_request)
except Exception:
pass
self.stacked_widget.addWidget(self.main_menu)
self.stacked_widget.addWidget(self.feature_placeholder)
self.stacked_widget.addWidget(self.modlist_tasks_screen)
self.stacked_widget.addWidget(self.additional_tasks_screen)
self.stacked_widget.addWidget(self.install_modlist_screen)
self.stacked_widget.addWidget(self.install_ttw_screen)
self.stacked_widget.addWidget(self.configure_new_modlist_screen)
self.stacked_widget.addWidget(self.wabbajack_installer_screen)
self.stacked_widget.addWidget(self.configure_existing_modlist_screen)
self.stacked_widget.currentChanged.connect(self._debug_screen_change)
self.stacked_widget.currentChanged.connect(self._maintain_fullscreen_on_deck)
bottom_bar = QWidget()
bottom_bar_layout = QHBoxLayout()
bottom_bar_layout.setContentsMargins(10, 2, 10, 2)
bottom_bar_layout.setSpacing(0)
bottom_bar.setLayout(bottom_bar_layout)
bottom_bar.setFixedHeight(32)
bottom_bar_style = "background-color: #181818; border-top: 1px solid #222;"
if DEBUG_BORDERS:
bottom_bar_style += " border: 2px solid lime;"
bottom_bar.setStyleSheet(bottom_bar_style)
version_label = QLabel(f"Jackify v{__version__}")
version_label.setStyleSheet("color: #bbb; font-size: 13px;")
bottom_bar_layout.addWidget(version_label, alignment=Qt.AlignLeft)
bottom_bar_layout.addStretch(1)
kofi_link = QLabel('<a href="#" style="color:#72A5F2; text-decoration:none;">Support on Ko-fi</a>')
kofi_link.setStyleSheet("color: #72A5F2; font-size: 13px;")
kofi_link.setTextInteractionFlags(Qt.TextBrowserInteraction)
kofi_link.setOpenExternalLinks(False)
kofi_link.linkActivated.connect(lambda: self._open_url("https://ko-fi.com/omni1"))
kofi_link.setToolTip("Support Jackify development")
bottom_bar_layout.addWidget(kofi_link, alignment=Qt.AlignCenter)
bottom_bar_layout.addStretch(1)
settings_btn = QLabel('<a href="#" style="color:#6cf; text-decoration:none;">Settings</a>')
settings_btn.setStyleSheet("color: #6cf; font-size: 13px; padding-right: 8px;")
settings_btn.setTextInteractionFlags(Qt.TextBrowserInteraction)
settings_btn.setOpenExternalLinks(False)
settings_btn.linkActivated.connect(self.open_settings_dialog)
bottom_bar_layout.addWidget(settings_btn, alignment=Qt.AlignRight)
about_btn = QLabel('<a href="#" style="color:#6cf; text-decoration:none;">About</a>')
about_btn.setStyleSheet("color: #6cf; font-size: 13px; padding-right: 8px;")
about_btn.setTextInteractionFlags(Qt.TextBrowserInteraction)
about_btn.setOpenExternalLinks(False)
about_btn.linkActivated.connect(self.open_about_dialog)
bottom_bar_layout.addWidget(about_btn, alignment=Qt.AlignRight)
central_widget = QWidget()
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addWidget(self.stacked_widget)
main_layout.addWidget(bottom_bar)
self.stacked_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)
self.stacked_widget.setCurrentIndex(0)
self._check_protontricks_on_startup()
def _debug_screen_change(self, index):
try:
idx = int(index) if index is not None else 0
widget = self.stacked_widget.widget(idx)
except (OverflowError, TypeError, ValueError):
widget = self.stacked_widget.currentWidget()
idx = None
if widget and hasattr(widget, 'reset_screen_to_defaults'):
widget.reset_screen_to_defaults()
from jackify.backend.handlers.config_handler import ConfigHandler
config_handler = ConfigHandler()
if not config_handler.get('debug_mode', False):
return
if idx is None:
return
try:
screen_names = {
0: "Main Menu",
1: "Feature Placeholder",
2: "Modlist Tasks Menu",
3: "Additional Tasks Menu",
4: "Install Modlist Screen",
5: "Install TTW Screen",
6: "Configure New Modlist",
7: "Wabbajack Installer",
8: "Configure Existing Modlist",
}
screen_name = screen_names.get(idx, f"Unknown Screen (Index {idx})")
widget = self.stacked_widget.widget(idx)
except (OverflowError, TypeError, ValueError):
return
widget_class = widget.__class__.__name__ if widget else "None"
print(f"[DEBUG] Screen changed to Index {idx}: {screen_name} (Widget: {widget_class})", file=sys.stderr)
if idx == 4:
print(" Install Modlist Screen details:", file=sys.stderr)
print(f" - Widget type: {type(widget)}", file=sys.stderr)
print(f" - Widget file: {widget.__class__.__module__}", file=sys.stderr)
if hasattr(widget, 'windowTitle'):
print(f" - Window title: {widget.windowTitle()}", file=sys.stderr)
if hasattr(widget, 'layout'):
layout = widget.layout()
if layout:
print(f" - Layout type: {type(layout)}", file=sys.stderr)
print(f" - Layout children count: {layout.count()}", file=sys.stderr)