added Source Context and Tags input to review_tab
This commit is contained in:
parent
89e93dd467
commit
c302e74c20
1 changed files with 118 additions and 165 deletions
|
|
@ -2,7 +2,8 @@
|
||||||
import random
|
import random
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
QPushButton, QFrame, QStackedWidget
|
QLineEdit, QPushButton, QTableWidget, QTableWidgetItem,
|
||||||
|
QHeaderView, QFormLayout, QMessageBox
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, pyqtSlot
|
from PyQt6.QtCore import Qt, pyqtSlot
|
||||||
import database
|
import database
|
||||||
|
|
@ -11,191 +12,143 @@ class ReviewTab(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
# Core State Variables
|
# Master cache of records loaded from the database
|
||||||
self.review_pool = []
|
self.all_cached_records = []
|
||||||
self.current_index = -1
|
|
||||||
|
|
||||||
# Primary Layout
|
# Primary Layout
|
||||||
main_layout = QVBoxLayout(self)
|
main_layout = QVBoxLayout(self)
|
||||||
main_layout.setContentsMargins(30, 20, 30, 20)
|
main_layout.setContentsMargins(30, 20, 30, 20)
|
||||||
main_layout.setSpacing(20)
|
main_layout.setSpacing(15)
|
||||||
|
|
||||||
# Header Status Tracker
|
# --- SECTION 1: TOP REGION (Source Context & Tags) ---
|
||||||
self.lbl_status = QLabel("Session Status: No active cards loaded.")
|
top_container = QWidget()
|
||||||
self.lbl_status.setStyleSheet("font-size: 13px; font-weight: bold; color: #7F8C8D; letter-spacing: 0.5px;")
|
top_layout = QFormLayout(top_container)
|
||||||
main_layout.addWidget(self.lbl_status)
|
top_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
|
||||||
|
top_layout.setSpacing(10)
|
||||||
|
|
||||||
# --- THE CARD CANVAS AREA ---
|
self.txt_review_context = QLineEdit()
|
||||||
self.card_frame = QFrame()
|
self.txt_review_context.setPlaceholderText("Filter deck by context (e.g., Camino 2027)...")
|
||||||
self.card_frame.setStyleSheet("""
|
self.txt_review_context.setStyleSheet("padding: 6px; font-size: 14px;")
|
||||||
QFrame {
|
self.txt_review_context.textChanged.connect(self.handle_live_filter)
|
||||||
background-color: #FAFAFA;
|
|
||||||
border: 2px solid #E5E7E9;
|
self.txt_review_tags = QLineEdit()
|
||||||
border-radius: 8px;
|
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("<b>Source Context:</b>"), self.txt_review_context)
|
||||||
|
top_layout.addRow(QLabel("<b>Tags:</b>"), 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)
|
self.btn_generate_deck.clicked.connect(self.generate_deck_action)
|
||||||
card_layout.setContentsMargins(40, 40, 40, 40)
|
|
||||||
|
|
||||||
# Stacked display interface separating Question and Answer card views
|
self.btn_generate_video = QPushButton("🎬 Generate Video")
|
||||||
self.card_stack = QStackedWidget()
|
self.btn_generate_video.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
|
self.btn_generate_video.setStyleSheet("""
|
||||||
# View A: Card Front Layout (Prompt and Source Language Text)
|
QPushButton {
|
||||||
self.view_front = QWidget()
|
background-color: #2980B9;
|
||||||
front_layout = QVBoxLayout(self.view_front)
|
color: white;
|
||||||
front_prompt = QLabel("TRANSLATE THIS TO SPANISH:")
|
font-weight: bold;
|
||||||
front_prompt.setStyleSheet("font-size: 12px; font-weight: bold; color: #BDC3C7; letter-spacing: 1px;")
|
font-size: 14px;
|
||||||
front_prompt.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
padding: 10px 22px;
|
||||||
|
border-radius: 5px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
QPushButton:hover { background-color: #1F618D; }
|
||||||
""")
|
""")
|
||||||
self.lbl_notes.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.btn_generate_video.clicked.connect(self.generate_video_action)
|
||||||
self.lbl_notes.setWordWrap(True)
|
|
||||||
|
|
||||||
back_layout.addWidget(self.lbl_spanish)
|
bottom_layout.addWidget(self.btn_generate_deck)
|
||||||
back_layout.addWidget(self.lbl_notes)
|
bottom_layout.addWidget(self.btn_generate_video)
|
||||||
back_layout.addStretch()
|
bottom_layout.addStretch()
|
||||||
|
|
||||||
# Mount the structural views into the execution layer index
|
main_layout.addLayout(bottom_layout)
|
||||||
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)
|
# Populate initial table layout on startup
|
||||||
|
|
||||||
# --- 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()
|
self.reload_review_pool()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def reload_review_pool(self):
|
def reload_review_pool(self):
|
||||||
"""Fetches consolidated text entries from the database module and scrambles their indexing."""
|
"""Fetches consolidated text entries from the database and initializes cache."""
|
||||||
raw_records = database.get_all_translations_explicit()
|
try:
|
||||||
|
self.all_cached_records = database.get_all_translations_explicit()
|
||||||
# Filter down records to ensure they possess safe core text parameters
|
self.handle_live_filter()
|
||||||
self.review_pool = [r for r in raw_records if r["es_text"] and r["en_text"]]
|
except Exception as e:
|
||||||
|
print(f"Error initializing flashcard display: {e}")
|
||||||
# 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()
|
@pyqtSlot()
|
||||||
def reveal_card_answer(self):
|
def handle_live_filter(self):
|
||||||
"""Displays translation targets on the card back."""
|
"""Filters the display table row contents matching current Context and Tags criteria."""
|
||||||
if not (0 <= self.current_index < len(self.review_pool)):
|
self.table.blockSignals(True)
|
||||||
return
|
self.table.setRowCount(0)
|
||||||
|
|
||||||
record = self.review_pool[self.current_index]
|
filter_ctx = self.txt_review_context.text().lower().strip()
|
||||||
self.lbl_spanish.setText(record["es_text"])
|
filter_tag = self.txt_review_tags.text().lower().strip()
|
||||||
|
|
||||||
# Show contextual notes or structural flags cleanly if populated
|
visible_row_index = 0
|
||||||
if record["notes"] or record["source_context"]:
|
for row in self.all_cached_records:
|
||||||
context_string = record["source_context"] if record["source_context"] else ""
|
val_ctx = (row["source_context"] or "").lower()
|
||||||
notes_string = f" | {record['notes']}" if record["notes"] else ""
|
val_tag = (row["tags"] or "").lower()
|
||||||
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
|
# Show row if it satisfies both filter boxes
|
||||||
self.card_stack.setCurrentIndex(1) # Swap card panel over to the back answer layout
|
if (filter_ctx in val_ctx) and (filter_tag in val_tag):
|
||||||
self.control_stack.setCurrentIndex(1) # Swap button layouts over to display 'Next Phrase' action bars
|
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()
|
@pyqtSlot()
|
||||||
def advance_review_index(self):
|
def generate_deck_action(self):
|
||||||
"""Increments index counters to display a new flashcard container."""
|
"""Placeholder function execution trigger for processing deck compiler passes."""
|
||||||
if not self.review_pool:
|
QMessageBox.information(
|
||||||
return
|
self,
|
||||||
|
"Deck Compiler Active",
|
||||||
|
f"Compiling a custom Anki training deck container using the {self.table.rowCount()} visible filtered rows."
|
||||||
|
)
|
||||||
|
|
||||||
self.current_index += 1
|
@pyqtSlot()
|
||||||
|
def generate_video_action(self):
|
||||||
# Loop review cards continuously if the session index boundary thresholds overflow
|
"""Placeholder function execution trigger for media compiler production automation."""
|
||||||
if self.current_index >= len(self.review_pool):
|
QMessageBox.information(
|
||||||
self.current_index = 0
|
self,
|
||||||
random.shuffle(self.review_pool) # Re-scramble deck upon completing the pass loop
|
"Media Generator Active",
|
||||||
|
f"Initiating background video asset production using the {self.table.rowCount()} visible phrases."
|
||||||
self.display_current_card_front()
|
)
|
||||||
Loading…
Reference in a new issue