forked from ArduPilot/MethodicConfigurator
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfrontend_tkinter_component_template_manager.py
More file actions
202 lines (157 loc) · 8.97 KB
/
frontend_tkinter_component_template_manager.py
File metadata and controls
202 lines (157 loc) · 8.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"""
Component template manager for the ArduPilot methodic configurator.
This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024-2026 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>, 2025 Francisco Fonseca
SPDX-License-Identifier: GPL-3.0-or-later
"""
import tkinter as tk
from collections.abc import Callable as CollectionsCallable
from tkinter import Menu, messagebox, simpledialog, ttk
from typing import Any, TypeVar, Union
from ardupilot_methodic_configurator import _
from ardupilot_methodic_configurator.backend_filesystem_vehicle_components import VehicleComponents
from ardupilot_methodic_configurator.frontend_tkinter_show import show_tooltip
T = TypeVar("T")
class ComponentTemplateManager: # pylint: disable=too-many-instance-attributes
"""Manages component templates for the ArduPilot methodic configurator."""
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
self,
root: Union[tk.Tk, tk.Toplevel],
entry_widgets: dict[tuple, Union[ttk.Entry, ttk.Combobox]],
get_component_data_callback: CollectionsCallable[[str], dict[str, Any]],
update_data_callback: CollectionsCallable[[str, dict[str, Any]], None],
derive_template_name_callback: CollectionsCallable[[dict[str, Any]], str],
save_component_to_system_templates: bool = False,
) -> None:
"""
Initialize the ComponentTemplateManager.
Args:
root: The root Tkinter window or toplevel widget
entry_widgets: Dictionary mapping component paths to entry widgets
get_component_data_callback: Callback to get component data
update_data_callback: Callback to update data when applying template
derive_template_name_callback: Callback to derive an initial template name from component
save_component_to_system_templates: Boolean save the component to the system file instead of the user file
"""
self.root = root
self.entry_widgets = entry_widgets
self.buttons: dict[str, ttk.Button] = {}
self.current_menu: Union[Menu, None] = None
self.template_manager = VehicleComponents(save_component_to_system_templates)
self.get_component_data_callback = get_component_data_callback
self.update_data_callback = update_data_callback
self.derive_template_name_callback = derive_template_name_callback
def add_template_controls(self, parent_frame: ttk.LabelFrame, component_name: str) -> None:
"""Add "template dropdown" and "save" buttons for a component."""
label_frame = ttk.Frame(parent_frame)
label_frame.pack(side=tk.TOP, fill=tk.X)
label = ttk.Label(label_frame, text=_("Template:"))
label.pack(side=tk.LEFT, padx=(5, 5))
# Create and add the template dropdown button
def show_template_for_component() -> None:
self.show_template_options(component_name)
dropdown_button = ttk.Button(label_frame, text="▼", width=2, command=show_template_for_component)
show_tooltip(dropdown_button, _("Select a template for this component"))
dropdown_button.pack(side=tk.LEFT)
# Create and add the save template button
def save_as_template() -> None:
self.save_component_as_template(component_name)
save_button = ttk.Button(label_frame, text="+", width=2, command=save_as_template)
show_tooltip(save_button, _("Save current configuration as template"))
save_button.pack(side=tk.LEFT, padx=(5, 0))
# Store the dropdown button reference
self.buttons[component_name] = dropdown_button
def save_component_as_template(self, component_name: str) -> None:
"""Save the current component configuration as a template."""
component_data = self.get_component_data_callback(component_name)
if not component_data:
messagebox.showerror(_("Error"), _("No data for component: ") + component_name)
return
initial_template_name = self.derive_initial_template_name(component_data)
template_name = simpledialog.askstring(
_("Save Template"), _("Enter a name for this template:"), parent=self.root, initialvalue=initial_template_name
)
if not template_name:
return
templates = self.template_manager.load_component_templates()
if component_name not in templates:
templates[component_name] = []
new_template = {"name": template_name, "data": component_data, "is_user_modified": True}
for i, template in enumerate(templates[component_name]):
if template.get("name") == template_name:
confirm = messagebox.askyesno(_("Template exists"), _("A template with this name already exists. Overwrite?"))
if confirm:
templates[component_name][i] = new_template
error, msg = self.template_manager.save_component_templates(templates)
if error:
messagebox.showerror(_("Template not Saved"), msg)
else:
messagebox.showinfo(_("Template Saved"), _("Template has been updated to file '{}'").format(msg))
return
templates[component_name].append(new_template)
error, msg = self.template_manager.save_component_templates(templates)
if error:
messagebox.showerror(_("Template not Saved"), msg)
else:
messagebox.showinfo(_("Template Saved"), _("Template has been saved to file '{}'").format(msg))
def derive_initial_template_name(self, component_data: dict[str, Any]) -> str:
"""Derive an initial template name from the component data."""
return self.derive_template_name_callback(component_data)
def create_template_dropdown_button(self, parent: ttk.Frame, component_name: str) -> ttk.Button:
"""Creates a dropdown button for component templates."""
button = ttk.Button(parent, text="▼", width=2, command=lambda: self.show_template_options(component_name))
show_tooltip(button, _("Select a template for this component"))
return button
def show_template_options(self, component_name: str) -> None:
"""Shows a dropdown menu with template options for the component."""
if isinstance(self.current_menu, Menu):
self.current_menu.unpost()
button = self.buttons.get(component_name)
if not button:
return
templates = self.template_manager.load_component_templates()
component_templates = templates.get(component_name, [])
component_templates = sorted(component_templates, key=lambda x: x.get("name", "").lower())
menu = Menu(self.root, tearoff=0)
if component_templates:
for template in component_templates:
template_name = template.get("name", "Template")
# Create a command function with proper closure
command_function = self._create_template_apply_function(component_name, template)
menu.add_command(label=template_name, command=command_function)
else:
menu.add_command(label=_("No component templates available"), state="disabled")
x = button.winfo_rootx()
y = button.winfo_rooty() + button.winfo_height()
menu.post(x, y)
self.current_menu = menu
def close_menu(_: tk.Event) -> None:
if isinstance(self.current_menu, Menu):
self.current_menu.unpost()
self.current_menu = None
self.root.unbind("<Button-1>", close_handler_id)
close_handler_id = self.root.bind("<Button-1>", close_menu, add="+")
def _create_template_apply_function(self, component_name: str, template: dict) -> CollectionsCallable[[], None]:
"""Create a function that will apply a template to a component when called."""
def apply_function() -> None:
self.apply_component_template(component_name, template)
return apply_function
def apply_component_template(self, component_name: str, template: dict) -> None:
"""Apply a template to a component."""
if "data" not in template:
return
template_data = template["data"]
# Call the callback to update the main data structure
self.update_data_callback(component_name, template_data)
for path, entry in self.entry_widgets.items():
if len(path) >= 1 and path[0] == component_name:
value = template_data
try:
for key in path[1:]:
value = value[key]
entry.delete(0, tk.END)
entry.insert(0, str(value))
except (KeyError, TypeError):
pass
template_name = template.get("name", "Template")
messagebox.showinfo(_("Template Applied"), _("{} has been applied to {}").format(template_name, component_name))