Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0d05540

Browse filesBrowse 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 509eb02 commit 0d05540
Copy full SHA for 0d05540

File tree

Expand file treeCollapse file tree

12 files changed

+243
-256
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+243
-256
lines changed
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Event handlers
2+
~~~~~~~~~~~~~~
3+
The ``draw_event``, ``resize_event``, ``close_event``, ``key_press_event``,
4+
``key_release_event``, ``pick_event``, ``scroll_event``,
5+
``button_press_event``, ``button_release_event``, ``motion_notify_event``,
6+
``enter_notify_event`` and ``leave_notify_event`` methods of `.FigureCanvasBase`
7+
are deprecated. They had inconsistent signatures across backends, and made it
8+
difficult to improve event metadata.
9+
10+
In order to trigger an event on a canvas, directly construct an `.Event` object
11+
of the correct class and call ``canvas.callbacks.process(event.name, event)``,
12+
or use the new helper method `.Event.process`.

‎lib/matplotlib/artist.py

Copy file name to clipboardExpand all lines: lib/matplotlib/artist.py
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ def pick(self, mouseevent):
516516
--------
517517
set_picker, get_picker, pickable
518518
"""
519+
from .backend_bases import PickEvent # Circular import.
519520
# Pick self
520521
if self.pickable():
521522
picker = self.get_picker()
@@ -524,7 +525,8 @@ def pick(self, mouseevent):
524525
else:
525526
inside, prop = self.contains(mouseevent)
526527
if inside:
527-
self.figure.canvas.pick_event(mouseevent, self, **prop)
528+
PickEvent("pick_event", self.figure.canvas,
529+
mouseevent, self, **prop).process()
528530

529531
# Pick children
530532
for a in self.get_children():

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+63Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,11 +1251,16 @@ class Event:
12511251
guiEvent
12521252
The GUI event that triggered the Matplotlib event.
12531253
"""
1254+
12541255
def __init__(self, name, canvas, guiEvent=None):
12551256
self.name = name
12561257
self.canvas = canvas
12571258
self.guiEvent = guiEvent
12581259

1260+
def process(self):
1261+
"""Generate an event with name ``self.name`` on ``self.canvas``."""
1262+
self.canvas.callbacks.process(self.name, self)
1263+
12591264

12601265
class DrawEvent(Event):
12611266
"""
@@ -1298,14 +1303,28 @@ class ResizeEvent(Event):
12981303
height : int
12991304
Height of the canvas in pixels.
13001305
"""
1306+
13011307
def __init__(self, name, canvas):
13021308
super().__init__(name, canvas)
13031309
self.width, self.height = canvas.get_width_height()
13041310

1311+
def process(self):
1312+
super().process()
1313+
self.canvas.draw_idle()
1314+
13051315

13061316
class CloseEvent(Event):
13071317
"""An event triggered by a figure being closed."""
13081318

1319+
def process(self):
1320+
try:
1321+
super().process()
1322+
except (AttributeError, TypeError):
1323+
pass
1324+
# Suppress AttributeError/TypeError that occur when the python
1325+
# session is being killed. It may be that a better solution would
1326+
# be a mechanism to disconnect all callbacks upon shutdown.
1327+
13091328

13101329
class LocationEvent(Event):
13111330
"""
@@ -1417,11 +1436,16 @@ class MouseEvent(LocationEvent):
14171436
----------
14181437
button : None or `MouseButton` or {'up', 'down'}
14191438
The button pressed. 'up' and 'down' are used for scroll events.
1439+
14201440
Note that LEFT and RIGHT actually refer to the "primary" and
14211441
"secondary" buttons, i.e. if the user inverts their left and right
14221442
buttons ("left-handed setting") then the LEFT button will be the one
14231443
physically on the right.
14241444
1445+
If this is unset, *name* is "scroll_event", and and *step* is nonzero,
1446+
then this will be set to "up" or "down" depending on the sign of
1447+
*step*.
1448+
14251449
key : None or str
14261450
The key pressed when the mouse event triggered, e.g. 'shift'.
14271451
See `KeyEvent`.
@@ -1459,6 +1483,11 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
14591483
"""
14601484
if button in MouseButton.__members__.values():
14611485
button = MouseButton(button)
1486+
if name == "scroll_event" and button is None:
1487+
if step > 0:
1488+
button = "up"
1489+
elif step < 0:
1490+
button = "down"
14621491
self.button = button
14631492
self.key = key
14641493
self.step = step
@@ -1468,6 +1497,17 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
14681497
# 'axes_enter_event', which requires a fully initialized event.
14691498
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
14701499

1500+
def process(self):
1501+
if self.name == "button_press_event":
1502+
self.canvas._button = self.button
1503+
elif self.name == "button_release_event":
1504+
self.canvas._button = None
1505+
if self.button is None and self.name != "scroll_event":
1506+
self.button = self.canvas._button
1507+
if self.key is None:
1508+
self.key = self.canvas._key
1509+
super().process()
1510+
14711511
def __str__(self):
14721512
return (f"{self.name}: "
14731513
f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
@@ -1508,8 +1548,11 @@ def on_pick(event):
15081548
15091549
cid = fig.canvas.mpl_connect('pick_event', on_pick)
15101550
"""
1551+
15111552
def __init__(self, name, canvas, mouseevent, artist,
15121553
guiEvent=None, **kwargs):
1554+
if guiEvent is None:
1555+
guiEvent = mouseevent.guiEvent
15131556
super().__init__(name, canvas, guiEvent)
15141557
self.mouseevent = mouseevent
15151558
self.artist = artist
@@ -1550,11 +1593,19 @@ def on_key(event):
15501593
15511594
cid = fig.canvas.mpl_connect('key_press_event', on_key)
15521595
"""
1596+
15531597
def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
15541598
self.key = key
15551599
# super-init deferred to the end: callback errors if called before
15561600
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
15571601

1602+
def process(self):
1603+
if self.name == "key_press_event":
1604+
self.canvas._key = self.key
1605+
elif self.name == "key_release_event":
1606+
self.canvas._key = None
1607+
super().process()
1608+
15581609

15591610
def _get_renderer(figure, print_method=None):
15601611
"""
@@ -1781,12 +1832,14 @@ def blit(self, bbox=None):
17811832
def resize(self, w, h):
17821833
"""Set the canvas size in pixels."""
17831834

1835+
@cbook.deprecated("3.3", alternative="DrawEvent(...).process()")
17841836
def draw_event(self, renderer):
17851837
"""Pass a `DrawEvent` to all functions connected to ``draw_event``."""
17861838
s = 'draw_event'
17871839
event = DrawEvent(s, self, renderer)
17881840
self.callbacks.process(s, event)
17891841

1842+
@cbook.deprecated("3.3", alternative="ResizeEvent(...).process()")
17901843
def resize_event(self):
17911844
"""
17921845
Pass a `ResizeEvent` to all functions connected to ``resize_event``.
@@ -1796,6 +1849,7 @@ def resize_event(self):
17961849
self.callbacks.process(s, event)
17971850
self.draw_idle()
17981851

1852+
@cbook.deprecated("3.3", alternative="CloseEvent(...).process()")
17991853
def close_event(self, guiEvent=None):
18001854
"""
18011855
Pass a `CloseEvent` to all functions connected to ``close_event``.
@@ -1812,6 +1866,7 @@ def close_event(self, guiEvent=None):
18121866
# AttributeError occurs on OSX with qt4agg upon exiting
18131867
# with an open window; 'callbacks' attribute no longer exists.
18141868

1869+
@cbook.deprecated("3.3", alternative="KeyEvent(...).process()")
18151870
def key_press_event(self, key, guiEvent=None):
18161871
"""
18171872
Pass a `KeyEvent` to all functions connected to ``key_press_event``.
@@ -1822,6 +1877,7 @@ def key_press_event(self, key, guiEvent=None):
18221877
s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
18231878
self.callbacks.process(s, event)
18241879

1880+
@cbook.deprecated("3.3", alternative="KeyEvent(...).process()")
18251881
def key_release_event(self, key, guiEvent=None):
18261882
"""
18271883
Pass a `KeyEvent` to all functions connected to ``key_release_event``.
@@ -1832,6 +1888,7 @@ def key_release_event(self, key, guiEvent=None):
18321888
self.callbacks.process(s, event)
18331889
self._key = None
18341890

1891+
@cbook.deprecated("3.3", alternative="PickEvent(...).process()")
18351892
def pick_event(self, mouseevent, artist, **kwargs):
18361893
"""
18371894
Callback processing for pick events.
@@ -1845,6 +1902,7 @@ def pick_event(self, mouseevent, artist, **kwargs):
18451902
**kwargs)
18461903
self.callbacks.process(s, event)
18471904

1905+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
18481906
def scroll_event(self, x, y, step, guiEvent=None):
18491907
"""
18501908
Callback processing for scroll events.
@@ -1865,6 +1923,7 @@ def scroll_event(self, x, y, step, guiEvent=None):
18651923
step=step, guiEvent=guiEvent)
18661924
self.callbacks.process(s, mouseevent)
18671925

1926+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
18681927
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
18691928
"""
18701929
Callback processing for mouse button press events.
@@ -1882,6 +1941,7 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
18821941
dblclick=dblclick, guiEvent=guiEvent)
18831942
self.callbacks.process(s, mouseevent)
18841943

1944+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
18851945
def button_release_event(self, x, y, button, guiEvent=None):
18861946
"""
18871947
Callback processing for mouse button release events.
@@ -1906,6 +1966,7 @@ def button_release_event(self, x, y, button, guiEvent=None):
19061966
self.callbacks.process(s, event)
19071967
self._button = None
19081968

1969+
@cbook.deprecated("3.3", alternative="MouseEvent(...).process()")
19091970
def motion_notify_event(self, x, y, guiEvent=None):
19101971
"""
19111972
Callback processing for mouse movement events.
@@ -1931,6 +1992,7 @@ def motion_notify_event(self, x, y, guiEvent=None):
19311992
guiEvent=guiEvent)
19321993
self.callbacks.process(s, event)
19331994

1995+
@cbook.deprecated("3.3", alternative="LocationEvent(...).process()")
19341996
def leave_notify_event(self, guiEvent=None):
19351997
"""
19361998
Callback processing for the mouse cursor leaving the canvas.
@@ -1947,6 +2009,7 @@ def leave_notify_event(self, guiEvent=None):
19472009
LocationEvent.lastevent = None
19482010
self._lastx, self._lasty = None, None
19492011

2012+
@cbook.deprecated("3.3", alternative="LocationEvent(...).process()")
19502013
def enter_notify_event(self, guiEvent=None, xy=None):
19512014
"""
19522015
Callback processing for the mouse cursor entering the canvas.

‎lib/matplotlib/backends/_backend_tk.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/_backend_tk.py
+30-17Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from matplotlib import backend_tools, cbook, _c_internal_utils
1515
from matplotlib.backend_bases import (
1616
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
17-
StatusbarBase, TimerBase, ToolContainerBase, cursors, _Mode)
17+
StatusbarBase, TimerBase, ToolContainerBase, cursors, _Mode,
18+
CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
1819
from matplotlib._pylab_helpers import Gcf
1920
from matplotlib.figure import Figure
2021
from matplotlib.widgets import SubplotTool
@@ -154,7 +155,7 @@ def __init__(self, figure, master=None, resize_callback=None):
154155
# to the window and filter.
155156
def filter_destroy(event):
156157
if event.widget is self._tkcanvas:
157-
self.close_event()
158+
CloseEvent("close_event", self).process()
158159
root.bind("<Destroy>", filter_destroy, "+")
159160

160161
self._master = master
@@ -176,7 +177,7 @@ def resize(self, event):
176177
master=self._tkcanvas, width=int(width), height=int(height))
177178
self._tkcanvas.create_image(
178179
int(width / 2), int(height / 2), image=self._tkphoto)
179-
self.resize_event()
180+
ResizeEvent("resize_event", self).process()
180181

181182
def draw_idle(self):
182183
# docstring inherited
@@ -206,13 +207,21 @@ def motion_notify_event(self, event):
206207
x = event.x
207208
# flipy so y=0 is bottom of canvas
208209
y = self.figure.bbox.height - event.y
209-
super().motion_notify_event(x, y, guiEvent=event)
210+
MouseEvent("motion_notify_event", self, x, y, guiEvent=event).process()
210211

211212
def enter_notify_event(self, event):
212213
x = event.x
213214
# flipy so y=0 is bottom of canvas
214215
y = self.figure.bbox.height - event.y
215-
super().enter_notify_event(guiEvent=event, xy=(x, y))
216+
LocationEvent("figure_enter_event", self,
217+
x, y, guiEvent=event).process()
218+
219+
def leave_notify_event(self, event):
220+
x = event.x
221+
# flipy so y=0 is bottom of canvas
222+
y = self.figure.bbox.height - event.y
223+
LocationEvent("figure_leave_event", self,
224+
x, y, guiEvent=event).process()
216225

217226
def button_press_event(self, event, dblclick=False):
218227
x = event.x
@@ -227,8 +236,8 @@ def button_press_event(self, event, dblclick=False):
227236
elif num == 3:
228237
num = 2
229238

230-
super().button_press_event(x, y, num,
231-
dblclick=dblclick, guiEvent=event)
239+
MouseEvent("button_press_event", self,
240+
x, y, num, dblclick=dblclick, guiEvent=event).process()
232241

233242
def button_dblclick_event(self, event):
234243
self.button_press_event(event, dblclick=True)
@@ -247,25 +256,29 @@ def button_release_event(self, event):
247256
elif num == 3:
248257
num = 2
249258

250-
super().button_release_event(x, y, num, guiEvent=event)
259+
MouseEvent("button_release_event", self,
260+
x, y, num, guiEvent=event).process()
251261

252262
def scroll_event(self, event):
253263
x = event.x
254264
y = self.figure.bbox.height - event.y
255265
num = getattr(event, 'num', None)
256266
step = 1 if num == 4 else -1 if num == 5 else 0
257-
super().scroll_event(x, y, step, guiEvent=event)
267+
MouseEvent("scroll_event", self,
268+
x, y, step=step, guiEvent=event).process()
258269

259270
def scroll_event_windows(self, event):
260271
"""MouseWheel event processor"""
261272
# need to find the window that contains the mouse
262273
w = event.widget.winfo_containing(event.x_root, event.y_root)
263-
if w == self._tkcanvas:
264-
x = event.x_root - w.winfo_rootx()
265-
y = event.y_root - w.winfo_rooty()
266-
y = self.figure.bbox.height - y
267-
step = event.delta/120.
268-
FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
274+
if w != self._tkcanvas:
275+
return
276+
x = event.x_root - w.winfo_rootx()
277+
y = event.y_root - w.winfo_rooty()
278+
y = self.figure.bbox.height - y
279+
step = event.delta / 120
280+
MouseEvent("scroll_event", self,
281+
x, y, step=step, guiEvent=event).process()
269282

270283
def _get_key(self, event):
271284
key = cbook._unikey_or_keysym_to_mplkey(event.char, event.keysym)
@@ -303,11 +316,11 @@ def _get_key(self, event):
303316

304317
def key_press(self, event):
305318
key = self._get_key(event)
306-
FigureCanvasBase.key_press_event(self, key, guiEvent=event)
319+
KeyEvent("key_press_event", self, key, guiEvent=event).process()
307320

308321
def key_release(self, event):
309322
key = self._get_key(event)
310-
FigureCanvasBase.key_release_event(self, key, guiEvent=event)
323+
KeyEvent("key_release_event", self, key, guiEvent=event).process()
311324

312325
def new_timer(self, *args, **kwargs):
313326
# docstring inherited

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.