diff --git a/src/config/constants.py b/src/config/constants.py index 14bf2bf..7f2a662 100644 --- a/src/config/constants.py +++ b/src/config/constants.py @@ -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 = "" diff --git a/src/ui/dialogs/EmptyPopupDialog.py b/src/ui/dialogs/EmptyPopupDialog.py index ae8d5f4..8cc84df 100644 --- a/src/ui/dialogs/EmptyPopupDialog.py +++ b/src/ui/dialogs/EmptyPopupDialog.py @@ -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 diff --git a/src/ui/dialogs/FutureSettingsDialog.py b/src/ui/dialogs/FutureSettingsDialog.py index 23abc69..5102b8e 100644 --- a/src/ui/dialogs/FutureSettingsDialog.py +++ b/src/ui/dialogs/FutureSettingsDialog.py @@ -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.") \ No newline at end of file diff --git a/src/ui/main_window.py b/src/ui/main_window.py index 91768b8..9f47880 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -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.""" diff --git a/src/utils/resolution.py b/src/utils/resolution.py index 1c07622..efc78d8 100644 --- a/src/utils/resolution.py +++ b/src/utils/resolution.py @@ -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): """ diff --git a/workers.py b/workers.py index 89bfb31..d541cb3 100644 --- a/workers.py +++ b/workers.py @@ -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 :