Skip to content

Commit 37c4503

Browse files
fix: timer and on-demand load along with fast loading on large parameter table
Signed-off-by: Omkar Sarkar <omkarsarkar24@gmail.com>
1 parent 784dd15 commit 37c4503

2 files changed

Lines changed: 90 additions & 75 deletions

File tree

ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_table.py

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -219,44 +219,82 @@ def _apply_scroll_position(self, scroll_to_bottom: bool) -> None:
219219
self.canvas.yview_moveto(position)
220220

221221
def _update_table(self, params: dict[str, ArduPilotParameter], gui_complexity: str) -> None:
222-
"""Update the parameter table with the given parameters."""
223-
current_param_name: str = ""
224-
show_upload_column = self._should_show_upload_column(gui_complexity)
222+
"""Initialize scroll load."""
223+
self._params_list = list(params.items())
224+
self._total_params = len(self._params_list)
225+
self._current_idx = 0
226+
self._gui_complexity = gui_complexity
227+
self._is_loading = False
228+
229+
self._load_next_chunk(chunk_size=20)
230+
self._page_scroll()
231+
232+
def _load_next_chunk(self, chunk_size: int = 15) -> None:
233+
"""Appends the next batch of widgets to the bottom of the table."""
234+
if self._is_loading or self._current_idx >= self._total_params:
235+
return
225236

226-
should_try_to_display_bitmask_parameter_editor_usage = False
227-
try:
228-
for i, (param_name, param) in enumerate(params.items(), 1):
229-
current_param_name = param_name
237+
self._is_loading = True
238+
show_upload_column = self._should_show_upload_column(self._gui_complexity)
239+
should_show_bitmask_usage = False
230240

231-
row_widgets: list[tk.Widget] = self._create_column_widgets(param_name, param, show_upload_column)
232-
if self.parameter_editor.should_display_bitmask_parameter_editor_usage(param_name):
233-
should_try_to_display_bitmask_parameter_editor_usage = True
234-
self._grid_column_widgets(row_widgets, i, show_upload_column)
241+
start_idx = self._current_idx
242+
end_idx = min(start_idx + chunk_size, self._total_params)
235243

236-
# Add the "Add" button at the bottom of the table
237-
add_button = ttk.Button(self.view_port, text=_("Add"), style="narrow.TButton", command=self._on_parameter_add)
238-
tooltip_msg = _("Add a parameter to the {self.parameter_editor.current_file} file")
239-
show_tooltip(add_button, tooltip_msg.format(**locals()))
240-
add_button.grid(row=len(params) + 2, column=0, sticky="w", padx=0)
244+
if hasattr(self, "_add_button_widget") and self._add_button_widget.winfo_exists():
245+
self._add_button_widget.destroy()
241246

247+
try:
248+
for i in range(start_idx, end_idx):
249+
param_name, param = self._params_list[i]
250+
row_widgets = self._create_column_widgets(param_name, param, show_upload_column)
251+
if self.parameter_editor.should_display_bitmask_parameter_editor_usage(param_name):
252+
should_show_bitmask_usage = True
253+
self._grid_column_widgets(row_widgets, i + 1, show_upload_column)
242254
except KeyError as e:
243255
logging_critical(
244256
_("Parameter %s not found in the %s file: %s"),
245-
current_param_name,
257+
param_name,
246258
self.parameter_editor.current_file,
247259
e,
248260
exc_info=True,
249261
)
250262
sys_exit(1)
251263

264+
self._current_idx = end_idx
265+
266+
# Fix layout if all items are loaded
267+
if self._current_idx >= self._total_params:
268+
self._add_button_widget = ttk.Button(
269+
self.view_port, text=_("Add"), style="narrow.TButton", command=self._on_parameter_add
270+
)
271+
show_tooltip(
272+
self._add_button_widget,
273+
_("Add a parameter to the {self.parameter_editor.current_file} file").format(**locals()),
274+
)
275+
self._add_button_widget.grid(row=self._total_params + 2, column=0, sticky="w", pady=(10, 0))
276+
277+
parent_root = self._get_parent_root()
278+
if parent_root and should_show_bitmask_usage and UsagePopupWindow.should_display("bitmask_parameter_editor"):
279+
display_bitmask_parameters_editor_usage_popup(parent_root)
280+
252281
self._configure_table_columns(show_upload_column)
253-
parent_root = self._get_parent_root()
254-
if (
255-
parent_root
256-
and should_try_to_display_bitmask_parameter_editor_usage
257-
and UsagePopupWindow.should_display("bitmask_parameter_editor")
258-
):
259-
display_bitmask_parameters_editor_usage_popup(parent_root)
282+
self.view_port.update_idletasks()
283+
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
284+
self._is_loading = False
285+
286+
def _page_scroll(self) -> None:
287+
"""Load more items when reaching the bottom."""
288+
if not self.winfo_exists() or self._current_idx >= self._total_params:
289+
return
290+
291+
try:
292+
if self.canvas.yview()[1] > 0.85:
293+
self._load_next_chunk(chunk_size=15)
294+
except tk.TclError:
295+
pass
296+
297+
self.after(150, self._page_scroll)
260298

261299
def _create_column_widgets(self, param_name: str, param: ArduPilotParameter, show_upload_column: bool) -> list[tk.Widget]:
262300
"""Create all column widgets for a parameter row."""

ardupilot_methodic_configurator/frontend_tkinter_show.py

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -477,33 +477,13 @@ def __init__( # pylint: disable=too-many-arguments, too-many-positional-argumen
477477
self.timers: dict[str, Optional[str]] = {}
478478

479479
# Bind the <Enter> and <Leave> events to show and hide the tooltip
480-
if platform_system() == "Darwin":
481-
# On macOS, defer tooltip creation slightly to avoid flashing while
482-
# moving through dense tables and controls.
483-
if tag_name and isinstance(self.widget, tk.Text):
484-
self.widget.tag_bind(tag_name, "<Enter>", self.schedule_show, "+")
485-
self.widget.tag_bind(tag_name, "<Leave>", self.destroy_hide, "+")
486-
else:
487-
self.widget.bind("<Enter>", self.schedule_show, "+")
488-
self.widget.bind("<Leave>", self.destroy_hide, "+")
480+
# Defer tooltip creation slightly to avoid flashing while moving through dense tables.
481+
if tag_name and isinstance(self.widget, tk.Text):
482+
self.widget.tag_bind(tag_name, "<Enter>", self.schedule_show, "+")
483+
self.widget.tag_bind(tag_name, "<Leave>", self.destroy_hide, "+")
489484
else:
490-
if tag_name and isinstance(self.widget, tk.Text):
491-
self.widget.tag_bind(tag_name, "<Enter>", self.show, "+")
492-
self.widget.tag_bind(tag_name, "<Leave>", self.hide, "+")
493-
else:
494-
self.widget.bind("<Enter>", self.show, "+")
495-
self.widget.bind("<Leave>", self.hide, "+")
496-
# On non-macOS, create the tooltip immediately and show/hide it on events
497-
self.tooltip = cast("tk.Toplevel", self.toplevel_class(widget))
498-
self.tooltip.wm_overrideredirect(boolean=True)
499-
tooltip_label = ttk.Label(
500-
self.tooltip, text=text, background="#ffffe0", relief="solid", borderwidth=1, justify=tk.LEFT
501-
)
502-
tooltip_label.pack()
503-
self.tooltip.withdraw() # Initially hide the tooltip
504-
# Bind to tooltip to prevent hiding when mouse is over it
505-
self.tooltip.bind("<Enter>", self._cancel_hide)
506-
self.tooltip.bind("<Leave>", self.hide)
485+
self.widget.bind("<Enter>", self.schedule_show, "+")
486+
self.widget.bind("<Leave>", self.destroy_hide, "+")
507487

508488
self.widget.bind("<Destroy>", self._on_widget_destroy, "+")
509489

@@ -517,14 +497,14 @@ def _cancel_timer(self, name: str) -> None:
517497
def _cancel_show(self) -> None:
518498
self._cancel_timer("show")
519499

520-
def show(self, event: Optional[tk.Event] = None) -> None: # noqa: ARG002 # pylint: disable=unused-argument
521-
"""On non-macOS, tooltip already exists, show it on events."""
522-
self._cancel_hide()
523-
self._hide_active_tooltip()
524-
if self.tooltip:
525-
self.position_tooltip()
526-
self.tooltip.deiconify()
527-
Tooltip._active_tooltip = self
500+
# def show(self, event: Optional[tk.Event] = None) -> None: # pylint: disable=unused-argument
501+
# """On non-macOS, tooltip already exists, show it on events."""
502+
# self._cancel_hide()
503+
# self._hide_active_tooltip()
504+
# if self.tooltip:
505+
# self.position_tooltip()
506+
# self.tooltip.deiconify()
507+
# Tooltip._active_tooltip = self
528508

529509
def _cancel_hide(self, event: Optional[tk.Event] = None) -> None: # noqa: ARG002 # pylint: disable=unused-argument
530510
self._cancel_timer("hide")
@@ -589,7 +569,7 @@ def create_show(self, event: Optional[tk.Event] = None) -> None: # noqa: ARG002
589569
"noActivates",
590570
)
591571
self.tooltip.configure(bg="#ffffe0")
592-
except AttributeError: # Catches protected member access error
572+
except (AttributeError, tk.TclError): # Catches protected member access error
593573
self.tooltip.wm_attributes("-alpha", 1.0) # Ensure opacity
594574
self.tooltip.wm_attributes("-topmost", True) # Keep on top # noqa: FBT003
595575
self.tooltip.configure(bg="#ffffe0")
@@ -652,30 +632,27 @@ def position_tooltip(self) -> None:
652632
# Silently ignore - tooltip will be recreated on next hover if needed
653633
pass
654634

655-
def hide(self, event: Optional[tk.Event] = None) -> None: # noqa: ARG002 # pylint: disable=unused-argument
656-
"""Hide the tooltip after a delay on non-macOS."""
657-
self._cancel_hide()
658-
self.timers["hide"] = self.widget.after(TOOLTIP_HIDE_DELAY_MS, self._do_hide)
635+
# def hide(self, event: Optional[tk.Event] = None) -> None: # pylint: disable=unused-argument
636+
# """Hide the tooltip after a delay on non-macOS."""
637+
# self._cancel_hide()
638+
# self.timers["hide"] = self.widget.after(TOOLTIP_HIDE_DELAY_MS, self._do_hide)
659639

660-
def _do_hide(self) -> None:
661-
"""Actually hide or destroy the tooltip depending on platform."""
662-
if self.tooltip:
663-
self.tooltip.withdraw()
664-
if Tooltip._active_tooltip is self:
665-
Tooltip._active_tooltip = None
666-
self.timers.pop("hide", None)
640+
# def _do_hide(self) -> None:
641+
# """Actually hide or destroy the tooltip depending on platform."""
642+
# if self.tooltip:
643+
# self.tooltip.withdraw()
644+
# if Tooltip._active_tooltip is self:
645+
# Tooltip._active_tooltip = None
646+
# self.timers.pop("hide", None)
667647

668648
def force_hide(self) -> None:
669649
"""Immediately hide or destroy the tooltip, depending on platform."""
670650
self._cancel_show()
671651
self._cancel_hide()
672652
self._cancel_timer("alpha")
673653
if self.tooltip:
674-
if platform_system() == "Darwin":
675-
self.tooltip.destroy()
676-
self.tooltip = None
677-
else:
678-
self.tooltip.withdraw()
654+
self.tooltip.destroy()
655+
self.tooltip = None
679656
if Tooltip._active_tooltip is self:
680657
Tooltip._active_tooltip = None
681658

0 commit comments

Comments
 (0)