# tabs/settings_tab.py import os from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QGroupBox, QFormLayout, QMessageBox, QFrame ) from PyQt6.QtCore import pyqtSignal, pyqtSlot import database class SettingsTab(QWidget): # Signals to communicate up to the centralized main.py loop coordinator settings_changed = pyqtSignal(str, str) # Emits: (key, value) export_anki_requested = pyqtSignal(str) # Emits: (full_deck_name) generate_video_requested = pyqtSignal() # Emits: trigger def __init__(self, parent=None): super().__init__(parent) main_layout = QVBoxLayout(self) main_layout.setSpacing(15) # --- SECTION 0: DATABASE FILE CONFIGURATION --- db_group = QGroupBox("Database Storage Configuration") db_form = QFormLayout(db_group) db_form.setSpacing(10) db_picker_layout = QHBoxLayout() self.txt_db_path = QLineEdit() self.txt_db_path.setReadOnly(True) self.txt_db_path.setStyleSheet("background-color: #F8F9F9; color: #34495E;") btn_browse_db = QPushButton("Browse...") btn_browse_db.clicked.connect(self.browse_database_file) btn_reset_db = QPushButton("Reset Default") btn_reset_db.clicked.connect(self.reset_default_database) db_picker_layout.addWidget(self.txt_db_path) db_picker_layout.addWidget(btn_browse_db) db_picker_layout.addWidget(btn_reset_db) db_form.addRow("Active Database File:", db_picker_layout) main_layout.addWidget(db_group) # --- SECTION 1: GLOBAL ANKI PACKAGING CONFIGURATIONS --- anki_group = QGroupBox("Anki Compilation Settings") anki_form = QFormLayout(anki_group) anki_form.setSpacing(10) self.txt_root_deck = QLineEdit() self.txt_root_deck.setPlaceholderText("e.g., Spanish::CAE_Course") self.txt_root_deck.textChanged.connect(lambda text: self.update_setting("anki_root_deck_name", text.strip())) self.txt_sub_deck = QLineEdit() self.txt_sub_deck.setPlaceholderText("e.g., Vocabulary::Unit_1") self.txt_sub_deck.textChanged.connect(lambda text: self.update_setting("anki_sub_deck_name", text.strip())) # Export Destination Directory Picker dir_picker_layout = QHBoxLayout() self.txt_export_dir = QLineEdit() self.txt_export_dir.setReadOnly(True) self.txt_export_dir.setStyleSheet("background-color: #F8F9F9; color: #34495E;") btn_browse = QPushButton("Browse...") btn_browse.clicked.connect(self.browse_export_directory) dir_picker_layout.addWidget(self.txt_export_dir) dir_picker_layout.addWidget(btn_browse) anki_form.addRow("Root Deck Name:", self.txt_root_deck) anki_form.addRow("Sub-Deck Namespace Hierarchy:", self.txt_sub_deck) anki_form.addRow("Export Target Directory:", dir_picker_layout) main_layout.addWidget(anki_group) # --- SECTION 2: AUDIO ENGINE & COMPILATION OVERRIDES --- engine_group = QGroupBox("Voice Synthesis & Training Configuration") engine_form = QFormLayout(engine_group) self.txt_tts_voice = QLineEdit() self.txt_tts_voice.setPlaceholderText("Apple_Monica") self.txt_tts_voice.textChanged.connect(lambda text: self.update_setting("tts_preferred_voice", text.strip())) self.txt_tts_speed = QLineEdit() self.txt_tts_speed.setPlaceholderText("0.75") self.txt_tts_speed.textChanged.connect(lambda text: self.update_setting("tts_playback_speed", text.strip())) engine_form.addRow("Fallback System Voice Name:", self.txt_tts_voice) engine_form.addRow("Target Speech Playback Multiplier:", self.txt_tts_speed) main_layout.addWidget(engine_group) # Decorative divider line divider = QFrame() divider.setFrameShape(QFrame.Shape.HLine) divider.setFrameShadow(QFrame.Shadow.Sunken) main_layout.addWidget(divider) # --- SECTION 3: SYSTEM ACTION EXECUTION BAR --- actions_group = QGroupBox("Execution Pipelines") actions_layout = QHBoxLayout(actions_group) actions_layout.setSpacing(20) self.btn_export_anki = QPushButton("🚀 Compile Lightweight Anki APKG") self.btn_export_anki.setStyleSheet(""" QPushButton { background-color: #2980B9; color: white; font-weight: bold; font-size: 13px; padding: 10px; border-radius: 4px; } QPushButton:hover { background-color: #3498DB; } """) self.btn_export_anki.clicked.connect(self.dispatch_anki_export) self.btn_gen_video = QPushButton("🎬 Generate MP4 Loop Playlists") self.btn_gen_video.setStyleSheet(""" QPushButton { background-color: #8E44AD; color: white; font-weight: bold; font-size: 13px; padding: 10px; border-radius: 4px; } QPushButton:hover { background-color: #9B59B6; } """) self.btn_gen_video.clicked.connect(self.generate_video_requested.emit) actions_layout.addWidget(self.btn_export_anki) actions_layout.addWidget(self.btn_gen_video) main_layout.addWidget(actions_group) main_layout.addStretch() # Push everything neatly to the top # Load settings from database onto inputs on initialization self.populate_fields_from_db_state() def populate_fields_from_db_state(self): """Fetches stored parameters on view load initialization.""" # Block signals briefly so loading state doesn't trigger write-back loops self.blockSignals(True) # Populate active database path self.txt_db_path.setText(database.get_db_path()) stored_settings = database.load_all_settings() self.txt_root_deck.setText(stored_settings.get("anki_root_deck_name", "Spanish")) self.txt_sub_deck.setText(stored_settings.get("anki_sub_deck_name", "")) self.txt_export_dir.setText(stored_settings.get("anki_export_directory", os.path.expanduser("~"))) self.txt_tts_voice.setText(stored_settings.get("tts_preferred_voice", "Apple_Monica")) self.txt_tts_speed.setText(stored_settings.get("tts_playback_speed", "0.75")) self.blockSignals(False) def update_setting(self, key, value): """Internal helper to communicate state mutations instantly upward.""" self.settings_changed.emit(key, value) def browse_database_file(self): """Allows user to choose an existing SQLite database file or create a new one.""" current_db = self.txt_db_path.text() file_path, _ = QFileDialog.getOpenFileName( self, "Select SQLite Database File", current_db, "SQLite Database (*.db *.sqlite *.sqlite3);;All Files (*)", ) if file_path: database.set_db_path(file_path) database.ensure_database_populated() self.populate_fields_from_db_state() QMessageBox.information( self, "Database Switched", f"Active database switched to:\n{file_path}", ) def reset_default_database(self): """Resets the database path back to macOS Application Support default directory.""" default_path = database.get_default_db_path() database.set_db_path(default_path) database.ensure_database_populated() self.populate_fields_from_db_state() QMessageBox.information( self, "Database Reset", f"Reset database path to default location:\n{default_path}", ) def browse_export_directory(self): """Invokes a native macOS directory finder path browser window.""" current_dir = self.txt_export_dir.text() or os.path.expanduser("~") selected_directory = QFileDialog.getExistingDirectory( self, "Select Anki Export Target Location", current_dir ) if selected_directory: self.txt_export_dir.setText(selected_directory) self.update_setting("anki_export_directory", selected_directory) def dispatch_anki_export(self): """Constructs and validates the structured deck names namespace before signaling main.py.""" root = self.txt_root_deck.text().strip() sub = self.txt_sub_deck.text().strip() if not root: QMessageBox.warning(self, "Invalid Parameters", "A root deck namespace destination must be provided.") return # Combine hierarchy into standard Anki format: 'Root::SubDeck' full_deck_name = f"{root}::{sub}" if sub else root self.export_anki_requested.emit(full_deck_name)