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 2cd9025858)

Co-authored-by: GeeTransit <geetransit@gmail.com>
This commit is contained in:
Miss Islington (bot) 2019-09-04 18:53:47 -07:00 committed by GitHub
parent 6ad0a2c45f
commit 9c2654d1aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 21 deletions

View file

@ -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 bpo-35771: To avoid occasional spurious test_idle failures on slower
machines, increase the ``hover_delay`` in test_tooltip. machines, increase the ``hover_delay`` in test_tooltip.

View file

@ -26,6 +26,7 @@ from idlelib import pyparse
from idlelib import query from idlelib import query
from idlelib import replace from idlelib import replace
from idlelib import search from idlelib import search
from idlelib.tree import wheel_event
from idlelib import window from idlelib import window
# The default tab setting for a Text widget, in average-width characters. # The default tab setting for a Text widget, in average-width characters.
@ -151,9 +152,10 @@ class EditorWindow(object):
else: else:
# Elsewhere, use right-click for popup menus. # Elsewhere, use right-click for popup menus.
text.bind("<3>",self.right_menu_event) text.bind("<3>",self.right_menu_event)
text.bind('<MouseWheel>', self.mousescroll)
text.bind('<Button-4>', self.mousescroll) text.bind('<MouseWheel>', wheel_event)
text.bind('<Button-5>', self.mousescroll) text.bind('<Button-4>', wheel_event)
text.bind('<Button-5>', wheel_event)
text.bind('<Configure>', self.handle_winconfig) text.bind('<Configure>', self.handle_winconfig)
text.bind("<<cut>>", self.cut) text.bind("<<cut>>", self.cut)
text.bind("<<copy>>", self.copy) text.bind("<<copy>>", self.copy)
@ -502,23 +504,6 @@ class EditorWindow(object):
self.text.yview(event, *args) self.text.yview(event, *args)
return 'break' 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 rmenu = None
def right_menu_event(self, event): def right_menu_event(self, event):

View file

@ -35,6 +35,14 @@ class MultiCallTest(unittest.TestCase):
mctext = self.mc(self.root) mctext = self.mc(self.root)
self.assertIsInstance(mctext._MultiCall__binders, list) 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__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)

View file

@ -4,7 +4,7 @@ from idlelib import tree
import unittest import unittest
from test.support import requires from test.support import requires
requires('gui') requires('gui')
from tkinter import Tk from tkinter import Tk, EventType, SCROLL
class TreeTest(unittest.TestCase): class TreeTest(unittest.TestCase):
@ -29,5 +29,32 @@ class TreeTest(unittest.TestCase):
node.expand() 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__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)

View file

@ -56,6 +56,30 @@ def listicons(icondir=ICONDIR):
column = 0 column = 0
root.images = images 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: class TreeNode:
@ -260,6 +284,9 @@ class TreeNode:
anchor="nw", window=self.label) anchor="nw", window=self.label)
self.label.bind("<1>", self.select_or_edit) self.label.bind("<1>", self.select_or_edit)
self.label.bind("<Double-1>", self.flip) self.label.bind("<Double-1>", self.flip)
self.label.bind("<MouseWheel>", lambda e: wheel_event(e, self.canvas))
self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas))
self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas))
self.text_id = id self.text_id = id
def select_or_edit(self, event=None): def select_or_edit(self, event=None):
@ -410,6 +437,7 @@ class FileTreeItem(TreeItem):
# A canvas widget with scroll bars and some useful bindings # A canvas widget with scroll bars and some useful bindings
class ScrolledCanvas: class ScrolledCanvas:
def __init__(self, master, **opts): def __init__(self, master, **opts):
if 'yscrollincrement' not in opts: if 'yscrollincrement' not in opts:
opts['yscrollincrement'] = 17 opts['yscrollincrement'] = 17
@ -431,6 +459,9 @@ class ScrolledCanvas:
self.canvas.bind("<Key-Next>", self.page_down) self.canvas.bind("<Key-Next>", self.page_down)
self.canvas.bind("<Key-Up>", self.unit_up) self.canvas.bind("<Key-Up>", self.unit_up)
self.canvas.bind("<Key-Down>", self.unit_down) self.canvas.bind("<Key-Down>", self.unit_down)
self.canvas.bind("<MouseWheel>", wheel_event)
self.canvas.bind("<Button-4>", wheel_event)
self.canvas.bind("<Button-5>", wheel_event)
#if isinstance(master, Toplevel) or isinstance(master, Tk): #if isinstance(master, Toplevel) or isinstance(master, Tk):
self.canvas.bind("<Alt-Key-2>", self.zoom_height) self.canvas.bind("<Alt-Key-2>", self.zoom_height)
self.canvas.focus_set() self.canvas.focus_set()

View file

@ -1863,6 +1863,7 @@ Nickolai Zeldovich
Yuxiao Zeng Yuxiao Zeng
Uwe Zessin Uwe Zessin
Cheng Zhang Cheng Zhang
George Zhang
Kai Zhu Kai Zhu
Tarek Ziadé Tarek Ziadé
Jelle Zijlstra Jelle Zijlstra

View file

@ -0,0 +1,2 @@
Add mousewheel scrolling for IDLE module, path, and stack browsers.
Patch by George Zhang.