# UI components and dialogs for the desktop application import tkinter as tk from tkinter import ttk, filedialog, messagebox from PIL import Image, ImageTk import os import uuid from config import THEME class MacroDialog: """Dialog for adding/editing macros""" def __init__(self, parent, macro_manager, macro_id=None): self.parent = parent self.macro_manager = macro_manager self.macro_id = macro_id self.dialog = None self.result = None def show(self): """Show the dialog and return the result""" self.dialog = tk.Toplevel(self.parent) self.dialog.title("Edit Macro" if self.macro_id else "Add Macro") self.dialog.geometry("450x400") self.dialog.transient(self.parent) self.dialog.configure(bg=THEME['bg_color']) self.dialog.grab_set() # If editing, get existing macro data if self.macro_id: macro = self.macro_manager.macros.get(self.macro_id, {}) else: macro = {} self._create_widgets(macro) # Wait for dialog to close self.dialog.wait_window() return self.result def _create_widgets(self, macro): """Create the dialog widgets""" # Name tk.Label(self.dialog, text="Macro Name:", bg=THEME['bg_color'], fg=THEME['fg_color']).grid(row=0, column=0, padx=5, pady=5, sticky="w") self.name_entry = tk.Entry(self.dialog, width=30, bg=THEME['highlight_color'], fg=THEME['fg_color'], insertbackground=THEME['fg_color']) self.name_entry.grid(row=0, column=1, padx=5, pady=5) self.name_entry.insert(0, macro.get("name", "")) # Category tk.Label(self.dialog, text="Category:", bg=THEME['bg_color'], fg=THEME['fg_color']).grid(row=1, column=0, padx=5, pady=5, sticky="w") self.category_entry = tk.Entry(self.dialog, width=30, bg=THEME['highlight_color'], fg=THEME['fg_color'], insertbackground=THEME['fg_color']) self.category_entry.grid(row=1, column=1, padx=5, pady=5) self.category_entry.insert(0, macro.get("category", "")) # Type tk.Label(self.dialog, text="Type:", bg=THEME['bg_color'], fg=THEME['fg_color']).grid(row=2, column=0, padx=5, pady=5, sticky="w") self.type_var = tk.StringVar(value=macro.get("type", "text")) radio_style = {'bg': THEME['bg_color'], 'fg': THEME['fg_color'], 'selectcolor': THEME['accent_color']} tk.Radiobutton(self.dialog, text="Text", variable=self.type_var, value="text", **radio_style).grid(row=2, column=1, sticky="w") tk.Radiobutton(self.dialog, text="Application", variable=self.type_var, value="app", **radio_style).grid(row=3, column=1, sticky="w") # Command/Text tk.Label(self.dialog, text="Command/Text:", bg=THEME['bg_color'], fg=THEME['fg_color']).grid(row=4, column=0, padx=5, pady=5, sticky="w") self.command_text = tk.Text(self.dialog, width=30, height=5, bg=THEME['highlight_color'], fg=THEME['fg_color'], insertbackground=THEME['fg_color']) self.command_text.grid(row=4, column=1, padx=5, pady=5) self.command_text.insert("1.0", macro.get("command", "")) # Modifiers self._create_modifiers(macro) # Image self.image_path = tk.StringVar() tk.Label(self.dialog, text="Image:", bg=THEME['bg_color'], fg=THEME['fg_color']).grid(row=6, column=0, padx=5, pady=5, sticky="w") image_entry = tk.Entry(self.dialog, textvariable=self.image_path, width=30, bg=THEME['highlight_color'], fg=THEME['fg_color'], insertbackground=THEME['fg_color']) image_entry.grid(row=6, column=1, padx=5, pady=5) button_style = {'bg': THEME['button_bg'], 'fg': THEME['button_fg'], 'activebackground': THEME['accent_color'], 'activeforeground': THEME['button_fg'], 'bd': 0, 'relief': tk.FLAT} tk.Button(self.dialog, text="Browse...", command=self._browse_image, **button_style).grid(row=6, column=2) # Buttons tk.Button(self.dialog, text="Save", command=self._save, **button_style).grid(row=7, column=0, padx=5, pady=20) tk.Button(self.dialog, text="Cancel", command=self._cancel, **button_style).grid(row=7, column=1, padx=5, pady=20) def _create_modifiers(self, macro): """Create modifier checkboxes""" mod_frame = tk.Frame(self.dialog, bg=THEME['bg_color']) mod_frame.grid(row=5, column=0, columnspan=2, sticky="w", padx=5, pady=5) tk.Label(mod_frame, text="Key Modifiers:", bg=THEME['bg_color'], fg=THEME['fg_color']).pack(side=tk.LEFT, padx=5) modifiers = macro.get("modifiers", {}) self.ctrl_var = tk.BooleanVar(value=modifiers.get("ctrl", False)) self.alt_var = tk.BooleanVar(value=modifiers.get("alt", False)) self.shift_var = tk.BooleanVar(value=modifiers.get("shift", False)) self.enter_var = tk.BooleanVar(value=modifiers.get("enter", False)) checkbox_style = {'bg': THEME['bg_color'], 'fg': THEME['fg_color'], 'selectcolor': THEME['accent_color'], 'activebackground': THEME['bg_color'], 'activeforeground': THEME['fg_color']} tk.Checkbutton(mod_frame, text="Ctrl", variable=self.ctrl_var, **checkbox_style).pack(side=tk.LEFT, padx=5) tk.Checkbutton(mod_frame, text="Alt", variable=self.alt_var, **checkbox_style).pack(side=tk.LEFT, padx=5) tk.Checkbutton(mod_frame, text="Shift", variable=self.shift_var, **checkbox_style).pack(side=tk.LEFT, padx=5) tk.Checkbutton(mod_frame, text="Add Enter", variable=self.enter_var, **checkbox_style).pack(side=tk.LEFT, padx=5) def _browse_image(self): """Browse for image file""" filename = filedialog.askopenfilename( filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")]) if filename: self.image_path.set(filename) def _save(self): """Save the macro""" name = self.name_entry.get().strip() if not name: messagebox.showerror("Error", "Macro name is required") return macro_type = self.type_var.get() command = self.command_text.get("1.0", tk.END).strip() category = self.category_entry.get().strip() modifiers = { "ctrl": self.ctrl_var.get(), "alt": self.alt_var.get(), "shift": self.shift_var.get(), "enter": self.enter_var.get() } if self.macro_id: # Update existing macro success = self.macro_manager.update_macro( self.macro_id, name, macro_type, command, category, modifiers, self.image_path.get()) else: # Add new macro self.macro_id = self.macro_manager.add_macro( name, macro_type, command, category, modifiers, self.image_path.get()) success = bool(self.macro_id) if success: self.result = self.macro_id self.dialog.destroy() else: messagebox.showerror("Error", "Failed to save macro") def _cancel(self): """Cancel dialog""" self.result = None self.dialog.destroy() class MacroSelector: """Dialog for selecting a macro from a list""" def __init__(self, parent, macro_manager, title="Select Macro"): self.parent = parent self.macro_manager = macro_manager self.title = title self.result = None def show(self): """Show the selection dialog""" if not self.macro_manager.macros: messagebox.showinfo("No Macros", "There are no macros available.") return None dialog = tk.Toplevel(self.parent) dialog.title(self.title) dialog.geometry("200x340") dialog.transient(self.parent) dialog.configure(bg=THEME['bg_color']) dialog.grab_set() # Instructions tk.Label(dialog, text=f"{self.title}:", bg=THEME['bg_color'], fg=THEME['fg_color']).pack(pady=5) # Listbox listbox = tk.Listbox(dialog, bg=THEME['highlight_color'], fg=THEME['fg_color'], selectbackground=THEME['accent_color']) listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # Populate listbox macro_ids = [] for macro_id, macro in self.macro_manager.macros.items(): listbox.insert(tk.END, macro["name"]) macro_ids.append(macro_id) def on_select(): if not listbox.curselection(): messagebox.showwarning("No Selection", f"Please select a macro.") return idx = listbox.curselection()[0] self.result = macro_ids[idx] dialog.destroy() button_style = {'bg': THEME['button_bg'], 'fg': THEME['button_fg'], 'activebackground': THEME['accent_color'], 'activeforeground': THEME['button_fg'], 'bd': 0, 'relief': tk.FLAT} tk.Button(dialog, text="Select", command=on_select, **button_style).pack(pady=10) tk.Button(dialog, text="Cancel", command=lambda: dialog.destroy(), **button_style).pack(pady=5) dialog.wait_window() return self.result class TabManager: """Dialog for managing macro categories/tabs""" def __init__(self, parent, macro_manager): self.parent = parent self.macro_manager = macro_manager def show(self): """Show tab management dialog""" dialog = tk.Toplevel(self.parent) dialog.title("Manage Tabs") dialog.geometry("450x400") # Increased width and height dialog.transient(self.parent) dialog.configure(bg=THEME['bg_color']) dialog.grab_set() # Instructions tk.Label(dialog, text="Assign categories to macros for better organization:", bg=THEME['bg_color'], fg=THEME['fg_color']).pack(pady=10) # Create scrollable frame list_frame = tk.Frame(dialog, bg=THEME['bg_color']) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) canvas = tk.Canvas(list_frame, bg=THEME['bg_color'], highlightthickness=0) scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=canvas.yview) scrollable_frame = tk.Frame(canvas, bg=THEME['bg_color']) scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Category entries for each macro category_vars = {} for macro_id, macro in self.macro_manager.macros.items(): frame = tk.Frame(scrollable_frame, bg=THEME['bg_color']) frame.pack(fill="x", pady=2, padx=5) tk.Label(frame, text=macro["name"], bg=THEME['bg_color'], fg=THEME['fg_color'], width=20, anchor="w").pack(side=tk.LEFT) category_var = tk.StringVar(value=macro.get("category", "")) category_vars[macro_id] = category_var entry = tk.Entry(frame, textvariable=category_var, width=15, bg=THEME['highlight_color'], fg=THEME['fg_color'], insertbackground=THEME['fg_color']) entry.pack(side=tk.RIGHT, padx=(5, 0)) # Buttons - use a fixed frame at bottom button_frame = tk.Frame(dialog, bg=THEME['bg_color']) button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=10) def save_categories(): for macro_id, category_var in category_vars.items(): category = category_var.get().strip() if category: self.macro_manager.macros[macro_id]["category"] = category else: self.macro_manager.macros[macro_id].pop("category", None) self.macro_manager.save_macros() dialog.destroy() button_style = {'bg': THEME['button_bg'], 'fg': THEME['button_fg'], 'activebackground': THEME['accent_color'], 'activeforeground': THEME['button_fg'], 'bd': 0, 'relief': tk.FLAT} tk.Button(button_frame, text="Save", command=save_categories, **button_style).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="Cancel", command=lambda: dialog.destroy(), **button_style).pack(side=tk.LEFT, padx=5) dialog.wait_window()