Initial Codes

This commit is contained in:
2026-05-21 15:46:41 -04:00
commit b01ad5ea40
40 changed files with 9102 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
"""
Results panel — shows query result data, DML messages, errors, and export controls.
"""
import os
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTableView, QLabel, QPushButton,
QHeaderView, QAbstractItemView, QFileDialog, QMessageBox, QStackedWidget,
QPlainTextEdit, QProgressBar,
)
from PyQt6.QtCore import Qt, pyqtSignal, QSortFilterProxyModel
from PyQt6.QtGui import QColor, QFont
from app.models.result_table_model import ResultTableModel
class ResultsPanel(QWidget):
status_message = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._model = ResultTableModel()
self._build_ui()
# ── UI ────────────────────────────────────────────────────────────────────
def _build_ui(self):
root = QVBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
# ── Status / toolbar ─────────────────────────────────────────────────
self._toolbar = QHBoxLayout()
self._toolbar.setContentsMargins(8, 4, 8, 4)
self._toolbar.setSpacing(8)
self._status_lbl = QLabel("Ready")
self._status_lbl.setObjectName("statusLabel")
self._export_csv = QPushButton("⬇ CSV")
self._export_json = QPushButton("⬇ JSON")
self._export_sql = QPushButton("⬇ SQL")
self._export_csv.setFixedWidth(72)
self._export_json.setFixedWidth(72)
self._export_sql.setFixedWidth(72)
self._export_csv.clicked.connect(self._do_export_csv)
self._export_json.clicked.connect(self._do_export_json)
self._export_sql.clicked.connect(self._do_export_sql)
self._toolbar.addWidget(self._status_lbl, 1)
self._toolbar.addWidget(self._export_csv)
self._toolbar.addWidget(self._export_json)
self._toolbar.addWidget(self._export_sql)
root.addLayout(self._toolbar)
# ── Stacked pages ─────────────────────────────────────────────────────
self._stack = QStackedWidget()
# Page 0 — table
self._table = QTableView()
self._proxy = QSortFilterProxyModel()
self._proxy.setSourceModel(self._model)
self._table.setModel(self._proxy)
self._table.setSortingEnabled(True)
self._table.setAlternatingRowColors(True)
self._table.setSelectionBehavior(
QAbstractItemView.SelectionBehavior.SelectItems)
self._table.setSelectionMode(
QAbstractItemView.SelectionMode.ExtendedSelection)
self._table.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Interactive)
self._table.horizontalHeader().setStretchLastSection(True)
self._table.verticalHeader().setDefaultSectionSize(24)
self._table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self._table.customContextMenuRequested.connect(self._table_context_menu)
self._stack.addWidget(self._table) # idx 0
# Page 1 — message / log
self._msg_view = QPlainTextEdit()
self._msg_view.setReadOnly(True)
self._msg_view.setFont(QFont("Consolas", 11))
self._stack.addWidget(self._msg_view) # idx 1
# Page 2 — loading spinner
loading = QWidget()
ll = QVBoxLayout(loading)
ll.setAlignment(Qt.AlignmentFlag.AlignCenter)
bar = QProgressBar()
bar.setMaximum(0)
bar.setFixedWidth(200)
ll.addWidget(QLabel("Executing query…"))
ll.addWidget(bar)
self._stack.addWidget(loading) # idx 2
root.addWidget(self._stack, 1)
self._set_export_visible(False)
# ── Public API ────────────────────────────────────────────────────────────
def show_loading(self):
self._model.clear()
self._stack.setCurrentIndex(2)
self._status_lbl.setText("Running…")
self._set_export_visible(False)
def show_data(self, cols: list, rows: list, count: int, elapsed: float):
self._model.set_data(cols, rows)
self._stack.setCurrentIndex(0)
t = f"{elapsed:.3f}s" if elapsed else ""
self._status_lbl.setText(f"{count:,} row(s) {t}")
self.status_message.emit(f"Fetched {count:,} rows {t}")
self._set_export_visible(bool(cols))
self._auto_resize()
def show_message(self, msg: str):
self._msg_view.appendPlainText(msg)
self._stack.setCurrentIndex(1)
self._status_lbl.setText(msg)
self._set_export_visible(False)
def show_error(self, msg: str):
self._msg_view.setPlainText(f"{msg}")
self._stack.setCurrentIndex(1)
self._status_lbl.setText(f"Error: {msg[:80]}")
self.status_message.emit(f"Error: {msg[:80]}")
self._set_export_visible(False)
def export_dialog(self):
self._do_export_csv()
# ── Private helpers ───────────────────────────────────────────────────────
def _set_export_visible(self, v: bool):
self._export_csv.setVisible(v)
self._export_json.setVisible(v)
self._export_sql.setVisible(v)
def _auto_resize(self):
header = self._table.horizontalHeader()
for i in range(self._model.columnCount()):
header.resizeSection(
i, min(self._table.columnWidth(i) + 20, 300)
)
self._table.resizeColumnsToContents()
def _do_export_csv(self):
path, _ = QFileDialog.getSaveFileName(
self, "Export CSV", "results.csv", "CSV Files (*.csv)")
if path:
try:
self._model.export_csv(path)
QMessageBox.information(self, "Exported", f"Saved to {path}")
except Exception as e:
QMessageBox.critical(self, "Error", str(e))
def _do_export_json(self):
path, _ = QFileDialog.getSaveFileName(
self, "Export JSON", "results.json", "JSON Files (*.json)")
if path:
try:
self._model.export_json(path)
QMessageBox.information(self, "Exported", f"Saved to {path}")
except Exception as e:
QMessageBox.critical(self, "Error", str(e))
def _do_export_sql(self):
path, _ = QFileDialog.getSaveFileName(
self, "Export SQL", "results.sql", "SQL Files (*.sql)")
if path:
try:
self._model.export_sql(path)
QMessageBox.information(self, "Exported", f"Saved to {path}")
except Exception as e:
QMessageBox.critical(self, "Error", str(e))
def _table_context_menu(self, pos):
from PyQt6.QtWidgets import QMenu
from PyQt6.QtGui import QClipboard
from PyQt6.QtWidgets import QApplication
idx = self._table.indexAt(pos)
if not idx.isValid():
return
menu = QMenu(self)
menu.addAction("📋 Copy cell", lambda: self._copy_cell(idx))
menu.addAction("📋 Copy row", lambda: self._copy_row(idx))
menu.addAction("📋 Copy all", lambda: self._copy_all())
menu.exec(self._table.viewport().mapToGlobal(pos))
def _copy_cell(self, idx):
from PyQt6.QtWidgets import QApplication
val = self._proxy.data(idx, Qt.ItemDataRole.DisplayRole) or ""
QApplication.clipboard().setText(str(val))
def _copy_row(self, idx):
from PyQt6.QtWidgets import QApplication
row = self._proxy.mapToSource(idx).row()
vals = [str(v or "") for v in self._model.get_row(row)]
QApplication.clipboard().setText("\t".join(vals))
def _copy_all(self):
from PyQt6.QtWidgets import QApplication
import csv, io
buf = io.StringIO()
writer = csv.writer(buf)
writer.writerow(self._model.column_names())
for r in range(self._model.rowCount()):
writer.writerow(self._model.get_row(r))
QApplication.clipboard().setText(buf.getvalue())