feat: shortcuts dialog, tab context menus, smart status bar, recent files
- ShortcutsDialog: grouped tree of all keyboard shortcuts (Help menu) - Workspace tab context menu: Rename, Close, Close Others, Close to Right - SQL query tab context menu: Rename, Duplicate, Close, Close Others - Smart status bar: right-side connection indicator + selected cell preview - File → Open SQL File (Ctrl+O), Save (Ctrl+S), Save As, Open Recent - Recent files persisted to ~/.dbclient/recent_files.json (last 10) - TableViewer.cell_selected signal wired to status bar cell preview - EditorTab._filepath tracks the file associated with each query tab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPlainTextEdit, QTextEdit,
|
||||
QTabWidget, QPushButton, QLabel, QSplitter, QTabBar, QSizePolicy,
|
||||
QFileDialog, QMessageBox, QToolButton, QComboBox, QLineEdit, QCheckBox,
|
||||
QMenu, QInputDialog,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QRect, QSize, pyqtSignal, QTimer
|
||||
from PyQt6.QtGui import (
|
||||
@@ -404,6 +405,7 @@ class EditorTab(QWidget):
|
||||
self._driver = driver
|
||||
self._database = database
|
||||
self._worker: QueryWorker | None = None
|
||||
self._filepath: str | None = None
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self):
|
||||
@@ -582,6 +584,14 @@ class SQLEditorWidget(QWidget):
|
||||
new_btn.clicked.connect(lambda: self.new_tab())
|
||||
self._tabs.setCornerWidget(new_btn, Qt.Corner.TopRightCorner)
|
||||
|
||||
# Right-click context menu on query tab bar
|
||||
self._tabs.tabBar().setContextMenuPolicy(
|
||||
Qt.ContextMenuPolicy.CustomContextMenu
|
||||
)
|
||||
self._tabs.tabBar().customContextMenuRequested.connect(
|
||||
self._tab_context_menu
|
||||
)
|
||||
|
||||
root.addWidget(self._tabs)
|
||||
|
||||
def new_tab(self, driver=None, database: str = "",
|
||||
@@ -599,6 +609,44 @@ class SQLEditorWidget(QWidget):
|
||||
if self._tabs.count() > 1:
|
||||
self._tabs.removeTab(idx)
|
||||
|
||||
def _tab_context_menu(self, pos) -> None:
|
||||
idx = self._tabs.tabBar().tabAt(pos)
|
||||
if idx < 0:
|
||||
return
|
||||
menu = QMenu(self)
|
||||
rename_act = menu.addAction("Rename…")
|
||||
dup_act = menu.addAction("Duplicate")
|
||||
menu.addSeparator()
|
||||
close_act = menu.addAction("Close")
|
||||
close_others = menu.addAction("Close Others")
|
||||
|
||||
close_act.setEnabled(self._tabs.count() > 1)
|
||||
close_others.setEnabled(self._tabs.count() > 1)
|
||||
|
||||
act = menu.exec(self._tabs.tabBar().mapToGlobal(pos))
|
||||
if act == rename_act:
|
||||
text, ok = QInputDialog.getText(
|
||||
self, "Rename Tab", "Tab name:",
|
||||
text=self._tabs.tabText(idx),
|
||||
)
|
||||
if ok and text.strip():
|
||||
self._tabs.setTabText(idx, text.strip())
|
||||
elif act == dup_act:
|
||||
tab = self._tabs.widget(idx)
|
||||
if isinstance(tab, EditorTab):
|
||||
new = self.new_tab(
|
||||
tab._driver, tab._database,
|
||||
sql=tab.get_sql(),
|
||||
title=self._tabs.tabText(idx) + " (copy)",
|
||||
)
|
||||
new._filepath = tab._filepath
|
||||
elif act == close_act:
|
||||
self._close_tab(idx)
|
||||
elif act == close_others:
|
||||
for i in range(self._tabs.count() - 1, -1, -1):
|
||||
if i != idx:
|
||||
self._close_tab(i)
|
||||
|
||||
def current_tab(self) -> EditorTab | None:
|
||||
w = self._tabs.currentWidget()
|
||||
return w if isinstance(w, EditorTab) else None
|
||||
|
||||
Reference in New Issue
Block a user