Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Lib/asyncio/selector_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,7 +1127,9 @@ def _write_sendmsg(self):
self._loop._remove_writer(self._sock_fd)
if self._empty_waiter is not None:
self._empty_waiter.set_result(None)
if self._closing:
# gh-156512: don't let _call_connection_lost be called twice
if self._closing and not self._conn_lost:
self._conn_lost += 1
self._call_connection_lost(None)
elif self._eof:
self._sock.shutdown(socket.SHUT_WR)
Expand Down Expand Up @@ -1173,7 +1175,9 @@ def _write_send(self):
self._loop._remove_writer(self._sock_fd)
if self._empty_waiter is not None:
self._empty_waiter.set_result(None)
if self._closing:
# gh-156512: don't let _call_connection_lost be called twice
if self._closing and not self._conn_lost:
self._conn_lost += 1
self._call_connection_lost(None)
elif self._eof:
self._sock.shutdown(socket.SHUT_WR)
Expand Down
8 changes: 2 additions & 6 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
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.util import bind_wheel, py_extensions, wheel_event
from idlelib import window
from idlelib.help import _get_dochome

Expand Down Expand Up @@ -115,10 +114,7 @@ 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('<MouseWheel>', wheel_event)
if text._windowingsystem == 'x11':
text.bind('<Button-4>', wheel_event)
text.bind('<Button-5>', wheel_event)
bind_wheel(text, wheel_event)
text.bind('<Configure>', self.handle_winconfig)
text.bind("<<cut>>", self.cut)
text.bind("<<copy>>", self.copy)
Expand Down
15 changes: 7 additions & 8 deletions Lib/idlelib/idle_test/test_sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -689,23 +690,21 @@ def test_mousewheel(self):
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
# <Button-4>/<Button-5> wheel events with <MouseWheel> (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)
# Simulate a mouse wheel notch with the events that Tk sends for
# one; the delta of a <MouseWheel> event is platform-dependent.
buttons = x11_buttons(sidebar.canvas)
delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120

# Scroll up.
if x11_buttons:
if buttons:
sidebar.canvas.event_generate('<Button-4>', x=0, y=0)
else:
sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=delta)
yield
self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0')))

# Scroll back down.
if x11_buttons:
if buttons:
sidebar.canvas.event_generate('<Button-5>', x=0, y=0)
else:
sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=-delta)
Expand Down
29 changes: 1 addition & 28 deletions Lib/idlelib/idle_test/test_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
152 changes: 152 additions & 0 deletions Lib/idlelib/idle_test/test_util.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,166 @@
"""Test util, coverage 100%"""

import sys
import unittest
from unittest import mock
from test.support import requires
from test.support.isolation import runInSubprocess
import tkinter
from tkinter import EventType
from idlelib import util
from idlelib.idle_test.mock_tk import Event


class UtilTest(unittest.TestCase):

def test_extensions(self):
for extension in {'.pyi', '.py', '.pyw'}:
self.assertIn(extension, util.py_extensions)

@unittest.skipUnless(sys.platform == 'win32', 'Windows only')
@runInSubprocess()
def test_fix_win_hidpi(self):
# Awareness is process-wide and cannot be undone.
import ctypes
PROCESS_DPI_UNAWARE = 0
util.fix_win_hidpi()
awareness = ctypes.c_int()
ctypes.OleDLL('shcore').GetProcessDpiAwareness(
None, ctypes.byref(awareness))
self.assertNotEqual(awareness.value, PROCESS_DPI_UNAWARE)


class WheelTest(unittest.TestCase):
"Test the wheel functions with a widget on this display."

@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tkinter.Tk()
cls.root.withdraw()

@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root

def setUp(self):
self.text = tkinter.Text(self.root)
self.addCleanup(self.text.destroy)

def test_x11_buttons(self):
# Only X11 before Tk 8.7 sends the wheel as button events.
text = self.text
if text._windowingsystem == 'x11' and tkinter.TkVersion < 8.7:
self.assertTrue(util.x11_buttons(text))
else:
self.assertFalse(util.x11_buttons(text))

def test_bind_wheel(self):
# The events Tk sends here are the ones bound.
text = self.text
util.bind_wheel(text, util.wheel_event)
if util.x11_buttons(text):
self.assertEqual(sorted(text.bind()),
['<Button-4>', '<Button-5>'])
else:
self.assertEqual(sorted(text.bind()), ['<MouseWheel>'])


class WheelEventTest(unittest.TestCase):
"Test the direction and the amount of the scroll."

# 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='??'):
# Tk leaves num '??' for a wheel event and delta 0 for a button.
return Event(type=event_type, delta=delta, num=num,
widget=mock.Mock())

def scroll(self, event, widget=None):
"Return the arguments of the yview call."
self.assertEqual(util.wheel_event(event, widget), 'break')
scrolled = event.widget if widget is None else widget
scrolled.yview.assert_called_once()
return scrolled.yview.call_args.args

def test_mousewheel(self):
# Delta is positive for up on all systems.
for delta in 120, 1, 1200:
self.assertEqual(self.scroll(self.event(EventType.MouseWheel,
delta)),
('scroll', -5, 'units'))
self.assertEqual(self.scroll(self.event(EventType.MouseWheel,
-delta)),
('scroll', 5, 'units'))

def test_buttons(self):
self.assertEqual(self.scroll(self.event(EventType.ButtonPress, num=4)),
('scroll', -5, 'units'))
self.assertEqual(self.scroll(self.event(EventType.ButtonPress, num=5)),
('scroll', 5, 'units'))

def test_widget_argument(self):
# A tree label scrolls the canvas, not itself.
event = self.event(EventType.MouseWheel, 120)
canvas = mock.Mock()
self.assertEqual(self.scroll(event, canvas), ('scroll', -5, 'units'))
event.widget.yview.assert_not_called()


class FixTest(unittest.TestCase):
"Test the fix_ functions, which need a display."

@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tkinter.Tk()
cls.root.withdraw()

@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root

def test_fix_scaling(self):
from tkinter import font
root = self.root
self.addCleanup(root.tk_scaling, root.tk_scaling())
# Both fonts go with the root; Font.delete_font is a flag.
pixels = font.Font(root=root, name='TestPixelFont', size=-16)
points = font.Font(root=root, name='TestPointFont', size=12)

root.tk_scaling(1.0)
util.fix_scaling(root) # No scaling, no change.
self.assertEqual(int(pixels['size']), -16)

root.tk_scaling(2.0)
util.fix_scaling(root) # A size in pixels becomes one in points.
self.assertEqual(int(pixels['size']), 12) # round(-0.75 * -16)
self.assertEqual(int(points['size']), 12) # Points are left alone.

def test_fix_word_breaks(self):
root = self.root
util.fix_word_breaks(root)
self.assertEqual(root.tk.call('set', 'tcl_wordchars'), r'\w')
self.assertEqual(root.tk.call('set', 'tcl_nonwordchars'), r'\W')

def test_fix_x11_paste(self):
root = self.root
classes = 'Text', 'Entry', 'Spinbox'
before = {cls: root.bind_class(cls, '<<Paste>>') for cls in classes}
util.fix_x11_paste(root)
for cls in classes:
with self.subTest(cls=cls):
after = root.bind_class(cls, '<<Paste>>')
if root._windowingsystem == 'x11':
# Deleting the selection makes paste replace it.
self.assertEqual(
after,
'catch {%W delete sel.first sel.last}\n' + before[cls])
else:
self.assertEqual(after, before[cls])


if __name__ == '__main__':
unittest.main(verbosity=2)
35 changes: 3 additions & 32 deletions Lib/idlelib/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from tkinter.ttk import Frame, Scrollbar

from idlelib.config import idleConf
from idlelib.util import bind_wheel, wheel_event
from idlelib import zoomheight

ICONDIR = "Icons"
Expand Down Expand Up @@ -56,30 +57,6 @@ 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:

Expand Down Expand Up @@ -285,10 +262,7 @@ def drawtext(self):
anchor="nw", window=self.label)
self.label.bind("<1>", self.select_or_edit)
self.label.bind("<Double-1>", self.flip)
self.label.bind("<MouseWheel>", lambda e: wheel_event(e, self.canvas))
if self.label._windowingsystem == 'x11':
self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas))
self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas))
bind_wheel(self.label, lambda e: wheel_event(e, self.canvas))
self.text_id = id
if TreeNode.dy == 0:
# The first row doesn't matter what the dy is, just measure its
Expand Down Expand Up @@ -466,10 +440,7 @@ def __init__(self, master, **opts):
self.canvas.bind("<Key-Next>", self.page_down)
self.canvas.bind("<Key-Up>", self.unit_up)
self.canvas.bind("<Key-Down>", self.unit_down)
self.canvas.bind("<MouseWheel>", wheel_event)
if self.canvas._windowingsystem == 'x11':
self.canvas.bind("<Button-4>", wheel_event)
self.canvas.bind("<Button-5>", wheel_event)
bind_wheel(self.canvas, wheel_event)
#if isinstance(master, Toplevel) or isinstance(master, Tk):
self.canvas.bind("<Alt-Key-2>", self.zoom_height)
self.canvas.focus_set()
Expand Down
Loading
Loading