Skip to content

Commit 66f7d82

Browse files
committed
WIP
1 parent 977345f commit 66f7d82

File tree

1 file changed

+98
-27
lines changed

1 file changed

+98
-27
lines changed

ardupilot_methodic_configurator/frontend_tkinter_stage_progress.py

100644100755
Lines changed: 98 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
The progress bar can be segmented into multiple bars, one for each configuration phase.
1010
The start on the configuration phase is defined in the json file, it's end is before the start of the next phase.
1111
Phases without a start are considered milestones and are to be ignored for now.
12+
The toplevel object is to be derived from ttk.FrameLabel and it should contain multiple frames inside it.
13+
One frame per stage, all create side-by-side, from left to right.
14+
Each tk.Frame is contains the respective phase progress bar and a label with the name of the phase bellow it.
15+
The description of the phase should be displayed as tooltip inside the entire frame.
1216
1317
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
1418
@@ -22,51 +26,109 @@
2226
from logging import basicConfig as logging_basicConfig
2327
from logging import getLevelName as logging_getLevelName
2428
from tkinter import ttk
29+
from typing import Union
2530

2631
from ardupilot_methodic_configurator import _
27-
from ardupilot_methodic_configurator.common_arguments import add_common_arguments
2832
from ardupilot_methodic_configurator.backend_filesystem_configuration_steps import ConfigurationSteps
33+
from ardupilot_methodic_configurator.common_arguments import add_common_arguments
34+
from ardupilot_methodic_configurator.frontend_tkinter_base import show_tooltip
2935

3036

31-
class StageProgress(ttk.Frame):
32-
"""Stage progress UI."""
37+
class StageProgressBar(ttk.LabelFrame):
38+
"""Stage-segmented Configuration sequence progress UI."""
3339

34-
def __init__(self, parent, phases: dict, total_steps: int) -> None:
35-
super().__init__(parent)
40+
def __init__(self, master: tk.Widget, phases: dict[str, dict], total_steps: int, **kwargs) -> None:
41+
super().__init__(master, text=_("Configuration sequence progress"), **kwargs)
3642
self.phases = phases
3743
self.total_files = total_steps
44+
self.phase_frames = {}
3845
self.phase_bars = []
39-
self.create_phase_progress_bars()
4046

41-
def create_phase_progress_bars(self) -> None:
42-
"""Create segmented progress bars for each phase."""
47+
self.grid_columnconfigure(0, weight=1)
48+
self.create_phase_frames()
49+
50+
self.bind("<Configure>", self._on_resize)
51+
52+
def create_phase_frames(self) -> None:
53+
"""Create frames for each phase with progress bars and labels."""
4354
# Get phases with start positions
4455
active_phases = {k: v for k, v in self.phases.items() if "start" in v}
4556

4657
# Sort phases by start position
4758
sorted_phases = dict(sorted(active_phases.items(), key=lambda x: x[1]["start"]))
4859

60+
num_phases = len(sorted_phases)
61+
62+
# Create container frame that will expand
63+
container = ttk.Frame(self)
64+
container.pack(fill=tk.X, expand=True, padx=5, pady=5)
65+
66+
# Configure columns to expand equally
67+
for i in range(num_phases):
68+
container.grid_columnconfigure(i, weight=1, uniform="phase")
69+
4970
# Calculate segment lengths
5071
for i, (phase_name, phase_data) in enumerate(sorted_phases.items()):
5172
start = phase_data["start"]
5273
# End is either start of next phase or total files
5374
end = list(sorted_phases.values())[i + 1]["start"] if i < len(sorted_phases) - 1 else self.total_files
54-
length = end - start
55-
56-
# Create progress bar for this phase
57-
progress_frame = ttk.Frame(self)
58-
progress_frame.pack(fill="x", padx=5, pady=2)
75+
segment_length = end - start
76+
77+
frame = ttk.Frame(container)
78+
frame.grid(row=0, column=i, sticky="ew", padx=1)
79+
80+
progress = ttk.Progressbar(frame, orient=tk.HORIZONTAL, mode="determinate", maximum=segment_length)
81+
progress.pack(fill=tk.X, pady=2)
82+
83+
label_text = phase_name
84+
first_space = label_text.find(" ")
85+
if "\n" not in label_text and 6 <= first_space < 20:
86+
label_text = label_text.replace(" ", "\n", 1)
87+
if "\n" not in label_text and len(label_text) < 20:
88+
label_text += "\n "
89+
90+
label = ttk.Label(
91+
frame,
92+
text=label_text,
93+
wraplength=0, # Will be updated in _on_resize
94+
justify=tk.CENTER,
95+
anchor="center",
96+
)
97+
label.pack(fill=tk.X)
98+
99+
self.phase_frames[phase_name] = frame
100+
show_tooltip(frame, phase_data.get("description", ""))
101+
self.phase_bars.append({"bar": progress, "start": start, "end": end})
59102

60-
label = ttk.Label(progress_frame, text=phase_name)
61-
label.pack(side="left", padx=5)
103+
def _on_resize(self, _event: Union[tk.Event, None] = None) -> None:
104+
"""Update progress bar and label widths when window is resized."""
105+
if not self.phase_frames:
106+
return
62107

63-
progress = ttk.Progressbar(progress_frame, length=200, mode="determinate", maximum=length)
64-
progress.pack(side="left", fill="x", expand=True)
108+
# Calculate new width per phase
109+
num_phases = len(self.phase_frames)
110+
padding = 4 # Account for frame padding
111+
new_width = (self.winfo_width() - padding) // num_phases
65112

66-
self.phase_bars.append({"bar": progress, "start": start, "end": end})
113+
# Update wraplength for all labels
114+
for frame in self.phase_frames.values():
115+
for child in frame.winfo_children():
116+
if isinstance(child, ttk.Label):
117+
child.configure(wraplength=new_width - padding)
67118

68119
def update_progress(self, current_file: int) -> None:
69-
"""Update progress bars based on current file number."""
120+
"""
121+
Update progress bars based on current file number.
122+
123+
Args:
124+
current_file: Current configuration file number
125+
126+
Each bar will be:
127+
- Empty (0%) if current_file < start
128+
- Full (100%) if current_file > end
129+
- Show progress between start-end otherwise
130+
131+
"""
70132
for phase in self.phase_bars:
71133
if phase["start"] <= current_file <= phase["end"]:
72134
# Calculate progress within this phase
@@ -92,12 +154,9 @@ def argument_parser() -> argparse.Namespace:
92154
"""
93155
parser = argparse.ArgumentParser(
94156
description=_(
95-
"ArduPilot methodic configurator is a GUI-based tool designed to simplify "
96-
"the management and visualization of ArduPilot parameters. It enables users "
97-
"to browse through various vehicle templates, edit parameter files, and "
98-
"apply changes directly to the flight controller. The tool is built to "
99-
"semi-automate the configuration process of ArduPilot for drones by "
100-
"providing a clear and intuitive interface for parameter management."
157+
"ArduPilot methodic configurator is a Wizard-style GUI tool to configure "
158+
"ArduPilot parameters. This module shows configuration sequence progress in "
159+
"the form of a configuration-stage-segmented progress bar."
101160
)
102161
)
103162
return add_common_arguments(parser).parse_args()
@@ -115,8 +174,20 @@ def main() -> None:
115174
config_steps = ConfigurationSteps("", "ArduCopter")
116175
config_steps.re_init("", "ArduCopter")
117176

118-
progress = StageProgress(root, config_steps.configuration_phases, 53)
119-
progress.pack(padx=10, pady=10, fill="x")
177+
progress = StageProgressBar(root, config_steps.configuration_phases, 54)
178+
progress.pack(padx=10, pady=10, fill="both", expand=True)
179+
180+
# Demo update function
181+
current_file = 2
182+
183+
def update_demo() -> None:
184+
nonlocal current_file
185+
progress.update_progress(current_file)
186+
current_file = 2 if current_file > 54 else current_file + 1
187+
root.after(1000, update_demo)
188+
189+
# Start demo updates
190+
update_demo()
120191

121192
root.mainloop()
122193

0 commit comments

Comments
 (0)