diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index a040d791bdeb528..2be98d766bb06bb 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -26,10 +26,10 @@ from idlelib import query from idlelib import replace from idlelib import search -from idlelib.tree import wheel_event from idlelib.util import py_extensions from idlelib import window from idlelib.help import _get_dochome +from idlelib.util import x11_buttons, wheel_event # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 @@ -115,10 +115,11 @@ def __init__(self, flist=None, filename=None, key=None, root=None): # Elsewhere, use right-click for popup menus. text.bind("<3>",self.right_menu_event) - text.bind('', wheel_event) - if text._windowingsystem == 'x11': + if x11_buttons: text.bind('', wheel_event) text.bind('', wheel_event) + else: + text.bind('', wheel_event) text.bind('', self.handle_winconfig) text.bind("<>", self.cut) text.bind("<>", self.copy) diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py index df84554c4f1a515..a7b76c25ef866c8 100644 --- a/Lib/idlelib/idle_test/test_sidebar.py +++ b/Lib/idlelib/idle_test/test_sidebar.py @@ -14,7 +14,8 @@ from idlelib.percolator import Percolator import idlelib.pyshell from idlelib.pyshell import PyShell, PyShellFileList -from idlelib.util import fix_scaling, fix_word_breaks, fix_x11_paste +from idlelib.util import (fix_scaling, fix_word_breaks, fix_x11_paste, + x11_buttons) import idlelib.sidebar from idlelib.sidebar import get_end_linenumber, get_lineno @@ -685,21 +686,14 @@ def test_mousewheel(self): self.do_input('x = """' + '\n'*100 + '"""\n') yield self.assertGreater(get_lineno(text, '@0,0'), 1) - last_lineno = get_end_linenumber(text) self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) - # Simulate a mouse wheel notch. Tk 8.7 replaced the X11 - # / wheel events with (whose delta is - # platform-dependent); older Tk on X11 still uses the button events. - x11_buttons = (sidebar.canvas._windowingsystem == 'x11' - and tk.TkVersion < 8.7) - delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120 - # Scroll up. if x11_buttons: sidebar.canvas.event_generate('', x=0, y=0) else: + delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120 sidebar.canvas.event_generate('', x=0, y=0, delta=delta) yield self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) diff --git a/Lib/idlelib/idle_test/test_tree.py b/Lib/idlelib/idle_test/test_tree.py index b3e4c10cf9e38e7..9be9abee361f083 100644 --- a/Lib/idlelib/idle_test/test_tree.py +++ b/Lib/idlelib/idle_test/test_tree.py @@ -4,7 +4,7 @@ import unittest from test.support import requires requires('gui') -from tkinter import Tk, EventType, SCROLL +from tkinter import Tk class TreeTest(unittest.TestCase): @@ -29,32 +29,5 @@ def test_init(self): node.expand() -class TestScrollEvent(unittest.TestCase): - - def test_wheel_event(self): - # Fake widget class containing `yview` only. - class _Widget: - def __init__(widget, *expected): - widget.expected = expected - def yview(widget, *args): - self.assertTupleEqual(widget.expected, args) - # Fake event class - class _Event: - pass - # (type, delta, num, amount) - tests = ((EventType.MouseWheel, 120, -1, -5), - (EventType.MouseWheel, -120, -1, 5), - (EventType.ButtonPress, -1, 4, -5), - (EventType.ButtonPress, -1, 5, 5)) - - event = _Event() - for ty, delta, num, amount in tests: - event.type = ty - event.delta = delta - event.num = num - res = tree.wheel_event(event, _Widget(SCROLL, amount, "units")) - self.assertEqual(res, "break") - - if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_util.py b/Lib/idlelib/idle_test/test_util.py index 20721fe980c784e..b6cc23dbed5c61c 100644 --- a/Lib/idlelib/idle_test/test_util.py +++ b/Lib/idlelib/idle_test/test_util.py @@ -1,4 +1,4 @@ -"""Test util, coverage 100%""" +"""Test util, coverage %""" import unittest from idlelib import util @@ -9,6 +9,42 @@ def test_extensions(self): for extension in {'.pyi', '.py', '.pyw'}: self.assertIn(extension, util.py_extensions) +class ScrollTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.orig_x11 = util.x11_buttons + + @classmethod + def tearDownClass(cls): + util.x11_buttons = cls.orig_x11 + + def test_wheel_event(self): + # Fake widget class containing `yview` only. + class _Widget: + def yview(widget, scroll, lines, units): + self.assertTupleEqual((scroll, lines, units), + ('scroll', widget.lines, 'units')) + # Fake event class + class _Event: + pass + + event = _Event() + event.widget = widget = _Widget() + tests = ((False, 120, -5), + (False, -120, 5), + (True, 4, -5), + (True, 5, 5)) + for x11, value, lines in tests: + util.x11_buttons = x11 + event.delta, event.num = (None, value) if x11 else (value, None) + widget.lines = lines + self.assertEqual(util.wheel_event(event), "break") + + (widget2 := _Widget()).lines = widget.lines + widget.lines = 0 # Make assert fail if widget.yview called. + util.wheel_event(event, widget2) # Test delagation to passed widget. + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/tree.py b/Lib/idlelib/tree.py index 182ce7189614daf..e910f91ca78604c 100644 --- a/Lib/idlelib/tree.py +++ b/Lib/idlelib/tree.py @@ -21,6 +21,7 @@ from idlelib.config import idleConf from idlelib import zoomheight +from idlelib.util import x11_buttons, wheel_event ICONDIR = "Icons" @@ -56,33 +57,8 @@ def listicons(icondir=ICONDIR): column = 0 root.images = images -def wheel_event(event, widget=None): - """Handle scrollwheel event. - - For wheel up, event.delta = 120*n on Windows, -1*n on darwin, - where n can be > 1 if one scrolls fast. Flicking the wheel - generates up to maybe 20 events with n up to 10 or more 1. - Macs use wheel down (delta = 1*n) to scroll up, so positive - delta means to scroll up on both systems. - - X-11 sends Control-Button-4,5 events instead. - - The widget parameter is needed so browser label bindings can pass - the underlying canvas. - - This function depends on widget.yview to not be overridden by - a subclass. - """ - up = {EventType.MouseWheel: event.delta > 0, - EventType.ButtonPress: event.num == 4} - lines = -5 if up[event.type] else 5 - widget = event.widget if widget is None else widget - widget.yview(SCROLL, lines, 'units') - return 'break' - class TreeNode: - dy = 0 def __init__(self, canvas, parent, item): @@ -285,10 +261,12 @@ def drawtext(self): anchor="nw", window=self.label) self.label.bind("<1>", self.select_or_edit) self.label.bind("", self.flip) - self.label.bind("", lambda e: wheel_event(e, self.canvas)) - if self.label._windowingsystem == 'x11': - self.label.bind("", lambda e: wheel_event(e, self.canvas)) - self.label.bind("", lambda e: wheel_event(e, self.canvas)) + def wheel_event2(event): wheel_event(event, self.canvas) + if x11_buttons: + self.label.bind('', wheel_event2) + self.label.bind('', wheel_event2) + else: + self.label.bind('', wheel_event2) self.text_id = id if TreeNode.dy == 0: # The first row doesn't matter what the dy is, just measure its @@ -466,10 +444,11 @@ def __init__(self, master, **opts): self.canvas.bind("", self.page_down) self.canvas.bind("", self.unit_up) self.canvas.bind("", self.unit_down) - self.canvas.bind("", wheel_event) - if self.canvas._windowingsystem == 'x11': - self.canvas.bind("", wheel_event) - self.canvas.bind("", wheel_event) + if x11_buttons: + self.canvas.bind('', wheel_event) + self.canvas.bind('', wheel_event) + else: + self.canvas.bind('', wheel_event) #if isinstance(master, Toplevel) or isinstance(master, Tk): self.canvas.bind("", self.zoom_height) self.canvas.focus_set() diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index e884e38bef90bf1..82e76b6b9d5f77d 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -13,13 +13,14 @@ * warning stuff (pyshell, run). """ import sys +import tkinter # .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') -# fix_x functions seem only needed once per process. +# fix_x functions seem needed only once per process. def fix_scaling(root): # Called in filelist _test, pyshell, and run. """Scale fonts on HiDPI displays, once per process.""" @@ -45,6 +46,7 @@ def fix_win_hidpi(): # Called in pyshell and turtledemo. except (ImportError, AttributeError, OSError): pass + def fix_word_breaks(root): # Called in editor htest, filelist _test, pyshell. # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt. # We want Motif style everywhere. See #21474, msg218992 and followup. @@ -65,6 +67,34 @@ def fix_x11_paste(root): root.bind_class(cls, '<>')) +# On X11, Tk 8.6- signals mouse wheel rotations with and +# events. With 8.7+, it generates 1 if one scrolls fast. + Macs use wheel down (delta = 1*n) to scroll up, so positive + delta means to scroll up on both systems. + + When x11_buttons is True, Button-4,5 events signal up and down. + + The widget parameter is needed in tree so browser events can be + passed to the underlying canvas vertical scrollbar. If tree is + replaced by ttk.Treeview, 'widget' can go. + """ + up = event.num == 4 if x11_buttons else event.delta > 0 + # The following works, though +-4*n might be more standard, + lines = -5 if up else 5 + widget = event.widget if widget is None else widget + widget.yview('scroll', lines, 'units') + return 'break' + + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_util', verbosity=2)