From 9c2654d1aa85968fede1b888fba86aebc06c5be6 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 4 Sep 2019 18:53:47 -0700 Subject: [PATCH] bpo-37902: IDLE: Add scrolling for IDLE browsers. (GH-15368) Modify the wheel event handler so it can also be used for module, path, and stack browsers. Patch by George Zhang. (cherry picked from commit 2cd902585815582eb059e3b40e014ebe4e7fdee7) Co-authored-by: GeeTransit --- Lib/idlelib/NEWS.txt | 3 ++ Lib/idlelib/editor.py | 25 +++------------ Lib/idlelib/idle_test/test_multicall.py | 8 +++++ Lib/idlelib/idle_test/test_tree.py | 29 ++++++++++++++++- Lib/idlelib/tree.py | 31 +++++++++++++++++++ Misc/ACKS | 1 + .../2019-08-21-16-02-49.bpo-37902._R_adE.rst | 2 ++ 7 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2019-08-21-16-02-49.bpo-37902._R_adE.rst diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 47c2291d237..c9e846a6fba 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,9 @@ Released on 2019-10-20? ====================================== +bpo-37092: Add mousewheel scrolling for IDLE module, path, and stack +browsers. Patch by George Zhang. + bpo-35771: To avoid occasional spurious test_idle failures on slower machines, increase the ``hover_delay`` in test_tooltip. diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 793ed3afaed..5cbf704ab27 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -26,6 +26,7 @@ from idlelib import pyparse from idlelib import query from idlelib import replace from idlelib import search +from idlelib.tree import wheel_event from idlelib import window # The default tab setting for a Text widget, in average-width characters. @@ -151,9 +152,10 @@ class EditorWindow(object): else: # Elsewhere, use right-click for popup menus. text.bind("<3>",self.right_menu_event) - text.bind('', self.mousescroll) - text.bind('', self.mousescroll) - text.bind('', self.mousescroll) + + text.bind('', wheel_event) + text.bind('', wheel_event) + text.bind('', wheel_event) text.bind('', self.handle_winconfig) text.bind("<>", self.cut) text.bind("<>", self.copy) @@ -502,23 +504,6 @@ class EditorWindow(object): self.text.yview(event, *args) return 'break' - def mousescroll(self, event): - """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 event instead. - """ - up = {EventType.MouseWheel: event.delta > 0, - EventType.Button: event.num == 4} - lines = -5 if up[event.type] else 5 - self.text.yview_scroll(lines, 'units') - return 'break' - rmenu = None def right_menu_event(self, event): diff --git a/Lib/idlelib/idle_test/test_multicall.py b/Lib/idlelib/idle_test/test_multicall.py index 68156a743d7..ba582bb3ca5 100644 --- a/Lib/idlelib/idle_test/test_multicall.py +++ b/Lib/idlelib/idle_test/test_multicall.py @@ -35,6 +35,14 @@ class MultiCallTest(unittest.TestCase): mctext = self.mc(self.root) self.assertIsInstance(mctext._MultiCall__binders, list) + def test_yview(self): + # Added for tree.wheel_event + # (it depends on yview to not be overriden) + mc = self.mc + self.assertIs(mc.yview, Text.yview) + mctext = self.mc(self.root) + self.assertIs(mctext.yview.__func__, Text.yview) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_tree.py b/Lib/idlelib/idle_test/test_tree.py index 9be9abee361..b3e4c10cf9e 100644 --- a/Lib/idlelib/idle_test/test_tree.py +++ b/Lib/idlelib/idle_test/test_tree.py @@ -4,7 +4,7 @@ from idlelib import tree import unittest from test.support import requires requires('gui') -from tkinter import Tk +from tkinter import Tk, EventType, SCROLL class TreeTest(unittest.TestCase): @@ -29,5 +29,32 @@ class TreeTest(unittest.TestCase): 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/tree.py b/Lib/idlelib/tree.py index 21426cbb33e..6229be4e5a8 100644 --- a/Lib/idlelib/tree.py +++ b/Lib/idlelib/tree.py @@ -56,6 +56,30 @@ 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: @@ -260,6 +284,9 @@ class TreeNode: 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)) + self.label.bind("", lambda e: wheel_event(e, self.canvas)) + self.label.bind("", lambda e: wheel_event(e, self.canvas)) self.text_id = id def select_or_edit(self, event=None): @@ -410,6 +437,7 @@ class FileTreeItem(TreeItem): # A canvas widget with scroll bars and some useful bindings class ScrolledCanvas: + def __init__(self, master, **opts): if 'yscrollincrement' not in opts: opts['yscrollincrement'] = 17 @@ -431,6 +459,9 @@ class ScrolledCanvas: self.canvas.bind("", self.page_down) self.canvas.bind("", self.unit_up) self.canvas.bind("", self.unit_down) + self.canvas.bind("", wheel_event) + self.canvas.bind("", wheel_event) + 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/Misc/ACKS b/Misc/ACKS index def874b0071..290f8ea3326 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1863,6 +1863,7 @@ Nickolai Zeldovich Yuxiao Zeng Uwe Zessin Cheng Zhang +George Zhang Kai Zhu Tarek Ziadé Jelle Zijlstra diff --git a/Misc/NEWS.d/next/IDLE/2019-08-21-16-02-49.bpo-37902._R_adE.rst b/Misc/NEWS.d/next/IDLE/2019-08-21-16-02-49.bpo-37902._R_adE.rst new file mode 100644 index 00000000000..24b41424846 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2019-08-21-16-02-49.bpo-37902._R_adE.rst @@ -0,0 +1,2 @@ +Add mousewheel scrolling for IDLE module, path, and stack browsers. +Patch by George Zhang.