# main.py import sys import os import shutil from PyQt6.QtWidgets import ( QMainWindow, QTabWidget, QApplication, QMessageBox, QVBoxLayout, QWidget, QFileDialog ) from PyQt6.QtGui import QAction, QKeySequence from PyQt6.QtCore import pyqtSlot # Import our unified data layer import database # Import the UI components from our sub-package from tabs.sandbox_tab import SandboxTab from tabs.review_tab import ReviewTab from tabs.settings_tab import SettingsTab # Import our background engine runners import anki_exporter import video_generator class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Spanish Voice Trainer") self.setMinimumSize(900, 650) # 1. Ensure database structure exists on startup database.ensure_database_populated() # 2. In-memory cache for application configurations self.app_settings = {} self.refresh_settings_cache() # 3. Setup central tabbed layout container self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.main_layout = QVBoxLayout(self.central_widget) self.tab_widget = QTabWidget() self.main_layout.addWidget(self.tab_widget) # 4. Instantiate isolated tab modules self.sandbox_tab = SandboxTab() self.review_tab = ReviewTab() self.settings_tab = SettingsTab() # 5. Mount tabs with Database Sandbox on the far left (Index 0) self.tab_widget.addTab(self.sandbox_tab, "Database Sandbox") self.tab_widget.addTab(self.review_tab, "Flashcard Review") self.tab_widget.addTab(self.settings_tab, "Configuration Settings") # 6. Build application Menu Bar self.create_menu_bar() # 7. Wire up the cross-module communication channels (Signals) self.wire_application_signals() def create_menu_bar(self): """Constructs native macOS Menu Bar integrating directly with the primary system process menu.""" menu_bar = self.menuBar() # --- 1. APP MENU (Spanish Voice Trainer) --- # An empty string title merges actions directly into the native primary macOS Application Menu app_menu = menu_bar.addMenu("") about_action = QAction("About Spanish Voice Trainer", self) about_action.setMenuRole(QAction.MenuRole.AboutRole) about_action.triggered.connect(self.show_about_dialog) app_menu.addAction(about_action) update_action = QAction("Check for Updates...", self) update_action.setMenuRole(QAction.MenuRole.ApplicationSpecificRole) update_action.triggered.connect(self.check_for_updates) app_menu.addAction(update_action) quit_action = QAction("Quit Spanish Voice Trainer", self) quit_action.setMenuRole(QAction.MenuRole.QuitRole) quit_action.setShortcut(QKeySequence.StandardKey.Quit) quit_action.triggered.connect(QApplication.instance().quit) app_menu.addAction(quit_action) # --- 2. TOOLS MENU --- tools_menu = menu_bar.addMenu("Tools") backup_action = QAction("Backup Database...", self) backup_action.triggered.connect(self.handle_backup_database) tools_menu.addAction(backup_action) # --- 3. HELP MENU --- help_menu = menu_bar.addMenu("Help") help_instructions_action = QAction("User Guide & Instructions", self) help_instructions_action.setMenuRole(QAction.MenuRole.ApplicationSpecificRole) help_instructions_action.setShortcut(QKeySequence.StandardKey.HelpContents) help_instructions_action.triggered.connect(self.show_help_dialog) help_menu.addAction(help_instructions_action) # --- Menu Action Handlers --- @pyqtSlot() def show_about_dialog(self): """Displays application details and copyright information.""" QMessageBox.about( self, "About Spanish Voice Trainer", "
Version: 1.0.0
" "A local-first application for Castilian Spanish vocabulary practice, " "neural TTS voice previewing, and Anki deck generation.
" ) @pyqtSlot() def check_for_updates(self): """Placeholder action for update checks.""" QMessageBox.information( self, "Check for Updates", "Check for Updates is not implemented yet in this version." ) @pyqtSlot() def handle_backup_database(self): """Copies the primary SQLite database file to a user-selected location.""" db_path = database.DB_PATH if hasattr(database, 'DB_PATH') else "spanish_app.db" if not os.path.exists(db_path): QMessageBox.warning(self, "Backup Error", f"Database file '{db_path}' could not be located.") return save_path, _ = QFileDialog.getSaveFileName( self, "Save Database Backup", os.path.expanduser("~/Desktop/spanish_app_backup.db"), "SQLite Database (*.db *.sqlite);;All Files (*)" ) if save_path: try: shutil.copy2(db_path, save_path) QMessageBox.information( self, "Backup Complete", f"Successfully created a backup copy of your database at:\n{save_path}" ) except Exception as e: QMessageBox.critical(self, "Backup Failed", f"Could not create database backup:\n{str(e)}") @pyqtSlot() def show_help_dialog(self): """Displays user guide and usage instructions.""" instructions_text = ( "