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 7373c2a

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 6b2d3f2 commit 7373c2a
Copy full SHA for 7373c2a

File tree

Expand file treeCollapse file tree

15 files changed

+407
-314
lines changed
Filter options
Expand file treeCollapse file tree

15 files changed

+407
-314
lines changed
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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)``.

‎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
@@ -497,6 +497,7 @@ def pick(self, mouseevent):
497497
--------
498498
set_picker, get_picker, pickable
499499
"""
500+
from .backend_bases import PickEvent # Circular import.
500501
# Pick self
501502
if self.pickable():
502503
picker = self.get_picker()
@@ -505,7 +506,8 @@ def pick(self, mouseevent):
505506
else:
506507
inside, prop = self.contains(mouseevent)
507508
if inside:
508-
self.figure.canvas.pick_event(mouseevent, self, **prop)
509+
PickEvent("pick_event", self.figure.canvas,
510+
mouseevent, self, **prop)._process()
509511

510512
# Pick children
511513
for a in self.get_children():

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+87-25Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,11 +1221,16 @@ class Event:
12211221
guiEvent
12221222
The GUI event that triggered the Matplotlib event.
12231223
"""
1224+
12241225
def __init__(self, name, canvas, guiEvent=None):
12251226
self.name = name
12261227
self.canvas = canvas
12271228
self.guiEvent = guiEvent
12281229

1230+
def _process(self):
1231+
"""Generate an event with name ``self.name`` on ``self.canvas``."""
1232+
self.canvas.callbacks.process(self.name, self)
1233+
12291234

12301235
class DrawEvent(Event):
12311236
"""
@@ -1268,14 +1273,28 @@ class ResizeEvent(Event):
12681273
height : int
12691274
Height of the canvas in pixels.
12701275
"""
1276+
12711277
def __init__(self, name, canvas):
12721278
super().__init__(name, canvas)
12731279
self.width, self.height = canvas.get_width_height()
12741280

1281+
def _process(self):
1282+
super()._process()
1283+
self.canvas.draw_idle()
1284+
12751285

12761286
class CloseEvent(Event):
12771287
"""An event triggered by a figure being closed."""
12781288

1289+
def _process(self):
1290+
try:
1291+
super()._process()
1292+
except (AttributeError, TypeError):
1293+
pass
1294+
# Suppress AttributeError/TypeError that occur when the python
1295+
# session is being killed. It may be that a better solution would
1296+
# be a mechanism to disconnect all callbacks upon shutdown.
1297+
12791298

12801299
class LocationEvent(Event):
12811300
"""
@@ -1295,7 +1314,7 @@ class LocationEvent(Event):
12951314
is not over an Axes.
12961315
"""
12971316

1298-
lastevent = None # the last event that was triggered before this one
1317+
lastevent = None # The last event processed so far.
12991318

13001319
def __init__(self, name, canvas, x, y, guiEvent=None):
13011320
super().__init__(name, canvas, guiEvent=guiEvent)
@@ -1309,7 +1328,6 @@ def __init__(self, name, canvas, x, y, guiEvent=None):
13091328

13101329
if x is None or y is None:
13111330
# cannot check if event was in Axes if no (x, y) info
1312-
self._update_enter_leave()
13131331
return
13141332

13151333
if self.canvas.mouse_grabber is None:
@@ -1327,33 +1345,21 @@ def __init__(self, name, canvas, x, y, guiEvent=None):
13271345
self.xdata = xdata
13281346
self.ydata = ydata
13291347

1330-
self._update_enter_leave()
1331-
1332-
def _update_enter_leave(self):
1333-
"""Process the figure/axes enter leave events."""
1334-
if LocationEvent.lastevent is not None:
1335-
last = LocationEvent.lastevent
1336-
if last.inaxes != self.inaxes:
1337-
# process Axes enter/leave events
1348+
def _process(self):
1349+
last = LocationEvent.lastevent
1350+
last_axes = last.inaxes if last is not None else None
1351+
if last_axes != self.inaxes:
1352+
if last_axes is not None:
13381353
try:
1339-
if last.inaxes is not None:
1340-
last.canvas.callbacks.process('axes_leave_event', last)
1354+
last.canvas.callbacks.process("axes_leave_event", last)
13411355
except Exception:
1356+
# The last canvas may already have been torn down.
13421357
pass
1343-
# See ticket 2901582.
1344-
# I think this is a valid exception to the rule
1345-
# against catching all exceptions; if anything goes
1346-
# wrong, we simply want to move on and process the
1347-
# current event.
1348-
if self.inaxes is not None:
1349-
self.canvas.callbacks.process('axes_enter_event', self)
1350-
1351-
else:
1352-
# process a figure enter event
13531358
if self.inaxes is not None:
1354-
self.canvas.callbacks.process('axes_enter_event', self)
1355-
1356-
LocationEvent.lastevent = self
1359+
self.canvas.callbacks.process("axes_enter_event", self)
1360+
LocationEvent.lastevent = (
1361+
None if self.name == "figure_leave_event" else self)
1362+
super()._process()
13571363

13581364

13591365
class MouseButton(IntEnum):
@@ -1376,11 +1382,16 @@ class MouseEvent(LocationEvent):
13761382
----------
13771383
button : None or `MouseButton` or {'up', 'down'}
13781384
The button pressed. 'up' and 'down' are used for scroll events.
1385+
13791386
Note that LEFT and RIGHT actually refer to the "primary" and
13801387
"secondary" buttons, i.e. if the user inverts their left and right
13811388
buttons ("left-handed setting") then the LEFT button will be the one
13821389
physically on the right.
13831390
1391+
If this is unset, *name* is "scroll_event", and and *step* is nonzero,
1392+
then this will be set to "up" or "down" depending on the sign of
1393+
*step*.
1394+
13841395
key : None or str
13851396
The key pressed when the mouse event triggered, e.g. 'shift'.
13861397
See `KeyEvent`.
@@ -1414,6 +1425,11 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
14141425
step=0, dblclick=False, guiEvent=None):
14151426
if button in MouseButton.__members__.values():
14161427
button = MouseButton(button)
1428+
if name == "scroll_event" and button is None:
1429+
if step > 0:
1430+
button = "up"
1431+
elif step < 0:
1432+
button = "down"
14171433
self.button = button
14181434
self.key = key
14191435
self.step = step
@@ -1423,6 +1439,17 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
14231439
# 'axes_enter_event', which requires a fully initialized event.
14241440
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
14251441

1442+
def _process(self):
1443+
if self.name == "button_press_event":
1444+
self.canvas._button = self.button
1445+
elif self.name == "button_release_event":
1446+
self.canvas._button = None
1447+
if self.button is None and self.name != "scroll_event":
1448+
self.button = self.canvas._button
1449+
if self.key is None:
1450+
self.key = self.canvas._key
1451+
super()._process()
1452+
14261453
def __str__(self):
14271454
return (f"{self.name}: "
14281455
f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
@@ -1468,8 +1495,11 @@ def on_pick(event):
14681495
14691496
cid = fig.canvas.mpl_connect('pick_event', on_pick)
14701497
"""
1498+
14711499
def __init__(self, name, canvas, mouseevent, artist,
14721500
guiEvent=None, **kwargs):
1501+
if guiEvent is None:
1502+
guiEvent = mouseevent.guiEvent
14731503
super().__init__(name, canvas, guiEvent)
14741504
self.mouseevent = mouseevent
14751505
self.artist = artist
@@ -1507,11 +1537,19 @@ def on_key(event):
15071537
15081538
cid = fig.canvas.mpl_connect('key_press_event', on_key)
15091539
"""
1540+
15101541
def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
15111542
self.key = key
15121543
# super-init deferred to the end: callback errors if called before
15131544
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
15141545

1546+
def _process(self):
1547+
if self.name == "key_press_event":
1548+
self.canvas._key = self.key
1549+
elif self.name == "key_release_event":
1550+
self.canvas._key = None
1551+
super()._process()
1552+
15151553

15161554
def _get_renderer(figure, print_method=None):
15171555
"""
@@ -1693,12 +1731,16 @@ def resize(self, w, h):
16931731
not a requirement of, nor is it used by, Matplotlib itself.
16941732
"""
16951733

1734+
@_api.deprecated("3.6", alternative=(
1735+
"callbacks.process('draw_event', DrawEvent(...))"))
16961736
def draw_event(self, renderer):
16971737
"""Pass a `DrawEvent` to all functions connected to ``draw_event``."""
16981738
s = 'draw_event'
16991739
event = DrawEvent(s, self, renderer)
17001740
self.callbacks.process(s, event)
17011741

1742+
@_api.deprecated("3.6", alternative=(
1743+
"callbacks.process('resize_event', ResizeEvent(...))"))
17021744
def resize_event(self):
17031745
"""
17041746
Pass a `ResizeEvent` to all functions connected to ``resize_event``.
@@ -1708,6 +1750,8 @@ def resize_event(self):
17081750
self.callbacks.process(s, event)
17091751
self.draw_idle()
17101752

1753+
@_api.deprecated("3.6", alternative=(
1754+
"callbacks.process('close_event', CloseEvent(...))"))
17111755
def close_event(self, guiEvent=None):
17121756
"""
17131757
Pass a `CloseEvent` to all functions connected to ``close_event``.
@@ -1724,6 +1768,8 @@ def close_event(self, guiEvent=None):
17241768
# AttributeError occurs on OSX with qt4agg upon exiting
17251769
# with an open window; 'callbacks' attribute no longer exists.
17261770

1771+
@_api.deprecated("3.6", alternative=(
1772+
"callbacks.process('key_press_event', KeyEvent(...))"))
17271773
def key_press_event(self, key, guiEvent=None):
17281774
"""
17291775
Pass a `KeyEvent` to all functions connected to ``key_press_event``.
@@ -1734,6 +1780,8 @@ def key_press_event(self, key, guiEvent=None):
17341780
s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
17351781
self.callbacks.process(s, event)
17361782

1783+
@_api.deprecated("3.6", alternative=(
1784+
"callbacks.process('key_release_event', KeyEvent(...))"))
17371785
def key_release_event(self, key, guiEvent=None):
17381786
"""
17391787
Pass a `KeyEvent` to all functions connected to ``key_release_event``.
@@ -1744,6 +1792,8 @@ def key_release_event(self, key, guiEvent=None):
17441792
self.callbacks.process(s, event)
17451793
self._key = None
17461794

1795+
@_api.deprecated("3.6", alternative=(
1796+
"callbacks.process('pick_event', PickEvent(...))"))
17471797
def pick_event(self, mouseevent, artist, **kwargs):
17481798
"""
17491799
Callback processing for pick events.
@@ -1760,6 +1810,8 @@ def pick_event(self, mouseevent, artist, **kwargs):
17601810
**kwargs)
17611811
self.callbacks.process(s, event)
17621812

1813+
@_api.deprecated("3.6", alternative=(
1814+
"callbacks.process('scroll_event', MouseEvent(...))"))
17631815
def scroll_event(self, x, y, step, guiEvent=None):
17641816
"""
17651817
Callback processing for scroll events.
@@ -1780,6 +1832,8 @@ def scroll_event(self, x, y, step, guiEvent=None):
17801832
step=step, guiEvent=guiEvent)
17811833
self.callbacks.process(s, mouseevent)
17821834

1835+
@_api.deprecated("3.6", alternative=(
1836+
"callbacks.process('button_press_event', MouseEvent(...))"))
17831837
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
17841838
"""
17851839
Callback processing for mouse button press events.
@@ -1797,6 +1851,8 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
17971851
dblclick=dblclick, guiEvent=guiEvent)
17981852
self.callbacks.process(s, mouseevent)
17991853

1854+
@_api.deprecated("3.6", alternative=(
1855+
"callbacks.process('button_release_event', MouseEvent(...))"))
18001856
def button_release_event(self, x, y, button, guiEvent=None):
18011857
"""
18021858
Callback processing for mouse button release events.
@@ -1821,6 +1877,8 @@ def button_release_event(self, x, y, button, guiEvent=None):
18211877
self.callbacks.process(s, event)
18221878
self._button = None
18231879

1880+
@_api.deprecated("3.6", alternative=(
1881+
"callbacks.process('motion_notify_event', MouseEvent(...))"))
18241882
def motion_notify_event(self, x, y, guiEvent=None):
18251883
"""
18261884
Callback processing for mouse movement events.
@@ -1846,6 +1904,8 @@ def motion_notify_event(self, x, y, guiEvent=None):
18461904
guiEvent=guiEvent)
18471905
self.callbacks.process(s, event)
18481906

1907+
@_api.deprecated("3.6", alternative=(
1908+
"callbacks.process('leave_notify_event', LocationEvent(...))"))
18491909
def leave_notify_event(self, guiEvent=None):
18501910
"""
18511911
Callback processing for the mouse cursor leaving the canvas.
@@ -1862,6 +1922,8 @@ def leave_notify_event(self, guiEvent=None):
18621922
LocationEvent.lastevent = None
18631923
self._lastx, self._lasty = None, None
18641924

1925+
@_api.deprecated("3.6", alternative=(
1926+
"callbacks.process('enter_notify_event', LocationEvent(...))"))
18651927
def enter_notify_event(self, guiEvent=None, xy=None):
18661928
"""
18671929
Callback processing for the mouse cursor entering the canvas.

0 commit comments

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