diff --git a/main.py b/main.py index 317bea5..401a433 100644 --- a/main.py +++ b/main.py @@ -115,6 +115,7 @@ class SpanishTrainerApp(QMainWindow): self.video_first_lang = "English First (en -> es)" self.video_repeats_count = "3" self.video_pause_duration = "4.0" + self.default_deck_name = "Trainer" # Unified structural configuration fallback conn = get_connection() cursor = conn.cursor() @@ -132,6 +133,8 @@ class SpanishTrainerApp(QMainWindow): self.video_repeats_count = row[1] elif row[0] == "video_pause_duration": self.video_pause_duration = row[1] + elif row[0] == "default_deck_name": + self.default_deck_name = row[1] except Exception as e: print(f"⚠️ Failed to read application settings from database: {e}") finally: @@ -151,6 +154,8 @@ class SpanishTrainerApp(QMainWindow): self.video_repeats_count = value elif key == "video_pause_duration": self.video_pause_duration = value + elif key == "default_deck_name": + self.default_deck_name = value except Exception as e: print(f"❌ Critical: Failed to save setting '{key}': {e}") finally: @@ -280,7 +285,7 @@ class SpanishTrainerApp(QMainWindow): self.input_tags.setPlaceholderText("e.g., irregular_er boots_verb") self.input_deck_tag = QLineEdit() - self.input_deck_tag.setPlaceholderText("Anki Sub-deck Hierarchy") + self.input_deck_tag.setPlaceholderText("Overrides System Default Workspace Deck") button_qss = """ QPushButton { @@ -357,7 +362,7 @@ class SpanishTrainerApp(QMainWindow): form_layout.addRow("Classification Profile:", self.combo_type) form_layout.addRow("Source Context ID (Raw):", self.input_context) form_layout.addRow("Anki Note Tags:", self.input_tags) - form_layout.addRow("Target Deck Scope:", self.input_deck_tag) + form_layout.addRow("Target Deck Scope (Optional):", self.input_deck_tag) crud_buttons = QHBoxLayout() self.btn_save = QPushButton("➕ Create Pair") @@ -513,6 +518,11 @@ class SpanishTrainerApp(QMainWindow): video_layout.addWidget(self.line_video_dir) video_layout.addWidget(btn_browse_video) + # New Workspace Target Settings Directive Entry + self.line_default_deck = QLineEdit(self.default_deck_name) + self.line_default_deck.setPlaceholderText("e.g., Spanish::Aula_Plus_1") + self.line_default_deck.textChanged.connect(lambda v: self.save_setting_to_db("default_deck_name", v.strip())) + self.combo_first_lang = QComboBox() self.combo_first_lang.addItems(["English First (en -> es)", "Spanish First (es -> en)"]) self.combo_first_lang.setCurrentText(self.video_first_lang) @@ -528,6 +538,7 @@ class SpanishTrainerApp(QMainWindow): form_layout.addRow("Anki Deck Export Destination:", anki_layout) form_layout.addRow("Video Assembly Output Target:", video_layout) + form_layout.addRow("Default Target Deck String:", self.line_default_deck) form_layout.addRow("Introductory Anchor Audio Language:", self.combo_first_lang) form_layout.addRow("Target Translation Loop Multiplier (Repeats):", self.spin_video_repeats) form_layout.addRow("User Recall Repetition Frame Intermission (Seconds):", self.spin_pause_duration) @@ -621,7 +632,6 @@ class SpanishTrainerApp(QMainWindow): self.lbl_card_text.setText(self.current_active_en_text) self.btn_flip_card.setText("👁️ Reveal Translation") - # --- t.notes (record[5]) has been intentionally omitted from this layout context --- meta_str = f"Link ID: {record[0]} | Context: {record[3] or 'N/A'}" if record[4]: meta_str += f" | Tags: {record[4]}" @@ -676,6 +686,10 @@ class SpanishTrainerApp(QMainWindow): (Card 2) forward-reverse pairs cleanly. Explicitly maps 4 fields: EnglishText, EnglishAudio, SpanishText, SpanishAudio. Context and notes are fully removed. """ + # Architectural Fallback: If pool is empty, run an internal update pass first to grab current matrix records + if not self.flashcard_ids_pool: + self.refresh_review_table() + targets = self.flashcard_ids_pool if not targets: QMessageBox.warning(self, "Export Aborted", "The active flashcard pool filter is completely empty. Nothing to export.") @@ -746,11 +760,21 @@ class SpanishTrainerApp(QMainWindow): css='.card { font-family: arial; font-size: 20px; text-align: center; background-color: #fafafa; padding: 25px; border-radius: 8px; }' ) - deck_name_fallback = records[0][6] if records[0][6] else "Castilian Voice Trainer" - anki_deck = genanki.Deck(DECK_ID, f"Spanish::{deck_name_fallback}") + # --- RESTRUCTURED STRINGS SAFEGUARD BLOCK --- + # Safeguard checks to normalize index 6 data entry and prevent generation string crashes + raw_deck_entry = records[0][6] + if raw_deck_entry and str(raw_deck_entry).strip(): + deck_name_fallback = str(raw_deck_entry).strip() + else: + deck_name_fallback = self.default_deck_name if self.default_deck_name else "Trainer" + + # Explicitly ensure the Anki namespace structural hierarchy sequence is intact + full_deck_string = f"Spanish::{deck_name_fallback}" if "Spanish::" not in deck_name_fallback else deck_name_fallback + + anki_deck = genanki.Deck(DECK_ID, full_deck_string) media_files_bundle = [] - print(f"📦 Assembling Anki audio package for {len(records)} notes...") + print(f"📦 Assembling Anki audio package for {len(records)} notes under deck: {full_deck_string}...") for row in records: tx_id, es_text, en_text, context, tags, notes, deck_group = row @@ -792,8 +816,6 @@ class SpanishTrainerApp(QMainWindow): if context: tag_list.append(str(context).replace(" ", "_").replace(".", "_")) - # Populate note fields sequentially matching model specification: - # EnglishText, EnglishAudio, SpanishText, SpanishAudio anki_note = genanki.Note( model=spanish_model, fields=[ @@ -806,19 +828,28 @@ class SpanishTrainerApp(QMainWindow): ) anki_deck.add_note(anki_note) - # Output compilation assembly - export_output_path = os.path.join(self.anki_export_dir, f"{deck_name_fallback.replace('::', '_')}.apkg") + # Output file name normalization optimization to sweep unmapped character sets + safe_file_name = deck_name_fallback.replace('::', '_').replace('/', '_').strip() + export_output_path = os.path.join(self.anki_export_dir, f"{safe_file_name}.apkg") - package = genanki.Package(anki_deck) - package.media_files = list(set(media_files_bundle)) # Excludes duplicate track instances - package.write_to_file(export_output_path) + try: + package = genanki.Package(anki_deck) + package.media_files = list(set(media_files_bundle)) + package.write_to_file(export_output_path) - print(f"✅ Success! Balanced text-audio cards exported cleanly: {export_output_path}") - QMessageBox.information( - self, - "Anki Package Compiled", - f"Successfully compiled {len(records)} balanced text-audio translation flashcard nodes.\n\nDestination:\n{export_output_path}" - ) + print(f"✅ Success! Balanced text-audio cards exported cleanly: {export_output_path}") + QMessageBox.information( + self, + "Anki Package Compiled", + f"Successfully compiled {len(records)} balanced text-audio translation flashcard nodes.\n\nDestination:\n{export_output_path}" + ) + except Exception as export_err: + print(f"❌ Genanki Write Failure Exception Error: {export_err}") + QMessageBox.critical( + self, + "Export Failure Error", + f"The packaging sub-engine failed to write file to disk:\n{export_err}" + ) def handle_export_video_assets(self): pass @@ -840,9 +871,12 @@ class SpanishTrainerApp(QMainWindow): """, (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip())) en_id = cursor.lastrowid + # Pulls from self.input_deck_tag if typed, cleanly defaults to global persistent variable self.default_deck_name + target_deck = self.input_deck_tag.text().strip() or self.default_deck_name + cursor.execute(""" 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, target_deck, self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip())) tx_id = cursor.lastrowid conn.commit() @@ -881,11 +915,14 @@ class SpanishTrainerApp(QMainWindow): WHERE id = ? """, (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), self.current_sandbox_en_id)) + # Updated field configuration using system preferences cache fallback assignment + target_deck = self.input_deck_tag.text().strip() or self.default_deck_name + cursor.execute(""" UPDATE translations SET deck_name = ?, notes = ?, tags = ? WHERE translation_id = ? - """, (self.input_deck_tag.text().strip() or "General", self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip(), int(tx_id_str))) + """, (target_deck, self.input_grammar_note.toPlainText().strip(), self.input_tags.text().strip(), int(tx_id_str))) conn.commit() except Exception as e: @@ -1038,7 +1075,7 @@ class SpanishTrainerApp(QMainWindow): self.combo_type.setCurrentText(str(record[3]) if record[3] else "phrase") self.input_context.setText(str(record[4]) if record[4] else "") self.input_tags.setText(str(record[7]) if record[7] is not None else "") - 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 "") self.input_grammar_note.setPlainText(str(record[6]) if record[6] is not None else "") self.current_sandbox_es_id = record[8] self.current_sandbox_en_id = record[9] @@ -1064,8 +1101,6 @@ class SpanishTrainerApp(QMainWindow): if __name__ == "__main__": app = QApplication(sys.argv) - app.setApplicationName("Spanish Voice Trainer") - app.setOrganizationName("Oxnee Pty. Ltd.") window = SpanishTrainerApp() window.show() sys.exit(app.exec()) \ No newline at end of file diff --git a/spanish_trainer.db b/spanish_trainer.db index 7234a38..42db890 100644 Binary files a/spanish_trainer.db and b/spanish_trainer.db differ