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": {
|
"lights": {
|
||||||
"ring1": {
|
"ring1": {
|
||||||
"names": [
|
"names": [
|
||||||
@@ -59,7 +106,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tab_password": "",
|
|
||||||
"tab_order": [
|
"tab_order": [
|
||||||
"ring1",
|
"ring1",
|
||||||
"ring2"
|
"ring2"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
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.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)
|
||||||
|
|||||||
Reference in New Issue
Block a user