Skip to content

Commit 7c980be

Browse files
committed
fix(mouse scroll): scroll the frame, do not change the combobox values if the comboboxes are not open
1 parent 037fc59 commit 7c980be

2 files changed

Lines changed: 157 additions & 1 deletion

File tree

ardupilot_methodic_configurator/frontend_tkinter_parameter_editor_table.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from platform import system as platform_system
1717
from sys import exit as sys_exit
1818
from tkinter import messagebox, ttk
19-
from typing import Union
19+
from typing import Optional, Union
2020

2121
from ardupilot_methodic_configurator import _
2222
from ardupilot_methodic_configurator.annotate_params import Par
@@ -358,6 +358,37 @@ def _update_combobox_style_on_selection( # pylint: disable=too-many-arguments,
358358
event.width = NEW_VALUE_WIDGET_WIDTH
359359
combobox_widget.on_combo_configure(event)
360360

361+
def _setup_combobox_mousewheel_handling(self, combobox: PairTupleCombobox) -> None:
362+
"""Set up mouse wheel handling for combobox to prevent unwanted value changes."""
363+
364+
# Prevent mouse wheel from changing value when dropdown is not open
365+
def handle_mousewheel(_event: tk.Event, widget: tk.Widget = combobox) -> Optional[str]:
366+
# Check if dropdown is open by examining the combobox's state
367+
dropdown_is_open = getattr(widget, "dropdown_is_open", False)
368+
if not dropdown_is_open:
369+
widget.master.event_generate("<MouseWheel>", delta=_event.delta)
370+
return "break" # Prevent default behavior
371+
return None # Allow default behavior when dropdown is open
372+
373+
# Set flag when dropdown opens or closes
374+
def dropdown_opened(_event: tk.Event, widget: tk.Widget = combobox) -> None:
375+
widget.dropdown_is_open = True # type: ignore[attr-defined]
376+
377+
def dropdown_closed(_event: tk.Event, widget: tk.Widget = combobox) -> None:
378+
widget.dropdown_is_open = False # type: ignore[attr-defined]
379+
380+
# Initialize the flag
381+
combobox.dropdown_is_open = False # type: ignore[attr-defined]
382+
383+
# Bind to events for dropdown opening and closing
384+
combobox.bind("<<ComboboxDropdown>>", dropdown_opened)
385+
combobox.bind("<FocusOut>", dropdown_closed, "+")
386+
387+
# Bind mouse wheel events
388+
combobox.bind("<MouseWheel>", handle_mousewheel) # Windows mouse wheel
389+
combobox.bind("<Button-4>", handle_mousewheel) # Linux mouse wheel up
390+
combobox.bind("<Button-5>", handle_mousewheel) # Linux mouse wheel down
391+
361392
@staticmethod
362393
def _update_new_value_entry_text(new_value_entry: ttk.Entry, param: ArduPilotParameter) -> None:
363394
"""Update the new value entry text and style."""
@@ -409,6 +440,9 @@ def _create_new_value_entry( # pylint: disable=too-many-statements # noqa: PLR0
409440
),
410441
"+",
411442
)
443+
444+
# Set up mouse wheel handling to prevent unwanted value changes
445+
self._setup_combobox_mousewheel_handling(new_value_entry)
412446
else:
413447
new_value_entry = ttk.Entry(self.view_port, width=NEW_VALUE_WIDGET_WIDTH + 1, justify=tk.RIGHT)
414448
self._update_new_value_entry_text(new_value_entry, param)

tests/test_frontend_tkinter_parameter_editor_table.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,125 @@ def test_gui_complexity_affects_complete_workflow(self, parameter_editor_table:
899899
parameter_editor_table._should_show_upload_column()
900900
)
901901
assert column_index_advanced == 7
902+
903+
904+
class TestMousewheelHandlingBehavior:
905+
"""Test mousewheel handling behavior for comboboxes."""
906+
907+
def test_setup_combobox_mousewheel_handling(self, parameter_editor_table: ParameterEditorTable) -> None:
908+
"""Test that mousewheel handling is properly set up for comboboxes."""
909+
# Create a mock PairTupleCombobox
910+
mock_combobox = MagicMock(spec=PairTupleCombobox)
911+
912+
# Call the mousewheel setup method
913+
parameter_editor_table._setup_combobox_mousewheel_handling(mock_combobox)
914+
915+
# Verify that the dropdown_is_open flag is initialized
916+
assert hasattr(mock_combobox, "dropdown_is_open")
917+
918+
# Verify that the required event bindings are set up
919+
expected_bindings = [
920+
("<<ComboboxDropdown>>",),
921+
("<FocusOut>",),
922+
("<MouseWheel>",),
923+
("<Button-4>",),
924+
("<Button-5>",),
925+
]
926+
927+
bind_calls = mock_combobox.bind.call_args_list
928+
for expected_binding in expected_bindings:
929+
assert any(call.args[0] == expected_binding[0] for call in bind_calls), f"Binding {expected_binding[0]} not found"
930+
931+
def test_mousewheel_handler_when_dropdown_closed(self, parameter_editor_table: ParameterEditorTable) -> None:
932+
"""Test mousewheel behavior when dropdown is closed."""
933+
with patch("tkinter.Tk"):
934+
mock_combobox = MagicMock(spec=PairTupleCombobox)
935+
mock_master = MagicMock()
936+
mock_combobox.master = mock_master
937+
mock_combobox.dropdown_is_open = False
938+
939+
# Set up mousewheel handling
940+
parameter_editor_table._setup_combobox_mousewheel_handling(mock_combobox)
941+
942+
# Get the mousewheel handler from the bind calls
943+
mousewheel_bind_call = None
944+
for call in mock_combobox.bind.call_args_list:
945+
if call[0][0] == "<MouseWheel>":
946+
mousewheel_bind_call = call
947+
break
948+
949+
assert mousewheel_bind_call is not None, "MouseWheel binding not found"
950+
951+
# Simulate a mousewheel event when dropdown is closed
952+
handler = mousewheel_bind_call[0][1]
953+
mock_event = MagicMock()
954+
mock_event.delta = 120
955+
956+
result = handler(mock_event)
957+
958+
# Should return "break" and generate event on master
959+
assert result == "break"
960+
mock_master.event_generate.assert_called_once_with("<MouseWheel>", delta=120)
961+
962+
def test_mousewheel_handler_when_dropdown_open(self, parameter_editor_table: ParameterEditorTable) -> None:
963+
"""Test mousewheel behavior when dropdown is open."""
964+
with patch("tkinter.Tk"):
965+
mock_combobox = MagicMock(spec=PairTupleCombobox)
966+
mock_master = MagicMock()
967+
mock_combobox.master = mock_master
968+
969+
# Set up mousewheel handling first
970+
parameter_editor_table._setup_combobox_mousewheel_handling(mock_combobox)
971+
972+
# Now set dropdown as open after the handler is set up
973+
mock_combobox.dropdown_is_open = True
974+
975+
# Get the mousewheel handler from the bind calls
976+
mousewheel_bind_call = None
977+
for call in mock_combobox.bind.call_args_list:
978+
if call[0][0] == "<MouseWheel>":
979+
mousewheel_bind_call = call
980+
break
981+
982+
assert mousewheel_bind_call is not None, "MouseWheel binding not found"
983+
984+
# Simulate a mousewheel event when dropdown is open
985+
handler = mousewheel_bind_call[0][1]
986+
mock_event = MagicMock()
987+
mock_event.delta = 120
988+
989+
result = handler(mock_event)
990+
991+
# Should return None and not generate event on master
992+
assert result is None
993+
mock_master.event_generate.assert_not_called()
994+
995+
def test_dropdown_state_management(self, parameter_editor_table: ParameterEditorTable) -> None:
996+
"""Test that dropdown state is properly managed."""
997+
with patch("tkinter.Tk"):
998+
mock_combobox = MagicMock(spec=PairTupleCombobox)
999+
1000+
# Set up mousewheel handling
1001+
parameter_editor_table._setup_combobox_mousewheel_handling(mock_combobox)
1002+
1003+
# Find the dropdown opened and closed handlers
1004+
dropdown_opened_handler = None
1005+
dropdown_closed_handler = None
1006+
1007+
for call in mock_combobox.bind.call_args_list:
1008+
if call[0][0] == "<<ComboboxDropdown>>":
1009+
dropdown_opened_handler = call[0][1]
1010+
elif call[0][0] == "<FocusOut>":
1011+
dropdown_closed_handler = call[0][1]
1012+
1013+
assert dropdown_opened_handler is not None, "ComboboxDropdown handler not found"
1014+
assert dropdown_closed_handler is not None, "FocusOut handler not found"
1015+
1016+
# Test dropdown opened
1017+
mock_event = MagicMock()
1018+
dropdown_opened_handler(mock_event)
1019+
assert mock_combobox.dropdown_is_open is True
1020+
1021+
# Test dropdown closed
1022+
dropdown_closed_handler(mock_event)
1023+
assert mock_combobox.dropdown_is_open is False

0 commit comments

Comments
 (0)