import sys import traceback # Added for enhanced error reporting from PyQt5.QtWidgets import ( QApplication, QDialog, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QStackedWidget, QSpacerItem, QSizePolicy, QCheckBox, QDesktopWidget ) from PyQt5.QtCore import Qt, QSettings, pyqtSignal class TourStepWidget(QWidget): """A single step/page in the tour.""" def __init__(self, title_text, content_text, parent=None): super().__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(20, 20, 20, 20) layout.setSpacing(10) # Adjusted spacing between title and content for bullet points title_label = QLabel(title_text) title_label.setAlignment(Qt.AlignCenter) # Increased padding-bottom for more space below title title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #E0E0E0; padding-bottom: 15px;") content_label = QLabel(content_text) content_label.setWordWrap(True) content_label.setAlignment(Qt.AlignLeft) content_label.setTextFormat(Qt.RichText) # Adjusted line-height for bullet point readability content_label.setStyleSheet("font-size: 11pt; color: #C8C8C8; line-height: 1.8;") layout.addWidget(title_label) layout.addWidget(content_label) layout.addStretch(1) class TourDialog(QDialog): """ A dialog that shows a multi-page tour to the user. Includes a "Never show again" checkbox. Uses QSettings to remember this preference. """ tour_finished_normally = pyqtSignal() tour_skipped = pyqtSignal() CONFIG_ORGANIZATION_NAME = "KemonoDownloader" CONFIG_APP_NAME_TOUR = "ApplicationTour" TOUR_SHOWN_KEY = "neverShowTourAgainV3" # Updated key for new tour content def __init__(self, parent=None): super().__init__(parent) self.settings = QSettings(self.CONFIG_ORGANIZATION_NAME, self.CONFIG_APP_NAME_TOUR) self.current_step = 0 self.setWindowTitle("Welcome to Kemono Downloader!") self.setModal(True) # Set fixed square size, smaller than main window self.setFixedSize(600, 620) # Slightly adjusted for potentially more text self.setStyleSheet(""" QDialog { background-color: #2E2E2E; border: 1px solid #5A5A5A; } QLabel { color: #E0E0E0; } QCheckBox { color: #C0C0C0; font-size: 10pt; spacing: 5px; } QCheckBox::indicator { width: 13px; height: 13px; } 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() self._center_on_screen() def _center_on_screen(self): """Centers the dialog on the screen.""" try: screen_geometry = QDesktopWidget().screenGeometry() dialog_geometry = self.frameGeometry() center_point = screen_geometry.center() dialog_geometry.moveCenter(center_point) self.move(dialog_geometry.topLeft()) except Exception as e: print(f"[Tour] Error centering dialog: {e}") 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) # --- Define Tour Steps with Updated Content --- step1_content = ( "Hello! This quick tour will walk you through the main features of the Kemono Downloader." "" ) self.step1 = TourStepWidget("👋 Welcome!", step1_content) step2_content = ( "Let's start with the basics for downloading:" "" ) self.step2 = TourStepWidget("① Getting Started", step2_content) step3_content = ( "Refine what you download with these filters:" "" ) self.step3 = TourStepWidget("② Filtering Downloads", step3_content) step4_content = ( "More options to customize your downloads:" "" ) self.step4 = TourStepWidget("③ Fine-Tuning Downloads", step4_content) step5_content = ( "Organize your downloads and manage performance:" "" ) self.step5 = TourStepWidget("④ Organization & Performance", step5_content) step6_content = ( "Monitoring and Controls:" "" "
You're all set! Click 'Finish' to close the tour and start using the downloader." ) self.step6 = TourStepWidget("⑤ Logs & Final Controls", step6_content) self.tour_steps = [self.step1, self.step2, self.step3, self.step4, self.step5, self.step6] for step_widget in self.tour_steps: self.stacked_widget.addWidget(step_widget) bottom_controls_layout = QVBoxLayout() bottom_controls_layout.setContentsMargins(15, 10, 15, 15) # Adjusted margins bottom_controls_layout.setSpacing(10) self.never_show_again_checkbox = QCheckBox("Never show this tour again") bottom_controls_layout.addWidget(self.never_show_again_checkbox, 0, Qt.AlignLeft) buttons_layout = QHBoxLayout() buttons_layout.setSpacing(10) self.skip_button = QPushButton("Skip Tour") self.skip_button.clicked.connect(self._skip_tour_action) 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.addWidget(self.skip_button) buttons_layout.addStretch(1) buttons_layout.addWidget(self.back_button) buttons_layout.addWidget(self.next_button) bottom_controls_layout.addLayout(buttons_layout) main_layout.addLayout(bottom_controls_layout) self._update_button_states() def _handle_exit_actions(self): if self.never_show_again_checkbox.isChecked(): self.settings.setValue(self.TOUR_SHOWN_KEY, True) self.settings.sync() # else: # print(f"[Tour] '{self.TOUR_SHOWN_KEY}' setting not set to True (checkbox was unchecked on exit).") def _next_step_action(self): if self.current_step < len(self.tour_steps) - 1: self.current_step += 1 self.stacked_widget.setCurrentIndex(self.current_step) else: self._handle_exit_actions() self.tour_finished_normally.emit() self.accept() 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 _skip_tour_action(self): self._handle_exit_actions() self.tour_skipped.emit() self.reject() def _update_button_states(self): if self.current_step == len(self.tour_steps) - 1: self.next_button.setText("Finish") else: self.next_button.setText("Next") self.back_button.setEnabled(self.current_step > 0) @staticmethod def run_tour_if_needed(parent_app_window): try: settings = QSettings(TourDialog.CONFIG_ORGANIZATION_NAME, TourDialog.CONFIG_APP_NAME_TOUR) never_show_again = settings.value(TourDialog.TOUR_SHOWN_KEY, False, type=bool) if never_show_again: return QDialog.Rejected tour_dialog = TourDialog(parent_app_window) result = tour_dialog.exec_() return result except Exception as e: print(f"[Tour] CRITICAL ERROR in run_tour_if_needed: {e}") traceback.print_exc() return QDialog.Rejected if __name__ == '__main__': app = QApplication(sys.argv) # --- For testing: force the tour to show by resetting the flag --- # print("[Tour Test] Resetting 'Never show again' flag for testing purposes.") # test_settings = QSettings(TourDialog.CONFIG_ORGANIZATION_NAME, TourDialog.CONFIG_APP_NAME_TOUR) # test_settings.setValue(TourDialog.TOUR_SHOWN_KEY, False) # Set to False to force tour # test_settings.sync() # --- End testing block --- print("[Tour Test] Running tour standalone...") result = TourDialog.run_tour_if_needed(None) if result == QDialog.Accepted: print("[Tour Test] Tour dialog was accepted (Finished).") elif result == QDialog.Rejected: print("[Tour Test] Tour dialog was rejected (Skipped or previously set to 'Never show again').") final_settings = QSettings(TourDialog.CONFIG_ORGANIZATION_NAME, TourDialog.CONFIG_APP_NAME_TOUR) print(f"[Tour Test] Final state of '{TourDialog.TOUR_SHOWN_KEY}' in settings: {final_settings.value(TourDialog.TOUR_SHOWN_KEY, False, type=bool)}") sys.exit()