diff --git a/src/search_filter_ui.py b/src/search_filter_ui.py index dc22624..f73c194 100644 --- a/src/search_filter_ui.py +++ b/src/search_filter_ui.py @@ -2,7 +2,7 @@ import tkinter as tk from collections.abc import Callable -from tkinter import messagebox, simpledialog, ttk +from tkinter import messagebox, ttk from init import logger from preferences import get_pref, save_preferences, set_pref @@ -521,11 +521,8 @@ class SearchFilterWidget: self._apply_filter_summary(summary) def _save_preset(self) -> None: - # Ask for a name - name = simpledialog.askstring("Save Preset", "Preset name:", parent=self.parent) - if not name: - return - name = name.strip() + # Ask for a name via themed modal dialog + name = self._ask_preset_name(initial=self.preset_var.get().strip()) if not name: return presets = get_pref("filter_presets", {}) or {} @@ -536,6 +533,65 @@ class SearchFilterWidget: self.preset_var.set(name) self._update_status() + def _ask_preset_name(self, initial: str = "") -> str | None: + """Prompt for a preset name using a themed ttk modal dialog. + + Returns the entered name (stripped) or None if cancelled. + """ + result: dict[str, str | None] = {"value": None} + + top = tk.Toplevel(self.parent) + top.title("Save Preset") + top.transient(self.parent) + top.grab_set() + + frame = ttk.Frame(top, padding="10") + frame.pack(fill="both", expand=True) + + ttk.Label(frame, text="Preset name:").pack(anchor="w") + name_var = tk.StringVar(value=initial) + entry = ttk.Entry(frame, textvariable=name_var, width=32) + entry.pack(fill="x", pady=(4, 10)) + + buttons = ttk.Frame(frame) + buttons.pack(anchor="e") + + def on_ok() -> None: + value = (name_var.get() or "").strip() + if not value: + messagebox.showwarning( + "Save Preset", "Please enter a name.", parent=top + ) + return + result["value"] = value + top.destroy() + + def on_cancel() -> None: + result["value"] = None + top.destroy() + + cancel_btn = ttk.Button(buttons, text="Cancel", command=on_cancel) + cancel_btn.pack(side="right") + ok_btn = ttk.Button(buttons, text="Save", command=on_ok) + ok_btn.pack(side="right", padx=(6, 0)) + + # Key bindings + entry.bind("", lambda e: on_ok()) + entry.bind("", lambda e: on_cancel()) + + # Center the dialog relative to parent + top.update_idletasks() + px, py = self.parent.winfo_rootx(), self.parent.winfo_rooty() + pw, ph = self.parent.winfo_width(), self.parent.winfo_height() + ww, wh = top.winfo_width(), top.winfo_height() + x = px + (pw // 2) - (ww // 2) + y = py + (ph // 2) - (wh // 2) + top.geometry(f"+{x}+{y}") + + entry.focus_set() + top.wait_window() + return result["value"] + def _delete_preset(self) -> None: name = self.preset_var.get().strip() if not name: