polish: row numbers, ping indicator, frozen column, keyboard nav
- ResultsPanel: _RowNumberProxy keeps vertical header numbers sequential in visual order (sort-stable); both views get a 48 px wide row-number header - MainWindow: _PingWorker pings each active connection every 30 s via test_connection(); status dot (green/red) shown in status bar with tooltip - TableViewer: 📌 toggle freezes first column using dual-view overlay (_frozen_view child widget) with synced scroll, row heights, and resize event-filter; column 0 is hidden in the main view while frozen - RowDialog: OK set as default button; first field focused on open - ConnectionDialog: OK set as default; Enter in password field submits Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -140,6 +140,10 @@ All four drivers have a `_where(where: dict) → (clause_str, params)` static he
|
|||||||
- File open/save/recent: Ctrl+O open SQL file, Ctrl+S save, File → Open Recent (persisted to `~/.dbclient/recent_files.json`, max 10)
|
- File open/save/recent: Ctrl+O open SQL file, Ctrl+S save, File → Open Recent (persisted to `~/.dbclient/recent_files.json`, max 10)
|
||||||
- Ctrl+W to close current workspace tab
|
- Ctrl+W to close current workspace tab
|
||||||
- Catppuccin Frappé theme: full palette upgrade from Mocha; all colors updated in `resources/style.qss`, syntax highlighter, completer popup, and inline Python stylesheets
|
- Catppuccin Frappé theme: full palette upgrade from Mocha; all colors updated in `resources/style.qss`, syntax highlighter, completer popup, and inline Python stylesheets
|
||||||
|
- Row numbers column in results grid: `_RowNumberProxy` shows sequential visual-order numbers in the vertical header (sort-stable); fixed 48 px wide header in both `ResultsPanel` and `TableViewer`
|
||||||
|
- Connection ping indicator: 30-second `_PingWorker` (QThread) pings each active connection via `test_connection()`; status dot (● green / ● red) shown in status bar right side with per-connection tooltip
|
||||||
|
- Frozen first column in `TableViewer`: 📌 toggle button in toolbar; dual-view overlay (`_frozen_view` child of `_table_view`) with synced vertical scroll and row heights; event-filter updates geometry on resize
|
||||||
|
- Keyboard navigation in dialogs: OK set as default button (Enter submits) in `RowDialog` and `ConnectionDialog`; focus jumps to first input field on open; Enter in password field submits `ConnectionDialog`
|
||||||
|
|
||||||
### Not Yet Implemented
|
### Not Yet Implemented
|
||||||
**High impact:**
|
**High impact:**
|
||||||
@@ -153,9 +157,3 @@ All four drivers have a `_where(where: dict) → (clause_str, params)` static he
|
|||||||
- Global schema search: search across all tables/columns/procedures in a database
|
- Global schema search: search across all tables/columns/procedures in a database
|
||||||
- Light theme: Catppuccin Latte or similar, toggle from Preferences
|
- Light theme: Catppuccin Latte or similar, toggle from Preferences
|
||||||
- Schema diff: compare two database schemas and show structural differences
|
- Schema diff: compare two database schemas and show structural differences
|
||||||
|
|
||||||
**Small polish:**
|
|
||||||
- Row numbers column in results grid (virtual, not from DB)
|
|
||||||
- Connection ping indicator: periodic heartbeat dot in tab / status bar
|
|
||||||
- Frozen / pinned columns in table viewer (keep PK column always visible while scrolling)
|
|
||||||
- Keyboard navigation in RowDialog / connection dialogs (Tab order, Enter to confirm)
|
|
||||||
|
|||||||
+71
-1
@@ -15,7 +15,7 @@ from PyQt6.QtWidgets import (
|
|||||||
QTabWidget, QStatusBar, QLabel, QMessageBox, QDockWidget,
|
QTabWidget, QStatusBar, QLabel, QMessageBox, QDockWidget,
|
||||||
QPushButton, QApplication, QMenu, QInputDialog, QFileDialog,
|
QPushButton, QApplication, QMenu, QInputDialog, QFileDialog,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal
|
||||||
from PyQt6.QtGui import QAction, QColor, QKeySequence
|
from PyQt6.QtGui import QAction, QColor, QKeySequence
|
||||||
|
|
||||||
from app.ui.schema_browser import SchemaBrowser
|
from app.ui.schema_browser import SchemaBrowser
|
||||||
@@ -41,6 +41,23 @@ from app.utils.logger import get_logger
|
|||||||
_log = get_logger(__name__)
|
_log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class _PingWorker(QThread):
|
||||||
|
"""Background thread that pings one connection and emits the result."""
|
||||||
|
pinged = pyqtSignal(str, bool) # (profile_id, ok)
|
||||||
|
|
||||||
|
def __init__(self, pid: str, driver, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._pid = pid
|
||||||
|
self._driver = driver
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
ok, _ = self._driver.test_connection()
|
||||||
|
self.pinged.emit(self._pid, ok)
|
||||||
|
except Exception:
|
||||||
|
self.pinged.emit(self._pid, False)
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -53,11 +70,19 @@ class MainWindow(QMainWindow):
|
|||||||
self._active_drivers: dict = {}
|
self._active_drivers: dict = {}
|
||||||
# profile_id → ConnectionProfile (all loaded profiles, connected or not)
|
# profile_id → ConnectionProfile (all loaded profiles, connected or not)
|
||||||
self._all_profiles: dict = {}
|
self._all_profiles: dict = {}
|
||||||
|
# ping state
|
||||||
|
self._ping_results: dict = {} # profile_id → bool
|
||||||
|
self._ping_workers: list = [] # keep QThread refs alive
|
||||||
|
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
self._build_menus()
|
self._build_menus()
|
||||||
self._build_status_bar()
|
self._build_status_bar()
|
||||||
|
|
||||||
|
# Periodic connection heartbeat (every 30 s)
|
||||||
|
self._ping_timer = QTimer(self)
|
||||||
|
self._ping_timer.timeout.connect(self._ping_all_connections)
|
||||||
|
self._ping_timer.start(30_000)
|
||||||
|
|
||||||
# Load saved profiles after the window is shown
|
# Load saved profiles after the window is shown
|
||||||
QTimer.singleShot(0, self._load_saved_profiles)
|
QTimer.singleShot(0, self._load_saved_profiles)
|
||||||
|
|
||||||
@@ -234,6 +259,12 @@ class MainWindow(QMainWindow):
|
|||||||
self._conn_lbl.setMinimumWidth(160)
|
self._conn_lbl.setMinimumWidth(160)
|
||||||
sb.addPermanentWidget(self._conn_lbl)
|
sb.addPermanentWidget(self._conn_lbl)
|
||||||
|
|
||||||
|
self._ping_lbl = QLabel("")
|
||||||
|
self._ping_lbl.setFixedWidth(16)
|
||||||
|
self._ping_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self._ping_lbl.setToolTip("Connection heartbeat")
|
||||||
|
sb.addPermanentWidget(self._ping_lbl)
|
||||||
|
|
||||||
def _set_status(self, msg: str):
|
def _set_status(self, msg: str):
|
||||||
self._status_lbl.setText(msg)
|
self._status_lbl.setText(msg)
|
||||||
|
|
||||||
@@ -404,6 +435,7 @@ class MainWindow(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self._schema_browser.remove_connection(profile_id, keep_saved=False)
|
self._schema_browser.remove_connection(profile_id, keep_saved=False)
|
||||||
self._all_profiles.pop(profile_id, None)
|
self._all_profiles.pop(profile_id, None)
|
||||||
|
self._ping_results.pop(profile_id, None)
|
||||||
delete_profile(profile_id)
|
delete_profile(profile_id)
|
||||||
self._set_status(f"Connection '{profile.name}' deleted.")
|
self._set_status(f"Connection '{profile.name}' deleted.")
|
||||||
|
|
||||||
@@ -484,6 +516,44 @@ class MainWindow(QMainWindow):
|
|||||||
preview = value[:60] + ("…" if len(value) > 60 else "")
|
preview = value[:60] + ("…" if len(value) > 60 else "")
|
||||||
self._cell_lbl.setText(f" {preview}")
|
self._cell_lbl.setText(f" {preview}")
|
||||||
|
|
||||||
|
# ── Connection heartbeat ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _ping_all_connections(self):
|
||||||
|
# Drop finished workers
|
||||||
|
self._ping_workers = [w for w in self._ping_workers if w.isRunning()]
|
||||||
|
if not self._active_drivers:
|
||||||
|
self._ping_lbl.setText("")
|
||||||
|
return
|
||||||
|
for pid, driver in list(self._active_drivers.items()):
|
||||||
|
# Skip if a ping for this connection is already in flight
|
||||||
|
if any(getattr(w, "_pid", None) == pid for w in self._ping_workers):
|
||||||
|
continue
|
||||||
|
worker = _PingWorker(pid, driver)
|
||||||
|
worker.pinged.connect(self._on_ping_result)
|
||||||
|
worker.start()
|
||||||
|
self._ping_workers.append(worker)
|
||||||
|
|
||||||
|
def _on_ping_result(self, pid: str, ok: bool):
|
||||||
|
if pid not in self._active_drivers:
|
||||||
|
return
|
||||||
|
self._ping_results[pid] = ok
|
||||||
|
all_ok = all(self._ping_results.get(p, True) for p in self._active_drivers)
|
||||||
|
color = "#a6d189" if all_ok else "#e78284"
|
||||||
|
self._ping_lbl.setText("●")
|
||||||
|
self._ping_lbl.setStyleSheet(f"color: {color}; font-size: 11pt;")
|
||||||
|
lines = []
|
||||||
|
for p_id in self._active_drivers:
|
||||||
|
profile = self._all_profiles.get(p_id)
|
||||||
|
name = profile.name if profile else p_id
|
||||||
|
icon = "✅" if self._ping_results.get(p_id, True) else "❌"
|
||||||
|
lines.append(f"{icon} {name}")
|
||||||
|
self._ping_lbl.setToolTip("Connections:\n" + "\n".join(lines))
|
||||||
|
if not ok:
|
||||||
|
profile = self._all_profiles.get(pid)
|
||||||
|
name = profile.name if profile else pid
|
||||||
|
_log.warning("Heartbeat failed for '%s'", name)
|
||||||
|
self._set_status(f"⚠️ Connection heartbeat failed: {name}")
|
||||||
|
|
||||||
# ── File open / save ──────────────────────────────────────────────────────
|
# ── File open / save ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _open_sql_file(self, filepath: str = None):
|
def _open_sql_file(self, filepath: str = None):
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ class ConnectionDialog(QDialog):
|
|||||||
)
|
)
|
||||||
bbox.accepted.connect(self._accept)
|
bbox.accepted.connect(self._accept)
|
||||||
bbox.rejected.connect(self.reject)
|
bbox.rejected.connect(self.reject)
|
||||||
|
ok_btn = bbox.button(QDialogButtonBox.StandardButton.Ok)
|
||||||
|
if ok_btn:
|
||||||
|
ok_btn.setDefault(True)
|
||||||
|
|
||||||
btn_row = QHBoxLayout()
|
btn_row = QHBoxLayout()
|
||||||
btn_row.addWidget(self._test_btn)
|
btn_row.addWidget(self._test_btn)
|
||||||
@@ -167,6 +170,12 @@ class ConnectionDialog(QDialog):
|
|||||||
self._timeout.setSuffix(" sec")
|
self._timeout.setSuffix(" sec")
|
||||||
form.addRow("Timeout:", self._timeout)
|
form.addRow("Timeout:", self._timeout)
|
||||||
|
|
||||||
|
# Enter in password submits the dialog
|
||||||
|
self._password.returnPressed.connect(self._accept)
|
||||||
|
|
||||||
|
# Focus starts on the name field
|
||||||
|
self._name.setFocus()
|
||||||
|
|
||||||
return w
|
return w
|
||||||
|
|
||||||
def _build_ssl_tab(self) -> QWidget:
|
def _build_ssl_tab(self) -> QWidget:
|
||||||
|
|||||||
+10
-1
@@ -19,6 +19,14 @@ from PyQt6.QtGui import QColor, QFont
|
|||||||
from app.models.result_table_model import ResultTableModel
|
from app.models.result_table_model import ResultTableModel
|
||||||
|
|
||||||
|
|
||||||
|
class _RowNumberProxy(QSortFilterProxyModel):
|
||||||
|
"""Proxy that shows sequential visual-order row numbers in the vertical header."""
|
||||||
|
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
|
||||||
|
if orientation == Qt.Orientation.Vertical and role == Qt.ItemDataRole.DisplayRole:
|
||||||
|
return str(section + 1)
|
||||||
|
return super().headerData(section, orientation, role)
|
||||||
|
|
||||||
|
|
||||||
class ResultsPanel(QWidget):
|
class ResultsPanel(QWidget):
|
||||||
status_message = pyqtSignal(str)
|
status_message = pyqtSignal(str)
|
||||||
|
|
||||||
@@ -86,7 +94,7 @@ class ResultsPanel(QWidget):
|
|||||||
def _make_table_tab(self, cols: list, rows: list) -> tuple:
|
def _make_table_tab(self, cols: list, rows: list) -> tuple:
|
||||||
model = ResultTableModel()
|
model = ResultTableModel()
|
||||||
model.set_data(cols, rows)
|
model.set_data(cols, rows)
|
||||||
proxy = QSortFilterProxyModel()
|
proxy = _RowNumberProxy()
|
||||||
proxy.setSourceModel(model)
|
proxy.setSourceModel(model)
|
||||||
|
|
||||||
table = QTableView()
|
table = QTableView()
|
||||||
@@ -99,6 +107,7 @@ class ResultsPanel(QWidget):
|
|||||||
QHeaderView.ResizeMode.Interactive)
|
QHeaderView.ResizeMode.Interactive)
|
||||||
table.horizontalHeader().setStretchLastSection(True)
|
table.horizontalHeader().setStretchLastSection(True)
|
||||||
table.verticalHeader().setDefaultSectionSize(24)
|
table.verticalHeader().setDefaultSectionSize(24)
|
||||||
|
table.verticalHeader().setFixedWidth(48)
|
||||||
table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
table.customContextMenuRequested.connect(
|
table.customContextMenuRequested.connect(
|
||||||
lambda pos, t=table, p=proxy, m=model:
|
lambda pos, t=table, p=proxy, m=model:
|
||||||
|
|||||||
+97
-1
@@ -16,7 +16,7 @@ from PyQt6.QtWidgets import (
|
|||||||
QMenu, QApplication,
|
QMenu, QApplication,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
Qt, QAbstractTableModel, QModelIndex, pyqtSignal,
|
Qt, QAbstractTableModel, QModelIndex, pyqtSignal, QEvent,
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import QColor, QBrush, QFont, QKeySequence, QShortcut
|
from PyQt6.QtGui import QColor, QBrush, QFont, QKeySequence, QShortcut
|
||||||
|
|
||||||
@@ -267,8 +267,15 @@ class RowDialog(QDialog):
|
|||||||
)
|
)
|
||||||
bbox.accepted.connect(self.accept)
|
bbox.accepted.connect(self.accept)
|
||||||
bbox.rejected.connect(self.reject)
|
bbox.rejected.connect(self.reject)
|
||||||
|
ok_btn = bbox.button(QDialogButtonBox.StandardButton.Ok)
|
||||||
|
if ok_btn:
|
||||||
|
ok_btn.setDefault(True)
|
||||||
root.addWidget(bbox)
|
root.addWidget(bbox)
|
||||||
|
|
||||||
|
# Focus the first input field so the user can start typing immediately
|
||||||
|
if self._fields:
|
||||||
|
next(iter(self._fields.values())).setFocus()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def values(self) -> dict:
|
def values(self) -> dict:
|
||||||
"""Return {col: value_or_None} for all fields.
|
"""Return {col: value_or_None} for all fields.
|
||||||
@@ -380,6 +387,13 @@ class TableViewer(QWidget):
|
|||||||
tb.addWidget(self._edit_btn)
|
tb.addWidget(self._edit_btn)
|
||||||
tb.addWidget(self._delete_btn)
|
tb.addWidget(self._delete_btn)
|
||||||
tb.addStretch()
|
tb.addStretch()
|
||||||
|
|
||||||
|
self._freeze_btn = QPushButton("📌")
|
||||||
|
self._freeze_btn.setFixedWidth(34)
|
||||||
|
self._freeze_btn.setToolTip("Freeze first column (keep it visible while scrolling)")
|
||||||
|
self._freeze_btn.setCheckable(True)
|
||||||
|
self._freeze_btn.clicked.connect(self._toggle_freeze)
|
||||||
|
tb.addWidget(self._freeze_btn)
|
||||||
root.addLayout(tb)
|
root.addLayout(tb)
|
||||||
|
|
||||||
# ── Table view ─────────────────────────────────────────────────────────
|
# ── Table view ─────────────────────────────────────────────────────────
|
||||||
@@ -395,6 +409,7 @@ class TableViewer(QWidget):
|
|||||||
QHeaderView.ResizeMode.Interactive)
|
QHeaderView.ResizeMode.Interactive)
|
||||||
self._table_view.horizontalHeader().setStretchLastSection(True)
|
self._table_view.horizontalHeader().setStretchLastSection(True)
|
||||||
self._table_view.verticalHeader().setDefaultSectionSize(24)
|
self._table_view.verticalHeader().setDefaultSectionSize(24)
|
||||||
|
self._table_view.verticalHeader().setFixedWidth(48)
|
||||||
self._table_view.setContextMenuPolicy(
|
self._table_view.setContextMenuPolicy(
|
||||||
Qt.ContextMenuPolicy.CustomContextMenu)
|
Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self._table_view.customContextMenuRequested.connect(self._context_menu)
|
self._table_view.customContextMenuRequested.connect(self._context_menu)
|
||||||
@@ -415,6 +430,40 @@ class TableViewer(QWidget):
|
|||||||
self._delete_selected)
|
self._delete_selected)
|
||||||
QShortcut(QKeySequence("Ins"), self._table_view, self._add_row)
|
QShortcut(QKeySequence("Ins"), self._table_view, self._add_row)
|
||||||
|
|
||||||
|
# ── Frozen first-column overlay ───────────────────────────────────────
|
||||||
|
self._freeze_active = False
|
||||||
|
self._frozen_view = QTableView(self._table_view)
|
||||||
|
self._frozen_view.setModel(self._model)
|
||||||
|
self._frozen_view.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||||
|
self._frozen_view.setVerticalScrollBarPolicy(
|
||||||
|
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
self._frozen_view.setHorizontalScrollBarPolicy(
|
||||||
|
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
self._frozen_view.setAlternatingRowColors(True)
|
||||||
|
self._frozen_view.verticalHeader().hide()
|
||||||
|
self._frozen_view.horizontalHeader().setSectionResizeMode(
|
||||||
|
QHeaderView.ResizeMode.Fixed)
|
||||||
|
self._frozen_view.setStyleSheet(
|
||||||
|
"QTableView { border: none; border-right: 2px solid #8caaee; }")
|
||||||
|
self._frozen_view.hide()
|
||||||
|
|
||||||
|
# Sync vertical scroll between the two views
|
||||||
|
self._table_view.verticalScrollBar().valueChanged.connect(
|
||||||
|
self._frozen_view.verticalScrollBar().setValue)
|
||||||
|
self._frozen_view.verticalScrollBar().valueChanged.connect(
|
||||||
|
self._table_view.verticalScrollBar().setValue)
|
||||||
|
|
||||||
|
# Sync row heights
|
||||||
|
self._table_view.verticalHeader().sectionResized.connect(
|
||||||
|
lambda row, _, h: self._frozen_view.setRowHeight(row, h))
|
||||||
|
|
||||||
|
# Update frozen geometry when column 0 is resized
|
||||||
|
self._table_view.horizontalHeader().sectionResized.connect(
|
||||||
|
lambda col, _old, _new: self._update_frozen_geometry() if col == 0 else None)
|
||||||
|
|
||||||
|
# Event filter to handle main view resize
|
||||||
|
self._table_view.installEventFilter(self)
|
||||||
|
|
||||||
root.addWidget(self._table_view, 1)
|
root.addWidget(self._table_view, 1)
|
||||||
|
|
||||||
# ── Pagination bar (prominent, always visible) ─────────────────────────
|
# ── Pagination bar (prominent, always visible) ─────────────────────────
|
||||||
@@ -505,11 +554,58 @@ class TableViewer(QWidget):
|
|||||||
self._refresh_action_states()
|
self._refresh_action_states()
|
||||||
self._table_view.resizeColumnsToContents()
|
self._table_view.resizeColumnsToContents()
|
||||||
self._table_view.horizontalHeader().setStretchLastSection(True)
|
self._table_view.horizontalHeader().setStretchLastSection(True)
|
||||||
|
if self._freeze_active and self._model.columnCount() > 0:
|
||||||
|
self._apply_freeze()
|
||||||
|
|
||||||
def _on_error(self, msg: str):
|
def _on_error(self, msg: str):
|
||||||
self._total_lbl.setText(f"Error: {msg[:80]}")
|
self._total_lbl.setText(f"Error: {msg[:80]}")
|
||||||
self.status_message.emit(f"Error: {msg}")
|
self.status_message.emit(f"Error: {msg}")
|
||||||
|
|
||||||
|
# ── Frozen column ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _toggle_freeze(self, checked: bool):
|
||||||
|
self._freeze_active = checked
|
||||||
|
if checked and self._model.columnCount() > 0:
|
||||||
|
self._apply_freeze()
|
||||||
|
elif not checked:
|
||||||
|
self._unapply_freeze()
|
||||||
|
|
||||||
|
def _apply_freeze(self):
|
||||||
|
col_count = self._model.columnCount()
|
||||||
|
if col_count == 0:
|
||||||
|
return
|
||||||
|
self._frozen_view.setSelectionModel(self._table_view.selectionModel())
|
||||||
|
for c in range(col_count):
|
||||||
|
self._frozen_view.setColumnHidden(c, c != 0)
|
||||||
|
self._frozen_view.resizeColumnToContents(0)
|
||||||
|
self._table_view.setColumnHidden(0, True)
|
||||||
|
self._update_frozen_geometry()
|
||||||
|
self._frozen_view.show()
|
||||||
|
self._frozen_view.raise_()
|
||||||
|
|
||||||
|
def _unapply_freeze(self):
|
||||||
|
self._frozen_view.hide()
|
||||||
|
if self._model.columnCount() > 0:
|
||||||
|
self._table_view.setColumnHidden(0, False)
|
||||||
|
|
||||||
|
def _update_frozen_geometry(self):
|
||||||
|
if not self._freeze_active:
|
||||||
|
return
|
||||||
|
fw = self._table_view.frameWidth()
|
||||||
|
vh_w = self._table_view.verticalHeader().width()
|
||||||
|
col0_w = self._frozen_view.columnWidth(0)
|
||||||
|
self._frozen_view.setGeometry(
|
||||||
|
fw + vh_w,
|
||||||
|
fw,
|
||||||
|
col0_w,
|
||||||
|
self._table_view.height() - 2 * fw,
|
||||||
|
)
|
||||||
|
|
||||||
|
def eventFilter(self, obj, event):
|
||||||
|
if obj is self._table_view and event.type() == QEvent.Type.Resize:
|
||||||
|
self._update_frozen_geometry()
|
||||||
|
return False
|
||||||
|
|
||||||
def _refresh_pagination(self):
|
def _refresh_pagination(self):
|
||||||
ps = self._page_size if self._page_size > 0 else max(self._total, 1)
|
ps = self._page_size if self._page_size > 0 else max(self._total, 1)
|
||||||
rows_loaded = self._model.rowCount()
|
rows_loaded = self._model.rowCount()
|
||||||
|
|||||||
Reference in New Issue
Block a user