This commit is contained in:
Yuvi9587
2025-07-15 21:08:11 -07:00
parent 9e58a9d574
commit 574d0d66b4
6 changed files with 306 additions and 183 deletions

View File

@@ -57,6 +57,8 @@ THEME_KEY = "currentThemeV2"
SCAN_CONTENT_IMAGES_KEY = "scanContentForImagesV1"
LANGUAGE_KEY = "currentLanguageV1"
DOWNLOAD_LOCATION_KEY = "downloadLocationV1"
RESOLUTION_KEY = "window_resolution"
UI_SCALE_KEY = "ui_scale_factor"
# --- UI Constants and Identifiers ---
HTML_PREFIX = "<!HTML!>"

View File

@@ -140,12 +140,11 @@ class EmptyPopupDialog (QDialog ):
def __init__ (self ,app_base_dir ,parent_app_ref ,parent =None ):
super ().__init__ (parent )
self .setMinimumSize (400 ,300 )
screen_height =QApplication .primaryScreen ().availableGeometry ().height ()if QApplication .primaryScreen ()else 768
scale_factor =screen_height /768.0
self .setMinimumSize (int (400 *scale_factor ),int (300 *scale_factor ))
self.parent_app = parent_app_ref
self .parent_app =parent_app_ref
scale_factor = getattr(self.parent_app, 'scale_factor', 1.0)
self.setMinimumSize(int(400 * scale_factor), int(300 * scale_factor))
self.current_scope_mode = self.SCOPE_CREATORS
self .app_base_dir =app_base_dir

View File

@@ -1,119 +1,139 @@
# --- Standard Library Imports ---
import os
import json
# --- PyQt5 Imports ---
from PyQt5.QtCore import Qt, QStandardPaths
from PyQt5.QtWidgets import (
QApplication, QDialog, QHBoxLayout, QLabel, QPushButton, QVBoxLayout,
QGroupBox, QComboBox, QMessageBox
QGroupBox, QComboBox, QMessageBox, QGridLayout
)
# --- Local Application Imports ---
# This assumes the new project structure is in place.
from ...i18n.translator import get_translation
from ...utils.resolution import get_dark_theme
from ..main_window import get_app_icon_object
from ...config.constants import (
THEME_KEY, LANGUAGE_KEY, DOWNLOAD_LOCATION_KEY
THEME_KEY, LANGUAGE_KEY, DOWNLOAD_LOCATION_KEY,
RESOLUTION_KEY, UI_SCALE_KEY
)
class FutureSettingsDialog(QDialog):
"""
A dialog for managing application-wide settings like theme, language,
and saving the default download path.
and display options, with an organized layout.
"""
def __init__(self, parent_app_ref, parent=None):
"""
Initializes the dialog.
Args:
parent_app_ref (DownloaderApp): A reference to the main application window.
parent (QWidget, optional): The parent widget. Defaults to None.
"""
super().__init__(parent)
self.parent_app = parent_app_ref
self.setModal(True)
# --- Basic Window Setup ---
app_icon = get_app_icon_object()
if app_icon and not app_icon.isNull():
self.setWindowIcon(app_icon)
# Set window size dynamically
screen_height = QApplication.primaryScreen().availableGeometry().height() if QApplication.primaryScreen() else 768
scale_factor = screen_height / 768.0
base_min_w, base_min_h = 380, 250
screen_height = QApplication.primaryScreen().availableGeometry().height() if QApplication.primaryScreen() else 800
scale_factor = screen_height / 800.0
base_min_w, base_min_h = 420, 320 # Adjusted height for new layout
scaled_min_w = int(base_min_w * scale_factor)
scaled_min_h = int(base_min_h * scale_factor)
self.setMinimumSize(scaled_min_w, scaled_min_h)
# --- Initialize UI and Apply Theming ---
self._init_ui()
self._retranslate_ui()
self._apply_theme()
def _init_ui(self):
"""Initializes all UI components and layouts for the dialog."""
layout = QVBoxLayout(self)
main_layout = QVBoxLayout(self)
# --- Appearance Settings ---
self.appearance_group_box = QGroupBox()
appearance_layout = QVBoxLayout(self.appearance_group_box)
# --- Group 1: Interface Settings ---
self.interface_group_box = QGroupBox()
interface_layout = QGridLayout(self.interface_group_box)
# Theme
self.theme_label = QLabel()
self.theme_toggle_button = QPushButton()
self.theme_toggle_button.clicked.connect(self._toggle_theme)
appearance_layout.addWidget(self.theme_toggle_button)
layout.addWidget(self.appearance_group_box)
interface_layout.addWidget(self.theme_label, 0, 0)
interface_layout.addWidget(self.theme_toggle_button, 0, 1)
# --- Language Settings ---
self.language_group_box = QGroupBox()
language_group_layout = QVBoxLayout(self.language_group_box)
self.language_selection_layout = QHBoxLayout()
# UI Scale
self.ui_scale_label = QLabel()
self.ui_scale_combo_box = QComboBox()
self.ui_scale_combo_box.currentIndexChanged.connect(self._display_setting_changed)
interface_layout.addWidget(self.ui_scale_label, 1, 0)
interface_layout.addWidget(self.ui_scale_combo_box, 1, 1)
# Language
self.language_label = QLabel()
self.language_selection_layout.addWidget(self.language_label)
self.language_combo_box = QComboBox()
self.language_combo_box.currentIndexChanged.connect(self._language_selection_changed)
self.language_selection_layout.addWidget(self.language_combo_box, 1)
language_group_layout.addLayout(self.language_selection_layout)
layout.addWidget(self.language_group_box)
# --- Download Settings ---
self.download_settings_group_box = QGroupBox()
download_settings_layout = QVBoxLayout(self.download_settings_group_box)
interface_layout.addWidget(self.language_label, 2, 0)
interface_layout.addWidget(self.language_combo_box, 2, 1)
main_layout.addWidget(self.interface_group_box)
# --- Group 2: Download & Window Settings ---
self.download_window_group_box = QGroupBox()
download_window_layout = QGridLayout(self.download_window_group_box)
# Window Size (Resolution)
self.window_size_label = QLabel()
self.resolution_combo_box = QComboBox()
self.resolution_combo_box.currentIndexChanged.connect(self._display_setting_changed)
download_window_layout.addWidget(self.window_size_label, 0, 0)
download_window_layout.addWidget(self.resolution_combo_box, 0, 1)
# Default Path
self.default_path_label = QLabel()
self.save_path_button = QPushButton()
self.save_path_button.clicked.connect(self._save_download_path)
download_settings_layout.addWidget(self.save_path_button)
layout.addWidget(self.download_settings_group_box)
download_window_layout.addWidget(self.default_path_label, 1, 0)
download_window_layout.addWidget(self.save_path_button, 1, 1)
layout.addStretch(1)
main_layout.addWidget(self.download_window_group_box)
main_layout.addStretch(1)
# --- OK Button ---
self.ok_button = QPushButton()
self.ok_button.clicked.connect(self.accept)
layout.addWidget(self.ok_button, 0, Qt.AlignRight | Qt.AlignBottom)
main_layout.addWidget(self.ok_button, 0, Qt.AlignRight | Qt.AlignBottom)
def _tr(self, key, default_text=""):
"""Helper to get translation based on the main application's current language."""
if callable(get_translation) and self.parent_app:
return get_translation(self.parent_app.current_selected_language, key, default_text)
return default_text
def _retranslate_ui(self):
"""Sets the text for all translatable UI elements."""
self.setWindowTitle(self._tr("settings_dialog_title", "Settings"))
self.appearance_group_box.setTitle(self._tr("appearance_group_title", "Appearance"))
self.language_group_box.setTitle(self._tr("language_group_title", "Language Settings"))
self.download_settings_group_box.setTitle(self._tr("settings_download_group_title", "Download Settings"))
self.language_label.setText(self._tr("language_label", "Language:"))
self._update_theme_toggle_button_text()
self._populate_language_combo_box()
# Group Box Titles
self.interface_group_box.setTitle(self._tr("interface_group_title", "Interface Settings"))
self.download_window_group_box.setTitle(self._tr("download_window_group_title", "Download & Window Settings"))
# Interface Group Labels
self.theme_label.setText(self._tr("theme_label", "Theme:"))
self.ui_scale_label.setText(self._tr("ui_scale_label", "UI Scale:"))
self.language_label.setText(self._tr("language_label", "Language:"))
# Download & Window Group Labels
self.window_size_label.setText(self._tr("window_size_label", "Window Size:"))
self.default_path_label.setText(self._tr("default_path_label", "Default Path:"))
# Buttons and Controls
self._update_theme_toggle_button_text()
self.save_path_button.setText(self._tr("settings_save_path_button", "Save Current Download Path"))
self.save_path_button.setToolTip(self._tr("settings_save_path_tooltip", "Save the current 'Download Location' for future sessions."))
self.ok_button.setText(self._tr("ok_button", "OK"))
# Populate dropdowns
self._populate_display_combo_boxes()
self._populate_language_combo_box()
def _apply_theme(self):
"""Applies the current theme from the parent application."""
if self.parent_app and self.parent_app.current_theme == "dark":
scale = getattr(self.parent_app, 'scale_factor', 1)
self.setStyleSheet(get_dark_theme(scale))
@@ -121,53 +141,106 @@ class FutureSettingsDialog(QDialog):
self.setStyleSheet("")
def _update_theme_toggle_button_text(self):
"""Updates the theme button text and tooltip based on the current theme."""
if self.parent_app.current_theme == "dark":
self.theme_toggle_button.setText(self._tr("theme_toggle_light", "Switch to Light Mode"))
self.theme_toggle_button.setToolTip(self._tr("theme_tooltip_light", "Change the application appearance to light."))
else:
self.theme_toggle_button.setText(self._tr("theme_toggle_dark", "Switch to Dark Mode"))
self.theme_toggle_button.setToolTip(self._tr("theme_tooltip_dark", "Change the application appearance to dark."))
def _toggle_theme(self):
"""Toggles the application theme and updates the UI."""
new_theme = "light" if self.parent_app.current_theme == "dark" else "dark"
self.parent_app.apply_theme(new_theme)
self._retranslate_ui()
self.parent_app.settings.setValue(THEME_KEY, new_theme)
self.parent_app.settings.sync()
self.parent_app.current_theme = new_theme
self._apply_theme()
if hasattr(self.parent_app, '_apply_theme_and_restart_prompt'):
self.parent_app._apply_theme_and_restart_prompt()
def _populate_display_combo_boxes(self):
self.resolution_combo_box.blockSignals(True)
self.resolution_combo_box.clear()
resolutions = [
("Auto", self._tr("auto_resolution", "Auto (System Default)")),
("1280x720", "1280 x 720"),
("1600x900", "1600 x 900"),
("1920x1080", "1920 x 1080 (Full HD)"),
("2560x1440", "2560 x 1440 (2K)"),
("3840x2160", "3840 x 2160 (4K)")
]
current_res = self.parent_app.settings.value(RESOLUTION_KEY, "Auto")
for res_key, res_name in resolutions:
self.resolution_combo_box.addItem(res_name, res_key)
if current_res == res_key:
self.resolution_combo_box.setCurrentIndex(self.resolution_combo_box.count() - 1)
self.resolution_combo_box.blockSignals(False)
self.ui_scale_combo_box.blockSignals(True)
self.ui_scale_combo_box.clear()
scales = [
(0.5, "50%"),
(0.7, "70%"),
(0.9, "90%"),
(1.0, "100% (Default)"),
(1.25, "125%"),
(1.50, "150%"),
(1.75, "175%"),
(2.0, "200%")
]
current_scale = float(self.parent_app.settings.value(UI_SCALE_KEY, 1.0))
for scale_val, scale_name in scales:
self.ui_scale_combo_box.addItem(scale_name, scale_val)
if abs(current_scale - scale_val) < 0.01:
self.ui_scale_combo_box.setCurrentIndex(self.ui_scale_combo_box.count() - 1)
self.ui_scale_combo_box.blockSignals(False)
def _display_setting_changed(self):
selected_res = self.resolution_combo_box.currentData()
selected_scale = self.ui_scale_combo_box.currentData()
self.parent_app.settings.setValue(RESOLUTION_KEY, selected_res)
self.parent_app.settings.setValue(UI_SCALE_KEY, selected_scale)
self.parent_app.settings.sync()
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Information)
msg_box.setWindowTitle(self._tr("display_change_title", "Display Settings Changed"))
msg_box.setText(self._tr("language_change_message", "A restart is required for these changes to take effect."))
msg_box.setInformativeText(self._tr("language_change_informative", "Would you like to restart now?"))
restart_button = msg_box.addButton(self._tr("restart_now_button", "Restart Now"), QMessageBox.ApplyRole)
ok_button = msg_box.addButton(self._tr("ok_button", "OK"), QMessageBox.AcceptRole)
msg_box.setDefaultButton(ok_button)
msg_box.exec_()
if msg_box.clickedButton() == restart_button:
self.parent_app._request_restart_application()
def _populate_language_combo_box(self):
"""Populates the language dropdown with available languages."""
self.language_combo_box.blockSignals(True)
self.language_combo_box.clear()
languages = [
("en","English"),
("ja","日本語 (Japanese)"),
("fr","Français (French)"),
("de","Deutsch (German)"),
("es","Español (Spanish)"),
("pt","Português (Portuguese)"),
("ru","Русский (Russian)"),
("zh_CN","简体中文 (Simplified Chinese)"),
("zh_TW","繁體中文 (Traditional Chinese)"),
("ko","한국어 (Korean)")
("en", "English"), ("ja", "日本語 (Japanese)"), ("fr", "Français (French)"),
("de", "Deutsch (German)"), ("es", "Español (Spanish)"), ("pt", "Português (Portuguese)"),
("ru", "Русский (Russian)"), ("zh_CN", "简体中文 (Simplified Chinese)"),
("zh_TW", "繁體中文 (Traditional Chinese)"), ("ko", "한국어 (Korean)")
]
current_lang = self.parent_app.current_selected_language
for lang_code, lang_name in languages:
self.language_combo_box.addItem(lang_name, lang_code)
if self.parent_app.current_selected_language == lang_code:
if current_lang == lang_code:
self.language_combo_box.setCurrentIndex(self.language_combo_box.count() - 1)
self.language_combo_box.blockSignals(False)
def _language_selection_changed(self, index):
"""Handles the user selecting a new language."""
selected_lang_code = self.language_combo_box.itemData(index)
if selected_lang_code and selected_lang_code != self.parent_app.current_selected_language:
self.parent_app.current_selected_language = selected_lang_code
self.parent_app.settings.setValue(LANGUAGE_KEY, selected_lang_code)
self.parent_app.settings.sync()
self.parent_app.current_selected_language = selected_lang_code
self._retranslate_ui()
if hasattr(self.parent_app, '_retranslate_main_ui'):
self.parent_app._retranslate_main_ui()
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Information)
msg_box.setWindowTitle(self._tr("language_change_title", "Language Changed"))
@@ -182,23 +255,21 @@ class FutureSettingsDialog(QDialog):
self.parent_app._request_restart_application()
def _save_download_path(self):
"""Saves the current download path from the main window to settings."""
if hasattr(self.parent_app, 'dir_input') and self.parent_app.dir_input:
current_path = self.parent_app.dir_input.text().strip()
if current_path:
if os.path.isdir(current_path):
self.parent_app.settings.setValue(DOWNLOAD_LOCATION_KEY, current_path)
self.parent_app.settings.sync()
QMessageBox.information(self,
self._tr("settings_save_path_success_title", "Path Saved"),
self._tr("settings_save_path_success_message", "Download location '{path}' saved.").format(path=current_path))
else:
QMessageBox.warning(self,
self._tr("settings_save_path_invalid_title", "Invalid Path"),
self._tr("settings_save_path_invalid_message", "The path '{path}' is not a valid directory.").format(path=current_path))
else:
QMessageBox.warning(self,
if current_path and os.path.isdir(current_path):
self.parent_app.settings.setValue(DOWNLOAD_LOCATION_KEY, current_path)
self.parent_app.settings.sync()
QMessageBox.information(self,
self._tr("settings_save_path_success_title", "Path Saved"),
self._tr("settings_save_path_success_message", "Download location '{path}' saved.").format(path=current_path))
elif not current_path:
QMessageBox.warning(self,
self._tr("settings_save_path_empty_title", "Empty Path"),
self._tr("settings_save_path_empty_message", "Download location cannot be empty."))
else:
QMessageBox.warning(self,
self._tr("settings_save_path_invalid_title", "Invalid Path"),
self._tr("settings_save_path_invalid_message", "The path '{path}' is not a valid directory.").format(path=current_path))
else:
QMessageBox.critical(self, "Error", "Could not access download path input from main application.")
QMessageBox.critical(self, "Error", "Could not access download path input from main application.")

View File

@@ -101,18 +101,22 @@ class DownloaderApp (QWidget ):
def __init__(self):
super().__init__()
self.settings = QSettings(CONFIG_ORGANIZATION_NAME, CONFIG_APP_NAME_MAIN)
# --- CORRECT PATH DEFINITION ---
# This block correctly determines the application's base directory whether
# it's running from source or as a frozen executable.
self.is_finishing = False
saved_res = self.settings.value(RESOLUTION_KEY, "Auto")
if saved_res != "Auto":
try:
width, height = map(int, saved_res.split('x'))
self.resize(width, height)
self._center_on_screen()
except ValueError:
self.log_signal.emit(f"⚠️ Invalid saved resolution '{saved_res}'. Using default.")
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
# Path for PyInstaller one-file bundle
self.app_base_dir = os.path.dirname(sys.executable)
else:
# Path for running from source code
self.app_base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
# All file paths will now correctly use the single, correct app_base_dir
self.config_file = os.path.join(self.app_base_dir, "appdata", "Known.txt")
self.session_file_path = os.path.join(self.app_base_dir, "appdata", "session.json")
self.persistent_history_file = os.path.join(self.app_base_dir, "appdata", "download_history.json")
@@ -272,6 +276,28 @@ class DownloaderApp (QWidget ):
self._update_button_states_and_connections()
self._check_for_interrupted_session()
def _apply_theme_and_restart_prompt(self):
"""Applies the theme and prompts the user to restart."""
if self.current_theme == "dark":
scale = getattr(self, 'scale_factor', 1)
self.setStyleSheet(get_dark_theme(scale))
else:
self.setStyleSheet("")
# Prompt for restart
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Information)
msg_box.setWindowTitle(self._tr("theme_change_title", "Theme Changed"))
msg_box.setText(self._tr("language_change_message", "A restart is required for these changes to take effect."))
msg_box.setInformativeText(self._tr("language_change_informative", "Would you like to restart now?"))
restart_button = msg_box.addButton(self._tr("restart_now_button", "Restart Now"), QMessageBox.ApplyRole)
ok_button = msg_box.addButton(self._tr("ok_button", "OK"), QMessageBox.AcceptRole)
msg_box.setDefaultButton(ok_button)
msg_box.exec_()
if msg_box.clickedButton() == restart_button:
self._request_restart_application()
def _create_initial_session_file(self, api_url_for_session, override_output_dir_for_session): # ADD override_output_dir_for_session
"""Creates the initial session file at the start of a new download."""
if self.is_restore_pending:
@@ -2602,6 +2628,7 @@ class DownloaderApp (QWidget ):
self .file_progress_label .setText ("")
def start_download(self, direct_api_url=None, override_output_dir=None, is_restore=False):
self.is_finishing = False
global KNOWN_NAMES, BackendDownloadThread, PostProcessorWorker, extract_post_info, clean_folder_name, MAX_FILE_THREADS_PER_POST_OR_WORKER
self._clear_stale_temp_files()
@@ -3521,9 +3548,7 @@ class DownloaderApp (QWidget ):
except Exception as e:
self.log_signal.emit(f"❌ Error in _handle_worker_result: {e}\n{traceback.format_exc(limit=2)}")
# Check if all submitted tasks are complete
if not self.is_fetcher_thread_running and self.processed_posts_count >= self.total_posts_to_process:
self.log_signal.emit("🏁 All fetcher and worker tasks complete.")
self.finished_signal.emit(self.download_counter, self.skip_counter, self.cancellation_event.is_set(), self.all_kept_original_filenames)
def _trigger_single_pdf_creation(self):
@@ -3850,6 +3875,7 @@ class DownloaderApp (QWidget ):
self ._filter_links_log ()
def cancel_download_button_action (self ):
self.is_finishing = True
if not self .cancel_btn .isEnabled ()and not self .cancellation_event .is_set ():self .log_signal .emit (" No active download to cancel or already cancelling.");return
self .log_signal .emit ("⚠️ Requesting cancellation of download process (soft reset)...")
@@ -3903,6 +3929,12 @@ class DownloaderApp (QWidget ):
return "kemono.su"
def download_finished (self ,total_downloaded ,total_skipped ,cancelled_by_user ,kept_original_names_list =None ):
if self.is_finishing:
return
self.is_finishing = True
self.log_signal.emit("🏁 All fetcher and worker tasks complete.")
if kept_original_names_list is None :
kept_original_names_list =list (self .all_kept_original_filenames )if hasattr (self ,'all_kept_original_filenames')else []
if kept_original_names_list is None :
@@ -3940,7 +3972,7 @@ class DownloaderApp (QWidget ):
self.single_pdf_setting = False
# Reset session state for the next run
self.session_text_content = []
self.session_temp_files = []
self.single_pdf_setting = False
if kept_original_names_list :
@@ -4029,7 +4061,8 @@ class DownloaderApp (QWidget ):
self ._process_next_favorite_download ()
else :
self .set_ui_enabled (True )
self .cancellation_message_logged_this_session =False
self .cancellation_message_logged_this_session =False
def _handle_thumbnail_mode_change (self ,thumbnails_checked ):
"""Handles UI changes when 'Download Thumbnails Only' is toggled."""

View File

@@ -10,7 +10,7 @@ from PyQt5.QtWidgets import (
QListWidget, QTextEdit, QApplication
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIntValidator # <-- Import QIntValidator from here
from PyQt5.QtGui import QIntValidator, QFont # <-- Import QFont
# --- Local Application Imports ---
# Assuming execution from project root
@@ -24,20 +24,24 @@ def setup_ui(main_app):
Args:
main_app: The instance of the main DownloaderApp.
"""
# --- START: New Scaling Logic ---
screen = QApplication.primaryScreen()
if screen:
resolution = screen.size()
if resolution.width() > 1920 and resolution.height() > 1200:
main_app.scale_factor = 2
else:
main_app.scale_factor = 1
else:
# Fallback if a primary screen isn't detected
main_app.scale_factor = 1
scale = main_app.scale_factor # Use a convenient local variable
# --- END: New Scaling Logic ---
# --- START: Modified Scaling Logic ---
# Force a fixed scale factor to disable UI scaling on high-DPI screens.
scale = float(main_app.settings.value(UI_SCALE_KEY, 1.0))
main_app.scale_factor = scale
# --- Set the global font size for the application ---
default_font = QApplication.font()
base_font_size = 9 # Use a standard base size
default_font.setPointSize(int(base_font_size * scale))
main_app.setFont(default_font)
# --- END: Modified Scaling Logic ---
# --- Set the global font size for the application ---
default_font = QApplication.font()
base_font_size = 9 # Use a standard base size
default_font.setPointSize(int(base_font_size * scale))
main_app.setFont(default_font)
# --- END: Improved Scaling Logic ---
main_app.main_splitter = QSplitter(Qt.Horizontal)
@@ -68,7 +72,11 @@ def setup_ui(main_app):
main_app.link_input.textChanged.connect(main_app.update_custom_folder_visibility)
url_input_layout.addWidget(main_app.link_input, 1)
main_app.empty_popup_button = QPushButton("🎨")
main_app.empty_popup_button.setStyleSheet(f"padding: {4*scale}px {6*scale}px;")
special_font_size = int(9.5 * scale)
main_app.empty_popup_button.setStyleSheet(f"""
padding: {4*scale}px {6*scale}px;
font-size: {special_font_size}pt;
""")
main_app.empty_popup_button.clicked.connect(main_app._show_empty_popup)
url_input_layout.addWidget(main_app.empty_popup_button)
main_app.page_range_label = QLabel(main_app._tr("page_range_label_text", "Page Range:"))
@@ -76,14 +84,14 @@ def setup_ui(main_app):
url_input_layout.addWidget(main_app.page_range_label)
main_app.start_page_input = QLineEdit()
main_app.start_page_input.setPlaceholderText(main_app._tr("start_page_input_placeholder", "Start"))
main_app.start_page_input.setFixedWidth(50 * scale)
main_app.start_page_input.setFixedWidth(int(50 * scale))
main_app.start_page_input.setValidator(QIntValidator(1, 99999))
url_input_layout.addWidget(main_app.start_page_input)
main_app.to_label = QLabel(main_app._tr("page_range_to_label_text", "to"))
url_input_layout.addWidget(main_app.to_label)
main_app.end_page_input = QLineEdit()
main_app.end_page_input.setPlaceholderText(main_app._tr("end_page_input_placeholder", "End"))
main_app.end_page_input.setFixedWidth(50 * scale)
main_app.end_page_input.setFixedWidth(int(50 * scale))
main_app.end_page_input.setToolTip(main_app._tr("end_page_input_tooltip", "For creator URLs: Specify the ending page number..."))
main_app.end_page_input.setValidator(QIntValidator(1, 99999))
url_input_layout.addWidget(main_app.end_page_input)
@@ -256,7 +264,7 @@ def setup_ui(main_app):
advanced_row1_layout.addWidget(main_app.use_cookie_checkbox)
advanced_row1_layout.addWidget(main_app.cookie_text_input, 2)
main_app.cookie_browse_button = QPushButton("Browse...")
main_app.cookie_browse_button.setFixedWidth(80 * scale)
main_app.cookie_browse_button.setFixedWidth(int(80 * scale))
advanced_row1_layout.addWidget(main_app.cookie_browse_button)
advanced_row1_layout.addStretch(1)
checkboxes_group_layout.addLayout(advanced_row1_layout)
@@ -270,7 +278,7 @@ def setup_ui(main_app):
main_app.thread_count_label = QLabel("Threads:")
multithreading_layout.addWidget(main_app.thread_count_label)
main_app.thread_count_input = QLineEdit("4")
main_app.thread_count_input.setFixedWidth(40 * scale)
main_app.thread_count_input.setFixedWidth(int(40 * scale))
main_app.thread_count_input.setValidator(QIntValidator(1, MAX_THREADS))
multithreading_layout.addWidget(main_app.thread_count_input)
advanced_row2_layout.addLayout(multithreading_layout)
@@ -288,7 +296,9 @@ def setup_ui(main_app):
btn_layout.setContentsMargins(0, 10, 0, 0)
btn_layout.setSpacing(10)
main_app.download_btn = QPushButton("⬇️ Start Download")
main_app.download_btn.setStyleSheet("font-weight: bold;")
font = main_app.download_btn.font()
font.setBold(True)
main_app.download_btn.setFont(font)
main_app.download_btn.clicked.connect(main_app.start_download)
main_app.pause_btn = QPushButton("⏸️ Pause Download")
main_app.pause_btn.setEnabled(False)
@@ -323,7 +333,7 @@ def setup_ui(main_app):
main_app.known_chars_label = QLabel("🎭 Known Shows/Characters (for Folder Names):")
known_chars_label_layout.addWidget(main_app.known_chars_label)
main_app.open_known_txt_button = QPushButton("Open Known.txt")
main_app.open_known_txt_button.setFixedWidth(120 * scale)
main_app.open_known_txt_button.setFixedWidth(int(120 * scale))
known_chars_label_layout.addWidget(main_app.open_known_txt_button)
main_app.character_search_input = QLineEdit()
main_app.character_search_input.setPlaceholderText("Search characters...")
@@ -347,23 +357,23 @@ def setup_ui(main_app):
char_manage_layout.addWidget(main_app.new_char_input, 2)
char_manage_layout.addWidget(main_app.add_char_button, 0)
main_app.known_names_help_button = QPushButton("?")
main_app.known_names_help_button.setFixedWidth(45 * scale)
main_app.known_names_help_button.setFixedWidth(int(45 * scale))
main_app.known_names_help_button.clicked.connect(main_app._show_feature_guide)
main_app.history_button = QPushButton("📜")
main_app.history_button.setFixedWidth(45 * scale)
main_app.history_button.setFixedWidth(int(45 * scale))
main_app.history_button.setToolTip(main_app._tr("history_button_tooltip_text", "View download history"))
main_app.future_settings_button = QPushButton("⚙️")
main_app.future_settings_button.setFixedWidth(45 * scale)
main_app.future_settings_button.setFixedWidth(int(45 * scale))
main_app.future_settings_button.clicked.connect(main_app._show_future_settings_dialog)
main_app.support_button = QPushButton("❤️ Support")
main_app.support_button.setFixedWidth(100 * scale)
main_app.support_button.setFixedWidth(int(100 * scale))
main_app.support_button.setToolTip("Support the application developer.")
char_manage_layout.addWidget(main_app.add_to_filter_button, 1)
char_manage_layout.addWidget(main_app.delete_char_button, 1)
char_manage_layout.addWidget(main_app.known_names_help_button, 0)
char_manage_layout.addWidget(main_app.history_button, 0)
char_manage_layout.addWidget(main_app.future_settings_button, 0)
char_manage_layout.addWidget(main_app.support_button, 0)
char_manage_layout.addWidget(main_app.support_button, 0)
left_layout.addLayout(char_manage_layout)
left_layout.addStretch(0)
@@ -379,11 +389,11 @@ def setup_ui(main_app):
log_title_layout.addWidget(main_app.link_search_input)
main_app.link_search_button = QPushButton("🔍")
main_app.link_search_button.setVisible(False)
main_app.link_search_button.setFixedWidth(30 * scale)
main_app.link_search_button.setFixedWidth(int(30 * scale))
log_title_layout.addWidget(main_app.link_search_button)
main_app.manga_rename_toggle_button = QPushButton()
main_app.manga_rename_toggle_button.setVisible(False)
main_app.manga_rename_toggle_button.setFixedWidth(140 * scale)
main_app.manga_rename_toggle_button.setFixedWidth(int(140 * scale))
main_app._update_manga_filename_style_button_text()
log_title_layout.addWidget(main_app.manga_rename_toggle_button)
main_app.manga_date_prefix_input = QLineEdit()
@@ -392,17 +402,17 @@ def setup_ui(main_app):
log_title_layout.addWidget(main_app.manga_date_prefix_input)
main_app.multipart_toggle_button = QPushButton()
main_app.multipart_toggle_button.setToolTip("Toggle between Multi-part and Single-stream downloads for large files.")
main_app.multipart_toggle_button.setFixedWidth(130 * scale)
main_app.multipart_toggle_button.setFixedWidth(int(130 * scale))
main_app._update_multipart_toggle_button_text()
log_title_layout.addWidget(main_app.multipart_toggle_button)
main_app.EYE_ICON = "\U0001F441"
main_app.CLOSED_EYE_ICON = "\U0001F648"
main_app.log_verbosity_toggle_button = QPushButton(main_app.EYE_ICON)
main_app.log_verbosity_toggle_button.setFixedWidth(45 * scale)
main_app.log_verbosity_toggle_button.setFixedWidth(int(45 * scale))
main_app.log_verbosity_toggle_button.setStyleSheet(f"font-size: {11 * scale}pt; padding: {4 * scale}px {2 * scale}px;")
log_title_layout.addWidget(main_app.log_verbosity_toggle_button)
main_app.reset_button = QPushButton("🔄 Reset")
main_app.reset_button.setFixedWidth(80 * scale)
main_app.reset_button.setFixedWidth(int(80 * scale))
log_title_layout.addWidget(main_app.reset_button)
right_layout.addLayout(log_title_layout)
main_app.log_splitter = QSplitter(Qt.Vertical)
@@ -426,17 +436,17 @@ def setup_ui(main_app):
export_button_layout = QHBoxLayout()
export_button_layout.addStretch(1)
main_app.export_links_button = QPushButton(main_app._tr("export_links_button_text", "Export Links"))
main_app.export_links_button.setFixedWidth(100 * scale)
main_app.export_links_button.setFixedWidth(int(100 * scale))
main_app.export_links_button.setEnabled(False)
main_app.export_links_button.setVisible(False)
export_button_layout.addWidget(main_app.export_links_button)
main_app.download_extracted_links_button = QPushButton(main_app._tr("download_extracted_links_button_text", "Download"))
main_app.download_extracted_links_button.setFixedWidth(100 * scale)
main_app.download_extracted_links_button.setFixedWidth(int(100 * scale))
main_app.download_extracted_links_button.setEnabled(False)
main_app.download_extracted_links_button.setVisible(False)
export_button_layout.addWidget(main_app.download_extracted_links_button)
main_app.log_display_mode_toggle_button = QPushButton()
main_app.log_display_mode_toggle_button.setFixedWidth(120 * scale)
main_app.log_display_mode_toggle_button.setFixedWidth(int(120 * scale))
main_app.log_display_mode_toggle_button.setVisible(False)
export_button_layout.addWidget(main_app.log_display_mode_toggle_button)
right_layout.addLayout(export_button_layout)
@@ -453,23 +463,15 @@ def setup_ui(main_app):
main_app.main_splitter.addWidget(left_scroll_area)
main_app.main_splitter.addWidget(right_panel_widget)
# --- START: Resolution-based Splitter Sizing ---
# Check screen resolution to set the initial splitter sizes
if screen:
resolution = screen.size()
if resolution.width() >= 1920 and resolution.height() >= 1200:
# For 1920x1200 and higher, set 40% left, 60% right
main_app.main_splitter.setStretchFactor(0, 4)
main_app.main_splitter.setStretchFactor(1, 6)
else:
# Default for lower resolutions
main_app.main_splitter.setStretchFactor(0, 7)
main_app.main_splitter.setStretchFactor(1, 3)
if main_app.width() >= 1920:
# For wider resolutions, give more space to the log panel (right).
main_app.main_splitter.setStretchFactor(0, 4)
main_app.main_splitter.setStretchFactor(1, 6)
else:
# Fallback if no screen is detected
# Default for lower resolutions, giving more space to controls (left).
main_app.main_splitter.setStretchFactor(0, 7)
main_app.main_splitter.setStretchFactor(1, 3)
# --- END: Resolution-based Splitter Sizing ---
top_level_layout = QHBoxLayout(main_app)
top_level_layout.setContentsMargins(0, 0, 0, 0)
@@ -505,45 +507,62 @@ def get_dark_theme(scale=1):
"""
Generates the stylesheet for the dark theme, scaled by the given factor.
"""
# Define base sizes
font_size_base = 10
font_size_small_base = 9.5
padding_base = 5
padding_small = 4
button_h_padding_base = 12
indicator_size_base = 14
# Adjust base font size for better readability
font_size_base = 9.5
font_size_small_base = 8.5
# Apply scaling
font_size = font_size_base * scale
font_size_small = font_size_small_base * scale
line_edit_padding = padding_base * scale
button_padding_v = padding_base * scale
button_padding_h = button_h_padding_base * scale
tooltip_padding = padding_small * scale
font_size = int(font_size_base * scale)
font_size_small = int(font_size_small_base * scale)
line_edit_padding = int(5 * scale)
button_padding_v = int(5 * scale)
button_padding_h = int(12 * scale)
tooltip_padding = int(4 * scale)
indicator_size = int(14 * scale)
return f"""
QWidget {{ background-color: #2E2E2E; color: #E0E0E0; font-family: Segoe UI, Arial, sans-serif; font-size: {font_size}pt; }}
QLineEdit, QListWidget {{ background-color: #3C3F41; border: 1px solid #5A5A5A; padding: {line_edit_padding}px; color: #F0F0F0; border-radius: 4px; }}
QTextEdit {{ background-color: #3C3F41; border: 1px solid #5A5A5A; padding: {line_edit_padding}px;
color: #F0F0F0; border-radius: 4px;
font-family: Consolas, Courier New, monospace; font-size: {font_size_small}pt; }}
QPushButton {{ background-color: #555; color: #F0F0F0; border: 1px solid #6A6A6A; padding: {button_padding_v}px {button_padding_h}px; border-radius: 4px; }}
QWidget {{
background-color: #2E2E2E;
color: #E0E0E0;
font-family: Segoe UI, Arial, sans-serif;
font-size: {font_size}pt;
}}
QLineEdit, QListWidget, QTextEdit {{
background-color: #3C3F41;
border: 1px solid #5A5A5A;
padding: {line_edit_padding}px;
color: #F0F0F0;
border-radius: 4px;
font-size: {font_size}pt;
}}
QTextEdit {{
font-family: Consolas, Courier New, monospace;
}}
QPushButton {{
background-color: #555;
color: #F0F0F0;
border: 1px solid #6A6A6A;
padding: {button_padding_v}px {button_padding_h}px;
border-radius: 4px;
}}
QPushButton:hover {{ background-color: #656565; border: 1px solid #7A7A7A; }}
QPushButton:pressed {{ background-color: #4A4A4A; }}
QPushButton:disabled {{ background-color: #404040; color: #888; border-color: #555; }}
QLabel {{ font-weight: bold; padding-top: {4 * scale}px; padding-bottom: {2 * scale}px; color: #C0C0C0; }}
QRadioButton, QCheckBox {{ spacing: {5 * scale}px; color: #E0E0E0; padding-top: {4 * scale}px; padding-bottom: {4 * scale}px; }}
QRadioButton::indicator, QCheckBox::indicator {{ width: {indicator_size_base * scale}px; height: {indicator_size_base * scale}px; }}
QListWidget {{ alternate-background-color: #353535; border: 1px solid #5A5A5A; }}
QLabel {{ font-weight: bold; color: #C0C0C0; }}
QRadioButton, QCheckBox {{ spacing: {int(5 * scale)}px; color: #E0E0E0; }}
QRadioButton::indicator, QCheckBox::indicator {{ width: {indicator_size}px; height: {indicator_size}px; }}
QListWidget {{ alternate-background-color: #353535; }}
QListWidget::item:selected {{ background-color: #007ACC; color: #FFFFFF; }}
QToolTip {{ background-color: #4A4A4A; color: #F0F0F0; border: 1px solid #6A6A6A; padding: {tooltip_padding}px; border-radius: 3px; }}
QSplitter::handle {{ background-color: #5A5A5A; }}
QSplitter::handle:horizontal {{ width: {5 * scale}px; }}
QSplitter::handle:vertical {{ height: {5 * scale}px; }}
QFrame[frameShape="4"], QFrame[frameShape="5"] {{
border: 1px solid #4A4A4A;
border-radius: 3px;
QToolTip {{
background-color: #4A4A4A;
color: #F0F0F0;
border: 1px solid #6A6A6A;
padding: {tooltip_padding}px;
border-radius: 3px;
}}
QSplitter::handle {{ background-color: #5A5A5A; }}
QSplitter::handle:horizontal {{ width: {int(5 * scale)}px; }}
QSplitter::handle:vertical {{ height: {int(5 * scale)}px; }}
"""
def apply_theme_to_app(main_app, theme_name, initial_load=False):
"""

View File

@@ -607,7 +607,6 @@ class PostProcessorWorker:
self.logger(f" ⚠️ Failed to rescue file despite matching size. Error: {rescue_exc}")
if self.check_cancel() or (skip_event and skip_event.is_set()) or (self.pause_event and self.pause_event.is_set() and not download_successful_flag):
self.logger(f" ⚠️ Download process interrupted for {api_original_filename}.")
if downloaded_part_file_path and os.path.exists(downloaded_part_file_path):
try: os.remove(downloaded_part_file_path)
except OSError: pass
@@ -1729,8 +1728,8 @@ class PostProcessorWorker:
'download_location':determined_post_save_path_for_history ,
'service':self .service ,'user_id':self .user_id ,
}
if self .check_cancel ():self .logger (f" Post {post_id } processing interrupted/cancelled.");
else :self .logger (f" Post {post_id } Summary: Downloaded={total_downloaded_this_post }, Skipped Files={total_skipped_this_post }")
if not self.check_cancel():
self.logger(f" Post {post_id} Summary: Downloaded={total_downloaded_this_post}, Skipped Files={total_skipped_this_post}")
if not self .extract_links_only and self .use_post_subfolders and total_downloaded_this_post ==0 :