"""
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
from PySide6.QtWidgets import (
QDialog,
QDialogButtonBox,
QHBoxLayout,
QLabel,
QMessageBox,
QPushButton,
QSizePolicy,
QTextEdit,
QVBoxLayout,
QWidget,
)
from src.utils.ytsage_localization import _
from src.core.ytsage_ffmpeg import get_ffmpeg_path
from src.core.ytsage_utils import _version_cache, check_ffmpeg, get_ffmpeg_version, get_ytdlp_version, refresh_version_cache
from src.core.ytsage_yt_dlp import check_ytdlp_installed, get_yt_dlp_path
class LogWindow(QDialog):
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.setWindowTitle("yt-dlp Log")
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(
"YTSage"
)
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title_label)
version_label = QLabel(
f"{_('about.version', version=getattr(self._parent, 'version', '4.9.0b'))}"
)
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_label = QLabel(
f"{_('about.author', author='oop7')}"
)
author_label.setOpenExternalLinks(True)
info_layout.addWidget(author_label)
repo_label = QLabel(
f"{_('about.github', repo='YTSage')}"
)
repo_label.setOpenExternalLinks(True)
info_layout.addWidget(repo_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 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."""
loading_label = QLabel(_("about.loading"))
loading_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
loading_label.setStyleSheet(
"""
QLabel {
color: #cccccc;
font-size: 11px;
font-style: italic;
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} {name}")
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:
"""Update the system information display with compact layout."""
# 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 = check_ytdlp_installed()
ytdlp_status_text = (
f"{_('about.detected')}" if ytdlp_found else f"{_('about.missing')}"
)
ytdlp_version = get_ytdlp_version()
# Get yt-dlp path
ytdlp_path = get_yt_dlp_path() if ytdlp_found else None
ytdlp_path_text = ytdlp_path if ytdlp_path and ytdlp_path != "yt-dlp" else None
# Simplified cache status
ytdlp_cache = _version_cache.get("ytdlp", {})
last_check = ytdlp_cache.get("last_check", 0)
cache_status = ""
if last_check > 0:
cache_time = datetime.fromtimestamp(last_check).strftime("%H:%M")
cache_status = f" ({cache_time})" # 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 = check_ffmpeg()
ffmpeg_status_text = (
f"{_('about.detected')}"
if ffmpeg_found
else f"{_('about.missing')}"
)
ffmpeg_version = get_ffmpeg_version() if ffmpeg_found else _('about.not_available')
# Get FFmpeg path
ffmpeg_path_text = None
if ffmpeg_found:
ffmpeg_path = get_ffmpeg_path()
ffmpeg_path_text = ffmpeg_path if ffmpeg_path else None
# Simplified cache status for FFmpeg
ffmpeg_cache = _version_cache.get("ffmpeg", {})
last_check = ffmpeg_cache.get("last_check", 0)
cache_status = ""
if last_check > 0 and ffmpeg_found:
cache_time = datetime.fromtimestamp(last_check).strftime("%H:%M")
cache_status = f" ({cache_time})" # 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)
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()