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

Skip to content

Make it easier to improve UI event metadata. #16931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 22, 2022
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
11 changes: 11 additions & 0 deletions doc/api/next_api_changes/deprecations/16931-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Event handlers
~~~~~~~~~~~~~~
The ``draw_event``, ``resize_event``, ``close_event``, ``key_press_event``,
``key_release_event``, ``pick_event``, ``scroll_event``,
``button_press_event``, ``button_release_event``, ``motion_notify_event``,
``enter_notify_event`` and ``leave_notify_event`` methods of `.FigureCanvasBase`
are deprecated. They had inconsistent signatures across backends, and made it
difficult to improve event metadata.

In order to trigger an event on a canvas, directly construct an `.Event` object
of the correct class and call ``canvas.callbacks.process(event.name, event)``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include a note about _process existing?

4 changes: 3 additions & 1 deletion lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ def pick(self, mouseevent):
--------
set_picker, get_picker, pickable
"""
from .backend_bases import PickEvent # Circular import.
# Pick self
if self.pickable():
picker = self.get_picker()
Expand All @@ -506,7 +507,8 @@ def pick(self, mouseevent):
else:
inside, prop = self.contains(mouseevent)
if inside:
self.figure.canvas.pick_event(mouseevent, self, **prop)
PickEvent("pick_event", self.figure.canvas,
mouseevent, self, **prop)._process()

# Pick children
for a in self.get_children():
Expand Down
119 changes: 83 additions & 36 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,11 +1220,16 @@ class Event:
guiEvent
The GUI event that triggered the Matplotlib event.
"""

def __init__(self, name, canvas, guiEvent=None):
self.name = name
self.canvas = canvas
self.guiEvent = guiEvent

def _process(self):
"""Generate an event with name ``self.name`` on ``self.canvas``."""
self.canvas.callbacks.process(self.name, self)


class DrawEvent(Event):
"""
Expand Down Expand Up @@ -1267,6 +1272,7 @@ class ResizeEvent(Event):
height : int
Height of the canvas in pixels.
"""

def __init__(self, name, canvas):
super().__init__(name, canvas)
self.width, self.height = canvas.get_width_height()
Expand Down Expand Up @@ -1294,7 +1300,7 @@ class LocationEvent(Event):
is not over an Axes.
"""

lastevent = None # the last event that was triggered before this one
lastevent = None # The last event processed so far.

def __init__(self, name, canvas, x, y, guiEvent=None):
super().__init__(name, canvas, guiEvent=guiEvent)
Expand All @@ -1308,7 +1314,6 @@ def __init__(self, name, canvas, x, y, guiEvent=None):

if x is None or y is None:
# cannot check if event was in Axes if no (x, y) info
self._update_enter_leave()
return

if self.canvas.mouse_grabber is None:
Expand All @@ -1326,34 +1331,6 @@ def __init__(self, name, canvas, x, y, guiEvent=None):
self.xdata = xdata
self.ydata = ydata

self._update_enter_leave()

def _update_enter_leave(self):
"""Process the figure/axes enter leave events."""
if LocationEvent.lastevent is not None:
last = LocationEvent.lastevent
if last.inaxes != self.inaxes:
# process Axes enter/leave events
try:
if last.inaxes is not None:
last.canvas.callbacks.process('axes_leave_event', last)
except Exception:
pass
# See ticket 2901582.
# I think this is a valid exception to the rule
# against catching all exceptions; if anything goes
# wrong, we simply want to move on and process the
# current event.
if self.inaxes is not None:
self.canvas.callbacks.process('axes_enter_event', self)

else:
# process a figure enter event
if self.inaxes is not None:
self.canvas.callbacks.process('axes_enter_event', self)

LocationEvent.lastevent = self


class MouseButton(IntEnum):
LEFT = 1
Expand All @@ -1375,11 +1352,15 @@ class MouseEvent(LocationEvent):
----------
button : None or `MouseButton` or {'up', 'down'}
The button pressed. 'up' and 'down' are used for scroll events.

Note that LEFT and RIGHT actually refer to the "primary" and
"secondary" buttons, i.e. if the user inverts their left and right
buttons ("left-handed setting") then the LEFT button will be the one
physically on the right.

If this is unset, *name* is "scroll_event", and *step* is nonzero, then
this will be set to "up" or "down" depending on the sign of *step*.

key : None or str
The key pressed when the mouse event triggered, e.g. 'shift'.
See `KeyEvent`.
Expand Down Expand Up @@ -1411,17 +1392,19 @@ def on_press(event):

def __init__(self, name, canvas, x, y, button=None, key=None,
step=0, dblclick=False, guiEvent=None):
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
if button in MouseButton.__members__.values():
button = MouseButton(button)
if name == "scroll_event" and button is None:
if step > 0:
button = "up"
elif step < 0:
button = "down"
self.button = button
self.key = key
self.step = step
self.dblclick = dblclick

# super-init is deferred to the end because it calls back on
# 'axes_enter_event', which requires a fully initialized event.
super().__init__(name, canvas, x, y, guiEvent=guiEvent)

def __str__(self):
return (f"{self.name}: "
f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
Expand Down Expand Up @@ -1467,8 +1450,11 @@ def on_pick(event):

cid = fig.canvas.mpl_connect('pick_event', on_pick)
"""

def __init__(self, name, canvas, mouseevent, artist,
guiEvent=None, **kwargs):
if guiEvent is None:
guiEvent = mouseevent.guiEvent
super().__init__(name, canvas, guiEvent)
self.mouseevent = mouseevent
self.artist = artist
Expand Down Expand Up @@ -1506,10 +1492,46 @@ def on_key(event):

cid = fig.canvas.mpl_connect('key_press_event', on_key)
"""

def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
self.key = key
# super-init deferred to the end: callback errors if called before
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
self.key = key


# Default callback for key events.
def _key_handler(event):
# Dead reckoning of key.
if event.name == "key_press_event":
event.canvas._key = event.key
elif event.name == "key_release_event":
event.canvas._key = None


# Default callback for mouse events.
def _mouse_handler(event):
# Dead-reckoning of button and key.
if event.name == "button_press_event":
event.canvas._button = event.button
elif event.name == "button_release_event":
event.canvas._button = None
elif event.name == "motion_notify_event" and event.button is None:
event.button = event.canvas._button
if event.key is None:
event.key = event.canvas._key
# Emit axes_enter/axes_leave.
if event.name == "motion_notify_event":
last = LocationEvent.lastevent
last_axes = last.inaxes if last is not None else None
if last_axes != event.inaxes:
if last_axes is not None:
try:
last.canvas.callbacks.process("axes_leave_event", last)
except Exception:
pass # The last canvas may already have been torn down.
if event.inaxes is not None:
event.canvas.callbacks.process("axes_enter_event", event)
LocationEvent.lastevent = (
None if event.name == "figure_leave_event" else event)


def _get_renderer(figure, print_method=None):
Expand Down Expand Up @@ -1720,12 +1742,16 @@ def resize(self, w, h):
_api.warn_deprecated("3.6", name="resize", obj_type="method",
alternative="FigureManagerBase.resize")

@_api.deprecated("3.6", alternative=(
"callbacks.process('draw_event', DrawEvent(...))"))
def draw_event(self, renderer):
"""Pass a `DrawEvent` to all functions connected to ``draw_event``."""
s = 'draw_event'
event = DrawEvent(s, self, renderer)
self.callbacks.process(s, event)

@_api.deprecated("3.6", alternative=(
"callbacks.process('resize_event', ResizeEvent(...))"))
def resize_event(self):
"""
Pass a `ResizeEvent` to all functions connected to ``resize_event``.
Expand All @@ -1735,6 +1761,8 @@ def resize_event(self):
self.callbacks.process(s, event)
self.draw_idle()

@_api.deprecated("3.6", alternative=(
"callbacks.process('close_event', CloseEvent(...))"))
def close_event(self, guiEvent=None):
"""
Pass a `CloseEvent` to all functions connected to ``close_event``.
Expand All @@ -1751,6 +1779,8 @@ def close_event(self, guiEvent=None):
# AttributeError occurs on OSX with qt4agg upon exiting
# with an open window; 'callbacks' attribute no longer exists.

@_api.deprecated("3.6", alternative=(
"callbacks.process('key_press_event', KeyEvent(...))"))
def key_press_event(self, key, guiEvent=None):
"""
Pass a `KeyEvent` to all functions connected to ``key_press_event``.
Expand All @@ -1761,6 +1791,8 @@ def key_press_event(self, key, guiEvent=None):
s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
self.callbacks.process(s, event)

@_api.deprecated("3.6", alternative=(
"callbacks.process('key_release_event', KeyEvent(...))"))
def key_release_event(self, key, guiEvent=None):
"""
Pass a `KeyEvent` to all functions connected to ``key_release_event``.
Expand All @@ -1771,6 +1803,8 @@ def key_release_event(self, key, guiEvent=None):
self.callbacks.process(s, event)
self._key = None

@_api.deprecated("3.6", alternative=(
"callbacks.process('pick_event', PickEvent(...))"))
def pick_event(self, mouseevent, artist, **kwargs):
"""
Callback processing for pick events.
Expand All @@ -1787,6 +1821,8 @@ def pick_event(self, mouseevent, artist, **kwargs):
**kwargs)
self.callbacks.process(s, event)

@_api.deprecated("3.6", alternative=(
"callbacks.process('scroll_event', MouseEvent(...))"))
def scroll_event(self, x, y, step, guiEvent=None):
"""
Callback processing for scroll events.
Expand All @@ -1807,6 +1843,8 @@ def scroll_event(self, x, y, step, guiEvent=None):
step=step, guiEvent=guiEvent)
self.callbacks.process(s, mouseevent)

@_api.deprecated("3.6", alternative=(
"callbacks.process('button_press_event', MouseEvent(...))"))
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
"""
Callback processing for mouse button press events.
Expand All @@ -1824,6 +1862,8 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
dblclick=dblclick, guiEvent=guiEvent)
self.callbacks.process(s, mouseevent)

@_api.deprecated("3.6", alternative=(
"callbacks.process('button_release_event', MouseEvent(...))"))
def button_release_event(self, x, y, button, guiEvent=None):
"""
Callback processing for mouse button release events.
Expand All @@ -1848,6 +1888,9 @@ def button_release_event(self, x, y, button, guiEvent=None):
self.callbacks.process(s, event)
self._button = None

# Also remove _lastx, _lasty when this goes away.
@_api.deprecated("3.6", alternative=(
"callbacks.process('motion_notify_event', MouseEvent(...))"))
def motion_notify_event(self, x, y, guiEvent=None):
"""
Callback processing for mouse movement events.
Expand All @@ -1873,6 +1916,8 @@ def motion_notify_event(self, x, y, guiEvent=None):
guiEvent=guiEvent)
self.callbacks.process(s, event)

@_api.deprecated("3.6", alternative=(
"callbacks.process('leave_notify_event', LocationEvent(...))"))
def leave_notify_event(self, guiEvent=None):
"""
Callback processing for the mouse cursor leaving the canvas.
Expand All @@ -1889,6 +1934,8 @@ def leave_notify_event(self, guiEvent=None):
LocationEvent.lastevent = None
self._lastx, self._lasty = None, None

@_api.deprecated("3.6", alternative=(
"callbacks.process('enter_notify_event', LocationEvent(...))"))
def enter_notify_event(self, guiEvent=None, xy=None):
"""
Callback processing for the mouse cursor entering the canvas.
Expand Down
Loading