Refactor UI startup and stylesheet handling

Moved the main application stylesheet to a new src/gui/ytsage_stylesheet.py module and applied it via StyleSheet.MAIN. Deferred blocking startup checks (FFmpeg, yt-dlp, Deno, updates) until after the UI is shown to improve responsiveness. Improved subtitle file deletion logic and added destination filename capture in downloader. Added custom style for auto-update checkbox in updater dialog.
This commit is contained in:
oop7
2026-01-24 13:35:02 +02:00
parent 4d4d9e10b9
commit 9759fa565f
5 changed files with 198 additions and 218 deletions
-17
View File
@@ -3,8 +3,6 @@ import sys
from PySide6.QtWidgets import QApplication, QMessageBox from PySide6.QtWidgets import QApplication, QMessageBox
from src.utils.ytsage_logger import logger from src.utils.ytsage_logger import logger
from src.core.ytsage_yt_dlp import check_ytdlp_binary, setup_ytdlp # Import the new yt-dlp setup functions
from src.core.ytsage_deno import check_deno_binary, setup_deno # Import the new Deno setup functions
from src.gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main from src.gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main
@@ -22,21 +20,6 @@ def main():
logger.info("Starting YTSage application") logger.info("Starting YTSage application")
app = QApplication(sys.argv) app = QApplication(sys.argv)
# Get the expected binary path and check if it exists
if not check_ytdlp_binary():
# No app-specific binary found, show setup dialog regardless of Python package
logger.warning("No yt-dlp binary found, starting setup process")
yt_dlp_path = setup_ytdlp()
if yt_dlp_path == "yt-dlp": # If user canceled or something went wrong
logger.warning("yt-dlp not configured properly")
# Check for Deno binary
if not check_deno_binary():
logger.warning("No Deno binary found, starting setup process")
deno_path = setup_deno()
if deno_path == "deno": # If user canceled or something went wrong
logger.warning("Deno not configured properly")
window = YTSageApp() # Instantiate the main application class window = YTSageApp() # Instantiate the main application class
window.show() window.show()
logger.info("Application window shown, entering main loop") logger.info("Application window shown, entering main loop")
+11 -2
View File
@@ -189,9 +189,12 @@ class DownloadThread(QThread):
def safe_delete(path: Path) -> bool: def safe_delete(path: Path) -> bool:
try: try:
# Check if file exists before trying to delete
if path.exists():
path.unlink(missing_ok=True) path.unlink(missing_ok=True)
logger.debug(f"Deleted subtitle file: {path.name}") logger.debug(f"Deleted subtitle file: {path.name}")
return True return True
return False
except Exception as e: except Exception as e:
logger.exception(f"Error deleting subtitle file {path}: {e}") logger.exception(f"Error deleting subtitle file {path}: {e}")
return False return False
@@ -410,8 +413,6 @@ class DownloadThread(QThread):
self._terminate_process_tree(self.process) self._terminate_process_tree(self.process)
# Add delay before cleanup to allow file handles to be released # Add delay before cleanup to allow file handles to be released
# Force garbage collection to help release resources
gc.collect()
time.sleep(2) time.sleep(2)
self.cleanup_partial_files() self.cleanup_partial_files()
self.status_signal.emit(_("download.cancelled")) self.status_signal.emit(_("download.cancelled"))
@@ -622,6 +623,14 @@ class DownloadThread(QThread):
if "Downloading webpage" in line or "Extracting URL" in line: if "Downloading webpage" in line or "Extracting URL" in line:
self.status_signal.emit(_("download.fetching_info")) self.status_signal.emit(_("download.fetching_info"))
self.progress_signal.emit(0) self.progress_signal.emit(0)
elif "[download] Destination:" in line:
# Extract the destination filename
match = re.search(r"Destination: (.+)", line)
if match:
dest_path = match.group(1).strip()
self.current_filename = Path(dest_path).name
self.last_file_path = dest_path
logger.debug(f"Captured destination filename: {self.current_filename}")
elif "Downloading API JSON" in line: elif "Downloading API JSON" in line:
self.status_signal.emit(_("download.processing_playlist")) self.status_signal.emit(_("download.processing_playlist"))
self.progress_signal.emit(0) self.progress_signal.emit(0)
@@ -492,6 +492,32 @@ class UpdaterTabWidget(QWidget):
# Enable/Disable auto-update checkbox # Enable/Disable auto-update checkbox
self.auto_update_enabled = QCheckBox(_("settings.enable_auto_updates")) self.auto_update_enabled = QCheckBox(_("settings.enable_auto_updates"))
self.auto_update_enabled.setStyleSheet(
"""
QCheckBox {
color: #ffffff;
spacing: 5px;
padding: 3px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
border-radius: 9px;
}
QCheckBox::indicator:unchecked {
border: 2px solid #666666;
background: #1d1e22;
border-radius: 9px;
}
QCheckBox::indicator:checked {
border: 2px solid #c90000;
background: #c90000;
border-radius: 9px;
}
QCheckBox:disabled { color: #888888; }
QCheckBox::indicator:disabled { border-color: #555555; background: #444444; }
"""
)
auto_update_layout.addWidget(self.auto_update_enabled) auto_update_layout.addWidget(self.auto_update_enabled)
# Frequency options # Frequency options
+32 -196
View File
@@ -51,6 +51,7 @@ from src.utils.ytsage_logger import logger
from src.utils.ytsage_config_manager import ConfigManager from src.utils.ytsage_config_manager import ConfigManager
from src.utils.ytsage_localization import LocalizationManager, _ from src.utils.ytsage_localization import LocalizationManager, _
from src.utils.ytsage_history_manager import HistoryManager from src.utils.ytsage_history_manager import HistoryManager
from src.gui.ytsage_stylesheet import StyleSheet
class UpdateCheckThread(QThread): class UpdateCheckThread(QThread):
@@ -105,30 +106,7 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
saved_language = ConfigManager.get("language") or "en" saved_language = ConfigManager.get("language") or "en"
LocalizationManager.initialize(saved_language) LocalizationManager.initialize(saved_language)
# Check for FFmpeg before proceeding
if not check_ffmpeg():
self.show_ffmpeg_dialog()
# Check for yt-dlp in our app's bin directory or system PATH
ytdlp_path = get_yt_dlp_path()
if ytdlp_path == "yt-dlp": # Not found in app dir or PATH
self.show_ytdlp_setup_dialog()
else:
logger.info(f"Using yt-dlp from: {ytdlp_path}")
# Check for Deno in our app's bin directory
deno_path = get_deno_path()
if deno_path == "deno": # Not found in app dir
self.show_deno_setup_dialog()
else:
logger.info(f"Using Deno from: {deno_path}")
self.version = APP_VERSION self.version = APP_VERSION
self.check_for_updates()
# Check for auto-updates if enabled
self.check_auto_update_ytdlp()
load_saved_path(self) load_saved_path(self)
# Load custom icon # Load custom icon
if ICON_PATH.exists(): if ICON_PATH.exists():
@@ -176,178 +154,11 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
self.analysis_completed = False self.analysis_completed = False
self.init_ui() self.init_ui()
self.setStyleSheet(
""" # Defer heavy start-up tasks to ensure UI renders immediately
QMainWindow { QTimer.singleShot(100, self._perform_startup_checks)
background-color: #15181b;
} self.setStyleSheet(StyleSheet.MAIN)
QWidget {
background-color: #15181b;
color: #ffffff;
}
QLineEdit {
padding: 5px 15px;
border: 2px solid #2a2d2e;
border-radius: 6px;
background-color: #1b2021;
color: #ffffff;
font-size: 13px;
}
QLineEdit:focus {
border-color: #ff6b6b;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
QPushButton:pressed {
background-color: #800000;
}
QPushButton:disabled {
background-color: #3d3d3d;
color: #888888;
}
QTableWidget {
border: 2px solid #1b2021;
border-radius: 4px;
background-color: #1b2021;
gridline-color: #1b2021;
}
QHeaderView::section {
background-color: #15181b;
padding: 5px;
border: 1px solid #1b2021;
color: #ffffff;
}
QProgressBar {
border: 2px solid #1b2021;
border-radius: 4px;
text-align: center;
color: white;
}
QProgressBar::chunk {
background-color: #c90000;
border-radius: 2px;
}
QLabel {
color: #ffffff;
}
/* Style for filter buttons */
QPushButton.filter-btn {
background-color: #1b2021;
padding: 5px 10px;
margin: 0 5px;
}
QPushButton.filter-btn:checked {
background-color: #c90000;
}
QPushButton.filter-btn:hover {
background-color: #444444;
}
QPushButton.filter-btn:checked:hover {
background-color: #a50000;
}
/* Modern Scrollbar Styling */
QScrollBar:vertical {
border: none;
background: #15181b;
width: 14px;
margin: 15px 0 15px 0;
border-radius: 7px;
}
QScrollBar::handle:vertical {
background: #404040;
min-height: 30px;
border-radius: 7px;
}
QScrollBar::handle:vertical:hover {
background: #505050;
}
QScrollBar::sub-line:vertical {
border: none;
background: #15181b;
height: 15px;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical {
border: none;
background: #15181b;
height: 15px;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:hover,
QScrollBar::add-line:vertical:hover {
background: #404040;
}
QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {
background: none;
width: 0;
height: 0;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
/* Horizontal Scrollbar */
QScrollBar:horizontal {
border: none;
background: #15181b;
height: 14px;
margin: 0 15px 0 15px;
border-radius: 7px;
}
QScrollBar::handle:horizontal {
background: #404040;
min-width: 30px;
border-radius: 7px;
}
QScrollBar::handle:horizontal:hover {
background: #505050;
}
QScrollBar::sub-line:horizontal {
border: none;
background: #15181b;
width: 15px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal {
border: none;
background: #15181b;
width: 15px;
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:hover,
QScrollBar::add-line:horizontal:hover {
background: #404040;
}
QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal {
background: none;
width: 0;
height: 0;
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: none;
}
"""
)
self.signals.update_progress.connect(self.update_progress_bar) self.signals.update_progress.connect(self.update_progress_bar)
# After adding format buttons # After adding format buttons
@@ -361,7 +172,32 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin): # Inherit from
# Initialize UI state based on current mode # Initialize UI state based on current mode
self.handle_mode_change() self.handle_mode_change()
# Init_sound method is removed, serve no purpose.
def _perform_startup_checks(self):
"""Perform potentially blocking startup checks after UI is shown."""
# Check for FFmpeg before proceeding
if not check_ffmpeg():
self.show_ffmpeg_dialog()
# Check for yt-dlp in our app's bin directory or system PATH
ytdlp_path = get_yt_dlp_path()
if ytdlp_path == "yt-dlp": # Not found in app dir or PATH
self.show_ytdlp_setup_dialog()
else:
logger.info(f"Using yt-dlp from: {ytdlp_path}")
# Check for Deno in our app's bin directory
deno_path = get_deno_path()
if deno_path == "deno": # Not found in app dir
self.show_deno_setup_dialog()
else:
logger.info(f"Using Deno from: {deno_path}")
self.check_for_updates()
# Check for auto-updates if enabled
QTimer.singleShot(2000, self.check_auto_update_ytdlp) # Further delay auto-update check
def play_notification_sound(self) -> None: def play_notification_sound(self) -> None:
"""Play notification sound asynchronously (non-blocking).""" """Play notification sound asynchronously (non-blocking)."""
+126
View File
@@ -0,0 +1,126 @@
class StyleSheet:
MAIN = """
QMainWindow {
background-color: #15181b;
}
QWidget {
background-color: #15181b;
color: #ffffff;
}
QLineEdit {
padding: 5px 15px;
border: 2px solid #2a2d2e;
border-radius: 6px;
background-color: #1b2021;
color: #ffffff;
font-size: 13px;
}
QLineEdit:focus {
border-color: #ff6b6b;
}
QPushButton {
padding: 8px 15px;
background-color: #c90000;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
}
QPushButton:hover {
background-color: #a50000;
}
QPushButton:pressed {
background-color: #800000;
}
QPushButton:disabled {
background-color: #3d3d3d;
color: #888888;
}
QTableWidget {
border: 2px solid #1b2021;
border-radius: 4px;
background-color: #1b2021;
gridline-color: #1b2021;
}
QHeaderView::section {
background-color: #15181b;
padding: 5px;
border: 1px solid #1b2021;
color: #ffffff;
}
QProgressBar {
border: 2px solid #1b2021;
border-radius: 4px;
text-align: center;
color: white;
}
QProgressBar::chunk {
background-color: #c90000;
border-radius: 2px;
}
QLabel {
color: #ffffff;
}
/* Style for filter buttons */
QPushButton.filter-btn {
background-color: #1b2021;
padding: 5px 10px;
margin: 0 5px;
}
QPushButton.filter-btn:checked {
background-color: #c90000;
}
QPushButton.filter-btn:hover {
background-color: #444444;
}
QPushButton.filter-btn:checked:hover {
background-color: #a50000;
}
/* Modern Scrollbar Styling */
QScrollBar:vertical {
border: none;
background: #15181b;
width: 14px;
margin: 15px 0 15px 0;
border-radius: 7px;
}
QScrollBar::handle:vertical {
background: #404040;
min-height: 30px;
border-radius: 7px;
}
QScrollBar::handle:vertical:hover {
background: #505050;
}
QScrollBar::sub-line:vertical {
border: none;
background: #15181b;
height: 15px;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical {
border: none;
background: #15181b;
height: 15px;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:hover,
QScrollBar::add-line:vertical:hover {
background: #404040;
}
QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {
background: none;
width: 0;
height: 0;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
"""