diff --git a/database.py b/database.py index 1718b67..301cf61 100644 --- a/database.py +++ b/database.py @@ -149,4 +149,17 @@ def delete_translation_record(translation_id): """, (translation_id,)) conn.commit() finally: - conn.close() \ No newline at end of file + conn.close() + +def insert_translation_record(es_text, en_text, source_context, tags, notes): + """Inserts a completely fresh record into the translations table.""" + conn = get_connection() + cursor = conn.cursor() + try: + cursor.execute(""" + INSERT INTO translations (es_text, en_text, source_context, tags, notes) + VALUES (?, ?, ?, ?, ?); + """, (es_text, en_text, source_context, tags, notes)) + conn.commit() + finally: + conn.close() \ No newline at end of file diff --git a/spanish_trainer.db b/spanish_trainer.db index 71e66d5..301cac0 100644 Binary files a/spanish_trainer.db and b/spanish_trainer.db differ diff --git a/tabs/sandbox_tab.py b/tabs/sandbox_tab.py index b2b19db..6e3aa26 100644 --- a/tabs/sandbox_tab.py +++ b/tabs/sandbox_tab.py @@ -15,6 +15,9 @@ class SandboxTab(QWidget): def __init__(self, parent=None): super().__init__(parent) + # Internal tracking variable to distinguish edits vs new entries + self.selected_translation_id = None + # Main layout structure main_layout = QVBoxLayout(self) main_layout.setContentsMargins(30, 20, 30, 20) @@ -30,14 +33,17 @@ class SandboxTab(QWidget): self.txt_english = QLineEdit() self.txt_english.setPlaceholderText("Enter English phrase or word...") self.txt_english.setStyleSheet("padding: 6px; font-size: 14px;") + self.txt_english.textChanged.connect(self.handle_live_filter) self.txt_spanish = QLineEdit() self.txt_spanish.setPlaceholderText("Introduce la frase en español...") self.txt_spanish.setStyleSheet("padding: 6px; font-size: 14px;") + self.txt_spanish.textChanged.connect(self.handle_live_filter) self.txt_context = QLineEdit() self.txt_context.setPlaceholderText("e.g., Camino 2027, Café, Market conversation...") self.txt_context.setStyleSheet("padding: 6px; font-size: 14px;") + self.txt_context.textChanged.connect(self.handle_live_filter) self.txt_notes = QLineEdit() self.txt_notes.setPlaceholderText("Grammar rules, formal vs informal nuances...") @@ -93,6 +99,7 @@ class SandboxTab(QWidget): # --- SECTION 3: DATA COMMIT CONTROL BAR --- control_layout = QHBoxLayout() + control_layout.setSpacing(15) self.btn_save = QPushButton("Save Translation Record") self.btn_save.setCursor(Qt.CursorShape.PointingHandCursor) @@ -109,6 +116,21 @@ class SandboxTab(QWidget): """) self.btn_save.clicked.connect(self.commit_translation_record) + self.btn_new_record = QPushButton("Create New Record") + self.btn_new_record.setCursor(Qt.CursorShape.PointingHandCursor) + self.btn_new_record.setStyleSheet(""" + QPushButton { + background-color: #27AE60; + color: white; + font-weight: bold; + font-size: 14px; + padding: 10px 20px; + border-radius: 5px; + } + QPushButton:hover { background-color: #219653; } + """) + self.btn_new_record.clicked.connect(self.prepare_for_new_record) + self.btn_clear = QPushButton("Clear Fields") self.btn_clear.setCursor(Qt.CursorShape.PointingHandCursor) self.btn_clear.setStyleSheet(""" @@ -122,9 +144,10 @@ class SandboxTab(QWidget): } QPushButton:hover { background-color: #95A5A6; } """) - self.btn_clear.clicked.connect(self.clear_input_fields) + self.btn_clear.clicked.connect(self.clear_all_fields_manually) control_layout.addWidget(self.btn_save) + control_layout.addWidget(self.btn_new_record) control_layout.addWidget(self.btn_clear) control_layout.addStretch() @@ -137,6 +160,9 @@ class SandboxTab(QWidget): self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) + # Wire up row selection change signals to auto-populate form + self.table.itemSelectionChanged.connect(self.handle_row_selection) + # Tweak display headers to scale nicely header = self.table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) @@ -147,7 +173,8 @@ class SandboxTab(QWidget): main_layout.addWidget(self.table) - # Populate live view from storage layout tracking engines + # Master cache of unfiltered row records to enable instant filtering loops + self.all_cached_records = [] self.reload_table_display() @pyqtSlot() @@ -164,56 +191,181 @@ class SandboxTab(QWidget): if text: subprocess.Popen(["say", "-v", "Monica", text]) + @pyqtSlot() + def handle_live_filter(self): + """Filters the visible database entries based on current text input criteria.""" + # Temporary block signals to prevent selection loops from fighting text changes + self.table.blockSignals(True) + + filter_en = self.txt_english.text().lower().strip() + filter_es = self.txt_spanish.text().lower().strip() + filter_ctx = self.txt_context.text().lower().strip() + + self.table.setRowCount(0) + visible_row_index = 0 + + for row in self.all_cached_records: + val_en = (row["en_text"] or "").lower() + val_es = (row["es_text"] or "").lower() + val_ctx = (row["source_context"] or "").lower() + + # Look for matches across English, Spanish, and Source Context + if (filter_en in val_en) and (filter_es in val_es) and (filter_ctx in val_ctx): + self.table.insertRow(visible_row_index) + + item_id = QTableWidgetItem(str(row["translation_id"])) + item_en = QTableWidgetItem(row["en_text"]) + item_es = QTableWidgetItem(row["es_text"]) + item_ctx = QTableWidgetItem(row["source_context"] or "") + item_nts = QTableWidgetItem(row["notes"] or "") + + item_id.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + + self.table.setItem(visible_row_index, 0, item_id) + self.table.setItem(visible_row_index, 1, item_en) + self.table.setItem(visible_row_index, 2, item_es) + self.table.setItem(visible_row_index, 3, item_ctx) + self.table.setItem(visible_row_index, 4, item_nts) + + # If we have an active editing ID, highlight that specific row during re-renders + if self.selected_translation_id == row["translation_id"]: + self.table.selectRow(visible_row_index) + + visible_row_index += 1 + + self.table.blockSignals(False) + + @pyqtSlot() + def handle_row_selection(self): + """Populates the input forms when a user clicks a row in the table view.""" + selected_ranges = self.table.selectedRanges() + if not selected_ranges: + return + + row_idx = selected_ranges[0].topRow() + id_item = self.table.item(row_idx, 0) + if not id_item: + return + + target_id = int(id_item.text()) + + # Locate item match within memory cache store elements + record = next((r for r in self.all_cached_records if r["translation_id"] == target_id), None) + if record: + # Block line edit text tracking temporarily so populating fields doesn't trigger filter loops + self.txt_english.blockSignals(True) + self.txt_spanish.blockSignals(True) + self.txt_context.blockSignals(True) + self.txt_notes.blockSignals(True) + + self.selected_translation_id = record["translation_id"] + self.txt_english.setText(record["en_text"]) + self.txt_spanish.setText(record["es_text"]) + self.txt_context.setText(record["source_context"] or "") + self.txt_notes.setText(record["notes"] or "") + + self.txt_english.blockSignals(False) + self.txt_spanish.blockSignals(False) + self.txt_context.blockSignals(False) + self.txt_notes.blockSignals(False) + + @pyqtSlot() + def prepare_for_new_record(self): + """Clears selection state so next click on 'Save' inserts fresh rows without scrubbing fields.""" + self.selected_translation_id = None + self.table.blockSignals(True) + self.table.clearSelection() + self.table.blockSignals(False) + QMessageBox.information(self, "Status Shift", "Ready to insert a new record using the current field content.") + @pyqtSlot() def commit_translation_record(self): - """Extracts text metrics out of input wrappers and saves down to database engine storage.""" + """Saves current text blocks. Dynamically detects insert vs edit based on selections.""" en_text = self.txt_english.text().strip() es_text = self.txt_spanish.text().strip() context = self.txt_context.text().strip() notes = self.txt_notes.text().strip() + tags = "" # Default empty placeholder string to fit the backend DB method signature if not en_text or not es_text: QMessageBox.warning(self, "Validation Alert", "Both English and Spanish base text blocks are required.") return - # Call explicit writing handlers down to SQLite database layer - database.insert_translation_explicit(en_text, es_text, context, notes) - - # Wipe structural field items cleanly on completions loop - self.clear_input_fields() - - # Sync state out across the rest of the app window elements - self.reload_table_display() - self.data_mutated.emit() + try: + if self.selected_translation_id is not None: + # Direct match for database.update_translation_record signature: + # (translation_id, es_text, en_text, source_context, tags, notes) + database.update_translation_record( + self.selected_translation_id, es_text, en_text, context, tags, notes + ) + else: + # Direct match for database.insert_translation_record signature: + # (es_text, en_text, source_context, tags, notes) + database.insert_translation_record( + es_text, en_text, context, tags, notes + ) + + # Clear pointer state values on successful writing commits + self.selected_translation_id = None + + # Wipe inputs cleanly and sync UI layers + self.clear_input_fields() + self.reload_table_display() + self.data_mutated.emit() + + except Exception as e: + QMessageBox.critical(self, "Database Commit Safeguard", f"Failed writing database operations:\n{str(e)}") @pyqtSlot() + def clear_all_fields_manually(self): + """Clears explicit states alongside visual row highlights simultaneously.""" + self.selected_translation_id = None + self.table.blockSignals(True) + self.table.clearSelection() + self.table.blockSignals(False) + self.clear_input_fields() + self.reload_table_display() + def clear_input_fields(self): - """Flushes transient cache items out of form line elements.""" + """Flushes transient text inside line editors without running filtering rules.""" + self.txt_english.blockSignals(True) + self.txt_spanish.blockSignals(True) + self.txt_context.blockSignals(True) + self.txt_notes.blockSignals(True) + self.txt_english.clear() self.txt_spanish.clear() self.txt_context.clear() self.txt_notes.clear() + + self.txt_english.blockSignals(False) + self.txt_spanish.blockSignals(False) + self.txt_context.blockSignals(False) + self.txt_notes.blockSignals(False) def reload_table_display(self): """Refetches database rows and populates the master dashboard grid view.""" + self.table.blockSignals(True) self.table.setRowCount(0) - records = database.get_all_translations_explicit() - for idx, row in enumerate(records): + # Keep internal reference arrays synced cleanly + self.all_cached_records = database.get_all_translations_explicit() + + for idx, row in enumerate(self.all_cached_records): self.table.insertRow(idx) - # Form clean mapping cell entities item_id = QTableWidgetItem(str(row["translation_id"])) item_en = QTableWidgetItem(row["en_text"]) item_es = QTableWidgetItem(row["es_text"]) item_ctx = QTableWidgetItem(row["source_context"] or "") item_nts = QTableWidgetItem(row["notes"] or "") - # Align center the ID key indices item_id.setTextAlignment(Qt.AlignmentFlag.AlignCenter) self.table.setItem(idx, 0, item_id) self.table.setItem(idx, 1, item_en) self.table.setItem(idx, 2, item_es) self.table.setItem(idx, 3, item_ctx) - self.table.setItem(idx, 4, item_nts) \ No newline at end of file + self.table.setItem(idx, 4, item_nts) + + self.table.blockSignals(False) \ No newline at end of file