Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b5763fa

Browse files
committed
Make it easier to improve UI event metadata.
Currently, UI events (MouseEvent, KeyEvent, etc.) are generated by letting the GUI-specific backends massage the native event objects into a list of args/kwargs and then call `FigureCanvasBase.motion_notify_event`/`.key_press_event`/etc. This makes it a bit tricky to improve the metadata on the events, because one needs to change the signature on both the `FigureCanvasBase` method and the event class. Moreover, the `motion_notify_event`/etc. methods are directly bound as event handlers in the gtk3 and tk backends, and thus have incompatible signatures there. Instead, the native GUI handlers can directly construct the relevant event objects and trigger the events themselves; a new `Event.process` helper method makes this even shorter (and allows to keep factoring some common functionality e.g. for tracking the last pressed button or key). As an example, this PR also updates figure_leave_event to always correctly set the event location based on the *current* cursor position, instead of the last triggered location event (which may be outdated); this can now easily be done on a backend-by-backend basis, instead of coordinating the change with FigureCanvasBase.figure_leave_event. This also exposed another (minor) issue, in that resize events often trigger *two* calls to draw_idle -- one in the GUI-specific handler, and one in FigureCanvasBase.draw_idle (now moved to ResizeEvent.process, but should perhaps instead be a callback autoconnected to "resize_event") -- could probably be fixed later.
1 parent e8547ae commit b5763fa

File tree

11 files changed

+243
-252
lines changed

11 files changed

+243
-252
lines changed

doc/api/next_api_changes/deprecations.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,16 @@ The qt4agg and qt4cairo backends are deprecated.
402402
``RendererWx.get_gc``
403403
~~~~~~~~~~~~~~~~~~~~~
404404
This method is deprecated. Access the ``gc`` attribute directly instead.
405+
406+
Event handlers
407+
~~~~~~~~~~~~~~
408+
The ``draw_event``, ``resize_event``, ``close_event``, ``key_press_event``,
409+
``key_release_event``, ``pick_event``, ``scroll_event``,
410+
``button_press_event``, ``button_release_event``, ``motion_notify_event``,
411+
``enter_notify_event`` and ``leave_notify_event`` methods of `.FigureCanvasBase`
412+
are deprecated. They had inconsistent signatures across backends, and made it
413+
difficult to improve event metadata.
414+
415+
In order to trigger an event on a canvas, directly construct an `.Event` object
416+
of the correct class and call ``canvas.callbacks.process(event.name, event)``,
417+
or use the new helper method `.Event.process`.

lib/matplotlib/artist.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ def pick(self, mouseevent):
488488
--------
489489
set_picker, get_picker, pickable
490490
"""
491+
from .backend_bases import PickEvent # Circular import.
491492
# Pick self
492493
if self.pickable():
493494
picker = self.get_picker()
@@ -496,7 +497,8 @@ def pick(self, mouseevent):
496497
else:
497498
inside, prop = self.contains(mouseevent)
498499
if inside:
499-
self.figure.canvas.pick_event(mouseevent, self, **prop)
500+
PickEvent("pick_event", self.figure.canvas,
501+
mouseevent, self, **prop).process()
500502

501503
# Pick children
502504
for a in self.get_children():

lib/matplotlib/backend_bases.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,11 +1191,16 @@ class Event:
11911191
guiEvent
11921192
The GUI event that triggered the Matplotlib event.
11931193
"""
1194+
11941195
def __init__(self, name, canvas, guiEvent=None):
11951196
self.name = name
11961197
self.canvas = canvas
11971198
self.guiEvent = guiEvent
11981199

1200+
def process(self):
1201+
"""Generate an event with name ``self.name`` on ``self.canvas``."""
1202+
self.canvas.callbacks.process(self.name, self)
1203+
11991204

12001205
class DrawEvent(Event):
12011206
"""
@@ -1238,14 +1243,28 @@ class ResizeEvent(Event):
12381243
height : int
12391244
Height of the canvas in pixels.
12401245
"""
1246+
12411247
def __init__(self, name, canvas):
12421248
Event.__init__(self, name, canvas)
12431249
self.width, self.height = canvas.get_width_height()
12441250

1251+
def process(self):
1252+
super().process()
1253+
self.canvas.draw_idle()
1254+
12451255

12461256
class CloseEvent(Event):
12471257
"""An event triggered by a figure being closed."""
12481258

1259+
def process(self):
1260+
try:
1261+
super().process()
1262+
except (AttributeError, TypeError):
1263+
pass
1264+
# Suppress AttributeError/TypeError that occur when the python
1265+
# session is being killed. It may be that a better solution would
1266+
# be a mechanism to disconnect all callbacks upon shutdown.
1267+
12491268

12501269
class LocationEvent(Event):
12511270
"""
@@ -1360,11 +1379,16 @@ class MouseEvent(LocationEvent):
13601379
Note that in the nbagg backend, both the middle and right clicks
13611380
return RIGHT since right clicking will bring up the context menu in
13621381
some browsers.
1382+
13631383
Note that LEFT and RIGHT actually refer to the "primary" and
13641384
"secondary" buttons, i.e. if the user inverts their left and right
13651385
buttons ("left-handed setting") then the LEFT button will be the one
13661386
physically on the right.
13671387
1388+
If this is unset, *name* is "scroll_event", and and *step* is nonzero,
1389+
then this will be set to "up" or "down" depending on the sign of
1390+
*step*.
1391+
13681392
key : None or str
13691393
The key pressed when the mouse event triggered, e.g. 'shift'.
13701394
See `KeyEvent`.
@@ -1403,11 +1427,27 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
14031427
LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
14041428
if button in MouseButton.__members__.values():
14051429
button = MouseButton(button)
1430+
if name == "scroll_event" and button is None:
1431+
if step > 0:
1432+
button = "up"
1433+
elif step < 0:
1434+
button = "down"
14061435
self.button = button
14071436
self.key = key
14081437
self.step = step
14091438
self.dblclick = dblclick
14101439

1440+
def process(self):
1441+
if self.name == "button_press_event":
1442+
self.canvas._button = self.button
1443+
elif self.name == "button_release_event":
1444+
self.canvas._button = None
1445+
if self.button is None and self.name != "scroll_event":
1446+
self.button = self.canvas._button
1447+
if self.key is None:
1448+
self.key = self.canvas._key
1449+
super().process()
1450+
14111451
def __str__(self):
14121452
return (f"{self.name}: "
14131453
f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
@@ -1448,8 +1488,11 @@ def on_pick(event):
14481488
14491489
cid = fig.canvas.mpl_connect('pick_event', on_pick)
14501490
"""
1491+
14511492
def __init__(self, name, canvas, mouseevent, artist,
14521493
guiEvent=None, **kwargs):
1494+
if guiEvent is None:
1495+
guiEvent = mouseevent.guiEvent
14531496
Event.__init__(self, name, canvas, guiEvent)
14541497
self.mouseevent = mouseevent
14551498
self.artist = artist
@@ -1490,10 +1533,18 @@ def on_key(event):
14901533
14911534
cid = fig.canvas.mpl_connect('key_press_event', on_key)
14921535
"""
1536+
14931537
def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
14941538
LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
14951539
self.key = key
14961540

1541+
def process(self):
1542+
if self.name == "key_press_event":
1543+
self.canvas._key = self.key
1544+
elif self.name == "key_release_event":
1545+
self.canvas._key = None
1546+
super().process()
1547+
14971548

14981549
def _get_renderer(figure, print_method, *, draw_disabled=False):
14991550
"""
@@ -1651,12 +1702,14 @@ def blit(self, bbox=None):
16511702
def resize(self, w, h):
16521703
"""Set the canvas size in pixels."""
16531704

1705+
@cbook.deprecated("3.3", alternative="DrawEvent(...).process()")
16541706
def draw_event(self, renderer):
16551707
"""Pass a `DrawEvent` to all functions connected to ``draw_event``."""
16561708
s = 'draw_event'
16571709
event = DrawEvent(s, self, renderer)
16581710
self.callbacks.process(s, event)
16591711

1712+
@cbook.deprecated("3.3", alternative="ResizeEvent(...).process()")
16601713
def resize_event(self):
16611714
"""
16621715
Pass a `ResizeEvent` to all functions connected to ``resize_event``.
@@ -1666,6 +1719,7 @@ def resize_event(self):
16661719
self.callbacks.process(s, event)
16671720
self.draw_idle()
16681721

1722+
@cbook.deprecated("3.3", alternative="CloseEvent(...).process()")
16691723
def close_event(self, guiEvent=None):
16701724
"""
16711725
Pass a `CloseEvent` to all functions connected to ``close_event``.
@@ -1682,6 +1736,7 @@ def close_event(self, guiEvent=None):
16821736
# AttributeError occurs on OSX with qt4agg upon exiting
16831737
# with an open window; 'callbacks' attribute no longer exists.
16841738

1739+
@cbook.deprecated("3.3", alternative="KeyEvent(...).process()")
16851740
def key_press_event(self, key, guiEvent=None):
16861741
"""
16871742
Pass a `KeyEvent` to all functions connected to ``key_press_event``.
@@ -1692,6 +1747,7 @@ def key_press_event(self, key, guiEvent=None):
16921747
s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
16931748
self.callbacks.process(s, event)
16941749

1750+
@cbook.deprecated("3.3", alternative="KeyEvent(...).process()")
16951751
def key_release_event(self, key, guiEvent=None):
16961752
"""
16971753
Pass a `KeyEvent` to all functions connected to ``key_release_event``.
@@ -1702,6 +1758,7 @@ def key_release_event(self, key, guiEvent=None):
17021758
self.callbacks.process(s, event)
17031759
self._key = None
17041760

1761+
@cbook.deprecated("3.3", alternative="PickEvent(...).process()")
17051762
def pick_event(self, mouseevent, artist, **kwargs):
17061763
"""
17071764
Callback processing for pick events.
@@ -1715,6 +1772,7 @@ def pick_event(self, mouseevent, artist, **kwargs):
17151772
**kwargs)
17161773
self.callbacks.process(s, event)
17171774

1775+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
17181776
def scroll_event(self, x, y, step, guiEvent=None):
17191777
"""
17201778
Callback processing for scroll events.
@@ -1735,6 +1793,7 @@ def scroll_event(self, x, y, step, guiEvent=None):
17351793
step=step, guiEvent=guiEvent)
17361794
self.callbacks.process(s, mouseevent)
17371795

1796+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
17381797
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
17391798
"""
17401799
Callback processing for mouse button press events.
@@ -1752,6 +1811,7 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
17521811
dblclick=dblclick, guiEvent=guiEvent)
17531812
self.callbacks.process(s, mouseevent)
17541813

1814+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
17551815
def button_release_event(self, x, y, button, guiEvent=None):
17561816
"""
17571817
Callback processing for mouse button release events.
@@ -1776,6 +1836,7 @@ def button_release_event(self, x, y, button, guiEvent=None):
17761836
self.callbacks.process(s, event)
17771837
self._button = None
17781838

1839+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
17791840
def motion_notify_event(self, x, y, guiEvent=None):
17801841
"""
17811842
Callback processing for mouse movement events.
@@ -1801,6 +1862,7 @@ def motion_notify_event(self, x, y, guiEvent=None):
18011862
guiEvent=guiEvent)
18021863
self.callbacks.process(s, event)
18031864

1865+
@cbook.deprecated("3.3", alternative="LocationEvent(...).process()")
18041866
def leave_notify_event(self, guiEvent=None):
18051867
"""
18061868
Callback processing for the mouse cursor leaving the canvas.
@@ -1817,6 +1879,7 @@ def leave_notify_event(self, guiEvent=None):
18171879
LocationEvent.lastevent = None
18181880
self._lastx, self._lasty = None, None
18191881

1882+
@cbook.deprecated("3.3", alternative="LocationEvent(...).process()")
18201883
def enter_notify_event(self, guiEvent=None, xy=None):
18211884
"""
18221885
Callback processing for the mouse cursor entering the canvas.

lib/matplotlib/backends/_backend_tk.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from matplotlib import backend_tools, cbook
1515
from matplotlib.backend_bases import (
1616
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
17-
StatusbarBase, TimerBase, ToolContainerBase, cursors)
17+
StatusbarBase, TimerBase, ToolContainerBase, cursors,
18+
CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
1819
from matplotlib.backend_managers import ToolManager
1920
from matplotlib._pylab_helpers import Gcf
2021
from matplotlib.figure import Figure
@@ -219,7 +220,7 @@ def __init__(self, figure, master=None, resize_callback=None):
219220
def filter_destroy(event):
220221
if event.widget is self._tkcanvas:
221222
self._master.update_idletasks()
222-
self.close_event()
223+
CloseEvent("close_event", self).process()
223224
root.bind("<Destroy>", filter_destroy, "+")
224225

225226
self._master = master
@@ -241,7 +242,7 @@ def resize(self, event):
241242
master=self._tkcanvas, width=int(width), height=int(height))
242243
self._tkcanvas.create_image(
243244
int(width / 2), int(height / 2), image=self._tkphoto)
244-
self.resize_event()
245+
ResizeEvent("resize_event", self).process()
245246
self.draw()
246247

247248
def draw_idle(self):
@@ -270,13 +271,21 @@ def motion_notify_event(self, event):
270271
x = event.x
271272
# flipy so y=0 is bottom of canvas
272273
y = self.figure.bbox.height - event.y
273-
FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)
274+
MouseEvent("motion_notify_event", self, x, y, guiEvent=event).process()
274275

275276
def enter_notify_event(self, event):
276277
x = event.x
277278
# flipy so y=0 is bottom of canvas
278279
y = self.figure.bbox.height - event.y
279-
FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y))
280+
LocationEvent("figure_enter_event", self,
281+
x, y, guiEvent=event).process()
282+
283+
def leave_notify_event(self, event):
284+
x = event.x
285+
# flipy so y=0 is bottom of canvas
286+
y = self.figure.bbox.height - event.y
287+
LocationEvent("figure_leave_event", self,
288+
x, y, guiEvent=event).process()
280289

281290
def button_press_event(self, event, dblclick=False):
282291
x = event.x
@@ -291,8 +300,8 @@ def button_press_event(self, event, dblclick=False):
291300
elif num == 3:
292301
num = 2
293302

294-
FigureCanvasBase.button_press_event(
295-
self, x, y, num, dblclick=dblclick, guiEvent=event)
303+
MouseEvent("button_press_event", self,
304+
x, y, num, dblclick=dblclick, guiEvent=event).process()
296305

297306
def button_dblclick_event(self, event):
298307
self.button_press_event(event, dblclick=True)
@@ -311,25 +320,29 @@ def button_release_event(self, event):
311320
elif num == 3:
312321
num = 2
313322

314-
FigureCanvasBase.button_release_event(self, x, y, num, guiEvent=event)
323+
MouseEvent("button_release_event", self,
324+
x, y, num, guiEvent=event).process()
315325

316326
def scroll_event(self, event):
317327
x = event.x
318328
y = self.figure.bbox.height - event.y
319329
num = getattr(event, 'num', None)
320330
step = 1 if num == 4 else -1 if num == 5 else 0
321-
FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
331+
MouseEvent("scroll_event", self,
332+
x, y, step=step, guiEvent=event).process()
322333

323334
def scroll_event_windows(self, event):
324335
"""MouseWheel event processor"""
325336
# need to find the window that contains the mouse
326337
w = event.widget.winfo_containing(event.x_root, event.y_root)
327-
if w == self._tkcanvas:
328-
x = event.x_root - w.winfo_rootx()
329-
y = event.y_root - w.winfo_rooty()
330-
y = self.figure.bbox.height - y
331-
step = event.delta/120.
332-
FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
338+
if w != self._tkcanvas:
339+
return
340+
x = event.x_root - w.winfo_rootx()
341+
y = event.y_root - w.winfo_rooty()
342+
y = self.figure.bbox.height - y
343+
step = event.delta / 120
344+
MouseEvent("scroll_event", self,
345+
x, y, step=step, guiEvent=event).process()
333346

334347
def _get_key(self, event):
335348
val = event.keysym_num
@@ -376,11 +389,11 @@ def _get_key(self, event):
376389

377390
def key_press(self, event):
378391
key = self._get_key(event)
379-
FigureCanvasBase.key_press_event(self, key, guiEvent=event)
392+
KeyEvent("key_press_event", self, key, guiEvent=event).process()
380393

381394
def key_release(self, event):
382395
key = self._get_key(event)
383-
FigureCanvasBase.key_release_event(self, key, guiEvent=event)
396+
KeyEvent("key_release_event", self, key, guiEvent=event).process()
384397

385398
def new_timer(self, *args, **kwargs):
386399
# docstring inherited

0 commit comments

Comments
 (0)