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.
Files changed (25) hide show
  1. {sqlbench-0.1.27 → sqlbench-0.1.28}/PKG-INFO +1 -1
  2. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/app.py +54 -43
  3. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/tabs/sql_tab.py +67 -13
  4. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/version.py +1 -1
  5. {sqlbench-0.1.27 → sqlbench-0.1.28}/.github/workflows/pypi-publish.yml +0 -0
  6. {sqlbench-0.1.27 → sqlbench-0.1.28}/.gitignore +0 -0
  7. {sqlbench-0.1.27 → sqlbench-0.1.28}/Makefile +0 -0
  8. {sqlbench-0.1.27 → sqlbench-0.1.28}/README.md +0 -0
  9. {sqlbench-0.1.27 → sqlbench-0.1.28}/pyproject.toml +0 -0
  10. {sqlbench-0.1.27 → sqlbench-0.1.28}/requirements.txt +0 -0
  11. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/__init__.py +0 -0
  12. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/__main__.py +0 -0
  13. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/adapters.py +0 -0
  14. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/database.py +0 -0
  15. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/dialogs/__init__.py +0 -0
  16. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/dialogs/connection_dialog.py +0 -0
  17. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
  18. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/launcher.py +0 -0
  19. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_ibmi.png +0 -0
  20. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_mysql.png +0 -0
  21. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_postgresql.png +0 -0
  22. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/db_unknown.png +0 -0
  23. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/resources/sqlbench.png +0 -0
  24. {sqlbench-0.1.27 → sqlbench-0.1.28}/sqlbench/tabs/__init__.py +0 -0
  25. {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.27
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.db.set_setting("layout_main_sash", str(sash_pos))
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 (use last found position for each type)
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
- sql_sash = tab_frame.sql_tab.paned.sashpos(0)
1645
- elif hasattr(tab_frame, 'spool_tab') and hasattr(tab_frame.spool_tab, 'paned'):
1646
- spool_sash = tab_frame.spool_tab.paned.sashpos(0)
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
- if sql_sash is not None:
1651
- self.db.set_setting("layout_sql_sash", str(sql_sash))
1652
- if spool_sash is not None:
1653
- self.db.set_setting("layout_spool_sash", str(spool_sash))
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
- sash_pos = self.db.get_setting("layout_main_sash")
1659
- if sash_pos:
1660
- self.root.update_idletasks()
1661
- self.main_paned.sashpos(0, int(sash_pos))
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-level layouts (only if saved value is reasonable)
1666
- sql_sash = self.db.get_setting("layout_sql_sash")
1667
- spool_sash = self.db.get_setting("layout_spool_sash")
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 sql_sash and hasattr(tab_frame, 'sql_tab') and hasattr(tab_frame.sql_tab, 'paned'):
1676
- self.root.update_idletasks()
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
- sash_val = int(sql_sash)
1679
- # Only apply bounds if pane is fully laid out (height > 300)
1680
- if pane_height > 300:
1681
- max_sash = pane_height - 150
1682
- sash_val = max(MIN_SASH_POS, min(sash_val, max_sash))
1683
- tab_frame.sql_tab.paned.sashpos(0, sash_val)
1684
- elif spool_sash and hasattr(tab_frame, 'spool_tab') and hasattr(tab_frame.spool_tab, 'paned'):
1685
- self.root.update_idletasks()
1686
- pane_height = tab_frame.spool_tab.paned.winfo_height()
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("<Button-3>", self._show_sql_context_menu)
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
- selected = self.sql_text.get("sel.first", "sel.last")
2664
- self.app.root.clipboard_clear()
2665
- self.app.root.clipboard_append(selected)
2666
- self.sql_text.delete("sel.first", "sel.last")
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
- selected = self.sql_text.get("sel.first", "sel.last")
2674
- self.app.root.clipboard_clear()
2675
- self.app.root.clipboard_append(selected)
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
- # Delete selection if any
2683
- try:
2684
- self.sql_text.delete("sel.first", "sel.last")
2685
- except tk.TclError:
2686
- pass
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
 
@@ -4,7 +4,7 @@ import threading
4
4
  import urllib.request
5
5
  import json
6
6
 
7
- __version__ = "0.1.27"
7
+ __version__ = "0.1.28"
8
8
 
9
9
 
10
10
  def get_installed_version():
File without changes
File without changes
File without changes
File without changes
File without changes