Initial Codes
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user