added sitting tab

This commit is contained in:
stephen 2026-06-19 15:09:05 +10:00
parent ae68ffd199
commit f03115a06a
4 changed files with 6352 additions and 5529 deletions

View file

@ -37,6 +37,11 @@
- [how to backup the sqlite3 database](#how-to-backup-the-sqlite3-database)
- [how ro alter a table within the data base](#how-ro-alter-a-table-within-the-data-base)
- [to examine the PRAGMA table](#to-examine-the-pragma-table)
- [1. The Cleanest Output: .schema](#1-the-cleanest-output-schema)
- [sqlite3 schema](#sqlite3-schema)
- [sqlite3 fullschema](#sqlite3-fullschema)
- [Pro-Tip: Running it Interactively](#pro-tip-running-it-interactively)
- [how to add a new table via command line](#how-to-add-a-new-table-via-command-line)
# 1. spanish-voice-trainer
# 2. Project Summary:
@ -470,4 +475,47 @@ sqlite3 spanish_trainer.db "ALTER TABLE translations ADD COLUMN tags TEXT DEFAUL
sqlite3 spanish_trainer.db "PRAGMA table_info(translations);"
```
## 1. The Cleanest Output: .schema
The most precise command is the dot-command .schema. You can pass it directly into the CLI call:
## sqlite3 schema
```zsh
sqlite3 spanish_trainer.db ".schema"
```
Why this is exactly what you need:
It prints out the exact SQL CREATE TABLE statements used to build your entire database structure.
It automatically includes any constraints (like PRIMARY KEY, FOREIGN KEY, or UNIQUE).
It shows all other structural items, such as your indexes and triggers, giving you a complete blueprint of the file.
2. The Comprehensive Alternative: .fullschema
If you ever start implementing complex custom database views or virtual tables down the road, you can use:
## sqlite3 fullschema
```zsh
sqlite3 spanish_trainer.db ".fullschema"
```
This does everything .schema does, but it also appends statistical metadata and structural configuration variables used to optimize the query planner.
## Pro-Tip: Running it Interactively
If you are already inside an active SQLite session using your terminal, you don't need to specify the database name or use quotation marks. You can just type the dot-command at the prompt:
SQL
sqlite> .schema
If you want a more compact table checklist just to see what names are present before inspecting their structures, you can use:
SQL
sqlite> .tables
## how to add a new table via command line
```zsh
sqlite3 spanish_trainer.db "CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT);"
```

File diff suppressed because it is too large Load diff

127
main.py
View file

@ -4,7 +4,7 @@ import os
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QTabWidget, QVBoxLayout,
QHBoxLayout, QLabel, QPushButton, QLineEdit, QComboBox,
QTableWidget, QTableWidgetItem, QSlider, QFormLayout, QTextEdit, QFrame, QMessageBox
QTableWidget, QTableWidgetItem, QSlider, QFormLayout, QTextEdit, QFrame, QMessageBox, QFileDialog
)
from PyQt6.QtCore import Qt, QUrl
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
@ -24,6 +24,9 @@ class SpanishTrainerApp(QMainWindow):
# 1. Initialize schema structures and check ingestion status
self.ensure_database_populated()
# Load system persistent settings from DB
self.load_system_settings()
# Audio Player Architecture Setup
self.media_player = QMediaPlayer()
self.audio_output = QAudioOutput()
@ -38,8 +41,9 @@ class SpanishTrainerApp(QMainWindow):
self.init_phrase_sandbox_tab()
self.init_flashcard_reviewer_tab()
self.init_settings_tab() # Mount the new settings panel
# 2. Populate both table grids on initialization
# 2. Populate table grids on initialization
self.refresh_crud_table()
self.refresh_review_table()
@ -51,6 +55,9 @@ class SpanishTrainerApp(QMainWindow):
conn = get_connection()
cursor = conn.cursor()
# Ensure the settings table exists alongside legacy core tables
cursor.execute("CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT);")
try:
cursor.execute("SELECT COUNT(*) FROM translations")
count = cursor.fetchone()[0]
@ -75,6 +82,38 @@ class SpanishTrainerApp(QMainWindow):
else:
print(f"❌ Error: Source document '{pdf_file}' is missing from the directory root.")
def load_system_settings(self):
"""Loads persistent path directories from the key-value settings table."""
self.anki_export_dir = os.getcwd()
self.video_export_dir = os.getcwd()
conn = get_connection()
cursor = conn.cursor()
try:
cursor.execute("SELECT key, value FROM settings")
rows = cursor.fetchall()
for row in rows:
if row[0] == "anki_export_directory":
self.anki_export_dir = row[1]
elif row[0] == "video_export_directory":
self.video_export_dir = row[1]
except Exception as e:
print(f"⚠️ Failed to read application settings from database: {e}")
finally:
conn.close()
def save_setting_to_db(self, key, value):
"""Updates or inserts a specific system runtime variable into the database."""
conn = get_connection()
cursor = conn.cursor()
try:
cursor.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", (key, value))
conn.commit()
except Exception as e:
print(f"❌ Critical: Failed to save setting '{key}': {e}")
finally:
conn.close()
# =====================================================================
# 🗄️ TAB 1: TRANSLATION-CENTRIC PHRASE SANDBOX (CRUD)
# =====================================================================
@ -245,13 +284,12 @@ class SpanishTrainerApp(QMainWindow):
self.tabs.addTab(tab, "🗄️ Phrase Sandbox (CRUD)")
# =====================================================================
# 🃏 TAB 2: FLASHCARD STUDY MODULE (UPGRADED TWIN FRAME WINDOW)
# 🃏 TAB 2: FLASHCARD STUDY MODULE
# =====================================================================
def init_flashcard_reviewer_tab(self):
tab = QWidget()
layout = QHBoxLayout(tab)
# --- LEFT SIDE PANEL: Symmetrical Review Filters & Substack Table ---
left_panel = QVBoxLayout()
filter_layout = QHBoxLayout()
@ -277,10 +315,8 @@ class SpanishTrainerApp(QMainWindow):
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.setStyleSheet("background-color: #ffffff; border: 2px solid #bdc3c7; border-radius: 12px;")
card_layout = QVBoxLayout(card_frame)
@ -303,7 +339,6 @@ class SpanishTrainerApp(QMainWindow):
card_layout.addStretch()
right_panel.addWidget(card_frame, stretch=4)
# Middle Operational Playback Stack Controls
playback_layout = QHBoxLayout()
playback_layout.addWidget(QLabel("🔊 Voice Speed:"))
self.slider_review_speed = QSlider(Qt.Orientation.Horizontal)
@ -329,7 +364,6 @@ class SpanishTrainerApp(QMainWindow):
right_panel.addSpacing(15)
# Bottom Utility Navigation & Deployment Array
bottom_utility_layout = QHBoxLayout()
self.btn_export_anki = QPushButton("📦 Export Anki Deck")
self.btn_export_video = QPushButton("🎬 Export Video")
@ -339,7 +373,6 @@ class SpanishTrainerApp(QMainWindow):
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)
@ -354,11 +387,64 @@ class SpanishTrainerApp(QMainWindow):
layout.addLayout(right_panel, stretch=3)
self.tabs.addTab(tab, "🃏 Flashcard Review")
# =====================================================================
# ⚙️ TAB 3: SYSTEM HARDWARE & EXPORT SETTINGS
# =====================================================================
def init_settings_tab(self):
tab = QWidget()
layout = QVBoxLayout(tab)
settings_frame = QFrame()
settings_frame.setFrameShape(QFrame.Shape.StyledPanel)
form_layout = QFormLayout(settings_frame)
# Anki Export Path Form Group
anki_layout = QHBoxLayout()
self.line_anki_dir = QLineEdit(self.anki_export_dir)
self.line_anki_dir.setReadOnly(True)
btn_browse_anki = QPushButton("Browse 📂")
btn_browse_anki.clicked.connect(self.handle_browse_anki_directory)
anki_layout.addWidget(self.line_anki_dir)
anki_layout.addWidget(btn_browse_anki)
# Video Export Path Form Group
video_layout = QHBoxLayout()
self.line_video_dir = QLineEdit(self.video_export_dir)
self.line_video_dir.setReadOnly(True)
btn_browse_video = QPushButton("Browse 📂")
btn_browse_video.clicked.connect(self.handle_browse_video_directory)
video_layout.addWidget(self.line_video_dir)
video_layout.addWidget(btn_browse_video)
form_layout.addRow("<b>Anki Deck Export Destination:</b>", anki_layout)
form_layout.addRow("<b>Video Assembly Output Target:</b>", video_layout)
layout.addWidget(QLabel("<h2>Application Preferences & Workspace Routing</h2>"))
layout.addWidget(settings_frame)
layout.addStretch()
self.tabs.addTab(tab, "⚙️ Settings")
def handle_browse_anki_directory(self):
"""Triggers a file browser directory selection for native Anki card compiles."""
directory = QFileDialog.getExistingDirectory(self, "Select Anki Export Folder", self.anki_export_dir)
if directory:
self.anki_export_dir = directory
self.line_anki_dir.setText(directory)
self.save_setting_to_db("anki_export_directory", directory)
def handle_browse_video_directory(self):
"""Triggers a file browser directory selection for video timeline outputs."""
directory = QFileDialog.getExistingDirectory(self, "Select Video Export Folder", self.video_export_dir)
if directory:
self.video_export_dir = directory
self.line_video_dir.setText(directory)
self.save_setting_to_db("video_export_directory", directory)
# =====================================================================
# ⚡ DATA MATRIX CONTROL & SYNCHRONIZATION VIEWS
# =====================================================================
def refresh_crud_table(self):
"""Pulls unified translation nodes into pairs while enforcing context text matches."""
conn = get_connection()
cursor = conn.cursor()
@ -396,7 +482,6 @@ class SpanishTrainerApp(QMainWindow):
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()
@ -428,7 +513,7 @@ class SpanishTrainerApp(QMainWindow):
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
self.flashcard_ids_pool.append(row_data[4])
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 "")))
@ -471,7 +556,6 @@ class SpanishTrainerApp(QMainWindow):
if not selected_ranges:
return
row = selected_ranges[0].topRow()
phrase_id = self.flashcard_ids_pool[row]
self.load_flashcard_by_id(phrase_id)
@ -560,7 +644,6 @@ class SpanishTrainerApp(QMainWindow):
# 🔊 AUDIO ENGINE & EXPANDED FLASHCARD ACTIONS
# =====================================================================
def load_flashcard_by_id(self, phrase_id):
"""Sets internal execution contexts cleanly targeting a specific card reference."""
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
@ -578,7 +661,6 @@ class SpanishTrainerApp(QMainWindow):
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
@ -586,7 +668,6 @@ class SpanishTrainerApp(QMainWindow):
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)
@ -596,7 +677,6 @@ class SpanishTrainerApp(QMainWindow):
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()
@ -611,7 +691,6 @@ class SpanishTrainerApp(QMainWindow):
os.makedirs("media", exist_ok=True)
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):
print(f"🔊 Review Fallback: Synthesizing missing audio asset on the fly for '{text_str}'...")
try:
@ -703,21 +782,21 @@ class SpanishTrainerApp(QMainWindow):
# 📦 ARTIFACT EXPORT GATEWAYS (ANKI & DEPLOYMENT CODES)
# =====================================================================
def handle_export_anki_deck(self):
"""Action handler placeholder loop for your upcoming genanki package deployment modules."""
"""Action handler loop for 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"
f"Target Directory: {self.anki_export_dir}\n"
f"Currently Filtered Scope count: {len(self.flashcard_ids_pool)} cards."
)
def handle_export_video_assets(self):
"""Action handler placeholder loop for compiling video cards."""
"""Action handler 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."
f"Target Directory: {self.video_export_dir}\n"
f"Currently Filtered Scope count: {len(self.flashcard_ids_pool)} sequences."
)
# =====================================================================

Binary file not shown.