diff --git a/tabs/review_tab.py b/tabs/review_tab.py
index e189a83..45ead07 100644
--- a/tabs/review_tab.py
+++ b/tabs/review_tab.py
@@ -2,7 +2,8 @@
import random
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
- QPushButton, QFrame, QStackedWidget
+ QLineEdit, QPushButton, QTableWidget, QTableWidgetItem,
+ QHeaderView, QFormLayout, QMessageBox
)
from PyQt6.QtCore import Qt, pyqtSlot
import database
@@ -11,191 +12,143 @@ class ReviewTab(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
- # Core State Variables
- self.review_pool = []
- self.current_index = -1
+ # Master cache of records loaded from the database
+ self.all_cached_records = []
# Primary Layout
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(30, 20, 30, 20)
- main_layout.setSpacing(20)
+ main_layout.setSpacing(15)
- # Header Status Tracker
- self.lbl_status = QLabel("Session Status: No active cards loaded.")
- self.lbl_status.setStyleSheet("font-size: 13px; font-weight: bold; color: #7F8C8D; letter-spacing: 0.5px;")
- main_layout.addWidget(self.lbl_status)
+ # --- SECTION 1: TOP REGION (Source Context & Tags) ---
+ top_container = QWidget()
+ top_layout = QFormLayout(top_container)
+ top_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
+ top_layout.setSpacing(10)
- # --- THE CARD CANVAS AREA ---
- self.card_frame = QFrame()
- self.card_frame.setStyleSheet("""
- QFrame {
- background-color: #FAFAFA;
- border: 2px solid #E5E7E9;
- border-radius: 8px;
+ self.txt_review_context = QLineEdit()
+ self.txt_review_context.setPlaceholderText("Filter deck by context (e.g., Camino 2027)...")
+ self.txt_review_context.setStyleSheet("padding: 6px; font-size: 14px;")
+ self.txt_review_context.textChanged.connect(self.handle_live_filter)
+
+ self.txt_review_tags = QLineEdit()
+ self.txt_review_tags.setPlaceholderText("Filter deck by tags (e.g., verb, greeting)...")
+ self.txt_review_tags.setStyleSheet("padding: 6px; font-size: 14px;")
+ self.txt_review_tags.textChanged.connect(self.handle_live_filter)
+
+ top_layout.addRow(QLabel("Source Context:"), self.txt_review_context)
+ top_layout.addRow(QLabel("Tags:"), self.txt_review_tags)
+
+ main_layout.addWidget(top_container)
+
+ # --- SECTION 2: MIDDLE REGION (Translations Table View) ---
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["English Phrase", "Spanish Translation"])
+ self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
+ self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
+
+ # Format table header behaviors to stretch beautifully
+ header = self.table.horizontalHeader()
+ header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
+ header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
+
+ main_layout.addWidget(self.table)
+
+ # --- SECTION 3: BOTTOM REGION (Action Control Panel) ---
+ bottom_layout = QHBoxLayout()
+ bottom_layout.setSpacing(15)
+
+ self.btn_generate_deck = QPushButton("🗂️ Generate Deck")
+ self.btn_generate_deck.setCursor(Qt.CursorShape.PointingHandCursor)
+ self.btn_generate_deck.setStyleSheet("""
+ QPushButton {
+ background-color: #27AE60;
+ color: white;
+ font-weight: bold;
+ font-size: 14px;
+ padding: 10px 22px;
+ border-radius: 5px;
}
+ QPushButton:hover { background-color: #219653; }
""")
- card_layout = QVBoxLayout(self.card_frame)
- card_layout.setContentsMargins(40, 40, 40, 40)
+ self.btn_generate_deck.clicked.connect(self.generate_deck_action)
- # Stacked display interface separating Question and Answer card views
- self.card_stack = QStackedWidget()
-
- # View A: Card Front Layout (Prompt and Source Language Text)
- self.view_front = QWidget()
- front_layout = QVBoxLayout(self.view_front)
- front_prompt = QLabel("TRANSLATE THIS TO SPANISH:")
- front_prompt.setStyleSheet("font-size: 12px; font-weight: bold; color: #BDC3C7; letter-spacing: 1px;")
- front_prompt.setAlignment(Qt.AlignmentFlag.AlignCenter)
-
- self.lbl_english = QLabel("English Text Layer")
- self.lbl_english.setStyleSheet("font-size: 26px; color: #34495E; font-weight: 500; margin-top: 20px;")
- self.lbl_english.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.lbl_english.setWordWrap(True)
-
- front_layout.addWidget(front_prompt)
- front_layout.addWidget(self.lbl_english)
- front_layout.addStretch()
-
- # View B: Card Back Layout (Revealed target text alongside historical context notes)
- self.view_back = QWidget()
- back_layout = QVBoxLayout(self.view_back)
-
- self.lbl_spanish = QLabel("Spanish Translated Phrase")
- self.lbl_spanish.setStyleSheet("font-size: 34px; font-weight: bold; color: #2980B9; margin-bottom: 10px;")
- self.lbl_spanish.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.lbl_spanish.setWordWrap(True)
-
- self.lbl_notes = QLabel("Context/Historical reference notes go here...")
- self.lbl_notes.setStyleSheet("""
- QLabel {
- font-size: 15px;
- font-style: italic;
- color: #7F8C8D;
- background-color: #EAEDED;
- padding: 12px;
- border-radius: 4px;
- margin-top: 20px;
+ self.btn_generate_video = QPushButton("🎬 Generate Video")
+ self.btn_generate_video.setCursor(Qt.CursorShape.PointingHandCursor)
+ self.btn_generate_video.setStyleSheet("""
+ QPushButton {
+ background-color: #2980B9;
+ color: white;
+ font-weight: bold;
+ font-size: 14px;
+ padding: 10px 22px;
+ border-radius: 5px;
}
+ QPushButton:hover { background-color: #1F618D; }
""")
- self.lbl_notes.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.lbl_notes.setWordWrap(True)
+ self.btn_generate_video.clicked.connect(self.generate_video_action)
- back_layout.addWidget(self.lbl_spanish)
- back_layout.addWidget(self.lbl_notes)
- back_layout.addStretch()
+ bottom_layout.addWidget(self.btn_generate_deck)
+ bottom_layout.addWidget(self.btn_generate_video)
+ bottom_layout.addStretch()
- # Mount the structural views into the execution layer index
- self.card_stack.addWidget(self.view_front)
- self.card_stack.addWidget(self.view_back)
- card_layout.addWidget(self.card_stack)
+ main_layout.addLayout(bottom_layout)
- main_layout.addWidget(self.card_frame)
-
- # --- THE INTERACTIVE BOTTOM BAR CONTROL PIPELINE ---
- self.control_stack = QStackedWidget()
-
- # Panel A: Contains exclusively the single full-width layout Reveal button
- self.panel_reveal = QWidget()
- reveal_layout = QHBoxLayout(self.panel_reveal)
- reveal_layout.setContentsMargins(0, 0, 0, 0)
- self.btn_reveal = QPushButton("Reveal Answer Verification")
- self.btn_reveal.setStyleSheet("""
- QPushButton { background-color: #34495E; color: white; font-weight: bold; font-size: 15px; padding: 12px; border-radius: 5px; }
- QPushButton:hover { background-color: #2C3E50; }
- """)
- self.btn_reveal.clicked.connect(self.reveal_card_answer)
- reveal_layout.addWidget(self.btn_reveal)
-
- # Panel B: Contains the standard sequential navigation tools (Pass / Fail iteration indicators)
- self.panel_navigation = QWidget()
- nav_layout = QHBoxLayout(self.panel_navigation)
- nav_layout.setContentsMargins(0, 0, 0, 0)
- nav_layout.setSpacing(15)
-
- self.btn_next = QPushButton("Next Phrase ➔")
- self.btn_next.setStyleSheet("""
- QPushButton { background-color: #27AE60; color: white; font-weight: bold; font-size: 15px; padding: 12px; border-radius: 5px; }
- QPushButton:hover { background-color: #2ECC71; }
- """)
- self.btn_next.clicked.connect(self.advance_review_index)
- nav_layout.addWidget(self.btn_next)
-
- self.control_stack.addWidget(self.panel_reveal)
- self.control_stack.addWidget(self.panel_navigation)
-
- main_layout.addWidget(self.control_stack)
-
- # Load up your initial study loop deck pool array elements
+ # Populate initial table layout on startup
self.reload_review_pool()
@pyqtSlot()
def reload_review_pool(self):
- """Fetches consolidated text entries from the database module and scrambles their indexing."""
- raw_records = database.get_all_translations_explicit()
-
- # Filter down records to ensure they possess safe core text parameters
- self.review_pool = [r for r in raw_records if r["es_text"] and r["en_text"]]
-
- # Randomize review sequencing to prevent memory bias based on insertion order
- random.shuffle(self.review_pool)
-
- if self.review_pool:
- self.current_index = 0
- self.display_current_card_front()
- else:
- self.current_index = -1
- self.lbl_status.setText("Session Status: No usable records found inside spanish_trainer.db")
- self.lbl_english.setText("The database appears to be empty.")
- self.control_stack.setEnabled(False)
-
- def display_current_card_front(self):
- """Configures the UI canvas parameters to display the prompt front."""
- if not (0 <= self.current_index < len(self.review_pool)):
- return
-
- record = self.review_pool[self.current_index]
- self.lbl_status.setText(f"Review Cycle Running: Phrase {self.current_index + 1} of {len(self.review_pool)}")
-
- # Render prompt text labels cleanly
- self.lbl_english.setText(record["en_text"])
-
- # Reset visual stack view states back to original baselines
- self.card_stack.setCurrentIndex(0) # Switch to front canvas text display
- self.control_stack.setCurrentIndex(0) # Toggle control element button bar back to 'Reveal' layout
+ """Fetches consolidated text entries from the database and initializes cache."""
+ try:
+ self.all_cached_records = database.get_all_translations_explicit()
+ self.handle_live_filter()
+ except Exception as e:
+ print(f"Error initializing flashcard display: {e}")
@pyqtSlot()
- def reveal_card_answer(self):
- """Displays translation targets on the card back."""
- if not (0 <= self.current_index < len(self.review_pool)):
- return
-
- record = self.review_pool[self.current_index]
- self.lbl_spanish.setText(record["es_text"])
+ def handle_live_filter(self):
+ """Filters the display table row contents matching current Context and Tags criteria."""
+ self.table.blockSignals(True)
+ self.table.setRowCount(0)
- # Show contextual notes or structural flags cleanly if populated
- if record["notes"] or record["source_context"]:
- context_string = record["source_context"] if record["source_context"] else ""
- notes_string = f" | {record['notes']}" if record["notes"] else ""
- self.lbl_notes.setText(f"Context: {context_string}{notes_string}")
- self.lbl_notes.setVisible(True)
- else:
- self.lbl_notes.setVisible(False)
+ filter_ctx = self.txt_review_context.text().lower().strip()
+ filter_tag = self.txt_review_tags.text().lower().strip()
+
+ visible_row_index = 0
+ for row in self.all_cached_records:
+ val_ctx = (row["source_context"] or "").lower()
+ val_tag = (row["tags"] or "").lower()
- # Toggle component visualization states
- self.card_stack.setCurrentIndex(1) # Swap card panel over to the back answer layout
- self.control_stack.setCurrentIndex(1) # Swap button layouts over to display 'Next Phrase' action bars
+ # Show row if it satisfies both filter boxes
+ if (filter_ctx in val_ctx) and (filter_tag in val_tag):
+ self.table.insertRow(visible_row_index)
+
+ item_en = QTableWidgetItem(row["en_text"])
+ item_es = QTableWidgetItem(row["es_text"])
+
+ self.table.setItem(visible_row_index, 0, item_en)
+ self.table.setItem(visible_row_index, 1, item_es)
+
+ visible_row_index += 1
+
+ self.table.blockSignals(False)
@pyqtSlot()
- def advance_review_index(self):
- """Increments index counters to display a new flashcard container."""
- if not self.review_pool:
- return
-
- self.current_index += 1
-
- # Loop review cards continuously if the session index boundary thresholds overflow
- if self.current_index >= len(self.review_pool):
- self.current_index = 0
- random.shuffle(self.review_pool) # Re-scramble deck upon completing the pass loop
-
- self.display_current_card_front()
\ No newline at end of file
+ def generate_deck_action(self):
+ """Placeholder function execution trigger for processing deck compiler passes."""
+ QMessageBox.information(
+ self,
+ "Deck Compiler Active",
+ f"Compiling a custom Anki training deck container using the {self.table.rowCount()} visible filtered rows."
+ )
+
+ @pyqtSlot()
+ def generate_video_action(self):
+ """Placeholder function execution trigger for media compiler production automation."""
+ QMessageBox.information(
+ self,
+ "Media Generator Active",
+ f"Initiating background video asset production using the {self.table.rowCount()} visible phrases."
+ )
\ No newline at end of file