8a05d5ff8f
The internal ytsage package name is kept deliberately - renaming it would touch every file and destroy the ability to merge upstream YTSage changes. - pyproject: distribution name sagetube v0.1.0, sagetube entrypoint (ytsage alias retained), URLs point at the Gitea repo with an Upstream link to YTSage. - Data dirs move to SageTube/ (fresh fork, fresh state) and the config file becomes sagetube_config.json; QApplication name and window title read SageTube. - About dialog credits Houmeres and links "Based on YTSage by oop7". - App self-update check against the upstream PyPI package is disabled by default; yt-dlp/deno/ffmpeg update flows are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
644 lines
23 KiB
Python
644 lines
23 KiB
Python
"""
|
|
Base dialogs for YTSage application.
|
|
Contains basic utility dialogs like LogWindow and AboutDialog.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from PySide6.QtCore import Qt, QThread, QTimer, Signal, QUrl
|
|
from PySide6.QtGui import QDesktopServices
|
|
from PySide6.QtWidgets import (
|
|
QDialog,
|
|
QDialogButtonBox,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QMessageBox,
|
|
QPushButton,
|
|
QSizePolicy,
|
|
QTextEdit,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from ... import __version__ as APP_VERSION
|
|
from ...utils.ytsage_localization import _
|
|
from ...utils.ytsage_logger import logger
|
|
from ...utils.ytsage_constants import APP_LOG_DIR
|
|
|
|
from ...core.ytsage_ffmpeg import get_ffmpeg_path
|
|
from ...core.ytsage_utils import _version_cache, check_ffmpeg, get_ffmpeg_version, get_ytdlp_version, get_deno_version, refresh_version_cache
|
|
from ...core.ytsage_yt_dlp import check_ytdlp_installed, get_yt_dlp_path, check_ytdlp_deno_integration
|
|
from ...core.ytsage_deno import check_deno_installed, get_deno_path
|
|
|
|
|
|
class SystemInfoThread(QThread):
|
|
"""Background thread to gather system information."""
|
|
info_ready = Signal(dict)
|
|
|
|
def run(self):
|
|
info = {}
|
|
|
|
# yt-dlp Status
|
|
ytdlp_found = check_ytdlp_installed()
|
|
info['ytdlp_found'] = ytdlp_found
|
|
info['ytdlp_version'] = get_ytdlp_version()
|
|
info['ytdlp_path'] = get_yt_dlp_path() if ytdlp_found else None
|
|
|
|
# yt-dlp cache status
|
|
ytdlp_cache = _version_cache.get("ytdlp", {})
|
|
info['ytdlp_last_check'] = ytdlp_cache.get("last_check", 0)
|
|
|
|
# FFmpeg Status
|
|
ffmpeg_found = check_ffmpeg()
|
|
info['ffmpeg_found'] = ffmpeg_found
|
|
info['ffmpeg_version'] = get_ffmpeg_version() if ffmpeg_found else _('about.not_available')
|
|
info['ffmpeg_path'] = get_ffmpeg_path() if ffmpeg_found else None
|
|
|
|
# FFmpeg cache status
|
|
ffmpeg_cache = _version_cache.get("ffmpeg", {})
|
|
info['ffmpeg_last_check'] = ffmpeg_cache.get("last_check", 0)
|
|
|
|
# Deno Status
|
|
deno_found = check_deno_installed()
|
|
info['deno_found'] = deno_found
|
|
info['deno_version'] = get_deno_version() if deno_found else _('about.not_available')
|
|
info['deno_path'] = get_deno_path() if deno_found else None
|
|
|
|
# Deno cache status
|
|
deno_cache = _version_cache.get("deno", {})
|
|
info['deno_last_check'] = deno_cache.get("last_check", 0)
|
|
|
|
# Check integration with yt-dlp if both are present
|
|
info['integration_status'] = False
|
|
if deno_found and ytdlp_found:
|
|
info['integration_status'] = check_ytdlp_deno_integration()
|
|
|
|
self.info_ready.emit(info)
|
|
|
|
|
|
class LogWindow(QDialog):
|
|
def __init__(self, parent=None) -> None:
|
|
super().__init__(parent)
|
|
self.setWindowTitle(_("dialogs.ytdlp_log_title"))
|
|
self.setMinimumSize(700, 500)
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
self.log_text = QTextEdit()
|
|
self.log_text.setReadOnly(True)
|
|
self.log_text.setStyleSheet(
|
|
"""
|
|
QTextEdit {
|
|
background-color: #2b2b2b;
|
|
color: #ffffff;
|
|
font-family: Consolas, monospace;
|
|
font-size: 12px;
|
|
border: 2px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
}
|
|
"""
|
|
)
|
|
|
|
layout.addWidget(self.log_text)
|
|
|
|
def append_log(self, message) -> None:
|
|
self.log_text.append(message)
|
|
# Auto-scroll to bottom
|
|
scrollbar = self.log_text.verticalScrollBar()
|
|
scrollbar.setValue(scrollbar.maximum())
|
|
|
|
|
|
class AboutDialog(QDialog):
|
|
def __init__(self, parent=None) -> None:
|
|
super().__init__(parent)
|
|
self._parent = parent # Store parent to access version etc.
|
|
self.setWindowTitle(_("about.title"))
|
|
self.setMinimumSize(460, 420) # Slightly increased to accommodate paths
|
|
self.resize(460, 440) # Slightly increased initial size
|
|
self.setMaximumSize(500, 480) # Reasonable maximum size
|
|
|
|
# Set window flags to make dialog independent of parent movement
|
|
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowCloseButtonHint)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(15) # Reduced spacing
|
|
layout.setContentsMargins(20, 20, 20, 20) # Reduced margins
|
|
layout.setSizeConstraint(QVBoxLayout.SizeConstraint.SetMinAndMaxSize)
|
|
|
|
# App Information Section
|
|
app_info_widget = self._create_app_info_section()
|
|
layout.addWidget(app_info_widget)
|
|
|
|
# Separator - subtle and compact
|
|
separator = QWidget()
|
|
separator.setFixedHeight(1)
|
|
separator.setStyleSheet("background-color: #2a2a2a; margin: 8px 20px;")
|
|
layout.addWidget(separator)
|
|
|
|
# System Information Section
|
|
system_info_widget = self._create_system_info_section()
|
|
layout.addWidget(system_info_widget)
|
|
|
|
# Close Button - compact positioning
|
|
layout.addSpacing(10) # Reduced space before button
|
|
button_layout = QHBoxLayout()
|
|
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
|
|
button_box.accepted.connect(self.accept)
|
|
|
|
# Center the button
|
|
button_layout.addStretch()
|
|
button_layout.addWidget(button_box)
|
|
button_layout.addStretch()
|
|
layout.addLayout(button_layout)
|
|
|
|
# Apply overall styling - improved consistency
|
|
self.setStyleSheet(
|
|
"""
|
|
QDialog {
|
|
background-color: #15181b;
|
|
color: #ffffff;
|
|
}
|
|
QLabel {
|
|
color: #cccccc;
|
|
}
|
|
QPushButton {
|
|
padding: 10px 30px;
|
|
background-color: #c90000;
|
|
border: none;
|
|
border-radius: 6px;
|
|
color: white;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
min-width: 80px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #a50000;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #800000;
|
|
}
|
|
QGroupBox {
|
|
font-weight: bold;
|
|
border: 1px solid #333333;
|
|
border-radius: 8px;
|
|
margin-top: 12px;
|
|
padding-top: 12px;
|
|
background-color: #15181b;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 15px;
|
|
padding: 0 10px 0 10px;
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
}
|
|
"""
|
|
)
|
|
|
|
def _create_app_info_section(self) -> QWidget:
|
|
"""Create the application information section - compact version"""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(6) # Reduced spacing
|
|
|
|
# Title and Version - more compact
|
|
title_label = QLabel(
|
|
"<span style='color: #c90000; font-size: 28px; font-weight: 300; letter-spacing: 2px;'>YTSage</span>"
|
|
)
|
|
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(title_label)
|
|
|
|
version_label = QLabel(
|
|
f"<span style='color: #cccccc; font-size: 13px; font-weight: normal;'>"
|
|
f"{_('about.version', version=getattr(self._parent, 'version', APP_VERSION))}</span>"
|
|
)
|
|
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(version_label)
|
|
|
|
# Description - more compact
|
|
description_label = QLabel(_("about.description"))
|
|
description_label.setWordWrap(True)
|
|
description_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
description_label.setStyleSheet("color: #ffffff; font-size: 11px; margin: 6px 0;")
|
|
layout.addWidget(description_label)
|
|
|
|
# Author and Links - compact single line
|
|
info_layout = QHBoxLayout()
|
|
info_layout.setSpacing(15)
|
|
|
|
author_link = '<a href="https://git.houmeres.sk/Houmeres" style="color: #c90000; text-decoration: none;">Houmeres</a>'
|
|
author_label = QLabel(
|
|
f"{_('about.author', author=author_link)}"
|
|
)
|
|
author_label.setOpenExternalLinks(True)
|
|
info_layout.addWidget(author_label)
|
|
|
|
repo_link = '<a href="https://git.houmeres.sk/Houmeres/SageTube" style="color: #c90000; text-decoration: none;">SageTube</a>'
|
|
repo_label = QLabel(
|
|
f"{_('about.github', repo=repo_link)}"
|
|
)
|
|
repo_label.setOpenExternalLinks(True)
|
|
info_layout.addWidget(repo_label)
|
|
|
|
upstream_link = (
|
|
'<a href="https://github.com/oop7/YTSage" style="color: #c90000; text-decoration: none;">'
|
|
"Based on YTSage by oop7</a>"
|
|
)
|
|
upstream_label = QLabel(upstream_link)
|
|
upstream_label.setOpenExternalLinks(True)
|
|
info_layout.addWidget(upstream_label)
|
|
|
|
# Center the info layout
|
|
info_container = QHBoxLayout()
|
|
info_container.addStretch()
|
|
info_container.addLayout(info_layout)
|
|
info_container.addStretch()
|
|
|
|
layout.addLayout(info_container)
|
|
|
|
return widget
|
|
|
|
def _create_system_info_section(self) -> QWidget:
|
|
"""Create the system information section with compact design"""
|
|
# Create main container with compact styling
|
|
container = QWidget()
|
|
container.setStyleSheet(
|
|
"""
|
|
QWidget {
|
|
border: 1px solid #333333;
|
|
border-radius: 8px;
|
|
background-color: #15181b;
|
|
margin-top: 5px;
|
|
}
|
|
"""
|
|
)
|
|
|
|
main_layout = QVBoxLayout(container)
|
|
main_layout.setSpacing(8) # Compact spacing
|
|
main_layout.setContentsMargins(15, 10, 15, 10)
|
|
|
|
# Create header with title and refresh button on same line
|
|
header_layout = QHBoxLayout()
|
|
header_layout.setContentsMargins(0, 0, 0, 5)
|
|
|
|
# System Information title
|
|
title_label = QLabel(_("about.system_info"))
|
|
title_label.setStyleSheet(
|
|
"""
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
padding: 0px;
|
|
margin: 0px;
|
|
}
|
|
"""
|
|
)
|
|
header_layout.addWidget(title_label)
|
|
|
|
# Add stretch to push refresh button to the right
|
|
header_layout.addStretch()
|
|
|
|
# Create logs button (minimal)
|
|
self.logs_btn = QPushButton(_("about.open_logs")) # Expected to be small text or icon
|
|
self.logs_btn.setToolTip(_("about.logs_tooltip"))
|
|
self.logs_btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
self.logs_btn.setStyleSheet(
|
|
"""
|
|
QPushButton {
|
|
padding: 1px 6px;
|
|
background-color: transparent;
|
|
border: 1px solid #333;
|
|
border-radius: 4px;
|
|
color: #888888;
|
|
font-size: 10px;
|
|
margin-right: 8px;
|
|
}
|
|
QPushButton:hover {
|
|
color: #ffffff;
|
|
border-color: #555;
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
}
|
|
"""
|
|
)
|
|
self.logs_btn.clicked.connect(self.open_logs_folder)
|
|
header_layout.addWidget(self.logs_btn)
|
|
|
|
# Create refresh button
|
|
self.refresh_btn = QPushButton(_("about.refresh"))
|
|
self.refresh_btn.setFixedSize(16, 16)
|
|
self.refresh_btn.setStyleSheet(
|
|
"""
|
|
QPushButton {
|
|
padding: 0px;
|
|
background-color: transparent;
|
|
border: none;
|
|
color: #cccccc;
|
|
font-size: 10px;
|
|
font-weight: normal;
|
|
margin: 0px;
|
|
}
|
|
QPushButton:hover {
|
|
color: #ffffff;
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
}
|
|
QPushButton:pressed {
|
|
color: #c90000;
|
|
background-color: rgba(201, 0, 0, 0.1);
|
|
}
|
|
"""
|
|
)
|
|
self.refresh_btn.clicked.connect(self.refresh_version_info)
|
|
header_layout.addWidget(self.refresh_btn)
|
|
|
|
main_layout.addLayout(header_layout)
|
|
|
|
# Compact status grid layout
|
|
self.status_container = QVBoxLayout()
|
|
self.status_container.setSpacing(6) # Tight spacing
|
|
self.status_container.setContentsMargins(0, 0, 0, 0)
|
|
|
|
main_layout.addLayout(self.status_container)
|
|
|
|
# Show loading message initially
|
|
self._show_loading_message()
|
|
|
|
# Populate system information asynchronously
|
|
QTimer.singleShot(100, self.update_system_info)
|
|
|
|
return container
|
|
|
|
def _show_loading_message(self) -> None:
|
|
"""Show a compact loading message while system information is being gathered."""
|
|
# Strip the emoji from the localized string to avoid rendering issues
|
|
loading_text = _("about.loading").replace("🔄", "").strip()
|
|
loading_label = QLabel(loading_text)
|
|
loading_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
loading_label.setStyleSheet(
|
|
"""
|
|
QLabel {
|
|
color: #888888;
|
|
font-size: 11px;
|
|
padding: 10px;
|
|
}
|
|
"""
|
|
)
|
|
|
|
self.status_container.addWidget(loading_label)
|
|
|
|
def _create_status_item(self, icon, name, status_text, version_text, path_text=None, cache_status="") -> QWidget:
|
|
"""Create a compact status item widget"""
|
|
item_widget = QWidget()
|
|
# Adjust height based on whether we have path info
|
|
item_height = 50 if path_text else 35
|
|
item_widget.setMaximumHeight(item_height)
|
|
item_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
|
|
# Main layout
|
|
item_layout = QVBoxLayout(item_widget)
|
|
item_layout.setContentsMargins(8, 4, 8, 4)
|
|
item_layout.setSpacing(2)
|
|
|
|
# First row: Icon, name, status, version
|
|
first_row = QHBoxLayout()
|
|
first_row.setSpacing(8)
|
|
|
|
# Icon and name - compact
|
|
name_label = QLabel(f"{icon} <b>{name}</b>")
|
|
name_label.setStyleSheet("font-size: 12px; color: #ffffff; font-weight: bold;")
|
|
name_label.setMinimumWidth(80)
|
|
first_row.addWidget(name_label)
|
|
|
|
# Status - compact
|
|
status_label = QLabel(status_text)
|
|
status_label.setStyleSheet("font-size: 11px; font-weight: bold;")
|
|
status_label.setMinimumWidth(70)
|
|
first_row.addWidget(status_label)
|
|
|
|
# Version info - improved readability
|
|
version_info = version_text
|
|
if cache_status:
|
|
version_info += cache_status
|
|
|
|
version_label = QLabel(version_info)
|
|
version_label.setStyleSheet("font-size: 11px; color: #cccccc;") # Increased from 10px
|
|
version_label.setWordWrap(False)
|
|
first_row.addWidget(version_label)
|
|
|
|
# Add stretch to push everything left
|
|
first_row.addStretch()
|
|
|
|
item_layout.addLayout(first_row)
|
|
|
|
# Second row: Path (if provided)
|
|
if path_text:
|
|
path_label = QLabel(f"📁 {path_text}")
|
|
path_label.setStyleSheet("font-size: 10px; color: #aaaaaa; margin-left: 12px;") # Increased from 9px, better color
|
|
path_label.setWordWrap(False)
|
|
# Truncate very long paths
|
|
if len(str(path_text)) > 60:
|
|
truncated_path = "..." + str(path_text)[-57:]
|
|
path_label.setText(f"📁 {truncated_path}")
|
|
item_layout.addWidget(path_label)
|
|
|
|
# Subtle background with minimal border
|
|
item_widget.setStyleSheet(
|
|
"""
|
|
QWidget {
|
|
background-color: rgba(45, 45, 45, 0.3);
|
|
border: 1px solid #2a2a2a;
|
|
border-radius: 4px;
|
|
margin: 1px;
|
|
}
|
|
QWidget:hover {
|
|
background-color: rgba(60, 60, 60, 0.4);
|
|
}
|
|
"""
|
|
)
|
|
|
|
return item_widget
|
|
|
|
def update_system_info(self) -> None:
|
|
"""Start background thread to gather system info."""
|
|
self.info_thread = SystemInfoThread()
|
|
self.info_thread.info_ready.connect(self._populate_system_info)
|
|
self.info_thread.start()
|
|
|
|
def _populate_system_info(self, info: dict) -> None:
|
|
"""Populate UI with gathered info."""
|
|
# Clear existing items
|
|
for i in reversed(range(self.status_container.count())):
|
|
child = self.status_container.itemAt(i).widget()
|
|
if child:
|
|
child.deleteLater()
|
|
|
|
# yt-dlp Status - compact version with path
|
|
ytdlp_found = info['ytdlp_found']
|
|
ytdlp_status_text = (
|
|
f"<span style='color: #4CAF50;'>{_('about.detected')}</span>" if ytdlp_found else f"<span style='color: #F44336;'>{_('about.missing')}</span>"
|
|
)
|
|
ytdlp_version = info['ytdlp_version']
|
|
|
|
# Get yt-dlp path
|
|
ytdlp_path = info['ytdlp_path']
|
|
ytdlp_path_text = ytdlp_path if ytdlp_path and ytdlp_path != "yt-dlp" else None
|
|
|
|
# Simplified cache status
|
|
last_check = info['ytdlp_last_check']
|
|
cache_status = ""
|
|
if last_check > 0:
|
|
cache_time = datetime.fromtimestamp(last_check).strftime("%H:%M")
|
|
cache_status = f" <span style='color: #888; font-size: 10px;'>({cache_time})</span>" # Increased from 9px
|
|
|
|
ytdlp_item = self._create_status_item(
|
|
"🎥",
|
|
"yt-dlp",
|
|
ytdlp_status_text,
|
|
ytdlp_version + cache_status,
|
|
ytdlp_path_text,
|
|
)
|
|
self.status_container.addWidget(ytdlp_item)
|
|
|
|
# FFmpeg Status - compact version with path
|
|
ffmpeg_found = info['ffmpeg_found']
|
|
ffmpeg_status_text = (
|
|
f"<span style='color: #4CAF50;'>{_('about.detected')}</span>"
|
|
if ffmpeg_found
|
|
else f"<span style='color: #F44336;'>{_('about.missing')}</span>"
|
|
)
|
|
ffmpeg_version = info['ffmpeg_version']
|
|
|
|
# Get FFmpeg path
|
|
ffmpeg_path_text = info['ffmpeg_path']
|
|
|
|
# Simplified cache status for FFmpeg
|
|
last_check = info['ffmpeg_last_check']
|
|
cache_status = ""
|
|
if last_check > 0 and ffmpeg_found:
|
|
cache_time = datetime.fromtimestamp(last_check).strftime("%H:%M")
|
|
cache_status = f" <span style='color: #888; font-size: 10px;'>({cache_time})</span>" # Increased from 9px
|
|
|
|
ffmpeg_item = self._create_status_item(
|
|
"🎬",
|
|
"FFmpeg",
|
|
ffmpeg_status_text,
|
|
ffmpeg_version + cache_status,
|
|
ffmpeg_path_text,
|
|
)
|
|
self.status_container.addWidget(ffmpeg_item)
|
|
|
|
# Deno Status - compact version with path (only show path if in app bin directory)
|
|
deno_found = info['deno_found']
|
|
deno_status_text = (
|
|
f"<span style='color: #4CAF50;'>{_('about.detected')}</span>" if deno_found else f"<span style='color: #F44336;'>{_('about.missing')}</span>"
|
|
)
|
|
deno_version = info['deno_version']
|
|
|
|
# Get Deno path - only show if in app bin directory
|
|
deno_path_text = None
|
|
if deno_found:
|
|
deno_path = info['deno_path']
|
|
# Only show path if it's not the fallback "deno" and the file exists
|
|
if deno_path and deno_path != "deno":
|
|
from pathlib import Path
|
|
from ...utils.ytsage_constants import DENO_APP_BIN_PATH
|
|
# Check if the path is our managed binary
|
|
if Path(deno_path).resolve() == DENO_APP_BIN_PATH.resolve():
|
|
deno_path_text = deno_path
|
|
|
|
# Simplified cache status for Deno
|
|
last_check = info['deno_last_check']
|
|
cache_status = ""
|
|
if last_check > 0 and deno_found:
|
|
cache_time = datetime.fromtimestamp(last_check).strftime("%H:%M")
|
|
cache_status = f" <span style='color: #888; font-size: 10px;'>({cache_time})</span>"
|
|
|
|
# Check integration with yt-dlp if both are present
|
|
integration_status = ""
|
|
if info.get('integration_status', False):
|
|
integration_status = f" <span style='color: #4CAF50; font-size: 10px; font-weight: bold;'> + yt-dlp</span>"
|
|
|
|
deno_item = self._create_status_item(
|
|
"🦕",
|
|
"Deno",
|
|
deno_status_text,
|
|
deno_version + cache_status + integration_status,
|
|
deno_path_text,
|
|
)
|
|
self.status_container.addWidget(deno_item)
|
|
|
|
|
|
def open_logs_folder(self):
|
|
"""Open the application logs folder in the system file explorer."""
|
|
try:
|
|
if not APP_LOG_DIR.exists():
|
|
logger.warning(f"Log directory does not exist: {APP_LOG_DIR}")
|
|
APP_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
log_url = QUrl.fromLocalFile(str(APP_LOG_DIR))
|
|
QDesktopServices.openUrl(log_url)
|
|
except Exception as e:
|
|
logger.error(f"Failed to open log folder: {e}")
|
|
# Minimal error feedback since this is about dialog
|
|
self.logs_btn.setToolTip(f"Error: {str(e)}")
|
|
|
|
def refresh_version_info(self) -> None:
|
|
"""Refresh version information manually."""
|
|
self.refresh_btn.setText(_('about.refreshing'))
|
|
self.refresh_btn.setEnabled(False)
|
|
|
|
# Perform refresh in a separate thread to avoid blocking UI
|
|
class RefreshThread(QThread):
|
|
finished = Signal(bool)
|
|
|
|
def run(self):
|
|
success = refresh_version_cache(force=True)
|
|
self.finished.emit(success)
|
|
|
|
self.refresh_thread = RefreshThread()
|
|
self.refresh_thread.finished.connect(self.on_refresh_finished)
|
|
self.refresh_thread.start()
|
|
|
|
def on_refresh_finished(self, success) -> None:
|
|
"""Handle refresh completion."""
|
|
self.refresh_btn.setText(_('about.refresh'))
|
|
self.refresh_btn.setEnabled(True)
|
|
|
|
if success:
|
|
self.update_system_info()
|
|
else:
|
|
# Show error message with proper styling
|
|
msg_box = QMessageBox(self)
|
|
msg_box.setIcon(QMessageBox.Icon.Warning)
|
|
msg_box.setWindowTitle(_('about.refresh_failed'))
|
|
msg_box.setText(_('about.refresh_failed_message'))
|
|
msg_box.setWindowIcon(self.windowIcon())
|
|
msg_box.setStyleSheet(
|
|
"""
|
|
QMessageBox {
|
|
background-color: #15181b;
|
|
color: #ffffff;
|
|
}
|
|
QMessageBox QLabel {
|
|
color: #ffffff;
|
|
}
|
|
QMessageBox QPushButton {
|
|
padding: 8px 15px;
|
|
background-color: #c90000;
|
|
border: none;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: bold;
|
|
min-width: 80px;
|
|
}
|
|
QMessageBox QPushButton:hover {
|
|
background-color: #a50000;
|
|
}
|
|
"""
|
|
)
|
|
msg_box.exec()
|