pomera-ai-commander 1.2.4 → 1.2.7

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.
package/pomera.py CHANGED
@@ -494,6 +494,22 @@ except ImportError as e:
494
494
  WIDGET_CACHE_AVAILABLE = False
495
495
  print(f"Widget Cache not available: {e}")
496
496
 
497
+ # Collapsible Panel import (UI redesign)
498
+ try:
499
+ from core.collapsible_panel import CollapsiblePanel
500
+ COLLAPSIBLE_PANEL_AVAILABLE = True
501
+ except ImportError as e:
502
+ COLLAPSIBLE_PANEL_AVAILABLE = False
503
+ print(f"Collapsible Panel not available: {e}")
504
+
505
+ # Tool Search Widget import (UI redesign)
506
+ try:
507
+ from core.tool_search_widget import ToolSearchPalette
508
+ TOOL_SEARCH_WIDGET_AVAILABLE = True
509
+ except ImportError as e:
510
+ TOOL_SEARCH_WIDGET_AVAILABLE = False
511
+ print(f"Tool Search Widget not available: {e}")
512
+
497
513
  class AppConfig:
498
514
  """Configuration constants for the application."""
499
515
  DEFAULT_WINDOW_SIZE = "1200x900"
@@ -938,7 +954,8 @@ class PromeraAIApp(tk.Tk):
938
954
  self.logger.info("Existing content found - skipping apply_tool to preserve loaded content")
939
955
  elif self.tool_var.get() == "Diff Viewer":
940
956
  self.central_frame.grid_remove()
941
- self.diff_frame.grid(row=0, column=0, sticky="nsew", pady=5)
957
+ # Use row=1 (same as central_frame) to not cover search bar in row=0
958
+ self.diff_frame.grid(row=1, column=0, sticky="nsew", pady=5)
942
959
  self.update_tool_settings_ui()
943
960
  self.load_diff_viewer_content()
944
961
  self.run_diff_viewer()
@@ -961,6 +978,15 @@ class PromeraAIApp(tk.Tk):
961
978
  if MCP_MANAGER_MODULE_AVAILABLE:
962
979
  self.bind_all("<Control-m>", lambda e: self.open_mcp_manager())
963
980
 
981
+ # Set up Tool Search keyboard shortcut (Ctrl+K)
982
+ if TOOL_SEARCH_WIDGET_AVAILABLE:
983
+ self.bind_all("<Control-k>", self.focus_tool_search)
984
+
985
+ # Set up Options Panel toggle shortcut (Ctrl+Shift+H)
986
+ if COLLAPSIBLE_PANEL_AVAILABLE:
987
+ self.bind_all("<Control-Shift-h>", self.toggle_options_panel)
988
+ self.bind_all("<Control-Shift-H>", self.toggle_options_panel) # Windows needs uppercase
989
+
964
990
  # Set up window focus and minimize event handlers for visibility-aware updates
965
991
  if hasattr(self, 'statistics_update_manager') and self.statistics_update_manager:
966
992
  self.bind("<FocusIn>", self._on_window_focus_in)
@@ -1196,26 +1222,12 @@ class PromeraAIApp(tk.Tk):
1196
1222
 
1197
1223
  ttk.Label(main_frame, text="Pomera AI Commander", font=('TkDefaultFont', 14, 'bold')).pack(pady=(0, 10))
1198
1224
 
1199
- # Get current version - try pyproject.toml first (for local dev), then importlib.metadata
1200
- current_version = None
1225
+ # Get current version from unified version module
1201
1226
  try:
1202
- # Try reading from pyproject.toml (when running from source)
1203
- import re
1204
- pyproject_path = Path(__file__).parent / "pyproject.toml"
1205
- if pyproject_path.exists():
1206
- content = pyproject_path.read_text()
1207
- match = re.search(r'version = "1.2.4"]+)"', content)
1208
- if match:
1209
- current_version = match.group(1)
1210
- except Exception:
1211
- pass
1212
-
1213
- if not current_version:
1214
- try:
1215
- import importlib.metadata
1216
- current_version = importlib.metadata.version("pomera-ai-commander")
1217
- except Exception:
1218
- current_version = "1.2.4"
1227
+ from pomera.version import __version__
1228
+ current_version = __version__
1229
+ except ImportError:
1230
+ current_version = "unknown"
1219
1231
 
1220
1232
  # Detect OS for download link
1221
1233
  system = platform.system()
@@ -1353,8 +1365,12 @@ class PromeraAIApp(tk.Tk):
1353
1365
 
1354
1366
  def _show_about_dialog(self):
1355
1367
  """Show About dialog."""
1356
- # Version managed by bump_version.py script
1357
- version = "1.2.4"
1368
+ # Version from unified version module (managed by setuptools_scm)
1369
+ try:
1370
+ from pomera.version import __version__
1371
+ version = __version__
1372
+ except ImportError:
1373
+ version = "unknown"
1358
1374
 
1359
1375
  messagebox.showinfo(
1360
1376
  "About Pomera AI Commander",
@@ -2203,11 +2219,17 @@ class PromeraAIApp(tk.Tk):
2203
2219
 
2204
2220
  main_frame = ttk.Frame(self, padding="10")
2205
2221
  main_frame.pack(fill=tk.BOTH, expand=True)
2206
- main_frame.rowconfigure(0, weight=1)
2222
+ main_frame.rowconfigure(1, weight=1) # Central frame gets the weight
2207
2223
  main_frame.columnconfigure(0, weight=1)
2224
+
2225
+ # Row 0: Search bar (below menu, above Input/Output)
2226
+ self.top_search_frame = ttk.Frame(main_frame)
2227
+ self.top_search_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
2228
+ self._create_top_search_bar(self.top_search_frame)
2208
2229
 
2230
+ # Row 1: Central frame with Input/Output panels
2209
2231
  self.central_frame = ttk.Frame(main_frame, padding="10")
2210
- self.central_frame.grid(row=0, column=0, sticky="nsew", pady=5)
2232
+ self.central_frame.grid(row=1, column=0, sticky="nsew", pady=5)
2211
2233
  self.central_frame.grid_columnconfigure(0, weight=1)
2212
2234
  self.central_frame.grid_columnconfigure(1, weight=1)
2213
2235
  self.central_frame.grid_rowconfigure(1, weight=1)
@@ -2220,12 +2242,13 @@ class PromeraAIApp(tk.Tk):
2220
2242
 
2221
2243
  self.create_diff_viewer(main_frame)
2222
2244
 
2223
- # Add separator
2245
+ # Row 2: Separator
2224
2246
  separator = ttk.Separator(main_frame, orient='horizontal')
2225
- separator.grid(row=1, column=0, sticky="ew", pady=10)
2247
+ separator.grid(row=2, column=0, sticky="ew", pady=10)
2226
2248
 
2249
+ # Row 3: Tool options frame (collapsible)
2227
2250
  tool_frame = ttk.Frame(main_frame, padding="10")
2228
- tool_frame.grid(row=2, column=0, sticky="ew", pady=5)
2251
+ tool_frame.grid(row=3, column=0, sticky="ew", pady=5)
2229
2252
  self.create_tool_widgets(tool_frame)
2230
2253
 
2231
2254
  # Setup context menus for all text widgets
@@ -3698,35 +3721,78 @@ class PromeraAIApp(tk.Tk):
3698
3721
  print(f"Filter text: {repr(self.output_filter_var.get())}")
3699
3722
  except Exception as e:
3700
3723
  print(f"Debug error: {e}")
3701
-
3702
- def create_tool_widgets(self, parent):
3703
- """Creates widgets for the tool selection and settings section."""
3724
+
3725
+ def _create_top_search_bar(self, parent):
3726
+ """Create the top search bar (below menu, above Input/Output panels)."""
3704
3727
  self.tool_var = tk.StringVar(value=self.settings.get("selected_tool", "Case Tool"))
3705
3728
 
3706
- self.tool_options = [
3707
- "AI Tools", "Base64 Encoder/Decoder", "Case Tool", "Column Tools",
3708
- "Cron Tool", "Diff Viewer", "Email Header Analyzer", "Extraction Tools",
3709
- "Find & Replace Text", "Folder File Reporter", "Generator Tools",
3710
- "JSON/XML Tool", "Line Tools", "Markdown Tools",
3711
- "Number Base Converter", "Sorter Tools",
3712
- "String Escape Tool", "Text Statistics", "Text Wrapper", "Timestamp Converter",
3713
- "Translator Tools", "URL Parser", "Whitespace Tools"
3714
- ]
3715
- self.filtered_tool_options = self.tool_options.copy()
3716
-
3717
- # Create custom dropdown that opens upwards
3718
- self.tool_menu = ttk.Menubutton(parent, textvariable=self.tool_var, direction="above", width=25)
3719
- self.tool_menu.pack(side=tk.LEFT, padx=5)
3720
-
3721
- # Create the dropdown menu
3722
- self.tool_dropdown_menu = tk.Menu(self.tool_menu, tearoff=0)
3723
- self.tool_menu.config(menu=self.tool_dropdown_menu)
3724
-
3725
- # Populate the dropdown menu with all tools
3726
- self.update_tool_dropdown_menu()
3729
+ # Use ToolSearchPalette if available
3730
+ if TOOL_SEARCH_WIDGET_AVAILABLE and TOOL_LOADER_AVAILABLE and self.tool_loader:
3731
+ self.tool_search_palette = ToolSearchPalette(
3732
+ parent,
3733
+ tool_loader=self.tool_loader,
3734
+ on_tool_selected=self._on_palette_tool_selected,
3735
+ settings=self.settings,
3736
+ on_settings_change=self._on_ui_settings_change
3737
+ )
3738
+ self.tool_search_palette.pack(fill=tk.X, expand=True, padx=5)
3739
+ self.tool_menu = self.tool_search_palette
3740
+ self.tool_dropdown_menu = None
3741
+ self.logger.info("Top search bar created with ToolSearchPalette")
3742
+ else:
3743
+ # Fallback: simple label
3744
+ self.tool_search_palette = None
3745
+ self.tool_menu = None
3746
+ self.tool_dropdown_menu = None
3747
+ ttk.Label(parent, text="Tool search not available").pack()
3748
+
3727
3749
 
3728
- self.tool_settings_frame = ttk.Frame(parent)
3729
- self.tool_settings_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
3750
+ def create_tool_widgets(self, parent):
3751
+ """Creates widgets for the tool options/settings section (bottom panel).
3752
+
3753
+ Note: The search bar is now created separately by _create_top_search_bar()
3754
+ at the top of the main layout.
3755
+ """
3756
+ # Get tool list for fallback
3757
+ if TOOL_LOADER_AVAILABLE and self.tool_loader:
3758
+ self.tool_options = self.tool_loader.get_available_tools()
3759
+ else:
3760
+ self.tool_options = [
3761
+ "AI Tools", "Base64 Encoder/Decoder", "Case Tool", "Column Tools",
3762
+ "Cron Tool", "Diff Viewer", "Email Header Analyzer", "Extraction Tools",
3763
+ "Find & Replace Text", "Folder File Reporter", "Generator Tools",
3764
+ "JSON/XML Tool", "Line Tools", "Markdown Tools",
3765
+ "Number Base Converter", "Sorter Tools",
3766
+ "String Escape Tool", "Text Statistics", "Text Wrapper", "Timestamp Converter",
3767
+ "Translator Tools", "URL Parser", "Whitespace Tools"
3768
+ ]
3769
+ self.filtered_tool_options = list(self.tool_options)
3770
+
3771
+ # Tool settings panel (with collapsible wrapper if available)
3772
+ if COLLAPSIBLE_PANEL_AVAILABLE:
3773
+ # Get collapsed state from settings
3774
+ ui_layout = self.settings.get("ui_layout", {})
3775
+ initial_collapsed = ui_layout.get("options_panel_collapsed", False)
3776
+
3777
+ # Create CollapsiblePanel to wrap tool settings
3778
+ self.options_collapsible = CollapsiblePanel(
3779
+ parent,
3780
+ title="Tool Options",
3781
+ collapsed=initial_collapsed,
3782
+ on_state_change=self._on_options_panel_toggle
3783
+ )
3784
+ self.options_collapsible.pack(fill=tk.BOTH, expand=True, padx=5)
3785
+
3786
+ # Tool settings go inside the collapsible panel's content
3787
+ self.tool_settings_frame = ttk.Frame(self.options_collapsible.content_frame)
3788
+ self.tool_settings_frame.pack(fill=tk.BOTH, expand=True)
3789
+
3790
+ self.logger.info(f"Options panel wrapped in CollapsiblePanel (collapsed={initial_collapsed})")
3791
+ else:
3792
+ # Fallback: Tool settings frame without collapsible wrapper
3793
+ self.tool_settings_frame = ttk.Frame(parent)
3794
+ self.tool_settings_frame.pack(fill=tk.BOTH, expand=True, padx=5)
3795
+ self.options_collapsible = None
3730
3796
 
3731
3797
  # Initialize Widget Cache for tool settings UI
3732
3798
  if WIDGET_CACHE_AVAILABLE:
@@ -3741,9 +3807,262 @@ class PromeraAIApp(tk.Tk):
3741
3807
  self.widget_cache = None
3742
3808
 
3743
3809
  self.update_tool_settings_ui()
3810
+
3811
+ def _create_tool_search_palette(self, parent):
3812
+ """Create the new ToolSearchPalette for tool selection."""
3813
+ # Get UI layout settings
3814
+ ui_layout = self.settings.get("ui_layout", {})
3815
+
3816
+ self.tool_search_palette = ToolSearchPalette(
3817
+ parent,
3818
+ tool_loader=self.tool_loader,
3819
+ on_tool_selected=self._on_palette_tool_selected,
3820
+ settings=self.settings,
3821
+ on_settings_change=self._on_ui_settings_change
3822
+ )
3823
+ # Pack to fill horizontally (top-center layout)
3824
+ self.tool_search_palette.pack(fill=tk.BOTH, expand=True, padx=5)
3825
+
3826
+ # For backwards compatibility, also keep a reference as tool_menu
3827
+ self.tool_menu = self.tool_search_palette
3828
+ self.tool_dropdown_menu = None # Not used with palette
3829
+
3830
+ self.logger.info("Tool Search Palette initialized with fuzzy search")
3831
+
3832
+ def _create_legacy_tool_dropdown(self, parent):
3833
+ """Create the legacy dropdown menu for tool selection (fallback)."""
3834
+ # Create custom dropdown that opens upwards
3835
+ self.tool_menu = ttk.Menubutton(parent, textvariable=self.tool_var, direction="above", width=25)
3836
+ self.tool_menu.pack(side=tk.LEFT, padx=5)
3837
+
3838
+ # Create the dropdown menu
3839
+ self.tool_dropdown_menu = tk.Menu(self.tool_menu, tearoff=0)
3840
+ self.tool_menu.config(menu=self.tool_dropdown_menu)
3841
+
3842
+ # Populate the dropdown menu with all tools
3843
+ self.update_tool_dropdown_menu()
3844
+
3845
+ self.tool_search_palette = None # Not available in legacy mode
3846
+ self.logger.info("Using legacy tool dropdown (ToolSearchPalette not available)")
3847
+
3848
+ # Sub-tools that should redirect to their parent category
3849
+ SUB_TOOL_TO_PARENT = {
3850
+ # AI Tools (11 sub-tools)
3851
+ "Google AI": "AI Tools",
3852
+ "Vertex AI": "AI Tools",
3853
+ "Azure AI": "AI Tools",
3854
+ "Anthropic AI": "AI Tools",
3855
+ "OpenAI": "AI Tools",
3856
+ "Cohere AI": "AI Tools",
3857
+ "HuggingFace AI": "AI Tools",
3858
+ "Groq AI": "AI Tools",
3859
+ "OpenRouterAI": "AI Tools",
3860
+ "LM Studio": "AI Tools",
3861
+ "AWS Bedrock": "AI Tools",
3862
+ # Generator Tools (8 sub-tools)
3863
+ "Strong Password Generator": "Generator Tools",
3864
+ "Repeating Text Generator": "Generator Tools",
3865
+ "Lorem Ipsum Generator": "Generator Tools",
3866
+ "UUID/GUID Generator": "Generator Tools",
3867
+ "Random Email Generator": "Generator Tools",
3868
+ "ASCII Art Generator": "Generator Tools",
3869
+ "Hash Generator": "Generator Tools",
3870
+ "Slug Generator": "Generator Tools",
3871
+ # Extraction Tools (4 sub-tools)
3872
+ "Email Extraction": "Extraction Tools",
3873
+ "HTML Tool": "Extraction Tools",
3874
+ "Regex Extractor": "Extraction Tools",
3875
+ "URL Link Extractor": "Extraction Tools",
3876
+ # Line Tools (6 sub-tools)
3877
+ "Remove Duplicates": "Line Tools",
3878
+ "Remove Empty Lines": "Line Tools",
3879
+ "Add Line Numbers": "Line Tools",
3880
+ "Remove Line Numbers": "Line Tools",
3881
+ "Reverse Lines": "Line Tools",
3882
+ "Shuffle Lines": "Line Tools",
3883
+ # Markdown Tools (5 sub-tools)
3884
+ "Strip Markdown": "Markdown Tools",
3885
+ "Extract Links": "Markdown Tools",
3886
+ "Extract Headers": "Markdown Tools",
3887
+ "Table to CSV": "Markdown Tools",
3888
+ "Format Table": "Markdown Tools",
3889
+ # Sorter Tools (2 sub-tools)
3890
+ "Number Sorter": "Sorter Tools",
3891
+ "Alphabetical Sorter": "Sorter Tools",
3892
+ # Text Wrapper (5 sub-tools)
3893
+ "Word Wrap": "Text Wrapper",
3894
+ "Justify Text": "Text Wrapper",
3895
+ "Prefix/Suffix": "Text Wrapper",
3896
+ "Indent Text": "Text Wrapper",
3897
+ "Quote Text": "Text Wrapper",
3898
+ # Translator Tools (2 sub-tools)
3899
+ "Morse Code Translator": "Translator Tools",
3900
+ "Binary Code Translator": "Translator Tools",
3901
+ # Whitespace Tools (4 sub-tools)
3902
+ "Trim Lines": "Whitespace Tools",
3903
+ "Remove Extra Spaces": "Whitespace Tools",
3904
+ "Tabs/Spaces Converter": "Whitespace Tools",
3905
+ "Normalize Line Endings": "Whitespace Tools",
3906
+ # Other sub-tools
3907
+ "Word Frequency Counter": "Text Statistics",
3908
+ }
3909
+
3910
+ # Tab index within parent category for sub-tools (0-indexed)
3911
+ # Based on actual tab order in UI from screenshot
3912
+ SUB_TOOL_TAB_INDEX = {
3913
+ # AI Tools tabs (11 tabs):
3914
+ "Google AI": 0,
3915
+ "Vertex AI": 1,
3916
+ "Azure AI": 2,
3917
+ "Anthropic AI": 3,
3918
+ "OpenAI": 4,
3919
+ "Cohere AI": 5,
3920
+ "HuggingFace AI": 6,
3921
+ "Groq AI": 7,
3922
+ "OpenRouterAI": 8,
3923
+ "LM Studio": 9,
3924
+ "AWS Bedrock": 10,
3925
+ # Generator Tools tabs (8 tabs):
3926
+ "Strong Password Generator": 0,
3927
+ "Repeating Text Generator": 1,
3928
+ "Lorem Ipsum Generator": 2,
3929
+ "UUID/GUID Generator": 3,
3930
+ "Random Email Generator": 4,
3931
+ "ASCII Art Generator": 5,
3932
+ "Hash Generator": 6,
3933
+ "Slug Generator": 7,
3934
+ # Extraction Tools tabs (4 tabs):
3935
+ "Email Extraction": 0,
3936
+ "HTML Tool": 1,
3937
+ "Regex Extractor": 2,
3938
+ "URL Link Extractor": 3,
3939
+ # Line Tools tabs (6 tabs):
3940
+ "Remove Duplicates": 0,
3941
+ "Remove Empty Lines": 1,
3942
+ "Add Line Numbers": 2,
3943
+ "Remove Line Numbers": 3,
3944
+ "Reverse Lines": 4,
3945
+ "Shuffle Lines": 5,
3946
+ # Markdown Tools tabs (5 tabs):
3947
+ "Strip Markdown": 0,
3948
+ "Extract Links": 1,
3949
+ "Extract Headers": 2,
3950
+ "Table to CSV": 3,
3951
+ "Format Table": 4,
3952
+ # Sorter Tools tabs (2 tabs):
3953
+ "Number Sorter": 0,
3954
+ "Alphabetical Sorter": 1,
3955
+ # Text Wrapper tabs (5 tabs):
3956
+ "Word Wrap": 0,
3957
+ "Justify Text": 1,
3958
+ "Prefix/Suffix": 2,
3959
+ "Indent Text": 3,
3960
+ "Quote Text": 4,
3961
+ # Translator Tools tabs (2 tabs):
3962
+ "Morse Code Translator": 0,
3963
+ "Binary Code Translator": 1,
3964
+ # Whitespace Tools tabs (4 tabs):
3965
+ "Trim Lines": 0,
3966
+ "Remove Extra Spaces": 1,
3967
+ "Tabs/Spaces Converter": 2,
3968
+ "Normalize Line Endings": 3,
3969
+ }
3970
+
3971
+ def _on_palette_tool_selected(self, tool_name):
3972
+ """Handle tool selection from the ToolSearchPalette."""
3973
+ # Map sub-tools to their parent category
3974
+ actual_tool = self.SUB_TOOL_TO_PARENT.get(tool_name, tool_name)
3975
+ sub_tool_tab = self.SUB_TOOL_TAB_INDEX.get(tool_name)
3976
+
3977
+ if actual_tool != tool_name:
3978
+ self.logger.debug(f"Mapped sub-tool '{tool_name}' to parent '{actual_tool}' (tab {sub_tool_tab})")
3979
+
3980
+ self.tool_var.set(actual_tool)
3981
+
3982
+ # Auto-expand options panel if collapsed
3983
+ if hasattr(self, 'options_collapsible') and self.options_collapsible:
3984
+ if self.options_collapsible.collapsed:
3985
+ self.options_collapsible.expand()
3986
+ self.logger.debug("Auto-expanded options panel on tool select")
3987
+
3988
+ self.on_tool_selected()
3989
+
3990
+ # Schedule tab selection after UI updates (if sub-tool with tab index)
3991
+ if sub_tool_tab is not None:
3992
+ self.after(100, lambda: self._select_tool_tab(actual_tool, sub_tool_tab))
3993
+
3994
+ def _select_tool_tab(self, tool_name: str, tab_index: int):
3995
+ """Select a specific tab within a tool's tabbed interface."""
3996
+ try:
3997
+ # Recursively find notebooks in tool_settings_frame
3998
+ def find_notebook(widget):
3999
+ if isinstance(widget, ttk.Notebook):
4000
+ return widget
4001
+ for child in widget.winfo_children():
4002
+ result = find_notebook(child)
4003
+ if result:
4004
+ return result
4005
+ return None
4006
+
4007
+ notebook = find_notebook(self.tool_settings_frame)
4008
+ if notebook:
4009
+ if 0 <= tab_index < notebook.index('end'):
4010
+ notebook.select(tab_index)
4011
+ self.logger.debug(f"Selected tab {tab_index} for {tool_name}")
4012
+ else:
4013
+ self.logger.warning(f"Tab index {tab_index} out of range for {tool_name}")
4014
+ except Exception as e:
4015
+ self.logger.warning(f"Could not select tab {tab_index} for {tool_name}: {e}")
4016
+
4017
+ def _on_ui_settings_change(self, settings_update):
4018
+ """Handle UI settings change from ToolSearchPalette."""
4019
+ try:
4020
+ # Merge with existing settings
4021
+ if "ui_layout" in settings_update:
4022
+ current_layout = self.settings.get("ui_layout", {})
4023
+ current_layout.update(settings_update["ui_layout"])
4024
+ self.settings["ui_layout"] = current_layout
4025
+
4026
+ # Save settings
4027
+ if hasattr(self, 'db_settings_manager'):
4028
+ self.db_settings_manager.save_settings(self.settings)
4029
+ except Exception as e:
4030
+ self.logger.warning(f"Error saving UI settings: {e}")
4031
+
4032
+ def _on_options_panel_toggle(self, collapsed: bool):
4033
+ """Handle options panel toggle (save collapsed state to settings)."""
4034
+ try:
4035
+ current_layout = self.settings.get("ui_layout", {})
4036
+ current_layout["options_panel_collapsed"] = collapsed
4037
+ self.settings["ui_layout"] = current_layout
4038
+
4039
+ # Save settings
4040
+ if hasattr(self, 'db_settings_manager'):
4041
+ self.db_settings_manager.save_settings(self.settings)
4042
+
4043
+ self.logger.debug(f"Options panel collapsed: {collapsed}")
4044
+ except Exception as e:
4045
+ self.logger.warning(f"Error saving options panel state: {e}")
4046
+
4047
+ def toggle_options_panel(self, event=None):
4048
+ """Toggle the options panel visibility (Ctrl+Shift+H shortcut)."""
4049
+ if hasattr(self, 'options_collapsible') and self.options_collapsible:
4050
+ self.options_collapsible.toggle()
4051
+ return "break"
4052
+ return None
4053
+
4054
+ def focus_tool_search(self, event=None):
4055
+ """Focus the tool search entry (Ctrl+K shortcut)."""
4056
+ if self.tool_search_palette:
4057
+ self.tool_search_palette.focus_search()
4058
+ return "break"
4059
+ return None
3744
4060
 
3745
4061
  def update_tool_dropdown_menu(self):
3746
4062
  """Updates the tool dropdown menu with all available tools."""
4063
+ if self.tool_dropdown_menu is None:
4064
+ return # Using ToolSearchPalette instead
4065
+
3747
4066
  # Clear existing menu items
3748
4067
  self.tool_dropdown_menu.delete(0, tk.END)
3749
4068
 
@@ -3837,7 +4156,8 @@ class PromeraAIApp(tk.Tk):
3837
4156
  self.curl_tool_window,
3838
4157
  logger=self.logger,
3839
4158
  send_to_input_callback=self.send_content_to_input_tab,
3840
- dialog_manager=self.dialog_manager
4159
+ dialog_manager=self.dialog_manager,
4160
+ db_settings_manager=getattr(self, 'db_settings_manager', None)
3841
4161
  )
3842
4162
 
3843
4163
  # Add context menus to cURL tool widgets
@@ -4465,8 +4785,8 @@ class PromeraAIApp(tk.Tk):
4465
4785
  self.after(10, self.diff_viewer_widget.update_tab_labels)
4466
4786
  self.diff_viewer_widget.run_comparison()
4467
4787
  else:
4468
- # Show placeholder when module not available
4469
- self.diff_frame.grid(row=0, column=0, sticky="nsew", pady=5)
4788
+ # Show placeholder when module not available (row=1 same as central_frame)
4789
+ self.diff_frame.grid(row=1, column=0, sticky="nsew", pady=5)
4470
4790
  self.update_tool_settings_ui()
4471
4791
  else:
4472
4792
  # Sync diff viewer content before switching away
@@ -4742,33 +5062,37 @@ class PromeraAIApp(tk.Tk):
4742
5062
 
4743
5063
  self._current_tool_ui = tool_name
4744
5064
  tool_settings = self.settings["tool_settings"].get(tool_name, {})
4745
-
5065
+
5066
+ # Use tool_settings_frame directly as parent
5067
+ # Note: Centering was causing layout issues (e.g. Folder File Reporter cut off)
5068
+ parent_frame = self.tool_settings_frame
5069
+
4746
5070
  if tool_name in ["Google AI", "Anthropic AI", "OpenAI", "Cohere AI", "HuggingFace AI", "Groq AI", "OpenRouterAI"]:
4747
- self.create_ai_provider_widgets(self.tool_settings_frame, tool_name)
5071
+ self.create_ai_provider_widgets(parent_frame, tool_name)
4748
5072
  elif tool_name == "AI Tools":
4749
- self.create_ai_tools_widget(self.tool_settings_frame)
5073
+ self.create_ai_tools_widget(parent_frame)
4750
5074
  elif tool_name == "Case Tool":
4751
5075
  if CASE_TOOL_MODULE_AVAILABLE and self.case_tool:
4752
5076
  self.case_tool_ui = self.case_tool.create_ui(
4753
- self.tool_settings_frame,
5077
+ parent_frame,
4754
5078
  tool_settings,
4755
5079
  on_setting_change_callback=self.on_tool_setting_change,
4756
5080
  apply_tool_callback=self.apply_tool
4757
5081
  )
4758
5082
  else:
4759
- ttk.Label(self.tool_settings_frame, text="Case Tool module not available").pack()
5083
+ ttk.Label(parent_frame, text="Case Tool module not available").pack()
4760
5084
  elif tool_name == "Translator Tools":
4761
- self.create_translator_tools_widget(self.tool_settings_frame)
5085
+ self.create_translator_tools_widget(parent_frame)
4762
5086
  elif tool_name == "Base64 Encoder/Decoder":
4763
- self.create_base64_tools_widget(self.tool_settings_frame)
5087
+ self.create_base64_tools_widget(parent_frame)
4764
5088
  elif tool_name == "JSON/XML Tool":
4765
- self.create_jsonxml_tool_widget(self.tool_settings_frame)
5089
+ self.create_jsonxml_tool_widget(parent_frame)
4766
5090
  elif tool_name == "Cron Tool":
4767
- self.create_cron_tool_widget(self.tool_settings_frame)
5091
+ self.create_cron_tool_widget(parent_frame)
4768
5092
  elif tool_name == "Extraction Tools":
4769
- self.create_extraction_tools_widget(self.tool_settings_frame)
5093
+ self.create_extraction_tools_widget(parent_frame)
4770
5094
  elif tool_name == "Sorter Tools":
4771
- self.create_sorter_tools_widget(self.tool_settings_frame)
5095
+ self.create_sorter_tools_widget(parent_frame)
4772
5096
  elif tool_name == "Find & Replace Text":
4773
5097
  if FIND_REPLACE_MODULE_AVAILABLE and self.find_replace_widget:
4774
5098
  # Set up callback for settings changes
@@ -4781,36 +5105,36 @@ class PromeraAIApp(tk.Tk):
4781
5105
  self.input_notebook, self.output_notebook
4782
5106
  )
4783
5107
  # Create the widgets
4784
- self.find_replace_widget.create_widgets(self.tool_settings_frame, tool_settings)
5108
+ self.find_replace_widget.create_widgets(parent_frame, tool_settings)
4785
5109
  else:
4786
5110
  # Find & Replace module not available
4787
- ttk.Label(self.tool_settings_frame, text="Find & Replace module not available").pack()
5111
+ ttk.Label(parent_frame, text="Find & Replace module not available").pack()
4788
5112
  elif tool_name == "Generator Tools":
4789
- self.create_generator_tools_widget(self.tool_settings_frame)
5113
+ self.create_generator_tools_widget(parent_frame)
4790
5114
  elif tool_name == "URL Parser":
4791
5115
  if URL_PARSER_MODULE_AVAILABLE and self.url_parser:
4792
5116
  tool_settings = self.settings["tool_settings"].get("URL Parser", {
4793
5117
  "ascii_decode": True
4794
5118
  })
4795
5119
  self.url_parser_ui = self.url_parser.create_ui(
4796
- self.tool_settings_frame,
5120
+ parent_frame,
4797
5121
  tool_settings,
4798
5122
  on_setting_change_callback=self.on_tool_setting_change,
4799
5123
  apply_tool_callback=self.apply_tool
4800
5124
  )
4801
5125
  else:
4802
- ttk.Label(self.tool_settings_frame, text="URL Parser module not available").pack()
5126
+ ttk.Label(parent_frame, text="URL Parser module not available").pack()
4803
5127
  elif tool_name == "Diff Viewer":
4804
5128
  if DIFF_VIEWER_MODULE_AVAILABLE:
4805
5129
  # Use the new modular settings widget
4806
5130
  self.diff_settings_widget = DiffViewerSettingsWidget(
4807
- self.tool_settings_frame,
5131
+ parent_frame,
4808
5132
  self.diff_viewer_widget,
4809
5133
  self.on_tool_setting_change
4810
5134
  )
4811
5135
  else:
4812
5136
  # Module not available - show message
4813
- ttk.Label(self.tool_settings_frame, text="Diff Viewer module not available").pack(side=tk.LEFT, padx=10)
5137
+ ttk.Label(parent_frame, text="Diff Viewer module not available").pack(side=tk.LEFT, padx=10)
4814
5138
  elif tool_name == "Email Extraction Tool":
4815
5139
  if EMAIL_EXTRACTION_MODULE_AVAILABLE and self.email_extraction_tool:
4816
5140
  tool_settings = self.settings["tool_settings"].get("Email Extraction Tool", {
@@ -4820,13 +5144,13 @@ class PromeraAIApp(tk.Tk):
4820
5144
  "only_domain": False
4821
5145
  })
4822
5146
  self.email_extraction_ui = self.email_extraction_tool.create_ui(
4823
- self.tool_settings_frame,
5147
+ parent_frame,
4824
5148
  tool_settings,
4825
5149
  on_setting_change_callback=self.on_tool_setting_change,
4826
5150
  apply_tool_callback=self.apply_tool
4827
5151
  )
4828
5152
  else:
4829
- ttk.Label(self.tool_settings_frame, text="Email Extraction Tool module not available").pack()
5153
+ ttk.Label(parent_frame, text="Email Extraction Tool module not available").pack()
4830
5154
  elif tool_name == "Email Header Analyzer":
4831
5155
  if EMAIL_HEADER_ANALYZER_MODULE_AVAILABLE and self.email_header_analyzer:
4832
5156
  tool_settings = self.settings["tool_settings"].get("Email Header Analyzer", {
@@ -4836,13 +5160,13 @@ class PromeraAIApp(tk.Tk):
4836
5160
  "show_spam_score": True
4837
5161
  })
4838
5162
  self.email_header_analyzer_ui = self.email_header_analyzer.create_ui(
4839
- self.tool_settings_frame,
5163
+ parent_frame,
4840
5164
  tool_settings,
4841
5165
  on_setting_change_callback=self.on_tool_setting_change,
4842
5166
  apply_tool_callback=self.apply_tool
4843
5167
  )
4844
5168
  else:
4845
- ttk.Label(self.tool_settings_frame, text="Email Header Analyzer module not available").pack()
5169
+ ttk.Label(parent_frame, text="Email Header Analyzer module not available").pack()
4846
5170
  elif tool_name == "Folder File Reporter":
4847
5171
  if FOLDER_FILE_REPORTER_MODULE_AVAILABLE and self.folder_file_reporter:
4848
5172
  tool_settings = self.settings["tool_settings"].get("Folder File Reporter", {
@@ -4850,29 +5174,29 @@ class PromeraAIApp(tk.Tk):
4850
5174
  "last_output_folder": ""
4851
5175
  })
4852
5176
  self.folder_file_reporter.load_settings(tool_settings)
4853
- self.folder_file_reporter_ui = self.folder_file_reporter.create_ui(self.tool_settings_frame)
5177
+ self.folder_file_reporter_ui = self.folder_file_reporter.create_ui(parent_frame)
4854
5178
  else:
4855
- ttk.Label(self.tool_settings_frame, text="Folder File Reporter module not available").pack()
5179
+ ttk.Label(parent_frame, text="Folder File Reporter module not available").pack()
4856
5180
  elif tool_name == "Line Tools":
4857
- self.create_line_tools_widget(self.tool_settings_frame)
5181
+ self.create_line_tools_widget(parent_frame)
4858
5182
  elif tool_name == "Whitespace Tools":
4859
- self.create_whitespace_tools_widget(self.tool_settings_frame)
5183
+ self.create_whitespace_tools_widget(parent_frame)
4860
5184
  elif tool_name == "Text Statistics":
4861
- self.create_text_statistics_widget(self.tool_settings_frame)
5185
+ self.create_text_statistics_widget(parent_frame)
4862
5186
  elif tool_name == "Markdown Tools":
4863
- self.create_markdown_tools_widget(self.tool_settings_frame)
5187
+ self.create_markdown_tools_widget(parent_frame)
4864
5188
  elif tool_name == "String Escape Tool":
4865
- self.create_string_escape_tool_widget(self.tool_settings_frame)
5189
+ self.create_string_escape_tool_widget(parent_frame)
4866
5190
  elif tool_name == "Number Base Converter":
4867
- self.create_number_base_converter_widget(self.tool_settings_frame)
5191
+ self.create_number_base_converter_widget(parent_frame)
4868
5192
  elif tool_name == "Text Wrapper":
4869
- self.create_text_wrapper_widget(self.tool_settings_frame)
5193
+ self.create_text_wrapper_widget(parent_frame)
4870
5194
  elif tool_name == "Slug Generator":
4871
- self.create_slug_generator_widget(self.tool_settings_frame)
5195
+ self.create_slug_generator_widget(parent_frame)
4872
5196
  elif tool_name == "Column Tools":
4873
- self.create_column_tools_widget(self.tool_settings_frame)
5197
+ self.create_column_tools_widget(parent_frame)
4874
5198
  elif tool_name == "Timestamp Converter":
4875
- self.create_timestamp_converter_widget(self.tool_settings_frame)
5199
+ self.create_timestamp_converter_widget(parent_frame)
4876
5200
 
4877
5201
 
4878
5202