# main.py import sys import os from PyQt6.QtWidgets import QMainWindow, QTabWidget, QApplication, QMessageBox, QVBoxLayout, QWidget 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("Castilian 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. Wire up the cross-module communication channels (Signals) self.wire_application_signals() def refresh_settings_cache(self): """Loads all database settings into an easy-to-read dictionary.""" self.app_settings = database.load_all_settings() def wire_application_signals(self): """Connects tab user-actions to execution managers inside main.py.""" # Settings Tab Signal Actions self.settings_tab.settings_changed.connect(self.handle_settings_update) self.settings_tab.export_anki_requested.connect(self.execute_anki_export) self.settings_tab.generate_video_requested.connect(self.execute_video_generation) # Sandbox / Review Tab synchronization signals self.sandbox_tab.data_mutated.connect(self.refresh_ui_views) @pyqtSlot(str, str) def handle_settings_update(self, key, value): """Saves a modified setting value directly down to the database and updates memory cache.""" database.save_setting_to_db(key, value) self.refresh_settings_cache() @pyqtSlot(str) def execute_anki_export(self, full_deck_name): """Bridges data coordinates out of SQLite to trigger the text-to-speech anki file compiler.""" self.refresh_settings_cache() output_dir = self.app_settings.get("anki_export_directory", os.path.expanduser("~")) output_file = os.path.join(output_dir, f"{full_deck_name.replace('::', '_')}.apkg") # Pull all active translation records out via explicit SQL matching records = database.get_all_translations_explicit() if not records: QMessageBox.warning(self, "Export Failed", "The database contains no translation records to export.") return try: anki_exporter.compile_anki_package(records, output_file, full_deck_name) QMessageBox.information(self, "Export Success", f"Successfully compiled light-weight TTS deck to:\n{output_file}") except Exception as e: QMessageBox.critical(self, "Export Error", f"Failed to parse or write Anki package archive:\n{str(e)}") @pyqtSlot() def execute_video_generation(self): """Coordinates data streams to invoke the independent video compilation generator.""" self.refresh_settings_cache() records = database.get_all_translations_explicit() if not records: QMessageBox.warning(self, "Generation Failed", "No phrases available to construct a training video playlist loops.") return # UI updates can block execution loops, so we notify before processing self.statusBar().showMessage("Generating training video assets. Please wait...") QApplication.processEvents() try: video_generator.generate_training_video(records, self.app_settings) QMessageBox.information(self, "Success", "MP4 Video compilation completed successfully.") self.statusBar().showMessage("Video compilation completed.", 5000) except Exception as e: QMessageBox.critical(self, "Video Error", f"An error occurred during frame rendering sequences:\n{str(e)}") self.statusBar().clearMessage() @pyqtSlot() def refresh_ui_views(self): """Instructs distinct active tabs to reload data windows after database changes occur.""" self.sandbox_tab.reload_table_display() self.review_tab.reload_review_pool() if __name__ == "__main__": app = QApplication(sys.argv) # Modern clean styling choices for desktop applications app.setStyle('Fusion') window = MainWindow() window.show() sys.exit(app.exec())