pomera-ai-commander 1.2.8 → 1.2.10

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.
@@ -0,0 +1,460 @@
1
+ """
2
+ Load Presets Dialog Module
3
+
4
+ Provides a dialog for users to view and reset tool settings to their defaults.
5
+ Shows all registered tools from SettingsDefaultsRegistry and allows selective reset.
6
+
7
+ Author: Pomera AI Commander Team
8
+ """
9
+
10
+ import tkinter as tk
11
+ from tkinter import ttk, messagebox
12
+ import logging
13
+ from typing import Dict, Any, List, Optional
14
+
15
+
16
+ class LoadPresetsDialog:
17
+ """
18
+ Dialog for loading/resetting tool presets to defaults.
19
+
20
+ Features:
21
+ - Lists all registered tools from SettingsDefaultsRegistry
22
+ - Shows current vs default values preview
23
+ - Allows selective reset of individual tools
24
+ - Confirmation dialog before reset
25
+ """
26
+
27
+ def __init__(self, parent, settings_manager, logger=None, dialog_manager=None):
28
+ """
29
+ Initialize the Load Presets Dialog.
30
+
31
+ Args:
32
+ parent: Parent window
33
+ settings_manager: Settings manager instance (database or file-based)
34
+ logger: Logger instance
35
+ dialog_manager: Optional DialogManager for consistent dialogs
36
+ """
37
+ self.parent = parent
38
+ self.settings_manager = settings_manager
39
+ self.logger = logger or logging.getLogger(__name__)
40
+ self.dialog_manager = dialog_manager
41
+
42
+ # Get registry
43
+ try:
44
+ from core.settings_defaults_registry import get_registry
45
+ self.registry = get_registry()
46
+ except ImportError:
47
+ self.registry = None
48
+ self.logger.error("SettingsDefaultsRegistry not available")
49
+
50
+ # Dialog window
51
+ self.dialog = None
52
+ self.tree = None
53
+ self.preview_text = None
54
+ self.selected_tools = set()
55
+
56
+ def show(self):
57
+ """Show the Load Presets dialog."""
58
+ if not self.registry:
59
+ self._show_error("Error", "Settings registry not available.")
60
+ return
61
+
62
+ self.dialog = tk.Toplevel(self.parent)
63
+ self.dialog.title("Load Presets - Reset Tool Settings")
64
+ self.dialog.geometry("800x550")
65
+ self.dialog.transient(self.parent)
66
+ self.dialog.grab_set()
67
+
68
+ # Make dialog modal
69
+ self.dialog.focus_set()
70
+
71
+ # Create main layout
72
+ self._create_widgets()
73
+
74
+ # Populate tools list
75
+ self._populate_tools_list()
76
+
77
+ # Center on parent
78
+ self._center_on_parent()
79
+
80
+ def _center_on_parent(self):
81
+ """Center dialog on parent window."""
82
+ self.dialog.update_idletasks()
83
+ parent_x = self.parent.winfo_x()
84
+ parent_y = self.parent.winfo_y()
85
+ parent_w = self.parent.winfo_width()
86
+ parent_h = self.parent.winfo_height()
87
+
88
+ dialog_w = self.dialog.winfo_width()
89
+ dialog_h = self.dialog.winfo_height()
90
+
91
+ x = parent_x + (parent_w - dialog_w) // 2
92
+ y = parent_y + (parent_h - dialog_h) // 2
93
+
94
+ self.dialog.geometry(f"+{x}+{y}")
95
+
96
+ def _create_widgets(self):
97
+ """Create the dialog widgets."""
98
+ main_frame = ttk.Frame(self.dialog, padding="10")
99
+ main_frame.pack(fill=tk.BOTH, expand=True)
100
+
101
+ # Title and description
102
+ title_label = ttk.Label(
103
+ main_frame,
104
+ text="Reset Tool Settings to Defaults",
105
+ font=("TkDefaultFont", 12, "bold")
106
+ )
107
+ title_label.pack(anchor="w", pady=(0, 5))
108
+
109
+ desc_label = ttk.Label(
110
+ main_frame,
111
+ text="Select tools to reset to their default settings. Current settings will be replaced.",
112
+ foreground="gray"
113
+ )
114
+ desc_label.pack(anchor="w", pady=(0, 10))
115
+
116
+ # Split pane: left=tools list, right=preview
117
+ paned = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
118
+ paned.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
119
+
120
+ # Left side: Tools list with checkboxes
121
+ left_frame = ttk.LabelFrame(paned, text="Tools with Defaults", padding="5")
122
+ paned.add(left_frame, weight=1)
123
+
124
+ # Select All / Deselect All buttons
125
+ btn_frame = ttk.Frame(left_frame)
126
+ btn_frame.pack(fill=tk.X, pady=(0, 5))
127
+
128
+ ttk.Button(btn_frame, text="Select All", command=self._select_all, width=12).pack(side=tk.LEFT, padx=(0, 5))
129
+ ttk.Button(btn_frame, text="Deselect All", command=self._deselect_all, width=12).pack(side=tk.LEFT)
130
+
131
+ # Treeview for tools
132
+ tree_frame = ttk.Frame(left_frame)
133
+ tree_frame.pack(fill=tk.BOTH, expand=True)
134
+
135
+ self.tree = ttk.Treeview(
136
+ tree_frame,
137
+ columns=("selected", "description"),
138
+ show="tree headings",
139
+ selectmode="browse"
140
+ )
141
+
142
+ self.tree.heading("#0", text="Tool Name")
143
+ self.tree.heading("selected", text="Reset?")
144
+ self.tree.heading("description", text="Description")
145
+
146
+ self.tree.column("#0", width=180, minwidth=150)
147
+ self.tree.column("selected", width=60, minwidth=50, anchor="center")
148
+ self.tree.column("description", width=150, minwidth=100)
149
+
150
+ # Scrollbar
151
+ scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
152
+ self.tree.configure(yscrollcommand=scrollbar.set)
153
+
154
+ self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
155
+ scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
156
+
157
+ # Bind selection and toggle
158
+ self.tree.bind("<<TreeviewSelect>>", self._on_tool_select)
159
+ self.tree.bind("<Double-1>", self._toggle_tool_selection)
160
+ self.tree.bind("<space>", self._toggle_tool_selection)
161
+
162
+ # Right side: Preview panel
163
+ right_frame = ttk.LabelFrame(paned, text="Settings Preview", padding="5")
164
+ paned.add(right_frame, weight=1)
165
+
166
+ self.preview_text = tk.Text(
167
+ right_frame,
168
+ wrap=tk.WORD,
169
+ font=("Consolas", 9),
170
+ state="disabled",
171
+ bg="#f5f5f5"
172
+ )
173
+ self.preview_text.pack(fill=tk.BOTH, expand=True)
174
+
175
+ # Bottom buttons
176
+ button_frame = ttk.Frame(main_frame)
177
+ button_frame.pack(fill=tk.X, pady=(10, 0))
178
+
179
+ # Left: Info about selected count
180
+ self.selected_count_label = ttk.Label(button_frame, text="0 tools selected for reset")
181
+ self.selected_count_label.pack(side=tk.LEFT)
182
+
183
+ # Right: Action buttons
184
+ ttk.Button(
185
+ button_frame,
186
+ text="Cancel",
187
+ command=self.dialog.destroy
188
+ ).pack(side=tk.RIGHT, padx=(5, 0))
189
+
190
+ self.reset_button = ttk.Button(
191
+ button_frame,
192
+ text="Reset Selected Tools",
193
+ command=self._reset_selected_tools,
194
+ style="Accent.TButton"
195
+ )
196
+ self.reset_button.pack(side=tk.RIGHT)
197
+ self.reset_button.config(state="disabled")
198
+
199
+ # Add Edit Defaults File button
200
+ ttk.Button(
201
+ button_frame,
202
+ text="Edit Defaults File",
203
+ command=self._open_defaults_file
204
+ ).pack(side=tk.RIGHT, padx=(0, 10))
205
+
206
+ def _populate_tools_list(self):
207
+ """Populate the tools treeview from registry."""
208
+ if not self.registry:
209
+ return
210
+
211
+ registered_tools = self.registry.get_registered_tools()
212
+
213
+ for tool_name in sorted(registered_tools):
214
+ spec = self.registry.get_tool_spec(tool_name)
215
+ description = spec.description if spec else ""
216
+
217
+ self.tree.insert(
218
+ "",
219
+ "end",
220
+ iid=tool_name,
221
+ text=tool_name,
222
+ values=("☐", description)
223
+ )
224
+
225
+ def _on_tool_select(self, event=None):
226
+ """Handle tool selection - show preview."""
227
+ selection = self.tree.selection()
228
+ if not selection:
229
+ return
230
+
231
+ tool_name = selection[0]
232
+ self._show_preview(tool_name)
233
+
234
+ def _toggle_tool_selection(self, event=None):
235
+ """Toggle the selection checkbox for a tool."""
236
+ selection = self.tree.selection()
237
+ if not selection:
238
+ return
239
+
240
+ tool_name = selection[0]
241
+
242
+ if tool_name in self.selected_tools:
243
+ self.selected_tools.discard(tool_name)
244
+ self.tree.set(tool_name, "selected", "☐")
245
+ else:
246
+ self.selected_tools.add(tool_name)
247
+ self.tree.set(tool_name, "selected", "☑")
248
+
249
+ self._update_selected_count()
250
+
251
+ def _select_all(self):
252
+ """Select all tools for reset."""
253
+ for item in self.tree.get_children():
254
+ self.selected_tools.add(item)
255
+ self.tree.set(item, "selected", "☑")
256
+ self._update_selected_count()
257
+
258
+ def _deselect_all(self):
259
+ """Deselect all tools."""
260
+ self.selected_tools.clear()
261
+ for item in self.tree.get_children():
262
+ self.tree.set(item, "selected", "☐")
263
+ self._update_selected_count()
264
+
265
+ def _update_selected_count(self):
266
+ """Update the selected count label and button state."""
267
+ count = len(self.selected_tools)
268
+ self.selected_count_label.config(text=f"{count} tool(s) selected for reset")
269
+
270
+ if count > 0:
271
+ self.reset_button.config(state="normal")
272
+ else:
273
+ self.reset_button.config(state="disabled")
274
+
275
+ def _show_preview(self, tool_name: str):
276
+ """Show current vs default settings preview for a tool."""
277
+ self.preview_text.config(state="normal")
278
+ self.preview_text.delete("1.0", tk.END)
279
+
280
+ try:
281
+ # Get defaults from registry
282
+ defaults = self.registry.get_tool_defaults(tool_name)
283
+
284
+ # Get current settings
285
+ current = {}
286
+ if hasattr(self.settings_manager, 'get_tool_settings'):
287
+ current = self.settings_manager.get_tool_settings(tool_name)
288
+ elif hasattr(self.settings_manager, 'settings'):
289
+ tool_settings = self.settings_manager.settings.get("tool_settings", {})
290
+ current = tool_settings.get(tool_name, {})
291
+
292
+ # Format preview
293
+ preview = f"=== {tool_name} ===\n\n"
294
+ preview += "--- DEFAULTS (will be applied) ---\n"
295
+
296
+ for key, value in sorted(defaults.items()):
297
+ # Truncate long values
298
+ value_str = str(value)
299
+ if len(value_str) > 50:
300
+ value_str = value_str[:50] + "..."
301
+ preview += f" {key}: {value_str}\n"
302
+
303
+ if current:
304
+ preview += "\n--- CURRENT VALUES ---\n"
305
+ for key, value in sorted(current.items()):
306
+ value_str = str(value)
307
+ if len(value_str) > 50:
308
+ value_str = value_str[:50] + "..."
309
+ # Highlight differences
310
+ if key in defaults and defaults[key] != value:
311
+ preview += f" {key}: {value_str} ⟵ differs\n"
312
+ else:
313
+ preview += f" {key}: {value_str}\n"
314
+
315
+ self.preview_text.insert("1.0", preview)
316
+
317
+ except Exception as e:
318
+ self.preview_text.insert("1.0", f"Error loading preview: {e}")
319
+ self.logger.error(f"Error showing preview for {tool_name}: {e}")
320
+
321
+ self.preview_text.config(state="disabled")
322
+
323
+ def _reset_selected_tools(self):
324
+ """Reset selected tools to defaults after confirmation."""
325
+ if not self.selected_tools:
326
+ return
327
+
328
+ # Build confirmation message
329
+ tools_list = "\n".join(f" • {name}" for name in sorted(self.selected_tools))
330
+ message = (
331
+ f"The following {len(self.selected_tools)} tool(s) will have their "
332
+ f"settings reset to defaults:\n\n{tools_list}\n\n"
333
+ "This action cannot be undone. Continue?"
334
+ )
335
+
336
+ # Show confirmation
337
+ if not self._ask_yes_no("Confirm Reset", message):
338
+ return
339
+
340
+ # Perform reset
341
+ reset_count = 0
342
+ errors = []
343
+
344
+ for tool_name in self.selected_tools:
345
+ try:
346
+ defaults = self.registry.get_tool_defaults(tool_name)
347
+
348
+ # Apply defaults to settings manager
349
+ if hasattr(self.settings_manager, 'update_tool_settings'):
350
+ self.settings_manager.update_tool_settings(tool_name, defaults)
351
+ elif hasattr(self.settings_manager, 'set_tool_settings'):
352
+ self.settings_manager.set_tool_settings(tool_name, defaults)
353
+ elif hasattr(self.settings_manager, 'settings'):
354
+ if "tool_settings" not in self.settings_manager.settings:
355
+ self.settings_manager.settings["tool_settings"] = {}
356
+ self.settings_manager.settings["tool_settings"][tool_name] = defaults.copy()
357
+
358
+ reset_count += 1
359
+ self.logger.info(f"Reset '{tool_name}' to defaults")
360
+
361
+ except Exception as e:
362
+ errors.append(f"{tool_name}: {e}")
363
+ self.logger.error(f"Error resetting {tool_name}: {e}")
364
+
365
+ # Save settings
366
+ try:
367
+ if hasattr(self.settings_manager, 'save_settings'):
368
+ self.settings_manager.save_settings()
369
+ except Exception as e:
370
+ self.logger.error(f"Error saving settings: {e}")
371
+
372
+ # Show result
373
+ if errors:
374
+ self._show_warning(
375
+ "Partial Reset",
376
+ f"Reset {reset_count} tool(s) successfully.\n\n"
377
+ f"Errors occurred for:\n" + "\n".join(errors)
378
+ )
379
+ else:
380
+ self._show_info(
381
+ "Reset Complete",
382
+ f"Successfully reset {reset_count} tool(s) to defaults.\n\n"
383
+ "Please restart affected tools for changes to take effect."
384
+ )
385
+
386
+ # Close dialog
387
+ self.dialog.destroy()
388
+
389
+ def _show_info(self, title: str, message: str) -> bool:
390
+ """Show info dialog."""
391
+ if self.dialog_manager:
392
+ return self.dialog_manager.show_info(title, message, parent=self.dialog)
393
+ else:
394
+ messagebox.showinfo(title, message, parent=self.dialog)
395
+ return True
396
+
397
+ def _show_warning(self, title: str, message: str) -> bool:
398
+ """Show warning dialog."""
399
+ if self.dialog_manager:
400
+ return self.dialog_manager.show_warning(title, message, parent=self.dialog)
401
+ else:
402
+ messagebox.showwarning(title, message, parent=self.dialog)
403
+ return True
404
+
405
+ def _show_error(self, title: str, message: str) -> bool:
406
+ """Show error dialog."""
407
+ if self.dialog_manager:
408
+ return self.dialog_manager.show_error(title, message, parent=self.parent)
409
+ else:
410
+ messagebox.showerror(title, message, parent=self.parent)
411
+ return True
412
+
413
+ def _ask_yes_no(self, title: str, message: str) -> bool:
414
+ """Show yes/no confirmation dialog."""
415
+ if self.dialog_manager:
416
+ return self.dialog_manager.ask_yes_no(title, message, parent=self.dialog)
417
+ else:
418
+ return messagebox.askyesno(title, message, parent=self.dialog)
419
+
420
+ def _open_defaults_file(self):
421
+ """Open the defaults.json file in the system default editor."""
422
+ import os
423
+ import subprocess
424
+ import platform
425
+
426
+ try:
427
+ # Get path from registry
428
+ json_path = self.registry.get_json_defaults_path() if self.registry else None
429
+
430
+ if not json_path:
431
+ # Try default location
432
+ json_path = os.path.join(os.path.dirname(__file__), "..", "defaults.json")
433
+
434
+ if not os.path.exists(json_path):
435
+ self._show_error(
436
+ "File Not Found",
437
+ f"defaults.json not found at:\n{json_path}\n\n"
438
+ "The file will be created when you restart the app."
439
+ )
440
+ return
441
+
442
+ # Open in system default editor
443
+ if platform.system() == "Windows":
444
+ os.startfile(json_path)
445
+ elif platform.system() == "Darwin": # macOS
446
+ subprocess.run(["open", json_path])
447
+ else: # Linux
448
+ subprocess.run(["xdg-open", json_path])
449
+
450
+ self.logger.info(f"Opened defaults.json: {json_path}")
451
+
452
+ except Exception as e:
453
+ self._show_error("Error", f"Could not open defaults.json:\n{e}")
454
+ self.logger.error(f"Error opening defaults.json: {e}")
455
+
456
+
457
+ def get_registry():
458
+ """Get the settings defaults registry singleton."""
459
+ from core.settings_defaults_registry import SettingsDefaultsRegistry
460
+ return SettingsDefaultsRegistry()