29f713cf17
Adjusted padding, margin, and spacing for a more compact layout. Standardized the thumbnail size to 240x135 (16:9 ratio) and reduced the menu button size for better visual balance.
658 lines
23 KiB
Python
658 lines
23 KiB
Python
"""
|
|
History Dialog for YTSage application.
|
|
Displays download history with thumbnails and provides options to redownload or remove entries.
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
from datetime import datetime
|
|
from io import BytesIO
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
import requests
|
|
from PIL import Image
|
|
from PySide6.QtCore import Qt, QSize, Signal, QThread, QTimer
|
|
from PySide6.QtGui import QPixmap, QIcon
|
|
from PySide6.QtWidgets import (
|
|
QDialog,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QLineEdit,
|
|
QPushButton,
|
|
QScrollArea,
|
|
QWidget,
|
|
QFrame,
|
|
QMenu,
|
|
QMessageBox,
|
|
QSizePolicy,
|
|
QProgressBar,
|
|
)
|
|
|
|
from src.utils.ytsage_history_manager import HistoryManager
|
|
from src.utils.ytsage_constants import APP_THUMBNAILS_DIR, SUBPROCESS_CREATIONFLAGS
|
|
from src.utils.ytsage_localization import _
|
|
from src.utils.ytsage_logger import logger
|
|
|
|
if TYPE_CHECKING:
|
|
from src.gui.ytsage_gui_main import YTSageApp
|
|
|
|
|
|
class HistoryLoaderThread(QThread):
|
|
"""Thread to load history and pre-fetch thumbnails."""
|
|
|
|
entries_loaded = Signal(list)
|
|
thumbnail_loaded = Signal(str)
|
|
|
|
def run(self):
|
|
try:
|
|
# HistoryManager uses get_all_entries, not get_all
|
|
entries = HistoryManager.get_all_entries()
|
|
|
|
# Emit entries immediately so UI shows up
|
|
self.entries_loaded.emit(entries)
|
|
|
|
# Pre-fetch thumbnails in background
|
|
for entry in entries:
|
|
thumbnail_url = entry.get("thumbnail_url")
|
|
entry_id = entry.get("id", "")
|
|
|
|
if not thumbnail_url or not entry_id:
|
|
continue
|
|
|
|
thumbnail_filename = f"{entry_id}.jpg"
|
|
thumbnail_path = APP_THUMBNAILS_DIR / thumbnail_filename
|
|
|
|
# If not exists, download it
|
|
if not thumbnail_path.exists():
|
|
try:
|
|
response = requests.get(thumbnail_url, timeout=5)
|
|
if response.status_code == 200:
|
|
APP_THUMBNAILS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
image = Image.open(BytesIO(response.content))
|
|
image.save(thumbnail_path, "JPEG", quality=95, optimize=True)
|
|
self.thumbnail_loaded.emit(entry_id)
|
|
except Exception as e:
|
|
logger.debug(f"Error caching thumbnail in background: {e}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error loading history: {e}")
|
|
self.entries_loaded.emit([])
|
|
|
|
|
|
class HistoryEntryWidget(QFrame):
|
|
"""Widget representing a single history entry."""
|
|
|
|
remove_requested = Signal(str) # Emit entry ID when remove is requested
|
|
redownload_requested = Signal(dict) # Emit entry data when redownload is requested
|
|
|
|
def __init__(self, entry: dict, parent=None):
|
|
super().__init__(parent)
|
|
self.entry = entry
|
|
self.entry_id = entry.get("id", "")
|
|
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
"""Setup the UI for this history entry."""
|
|
self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Raised)
|
|
self.setStyleSheet("""
|
|
QFrame {
|
|
background-color: #1d1e22;
|
|
border: 1px solid #2a2d36;
|
|
border-radius: 8px;
|
|
padding: 4px;
|
|
margin: 2px;
|
|
}
|
|
QFrame:hover {
|
|
background-color: #252830;
|
|
border-color: #3a3d46;
|
|
}
|
|
""")
|
|
|
|
main_layout = QHBoxLayout(self)
|
|
main_layout.setSpacing(12)
|
|
main_layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
# Thumbnail - Standard YouTube 16:9 ratio (e.g., 240x135)
|
|
self.thumbnail_label = QLabel()
|
|
self.thumbnail_label.setFixedSize(240, 135)
|
|
self.thumbnail_label.setStyleSheet("""
|
|
QLabel {
|
|
border: 2px solid #3d3d3d;
|
|
border-radius: 6px;
|
|
background-color: #15181b;
|
|
}
|
|
""")
|
|
self.thumbnail_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
self.thumbnail_label.setScaledContents(True)
|
|
|
|
# Load thumbnail
|
|
self.load_thumbnail()
|
|
|
|
main_layout.addWidget(self.thumbnail_label, alignment=Qt.AlignmentFlag.AlignTop)
|
|
|
|
# Info section
|
|
info_layout = QVBoxLayout()
|
|
info_layout.setSpacing(5)
|
|
|
|
# Title
|
|
title = self.entry.get("title", _("video_info.unknown_title"))
|
|
self.title_label = QLabel(title)
|
|
self.title_label.setWordWrap(True)
|
|
self.title_label.setStyleSheet("""
|
|
QLabel {
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
color: #ffffff;
|
|
}
|
|
""")
|
|
info_layout.addWidget(self.title_label)
|
|
|
|
# Channel (if available)
|
|
channel = self.entry.get("channel")
|
|
if channel:
|
|
channel_label = QLabel(f"{_('video_info.channel')}: {channel}")
|
|
channel_label.setStyleSheet("color: #cccccc; font-size: 12px;")
|
|
info_layout.addWidget(channel_label)
|
|
|
|
# Download date
|
|
download_date = self.entry.get("download_date", "")
|
|
if download_date:
|
|
try:
|
|
dt = datetime.fromisoformat(download_date)
|
|
date_str = dt.strftime("%Y-%m-%d %H:%M")
|
|
date_label = QLabel(_("history.downloaded_on", date=date_str))
|
|
date_label.setStyleSheet("color: #aaaaaa; font-size: 11px;")
|
|
info_layout.addWidget(date_label)
|
|
except Exception as e:
|
|
logger.debug(f"Error parsing date: {e}")
|
|
|
|
# File size and type
|
|
file_size = self.entry.get("file_size", 0)
|
|
is_audio = self.entry.get("is_audio_only", False)
|
|
|
|
size_type_layout = QHBoxLayout()
|
|
|
|
# File type badge
|
|
type_badge = QLabel(_("history.audio_download") if is_audio else _("history.video_download"))
|
|
type_badge.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: {'#c90000' if not is_audio else '#0066cc'};
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 3px;
|
|
font-size: 10px;
|
|
font-weight: bold;
|
|
}}
|
|
""")
|
|
size_type_layout.addWidget(type_badge)
|
|
|
|
# File size
|
|
if file_size > 0:
|
|
size_str = self.format_file_size(file_size)
|
|
size_label = QLabel(_("history.file_size", size=size_str))
|
|
size_label.setStyleSheet("color: #aaaaaa; font-size: 11px;")
|
|
size_type_layout.addWidget(size_label)
|
|
|
|
size_type_layout.addStretch()
|
|
info_layout.addLayout(size_type_layout)
|
|
|
|
info_layout.addStretch()
|
|
|
|
main_layout.addLayout(info_layout, 1)
|
|
|
|
# Three-dot menu button
|
|
self.menu_button = QPushButton("⋮")
|
|
self.menu_button.setFixedSize(32, 32)
|
|
self.menu_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #2a2d36;
|
|
border: none;
|
|
border-radius: 16px;
|
|
color: white;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
padding-bottom: 5px; /* Adjust vertical alignment of dots */
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #3a3d46;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #c90000;
|
|
}
|
|
""")
|
|
self.menu_button.clicked.connect(self.show_menu)
|
|
|
|
main_layout.addWidget(self.menu_button, alignment=Qt.AlignmentFlag.AlignTop)
|
|
|
|
def load_thumbnail(self):
|
|
"""Load and display the thumbnail."""
|
|
thumbnail_url = self.entry.get("thumbnail_url")
|
|
|
|
if not thumbnail_url:
|
|
self.set_placeholder_thumbnail()
|
|
return
|
|
|
|
# Check if thumbnail is cached
|
|
thumbnail_filename = f"{self.entry_id}.jpg"
|
|
thumbnail_path = APP_THUMBNAILS_DIR / thumbnail_filename
|
|
|
|
if thumbnail_path.exists():
|
|
try:
|
|
pixmap = QPixmap(str(thumbnail_path))
|
|
if not pixmap.isNull():
|
|
# Don't scale here, let setScaledContents handle it
|
|
self.thumbnail_label.setPixmap(pixmap)
|
|
return
|
|
except Exception as e:
|
|
logger.debug(f"Error loading cached thumbnail: {e}")
|
|
|
|
# If not in cache, just set placeholder.
|
|
# Background thread will download it and notify parent to reload.
|
|
self.set_placeholder_thumbnail()
|
|
|
|
def reload_thumbnail(self):
|
|
"""Reload thumbnail from cache (called when background download finishes)."""
|
|
self.load_thumbnail()
|
|
|
|
def set_placeholder_thumbnail(self):
|
|
"""Set a placeholder when thumbnail is not available."""
|
|
self.thumbnail_label.setText("📹" if not self.entry.get("is_audio_only") else "🎵")
|
|
self.thumbnail_label.setStyleSheet("""
|
|
QLabel {
|
|
border: 1px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
background-color: #15181b;
|
|
color: #666666;
|
|
font-size: 48px;
|
|
}
|
|
""")
|
|
|
|
def format_file_size(self, size_bytes: int) -> str:
|
|
"""Format file size in human-readable format."""
|
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
if size_bytes < 1024.0:
|
|
return f"{size_bytes:.1f} {unit}"
|
|
size_bytes /= 1024.0
|
|
return f"{size_bytes:.1f} TB"
|
|
|
|
def show_menu(self):
|
|
"""Show the context menu with options."""
|
|
menu = QMenu(self)
|
|
menu.setStyleSheet("""
|
|
QMenu {
|
|
background-color: #2a2d36;
|
|
border: 1px solid #3a3d46;
|
|
color: white;
|
|
padding: 5px;
|
|
}
|
|
QMenu::item {
|
|
padding: 8px 20px;
|
|
border-radius: 4px;
|
|
}
|
|
QMenu::item:selected {
|
|
background-color: #c90000;
|
|
}
|
|
""")
|
|
|
|
# Open file location
|
|
open_action = menu.addAction("📁 " + _("history.open_location"))
|
|
open_action.triggered.connect(self.open_file_location)
|
|
|
|
# Redownload
|
|
redownload_action = menu.addAction("⬇️ " + _("history.redownload"))
|
|
redownload_action.triggered.connect(self.redownload)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Remove from history
|
|
remove_action = menu.addAction("🗑️ " + _("history.remove"))
|
|
remove_action.triggered.connect(self.remove_from_history)
|
|
|
|
# Show menu at button position
|
|
menu.exec(self.menu_button.mapToGlobal(self.menu_button.rect().bottomLeft()))
|
|
|
|
def open_file_location(self):
|
|
"""Open the file location in the system file explorer."""
|
|
file_path = Path(self.entry.get("file_path", ""))
|
|
|
|
if not file_path.exists():
|
|
QMessageBox.warning(
|
|
self,
|
|
_("history.file_not_found"),
|
|
_("history.file_not_found_message", path=str(file_path))
|
|
)
|
|
return
|
|
|
|
try:
|
|
# On Windows, use explorer with /select to highlight the file
|
|
if os.name == "nt":
|
|
subprocess.run(['explorer', '/select,', str(file_path)], creationflags=SUBPROCESS_CREATIONFLAGS)
|
|
# On macOS, use open with -R to reveal in Finder
|
|
elif subprocess.sys.platform == "darwin":
|
|
subprocess.run(['open', '-R', str(file_path)])
|
|
# On Linux, try to open the folder
|
|
else:
|
|
folder_path = file_path.parent
|
|
subprocess.run(['xdg-open', str(folder_path)])
|
|
|
|
logger.info(f"Opened file location: {file_path}")
|
|
except Exception as e:
|
|
logger.exception(f"Error opening file location: {e}")
|
|
QMessageBox.warning(self, "Error", f"Could not open file location: {str(e)}")
|
|
|
|
def redownload(self):
|
|
"""Request redownload of this entry."""
|
|
reply = QMessageBox.question(
|
|
self,
|
|
_("history.redownload_confirm_title"),
|
|
_("history.redownload_confirm_message", title=self.entry.get("title", "")),
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
self.redownload_requested.emit(self.entry)
|
|
|
|
def remove_from_history(self):
|
|
"""Request removal of this entry from history."""
|
|
reply = QMessageBox.question(
|
|
self,
|
|
_("history.remove_confirm_title"),
|
|
_("history.remove_confirm_message", title=self.entry.get("title", "")),
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
self.remove_requested.emit(self.entry_id)
|
|
|
|
|
|
class HistoryDialog(QDialog):
|
|
"""Dialog to display and manage download history."""
|
|
|
|
redownload_requested = Signal(dict) # Signal to request redownload in main window
|
|
|
|
def __init__(self, parent: Optional["YTSageApp"] = None):
|
|
super().__init__(parent)
|
|
self.parent_app = parent
|
|
self.entry_widgets = []
|
|
self.entry_widgets_map = {} # Map entry_id -> widget
|
|
|
|
self.setup_ui()
|
|
|
|
# Show loading state initially
|
|
self.show_loading_state()
|
|
|
|
# Start loading history in background after a short delay
|
|
# to ensure the dialog is shown first
|
|
QTimer.singleShot(100, self.start_loading_history)
|
|
|
|
def start_loading_history(self):
|
|
"""Start the background thread to load history."""
|
|
self.loader_thread = HistoryLoaderThread()
|
|
self.loader_thread.entries_loaded.connect(self.on_history_loaded)
|
|
self.loader_thread.thumbnail_loaded.connect(self.update_entry_thumbnail)
|
|
self.loader_thread.start()
|
|
|
|
def on_history_loaded(self, entries):
|
|
"""Called when history is loaded from background thread."""
|
|
self.load_history_entries(entries)
|
|
|
|
def update_entry_thumbnail(self, entry_id: str):
|
|
"""Called when a thumbnail is downloaded in the background."""
|
|
if entry_id in self.entry_widgets_map:
|
|
self.entry_widgets_map[entry_id].reload_thumbnail()
|
|
|
|
def setup_ui(self):
|
|
"""Setup the dialog UI."""
|
|
self.setWindowTitle(_("history.title"))
|
|
self.setMinimumSize(700, 500)
|
|
self.resize(850, 600)
|
|
|
|
# Set window flags
|
|
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowCloseButtonHint)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(10)
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
# Header with title and buttons
|
|
header_layout = QHBoxLayout()
|
|
|
|
title_label = QLabel(_("history.title"))
|
|
title_label.setStyleSheet("""
|
|
QLabel {
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
color: white;
|
|
}
|
|
""")
|
|
header_layout.addWidget(title_label)
|
|
|
|
header_layout.addStretch()
|
|
|
|
# Clear all button
|
|
self.clear_all_btn = QPushButton(_("history.clear_all"))
|
|
self.clear_all_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #c90000;
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #a50000;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #800000;
|
|
}
|
|
""")
|
|
self.clear_all_btn.clicked.connect(self.clear_all_history)
|
|
header_layout.addWidget(self.clear_all_btn)
|
|
|
|
layout.addLayout(header_layout)
|
|
|
|
# Search bar
|
|
self.search_input = QLineEdit()
|
|
self.search_input.setPlaceholderText(_("history.search_placeholder"))
|
|
self.search_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 10px;
|
|
border: 2px solid #1b2021;
|
|
border-radius: 4px;
|
|
background-color: #1b2021;
|
|
color: #ffffff;
|
|
font-size: 13px;
|
|
}
|
|
""")
|
|
self.search_input.textChanged.connect(self.filter_history)
|
|
layout.addWidget(self.search_input)
|
|
|
|
# Scroll area for history entries
|
|
scroll_area = QScrollArea()
|
|
scroll_area.setWidgetResizable(True)
|
|
scroll_area.setStyleSheet("""
|
|
QScrollArea {
|
|
border: none;
|
|
background-color: transparent;
|
|
}
|
|
""")
|
|
|
|
# Container for history entries
|
|
self.history_container = QWidget()
|
|
self.history_layout = QVBoxLayout(self.history_container)
|
|
self.history_layout.setSpacing(10)
|
|
self.history_layout.setContentsMargins(0, 0, 0, 0)
|
|
self.history_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
scroll_area.setWidget(self.history_container)
|
|
layout.addWidget(scroll_area)
|
|
|
|
# Status label
|
|
self.status_label = QLabel()
|
|
self.status_label.setStyleSheet("color: #aaaaaa; font-size: 12px;")
|
|
layout.addWidget(self.status_label)
|
|
|
|
# Apply dark theme
|
|
self.setStyleSheet("""
|
|
QDialog {
|
|
background-color: #15181b;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
}
|
|
""")
|
|
|
|
def clear_history_list(self):
|
|
"""Clear existing history widgets."""
|
|
for widget in self.entry_widgets:
|
|
widget.deleteLater()
|
|
self.entry_widgets.clear()
|
|
self.entry_widgets_map.clear()
|
|
|
|
def show_loading_state(self):
|
|
"""Show loading indicator."""
|
|
# Clear existing content
|
|
self.clear_history_list()
|
|
|
|
loading_widget = QWidget()
|
|
loading_layout = QVBoxLayout(loading_widget)
|
|
loading_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
spinner_label = QLabel("⏳") # Simple spinner icon
|
|
spinner_label.setStyleSheet("font-size: 48px; color: #c90000;")
|
|
spinner_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
loading_layout.addWidget(spinner_label)
|
|
|
|
# text_label removed as per user request
|
|
|
|
self.history_layout.addWidget(loading_widget)
|
|
self.entry_widgets.append(loading_widget)
|
|
|
|
self.status_label.setText("Loading...")
|
|
self.clear_all_btn.setEnabled(False)
|
|
|
|
def load_history(self):
|
|
"""Load and display history entries."""
|
|
if hasattr(self, 'history_container'):
|
|
self.show_loading_state()
|
|
self.start_loading_history()
|
|
|
|
def load_history_entries(self, entries):
|
|
"""Populate the history list with entries."""
|
|
self.clear_history_list()
|
|
|
|
if not entries:
|
|
self.show_empty_state()
|
|
return
|
|
|
|
# Create widgets for each entry
|
|
for entry in entries:
|
|
widget = HistoryEntryWidget(entry, self.history_container)
|
|
widget.remove_requested.connect(self.remove_entry)
|
|
widget.redownload_requested.connect(self.handle_redownload)
|
|
|
|
self.history_layout.addWidget(widget)
|
|
|
|
# Map widget by ID for updates
|
|
entry_id = entry.get("id")
|
|
if entry_id:
|
|
self.entry_widgets_map[entry_id] = widget
|
|
self.entry_widgets.append(widget)
|
|
|
|
# Update status
|
|
count = len(entries)
|
|
if count == 1:
|
|
status_text = _("history.one_entry")
|
|
else:
|
|
status_text = _("history.entries_count", count=count)
|
|
self.status_label.setText(status_text)
|
|
self.clear_all_btn.setEnabled(True)
|
|
|
|
def show_empty_state(self):
|
|
"""Show empty state when there's no history."""
|
|
empty_widget = QWidget()
|
|
empty_layout = QVBoxLayout(empty_widget)
|
|
empty_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
icon_label = QLabel("📂")
|
|
icon_label.setStyleSheet("font-size: 64px; color: #555555;")
|
|
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
empty_layout.addWidget(icon_label)
|
|
|
|
title_label = QLabel(_("history.no_history"))
|
|
title_label.setStyleSheet("font-size: 16px; color: #888888; font-weight: bold;")
|
|
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
empty_layout.addWidget(title_label)
|
|
|
|
desc_label = QLabel(_("history.no_history_description"))
|
|
desc_label.setStyleSheet("font-size: 13px; color: #666666;")
|
|
desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
empty_layout.addWidget(desc_label)
|
|
|
|
self.history_layout.addWidget(empty_widget)
|
|
self.entry_widgets.append(empty_widget)
|
|
|
|
self.status_label.setText("")
|
|
self.clear_all_btn.setEnabled(False)
|
|
|
|
def filter_history(self, query: str):
|
|
"""Filter history entries based on search query."""
|
|
if not query:
|
|
# Show all entries
|
|
for widget in self.entry_widgets:
|
|
widget.show()
|
|
return
|
|
|
|
# Hide/show based on query
|
|
query_lower = query.lower()
|
|
visible_count = 0
|
|
|
|
for widget in self.entry_widgets:
|
|
if isinstance(widget, HistoryEntryWidget):
|
|
title = (widget.entry.get("title") or "").lower()
|
|
channel = (widget.entry.get("channel") or "").lower()
|
|
|
|
if query_lower in title or query_lower in channel:
|
|
widget.show()
|
|
visible_count += 1
|
|
else:
|
|
widget.hide()
|
|
|
|
def remove_entry(self, entry_id: str):
|
|
"""Remove an entry from history."""
|
|
success = HistoryManager.remove_entry(entry_id)
|
|
|
|
if success:
|
|
# Reload history
|
|
self.load_history()
|
|
logger.info(f"Removed entry from history: {entry_id}")
|
|
else:
|
|
QMessageBox.warning(self, "Error", "Failed to remove entry from history")
|
|
|
|
def clear_all_history(self):
|
|
"""Clear all history entries."""
|
|
reply = QMessageBox.question(
|
|
self,
|
|
_("history.clear_confirm_title"),
|
|
_("history.clear_confirm_message"),
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
count = HistoryManager.clear_history()
|
|
self.load_history()
|
|
logger.info(f"Cleared all history: {count} entries")
|
|
|
|
def handle_redownload(self, entry: dict):
|
|
"""Handle redownload request."""
|
|
# Emit signal to parent window
|
|
self.redownload_requested.emit(entry)
|
|
|
|
# Close dialog
|
|
self.accept()
|