mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Sync from development - prepare for v0.2.0.8
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
|||||||
# Jackify Changelog
|
# Jackify Changelog
|
||||||
|
|
||||||
|
## v0.2.0.8 - Bug Fixes and Improvements
|
||||||
|
**Release Date:** 2025-12-29
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fixed Configure New/Existing/TTW screens missing Activity tab and progress updates
|
||||||
|
- Fixed cancel/back buttons crashing in Configure workflows
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- Install directory now auto-appends modlist name when selected from gallery
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- Mod filter temporarily disabled in gallery due to technical issue (tag and game filters still work)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.2.0.7 - Critical Auth Fix
|
## v0.2.0.7 - Critical Auth Fix
|
||||||
**Release Date:** 2025-12-28
|
**Release Date:** 2025-12-28
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
|
|||||||
Wabbajack modlists natively on Linux systems.
|
Wabbajack modlists natively on Linux systems.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.2.0.7"
|
__version__ = "0.2.0.8"
|
||||||
|
|||||||
@@ -1337,6 +1337,15 @@ class JackifyMainWindow(QMainWindow):
|
|||||||
self.install_modlist_screen.resize_request.connect(self._on_child_resize_request)
|
self.install_modlist_screen.resize_request.connect(self._on_child_resize_request)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Let Configure screens request window resize for expand/collapse
|
||||||
|
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
|
||||||
|
|
||||||
# Add screens to stacked widget
|
# Add screens to stacked widget
|
||||||
self.stacked_widget.addWidget(self.main_menu) # Index 0: Main Menu
|
self.stacked_widget.addWidget(self.main_menu) # Index 0: Main Menu
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ def debug_print(message):
|
|||||||
|
|
||||||
class ConfigureExistingModlistScreen(QWidget):
|
class ConfigureExistingModlistScreen(QWidget):
|
||||||
steam_restart_finished = Signal(bool, str)
|
steam_restart_finished = Signal(bool, str)
|
||||||
|
resize_request = Signal(str)
|
||||||
def __init__(self, stacked_widget=None, main_menu_index=0):
|
def __init__(self, stacked_widget=None, main_menu_index=0):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
debug_print("DEBUG: ConfigureExistingModlistScreen __init__ called")
|
debug_print("DEBUG: ConfigureExistingModlistScreen __init__ called")
|
||||||
@@ -220,27 +221,49 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
if self.debug:
|
if self.debug:
|
||||||
user_config_widget.setStyleSheet("border: 2px solid orange;")
|
user_config_widget.setStyleSheet("border: 2px solid orange;")
|
||||||
user_config_widget.setToolTip("USER_CONFIG_WIDGET")
|
user_config_widget.setToolTip("USER_CONFIG_WIDGET")
|
||||||
# Right: Activity window (FileProgressList widget)
|
# Right: Tabbed interface with Activity and Process Monitor
|
||||||
# Fixed size policy to prevent shrinking when window expands
|
# Both tabs are always available, user can switch between them
|
||||||
|
self.process_monitor = QTextEdit()
|
||||||
|
self.process_monitor.setReadOnly(True)
|
||||||
|
self.process_monitor.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard)
|
||||||
|
self.process_monitor.setMinimumSize(QSize(300, 20))
|
||||||
|
self.process_monitor.setStyleSheet(f"background: #222; color: {JACKIFY_COLOR_BLUE}; font-family: monospace; font-size: 11px; border: 1px solid #444;")
|
||||||
|
self.process_monitor_heading = QLabel("<b>[Process Monitor]</b>")
|
||||||
|
self.process_monitor_heading.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 13px; margin-bottom: 2px;")
|
||||||
|
self.process_monitor_heading.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
|
process_vbox = QVBoxLayout()
|
||||||
|
process_vbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
process_vbox.setSpacing(2)
|
||||||
|
process_vbox.addWidget(self.process_monitor_heading)
|
||||||
|
process_vbox.addWidget(self.process_monitor)
|
||||||
|
process_monitor_widget = QWidget()
|
||||||
|
process_monitor_widget.setLayout(process_vbox)
|
||||||
|
process_monitor_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
if self.debug:
|
||||||
|
process_monitor_widget.setStyleSheet("border: 2px solid purple;")
|
||||||
|
process_monitor_widget.setToolTip("PROCESS_MONITOR")
|
||||||
|
self.process_monitor_widget = process_monitor_widget
|
||||||
|
|
||||||
|
# Set up File Progress List (Activity tab)
|
||||||
self.file_progress_list.setMinimumSize(QSize(300, 20))
|
self.file_progress_list.setMinimumSize(QSize(300, 20))
|
||||||
self.file_progress_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.file_progress_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
|
||||||
activity_widget = QWidget()
|
# Create tab widget to hold both Activity and Process Monitor
|
||||||
activity_layout = QVBoxLayout()
|
self.activity_tabs = QTabWidget()
|
||||||
activity_layout.setContentsMargins(0, 0, 0, 0)
|
self.activity_tabs.setStyleSheet("QTabWidget::pane { background: #222; border: 1px solid #444; } QTabBar::tab { background: #222; color: #ccc; padding: 6px 16px; } QTabBar::tab:selected { background: #333; color: #3fd0ea; } QTabWidget { margin: 0px; padding: 0px; } QTabBar { margin: 0px; padding: 0px; }")
|
||||||
activity_layout.setSpacing(0)
|
self.activity_tabs.setContentsMargins(0, 0, 0, 0)
|
||||||
activity_layout.addWidget(self.file_progress_list)
|
self.activity_tabs.setDocumentMode(False)
|
||||||
activity_widget.setLayout(activity_layout)
|
self.activity_tabs.setTabPosition(QTabWidget.North)
|
||||||
if self.debug:
|
if self.debug:
|
||||||
activity_widget.setStyleSheet("border: 2px solid purple;")
|
self.activity_tabs.setStyleSheet("border: 2px solid cyan;")
|
||||||
activity_widget.setToolTip("ACTIVITY_WINDOW")
|
self.activity_tabs.setToolTip("ACTIVITY_TABS")
|
||||||
upper_hbox.addWidget(user_config_widget, stretch=11)
|
|
||||||
upper_hbox.addWidget(activity_widget, stretch=9)
|
|
||||||
|
|
||||||
# Keep legacy process monitor hidden (for compatibility with existing code)
|
# Add both widgets as tabs
|
||||||
self.process_monitor = QTextEdit()
|
self.activity_tabs.addTab(self.file_progress_list, "Activity")
|
||||||
self.process_monitor.setReadOnly(True)
|
self.activity_tabs.addTab(process_monitor_widget, "Process Monitor")
|
||||||
self.process_monitor.setVisible(False) # Hidden in compact mode
|
|
||||||
|
upper_hbox.addWidget(user_config_widget, stretch=11)
|
||||||
|
upper_hbox.addWidget(self.activity_tabs, stretch=9)
|
||||||
upper_hbox.setAlignment(Qt.AlignTop)
|
upper_hbox.setAlignment(Qt.AlignTop)
|
||||||
upper_section_widget = QWidget()
|
upper_section_widget = QWidget()
|
||||||
upper_section_widget.setLayout(upper_hbox)
|
upper_section_widget.setLayout(upper_hbox)
|
||||||
@@ -490,6 +513,42 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _handle_progress_update(self, text):
|
||||||
|
"""Handle progress updates - update console, activity window, and progress indicator"""
|
||||||
|
# Always append to console
|
||||||
|
self._safe_append_text(text)
|
||||||
|
|
||||||
|
# Parse the message to update UI widgets
|
||||||
|
message_lower = text.lower()
|
||||||
|
|
||||||
|
# Update progress indicator based on key status messages
|
||||||
|
if "creating steam shortcut" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Creating Steam shortcut...", 10)
|
||||||
|
elif "restarting steam" in message_lower or "restart steam" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Restarting Steam...", 20)
|
||||||
|
elif "steam restart" in message_lower and "success" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Steam restarted successfully", 30)
|
||||||
|
elif "creating proton prefix" in message_lower or "prefix creation" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Creating Proton prefix...", 50)
|
||||||
|
elif "prefix created" in message_lower or "prefix creation" in message_lower and "success" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Proton prefix created", 70)
|
||||||
|
elif "verifying" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Verifying setup...", 80)
|
||||||
|
elif "steam integration complete" in message_lower or "configuration complete" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Configuration complete", 95)
|
||||||
|
elif "complete" in message_lower and not "prefix" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Finishing up...", 90)
|
||||||
|
|
||||||
|
# Update activity window with generic configuration status
|
||||||
|
# Only update if message contains meaningful progress (not blank lines or separators)
|
||||||
|
if text.strip() and not text.strip().startswith('='):
|
||||||
|
# Show generic "Configuring modlist..." in activity window
|
||||||
|
self.file_progress_list.update_files(
|
||||||
|
[],
|
||||||
|
current_phase="Configuring",
|
||||||
|
summary_info={"current": 1, "total": 1, "label": "Setting up modlist"}
|
||||||
|
)
|
||||||
|
|
||||||
def _safe_append_text(self, text):
|
def _safe_append_text(self, text):
|
||||||
"""Append text with professional auto-scroll behavior"""
|
"""Append text with professional auto-scroll behavior"""
|
||||||
# Write all messages to log file
|
# Write all messages to log file
|
||||||
@@ -660,7 +719,7 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
|
|
||||||
# Create and start the configuration thread
|
# Create and start the configuration thread
|
||||||
self.config_thread = ConfigurationThread(modlist_name, install_dir, resolution)
|
self.config_thread = ConfigurationThread(modlist_name, install_dir, resolution)
|
||||||
self.config_thread.progress_update.connect(self._safe_append_text)
|
self.config_thread.progress_update.connect(self._handle_progress_update)
|
||||||
self.config_thread.configuration_complete.connect(self.on_configuration_complete)
|
self.config_thread.configuration_complete.connect(self.on_configuration_complete)
|
||||||
self.config_thread.error_occurred.connect(self.on_configuration_error)
|
self.config_thread.error_occurred.connect(self.on_configuration_error)
|
||||||
self.config_thread.start()
|
self.config_thread.start()
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class SelectionDialog(QDialog):
|
|||||||
|
|
||||||
class ConfigureNewModlistScreen(QWidget):
|
class ConfigureNewModlistScreen(QWidget):
|
||||||
steam_restart_finished = Signal(bool, str)
|
steam_restart_finished = Signal(bool, str)
|
||||||
|
resize_request = Signal(str)
|
||||||
def __init__(self, stacked_widget=None, main_menu_index=0):
|
def __init__(self, stacked_widget=None, main_menu_index=0):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
debug_print("DEBUG: ConfigureNewModlistScreen __init__ called")
|
debug_print("DEBUG: ConfigureNewModlistScreen __init__ called")
|
||||||
@@ -300,27 +301,49 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
if self.debug:
|
if self.debug:
|
||||||
user_config_widget.setStyleSheet("border: 2px solid orange;")
|
user_config_widget.setStyleSheet("border: 2px solid orange;")
|
||||||
user_config_widget.setToolTip("USER_CONFIG_WIDGET")
|
user_config_widget.setToolTip("USER_CONFIG_WIDGET")
|
||||||
# Right: Activity window (FileProgressList widget)
|
# Right: Tabbed interface with Activity and Process Monitor
|
||||||
# Fixed size policy to prevent shrinking when window expands
|
# Both tabs are always available, user can switch between them
|
||||||
|
self.process_monitor = QTextEdit()
|
||||||
|
self.process_monitor.setReadOnly(True)
|
||||||
|
self.process_monitor.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard)
|
||||||
|
self.process_monitor.setMinimumSize(QSize(300, 20))
|
||||||
|
self.process_monitor.setStyleSheet(f"background: #222; color: {JACKIFY_COLOR_BLUE}; font-family: monospace; font-size: 11px; border: 1px solid #444;")
|
||||||
|
self.process_monitor_heading = QLabel("<b>[Process Monitor]</b>")
|
||||||
|
self.process_monitor_heading.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 13px; margin-bottom: 2px;")
|
||||||
|
self.process_monitor_heading.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
|
process_vbox = QVBoxLayout()
|
||||||
|
process_vbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
process_vbox.setSpacing(2)
|
||||||
|
process_vbox.addWidget(self.process_monitor_heading)
|
||||||
|
process_vbox.addWidget(self.process_monitor)
|
||||||
|
process_monitor_widget = QWidget()
|
||||||
|
process_monitor_widget.setLayout(process_vbox)
|
||||||
|
process_monitor_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
if self.debug:
|
||||||
|
process_monitor_widget.setStyleSheet("border: 2px solid purple;")
|
||||||
|
process_monitor_widget.setToolTip("PROCESS_MONITOR")
|
||||||
|
self.process_monitor_widget = process_monitor_widget
|
||||||
|
|
||||||
|
# Set up File Progress List (Activity tab)
|
||||||
self.file_progress_list.setMinimumSize(QSize(300, 20))
|
self.file_progress_list.setMinimumSize(QSize(300, 20))
|
||||||
self.file_progress_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.file_progress_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
|
||||||
activity_widget = QWidget()
|
# Create tab widget to hold both Activity and Process Monitor
|
||||||
activity_layout = QVBoxLayout()
|
self.activity_tabs = QTabWidget()
|
||||||
activity_layout.setContentsMargins(0, 0, 0, 0)
|
self.activity_tabs.setStyleSheet("QTabWidget::pane { background: #222; border: 1px solid #444; } QTabBar::tab { background: #222; color: #ccc; padding: 6px 16px; } QTabBar::tab:selected { background: #333; color: #3fd0ea; } QTabWidget { margin: 0px; padding: 0px; } QTabBar { margin: 0px; padding: 0px; }")
|
||||||
activity_layout.setSpacing(0)
|
self.activity_tabs.setContentsMargins(0, 0, 0, 0)
|
||||||
activity_layout.addWidget(self.file_progress_list)
|
self.activity_tabs.setDocumentMode(False)
|
||||||
activity_widget.setLayout(activity_layout)
|
self.activity_tabs.setTabPosition(QTabWidget.North)
|
||||||
if self.debug:
|
if self.debug:
|
||||||
activity_widget.setStyleSheet("border: 2px solid purple;")
|
self.activity_tabs.setStyleSheet("border: 2px solid cyan;")
|
||||||
activity_widget.setToolTip("ACTIVITY_WINDOW")
|
self.activity_tabs.setToolTip("ACTIVITY_TABS")
|
||||||
upper_hbox.addWidget(user_config_widget, stretch=11)
|
|
||||||
upper_hbox.addWidget(activity_widget, stretch=9)
|
|
||||||
|
|
||||||
# Keep legacy process monitor hidden (for compatibility with existing code)
|
# Add both widgets as tabs
|
||||||
self.process_monitor = QTextEdit()
|
self.activity_tabs.addTab(self.file_progress_list, "Activity")
|
||||||
self.process_monitor.setReadOnly(True)
|
self.activity_tabs.addTab(process_monitor_widget, "Process Monitor")
|
||||||
self.process_monitor.setVisible(False) # Hidden in compact mode
|
|
||||||
|
upper_hbox.addWidget(user_config_widget, stretch=11)
|
||||||
|
upper_hbox.addWidget(self.activity_tabs, stretch=9)
|
||||||
upper_hbox.setAlignment(Qt.AlignTop)
|
upper_hbox.setAlignment(Qt.AlignTop)
|
||||||
upper_section_widget = QWidget()
|
upper_section_widget = QWidget()
|
||||||
upper_section_widget.setLayout(upper_hbox)
|
upper_section_widget.setLayout(upper_hbox)
|
||||||
@@ -570,6 +593,42 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _handle_progress_update(self, text):
|
||||||
|
"""Handle progress updates - update console, activity window, and progress indicator"""
|
||||||
|
# Always append to console
|
||||||
|
self._safe_append_text(text)
|
||||||
|
|
||||||
|
# Parse the message to update UI widgets
|
||||||
|
message_lower = text.lower()
|
||||||
|
|
||||||
|
# Update progress indicator based on key status messages
|
||||||
|
if "creating steam shortcut" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Creating Steam shortcut...", 10)
|
||||||
|
elif "restarting steam" in message_lower or "restart steam" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Restarting Steam...", 20)
|
||||||
|
elif "steam restart" in message_lower and "success" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Steam restarted successfully", 30)
|
||||||
|
elif "creating proton prefix" in message_lower or "prefix creation" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Creating Proton prefix...", 50)
|
||||||
|
elif "prefix created" in message_lower or "prefix creation" in message_lower and "success" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Proton prefix created", 70)
|
||||||
|
elif "verifying" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Verifying setup...", 80)
|
||||||
|
elif "steam integration complete" in message_lower or "configuration complete" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Configuration complete", 95)
|
||||||
|
elif "complete" in message_lower and not "prefix" in message_lower:
|
||||||
|
self.progress_indicator.set_status("Finishing up...", 90)
|
||||||
|
|
||||||
|
# Update activity window with generic configuration status
|
||||||
|
# Only update if message contains meaningful progress (not blank lines or separators)
|
||||||
|
if text.strip() and not text.strip().startswith('='):
|
||||||
|
# Show generic "Configuring modlist..." in activity window
|
||||||
|
self.file_progress_list.update_files(
|
||||||
|
[],
|
||||||
|
current_phase="Configuring",
|
||||||
|
summary_info={"current": 1, "total": 1, "label": "Setting up modlist"}
|
||||||
|
)
|
||||||
|
|
||||||
def _safe_append_text(self, text):
|
def _safe_append_text(self, text):
|
||||||
"""Append text with professional auto-scroll behavior"""
|
"""Append text with professional auto-scroll behavior"""
|
||||||
# Write all messages to log file
|
# Write all messages to log file
|
||||||
@@ -635,6 +694,11 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
if hasattr(self, 'file_progress_list'):
|
if hasattr(self, 'file_progress_list'):
|
||||||
self.file_progress_list.stop_cpu_tracking()
|
self.file_progress_list.stop_cpu_tracking()
|
||||||
|
|
||||||
|
# Clean up automated prefix thread if running
|
||||||
|
if hasattr(self, 'automated_prefix_thread') and self.automated_prefix_thread.isRunning():
|
||||||
|
self.automated_prefix_thread.terminate()
|
||||||
|
self.automated_prefix_thread.wait(1000)
|
||||||
|
|
||||||
# Clean up configuration thread if running
|
# Clean up configuration thread if running
|
||||||
if hasattr(self, 'config_thread') and self.config_thread.isRunning():
|
if hasattr(self, 'config_thread') and self.config_thread.isRunning():
|
||||||
self.config_thread.terminate()
|
self.config_thread.terminate()
|
||||||
@@ -929,7 +993,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
|
|
||||||
# Create and start the thread
|
# Create and start the thread
|
||||||
self.automated_prefix_thread = AutomatedPrefixThread(modlist_name, install_dir, mo2_exe_path, _is_steamdeck)
|
self.automated_prefix_thread = AutomatedPrefixThread(modlist_name, install_dir, mo2_exe_path, _is_steamdeck)
|
||||||
self.automated_prefix_thread.progress_update.connect(self._safe_append_text)
|
self.automated_prefix_thread.progress_update.connect(self._handle_progress_update)
|
||||||
self.automated_prefix_thread.workflow_complete.connect(self._on_automated_prefix_complete)
|
self.automated_prefix_thread.workflow_complete.connect(self._on_automated_prefix_complete)
|
||||||
self.automated_prefix_thread.error_occurred.connect(self._on_automated_prefix_error)
|
self.automated_prefix_thread.error_occurred.connect(self._on_automated_prefix_error)
|
||||||
self.automated_prefix_thread.start()
|
self.automated_prefix_thread.start()
|
||||||
@@ -1323,7 +1387,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
|
|
||||||
# Start configuration thread
|
# Start configuration thread
|
||||||
self.config_thread = ConfigThread(updated_context)
|
self.config_thread = ConfigThread(updated_context)
|
||||||
self.config_thread.progress_update.connect(self._safe_append_text)
|
self.config_thread.progress_update.connect(self._handle_progress_update)
|
||||||
self.config_thread.configuration_complete.connect(self.on_configuration_complete)
|
self.config_thread.configuration_complete.connect(self.on_configuration_complete)
|
||||||
self.config_thread.error_occurred.connect(self.on_configuration_error)
|
self.config_thread.error_occurred.connect(self.on_configuration_error)
|
||||||
self.config_thread.start()
|
self.config_thread.start()
|
||||||
@@ -1424,7 +1488,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
|
|
||||||
# Create and start the configuration thread
|
# Create and start the configuration thread
|
||||||
self.config_thread = ConfigThread(updated_context)
|
self.config_thread = ConfigThread(updated_context)
|
||||||
self.config_thread.progress_update.connect(self._safe_append_text)
|
self.config_thread.progress_update.connect(self._handle_progress_update)
|
||||||
self.config_thread.configuration_complete.connect(self.on_configuration_complete)
|
self.config_thread.configuration_complete.connect(self.on_configuration_complete)
|
||||||
self.config_thread.error_occurred.connect(self.on_configuration_error)
|
self.config_thread.error_occurred.connect(self.on_configuration_error)
|
||||||
self.config_thread.start()
|
self.config_thread.start()
|
||||||
|
|||||||
@@ -1517,6 +1517,16 @@ class InstallModlistScreen(QWidget):
|
|||||||
'force_down': metadata.forceDown
|
'force_down': metadata.forceDown
|
||||||
}
|
}
|
||||||
self.modlist_name_edit.setText(metadata.title)
|
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
|
||||||
|
import re
|
||||||
|
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:
|
finally:
|
||||||
if cursor_overridden:
|
if cursor_overridden:
|
||||||
QApplication.restoreOverrideCursor()
|
QApplication.restoreOverrideCursor()
|
||||||
@@ -3953,11 +3963,11 @@ class InstallModlistScreen(QWidget):
|
|||||||
self.retry_automated_workflow_with_new_name(new_name)
|
self.retry_automated_workflow_with_new_name(new_name)
|
||||||
elif new_name == modlist_name:
|
elif new_name == modlist_name:
|
||||||
# Same name - show warning
|
# Same name - show warning
|
||||||
from jackify.backend.services.message_service import MessageService
|
from jackify.frontends.gui.services.message_service import MessageService
|
||||||
MessageService.warning(self, "Same Name", "Please enter a different name to resolve the conflict.")
|
MessageService.warning(self, "Same Name", "Please enter a different name to resolve the conflict.")
|
||||||
else:
|
else:
|
||||||
# Empty name
|
# Empty name
|
||||||
from jackify.backend.services.message_service import MessageService
|
from jackify.frontends.gui.services.message_service import MessageService
|
||||||
MessageService.warning(self, "Invalid Name", "Please enter a valid shortcut name.")
|
MessageService.warning(self, "Invalid Name", "Please enter a valid shortcut name.")
|
||||||
|
|
||||||
def on_cancel():
|
def on_cancel():
|
||||||
|
|||||||
@@ -306,29 +306,49 @@ class InstallTTWScreen(QWidget):
|
|||||||
user_config_widget.setStyleSheet("border: 2px solid orange;")
|
user_config_widget.setStyleSheet("border: 2px solid orange;")
|
||||||
user_config_widget.setToolTip("USER_CONFIG_WIDGET")
|
user_config_widget.setToolTip("USER_CONFIG_WIDGET")
|
||||||
|
|
||||||
# Right: Activity window (FileProgressList widget)
|
# Right: Tabbed interface with Activity and Process Monitor
|
||||||
# Fixed size policy to prevent shrinking when window expands
|
# Both tabs are always available, user can switch between them
|
||||||
self.file_progress_list = FileProgressList()
|
self.file_progress_list = FileProgressList()
|
||||||
self.file_progress_list.setMinimumSize(QSize(300, 20))
|
self.file_progress_list.setMinimumSize(QSize(300, 20))
|
||||||
self.file_progress_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.file_progress_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
|
||||||
activity_widget = QWidget()
|
|
||||||
activity_layout = QVBoxLayout()
|
|
||||||
activity_layout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
activity_layout.setSpacing(0)
|
|
||||||
activity_layout.addWidget(self.file_progress_list)
|
|
||||||
activity_widget.setLayout(activity_layout)
|
|
||||||
if self.debug:
|
|
||||||
activity_widget.setStyleSheet("border: 2px solid purple;")
|
|
||||||
activity_widget.setToolTip("ACTIVITY_WINDOW")
|
|
||||||
|
|
||||||
upper_hbox.addWidget(user_config_widget, stretch=11)
|
|
||||||
upper_hbox.addWidget(activity_widget, stretch=9)
|
|
||||||
|
|
||||||
# Keep legacy process monitor hidden (for compatibility with existing code)
|
|
||||||
self.process_monitor = QTextEdit()
|
self.process_monitor = QTextEdit()
|
||||||
self.process_monitor.setReadOnly(True)
|
self.process_monitor.setReadOnly(True)
|
||||||
self.process_monitor.setVisible(False)
|
self.process_monitor.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard)
|
||||||
|
self.process_monitor.setMinimumSize(QSize(300, 20))
|
||||||
|
self.process_monitor.setStyleSheet(f"background: #222; color: {JACKIFY_COLOR_BLUE}; font-family: monospace; font-size: 11px; border: 1px solid #444;")
|
||||||
|
self.process_monitor_heading = QLabel("<b>[Process Monitor]</b>")
|
||||||
|
self.process_monitor_heading.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 13px; margin-bottom: 2px;")
|
||||||
|
self.process_monitor_heading.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
|
process_vbox = QVBoxLayout()
|
||||||
|
process_vbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
process_vbox.setSpacing(2)
|
||||||
|
process_vbox.addWidget(self.process_monitor_heading)
|
||||||
|
process_vbox.addWidget(self.process_monitor)
|
||||||
|
process_monitor_widget = QWidget()
|
||||||
|
process_monitor_widget.setLayout(process_vbox)
|
||||||
|
process_monitor_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||||
|
if self.debug:
|
||||||
|
process_monitor_widget.setStyleSheet("border: 2px solid purple;")
|
||||||
|
process_monitor_widget.setToolTip("PROCESS_MONITOR")
|
||||||
|
self.process_monitor_widget = process_monitor_widget
|
||||||
|
|
||||||
|
# Create tab widget to hold both Activity and Process Monitor
|
||||||
|
self.activity_tabs = QTabWidget()
|
||||||
|
self.activity_tabs.setStyleSheet("QTabWidget::pane { background: #222; border: 1px solid #444; } QTabBar::tab { background: #222; color: #ccc; padding: 6px 16px; } QTabBar::tab:selected { background: #333; color: #3fd0ea; } QTabWidget { margin: 0px; padding: 0px; } QTabBar { margin: 0px; padding: 0px; }")
|
||||||
|
self.activity_tabs.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.activity_tabs.setDocumentMode(False)
|
||||||
|
self.activity_tabs.setTabPosition(QTabWidget.North)
|
||||||
|
if self.debug:
|
||||||
|
self.activity_tabs.setStyleSheet("border: 2px solid cyan;")
|
||||||
|
self.activity_tabs.setToolTip("ACTIVITY_TABS")
|
||||||
|
|
||||||
|
# Add both widgets as tabs
|
||||||
|
self.activity_tabs.addTab(self.file_progress_list, "Activity")
|
||||||
|
self.activity_tabs.addTab(process_monitor_widget, "Process Monitor")
|
||||||
|
|
||||||
|
upper_hbox.addWidget(user_config_widget, stretch=11)
|
||||||
|
upper_hbox.addWidget(self.activity_tabs, stretch=9)
|
||||||
upper_hbox.setAlignment(Qt.AlignTop)
|
upper_hbox.setAlignment(Qt.AlignTop)
|
||||||
self.upper_section_widget = QWidget()
|
self.upper_section_widget = QWidget()
|
||||||
self.upper_section_widget.setLayout(upper_hbox)
|
self.upper_section_widget.setLayout(upper_hbox)
|
||||||
@@ -2815,11 +2835,11 @@ class InstallTTWScreen(QWidget):
|
|||||||
self.retry_automated_workflow_with_new_name(new_name)
|
self.retry_automated_workflow_with_new_name(new_name)
|
||||||
elif new_name == modlist_name:
|
elif new_name == modlist_name:
|
||||||
# Same name - show warning
|
# Same name - show warning
|
||||||
from jackify.backend.services.message_service import MessageService
|
from jackify.frontends.gui.services.message_service import MessageService
|
||||||
MessageService.warning(self, "Same Name", "Please enter a different name to resolve the conflict.")
|
MessageService.warning(self, "Same Name", "Please enter a different name to resolve the conflict.")
|
||||||
else:
|
else:
|
||||||
# Empty name
|
# Empty name
|
||||||
from jackify.backend.services.message_service import MessageService
|
from jackify.frontends.gui.services.message_service import MessageService
|
||||||
MessageService.warning(self, "Invalid Name", "Please enter a valid shortcut name.")
|
MessageService.warning(self, "Invalid Name", "Please enter a valid shortcut name.")
|
||||||
|
|
||||||
def on_cancel():
|
def on_cancel():
|
||||||
|
|||||||
@@ -933,27 +933,28 @@ class ModlistGalleryDialog(QDialog):
|
|||||||
# Add spacing between Tags and Mods sections
|
# Add spacing between Tags and Mods sections
|
||||||
layout.addSpacing(8)
|
layout.addSpacing(8)
|
||||||
|
|
||||||
# Mod filter
|
# Mod filter - TEMPORARILY DISABLED (not working correctly in v0.2.0.8)
|
||||||
mods_label = QLabel("Mods:")
|
# TODO: Re-enable once mod search index issue is resolved
|
||||||
layout.addWidget(mods_label)
|
# mods_label = QLabel("Mods:")
|
||||||
|
# layout.addWidget(mods_label)
|
||||||
self.mod_search = QLineEdit()
|
#
|
||||||
self.mod_search.setPlaceholderText("Search mods...")
|
# self.mod_search = QLineEdit()
|
||||||
self.mod_search.setStyleSheet("QLineEdit { background: #2a2a2a; color: #fff; border: 1px solid #555; padding: 4px; }")
|
# self.mod_search.setPlaceholderText("Search mods...")
|
||||||
self.mod_search.textChanged.connect(self._filter_mods_list)
|
# self.mod_search.setStyleSheet("QLineEdit { background: #2a2a2a; color: #fff; border: 1px solid #555; padding: 4px; }")
|
||||||
# Prevent Enter from triggering default button (which would close dialog)
|
# self.mod_search.textChanged.connect(self._filter_mods_list)
|
||||||
self.mod_search.returnPressed.connect(lambda: self.mod_search.clearFocus())
|
# # Prevent Enter from triggering default button (which would close dialog)
|
||||||
layout.addWidget(self.mod_search)
|
# self.mod_search.returnPressed.connect(lambda: self.mod_search.clearFocus())
|
||||||
|
# layout.addWidget(self.mod_search)
|
||||||
self.mods_list = QListWidget()
|
#
|
||||||
self.mods_list.setSelectionMode(QListWidget.MultiSelection)
|
# self.mods_list = QListWidget()
|
||||||
self.mods_list.setMaximumHeight(150)
|
# self.mods_list.setSelectionMode(QListWidget.MultiSelection)
|
||||||
self.mods_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # Remove horizontal scrollbar
|
# self.mods_list.setMaximumHeight(150)
|
||||||
self.mods_list.setStyleSheet("QListWidget { background: #2a2a2a; color: #fff; border: 1px solid #555; }")
|
# self.mods_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # Remove horizontal scrollbar
|
||||||
self.mods_list.itemSelectionChanged.connect(self._apply_filters)
|
# self.mods_list.setStyleSheet("QListWidget { background: #2a2a2a; color: #fff; border: 1px solid #555; }")
|
||||||
layout.addWidget(self.mods_list)
|
# self.mods_list.itemSelectionChanged.connect(self._apply_filters)
|
||||||
|
# layout.addWidget(self.mods_list)
|
||||||
self.all_mods_list = [] # Store all mods for filtering
|
#
|
||||||
|
# self.all_mods_list = [] # Store all mods for filtering
|
||||||
|
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
|
|
||||||
@@ -1156,9 +1157,9 @@ class ModlistGalleryDialog(QDialog):
|
|||||||
if index >= 0:
|
if index >= 0:
|
||||||
self.game_combo.setCurrentIndex(index)
|
self.game_combo.setCurrentIndex(index)
|
||||||
|
|
||||||
# Populate tag and mod filters
|
# Populate tag filter (mod filter temporarily disabled)
|
||||||
self._populate_tag_filter()
|
self._populate_tag_filter()
|
||||||
self._populate_mod_filter()
|
# self._populate_mod_filter() # TEMPORARILY DISABLED
|
||||||
|
|
||||||
# Create cards immediately (will show placeholders for images not in cache)
|
# Create cards immediately (will show placeholders for images not in cache)
|
||||||
self._create_all_cards()
|
self._create_all_cards()
|
||||||
@@ -1228,9 +1229,9 @@ class ModlistGalleryDialog(QDialog):
|
|||||||
if index >= 0:
|
if index >= 0:
|
||||||
self.game_combo.setCurrentIndex(index)
|
self.game_combo.setCurrentIndex(index)
|
||||||
|
|
||||||
# Populate tag and mod filters
|
# Populate tag filter (mod filter temporarily disabled)
|
||||||
self._populate_tag_filter()
|
self._populate_tag_filter()
|
||||||
self._populate_mod_filter()
|
# self._populate_mod_filter() # TEMPORARILY DISABLED
|
||||||
|
|
||||||
# Create cards immediately (will show placeholders for images not in cache)
|
# Create cards immediately (will show placeholders for images not in cache)
|
||||||
self._create_all_cards()
|
self._create_all_cards()
|
||||||
@@ -1340,58 +1341,64 @@ class ModlistGalleryDialog(QDialog):
|
|||||||
|
|
||||||
def _populate_mod_filter(self):
|
def _populate_mod_filter(self):
|
||||||
"""Populate mod filter with all available mods from search index"""
|
"""Populate mod filter with all available mods from search index"""
|
||||||
all_mods = set()
|
# TEMPORARILY DISABLED - mod filter feature removed in v0.2.0.8
|
||||||
# Track which mods come from NSFW modlists only
|
return
|
||||||
mods_from_nsfw_only = set()
|
|
||||||
mods_from_sfw = set()
|
|
||||||
modlists_with_mods = 0
|
|
||||||
|
|
||||||
for modlist in self.all_modlists:
|
# all_mods = set()
|
||||||
if hasattr(modlist, 'mods') and modlist.mods:
|
# # Track which mods come from NSFW modlists only
|
||||||
modlists_with_mods += 1
|
# mods_from_nsfw_only = set()
|
||||||
for mod in modlist.mods:
|
# mods_from_sfw = set()
|
||||||
all_mods.add(mod)
|
# modlists_with_mods = 0
|
||||||
if modlist.nsfw:
|
#
|
||||||
mods_from_nsfw_only.add(mod)
|
# for modlist in self.all_modlists:
|
||||||
else:
|
# if hasattr(modlist, 'mods') and modlist.mods:
|
||||||
mods_from_sfw.add(mod)
|
# modlists_with_mods += 1
|
||||||
|
# for mod in modlist.mods:
|
||||||
# Mods that are ONLY in NSFW modlists (not in any SFW modlists)
|
# all_mods.add(mod)
|
||||||
self.nsfw_only_mods = mods_from_nsfw_only - mods_from_sfw
|
# if modlist.nsfw:
|
||||||
|
# mods_from_nsfw_only.add(mod)
|
||||||
self.all_mods_list = sorted(all_mods)
|
# else:
|
||||||
|
# mods_from_sfw.add(mod)
|
||||||
self._filter_mods_list("") # Populate with all mods initially
|
#
|
||||||
|
# # Mods that are ONLY in NSFW modlists (not in any SFW modlists)
|
||||||
|
# self.nsfw_only_mods = mods_from_nsfw_only - mods_from_sfw
|
||||||
|
#
|
||||||
|
# self.all_mods_list = sorted(all_mods)
|
||||||
|
#
|
||||||
|
# self._filter_mods_list("") # Populate with all mods initially
|
||||||
|
|
||||||
def _filter_mods_list(self, search_text: str = ""):
|
def _filter_mods_list(self, search_text: str = ""):
|
||||||
"""Filter the mods list based on search text and NSFW checkbox"""
|
"""Filter the mods list based on search text and NSFW checkbox"""
|
||||||
|
# TEMPORARILY DISABLED - mod filter feature removed in v0.2.0.8
|
||||||
|
return
|
||||||
|
|
||||||
# Get search text from the widget if not provided
|
# Get search text from the widget if not provided
|
||||||
if not search_text and hasattr(self, 'mod_search'):
|
# if not search_text and hasattr(self, 'mod_search'):
|
||||||
search_text = self.mod_search.text()
|
# search_text = self.mod_search.text()
|
||||||
|
#
|
||||||
self.mods_list.clear()
|
# self.mods_list.clear()
|
||||||
search_lower = search_text.lower().strip()
|
# search_lower = search_text.lower().strip()
|
||||||
|
#
|
||||||
# Start with all mods or filtered by search
|
# # Start with all mods or filtered by search
|
||||||
if search_lower:
|
# if search_lower:
|
||||||
filtered_mods = [m for m in self.all_mods_list if search_lower in m.lower()]
|
# filtered_mods = [m for m in self.all_mods_list if search_lower in m.lower()]
|
||||||
else:
|
# else:
|
||||||
filtered_mods = self.all_mods_list
|
# filtered_mods = self.all_mods_list
|
||||||
|
#
|
||||||
# Filter out NSFW-only mods if NSFW checkbox is not checked
|
# # Filter out NSFW-only mods if NSFW checkbox is not checked
|
||||||
if not self.show_nsfw.isChecked():
|
# if not self.show_nsfw.isChecked():
|
||||||
filtered_mods = [m for m in filtered_mods if m not in getattr(self, 'nsfw_only_mods', set())]
|
# filtered_mods = [m for m in filtered_mods if m not in getattr(self, 'nsfw_only_mods', set())]
|
||||||
|
#
|
||||||
# Limit to first 500 results for performance
|
# # Limit to first 500 results for performance
|
||||||
for mod in filtered_mods[:500]:
|
# for mod in filtered_mods[:500]:
|
||||||
self.mods_list.addItem(mod)
|
# self.mods_list.addItem(mod)
|
||||||
|
#
|
||||||
if len(filtered_mods) > 500:
|
# if len(filtered_mods) > 500:
|
||||||
self.mods_list.addItem(f"... and {len(filtered_mods) - 500} more (refine search)")
|
# self.mods_list.addItem(f"... and {len(filtered_mods) - 500} more (refine search)")
|
||||||
|
|
||||||
def _on_nsfw_toggled(self, checked: bool):
|
def _on_nsfw_toggled(self, checked: bool):
|
||||||
"""Handle NSFW checkbox toggle - refresh mod list and apply filters"""
|
"""Handle NSFW checkbox toggle - refresh mod list and apply filters"""
|
||||||
self._filter_mods_list() # Refresh mod list based on NSFW state
|
# self._filter_mods_list() # TEMPORARILY DISABLED - Refresh mod list based on NSFW state
|
||||||
self._apply_filters() # Apply all filters
|
self._apply_filters() # Apply all filters
|
||||||
|
|
||||||
def _set_filter_controls_enabled(self, enabled: bool):
|
def _set_filter_controls_enabled(self, enabled: bool):
|
||||||
@@ -1402,8 +1409,8 @@ class ModlistGalleryDialog(QDialog):
|
|||||||
self.show_nsfw.setEnabled(enabled)
|
self.show_nsfw.setEnabled(enabled)
|
||||||
self.hide_unavailable.setEnabled(enabled)
|
self.hide_unavailable.setEnabled(enabled)
|
||||||
self.tags_list.setEnabled(enabled)
|
self.tags_list.setEnabled(enabled)
|
||||||
self.mod_search.setEnabled(enabled)
|
# self.mod_search.setEnabled(enabled) # TEMPORARILY DISABLED
|
||||||
self.mods_list.setEnabled(enabled)
|
# self.mods_list.setEnabled(enabled) # TEMPORARILY DISABLED
|
||||||
|
|
||||||
def _apply_filters(self):
|
def _apply_filters(self):
|
||||||
"""Apply current filters to modlist display"""
|
"""Apply current filters to modlist display"""
|
||||||
@@ -1459,10 +1466,10 @@ class ModlistGalleryDialog(QDialog):
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Mod filter - modlist must contain ALL selected mods
|
# Mod filter - TEMPORARILY DISABLED (not working correctly in v0.2.0.8)
|
||||||
selected_mods = [item.text() for item in self.mods_list.selectedItems()]
|
# selected_mods = [item.text() for item in self.mods_list.selectedItems()]
|
||||||
if selected_mods:
|
# if selected_mods:
|
||||||
filtered = [m for m in filtered if m.mods and all(mod in m.mods for mod in selected_mods)]
|
# filtered = [m for m in filtered if m.mods and all(mod in m.mods for mod in selected_mods)]
|
||||||
|
|
||||||
self.filtered_modlists = filtered
|
self.filtered_modlists = filtered
|
||||||
self._update_grid()
|
self._update_grid()
|
||||||
|
|||||||
Reference in New Issue
Block a user