mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-08 00:07:45 +02:00
196 lines
7.6 KiB
Python
196 lines
7.6 KiB
Python
"""
|
|
File progress item widget for a single file's progress display.
|
|
"""
|
|
|
|
from PySide6.QtWidgets import (
|
|
QWidget, QHBoxLayout, QLabel, QProgressBar, QSizePolicy
|
|
)
|
|
from PySide6.QtCore import Qt, QTimer
|
|
|
|
from jackify.shared.progress_models import FileProgress, OperationType
|
|
from ..shared_theme import JACKIFY_COLOR_BLUE
|
|
|
|
|
|
class FileProgressItem(QWidget):
|
|
"""Widget representing a single file's progress."""
|
|
|
|
def __init__(self, file_progress: FileProgress, parent=None):
|
|
super().__init__(parent)
|
|
self.file_progress = file_progress
|
|
self._target_percent = file_progress.percent
|
|
self._current_display_percent = file_progress.percent
|
|
self._spinner_position = 0
|
|
self._is_indeterminate = False
|
|
self._animation_timer = QTimer(self)
|
|
self._animation_timer.timeout.connect(self._animate_progress)
|
|
self._animation_timer.setInterval(16)
|
|
self._setup_ui()
|
|
self._update_display()
|
|
|
|
def _setup_ui(self):
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(4, 2, 4, 2)
|
|
layout.setSpacing(8)
|
|
|
|
operation_label = QLabel(self._get_operation_symbol())
|
|
operation_label.setFixedWidth(20)
|
|
operation_label.setAlignment(Qt.AlignCenter)
|
|
operation_label.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-weight: bold;")
|
|
layout.addWidget(operation_label)
|
|
|
|
filename_label = QLabel(self._truncate_filename(self.file_progress.filename))
|
|
filename_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
filename_label.setToolTip(self.file_progress.filename)
|
|
filename_label.setStyleSheet("color: #ccc; font-size: 11px;")
|
|
layout.addWidget(filename_label, 1)
|
|
self.filename_label = filename_label
|
|
|
|
percent_label = QLabel()
|
|
percent_label.setFixedWidth(40)
|
|
percent_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
percent_label.setStyleSheet("color: #aaa; font-size: 11px;")
|
|
layout.addWidget(percent_label)
|
|
self.percent_label = percent_label
|
|
|
|
progress_bar = QProgressBar()
|
|
progress_bar.setFixedHeight(12)
|
|
progress_bar.setFixedWidth(80)
|
|
progress_bar.setTextVisible(False)
|
|
progress_bar.setStyleSheet(f"""
|
|
QProgressBar {{
|
|
border: 1px solid #444;
|
|
border-radius: 2px;
|
|
background-color: #1a1a1a;
|
|
}}
|
|
QProgressBar::chunk {{
|
|
background-color: {JACKIFY_COLOR_BLUE};
|
|
border-radius: 1px;
|
|
}}
|
|
""")
|
|
layout.addWidget(progress_bar)
|
|
self.progress_bar = progress_bar
|
|
|
|
def _get_operation_symbol(self) -> str:
|
|
symbols = {
|
|
OperationType.DOWNLOAD: "↓",
|
|
OperationType.EXTRACT: "↻",
|
|
OperationType.VALIDATE: "✓",
|
|
OperationType.INSTALL: "→",
|
|
}
|
|
return symbols.get(self.file_progress.operation, "•")
|
|
|
|
def _truncate_filename(self, filename: str, max_length: int = 40) -> str:
|
|
if len(filename) <= max_length:
|
|
return filename
|
|
return filename[:max_length-3] + "..."
|
|
|
|
def _update_display(self):
|
|
is_summary = hasattr(self.file_progress, '_is_summary') and self.file_progress._is_summary
|
|
no_progress_bar = hasattr(self.file_progress, '_no_progress_bar') and self.file_progress._no_progress_bar
|
|
|
|
if 'Installing Files' in self.file_progress.filename or 'Converting Texture' in self.file_progress.filename or 'BSA:' in self.file_progress.filename:
|
|
name_display = self.file_progress.filename
|
|
elif self.file_progress.filename.startswith('Wine component:'):
|
|
rest = self.file_progress.filename.split(':', 1)[1].strip()
|
|
comp_id = rest.split('|')[0].strip() if '|' in rest else rest
|
|
name_display = f"Installing {comp_id}..."
|
|
else:
|
|
name_display = self._truncate_filename(self.file_progress.filename)
|
|
|
|
if not is_summary and not no_progress_bar:
|
|
size_display = self.file_progress.size_display
|
|
if size_display:
|
|
name_display = f"{name_display} ({size_display})"
|
|
|
|
self.filename_label.setText(name_display)
|
|
self.filename_label.setToolTip(self.file_progress.filename)
|
|
|
|
if no_progress_bar:
|
|
self._animation_timer.stop()
|
|
self.percent_label.setText("")
|
|
self.progress_bar.setVisible(False)
|
|
return
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
if is_summary:
|
|
summary_step = getattr(self.file_progress, '_summary_step', 0)
|
|
summary_max = getattr(self.file_progress, '_summary_max', 0)
|
|
|
|
if summary_max > 0:
|
|
percent = (summary_step / summary_max) * 100.0
|
|
self._target_percent = max(0, min(100, percent))
|
|
if not self._animation_timer.isActive():
|
|
self._animation_timer.start()
|
|
self.progress_bar.setRange(0, 100)
|
|
else:
|
|
self._is_indeterminate = True
|
|
self.percent_label.setText("")
|
|
self.progress_bar.setRange(0, 100)
|
|
if not self._animation_timer.isActive():
|
|
self._animation_timer.start()
|
|
return
|
|
|
|
is_queued = (
|
|
self.file_progress.total_size > 0 and
|
|
self.file_progress.percent == 0 and
|
|
self.file_progress.current_size == 0 and
|
|
self.file_progress.speed <= 0
|
|
)
|
|
|
|
if is_queued:
|
|
self._is_indeterminate = False
|
|
self._animation_timer.stop()
|
|
self.percent_label.setText("Queued")
|
|
self.progress_bar.setRange(0, 100)
|
|
self.progress_bar.setValue(0)
|
|
return
|
|
|
|
has_meaningful_progress = (
|
|
self.file_progress.percent > 0 or
|
|
(self.file_progress.total_size > 0 and self.file_progress.current_size > 0) or
|
|
(self.file_progress.speed > 0 and self.file_progress.percent >= 0)
|
|
)
|
|
|
|
if has_meaningful_progress:
|
|
self._is_indeterminate = False
|
|
self._target_percent = max(0, self.file_progress.percent)
|
|
if not self._animation_timer.isActive():
|
|
self._animation_timer.start()
|
|
self.progress_bar.setRange(0, 100)
|
|
else:
|
|
self._is_indeterminate = True
|
|
self.percent_label.setText("")
|
|
self.progress_bar.setRange(0, 100)
|
|
if not self._animation_timer.isActive():
|
|
self._animation_timer.start()
|
|
|
|
def _animate_progress(self):
|
|
if self._is_indeterminate:
|
|
self._spinner_position = (self._spinner_position + 4) % 200
|
|
if self._spinner_position < 100:
|
|
display_value = self._spinner_position
|
|
else:
|
|
display_value = 200 - self._spinner_position
|
|
self.progress_bar.setValue(display_value)
|
|
else:
|
|
diff = self._target_percent - self._current_display_percent
|
|
if abs(diff) >= 0.1:
|
|
self._current_display_percent += diff * 0.2
|
|
self._current_display_percent = max(0, min(100, self._current_display_percent))
|
|
|
|
display_percent = self._current_display_percent
|
|
self.progress_bar.setValue(int(display_percent))
|
|
if self.file_progress.percent > 0:
|
|
self.percent_label.setText(f"{display_percent:.0f}%")
|
|
else:
|
|
self.percent_label.setText("")
|
|
|
|
def update_progress(self, file_progress: FileProgress):
|
|
self.file_progress = file_progress
|
|
self._update_display()
|
|
|
|
def cleanup(self):
|
|
if self._animation_timer.isActive():
|
|
self._animation_timer.stop()
|