2025-06-01 12:38:41 -07:00
|
|
|
# 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")
|
2025-06-01 14:02:29 -07:00
|
|
|
dialog.geometry("450x400") # Increased width and height
|
2025-06-01 12:38:41 -07:00
|
|
|
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
|
2025-06-01 14:02:29 -07:00
|
|
|
list_frame = tk.Frame(dialog, bg=THEME['bg_color'])
|
2025-06-01 12:38:41 -07:00
|
|
|
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)
|
2025-06-01 14:02:29 -07:00
|
|
|
scrollable_frame = tk.Frame(canvas, bg=THEME['bg_color'])
|
2025-06-01 12:38:41 -07:00
|
|
|
|
|
|
|
scrollable_frame.bind(
|
|
|
|
"<Configure>",
|
|
|
|
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():
|
2025-06-01 14:02:29 -07:00
|
|
|
frame = tk.Frame(scrollable_frame, bg=THEME['bg_color'])
|
2025-06-01 12:38:41 -07:00
|
|
|
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))
|
|
|
|
|
2025-06-01 14:02:29 -07:00
|
|
|
# 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)
|
2025-06-01 12:38:41 -07:00
|
|
|
|
|
|
|
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()
|