diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index d90a0784075d3e0..7e69a5c972f1f69 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -8,6 +8,7 @@ import tkinter from tkinter import EventType from idlelib import util +from idlelib.multicall import MC_SHIFT, MC_CONTROL from idlelib.idle_test.mock_tk import Event @@ -72,9 +73,9 @@ class WheelEventTest(unittest.TestCase): # An unmapped widget has no height and does not scroll by lines, # so record the yview call instead of a real scroll. - def event(self, event_type, delta=0, num='??'): + def event(self, event_type, delta=0, num='??', state=0): # Tk leaves num '??' for a wheel event and delta 0 for a button. - return Event(type=event_type, delta=delta, num=num, + return Event(type=event_type, delta=delta, num=num, state=state, widget=mock.Mock()) def scroll(self, event, widget=None): @@ -82,8 +83,17 @@ def scroll(self, event, widget=None): self.assertEqual(util.wheel_event(event, widget), 'break') scrolled = event.widget if widget is None else widget scrolled.yview.assert_called_once() + scrolled.xview.assert_not_called() return scrolled.yview.call_args.args + def hscroll(self, event, widget=None): + "Return the arguments of the xview call." + self.assertEqual(util.wheel_event(event, widget), 'break') + scrolled = event.widget if widget is None else widget + scrolled.xview.assert_called_once() + scrolled.yview.assert_not_called() + return scrolled.xview.call_args.args + def test_mousewheel(self): # Delta is positive for up on all systems. for delta in 120, 1, 1200: @@ -100,6 +110,26 @@ def test_buttons(self): self.assertEqual(self.scroll(self.event(EventType.ButtonPress, num=5)), ('scroll', 5, 'units')) + def test_horizontal(self): + # The Shift modifier means horizontal. + for delta in 120, 1, 1200: + self.assertEqual(self.hscroll(self.event(EventType.MouseWheel, + delta, state=MC_SHIFT)), + ('scroll', -5, 'units')) + self.assertEqual(self.hscroll(self.event(EventType.MouseWheel, + -delta, state=MC_SHIFT)), + ('scroll', 5, 'units')) + self.assertEqual(self.hscroll(self.event(EventType.ButtonPress, + num=4, state=MC_SHIFT)), + ('scroll', -5, 'units')) + self.assertEqual(self.hscroll(self.event(EventType.ButtonPress, + num=5, state=MC_SHIFT)), + ('scroll', 5, 'units')) + # Other modifiers alone do not. + self.assertEqual(self.scroll(self.event(EventType.MouseWheel, + 120, state=MC_CONTROL)), + ('scroll', -5, 'units')) + def test_widget_argument(self): # A tree label scrolls the canvas, not itself. event = self.event(EventType.MouseWheel, 120) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index f408daf728d4a97..f80bd5078d667a3 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -14,6 +14,8 @@ """ import sys +from idlelib.multicall import MC_SHIFT + # .pyw is for Windows; .pyi is for typing stub files. # The extension order is needed for iomenu open/save dialogs. py_extensions = ('.py', '.pyw', '.pyi') @@ -101,16 +103,24 @@ def wheel_event(event, widget=None): A or event (see x11_buttons) says up or down by its number, and has no delta; a wheel event has no number. + If the Shift modifier is set in event.state, scroll horizontally. + Tk 9 reports horizontal scrolling, as with a touchpad, as a + event on all systems, and Tk 8.6.10 and newer + on X11 report it as a or event. + The widget parameter is needed so tree label bindings can pass the underlying canvas. If tree is replaced by ttk.Treeview, it can go. - This function depends on widget.yview to not be overridden by - a subclass. + This function depends on widget.xview and widget.yview to not be + overridden by a subclass. """ up = event.num == 4 if event.num in (4, 5) else event.delta > 0 lines = -5 if up else 5 widget = event.widget if widget is None else widget - widget.yview('scroll', lines, 'units') + if event.state & MC_SHIFT: + widget.xview('scroll', lines, 'units') + else: + widget.yview('scroll', lines, 'units') return 'break' diff --git a/Misc/ACKS b/Misc/ACKS index 4a3f6482294e087..923c68ef23ff8f5 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1975,6 +1975,7 @@ John Tromp Diane Trout Jason Trowbridge Steven Troxler +Daniel Tsvetkov Brent Tubbs Anthony Tuininga Erno Tukia diff --git a/Misc/NEWS.d/next/IDLE/2025-10-20-21-40-38.gh-issue-140389.vOs4uy.rst b/Misc/NEWS.d/next/IDLE/2025-10-20-21-40-38.gh-issue-140389.vOs4uy.rst new file mode 100644 index 000000000000000..2e48176610b81d1 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2025-10-20-21-40-38.gh-issue-140389.vOs4uy.rst @@ -0,0 +1 @@ +Fixed trackpad scrolling direction in :mod:`IDLE `