Sync from development - prepare for v0.2.1.1

This commit is contained in:
Omni
2026-01-15 18:06:02 +00:00
parent 29e1800074
commit 02f3d71a82
22 changed files with 803 additions and 193 deletions

View File

@@ -45,19 +45,20 @@ class SuccessDialog(QDialog):
self.setStyleSheet("QDialog { background: #181818; color: #fff; border-radius: 12px; }" )
layout = QVBoxLayout(self)
layout.setSpacing(0)
layout.setContentsMargins(30, 30, 30, 30)
layout.setContentsMargins(30, 20, 30, 20) # Reduced top/bottom margins to prevent truncation
# --- Card background for content ---
card = QFrame(self)
card.setObjectName("successCard")
card.setFrameShape(QFrame.StyledPanel)
card.setFrameShadow(QFrame.Raised)
card.setFixedWidth(440)
card.setMinimumHeight(380)
# Increase card width and reduce margins to maximize text width for 800p screens
card.setFixedWidth(460)
# Remove fixed minimum height to allow natural sizing based on content
card.setMaximumHeight(16777215) # Remove max height constraint to allow expansion
card_layout = QVBoxLayout(card)
card_layout.setSpacing(12)
card_layout.setContentsMargins(28, 28, 28, 28)
card_layout.setContentsMargins(20, 28, 20, 28) # Reduced left/right margins to give more text width
card.setStyleSheet(
"QFrame#successCard { "
" background: #23272e; "
@@ -81,29 +82,38 @@ class SuccessDialog(QDialog):
card_layout.addWidget(title_label)
# Personalized success message (modlist name in Jackify Blue, but less bold)
message_text = self._build_success_message()
modlist_name_html = f'<span style="color:#3fb7d6; font-size:17px; font-weight:500;">{self.modlist_name}</span>'
if self.workflow_type == "install":
message_html = f"<span style='font-size:15px;'>{modlist_name_html} installed successfully!</span>"
suffix_text = "installed successfully!"
elif self.workflow_type == "configure_new":
suffix_text = "configured successfully!"
elif self.workflow_type == "configure_existing":
suffix_text = "configuration updated successfully!"
else:
message_html = message_text
# Fallback for other workflow types
message_text = self._build_success_message()
suffix_text = message_text.replace(self.modlist_name, "").strip()
# Build complete message with proper HTML formatting - ensure both parts are visible
message_html = f'{modlist_name_html} <span style="font-size:15px; color:#e0e0e0;">{suffix_text}</span>'
message_label = QLabel(message_html)
# Center the success message within the wider card for all screen sizes
message_label.setAlignment(Qt.AlignCenter)
message_label.setWordWrap(True)
message_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding)
message_label.setMinimumHeight(30) # Ensure label has minimum height to be visible
message_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
message_label.setStyleSheet(
"QLabel { "
" font-size: 15px; "
" color: #e0e0e0; "
" line-height: 1.3; "
" margin-bottom: 6px; "
" word-wrap: break-word; "
" padding: 0px; "
"}"
)
message_label.setTextFormat(Qt.RichText)
# Ensure the label itself is centered in the card layout and uses full width
card_layout.addWidget(message_label, alignment=Qt.AlignCenter)
# Ensure the label uses full width of the card before wrapping
card_layout.addWidget(message_label)
# Time taken
time_label = QLabel(f"Completed in {self.time_taken}")
@@ -123,7 +133,7 @@ class SuccessDialog(QDialog):
next_steps_label = QLabel(next_steps_text)
next_steps_label.setAlignment(Qt.AlignCenter)
next_steps_label.setWordWrap(True)
next_steps_label.setMinimumHeight(100)
# Remove fixed minimum height to allow natural sizing
next_steps_label.setStyleSheet(
"QLabel { "
" font-size: 13px; "

View File

@@ -1495,7 +1495,8 @@ class JackifyMainWindow(QMainWindow):
4: "Install Modlist Screen",
5: "Install TTW Screen",
6: "Configure New Modlist",
7: "Configure Existing Modlist",
7: "Wabbajack Installer",
8: "Configure Existing Modlist",
}
screen_name = screen_names.get(index, f"Unknown Screen (Index {index})")
widget = self.stacked_widget.widget(index)
@@ -1919,7 +1920,6 @@ def main():
debug_mode = True
# Temporarily save CLI debug flag to config so engine can see it
config_handler.set('debug_mode', True)
print("[DEBUG] CLI --debug flag detected, saved debug_mode=True to config")
import logging
# Initialize file logging on root logger so all modules inherit it
@@ -1928,13 +1928,22 @@ def main():
# Only rotate log file when debug mode is enabled
if debug_mode:
logging_handler.rotate_log_for_logger('jackify_gui', 'jackify-gui.log')
root_logger = logging_handler.setup_logger('', 'jackify-gui.log', is_general=True) # Empty name = root logger
root_logger = logging_handler.setup_logger('', 'jackify-gui.log', is_general=True, debug_mode=debug_mode) # Empty name = root logger
# CRITICAL: Set root logger level BEFORE any child loggers are used
# This ensures DEBUG messages from child loggers propagate correctly
if debug_mode:
logging.getLogger().setLevel(logging.DEBUG)
print("[Jackify] Debug mode enabled (from config or CLI)")
root_logger.setLevel(logging.DEBUG)
logging.getLogger().setLevel(logging.DEBUG) # Also set on root via getLogger() for compatibility
root_logger.debug("CLI --debug flag detected, saved debug_mode=True to config")
root_logger.info("Debug mode enabled (from config or CLI)")
else:
root_logger.setLevel(logging.WARNING)
logging.getLogger().setLevel(logging.WARNING)
# Root logger should not propagate (it's the top level)
# Child loggers will propagate to root logger by default (unless they explicitly set propagate=False)
root_logger.propagate = False
dev_mode = '--dev' in sys.argv
@@ -1990,7 +1999,7 @@ def main():
icon = QIcon(path)
if not icon.isNull():
if debug_mode:
print(f"[DEBUG] Using AppImage icon: {path}")
logging.getLogger().debug(f"Using AppImage icon: {path}")
break
# Priority 3: Fallback to any PNG in assets directory
@@ -2001,8 +2010,8 @@ def main():
icon = QIcon(try_path)
if debug_mode:
print(f"[DEBUG] Final icon path: {icon_path}")
print(f"[DEBUG] Icon is null: {icon.isNull()}")
logging.getLogger().debug(f"Final icon path: {icon_path}")
logging.getLogger().debug(f"Icon is null: {icon.isNull()}")
app.setWindowIcon(icon)
window = JackifyMainWindow(dev_mode=dev_mode)

View File

@@ -648,7 +648,7 @@ class ConfigureExistingModlistScreen(QWidget):
class ConfigurationThread(QThread):
progress_update = Signal(str)
configuration_complete = Signal(bool, str, str)
configuration_complete = Signal(bool, str, str, bool)
error_occurred = Signal(str)
def __init__(self, modlist_name, install_dir, resolution):
@@ -691,8 +691,8 @@ class ConfigureExistingModlistScreen(QWidget):
def progress_callback(message):
self.progress_update.emit(message)
def completion_callback(success, message, modlist_name):
self.configuration_complete.emit(success, message, modlist_name)
def completion_callback(success, message, modlist_name, enb_detected=False):
self.configuration_complete.emit(success, message, modlist_name, enb_detected)
def manual_steps_callback(modlist_name, retry_count):
# Existing modlists shouldn't need manual steps, but handle gracefully
@@ -729,7 +729,7 @@ class ConfigureExistingModlistScreen(QWidget):
self._safe_append_text(f"[ERROR] Failed to start configuration: {e}")
MessageService.critical(self, "Configuration Error", f"Failed to start configuration: {e}", safety_level="medium")
def on_configuration_complete(self, success, message, modlist_name):
def on_configuration_complete(self, success, message, modlist_name, enb_detected=False):
"""Handle configuration completion"""
# Re-enable all controls when workflow completes
self._enable_controls_after_operation()

View File

@@ -30,7 +30,7 @@ from jackify.backend.utils.nexus_premium_detector import is_non_premium_indicato
from jackify.backend.handlers.progress_parser import ProgressStateManager
from jackify.frontends.gui.widgets.progress_indicator import OverallProgressIndicator
from jackify.frontends.gui.widgets.file_progress_list import FileProgressList
from jackify.shared.progress_models import InstallationPhase, InstallationProgress, OperationType
from jackify.shared.progress_models import InstallationPhase, InstallationProgress, OperationType, FileProgress
# Modlist gallery (imported at module level to avoid import delay when opening dialog)
from jackify.frontends.gui.screens.modlist_gallery import ModlistGalleryDialog
@@ -2718,7 +2718,6 @@ class InstallModlistScreen(QWidget):
# Render loop handles smooth updates - just set target state
current_step = progress_state.phase_step
from jackify.shared.progress_models import FileProgress, OperationType
display_items = []

View File

@@ -175,7 +175,7 @@ class InstallTTWScreen(QWidget):
instruction_text = QLabel(
"Tale of Two Wastelands installation requires a .mpi file you can get from: "
'<a href="https://mod.pub/ttw/133/files">https://mod.pub/ttw/133/files</a> '
"(requires a user account for mod.db)"
"(requires a user account for ModPub)"
)
instruction_text.setWordWrap(True)
instruction_text.setStyleSheet("color: #ccc; font-size: 12px; margin: 0px; padding: 0px; line-height: 1.2;")

View File

@@ -209,7 +209,9 @@ class ModlistTasksScreen(QWidget):
elif action_id == "configure_new_modlist":
self.stacked_widget.setCurrentIndex(6) # Configure New Modlist Screen
elif action_id == "configure_existing_modlist":
self.stacked_widget.setCurrentIndex(7) # Configure Existing Modlist Screen
self.stacked_widget.setCurrentIndex(8) # Configure Existing Modlist Screen
elif action_id == "install_wabbajack":
self.stacked_widget.setCurrentIndex(7) # Wabbajack Installer Screen
def go_back(self):
"""Return to main menu"""

View File

@@ -106,6 +106,77 @@ class OverallProgressIndicator(QWidget):
if not display_text or display_text == "Processing...":
display_text = progress.phase_name or progress.phase.value.title() or "Processing..."
# Add total download size, remaining size (MB/GB), and ETA for download phase
from jackify.shared.progress_models import InstallationPhase, FileProgress
if progress.phase == InstallationPhase.DOWNLOAD:
# Try to get overall download totals - either from data_total or aggregate from active_files
total_bytes = progress.data_total
processed_bytes = progress.data_processed
using_aggregated = False
# If data_total is 0, try to aggregate from active_files
if total_bytes == 0 and progress.active_files:
total_bytes = sum(f.total_size for f in progress.active_files if f.total_size > 0)
processed_bytes = sum(f.current_size for f in progress.active_files if f.current_size > 0)
using_aggregated = True
# Add remaining download size (MB or GB) if available
if total_bytes > 0:
remaining_bytes = total_bytes - processed_bytes
if remaining_bytes > 0:
# Format as MB if less than 1GB, otherwise GB
if remaining_bytes < (1024.0 ** 3):
remaining_mb = remaining_bytes / (1024.0 ** 2)
display_text += f" | {remaining_mb:.1f}MB remaining"
else:
remaining_gb = remaining_bytes / (1024.0 ** 3)
display_text += f" | {remaining_gb:.1f}GB remaining"
# Calculate ETA - prefer aggregated calculation for concurrent downloads
eta_seconds = -1.0
if using_aggregated:
# For concurrent downloads: sum all active download speeds (not average)
# This gives us the combined throughput
active_speeds = [f.speed for f in progress.active_files if f.speed > 0]
if active_speeds:
combined_speed = sum(active_speeds) # Sum speeds for concurrent downloads
if combined_speed > 0:
eta_seconds = remaining_bytes / combined_speed
else:
# Use the standard ETA calculation from progress model
eta_seconds = progress.get_eta_seconds(use_smoothing=True)
# Format and display ETA
if eta_seconds > 0:
if eta_seconds < 60:
display_text += f" | ETA: {int(eta_seconds)}s"
elif eta_seconds < 3600:
mins = int(eta_seconds // 60)
secs = int(eta_seconds % 60)
if secs > 0:
display_text += f" | ETA: {mins}m {secs}s"
else:
display_text += f" | ETA: {mins}m"
else:
hours = int(eta_seconds // 3600)
mins = int((eta_seconds % 3600) // 60)
if mins > 0:
display_text += f" | ETA: {hours}h {mins}m"
else:
display_text += f" | ETA: {hours}h"
else:
# No total size available - try to show ETA if we have speed info from active files
if progress.active_files:
active_speeds = [f.speed for f in progress.active_files if f.speed > 0]
if active_speeds:
# Can't calculate accurate ETA without total size, but could show speed
pass
# Fallback to standard ETA if available
if not using_aggregated:
eta_display = progress.eta_display
if eta_display:
display_text += f" | ETA: {eta_display}"
self.status_label.setText(display_text)
# Update progress bar if enabled
@@ -150,6 +221,23 @@ class OverallProgressIndicator(QWidget):
tooltip_parts.append(f"Step: {progress.phase_progress_text}")
if progress.data_progress_text:
tooltip_parts.append(f"Data: {progress.data_progress_text}")
# Add total download size in GB for download phase
from jackify.shared.progress_models import InstallationPhase
if progress.phase == InstallationPhase.DOWNLOAD and progress.data_total > 0:
total_gb = progress.total_download_size_gb
remaining_gb = progress.remaining_download_size_gb
if total_gb > 0:
tooltip_parts.append(f"Total Download: {total_gb:.2f}GB")
if remaining_gb > 0:
tooltip_parts.append(f"Remaining: {remaining_gb:.2f}GB")
# Add ETA for download phase
if progress.phase == InstallationPhase.DOWNLOAD:
eta_display = progress.eta_display
if eta_display:
tooltip_parts.append(f"Estimated Time Remaining: {eta_display}")
if progress.overall_percent > 0:
tooltip_parts.append(f"Overall: {progress.overall_percent:.1f}%")