Remove associated names label and always show n parameter inputs

This commit is contained in:
2025-11-30 17:23:32 +13:00
parent 2db2d9e120
commit c8ae113355
3 changed files with 238 additions and 113 deletions

View File

@@ -1,4 +1,51 @@
{ {
"tab_password": "",
"patterns": {
"on": {
"min_delay": 10,
"max_delay": 10000
},
"off": {
"min_delay": 10,
"max_delay": 10000
},
"rainbow": {
"Step Rate": "n1",
"min_delay": 10,
"max_delay": 10000
},
"transition": {
"min_delay": 10,
"max_delay": 10000
},
"chase": {
"Colour 1 Length": "n1",
"Colour 2 Length": "n2",
"Step 1": "n3",
"Step 2": "n4",
"min_delay": 10,
"max_delay": 10000
},
"pulse": {
"Attack": "n1",
"Hold": "n2",
"Decay": "n3",
"min_delay": 10,
"max_delay": 10000
},
"circle": {
"Head Rate": "n1",
"Max Length": "n2",
"Tail Rate": "n3",
"Min Length": "n4",
"min_delay": 10,
"max_delay": 10000
},
"blink": {
"min_delay": 10,
"max_delay": 10000
}
},
"lights": { "lights": {
"ring1": { "ring1": {
"names": [ "names": [
@@ -59,7 +106,6 @@
} }
} }
}, },
"tab_password": "",
"tab_order": [ "tab_order": [
"ring1", "ring1",
"ring2" "ring2"

View File

@@ -1,6 +1,6 @@
{ {
"tab_password": "qwerty1234", "tab_password": "",
"current_profile": "tt", "current_profile": "ring",
"patterns": { "patterns": {
"on": { "on": {
"min_delay": 10, "min_delay": 10,

View File

@@ -109,114 +109,136 @@ class App:
style.map("TNotebook.Tab", background=[("selected", active_bg_color)], foreground=[("selected", fg_color)]) style.map("TNotebook.Tab", background=[("selected", active_bg_color)], foreground=[("selected", fg_color)])
style.configure("TFrame", background=bg_color) style.configure("TFrame", background=bg_color)
# Tab management buttons frame (packed at bottom first to ensure it's always visible) # Create a frame to hold notebook and menu button on same row
tab_management_frame = tk.Frame(self.root, bg=bg_color) # The notebook tabs appear at the top, so we'll position the menu button there
tab_management_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=self.scale_size(10), pady=self.scale_size(5)) top_frame = tk.Frame(self.root, bg=bg_color)
top_frame.pack(side=tk.TOP, fill=tk.X)
# Create Notebook for tabs (packed after buttons, takes remaining space) # Create Notebook for tabs
self.notebook = ttk.Notebook(self.root) self.notebook = ttk.Notebook(self.root)
self.notebook.pack(expand=True, fill="both") self.notebook.pack(expand=True, fill="both")
add_tab_btn = tk.Button( # Create menu button positioned to appear on same row as tabs
tab_management_frame, # Calculate approximate tab height: font size + padding
text="+ Add Tab", tab_font_size = self.scale_font(30)
command=self.add_tab_dialog, tab_padding = self.scale_size(5) * 2
tab_height = tab_font_size + tab_padding + self.scale_size(10)
menu_btn = tk.Menubutton(
self.root,
text="☰ Menu",
bg=active_bg_color, bg=active_bg_color,
fg=fg_color, fg=fg_color,
font=("Arial", self.scale_font(14)), font=("Arial", self.scale_font(20)),
padx=self.scale_size(10), padx=self.scale_size(20),
pady=self.scale_size(5) pady=self.scale_size(10),
relief=tk.RAISED,
direction="below"
) )
add_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5)) # Position menu button aligned with tabs (vertically centered with tab row)
# The tabs are at the top of the notebook, so position button at same level
# We need to wait for the notebook to be rendered to get accurate positioning
def position_menu_button():
self.root.update_idletasks()
# Get the notebook's tab area position
notebook_y = self.notebook.winfo_y()
# Position button at the same vertical level as tabs (tabs are at top of notebook)
menu_btn.place(relx=1.0, y=notebook_y + tab_height//2, anchor="e", x=-self.scale_size(10))
edit_tab_btn = tk.Button( # Position after initial layout
tab_management_frame, self.root.after(10, position_menu_button)
text="✎ Edit Tab", # Also position immediately as fallback
command=self.edit_tab_dialog, menu_btn.place(relx=1.0, y=tab_height//2, anchor="e", x=-self.scale_size(10))
bg=active_bg_color,
fg=fg_color,
font=("Arial", self.scale_font(14)),
padx=self.scale_size(10),
pady=self.scale_size(5)
)
edit_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
delete_tab_btn = tk.Button( # Create the menu (bigger font)
tab_management_frame, menu = tk.Menu(menu_btn, tearoff=0, bg=bg_color, fg=fg_color, font=("Arial", self.scale_font(16)))
text="✗ Delete Tab", menu_btn.config(menu=menu)
command=self.delete_tab_dialog,
bg=active_bg_color,
fg=fg_color,
font=("Arial", self.scale_font(14)),
padx=self.scale_size(10),
pady=self.scale_size(5)
)
delete_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
# Tab reorder buttons # Add tab management items directly
move_left_btn = tk.Button( menu.add_command(label="+ Add Tab", command=self.add_tab_dialog)
tab_management_frame, menu.add_command(label="✎ Edit Tab", command=self.edit_tab_dialog)
text="← Move Left", menu.add_command(label="✗ Delete Tab", command=self.delete_tab_dialog)
command=self.move_tab_left, menu.add_separator()
bg=active_bg_color, menu.add_command(label="← Move Tab Left", command=self.move_tab_left)
fg=fg_color, menu.add_command(label="→ Move Tab Right", command=self.move_tab_right)
font=("Arial", self.scale_font(14)), menu.add_separator()
padx=self.scale_size(10),
pady=self.scale_size(5)
)
move_left_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
move_right_btn = tk.Button( # Profile management - use a custom popup menu that opens to the left
tab_management_frame, # Store reference for the profile menu function
text="→ Move Right", self.profile_menu_items = []
command=self.move_tab_right,
bg=active_bg_color,
fg=fg_color,
font=("Arial", self.scale_font(14)),
padx=self.scale_size(10),
pady=self.scale_size(5)
)
move_right_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
# Profile dropdown def show_profile_menu():
tk.Label(tab_management_frame, text="Profile:", bg=bg_color, fg=fg_color, font=("Arial", self.scale_font(14))).pack(side=tk.LEFT, padx=(self.scale_size(20), self.scale_size(5))) """Show profile menu as a popup to the left"""
# Create a popup menu window
popup = tk.Toplevel(self.root)
popup.overrideredirect(True) # Remove window decorations
popup.configure(bg=bg_color)
# Position to the left of the menu button
menu_btn.update_idletasks()
btn_x = menu_btn.winfo_x()
btn_y = menu_btn.winfo_y()
btn_height = menu_btn.winfo_height()
# Calculate popup position (to the left of menu button)
popup_width = self.scale_size(200)
popup_x = btn_x - popup_width - self.scale_size(5)
popup_y = btn_y
popup.geometry(f"{popup_width}x{self.scale_size(400)}+{popup_x}+{popup_y}")
# Create menu frame
menu_frame = tk.Frame(popup, bg=bg_color)
menu_frame.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
# Add menu items
def add_menu_item(text, command=None, state="normal"):
if state == "disabled":
label = tk.Label(menu_frame, text=text, bg=bg_color, fg=fg_color,
font=("Arial", self.scale_font(14)), anchor="w")
label.pack(fill=tk.X, padx=self.scale_size(5), pady=self.scale_size(2))
else:
btn = tk.Button(menu_frame, text=text, bg=bg_color, fg=fg_color,
font=("Arial", self.scale_font(14)), anchor="w",
relief=tk.FLAT, command=lambda: (command() if command else None, popup.destroy()))
btn.pack(fill=tk.X, padx=self.scale_size(5), pady=self.scale_size(2))
btn.bind("<Enter>", lambda e: btn.config(bg=active_bg_color))
btn.bind("<Leave>", lambda e: btn.config(bg=bg_color))
add_menu_item("+ New Profile", self.new_profile_dialog)
add_menu_item("💾 Save Profile", self.save_profile_dialog)
add_menu_item("", state="disabled") # Separator
add_menu_item("Load Profile:", state="disabled")
# Get profiles
profiles_dir = "profiles"
profiles = []
if os.path.exists(profiles_dir):
for filename in os.listdir(profiles_dir):
if filename.endswith('.json'):
profiles.append(filename[:-5])
profiles.sort()
current_profile = self.settings.get("current_profile", "")
# Add profile list
for profile in profiles:
label = f"{profile}" if profile == current_profile else profile
add_menu_item(label, lambda p=profile: (self.on_profile_selected_menu(p), popup.destroy()))
# Close popup when clicking outside
def close_on_focus_out(event):
if event.widget == popup:
popup.destroy()
popup.bind("<FocusOut>", close_on_focus_out)
popup.focus_set()
self.profile_var = tk.StringVar() # Add Profiles item that opens the custom popup menu
self.profile_dropdown = ttk.Combobox( menu.add_command(label="Profiles →", command=show_profile_menu)
tab_management_frame,
textvariable=self.profile_var,
font=("Arial", self.scale_font(14)),
width=self.scale_size(20),
state="readonly"
)
self.profile_dropdown.pack(side=tk.LEFT, padx=self.scale_size(5))
self.profile_dropdown.bind("<<ComboboxSelected>>", self.on_profile_selected)
# New profile button # Store menu references for updating
new_profile_btn = tk.Button( self.menu_btn = menu_btn
tab_management_frame, self.profile_var = tk.StringVar() # Keep for compatibility
text="+ New Profile", self.profile_menu = None # Not using standard menu for profiles
command=self.new_profile_dialog,
bg=active_bg_color,
fg=fg_color,
font=("Arial", self.scale_font(14)),
padx=self.scale_size(10),
pady=self.scale_size(5)
)
new_profile_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
# Save profile button
save_profile_btn = tk.Button(
tab_management_frame,
text="💾 Save Profile",
command=self.save_profile_dialog,
bg=active_bg_color,
fg=fg_color,
font=("Arial", self.scale_font(14)),
padx=self.scale_size(10),
pady=self.scale_size(5)
)
save_profile_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
# Load profiles # Load profiles
self.profiles_dir = "profiles" self.profiles_dir = "profiles"
@@ -362,32 +384,89 @@ class App:
if filename.endswith('.json'): if filename.endswith('.json'):
profiles.append(filename[:-5]) # Remove .json extension profiles.append(filename[:-5]) # Remove .json extension
profiles.sort() profiles.sort()
self.profile_dropdown['values'] = profiles
# Update profile menu - clear existing profile items
# Find "Load Profile:" label index
load_profile_label_idx = None
try:
menu_count = self.profile_menu.index(tk.END)
for i in range(menu_count + 1):
try:
if self.profile_menu.type(i) == "command":
label = self.profile_menu.entryconfig(i, "label")[4]
if label == "Load Profile:":
load_profile_label_idx = i
break
except:
pass
except:
pass
if load_profile_label_idx is not None:
# Delete all items after "Load Profile:" until we hit the end
try:
menu_count = self.profile_menu.index(tk.END)
# Delete backwards from the end
items_to_delete = []
for i in range(menu_count, load_profile_label_idx, -1):
try:
item_type = self.profile_menu.type(i)
if item_type == "command":
label = self.profile_menu.entryconfig(i, "label")[4]
if label not in ["Load Profile:", "+ New Profile", "💾 Save Profile"]:
items_to_delete.append(i)
elif item_type == "separator":
# Delete separators after "Load Profile:"
items_to_delete.append(i)
except:
pass
# Delete items
for i in sorted(items_to_delete, reverse=True):
try:
self.profile_menu.delete(i)
except:
pass
except Exception as e:
print(f"Error clearing profile menu: {e}")
# Get current profile
current_profile = self.settings.get("current_profile", "")
# Add profile items to menu (after "Load Profile:" label)
if profiles: if profiles:
# Try to load current profile name from settings insert_pos = load_profile_label_idx + 1 if load_profile_label_idx is not None else self.profile_menu.index(tk.END) + 1
current_profile = self.settings.get("current_profile", "")
if current_profile in profiles: # Add each profile as a menu item
self.profile_var.set(current_profile) for profile in profiles:
else: label = f"{profile}" if profile == current_profile else profile
self.profile_var.set("") self.profile_menu.insert_command(
insert_pos,
label=label,
command=lambda p=profile: self.on_profile_selected_menu(p)
)
insert_pos += 1
except Exception as e: except Exception as e:
print(f"Error refreshing profiles: {e}") print(f"Error refreshing profiles: {e}")
def on_profile_selected(self, event=None): def on_profile_selected(self, event=None):
"""Handle profile selection from dropdown""" """Handle profile selection from dropdown (legacy, not used with menu)"""
selected_profile = self.profile_var.get() pass
if not selected_profile:
def on_profile_selected_menu(self, profile_name):
"""Handle profile selection from menu"""
if not profile_name:
return return
# Confirm before loading (will overwrite current settings) # Confirm before loading (will overwrite current settings)
result = messagebox.askyesno( result = messagebox.askyesno(
"Load Profile", "Load Profile",
f"Load profile '{selected_profile}'?\n\nThis will replace your current settings.", f"Load profile '{profile_name}'?\n\nThis will replace your current settings.",
icon="question" icon="question"
) )
if result: if result:
self.load_profile(selected_profile) self.load_profile(profile_name)
def load_profile(self, profile_name): def load_profile(self, profile_name):
"""Load a profile from the profiles directory""" """Load a profile from the profiles directory"""
@@ -583,12 +662,13 @@ class App:
desc_name = self.get_n_parameter_name(pattern_name, i) desc_name = self.get_n_parameter_name(pattern_name, i)
if desc_name: if desc_name:
# Show the input and update label # Show the input and update label with descriptive name
tab.widgets[frame_key].grid() tab.widgets[frame_key].grid()
tab.widgets[label_key].config(text=desc_name) tab.widgets[label_key].config(text=desc_name)
else: else:
# Hide the input if no description # Show the input with default n{i} label if no description
tab.widgets[frame_key].grid_remove() tab.widgets[frame_key].grid()
tab.widgets[label_key].config(text=f"n{i}")
def get_pattern_settings(self, tab_name, pattern_name): def get_pattern_settings(self, tab_name, pattern_name):
"""Get pattern-specific settings (colors, delay, n params). Returns defaults if not found.""" """Get pattern-specific settings (colors, delay, n params). Returns defaults if not found."""
@@ -958,7 +1038,6 @@ class App:
# IDs section - MODIFIED TO BE SIDE-BY-SIDE # IDs section - MODIFIED TO BE SIDE-BY-SIDE
ids_frame = tk.Frame(right_panel_frame, bg=bg_color) ids_frame = tk.Frame(right_panel_frame, bg=bg_color)
ids_frame.pack(pady=10, fill=tk.X) ids_frame.pack(pady=10, fill=tk.X)
tk.Label(ids_frame, text="Associated Names:", font=("Arial", self.scale_font(20)), bg=bg_color, fg=fg_color).pack(pady=self.scale_size(10))
# New inner frame for the IDs to be displayed horizontally # New inner frame for the IDs to be displayed horizontally
ids_inner_frame = tk.Frame(ids_frame, bg=bg_color) ids_inner_frame = tk.Frame(ids_frame, bg=bg_color)