modification of Flashcard Review tab
This commit is contained in:
parent
15717ff43a
commit
5312eb75be
2 changed files with 242 additions and 132 deletions
BIN
database/trainer_backup_20260618_224811.db
Normal file
BIN
database/trainer_backup_20260618_224811.db
Normal file
Binary file not shown.
392
main.py
392
main.py
|
|
@ -19,7 +19,7 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle("Castilian Voice Trainer Pro")
|
self.setWindowTitle("Castilian Voice Trainer Pro")
|
||||||
self.setMinimumSize(1150, 700)
|
self.setMinimumSize(1200, 750)
|
||||||
|
|
||||||
# 1. Initialize schema structures and check ingestion status
|
# 1. Initialize schema structures and check ingestion status
|
||||||
self.ensure_database_populated()
|
self.ensure_database_populated()
|
||||||
|
|
@ -27,10 +27,10 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
# Audio Player Architecture Setup
|
# Audio Player Architecture Setup
|
||||||
self.media_player = QMediaPlayer()
|
self.media_player = QMediaPlayer()
|
||||||
self.audio_output = QAudioOutput()
|
self.audio_output = QAudioOutput()
|
||||||
|
|
||||||
# Set the audio output routing to the player instance
|
|
||||||
self.media_player.setAudioOutput(self.audio_output)
|
self.media_player.setAudioOutput(self.audio_output)
|
||||||
|
|
||||||
self.current_flashcard_id = None
|
self.current_flashcard_id = None
|
||||||
|
self.flashcard_ids_pool = [] # Tracks currently filtered study list IDs
|
||||||
|
|
||||||
# Central Main Window Tabs Interface
|
# Central Main Window Tabs Interface
|
||||||
self.tabs = QTabWidget()
|
self.tabs = QTabWidget()
|
||||||
|
|
@ -39,8 +39,9 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
self.init_phrase_sandbox_tab()
|
self.init_phrase_sandbox_tab()
|
||||||
self.init_flashcard_reviewer_tab()
|
self.init_flashcard_reviewer_tab()
|
||||||
|
|
||||||
# 2. Populate unified database rows into interface layout grid
|
# 2. Populate both table grids on initialization
|
||||||
self.refresh_crud_table()
|
self.refresh_crud_table()
|
||||||
|
self.refresh_review_table()
|
||||||
|
|
||||||
def ensure_database_populated(self):
|
def ensure_database_populated(self):
|
||||||
"""Forces database configuration structure and triggers pipeline execution if empty."""
|
"""Forces database configuration structure and triggers pipeline execution if empty."""
|
||||||
|
|
@ -50,7 +51,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Verify if the translations table has rows populated
|
|
||||||
try:
|
try:
|
||||||
cursor.execute("SELECT COUNT(*) FROM translations")
|
cursor.execute("SELECT COUNT(*) FROM translations")
|
||||||
count = cursor.fetchone()[0]
|
count = cursor.fetchone()[0]
|
||||||
|
|
@ -66,11 +66,9 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
pdf_file = "aula_int_plus_1_glos_en_alfa.pdf"
|
pdf_file = "aula_int_plus_1_glos_en_alfa.pdf"
|
||||||
|
|
||||||
if os.path.exists(pdf_file):
|
if os.path.exists(pdf_file):
|
||||||
# Run the layout parser staging execution pass
|
|
||||||
importer = BulkImporter()
|
importer = BulkImporter()
|
||||||
importer.import_pdf_glossary(pdf_file, "Aula Internacional Plus 1")
|
importer.import_pdf_glossary(pdf_file, "Aula Internacional Plus 1")
|
||||||
|
|
||||||
# Clean, pair up English/Spanish, and insert the final rows
|
|
||||||
cleaner = GlossaryCleaner()
|
cleaner = GlossaryCleaner()
|
||||||
cleaner.process_database_clean()
|
cleaner.process_database_clean()
|
||||||
print("✨ Ingestion pipeline processing sequence successfully completed.")
|
print("✨ Ingestion pipeline processing sequence successfully completed.")
|
||||||
|
|
@ -84,7 +82,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
tab = QWidget()
|
tab = QWidget()
|
||||||
layout = QHBoxLayout(tab)
|
layout = QHBoxLayout(tab)
|
||||||
|
|
||||||
# --- LEFT SIDE PANEL: Filter Controls & Unified Data Grid ---
|
|
||||||
left_panel = QVBoxLayout()
|
left_panel = QVBoxLayout()
|
||||||
|
|
||||||
filter_layout = QHBoxLayout()
|
filter_layout = QHBoxLayout()
|
||||||
|
|
@ -96,14 +93,13 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
|
|
||||||
filter_layout.addWidget(QLabel("📂 Context:"))
|
filter_layout.addWidget(QLabel("📂 Context:"))
|
||||||
self.search_context_input = QLineEdit()
|
self.search_context_input = QLineEdit()
|
||||||
self.search_context_input.setPlaceholderText("e.g. U2 or U8_5A")
|
self.search_context_input.setPlaceholderText("e.g. U2")
|
||||||
self.search_context_input.setMaximumWidth(130)
|
self.search_context_input.setMaximumWidth(130)
|
||||||
self.search_context_input.textChanged.connect(self.refresh_crud_table)
|
self.search_context_input.textChanged.connect(self.refresh_crud_table)
|
||||||
filter_layout.addWidget(self.search_context_input)
|
filter_layout.addWidget(self.search_context_input)
|
||||||
|
|
||||||
left_panel.addLayout(filter_layout)
|
left_panel.addLayout(filter_layout)
|
||||||
|
|
||||||
# Unified Translations Row Table Matrix
|
|
||||||
self.translation_table = QTableWidget()
|
self.translation_table = QTableWidget()
|
||||||
self.translation_table.setColumnCount(6)
|
self.translation_table.setColumnCount(6)
|
||||||
self.translation_table.setHorizontalHeaderLabels([
|
self.translation_table.setHorizontalHeaderLabels([
|
||||||
|
|
@ -112,7 +108,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
self.translation_table.itemSelectionChanged.connect(self.handle_table_row_select)
|
self.translation_table.itemSelectionChanged.connect(self.handle_table_row_select)
|
||||||
left_panel.addWidget(self.translation_table)
|
left_panel.addWidget(self.translation_table)
|
||||||
|
|
||||||
# Row Pointer Navigation Steppers
|
|
||||||
nav_layout = QHBoxLayout()
|
nav_layout = QHBoxLayout()
|
||||||
self.btn_row_up = QPushButton("🔼 Previous Pair")
|
self.btn_row_up = QPushButton("🔼 Previous Pair")
|
||||||
self.btn_row_down = QPushButton("🔽 Next Pair")
|
self.btn_row_down = QPushButton("🔽 Next Pair")
|
||||||
|
|
@ -122,7 +117,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
nav_layout.addWidget(self.btn_row_down)
|
nav_layout.addWidget(self.btn_row_down)
|
||||||
left_panel.addLayout(nav_layout)
|
left_panel.addLayout(nav_layout)
|
||||||
|
|
||||||
# --- RIGHT SIDE PANEL: Side-by-Side Unified Twin Form Box Views ---
|
|
||||||
right_panel = QVBoxLayout()
|
right_panel = QVBoxLayout()
|
||||||
form_frame = QFrame()
|
form_frame = QFrame()
|
||||||
form_frame.setFrameShape(QFrame.Shape.StyledPanel)
|
form_frame.setFrameShape(QFrame.Shape.StyledPanel)
|
||||||
|
|
@ -144,14 +138,12 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
self.input_context = QLineEdit()
|
self.input_context = QLineEdit()
|
||||||
self.input_context.setPlaceholderText("e.g., U8_5A")
|
self.input_context.setPlaceholderText("e.g., U8_5A")
|
||||||
|
|
||||||
# Explicit Tags field implementation
|
|
||||||
self.input_tags = QLineEdit()
|
self.input_tags = QLineEdit()
|
||||||
self.input_tags.setPlaceholderText("e.g., irregular_er boots_verb (space-separated)")
|
self.input_tags.setPlaceholderText("e.g., irregular_er boots_verb")
|
||||||
|
|
||||||
self.input_deck_tag = QLineEdit()
|
self.input_deck_tag = QLineEdit()
|
||||||
self.input_deck_tag.setPlaceholderText("Anki Sub-deck Hierarchy")
|
self.input_deck_tag.setPlaceholderText("Anki Sub-deck Hierarchy")
|
||||||
|
|
||||||
# Common layout stylesheet for the utility buttons
|
|
||||||
button_qss = """
|
button_qss = """
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
|
|
@ -170,7 +162,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 🔊 1. Spanish Header Layout with Speed Controls
|
|
||||||
es_header_layout = QHBoxLayout()
|
es_header_layout = QHBoxLayout()
|
||||||
es_header_layout.setContentsMargins(0, 5, 0, 5)
|
es_header_layout.setContentsMargins(0, 5, 0, 5)
|
||||||
es_header_layout.addWidget(QLabel("<b>🇪🇸 Castilian Spanish Text Element:</b>"))
|
es_header_layout.addWidget(QLabel("<b>🇪🇸 Castilian Spanish Text Element:</b>"))
|
||||||
|
|
@ -190,7 +181,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
es_header_layout.addWidget(self.combo_speed_es)
|
es_header_layout.addWidget(self.combo_speed_es)
|
||||||
es_header_layout.addStretch()
|
es_header_layout.addStretch()
|
||||||
|
|
||||||
# 🔊 2. English Header Layout Activated with Speed Controls
|
|
||||||
en_header_layout = QHBoxLayout()
|
en_header_layout = QHBoxLayout()
|
||||||
en_header_layout.setContentsMargins(0, 5, 0, 5)
|
en_header_layout.setContentsMargins(0, 5, 0, 5)
|
||||||
en_header_layout.addWidget(QLabel("<b>🇬🇧 English Target Translation:</b>"))
|
en_header_layout.addWidget(QLabel("<b>🇬🇧 English Target Translation:</b>"))
|
||||||
|
|
@ -210,7 +200,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
en_header_layout.addWidget(self.combo_speed_en)
|
en_header_layout.addWidget(self.combo_speed_en)
|
||||||
en_header_layout.addStretch()
|
en_header_layout.addStretch()
|
||||||
|
|
||||||
# 📝 3. Symmetrical Grammar/Usage Note Header Layout
|
|
||||||
grammar_header_layout = QHBoxLayout()
|
grammar_header_layout = QHBoxLayout()
|
||||||
grammar_header_layout.setContentsMargins(0, 5, 0, 5)
|
grammar_header_layout.setContentsMargins(0, 5, 0, 5)
|
||||||
grammar_header_layout.addWidget(QLabel("<b>📝 Grammar / Usage Note:</b>"))
|
grammar_header_layout.addWidget(QLabel("<b>📝 Grammar / Usage Note:</b>"))
|
||||||
|
|
@ -218,9 +207,8 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
|
|
||||||
self.input_grammar_note = QTextEdit()
|
self.input_grammar_note = QTextEdit()
|
||||||
self.input_grammar_note.setMaximumHeight(75)
|
self.input_grammar_note.setMaximumHeight(75)
|
||||||
self.input_grammar_note.setPlaceholderText("e.g., feminine variant, irregular radical change, requires subjunctive...")
|
self.input_grammar_note.setPlaceholderText("e.g., feminine variant...")
|
||||||
|
|
||||||
# Mount elements sequentially into the form frame mapping structure
|
|
||||||
form_layout.addRow("<b>Translation Link ID:</b>", self.input_tx_id)
|
form_layout.addRow("<b>Translation Link ID:</b>", self.input_tx_id)
|
||||||
form_layout.addRow(es_header_layout)
|
form_layout.addRow(es_header_layout)
|
||||||
form_layout.addRow(self.input_text_es)
|
form_layout.addRow(self.input_text_es)
|
||||||
|
|
@ -257,21 +245,52 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
self.tabs.addTab(tab, "🗄️ Phrase Sandbox (CRUD)")
|
self.tabs.addTab(tab, "🗄️ Phrase Sandbox (CRUD)")
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# 🃏 TAB 2: FLASHCARD STUDY MODULE
|
# 🃏 TAB 2: FLASHCARD STUDY MODULE (UPGRADED TWIN FRAME WINDOW)
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
def init_flashcard_reviewer_tab(self):
|
def init_flashcard_reviewer_tab(self):
|
||||||
tab = QWidget()
|
tab = QWidget()
|
||||||
layout = QVBoxLayout(tab)
|
layout = QHBoxLayout(tab)
|
||||||
|
|
||||||
|
# --- LEFT SIDE PANEL: Symmetrical Review Filters & Substack Table ---
|
||||||
|
left_panel = QVBoxLayout()
|
||||||
|
|
||||||
|
filter_layout = QHBoxLayout()
|
||||||
|
filter_layout.addWidget(QLabel("📂 Context:"))
|
||||||
|
self.review_context_filter = QLineEdit()
|
||||||
|
self.review_context_filter.setPlaceholderText("Filter Context...")
|
||||||
|
self.review_context_filter.textChanged.connect(self.refresh_review_table)
|
||||||
|
filter_layout.addWidget(self.review_context_filter)
|
||||||
|
|
||||||
|
filter_layout.addWidget(QLabel("🏷️ Tag:"))
|
||||||
|
self.review_tag_filter = QLineEdit()
|
||||||
|
self.review_tag_filter.setPlaceholderText("Filter Tag...")
|
||||||
|
self.review_tag_filter.textChanged.connect(self.refresh_review_table)
|
||||||
|
filter_layout.addWidget(self.review_tag_filter)
|
||||||
|
|
||||||
|
left_panel.addLayout(filter_layout)
|
||||||
|
|
||||||
|
self.review_table = QTableWidget()
|
||||||
|
self.review_table.setColumnCount(4)
|
||||||
|
self.review_table.setHorizontalHeaderLabels(["Tx ID", "Spanish Phrase", "Context", "Tags"])
|
||||||
|
self.review_table.itemSelectionChanged.connect(self.handle_review_table_select)
|
||||||
|
left_panel.addWidget(self.review_table)
|
||||||
|
|
||||||
|
layout.addLayout(left_panel, stretch=4)
|
||||||
|
|
||||||
|
# --- RIGHT SIDE PANEL: Workspace Review Controls & Active Layout Flashcard Canvas ---
|
||||||
|
right_panel = QVBoxLayout()
|
||||||
|
|
||||||
|
# Large Display Flashcard Frame Window Canvas (Top Half Block)
|
||||||
card_frame = QFrame()
|
card_frame = QFrame()
|
||||||
card_frame.setStyleSheet("background-color: #ffffff; border: 2px solid #bdc3c7; border-radius: 12px;")
|
card_frame.setStyleSheet("background-color: #ffffff; border: 2px solid #bdc3c7; border-radius: 12px;")
|
||||||
card_layout = QVBoxLayout(card_frame)
|
card_layout = QVBoxLayout(card_frame)
|
||||||
|
card_frame.setMinimumHeight(280)
|
||||||
|
|
||||||
self.lbl_card_text = QLabel("Select 'Next Card' to initiate study sequence...")
|
self.lbl_card_text = QLabel("Select a row or click 'Next Card' to initiate...")
|
||||||
self.lbl_card_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.lbl_card_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.lbl_card_text.setFont(QFont("Arial", 22, QFont.Weight.Bold))
|
self.lbl_card_text.setFont(QFont("Arial", 20, QFont.Weight.Bold))
|
||||||
self.lbl_card_text.setWordWrap(True)
|
self.lbl_card_text.setWordWrap(True)
|
||||||
self.lbl_card_text.setStyleSheet("color: #2c3e50; border: none; padding: 25px;")
|
self.lbl_card_text.setStyleSheet("color: #2c3e50; border: none; padding: 20px;")
|
||||||
|
|
||||||
self.lbl_card_meta = QLabel("")
|
self.lbl_card_meta = QLabel("")
|
||||||
self.lbl_card_meta.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.lbl_card_meta.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
@ -282,40 +301,61 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
card_layout.addWidget(self.lbl_card_text)
|
card_layout.addWidget(self.lbl_card_text)
|
||||||
card_layout.addWidget(self.lbl_card_meta)
|
card_layout.addWidget(self.lbl_card_meta)
|
||||||
card_layout.addStretch()
|
card_layout.addStretch()
|
||||||
|
right_panel.addWidget(card_frame, stretch=4)
|
||||||
|
|
||||||
|
# Middle Operational Playback Stack Controls
|
||||||
playback_layout = QHBoxLayout()
|
playback_layout = QHBoxLayout()
|
||||||
playback_layout.addWidget(QLabel("🔊 Voice Track Speed:"))
|
playback_layout.addWidget(QLabel("🔊 Voice Speed:"))
|
||||||
self.slider_review_speed = QSlider(Qt.Orientation.Horizontal)
|
self.slider_review_speed = QSlider(Qt.Orientation.Horizontal)
|
||||||
self.slider_review_speed.setMinimum(50)
|
self.slider_review_speed.setMinimum(50)
|
||||||
self.slider_review_speed.setMaximum(150)
|
self.slider_review_speed.setMaximum(150)
|
||||||
self.slider_review_speed.setValue(100)
|
self.slider_review_speed.setValue(100)
|
||||||
self.lbl_review_speed = QLabel("1.0x")
|
self.lbl_review_speed = QLabel("1.00x")
|
||||||
self.slider_review_speed.valueChanged.connect(self.handle_live_speed_change)
|
self.slider_review_speed.valueChanged.connect(self.handle_live_speed_change)
|
||||||
playback_layout.addWidget(self.slider_review_speed)
|
playback_layout.addWidget(self.slider_review_speed)
|
||||||
playback_layout.addWidget(self.lbl_review_speed)
|
playback_layout.addWidget(self.lbl_review_speed)
|
||||||
|
right_panel.addLayout(playback_layout)
|
||||||
|
|
||||||
action_buttons = QHBoxLayout()
|
action_buttons = QHBoxLayout()
|
||||||
self.btn_play_voice = QPushButton("🗣️ Play Voice Track")
|
self.btn_play_voice = QPushButton("🗣️ Play Voice Track")
|
||||||
self.btn_flip_card = QPushButton("👁️ Reveal English Partner")
|
self.btn_flip_card = QPushButton("👁️ Reveal English Partner")
|
||||||
self.btn_load_next = QPushButton("➡️ Next Card")
|
|
||||||
|
|
||||||
self.btn_play_voice.clicked.connect(self.handle_play_voice)
|
self.btn_play_voice.clicked.connect(self.handle_play_voice)
|
||||||
self.btn_flip_card.clicked.connect(self.handle_flip_card)
|
self.btn_flip_card.clicked.connect(self.handle_flip_card)
|
||||||
self.btn_load_next.clicked.connect(self.handle_load_next_card)
|
|
||||||
|
|
||||||
action_buttons.addWidget(self.btn_play_voice)
|
action_buttons.addWidget(self.btn_play_voice)
|
||||||
action_buttons.addWidget(self.btn_flip_card)
|
action_buttons.addWidget(self.btn_flip_card)
|
||||||
action_buttons.addStretch()
|
right_panel.addLayout(action_buttons)
|
||||||
action_buttons.addWidget(self.btn_load_next)
|
|
||||||
|
|
||||||
layout.addWidget(card_frame, stretch=1)
|
right_panel.addSpacing(15)
|
||||||
layout.addLayout(playback_layout)
|
|
||||||
layout.addLayout(action_buttons)
|
|
||||||
|
|
||||||
|
# Bottom Utility Navigation & Deployment Array
|
||||||
|
bottom_utility_layout = QHBoxLayout()
|
||||||
|
self.btn_export_anki = QPushButton("📦 Export Anki Deck")
|
||||||
|
self.btn_export_video = QPushButton("🎬 Export Video")
|
||||||
|
self.btn_load_next = QPushButton("➡️ Next Card")
|
||||||
|
|
||||||
|
self.btn_export_anki.clicked.connect(self.handle_export_anki_deck)
|
||||||
|
self.btn_export_video.clicked.connect(self.handle_export_video_assets)
|
||||||
|
self.btn_load_next.clicked.connect(self.handle_load_next_card)
|
||||||
|
|
||||||
|
# Style deployment array distinctively
|
||||||
|
utility_qss = "QPushButton { font-weight: bold; background-color: #eaf2f8; padding: 6px; border-radius: 4px; }"
|
||||||
|
self.btn_export_anki.setStyleSheet(utility_qss)
|
||||||
|
self.btn_export_video.setStyleSheet(utility_qss)
|
||||||
|
self.btn_load_next.setStyleSheet("QPushButton { font-weight: bold; background-color: #d5f5e3; padding: 6px; border-radius: 4px; }")
|
||||||
|
|
||||||
|
bottom_utility_layout.addWidget(self.btn_export_anki)
|
||||||
|
bottom_utility_layout.addWidget(self.btn_export_video)
|
||||||
|
bottom_utility_layout.addStretch()
|
||||||
|
bottom_utility_layout.addWidget(self.btn_load_next)
|
||||||
|
right_panel.addLayout(bottom_utility_layout)
|
||||||
|
|
||||||
|
layout.addLayout(right_panel, stretch=3)
|
||||||
self.tabs.addTab(tab, "🃏 Flashcard Review")
|
self.tabs.addTab(tab, "🃏 Flashcard Review")
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# ⚡ ENGINE DATABASE LOGIC & FILTER COMPILATIONS
|
# ⚡ DATA MATRIX CONTROL & SYNCHRONIZATION VIEWS
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
def refresh_crud_table(self):
|
def refresh_crud_table(self):
|
||||||
"""Pulls unified translation nodes into pairs while enforcing context text matches."""
|
"""Pulls unified translation nodes into pairs while enforcing context text matches."""
|
||||||
|
|
@ -326,7 +366,7 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
context_filter = self.search_context_input.text().strip()
|
context_filter = self.search_context_input.text().strip()
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
SELECT t.translation_id, p1.text, p2.text, p1.word_type, p1.source_context, t.deck_name, t.notes
|
SELECT t.translation_id, p1.text, p2.text, p1.word_type, p1.source_context, t.deck_name
|
||||||
FROM translations t
|
FROM translations t
|
||||||
JOIN phrases p1 ON t.source_phrase_id = p1.id
|
JOIN phrases p1 ON t.source_phrase_id = p1.id
|
||||||
JOIN phrases p2 ON t.target_phrase_id = p2.id
|
JOIN phrases p2 ON t.target_phrase_id = p2.id
|
||||||
|
|
@ -355,6 +395,44 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
val = row_data[col_idx]
|
val = row_data[col_idx]
|
||||||
self.translation_table.setItem(row_idx, col_idx, QTableWidgetItem(str(val if val is not None else "")))
|
self.translation_table.setItem(row_idx, col_idx, QTableWidgetItem(str(val if val is not None else "")))
|
||||||
|
|
||||||
|
def refresh_review_table(self):
|
||||||
|
"""Pulls and displays a targeted subset stack matching specific context or tag filters."""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
context_filter = self.review_context_filter.text().strip()
|
||||||
|
tag_filter = self.review_tag_filter.text().strip()
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT t.translation_id, p1.text, p1.source_context, t.tags, p1.id
|
||||||
|
FROM translations t
|
||||||
|
JOIN phrases p1 ON t.source_phrase_id = p1.id
|
||||||
|
WHERE p1.language = 'es'
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
if context_filter:
|
||||||
|
query += " AND p1.source_context LIKE ?"
|
||||||
|
params.append(f"%{context_filter}%")
|
||||||
|
if tag_filter:
|
||||||
|
query += " AND t.tags LIKE ?"
|
||||||
|
params.append(f"%{tag_filter}%")
|
||||||
|
|
||||||
|
query += " ORDER BY t.translation_id ASC"
|
||||||
|
|
||||||
|
cursor.execute(query, params)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
self.review_table.setRowCount(0)
|
||||||
|
self.flashcard_ids_pool = []
|
||||||
|
|
||||||
|
for row_idx, row_data in enumerate(rows):
|
||||||
|
self.review_table.insertRow(row_idx)
|
||||||
|
self.flashcard_ids_pool.append(row_data[4]) # Track active selection text key reference IDs
|
||||||
|
for col_idx in range(4):
|
||||||
|
val = row_data[col_idx]
|
||||||
|
self.review_table.setItem(row_idx, col_idx, QTableWidgetItem(str(val if val is not None else "")))
|
||||||
|
|
||||||
def handle_table_row_select(self):
|
def handle_table_row_select(self):
|
||||||
selected_ranges = self.translation_table.selectedRanges()
|
selected_ranges = self.translation_table.selectedRanges()
|
||||||
if not selected_ranges:
|
if not selected_ranges:
|
||||||
|
|
@ -388,30 +466,38 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
self.input_deck_tag.setText(str(record[5]) if record[5] else "General")
|
self.input_deck_tag.setText(str(record[5]) if record[5] else "General")
|
||||||
self.input_grammar_note.setPlainText(str(record[6]) if record[6] is not None else "")
|
self.input_grammar_note.setPlainText(str(record[6]) if record[6] is not None else "")
|
||||||
|
|
||||||
|
def handle_review_table_select(self):
|
||||||
|
selected_ranges = self.review_table.selectedRanges()
|
||||||
|
if not selected_ranges:
|
||||||
|
return
|
||||||
|
row = selected_ranges[0].topRow()
|
||||||
|
|
||||||
|
phrase_id = self.flashcard_ids_pool[row]
|
||||||
|
self.load_flashcard_by_id(phrase_id)
|
||||||
|
|
||||||
def step_table_row(self, direction):
|
def step_table_row(self, direction):
|
||||||
"""Steps your focus row highlighting pointer index sequentially."""
|
|
||||||
current_row = self.translation_table.currentRow()
|
current_row = self.translation_table.currentRow()
|
||||||
next_row = current_row + direction
|
next_row = current_row + direction
|
||||||
if 0 <= next_row < self.translation_table.rowCount():
|
if 0 <= next_row < self.translation_table.rowCount():
|
||||||
self.translation_table.setCurrentCell(next_row, 0)
|
self.translation_table.setCurrentCell(next_row, 0)
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# ➕ ENGINE ATOMIC OPERATIONS LOGIC (CRUD MODIFIERS)
|
||||||
|
# =====================================================================
|
||||||
def crud_create_pair(self):
|
def crud_create_pair(self):
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Write Spanish node entry
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO phrases (text, language, word_type, source_context) VALUES (?, 'es', ?, ?)
|
INSERT INTO phrases (text, language, word_type, source_context) VALUES (?, 'es', ?, ?)
|
||||||
""", (self.input_text_es.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
|
""", (self.input_text_es.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
|
||||||
es_id = cursor.lastrowid
|
es_id = cursor.lastrowid
|
||||||
|
|
||||||
# Write English node entry
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO phrases (text, language, word_type, source_context) VALUES (?, 'en', ?, ?)
|
INSERT INTO phrases (text, language, word_type, source_context) VALUES (?, 'en', ?, ?)
|
||||||
""", (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
|
""", (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
|
||||||
en_id = cursor.lastrowid
|
en_id = cursor.lastrowid
|
||||||
|
|
||||||
# Build cross-referencing translation relational binding matrix row complete with tags data
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO translations (source_phrase_id, target_phrase_id, deck_name, notes, tags) VALUES (?, ?, ?, ?, ?)
|
INSERT INTO translations (source_phrase_id, target_phrase_id, deck_name, notes, tags) VALUES (?, ?, ?, ?, ?)
|
||||||
""", (es_id, en_id, self.input_deck_tag.text().strip() or "General", self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip()))
|
""", (es_id, en_id, self.input_deck_tag.text().strip() or "General", self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip()))
|
||||||
|
|
@ -419,6 +505,7 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
self.refresh_crud_table()
|
self.refresh_crud_table()
|
||||||
|
self.refresh_review_table()
|
||||||
QMessageBox.information(self, "Success", "Isolated phrase pairs created and relational link bound.")
|
QMessageBox.information(self, "Success", "Isolated phrase pairs created and relational link bound.")
|
||||||
|
|
||||||
def crud_update_pair(self):
|
def crud_update_pair(self):
|
||||||
|
|
@ -433,7 +520,6 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
|
|
||||||
if ids:
|
if ids:
|
||||||
es_id, en_id = ids
|
es_id, en_id = ids
|
||||||
# Keep underscores exact in context updates
|
|
||||||
cursor.execute("UPDATE phrases SET text=?, word_type=?, source_context=? WHERE id=?",
|
cursor.execute("UPDATE phrases SET text=?, word_type=?, source_context=? WHERE id=?",
|
||||||
(self.input_text_es.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), es_id))
|
(self.input_text_es.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), es_id))
|
||||||
cursor.execute("UPDATE phrases SET text=?, word_type=?, source_context=? WHERE id=?",
|
cursor.execute("UPDATE phrases SET text=?, word_type=?, source_context=? WHERE id=?",
|
||||||
|
|
@ -444,6 +530,7 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
self.refresh_crud_table()
|
self.refresh_crud_table()
|
||||||
|
self.refresh_review_table()
|
||||||
QMessageBox.information(self, "Success", "Relational node structural update complete.")
|
QMessageBox.information(self, "Success", "Relational node structural update complete.")
|
||||||
|
|
||||||
def crud_delete_pair(self):
|
def crud_delete_pair(self):
|
||||||
|
|
@ -462,6 +549,7 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
self.refresh_crud_table()
|
self.refresh_crud_table()
|
||||||
|
self.refresh_review_table()
|
||||||
self.input_tx_id.clear()
|
self.input_tx_id.clear()
|
||||||
self.input_text_es.clear()
|
self.input_text_es.clear()
|
||||||
self.input_text_en.clear()
|
self.input_text_en.clear()
|
||||||
|
|
@ -469,111 +557,76 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
self.input_tags.clear()
|
self.input_tags.clear()
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# 🔊 AUDIO OPERATIONS & FLASHCARD CONTROL
|
# 🔊 AUDIO ENGINE & EXPANDED FLASHCARD ACTIONS
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
def handle_sandbox_play_es(self):
|
def load_flashcard_by_id(self, phrase_id):
|
||||||
"""Generates/plays the Spanish phrase from the Sandbox using the chosen speed rate."""
|
"""Sets internal execution contexts cleanly targeting a specific card reference."""
|
||||||
text_str = self.input_text_es.toPlainText().strip()
|
conn = get_connection()
|
||||||
if not text_str:
|
cursor = conn.cursor()
|
||||||
QMessageBox.warning(self, "Empty Value", "Please select a valid translation pair or type a Spanish phrase to play.")
|
cursor.execute("""
|
||||||
|
SELECT t.translation_id, p1.text, p1.source_context, t.deck_name, p1.id, t.tags
|
||||||
|
FROM translations t
|
||||||
|
JOIN phrases p1 ON t.source_phrase_id = p1.id
|
||||||
|
WHERE p1.id = ?
|
||||||
|
""", (phrase_id,))
|
||||||
|
record = cursor.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if record:
|
||||||
|
self.current_flashcard_id = record[4]
|
||||||
|
self.lbl_card_text.setText(record[1])
|
||||||
|
self.lbl_card_meta.setText(f"Link ID: {record[0]} • Context: {record[2]} • Tag: {record[5]} • Deck: {record[3]}")
|
||||||
|
|
||||||
|
def handle_load_next_card(self):
|
||||||
|
"""Picks a random track row selection pulled directly from the current filtered list pool."""
|
||||||
|
if not self.flashcard_ids_pool:
|
||||||
|
QMessageBox.information(self, "Empty Pool", "No flashcards match your selected filter configurations.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
import random
|
||||||
|
target_id = random.choice(self.flashcard_ids_pool)
|
||||||
|
|
||||||
|
# Highlight matching row inside left panel layout matrix for context tracking
|
||||||
|
try:
|
||||||
|
matched_idx = self.flashcard_ids_pool.index(target_id)
|
||||||
|
self.review_table.setCurrentCell(matched_idx, 0)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.load_flashcard_by_id(target_id)
|
||||||
|
|
||||||
|
def handle_play_voice(self):
|
||||||
|
"""Plays or generates the Castilian neural track dynamically on fallback request loops."""
|
||||||
|
if not self.current_flashcard_id:
|
||||||
|
return
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT text, language FROM phrases WHERE id = ?", (self.current_flashcard_id,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
text_str, lang = row
|
||||||
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
|
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
|
||||||
os.makedirs("media", exist_ok=True)
|
os.makedirs("media", exist_ok=True)
|
||||||
target_file = f"media/{safe_name}_es_female.mp3"
|
target_file = f"media/{safe_name}_{lang}_female.mp3"
|
||||||
|
|
||||||
|
# Fault-Tolerance Loop: If Sandbox pass missed this track, generate it seamlessly right here
|
||||||
if not os.path.exists(target_file):
|
if not os.path.exists(target_file):
|
||||||
print(f"🔊 Generating Neural Castilian Spanish track for '{text_str}'...")
|
print(f"🔊 Review Fallback: Synthesizing missing audio asset on the fly for '{text_str}'...")
|
||||||
try:
|
try:
|
||||||
import asyncio
|
import asyncio
|
||||||
import edge_tts
|
import edge_tts
|
||||||
communicate = edge_tts.Communicate(text_str, "es-ES-ElviraNeural")
|
communicate = edge_tts.Communicate(text_str, "es-ES-ElviraNeural")
|
||||||
asyncio.run(communicate.save(target_file))
|
asyncio.run(communicate.save(target_file))
|
||||||
except Exception as tts_err:
|
except Exception as tts_err:
|
||||||
QMessageBox.critical(self, "TTS Error", f"Failed to synthesize Spanish voice:\n{tts_err}")
|
QMessageBox.critical(self, "TTS Error", f"Review pipeline failed to synthesize track:\n{tts_err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if os.path.exists(target_file):
|
|
||||||
speed_multiplier = float(self.combo_speed_es.currentText().replace("x", ""))
|
|
||||||
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
|
|
||||||
self.media_player.setPlaybackRate(speed_multiplier)
|
|
||||||
self.media_player.play()
|
|
||||||
|
|
||||||
def handle_sandbox_play_en(self):
|
|
||||||
"""Generates/plays the English translation string from the Sandbox using its speed rate."""
|
|
||||||
text_str = self.input_text_en.toPlainText().strip()
|
|
||||||
if not text_str:
|
|
||||||
QMessageBox.warning(self, "Empty Value", "Please enter text inside the English target translation container.")
|
|
||||||
return
|
|
||||||
|
|
||||||
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
|
|
||||||
os.makedirs("media", exist_ok=True)
|
|
||||||
target_file = f"media/{safe_name}_en_female.mp3"
|
|
||||||
|
|
||||||
if not os.path.exists(target_file):
|
|
||||||
print(f"🔊 Generating Neural English voice track for '{text_str}'...")
|
|
||||||
try:
|
|
||||||
import asyncio
|
|
||||||
import edge_tts
|
|
||||||
communicate = edge_tts.Communicate(text_str, "en-GB-SoniaNeural")
|
|
||||||
asyncio.run(communicate.save(target_file))
|
|
||||||
except Exception as tts_err:
|
|
||||||
QMessageBox.critical(self, "TTS Error", f"Failed to synthesize English voice:\n{tts_err}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if os.path.exists(target_file):
|
|
||||||
speed_multiplier = float(self.combo_speed_en.currentText().replace("x", ""))
|
|
||||||
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
|
|
||||||
self.media_player.setPlaybackRate(speed_multiplier)
|
|
||||||
self.media_player.play()
|
|
||||||
|
|
||||||
def handle_load_next_card(self):
|
|
||||||
"""Picks a random phrase node from the database, shifting state context to flashcard mode."""
|
|
||||||
conn = get_connection()
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT t.translation_id, p1.text, p1.word_type, p1.source_context, t.deck_name, p1.id
|
|
||||||
FROM translations t
|
|
||||||
JOIN phrases p1 ON t.source_phrase_id = p1.id
|
|
||||||
WHERE p1.language = 'es'
|
|
||||||
ORDER BY RANDOM() LIMIT 1
|
|
||||||
""")
|
|
||||||
record = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if record:
|
|
||||||
self.current_flashcard_id = record[5]
|
|
||||||
self.lbl_card_text.setText(record[1])
|
|
||||||
self.lbl_card_meta.setText(f"Tx Link node: {record[0]} • Context: {record[3]} • Subdeck: {record[4]}")
|
|
||||||
|
|
||||||
def handle_play_voice(self):
|
|
||||||
if not self.current_flashcard_id:
|
|
||||||
return
|
|
||||||
conn = get_connection()
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("SELECT text, language, voice_gender FROM phrases WHERE id = ?", (self.current_flashcard_id,))
|
|
||||||
row = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if row:
|
|
||||||
text_str, lang, gender = row
|
|
||||||
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
|
|
||||||
|
|
||||||
resolved_gender = gender if gender else "female"
|
|
||||||
target_file = f"media/{safe_name}_{lang}_{resolved_gender}.mp3"
|
|
||||||
|
|
||||||
if os.path.exists(target_file):
|
if os.path.exists(target_file):
|
||||||
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
|
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
|
||||||
self.media_player.setPlaybackRate(self.slider_review_speed.value() / 100.0)
|
self.media_player.setPlaybackRate(self.slider_review_speed.value() / 100.0)
|
||||||
self.media_player.play()
|
self.media_player.play()
|
||||||
else:
|
|
||||||
QMessageBox.warning(self, "Asset Missing", f"Audio file not found at path location:\n{target_file}")
|
|
||||||
|
|
||||||
def handle_live_speed_change(self, value):
|
|
||||||
rate = value / 100.0
|
|
||||||
self.lbl_review_speed.setText(f"{rate:.2f}x")
|
|
||||||
if self.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
|
|
||||||
self.media_player.setPlaybackRate(rate)
|
|
||||||
|
|
||||||
def handle_flip_card(self):
|
def handle_flip_card(self):
|
||||||
if not self.current_flashcard_id:
|
if not self.current_flashcard_id:
|
||||||
|
|
@ -592,13 +645,81 @@ class SpanishTrainerApp(QMainWindow):
|
||||||
if row:
|
if row:
|
||||||
clean_es = self.lbl_card_text.text().split("\n\n👉")[0]
|
clean_es = self.lbl_card_text.text().split("\n\n👉")[0]
|
||||||
display_text = f"{clean_es}\n\n👉 [ {row[0]} ]"
|
display_text = f"{clean_es}\n\n👉 [ {row[0]} ]"
|
||||||
|
|
||||||
# If a note exists for the card, append it dynamically to the reveal layout
|
|
||||||
if row[1]:
|
if row[1]:
|
||||||
display_text += f"\n\n💡 Note: {row[1]}"
|
display_text += f"\n\n💡 Note: {row[1]}"
|
||||||
|
|
||||||
self.lbl_card_text.setText(display_text)
|
self.lbl_card_text.setText(display_text)
|
||||||
|
|
||||||
|
def handle_sandbox_play_es(self):
|
||||||
|
text_str = self.input_text_es.toPlainText().strip()
|
||||||
|
if not text_str:
|
||||||
|
return
|
||||||
|
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
|
||||||
|
os.makedirs("media", exist_ok=True)
|
||||||
|
target_file = f"media/{safe_name}_es_female.mp3"
|
||||||
|
|
||||||
|
if not os.path.exists(target_file):
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
import edge_tts
|
||||||
|
communicate = edge_tts.Communicate(text_str, "es-ES-ElviraNeural")
|
||||||
|
asyncio.run(communicate.save(target_file))
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "TTS Error", str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
|
||||||
|
self.media_player.setPlaybackRate(float(self.combo_speed_es.currentText().replace("x", "")))
|
||||||
|
self.media_player.play()
|
||||||
|
|
||||||
|
def handle_sandbox_play_en(self):
|
||||||
|
text_str = self.input_text_en.toPlainText().strip()
|
||||||
|
if not text_str:
|
||||||
|
return
|
||||||
|
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).strip().replace(" ", "_").lower()
|
||||||
|
os.makedirs("media", exist_ok=True)
|
||||||
|
target_file = f"media/{safe_name}_en_female.mp3"
|
||||||
|
|
||||||
|
if not os.path.exists(target_file):
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
import edge_tts
|
||||||
|
communicate = edge_tts.Communicate(text_str, "en-GB-SoniaNeural")
|
||||||
|
asyncio.run(communicate.save(target_file))
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "TTS Error", str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
|
||||||
|
self.media_player.setPlaybackRate(float(self.combo_speed_en.currentText().replace("x", "")))
|
||||||
|
self.media_player.play()
|
||||||
|
|
||||||
|
def handle_live_speed_change(self, value):
|
||||||
|
rate = value / 100.0
|
||||||
|
self.lbl_review_speed.setText(f"{rate:.2f}x")
|
||||||
|
if self.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
|
||||||
|
self.media_player.setPlaybackRate(rate)
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# 📦 ARTIFACT EXPORT GATEWAYS (ANKI & DEPLOYMENT CODES)
|
||||||
|
# =====================================================================
|
||||||
|
def handle_export_anki_deck(self):
|
||||||
|
"""Action handler placeholder loop for your upcoming genanki package deployment modules."""
|
||||||
|
QMessageBox.information(
|
||||||
|
self, "Anki Export Engine",
|
||||||
|
f"Staging packaging manifest for active view subset!\n\n"
|
||||||
|
f"Currently Filtered Scope count: {len(self.flashcard_ids_pool)} cards.\n"
|
||||||
|
f"Target Destination output: Castilian_Spanish_Workspace.apkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_export_video_assets(self):
|
||||||
|
"""Action handler placeholder loop for compiling video cards."""
|
||||||
|
QMessageBox.information(
|
||||||
|
self, "Video Synthesis Suite",
|
||||||
|
f"Staging visual timeline render frames loop!\n\n"
|
||||||
|
f"Currently Filtered Scope count: {len(self.flashcard_ids_pool)} sequences.\n"
|
||||||
|
f"Target Audio Tracks bound: edge-tts neural assets map."
|
||||||
|
)
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# 🚀 DIAGNOSTIC STARTUP FRAMEWORK WRAPPER
|
# 🚀 DIAGNOSTIC STARTUP FRAMEWORK WRAPPER
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
|
|
@ -606,22 +727,11 @@ if __name__ == "__main__":
|
||||||
print("🚀 Initializing PyQt6 Application Framework...")
|
print("🚀 Initializing PyQt6 Application Framework...")
|
||||||
try:
|
try:
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
print("🔧 Spawning SpanishTrainerApp Instance...")
|
|
||||||
window = SpanishTrainerApp()
|
window = SpanishTrainerApp()
|
||||||
|
|
||||||
print("🖥️ Mounting User Interface Windows...")
|
|
||||||
window.show()
|
window.show()
|
||||||
|
|
||||||
print("🎯 Event loop engaged. Handing over control thread...")
|
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
except Exception as fatal_error:
|
except Exception as fatal_error:
|
||||||
import traceback
|
import traceback
|
||||||
print("\n❌ CRITICAL CRASH DETECTED ON CORE STARTUP THREAD!")
|
print("\n❌ CRITICAL CRASH DETECTED ON CORE STARTUP THREAD!")
|
||||||
print("====================================================")
|
|
||||||
print(f"Error Type: {type(fatal_error).__name__}")
|
|
||||||
print(f"Error Message: {fatal_error}")
|
|
||||||
print("====================================================")
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
Loading…
Reference in a new issue