diff --git a/Kemono.png b/Kemono.png new file mode 100644 index 0000000..ee1e3b1 Binary files /dev/null and b/Kemono.png differ diff --git a/main.py b/main.py index 46bf0df..1e7b8f6 100644 --- a/main.py +++ b/main.py @@ -214,9 +214,86 @@ class ConfirmAddAllDialog(QDialog): super().exec_() # If user accepted but selected nothing, treat it as skipping addition if isinstance(self.user_choice, list) and not self.user_choice: - QMessageBox.information(self, "No Selection", "No names were selected to be added. Skipping addition.") + # QMessageBox.information(self, "No Selection", "No names were selected to be added. Skipping addition.") return CONFIRM_ADD_ALL_SKIP_ADDING return self.user_choice + +class HelpGuideDialog(QDialog): + """A multi-page dialog for displaying the feature guide.""" + def __init__(self, steps_data, parent=None): + super().__init__(parent) + self.current_step = 0 + self.steps_data = steps_data # List of (title, content_html) tuples + + self.setWindowTitle("Kemono Downloader - Feature Guide") + self.setModal(True) + self.setFixedSize(650, 600) # Adjusted size for guide content + + # Apply similar styling to TourDialog, or a distinct one if preferred + self.setStyleSheet(parent.get_dark_theme() if hasattr(parent, 'get_dark_theme') else """ + QDialog { background-color: #2E2E2E; border: 1px solid #5A5A5A; } + QLabel { color: #E0E0E0; } + QPushButton { background-color: #555; color: #F0F0F0; border: 1px solid #6A6A6A; padding: 8px 15px; border-radius: 4px; min-height: 25px; font-size: 11pt; } + QPushButton:hover { background-color: #656565; } + QPushButton:pressed { background-color: #4A4A4A; } + """) + self._init_ui() + if parent: # Attempt to center on parent + self.move(parent.geometry().center() - self.rect().center()) + + def _init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + self.stacked_widget = QStackedWidget() + main_layout.addWidget(self.stacked_widget, 1) + + self.tour_steps_widgets = [] # To hold TourStepWidget instances + for title, content in self.steps_data: + step_widget = TourStepWidget(title, content) # Reuse TourStepWidget + self.tour_steps_widgets.append(step_widget) + self.stacked_widget.addWidget(step_widget) + + buttons_layout = QHBoxLayout() + buttons_layout.setContentsMargins(15, 10, 15, 15) + buttons_layout.setSpacing(10) + + self.back_button = QPushButton("Back") + self.back_button.clicked.connect(self._previous_step) + self.back_button.setEnabled(False) + + self.next_button = QPushButton("Next") + self.next_button.clicked.connect(self._next_step_action) + self.next_button.setDefault(True) + + buttons_layout.addStretch(1) + buttons_layout.addWidget(self.back_button) + buttons_layout.addWidget(self.next_button) + main_layout.addLayout(buttons_layout) + self._update_button_states() + + def _next_step_action(self): + if self.current_step < len(self.tour_steps_widgets) - 1: + self.current_step += 1 + self.stacked_widget.setCurrentIndex(self.current_step) + else: # Last page + self.accept() # Close dialog + self._update_button_states() + + def _previous_step(self): + if self.current_step > 0: + self.current_step -= 1 + self.stacked_widget.setCurrentIndex(self.current_step) + self._update_button_states() + + def _update_button_states(self): + if self.current_step == len(self.tour_steps_widgets) - 1: + self.next_button.setText("Finish") + else: + self.next_button.setText("Next") + self.back_button.setEnabled(self.current_step > 0) + class TourStepWidget(QWidget): """A single step/page in the tour.""" def __init__(self, title_text, content_text, parent=None): @@ -1369,8 +1446,18 @@ class DownloaderApp(QWidget): self.new_char_input.returnPressed.connect(self.add_char_button.click) self.delete_char_button.clicked.connect(self.delete_selected_character) char_manage_layout.addWidget(self.new_char_input, 2) - char_manage_layout.addWidget(self.add_char_button, 1) - char_manage_layout.addWidget(self.delete_char_button, 1) + char_manage_layout.addWidget(self.add_char_button, 0) + + # Help button for Known Names list + self.known_names_help_button = QPushButton("?") # Restored question mark + self.known_names_help_button.setFixedWidth(35) # Small width for a square-like button + # self.known_names_help_button.setStyleSheet("font-weight: bold; padding-left: 8px; padding-right: 8px;") # Removed stylesheet + self.known_names_help_button.setToolTip("Open the application feature guide.") + self.known_names_help_button.clicked.connect(self._show_feature_guide) + + + char_manage_layout.addWidget(self.delete_char_button, 0) + char_manage_layout.addWidget(self.known_names_help_button, 0) # Moved to the end (rightmost) left_layout.addLayout(char_manage_layout) left_layout.addStretch(0) @@ -1378,7 +1465,7 @@ class DownloaderApp(QWidget): self.progress_log_label = QLabel("πŸ“œ Progress Log:") log_title_layout.addWidget(self.progress_log_label) log_title_layout.addStretch(1) - + self.link_search_input = QLineEdit() self.link_search_input.setToolTip("When in 'Only Links' mode, type here to filter the displayed links by text, URL, or platform.") self.link_search_input.setPlaceholderText("Search Links...") @@ -1897,12 +1984,19 @@ class DownloaderApp(QWidget): self.update_external_links_setting(self.external_links_checkbox.isChecked() if self.external_links_checkbox else False) self.log_signal.emit(f"="*20 + f" Mode changed to: {filter_mode_text} " + "="*20) - subfolders_on = self.use_subfolders_checkbox.isChecked() if self.use_subfolders_checkbox else False - + subfolders_on = self.use_subfolders_checkbox.isChecked() if self.use_subfolders_checkbox else False manga_on = self.manga_mode_checkbox.isChecked() if self.manga_mode_checkbox else False - - enable_character_filter_related_widgets = file_download_mode_active and (subfolders_on or manga_on) + # Determine if character filter section should be active (visible and enabled) + # It should be active if we are in a file downloading mode (not 'Only Links' or 'Only Archives') + character_filter_should_be_active = not is_only_links and not is_only_archives + + if self.character_filter_widget: + self.character_filter_widget.setVisible(character_filter_should_be_active) + + # Enable/disable character input and its scope button based on whether character filtering is active + enable_character_filter_related_widgets = character_filter_should_be_active + if self.character_input: self.character_input.setEnabled(enable_character_filter_related_widgets) if not enable_character_filter_related_widgets: @@ -1911,7 +2005,9 @@ class DownloaderApp(QWidget): if self.char_filter_scope_toggle_button: self.char_filter_scope_toggle_button.setEnabled(enable_character_filter_related_widgets) - self.update_ui_for_subfolders(subfolders_on) + # Call update_ui_for_subfolders to correctly set the "Subfolder per Post" checkbox state + # and "Custom Folder Name" visibility, which DO depend on the "Separate Folders" checkbox. + self.update_ui_for_subfolders(subfolders_on) # Pass the current state of the main subfolder checkbox self.update_custom_folder_visibility() self.update_ui_for_manga_mode(self.manga_mode_checkbox.isChecked() if self.manga_mode_checkbox else False) @@ -2307,23 +2403,13 @@ class DownloaderApp(QWidget): is_only_archives = self.radio_only_archives and self.radio_only_archives.isChecked() if self.use_subfolder_per_post_checkbox: - self.use_subfolder_per_post_checkbox.setEnabled(not is_only_links and not is_only_archives) + can_enable_subfolder_per_post = checked and not is_only_links and not is_only_archives + self.use_subfolder_per_post_checkbox.setEnabled(can_enable_subfolder_per_post) + if not can_enable_subfolder_per_post: # If it's disabled, also uncheck it + self.use_subfolder_per_post_checkbox.setChecked(False) - if hasattr(self, 'use_cookie_checkbox'): - self.use_cookie_checkbox.setEnabled(not is_only_links) # Cookies might be relevant for archives - - - enable_character_filter_related_widgets = checked and not is_only_links and not is_only_archives - - - if self.character_filter_widget: - self.character_filter_widget.setVisible(enable_character_filter_related_widgets) - if not self.character_filter_widget.isVisible(): - if self.character_input: self.character_input.clear() - if self.char_filter_scope_toggle_button: self.char_filter_scope_toggle_button.setEnabled(False) - else: - if self.char_filter_scope_toggle_button: self.char_filter_scope_toggle_button.setEnabled(True) - + # Visibility and enabled state of character filter widgets are now primarily handled + # by _handle_filter_mode_change to decouple from the subfolder checkbox. self.update_custom_folder_visibility() @@ -2468,14 +2554,16 @@ class DownloaderApp(QWidget): self.update_page_range_enabled_state() file_download_mode_active = not (self.radio_only_links and self.radio_only_links.isChecked()) - subfolders_on = self.use_subfolders_checkbox.isChecked() if self.use_subfolders_checkbox else False - enable_char_filter_widgets = file_download_mode_active and (subfolders_on or manga_mode_effectively_on) + # Character filter widgets should be enabled if it's a file download mode + enable_char_filter_widgets = file_download_mode_active and not (self.radio_only_archives and self.radio_only_archives.isChecked()) if self.character_input: self.character_input.setEnabled(enable_char_filter_widgets) if not enable_char_filter_widgets: self.character_input.clear() if self.char_filter_scope_toggle_button: self.char_filter_scope_toggle_button.setEnabled(enable_char_filter_widgets) + if self.character_filter_widget: # Also ensure the main widget visibility is correct + self.character_filter_widget.setVisible(enable_char_filter_widgets) self._update_multithreading_for_date_mode() # Update multithreading state based on manga mode @@ -3776,6 +3864,261 @@ class DownloaderApp(QWidget): self._update_manga_filename_style_button_text() self.update_ui_for_manga_mode(False) + def _show_feature_guide(self): + # Define content for each page + page1_title = "β‘  Introduction & Main Inputs" + page1_content = """ +

This guide provides an overview of the Kemono Downloader's features, fields, and buttons.

+ +

Main Input Area (Top Left)

+ """ + + page2_title = "β‘‘ Filtering Downloads" + page2_content = """ +

Filtering Downloads (Left Panel)

+ """ + + page3_title = "β‘’ Download Options & Settings" + page3_content = """ +

Download Options & Settings (Left Panel)

+ """ + + page4_title = "β‘£ Advanced Settings (Part 1)" + page4_content = """

βš™οΈ Advanced Settings (Continued)

""" + + page5_title = "β‘€ Advanced Settings (Part 2) & Actions" + page5_content = """

βš™οΈ Advanced Settings (Continued)

+ +

Main Action Buttons (Left Panel)

+ """ + + page6_title = "β‘₯ Known Shows/Characters List" + page6_content = """ +

Known Shows/Characters List Management (Bottom Left)

+

This section helps manage the Known.txt file, which is used for smart folder organization when 'Separate Folders by Name/Title' is enabled, especially as a fallback if a post doesn't match your active 'Filter by Character(s)' input.

+ """ + + page7_title = "⑦ Log Area & Controls" + page7_content = """ +

Log Area & Controls (Right Panel)

+ """ + + page8_title = "β‘§ Key Files & Tour" + page8_content = """ +

Key Files Used by the Application

+ + +

First-Time User Tour

+ +

Many UI elements also have tooltips that appear when you hover your mouse over them, providing quick hints.

+ + """ + + steps = [ + (page1_title, page1_content), + (page2_title, page2_content), + (page3_title, page3_content), + (page4_title, page4_content), + (page5_title, page5_content), + (page6_title, page6_content), + (page7_title, page7_content), + (page8_title, page8_content), + ] + guide_dialog = HelpGuideDialog(steps, self) + guide_dialog.exec_() + def prompt_add_character(self, character_name): global KNOWN_NAMES reply = QMessageBox.question(self, "Add Filter Name to Known List?", f"The name '{character_name}' was encountered or used as a filter.\nIt's not in your known names list (used for folder suggestions).\nAdd it now?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)