sqlbench 0.1.27__tar.gz → 0.1.28__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.27 → sqlbench-0.1.28}/PKG-INFO +1 -1
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/app.py +54 -43
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/tabs/sql_tab.py +67 -13
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/version.py +1 -1
- {sqlbench-0.1.27 → sqlbench-0.1.28}/.github/workflows/pypi-publish.yml +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/.gitignore +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/Makefile +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/README.md +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/pyproject.toml +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/requirements.txt +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/__init__.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/__main__.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/adapters.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/database.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/dialogs/__init__.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/dialogs/connection_dialog.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/launcher.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_ibmi.png +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_mysql.png +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_postgresql.png +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_unknown.png +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/sqlbench.png +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/tabs/__init__.py +0 -0
- {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/tabs/spool_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.28
|
|
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
|
|
@@ -1625,71 +1625,79 @@ class SQLBenchApp:
|
|
|
1625
1625
|
self.db.set_setting("window_geometry", geometry)
|
|
1626
1626
|
|
|
1627
1627
|
def _save_layout(self):
|
|
1628
|
-
"""Save paned window sash positions."""
|
|
1628
|
+
"""Save paned window sash positions as ratios for reliable restore."""
|
|
1629
1629
|
try:
|
|
1630
|
-
# Main paned window (connections | tabs)
|
|
1630
|
+
# Main paned window (connections | tabs) - horizontal, save as ratio of width
|
|
1631
1631
|
sash_pos = self.main_paned.sashpos(0)
|
|
1632
|
-
self.
|
|
1632
|
+
pane_width = self.main_paned.winfo_width()
|
|
1633
|
+
if pane_width > 100:
|
|
1634
|
+
ratio = sash_pos / pane_width
|
|
1635
|
+
self.db.set_setting("layout_main_ratio", f"{ratio:.4f}")
|
|
1633
1636
|
except Exception:
|
|
1634
1637
|
pass
|
|
1635
1638
|
|
|
1636
|
-
# Save tab-level layouts
|
|
1637
|
-
sql_sash = None
|
|
1638
|
-
spool_sash = None
|
|
1639
|
-
|
|
1639
|
+
# Save tab-level layouts as ratios
|
|
1640
1640
|
for tab_id in self.notebook.tabs():
|
|
1641
1641
|
try:
|
|
1642
1642
|
tab_frame = self.notebook.nametowidget(tab_id)
|
|
1643
1643
|
if hasattr(tab_frame, 'sql_tab') and hasattr(tab_frame.sql_tab, 'paned'):
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1644
|
+
sash_pos = tab_frame.sql_tab.paned.sashpos(0)
|
|
1645
|
+
pane_height = tab_frame.sql_tab.paned.winfo_height()
|
|
1646
|
+
if pane_height > 100:
|
|
1647
|
+
ratio = sash_pos / pane_height
|
|
1648
|
+
self.db.set_setting("layout_sql_ratio", f"{ratio:.4f}")
|
|
1649
|
+
break # Only need one SQL tab's position
|
|
1647
1650
|
except Exception:
|
|
1648
1651
|
pass
|
|
1649
1652
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1653
|
+
for tab_id in self.notebook.tabs():
|
|
1654
|
+
try:
|
|
1655
|
+
tab_frame = self.notebook.nametowidget(tab_id)
|
|
1656
|
+
if hasattr(tab_frame, 'spool_tab') and hasattr(tab_frame.spool_tab, 'paned'):
|
|
1657
|
+
sash_pos = tab_frame.spool_tab.paned.sashpos(0)
|
|
1658
|
+
pane_width = tab_frame.spool_tab.paned.winfo_width()
|
|
1659
|
+
if pane_width > 100:
|
|
1660
|
+
ratio = sash_pos / pane_width
|
|
1661
|
+
self.db.set_setting("layout_spool_ratio", f"{ratio:.4f}")
|
|
1662
|
+
break # Only need one spool tab's position
|
|
1663
|
+
except Exception:
|
|
1664
|
+
pass
|
|
1654
1665
|
|
|
1655
1666
|
def _restore_layout(self):
|
|
1656
|
-
"""Restore paned window sash positions."""
|
|
1667
|
+
"""Restore paned window sash positions from saved ratios."""
|
|
1668
|
+
self.root.update_idletasks()
|
|
1669
|
+
|
|
1670
|
+
# Restore main paned (connections | tabs) from ratio
|
|
1657
1671
|
try:
|
|
1658
|
-
|
|
1659
|
-
if
|
|
1660
|
-
|
|
1661
|
-
self.main_paned.
|
|
1672
|
+
ratio_str = self.db.get_setting("layout_main_ratio")
|
|
1673
|
+
if ratio_str:
|
|
1674
|
+
ratio = float(ratio_str)
|
|
1675
|
+
pane_width = self.main_paned.winfo_width()
|
|
1676
|
+
if pane_width > 100 and 0.05 <= ratio <= 0.95:
|
|
1677
|
+
sash_pos = int(ratio * pane_width)
|
|
1678
|
+
self.main_paned.sashpos(0, sash_pos)
|
|
1662
1679
|
except Exception:
|
|
1663
1680
|
pass
|
|
1664
1681
|
|
|
1665
|
-
# Restore tab
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
# Ignore sash positions that are too small (would hide the pane)
|
|
1670
|
-
MIN_SASH_POS = 50
|
|
1682
|
+
# Restore SQL tab sash from ratio
|
|
1683
|
+
sql_ratio_str = self.db.get_setting("layout_sql_ratio")
|
|
1684
|
+
spool_ratio_str = self.db.get_setting("layout_spool_ratio")
|
|
1671
1685
|
|
|
1672
1686
|
for tab_id in self.notebook.tabs():
|
|
1673
1687
|
try:
|
|
1674
1688
|
tab_frame = self.notebook.nametowidget(tab_id)
|
|
1675
|
-
if
|
|
1676
|
-
|
|
1689
|
+
if sql_ratio_str and hasattr(tab_frame, 'sql_tab') and hasattr(tab_frame.sql_tab, 'paned'):
|
|
1690
|
+
ratio = float(sql_ratio_str)
|
|
1677
1691
|
pane_height = tab_frame.sql_tab.paned.winfo_height()
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
tab_frame.
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
sash_val = int(spool_sash)
|
|
1688
|
-
# Only apply bounds if pane is fully laid out (height > 300)
|
|
1689
|
-
if pane_height > 300:
|
|
1690
|
-
max_sash = pane_height - 150
|
|
1691
|
-
sash_val = max(MIN_SASH_POS, min(sash_val, max_sash))
|
|
1692
|
-
tab_frame.spool_tab.paned.sashpos(0, sash_val)
|
|
1692
|
+
if pane_height > 100 and 0.1 <= ratio <= 0.9:
|
|
1693
|
+
sash_pos = int(ratio * pane_height)
|
|
1694
|
+
tab_frame.sql_tab.paned.sashpos(0, sash_pos)
|
|
1695
|
+
elif spool_ratio_str and hasattr(tab_frame, 'spool_tab') and hasattr(tab_frame.spool_tab, 'paned'):
|
|
1696
|
+
ratio = float(spool_ratio_str)
|
|
1697
|
+
pane_width = tab_frame.spool_tab.paned.winfo_width()
|
|
1698
|
+
if pane_width > 100 and 0.1 <= ratio <= 0.9:
|
|
1699
|
+
sash_pos = int(ratio * pane_width)
|
|
1700
|
+
tab_frame.spool_tab.paned.sashpos(0, sash_pos)
|
|
1693
1701
|
except Exception:
|
|
1694
1702
|
pass
|
|
1695
1703
|
|
|
@@ -1701,10 +1709,13 @@ class SQLBenchApp:
|
|
|
1701
1709
|
|
|
1702
1710
|
def _reset_layout(self):
|
|
1703
1711
|
"""Reset layout to defaults."""
|
|
1704
|
-
# Clear saved layout settings
|
|
1712
|
+
# Clear saved layout settings (both old and new format)
|
|
1705
1713
|
self.db.set_setting("layout_main_sash", "")
|
|
1706
1714
|
self.db.set_setting("layout_sql_sash", "")
|
|
1707
1715
|
self.db.set_setting("layout_spool_sash", "")
|
|
1716
|
+
self.db.set_setting("layout_main_ratio", "")
|
|
1717
|
+
self.db.set_setting("layout_sql_ratio", "")
|
|
1718
|
+
self.db.set_setting("layout_spool_ratio", "")
|
|
1708
1719
|
|
|
1709
1720
|
# Reset font size to default
|
|
1710
1721
|
self.font_size = 10
|
|
@@ -2606,14 +2606,35 @@ class SQLTab:
|
|
|
2606
2606
|
self.sql_context_menu.add_separator()
|
|
2607
2607
|
self.sql_context_menu.add_command(label="Find...", command=self._show_sql_search_dialog, accelerator="Ctrl+F")
|
|
2608
2608
|
|
|
2609
|
-
self.sql_text.bind("<
|
|
2609
|
+
self.sql_text.bind("<ButtonPress-3>", self._save_selection_for_context_menu)
|
|
2610
|
+
self.sql_text.bind("<ButtonRelease-3>", self._show_sql_context_menu)
|
|
2610
2611
|
self.sql_text.bind("<Control-f>", lambda e: self._show_sql_search_dialog())
|
|
2611
2612
|
self.sql_text.bind("<Control-F>", lambda e: self._show_sql_search_dialog())
|
|
2612
2613
|
self.sql_text.bind("<Control-z>", lambda e: self._sql_undo())
|
|
2613
2614
|
self.sql_text.bind("<Control-Z>", lambda e: self._sql_redo()) # Ctrl+Shift+Z
|
|
2615
|
+
self.sql_text.bind("<Control-v>", self._on_paste)
|
|
2616
|
+
self.sql_text.bind("<Control-V>", self._on_paste)
|
|
2617
|
+
|
|
2618
|
+
def _on_paste(self, event):
|
|
2619
|
+
"""Handle paste to replace selection like a normal editor."""
|
|
2620
|
+
self._sql_paste()
|
|
2621
|
+
return "break" # Prevent default paste behavior
|
|
2622
|
+
|
|
2623
|
+
def _save_selection_for_context_menu(self, event):
|
|
2624
|
+
"""Save selection before right-click clears it."""
|
|
2625
|
+
try:
|
|
2626
|
+
self._saved_selection = (self.sql_text.index("sel.first"), self.sql_text.index("sel.last"))
|
|
2627
|
+
except tk.TclError:
|
|
2628
|
+
self._saved_selection = None
|
|
2629
|
+
return "break" # Prevent default behavior from clearing selection
|
|
2614
2630
|
|
|
2615
2631
|
def _show_sql_context_menu(self, event):
|
|
2616
2632
|
"""Show context menu at mouse position."""
|
|
2633
|
+
# Restore selection if it was saved (right-click clears it)
|
|
2634
|
+
if hasattr(self, '_saved_selection') and self._saved_selection:
|
|
2635
|
+
self.sql_text.tag_remove("sel", "1.0", "end")
|
|
2636
|
+
self.sql_text.tag_add("sel", self._saved_selection[0], self._saved_selection[1])
|
|
2637
|
+
|
|
2617
2638
|
# Enable/disable undo based on edit history
|
|
2618
2639
|
try:
|
|
2619
2640
|
self.sql_text.edit_undo()
|
|
@@ -2638,6 +2659,7 @@ class SQLTab:
|
|
|
2638
2659
|
self.sql_context_menu.entryconfig("Paste", state=tk.DISABLED)
|
|
2639
2660
|
|
|
2640
2661
|
self.sql_context_menu.tk_popup(event.x_root, event.y_root, 0)
|
|
2662
|
+
return "break" # Prevent default behavior that moves cursor/deselects
|
|
2641
2663
|
|
|
2642
2664
|
def _sql_undo(self):
|
|
2643
2665
|
"""Undo last edit."""
|
|
@@ -2660,33 +2682,65 @@ class SQLTab:
|
|
|
2660
2682
|
def _sql_cut(self):
|
|
2661
2683
|
"""Cut selected text to clipboard."""
|
|
2662
2684
|
try:
|
|
2663
|
-
|
|
2664
|
-
self.
|
|
2665
|
-
|
|
2666
|
-
|
|
2685
|
+
# Use saved selection from context menu if available
|
|
2686
|
+
if hasattr(self, '_saved_selection') and self._saved_selection:
|
|
2687
|
+
sel_start, sel_end = self._saved_selection
|
|
2688
|
+
selected = self.sql_text.get(sel_start, sel_end)
|
|
2689
|
+
self.app.root.clipboard_clear()
|
|
2690
|
+
self.app.root.clipboard_append(selected)
|
|
2691
|
+
self.sql_text.delete(sel_start, sel_end)
|
|
2692
|
+
self._saved_selection = None
|
|
2693
|
+
else:
|
|
2694
|
+
selected = self.sql_text.get("sel.first", "sel.last")
|
|
2695
|
+
self.app.root.clipboard_clear()
|
|
2696
|
+
self.app.root.clipboard_append(selected)
|
|
2697
|
+
self.sql_text.delete("sel.first", "sel.last")
|
|
2667
2698
|
except tk.TclError:
|
|
2668
2699
|
pass # No selection
|
|
2669
2700
|
|
|
2670
2701
|
def _sql_copy(self):
|
|
2671
2702
|
"""Copy selected text to clipboard."""
|
|
2672
2703
|
try:
|
|
2673
|
-
|
|
2674
|
-
self.
|
|
2675
|
-
|
|
2704
|
+
# Use saved selection from context menu if available
|
|
2705
|
+
if hasattr(self, '_saved_selection') and self._saved_selection:
|
|
2706
|
+
sel_start, sel_end = self._saved_selection
|
|
2707
|
+
selected = self.sql_text.get(sel_start, sel_end)
|
|
2708
|
+
self.app.root.clipboard_clear()
|
|
2709
|
+
self.app.root.clipboard_append(selected)
|
|
2710
|
+
self._saved_selection = None
|
|
2711
|
+
else:
|
|
2712
|
+
selected = self.sql_text.get("sel.first", "sel.last")
|
|
2713
|
+
self.app.root.clipboard_clear()
|
|
2714
|
+
self.app.root.clipboard_append(selected)
|
|
2676
2715
|
except tk.TclError:
|
|
2677
2716
|
pass # No selection
|
|
2678
2717
|
|
|
2679
2718
|
def _sql_paste(self):
|
|
2680
2719
|
"""Paste text from clipboard."""
|
|
2681
2720
|
try:
|
|
2682
|
-
#
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2721
|
+
# Mark undo separator before paste
|
|
2722
|
+
self.sql_text.edit_separator()
|
|
2723
|
+
|
|
2724
|
+
# Delete selection if any (check saved selection from context menu first)
|
|
2725
|
+
deleted = False
|
|
2726
|
+
if hasattr(self, '_saved_selection') and self._saved_selection:
|
|
2727
|
+
# Use saved selection from context menu
|
|
2728
|
+
self.sql_text.delete(self._saved_selection[0], self._saved_selection[1])
|
|
2729
|
+
self.sql_text.mark_set("insert", self._saved_selection[0])
|
|
2730
|
+
self._saved_selection = None
|
|
2731
|
+
deleted = True
|
|
2732
|
+
if not deleted:
|
|
2733
|
+
try:
|
|
2734
|
+
self.sql_text.delete("sel.first", "sel.last")
|
|
2735
|
+
except tk.TclError:
|
|
2736
|
+
pass
|
|
2687
2737
|
# Get clipboard content and insert
|
|
2688
2738
|
text = self.app.root.clipboard_get()
|
|
2689
2739
|
self.sql_text.insert("insert", text)
|
|
2740
|
+
|
|
2741
|
+
# Mark undo separator after paste and update highlighting
|
|
2742
|
+
self.sql_text.edit_separator()
|
|
2743
|
+
self._highlight_sql()
|
|
2690
2744
|
except tk.TclError:
|
|
2691
2745
|
pass # Nothing in clipboard
|
|
2692
2746
|
|
|
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
|