sqlbench 0.1.57__tar.gz → 0.1.59__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlbench-0.1.57 → sqlbench-0.1.59}/CLAUDE.md +2 -1
- {sqlbench-0.1.57 → sqlbench-0.1.59}/PKG-INFO +1 -1
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/connection_tree.py +24 -67
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/dialogs/connection_dialog.py +31 -9
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/main_window.py +15 -9
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/tabs/sql_tab.py +53 -1
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/version.py +1 -1
- {sqlbench-0.1.57 → sqlbench-0.1.59}/.github/workflows/pypi-publish.yml +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/.gitignore +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/Makefile +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/PYQT_MIGRATION_FEATURES.md +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/README.md +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/pyproject.toml +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/requirements.txt +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/__init__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/__main__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/adapters.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/app.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/database.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/dialogs/__init__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/dialogs/connection_dialog.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/launcher.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/__init__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/dialogs/__init__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/dialogs/query_manager_dialog.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/dialogs/record_viewer_dialog.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/dialogs/settings_dialog.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/icons.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/syntax.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/tab_widget.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/tabs/__init__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/tabs/spool_tab.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/qt/theme.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/resources/db_ibmi.png +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/resources/db_mysql.png +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/resources/db_postgresql.png +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/resources/db_unknown.png +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/resources/sqlbench.png +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/tabs/__init__.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/tabs/spool_tab.py +0 -0
- {sqlbench-0.1.57 → sqlbench-0.1.59}/sqlbench/tabs/sql_tab.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlbench
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.59
|
|
4
4
|
Summary: A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL
|
|
5
5
|
Project-URL: Homepage, https://github.com/jpsteil/sqlbench
|
|
6
6
|
Project-URL: Repository, https://github.com/jpsteil/sqlbench
|
|
@@ -38,8 +38,7 @@ class ConnectionTreeWidget(QWidget):
|
|
|
38
38
|
show_rows_requested = pyqtSignal(str, str, str) # connection, schema, table
|
|
39
39
|
edit_connection_requested = pyqtSignal(str) # connection_name
|
|
40
40
|
new_connection_requested = pyqtSignal()
|
|
41
|
-
connect_requested = pyqtSignal(str) # connection_name
|
|
42
|
-
disconnect_requested = pyqtSignal(str) # connection_name
|
|
41
|
+
connect_requested = pyqtSignal(str) # connection_name (auto-activate on expand)
|
|
43
42
|
|
|
44
43
|
def __init__(self, parent: Optional[QWidget] = None):
|
|
45
44
|
super().__init__(parent)
|
|
@@ -130,12 +129,6 @@ class ConnectionTreeWidget(QWidget):
|
|
|
130
129
|
"""Set up right-click context menu."""
|
|
131
130
|
self.context_menu = QMenu(self)
|
|
132
131
|
|
|
133
|
-
self.action_connect = QAction("Connect", self)
|
|
134
|
-
self.action_connect.triggered.connect(self._connect_selected)
|
|
135
|
-
|
|
136
|
-
self.action_disconnect = QAction("Disconnect", self)
|
|
137
|
-
self.action_disconnect.triggered.connect(self._disconnect_selected)
|
|
138
|
-
|
|
139
132
|
self.action_new_sql = QAction("New SQL", self)
|
|
140
133
|
self.action_new_sql.triggered.connect(self._new_sql_for_selected)
|
|
141
134
|
|
|
@@ -173,7 +166,9 @@ class ConnectionTreeWidget(QWidget):
|
|
|
173
166
|
self._connections_info[name] = conn
|
|
174
167
|
self._connected[name] = False
|
|
175
168
|
|
|
176
|
-
|
|
169
|
+
host = conn.get('host', '')
|
|
170
|
+
display_name = f"{name} - {host}" if host else name
|
|
171
|
+
item = QTreeWidgetItem([display_name])
|
|
177
172
|
item.setData(0, Qt.ItemDataRole.UserRole, {
|
|
178
173
|
'type': 'connection',
|
|
179
174
|
'name': name,
|
|
@@ -193,29 +188,31 @@ class ConnectionTreeWidget(QWidget):
|
|
|
193
188
|
return item
|
|
194
189
|
|
|
195
190
|
def set_connected(self, connection_name: str, connected: bool) -> None:
|
|
196
|
-
"""Update connection status."""
|
|
191
|
+
"""Update connection activation status."""
|
|
197
192
|
self._connected[connection_name] = connected
|
|
198
193
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if
|
|
205
|
-
item.setText(0, f"{connection_name} (connected)")
|
|
206
|
-
# Expand if this was pending from double-click
|
|
207
|
-
if hasattr(self, '_pending_expand') and self._pending_expand == item:
|
|
208
|
-
item.setExpanded(True)
|
|
209
|
-
self._pending_expand = None
|
|
210
|
-
else:
|
|
211
|
-
item.setText(0, connection_name)
|
|
212
|
-
# Clear children
|
|
194
|
+
if not connected:
|
|
195
|
+
# Clear cached schemas
|
|
196
|
+
for i in range(self.tree.topLevelItemCount()):
|
|
197
|
+
item = self.tree.topLevelItem(i)
|
|
198
|
+
data = item.data(0, Qt.ItemDataRole.UserRole)
|
|
199
|
+
if data and data.get('name') == connection_name:
|
|
213
200
|
item.takeChildren()
|
|
214
201
|
placeholder = QTreeWidgetItem(["Loading..."])
|
|
215
202
|
placeholder.setData(0, Qt.ItemDataRole.UserRole, {'type': 'placeholder'})
|
|
216
203
|
item.addChild(placeholder)
|
|
217
204
|
self._loaded_schemas[connection_name] = False
|
|
218
|
-
|
|
205
|
+
break
|
|
206
|
+
else:
|
|
207
|
+
# Expand if pending from expand attempt
|
|
208
|
+
if self._pending_expand:
|
|
209
|
+
for i in range(self.tree.topLevelItemCount()):
|
|
210
|
+
item = self.tree.topLevelItem(i)
|
|
211
|
+
data = item.data(0, Qt.ItemDataRole.UserRole)
|
|
212
|
+
if data and data.get('name') == connection_name and item == self._pending_expand:
|
|
213
|
+
item.setExpanded(True)
|
|
214
|
+
self._pending_expand = None
|
|
215
|
+
break
|
|
219
216
|
|
|
220
217
|
def _on_item_expanded(self, item: QTreeWidgetItem) -> None:
|
|
221
218
|
"""Handle item expansion - load children if needed."""
|
|
@@ -541,14 +538,7 @@ class ConnectionTreeWidget(QWidget):
|
|
|
541
538
|
|
|
542
539
|
if item_type == 'connection':
|
|
543
540
|
connection_name = data.get('name')
|
|
544
|
-
is_connected = self._connected.get(connection_name, False)
|
|
545
|
-
|
|
546
|
-
if is_connected:
|
|
547
|
-
menu.addAction(self.action_disconnect)
|
|
548
|
-
else:
|
|
549
|
-
menu.addAction(self.action_connect)
|
|
550
541
|
|
|
551
|
-
menu.addSeparator()
|
|
552
542
|
menu.addAction(self.action_new_sql)
|
|
553
543
|
|
|
554
544
|
# Check if IBM i for spool files
|
|
@@ -576,16 +566,7 @@ class ConnectionTreeWidget(QWidget):
|
|
|
576
566
|
|
|
577
567
|
item_type = data.get('type')
|
|
578
568
|
|
|
579
|
-
if item_type
|
|
580
|
-
connection_name = data.get('name')
|
|
581
|
-
if not self._connected.get(connection_name):
|
|
582
|
-
self.connect_requested.emit(connection_name)
|
|
583
|
-
# Store item to expand after connect succeeds
|
|
584
|
-
self._pending_expand = item
|
|
585
|
-
else:
|
|
586
|
-
item.setExpanded(not item.isExpanded())
|
|
587
|
-
|
|
588
|
-
elif item_type in ('schema', 'table'):
|
|
569
|
+
if item_type in ('connection', 'schema', 'table'):
|
|
589
570
|
item.setExpanded(not item.isExpanded())
|
|
590
571
|
|
|
591
572
|
def _on_current_changed(self, current: QTreeWidgetItem,
|
|
@@ -609,22 +590,6 @@ class ConnectionTreeWidget(QWidget):
|
|
|
609
590
|
else:
|
|
610
591
|
return data.get('connection')
|
|
611
592
|
|
|
612
|
-
def _connect_selected(self) -> None:
|
|
613
|
-
"""Connect the selected connection."""
|
|
614
|
-
item = self.tree.currentItem()
|
|
615
|
-
if item:
|
|
616
|
-
connection_name = self._get_connection_for_item(item)
|
|
617
|
-
if connection_name:
|
|
618
|
-
self.connect_requested.emit(connection_name)
|
|
619
|
-
|
|
620
|
-
def _disconnect_selected(self) -> None:
|
|
621
|
-
"""Disconnect the selected connection."""
|
|
622
|
-
item = self.tree.currentItem()
|
|
623
|
-
if item:
|
|
624
|
-
connection_name = self._get_connection_for_item(item)
|
|
625
|
-
if connection_name:
|
|
626
|
-
self.disconnect_requested.emit(connection_name)
|
|
627
|
-
|
|
628
593
|
def _new_sql_for_selected(self) -> None:
|
|
629
594
|
"""Create new SQL tab for selected connection."""
|
|
630
595
|
item = self.tree.currentItem()
|
|
@@ -707,14 +672,6 @@ class ConnectionTreeWidget(QWidget):
|
|
|
707
672
|
elif item.parent():
|
|
708
673
|
self.tree.setCurrentItem(item.parent())
|
|
709
674
|
elif key in (Qt.Key.Key_Return, Qt.Key.Key_Space):
|
|
710
|
-
|
|
711
|
-
if data:
|
|
712
|
-
if data.get('type') == 'connection':
|
|
713
|
-
if not self._connected.get(data.get('name')):
|
|
714
|
-
self.connect_requested.emit(data.get('name'))
|
|
715
|
-
else:
|
|
716
|
-
item.setExpanded(not item.isExpanded())
|
|
717
|
-
else:
|
|
718
|
-
item.setExpanded(not item.isExpanded())
|
|
675
|
+
item.setExpanded(not item.isExpanded())
|
|
719
676
|
else:
|
|
720
677
|
super().keyPressEvent(event)
|
|
@@ -91,12 +91,14 @@ class ConnectionDialog(QDialog):
|
|
|
91
91
|
self.btn_new = QPushButton("+")
|
|
92
92
|
self.btn_new.setFixedWidth(32)
|
|
93
93
|
self.btn_new.setToolTip("New connection")
|
|
94
|
+
self.btn_new.setAutoDefault(False)
|
|
94
95
|
self.btn_new.clicked.connect(self._new_connection)
|
|
95
96
|
btn_layout.addWidget(self.btn_new)
|
|
96
97
|
|
|
97
98
|
self.btn_delete = QPushButton("−")
|
|
98
99
|
self.btn_delete.setFixedWidth(32)
|
|
99
100
|
self.btn_delete.setToolTip("Delete connection")
|
|
101
|
+
self.btn_delete.setAutoDefault(False)
|
|
100
102
|
self.btn_delete.clicked.connect(self._delete_connection)
|
|
101
103
|
btn_layout.addWidget(self.btn_delete)
|
|
102
104
|
|
|
@@ -170,6 +172,11 @@ class ConnectionDialog(QDialog):
|
|
|
170
172
|
self.txt_password.setPlaceholderText("password")
|
|
171
173
|
form_layout.addRow("Password:", self.txt_password)
|
|
172
174
|
|
|
175
|
+
# Enter key in any field triggers Save
|
|
176
|
+
for field in (self.txt_name, self.txt_host, self.txt_port,
|
|
177
|
+
self.txt_database, self.txt_user, self.txt_password):
|
|
178
|
+
field.returnPressed.connect(self._save_connection)
|
|
179
|
+
|
|
173
180
|
# Options
|
|
174
181
|
options_layout = QVBoxLayout()
|
|
175
182
|
|
|
@@ -194,6 +201,7 @@ class ConnectionDialog(QDialog):
|
|
|
194
201
|
btn_layout = QHBoxLayout()
|
|
195
202
|
|
|
196
203
|
self.btn_test = QPushButton("Test Connection")
|
|
204
|
+
self.btn_test.setAutoDefault(False)
|
|
197
205
|
self.btn_test.clicked.connect(self._test_connection)
|
|
198
206
|
btn_layout.addWidget(self.btn_test)
|
|
199
207
|
|
|
@@ -201,10 +209,12 @@ class ConnectionDialog(QDialog):
|
|
|
201
209
|
|
|
202
210
|
self.btn_save = QPushButton("Save")
|
|
203
211
|
self.btn_save.setProperty("primary", True)
|
|
212
|
+
self.btn_save.setDefault(True)
|
|
204
213
|
self.btn_save.clicked.connect(self._save_connection)
|
|
205
214
|
btn_layout.addWidget(self.btn_save)
|
|
206
215
|
|
|
207
216
|
self.btn_close = QPushButton("Close")
|
|
217
|
+
self.btn_close.setAutoDefault(False)
|
|
208
218
|
self.btn_close.clicked.connect(self.accept)
|
|
209
219
|
btn_layout.addWidget(self.btn_close)
|
|
210
220
|
|
|
@@ -251,7 +261,7 @@ class ConnectionDialog(QDialog):
|
|
|
251
261
|
|
|
252
262
|
self.txt_name.setText(conn.get('name', ''))
|
|
253
263
|
self.txt_host.setText(conn.get('host', ''))
|
|
254
|
-
self.txt_port.setText(str(conn.get('port'
|
|
264
|
+
self.txt_port.setText(str(conn['port']) if conn.get('port') else '')
|
|
255
265
|
self.txt_database.setText(conn.get('database', ''))
|
|
256
266
|
self.txt_user.setText(conn.get('user', ''))
|
|
257
267
|
self.txt_password.setText(conn.get('password', ''))
|
|
@@ -284,15 +294,20 @@ class ConnectionDialog(QDialog):
|
|
|
284
294
|
def _on_type_changed(self) -> None:
|
|
285
295
|
"""Handle database type change."""
|
|
286
296
|
db_type = self.cmb_type.currentData()
|
|
297
|
+
adapter_cls = ADAPTERS.get(db_type)
|
|
287
298
|
|
|
288
|
-
|
|
289
|
-
|
|
299
|
+
has_port = adapter_cls.default_port is not None if adapter_cls else True
|
|
300
|
+
has_database = adapter_cls.requires_database if adapter_cls else True
|
|
290
301
|
|
|
291
302
|
# Hide/show port and database fields with their labels
|
|
292
|
-
self.txt_port.setVisible(
|
|
293
|
-
self.
|
|
294
|
-
self.
|
|
295
|
-
self.lbl_database.setVisible(
|
|
303
|
+
self.txt_port.setVisible(has_port)
|
|
304
|
+
self.lbl_port.setVisible(has_port)
|
|
305
|
+
self.txt_database.setVisible(has_database)
|
|
306
|
+
self.lbl_database.setVisible(has_database)
|
|
307
|
+
|
|
308
|
+
# Pre-fill default port if empty
|
|
309
|
+
if has_port and not self.txt_port.text().strip() and adapter_cls:
|
|
310
|
+
self.txt_port.setText(str(adapter_cls.default_port))
|
|
296
311
|
|
|
297
312
|
# Show install button if driver not available
|
|
298
313
|
adapters = get_available_adapters()
|
|
@@ -441,13 +456,20 @@ class ConnectionDialog(QDialog):
|
|
|
441
456
|
return
|
|
442
457
|
|
|
443
458
|
db_type = self.cmb_type.currentData()
|
|
444
|
-
port = self.txt_port.text()
|
|
459
|
+
port = self.txt_port.text().strip()
|
|
460
|
+
|
|
461
|
+
# Use adapter default port if blank
|
|
462
|
+
if not port or not port.isdigit():
|
|
463
|
+
adapter_cls = ADAPTERS.get(db_type)
|
|
464
|
+
port = adapter_cls.default_port if adapter_cls else None
|
|
465
|
+
else:
|
|
466
|
+
port = int(port)
|
|
445
467
|
|
|
446
468
|
conn_data = {
|
|
447
469
|
'name': name,
|
|
448
470
|
'db_type': db_type,
|
|
449
471
|
'host': self.txt_host.text(),
|
|
450
|
-
'port':
|
|
472
|
+
'port': port,
|
|
451
473
|
'database': self.txt_database.text(),
|
|
452
474
|
'user': self.txt_user.text(),
|
|
453
475
|
'password': self.txt_password.text(),
|
|
@@ -35,7 +35,8 @@ class MainWindow(QMainWindow):
|
|
|
35
35
|
def __init__(self):
|
|
36
36
|
super().__init__()
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
from ..version import __version__
|
|
39
|
+
self.setWindowTitle(f"SQLBench v{__version__}")
|
|
39
40
|
self.setMinimumSize(1024, 600)
|
|
40
41
|
|
|
41
42
|
# Load theme preference
|
|
@@ -114,7 +115,6 @@ class MainWindow(QMainWindow):
|
|
|
114
115
|
self.connection_tree = ConnectionTreeWidget()
|
|
115
116
|
self.connection_tree.connection_selected.connect(self._on_connection_selected)
|
|
116
117
|
self.connection_tree.connect_requested.connect(self._on_connect_requested)
|
|
117
|
-
self.connection_tree.disconnect_requested.connect(self._on_disconnect_requested)
|
|
118
118
|
self.connection_tree.new_sql_requested.connect(self._on_new_sql_tab)
|
|
119
119
|
self.connection_tree.new_spool_requested.connect(self._on_new_spool_tab)
|
|
120
120
|
self.connection_tree.show_rows_requested.connect(self._on_show_rows)
|
|
@@ -315,8 +315,17 @@ class MainWindow(QMainWindow):
|
|
|
315
315
|
from .dialogs.settings_dialog import SettingsDialog
|
|
316
316
|
dialog = SettingsDialog(self)
|
|
317
317
|
if dialog.exec():
|
|
318
|
-
|
|
319
|
-
|
|
318
|
+
self._apply_font_size()
|
|
319
|
+
|
|
320
|
+
def _apply_font_size(self) -> None:
|
|
321
|
+
"""Apply current font size setting to all open tabs."""
|
|
322
|
+
from .tabs.sql_tab import SQLTab
|
|
323
|
+
size = int(get_setting("font_size", "13"))
|
|
324
|
+
for i in range(self.tab_container.count()):
|
|
325
|
+
tab = self.tab_container.widget(i)
|
|
326
|
+
if isinstance(tab, SQLTab):
|
|
327
|
+
tab.set_font_size(size)
|
|
328
|
+
self.status_bar.showMessage(f"Font size set to {size}", 3000)
|
|
320
329
|
|
|
321
330
|
def _reset_layout(self) -> None:
|
|
322
331
|
"""Reset window layout to defaults."""
|
|
@@ -331,6 +340,7 @@ class MainWindow(QMainWindow):
|
|
|
331
340
|
)
|
|
332
341
|
|
|
333
342
|
set_setting("font_size", "13")
|
|
343
|
+
self._apply_font_size()
|
|
334
344
|
self.status_bar.showMessage("Layout reset to defaults", 3000)
|
|
335
345
|
|
|
336
346
|
def _restore_tab_layouts(self) -> None:
|
|
@@ -373,13 +383,9 @@ class MainWindow(QMainWindow):
|
|
|
373
383
|
self.status_bar.showMessage(f"Selected: {connection_name}")
|
|
374
384
|
|
|
375
385
|
def _on_connect_requested(self, connection_name: str) -> None:
|
|
376
|
-
"""Handle request to
|
|
386
|
+
"""Handle request to activate a connection (verify credentials)."""
|
|
377
387
|
self._connect(connection_name)
|
|
378
388
|
|
|
379
|
-
def _on_disconnect_requested(self, connection_name: str) -> None:
|
|
380
|
-
"""Handle request to disconnect from a database."""
|
|
381
|
-
self.disconnect(connection_name)
|
|
382
|
-
|
|
383
389
|
def _on_new_sql_tab(self, connection_name: str) -> None:
|
|
384
390
|
"""Create new SQL tab for connection."""
|
|
385
391
|
from .tabs.sql_tab import SQLTab
|
|
@@ -44,6 +44,7 @@ from PyQt6.QtWidgets import (
|
|
|
44
44
|
QMessageBox,
|
|
45
45
|
QHeaderView,
|
|
46
46
|
QAbstractItemView,
|
|
47
|
+
QAbstractItemDelegate,
|
|
47
48
|
QApplication,
|
|
48
49
|
QFrame,
|
|
49
50
|
)
|
|
@@ -498,10 +499,36 @@ class ResultsTable(QTableWidget):
|
|
|
498
499
|
self.horizontalHeader().sectionDoubleClicked.connect(self._auto_fit_column)
|
|
499
500
|
self.verticalHeader().setDefaultSectionSize(24)
|
|
500
501
|
|
|
502
|
+
# Column width tracking for font scaling
|
|
503
|
+
self._base_widths: dict = {} # col_index -> base width at scale 1.0
|
|
504
|
+
self._pk_indices: List[int] = [] # set by SQLTab for tab-to-next-cell
|
|
505
|
+
|
|
501
506
|
# Context menu
|
|
502
507
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
503
508
|
self.customContextMenuRequested.connect(self._show_context_menu)
|
|
504
509
|
|
|
510
|
+
def closeEditor(self, editor, hint) -> None:
|
|
511
|
+
"""Override to skip PK columns when Tab is pressed during editing."""
|
|
512
|
+
if hint == QAbstractItemDelegate.EndEditHint.EditNextItem and self._pk_indices:
|
|
513
|
+
# Tab was pressed — commit and move to next non-PK column
|
|
514
|
+
super().closeEditor(editor, QAbstractItemDelegate.EndEditHint.NoHint)
|
|
515
|
+
row = self.currentRow()
|
|
516
|
+
col = self.currentColumn()
|
|
517
|
+
next_col = col + 1
|
|
518
|
+
while next_col < self.columnCount():
|
|
519
|
+
if next_col not in self._pk_indices:
|
|
520
|
+
self.setCurrentCell(row, next_col)
|
|
521
|
+
self.editItem(self.item(row, next_col))
|
|
522
|
+
return
|
|
523
|
+
next_col += 1
|
|
524
|
+
return
|
|
525
|
+
super().closeEditor(editor, hint)
|
|
526
|
+
|
|
527
|
+
def scale_columns(self, scale: float) -> None:
|
|
528
|
+
"""Scale column widths based on font size ratio."""
|
|
529
|
+
for col, base_w in self._base_widths.items():
|
|
530
|
+
self.setColumnWidth(col, int(base_w * scale))
|
|
531
|
+
|
|
505
532
|
def _show_context_menu(self, pos) -> None:
|
|
506
533
|
"""Show context menu."""
|
|
507
534
|
menu = QMenu(self)
|
|
@@ -623,10 +650,15 @@ class ResultsTable(QTableWidget):
|
|
|
623
650
|
# Resize columns to content
|
|
624
651
|
self.resizeColumnsToContents()
|
|
625
652
|
|
|
626
|
-
# Cap column widths
|
|
653
|
+
# Cap column widths and store base widths for font scaling
|
|
654
|
+
font_size = int(get_setting("font_size", "13"))
|
|
655
|
+
scale = font_size / 13.0
|
|
656
|
+
self._base_widths = {}
|
|
627
657
|
for i in range(self.columnCount()):
|
|
628
658
|
if self.columnWidth(i) > 300:
|
|
629
659
|
self.setColumnWidth(i, 300)
|
|
660
|
+
# Store base width (at scale 1.0) for later rescaling
|
|
661
|
+
self._base_widths[i] = int(self.columnWidth(i) / scale) if scale else self.columnWidth(i)
|
|
630
662
|
|
|
631
663
|
self.setSortingEnabled(True)
|
|
632
664
|
|
|
@@ -1133,6 +1165,24 @@ class SQLTab(QWidget):
|
|
|
1133
1165
|
"""Check if this tab has unsaved cell edits."""
|
|
1134
1166
|
return bool(self._modified_cells)
|
|
1135
1167
|
|
|
1168
|
+
def set_font_size(self, size: int) -> None:
|
|
1169
|
+
"""Apply new font size to editor and results."""
|
|
1170
|
+
self.editor.set_font_size(size)
|
|
1171
|
+
|
|
1172
|
+
# Scale results and fields column widths
|
|
1173
|
+
scale = size / 13.0
|
|
1174
|
+
self.results_table.scale_columns(scale)
|
|
1175
|
+
|
|
1176
|
+
# Scale row heights
|
|
1177
|
+
row_height = max(24, int(size * 1.8))
|
|
1178
|
+
self.results_table.verticalHeader().setDefaultSectionSize(row_height)
|
|
1179
|
+
self.fields_table.verticalHeader().setDefaultSectionSize(row_height)
|
|
1180
|
+
|
|
1181
|
+
# Update stats font
|
|
1182
|
+
font = self.stats_text.font()
|
|
1183
|
+
font.setPointSize(size)
|
|
1184
|
+
self.stats_text.setFont(font)
|
|
1185
|
+
|
|
1136
1186
|
def set_sql(self, sql: str) -> None:
|
|
1137
1187
|
"""Set the SQL text."""
|
|
1138
1188
|
self.editor.setPlainText(sql)
|
|
@@ -2005,6 +2055,7 @@ class SQLTab(QWidget):
|
|
|
2005
2055
|
self._edit_schema = None
|
|
2006
2056
|
self._pk_columns = []
|
|
2007
2057
|
self._pk_indices = []
|
|
2058
|
+
self.results_table._pk_indices = []
|
|
2008
2059
|
self._original_values = {}
|
|
2009
2060
|
self._modified_cells = {}
|
|
2010
2061
|
self.btn_save_changes.hide()
|
|
@@ -2053,6 +2104,7 @@ class SQLTab(QWidget):
|
|
|
2053
2104
|
self._edit_schema = schema
|
|
2054
2105
|
self._pk_columns = pk_cols
|
|
2055
2106
|
self._pk_indices = pk_indices
|
|
2107
|
+
self.results_table._pk_indices = pk_indices
|
|
2056
2108
|
|
|
2057
2109
|
# Store original values
|
|
2058
2110
|
for row_idx, row in enumerate(rows):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|