pomera-ai-commander 1.2.8 → 1.2.9
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/core/database_schema.py +24 -1
- package/core/database_schema_manager.py +4 -2
- package/core/database_settings_manager.py +25 -2
- package/core/dialog_manager.py +4 -4
- package/core/efficient_line_numbers.py +5 -4
- package/core/load_presets_dialog.py +460 -0
- package/core/mcp/tool_registry.py +327 -0
- package/core/settings_defaults_registry.py +159 -15
- package/mcp.json +1 -1
- package/package.json +1 -1
- package/pomera.py +755 -22
- package/tools/case_tool.py +4 -4
- package/tools/curl_settings.py +12 -1
- package/tools/curl_tool.py +176 -11
- package/tools/tool_loader.py +18 -0
- package/tools/url_content_reader.py +402 -0
- package/tools/web_search.py +522 -0
package/core/database_schema.py
CHANGED
|
@@ -398,6 +398,21 @@ class DataTypeConverter:
|
|
|
398
398
|
"""
|
|
399
399
|
import json
|
|
400
400
|
|
|
401
|
+
# Handle None or empty string for all types
|
|
402
|
+
if value_str is None or value_str == '':
|
|
403
|
+
if data_type in ('json', 'array'):
|
|
404
|
+
return [] if data_type == 'array' else {}
|
|
405
|
+
elif data_type == 'str':
|
|
406
|
+
return ''
|
|
407
|
+
elif data_type == 'int':
|
|
408
|
+
return 0
|
|
409
|
+
elif data_type == 'float':
|
|
410
|
+
return 0.0
|
|
411
|
+
elif data_type == 'bool':
|
|
412
|
+
return False
|
|
413
|
+
else:
|
|
414
|
+
return ''
|
|
415
|
+
|
|
401
416
|
if data_type == 'str':
|
|
402
417
|
return value_str
|
|
403
418
|
elif data_type == 'int':
|
|
@@ -407,6 +422,14 @@ class DataTypeConverter:
|
|
|
407
422
|
elif data_type == 'bool':
|
|
408
423
|
return value_str == '1'
|
|
409
424
|
elif data_type in ('json', 'array'):
|
|
410
|
-
|
|
425
|
+
# Handle whitespace-only strings as empty
|
|
426
|
+
if not value_str.strip():
|
|
427
|
+
return [] if data_type == 'array' else {}
|
|
428
|
+
try:
|
|
429
|
+
return json.loads(value_str)
|
|
430
|
+
except json.JSONDecodeError as e:
|
|
431
|
+
# Debug: print what value caused the error
|
|
432
|
+
print(f"DEBUG: JSON parse failed for data_type='{data_type}': value_str='{value_str[:100]}...' error={e}")
|
|
433
|
+
return [] if data_type == 'array' else {}
|
|
411
434
|
else:
|
|
412
435
|
return value_str # Fallback to string
|
|
@@ -308,8 +308,10 @@ class DatabaseSchemaManager:
|
|
|
308
308
|
for index_sql in table_indexes:
|
|
309
309
|
# Extract index name from CREATE INDEX statement
|
|
310
310
|
parts = index_sql.split()
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
# "CREATE INDEX IF NOT EXISTS idx_name ON table(...)"
|
|
312
|
+
# parts: [0]=CREATE [1]=INDEX [2]=IF [3]=NOT [4]=EXISTS [5]=idx_name
|
|
313
|
+
if len(parts) >= 6 and parts[0].upper() == "CREATE" and parts[1].upper() == "INDEX":
|
|
314
|
+
index_name = parts[5] # After "CREATE INDEX IF NOT EXISTS"
|
|
313
315
|
expected_indexes.add(index_name)
|
|
314
316
|
|
|
315
317
|
# Get existing indexes
|
|
@@ -143,6 +143,27 @@ class NestedSettingsProxy:
|
|
|
143
143
|
"""Return a copy of the underlying data as a regular dictionary."""
|
|
144
144
|
return self._data.copy()
|
|
145
145
|
|
|
146
|
+
def pop(self, key: str, *args):
|
|
147
|
+
"""Remove and return a value from the nested settings.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
key: Key to remove
|
|
151
|
+
*args: Optional default value if key not found
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The removed value, or default if provided and key not found
|
|
155
|
+
"""
|
|
156
|
+
if args:
|
|
157
|
+
result = self._data.pop(key, args[0])
|
|
158
|
+
else:
|
|
159
|
+
result = self._data.pop(key)
|
|
160
|
+
|
|
161
|
+
# Save the change to database
|
|
162
|
+
full_path = f"{self._parent_key}"
|
|
163
|
+
self._settings_manager.set_setting(full_path, self._data)
|
|
164
|
+
|
|
165
|
+
return result
|
|
166
|
+
|
|
146
167
|
def _update_nested_value(self, data: Dict[str, Any], path: str, value: Any) -> None:
|
|
147
168
|
"""Update value in nested dictionary using dot notation."""
|
|
148
169
|
keys = path.split('.')
|
|
@@ -1112,8 +1133,10 @@ class DatabaseSettingsManager:
|
|
|
1112
1133
|
|
|
1113
1134
|
critical_issues = [i for i in issues if i.severity == 'critical']
|
|
1114
1135
|
if critical_issues:
|
|
1115
|
-
|
|
1116
|
-
|
|
1136
|
+
# Log specific issues for debugging but allow import to proceed
|
|
1137
|
+
for issue in critical_issues:
|
|
1138
|
+
self.logger.warning(f"Import validation issue: {issue.location} - {issue.message}")
|
|
1139
|
+
self.logger.warning(f"Imported settings have {len(critical_issues)} validation issues - proceeding anyway")
|
|
1117
1140
|
|
|
1118
1141
|
# Save imported settings
|
|
1119
1142
|
success = self.save_settings(settings_data)
|
package/core/dialog_manager.py
CHANGED
|
@@ -283,8 +283,8 @@ class DialogManager:
|
|
|
283
283
|
try:
|
|
284
284
|
dialog_settings = self.settings_manager.get_setting("dialog_settings", {})
|
|
285
285
|
|
|
286
|
-
# Validate settings structure
|
|
287
|
-
if not isinstance(dialog_settings, dict):
|
|
286
|
+
# Validate settings structure - accept dict or dict-like objects (NestedSettingsProxy)
|
|
287
|
+
if not isinstance(dialog_settings, dict) and not hasattr(dialog_settings, 'get'):
|
|
288
288
|
self.logger.warning(f"Invalid dialog_settings structure: {type(dialog_settings)}, using defaults")
|
|
289
289
|
return True
|
|
290
290
|
|
|
@@ -663,8 +663,8 @@ class DialogManager:
|
|
|
663
663
|
# Force a fresh read of dialog settings
|
|
664
664
|
dialog_settings = self.settings_manager.get_setting("dialog_settings", {})
|
|
665
665
|
|
|
666
|
-
# Validate settings structure
|
|
667
|
-
if not isinstance(dialog_settings, dict):
|
|
666
|
+
# Validate settings structure - accept dict or dict-like objects (NestedSettingsProxy)
|
|
667
|
+
if not isinstance(dialog_settings, dict) and not hasattr(dialog_settings, 'get'):
|
|
668
668
|
self.logger.error(f"Invalid dialog_settings structure: {type(dialog_settings)}, skipping refresh")
|
|
669
669
|
return
|
|
670
670
|
|
|
@@ -9,6 +9,7 @@ from tkinter import scrolledtext
|
|
|
9
9
|
import platform
|
|
10
10
|
import time
|
|
11
11
|
import threading
|
|
12
|
+
import hashlib
|
|
12
13
|
from typing import Dict, List, Tuple, Optional, Any
|
|
13
14
|
from dataclasses import dataclass
|
|
14
15
|
|
|
@@ -31,7 +32,7 @@ class EfficientLineNumbers(tk.Frame):
|
|
|
31
32
|
|
|
32
33
|
# Configuration
|
|
33
34
|
self.line_number_width = 50 # Adjustable width
|
|
34
|
-
self.debounce_delay =
|
|
35
|
+
self.debounce_delay = 50 # ms - balanced responsiveness vs. efficiency
|
|
35
36
|
self.cache_size_limit = 1000 # Maximum cached line positions
|
|
36
37
|
|
|
37
38
|
# Create widgets
|
|
@@ -280,12 +281,12 @@ class EfficientLineNumbers(tk.Frame):
|
|
|
280
281
|
return None
|
|
281
282
|
|
|
282
283
|
def _get_content_hash(self) -> str:
|
|
283
|
-
"""Get a hash of the current content for change detection."""
|
|
284
|
+
"""Get a hash of the current content for change detection using MD5."""
|
|
284
285
|
try:
|
|
285
286
|
content = self.text.get("1.0", "end-1c")
|
|
286
|
-
# Simple hash based on content length and first/last chars
|
|
287
287
|
if content:
|
|
288
|
-
|
|
288
|
+
# Use MD5 for reliable change detection (truncated for efficiency)
|
|
289
|
+
return hashlib.md5(content.encode('utf-8', errors='replace')).hexdigest()[:16]
|
|
289
290
|
return "empty"
|
|
290
291
|
except Exception:
|
|
291
292
|
return "error"
|
|
@@ -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()
|