Commit 2cd90258 authored by GeeTransit's avatar GeeTransit Committed by Terry Jan Reedy

bpo-37902: IDLE: Add scrolling for IDLE browsers. (#15368)

Modify the wheel event handler so it can also be used for module, path, and stack browsers.
Patch by George Zhang.
parent 87bd2071
...@@ -3,6 +3,9 @@ Released on 2019-10-20? ...@@ -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.
......
...@@ -26,6 +26,7 @@ from idlelib import pyparse ...@@ -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): ...@@ -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): ...@@ -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):
......
...@@ -35,6 +35,14 @@ class MultiCallTest(unittest.TestCase): ...@@ -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)
...@@ -4,7 +4,7 @@ from idlelib import tree ...@@ -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): ...@@ -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)
...@@ -56,6 +56,30 @@ def listicons(icondir=ICONDIR): ...@@ -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: ...@@ -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): ...@@ -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: ...@@ -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()
......
...@@ -1871,6 +1871,7 @@ Nickolai Zeldovich ...@@ -1871,6 +1871,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
......
Add mousewheel scrolling for IDLE module, path, and stack browsers.
Patch by George Zhang.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment