# tabs/review_tab.py import random from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QStackedWidget ) from PyQt6.QtCore import Qt, pyqtSlot import database class ReviewTab(QWidget): def __init__(self, parent=None): super().__init__(parent) # Core State Variables self.review_pool = [] self.current_index = -1 # Primary Layout main_layout = QVBoxLayout(self) main_layout.setContentsMargins(30, 20, 30, 20) main_layout.setSpacing(20) # 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) # --- THE CARD CANVAS AREA --- self.card_frame = QFrame() self.card_frame.setStyleSheet(""" QFrame { background-color: #FAFAFA; border: 2px solid #E5E7E9; border-radius: 8px; } """) card_layout = QVBoxLayout(self.card_frame) card_layout.setContentsMargins(40, 40, 40, 40) # 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.lbl_notes.setAlignment(Qt.AlignmentFlag.AlignCenter) self.lbl_notes.setWordWrap(True) back_layout.addWidget(self.lbl_spanish) back_layout.addWidget(self.lbl_notes) back_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.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 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 @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"]) # 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) # 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 @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()