Remove associated names label and always show n parameter inputs
This commit is contained in:
@@ -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": {
|
||||
"ring1": {
|
||||
"names": [
|
||||
@@ -59,7 +106,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tab_password": "",
|
||||
"tab_order": [
|
||||
"ring1",
|
||||
"ring2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"tab_password": "qwerty1234",
|
||||
"current_profile": "tt",
|
||||
"tab_password": "",
|
||||
"current_profile": "ring",
|
||||
"patterns": {
|
||||
"on": {
|
||||
"min_delay": 10,
|
||||
|
||||
299
src/main.py
299
src/main.py
@@ -109,114 +109,136 @@ class App:
|
||||
style.map("TNotebook.Tab", background=[("selected", active_bg_color)], foreground=[("selected", fg_color)])
|
||||
style.configure("TFrame", background=bg_color)
|
||||
|
||||
# Tab management buttons frame (packed at bottom first to ensure it's always visible)
|
||||
tab_management_frame = tk.Frame(self.root, bg=bg_color)
|
||||
tab_management_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=self.scale_size(10), pady=self.scale_size(5))
|
||||
# Create a frame to hold notebook and menu button on same row
|
||||
# The notebook tabs appear at the top, so we'll position the menu button there
|
||||
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.pack(expand=True, fill="both")
|
||||
|
||||
add_tab_btn = tk.Button(
|
||||
tab_management_frame,
|
||||
text="+ Add Tab",
|
||||
command=self.add_tab_dialog,
|
||||
# Create menu button positioned to appear on same row as tabs
|
||||
# Calculate approximate tab height: font size + padding
|
||||
tab_font_size = self.scale_font(30)
|
||||
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,
|
||||
fg=fg_color,
|
||||
font=("Arial", self.scale_font(14)),
|
||||
padx=self.scale_size(10),
|
||||
pady=self.scale_size(5)
|
||||
font=("Arial", self.scale_font(20)),
|
||||
padx=self.scale_size(20),
|
||||
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(
|
||||
tab_management_frame,
|
||||
text="✎ Edit Tab",
|
||||
command=self.edit_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)
|
||||
)
|
||||
edit_tab_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
|
||||
# Position after initial layout
|
||||
self.root.after(10, position_menu_button)
|
||||
# Also position immediately as fallback
|
||||
menu_btn.place(relx=1.0, y=tab_height//2, anchor="e", x=-self.scale_size(10))
|
||||
|
||||
delete_tab_btn = tk.Button(
|
||||
tab_management_frame,
|
||||
text="✗ Delete Tab",
|
||||
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))
|
||||
# Create the menu (bigger font)
|
||||
menu = tk.Menu(menu_btn, tearoff=0, bg=bg_color, fg=fg_color, font=("Arial", self.scale_font(16)))
|
||||
menu_btn.config(menu=menu)
|
||||
|
||||
# Tab reorder buttons
|
||||
move_left_btn = tk.Button(
|
||||
tab_management_frame,
|
||||
text="← Move Left",
|
||||
command=self.move_tab_left,
|
||||
bg=active_bg_color,
|
||||
fg=fg_color,
|
||||
font=("Arial", self.scale_font(14)),
|
||||
padx=self.scale_size(10),
|
||||
pady=self.scale_size(5)
|
||||
)
|
||||
move_left_btn.pack(side=tk.LEFT, padx=self.scale_size(5))
|
||||
# Add tab management items directly
|
||||
menu.add_command(label="+ Add Tab", command=self.add_tab_dialog)
|
||||
menu.add_command(label="✎ Edit Tab", command=self.edit_tab_dialog)
|
||||
menu.add_command(label="✗ Delete Tab", command=self.delete_tab_dialog)
|
||||
menu.add_separator()
|
||||
menu.add_command(label="← Move Tab Left", command=self.move_tab_left)
|
||||
menu.add_command(label="→ Move Tab Right", command=self.move_tab_right)
|
||||
menu.add_separator()
|
||||
|
||||
move_right_btn = tk.Button(
|
||||
tab_management_frame,
|
||||
text="→ Move Right",
|
||||
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 management - use a custom popup menu that opens to the left
|
||||
# Store reference for the profile menu function
|
||||
self.profile_menu_items = []
|
||||
|
||||
# Profile dropdown
|
||||
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)))
|
||||
def show_profile_menu():
|
||||
"""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()
|
||||
self.profile_dropdown = ttk.Combobox(
|
||||
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)
|
||||
# Add Profiles item that opens the custom popup menu
|
||||
menu.add_command(label="Profiles →", command=show_profile_menu)
|
||||
|
||||
# New profile button
|
||||
new_profile_btn = tk.Button(
|
||||
tab_management_frame,
|
||||
text="+ New Profile",
|
||||
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))
|
||||
# Store menu references for updating
|
||||
self.menu_btn = menu_btn
|
||||
self.profile_var = tk.StringVar() # Keep for compatibility
|
||||
self.profile_menu = None # Not using standard menu for profiles
|
||||
|
||||
# Load profiles
|
||||
self.profiles_dir = "profiles"
|
||||
@@ -362,32 +384,89 @@ class App:
|
||||
if filename.endswith('.json'):
|
||||
profiles.append(filename[:-5]) # Remove .json extension
|
||||
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:
|
||||
# Try to load current profile name from settings
|
||||
current_profile = self.settings.get("current_profile", "")
|
||||
if current_profile in profiles:
|
||||
self.profile_var.set(current_profile)
|
||||
else:
|
||||
self.profile_var.set("")
|
||||
insert_pos = load_profile_label_idx + 1 if load_profile_label_idx is not None else self.profile_menu.index(tk.END) + 1
|
||||
|
||||
# Add each profile as a menu item
|
||||
for profile in profiles:
|
||||
label = f"✓ {profile}" if profile == current_profile else profile
|
||||
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:
|
||||
print(f"Error refreshing profiles: {e}")
|
||||
|
||||
def on_profile_selected(self, event=None):
|
||||
"""Handle profile selection from dropdown"""
|
||||
selected_profile = self.profile_var.get()
|
||||
if not selected_profile:
|
||||
"""Handle profile selection from dropdown (legacy, not used with menu)"""
|
||||
pass
|
||||
|
||||
def on_profile_selected_menu(self, profile_name):
|
||||
"""Handle profile selection from menu"""
|
||||
if not profile_name:
|
||||
return
|
||||
|
||||
# Confirm before loading (will overwrite current settings)
|
||||
result = messagebox.askyesno(
|
||||
"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"
|
||||
)
|
||||
|
||||
if result:
|
||||
self.load_profile(selected_profile)
|
||||
self.load_profile(profile_name)
|
||||
|
||||
def load_profile(self, profile_name):
|
||||
"""Load a profile from the profiles directory"""
|
||||
@@ -583,12 +662,13 @@ class App:
|
||||
desc_name = self.get_n_parameter_name(pattern_name, i)
|
||||
|
||||
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[label_key].config(text=desc_name)
|
||||
else:
|
||||
# Hide the input if no description
|
||||
tab.widgets[frame_key].grid_remove()
|
||||
# Show the input with default n{i} label if no description
|
||||
tab.widgets[frame_key].grid()
|
||||
tab.widgets[label_key].config(text=f"n{i}")
|
||||
|
||||
def get_pattern_settings(self, tab_name, pattern_name):
|
||||
"""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_frame = tk.Frame(right_panel_frame, bg=bg_color)
|
||||
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
|
||||
ids_inner_frame = tk.Frame(ids_frame, bg=bg_color)
|
||||
|
||||
Reference in New Issue
Block a user