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