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

Skip to content

Separately track modifier keys for mouse events. #23473

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 2 commits into from
Dec 16, 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
14 changes: 10 additions & 4 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1316,11 +1316,13 @@ class LocationEvent(Event):
xdata, ydata : float or None
Data coordinates of the mouse within *inaxes*, or *None* if the mouse
is not over an Axes.
modifiers : frozenset
The keyboard modifiers currently being pressed (except for KeyEvent).
"""

lastevent = None # The last event processed so far.

def __init__(self, name, canvas, x, y, guiEvent=None):
def __init__(self, name, canvas, x, y, guiEvent=None, *, modifiers=None):
super().__init__(name, canvas, guiEvent=guiEvent)
# x position - pixels from left of canvas
self.x = int(x) if x is not None else x
Expand All @@ -1329,6 +1331,7 @@ def __init__(self, name, canvas, x, y, guiEvent=None):
self.inaxes = None # the Axes instance the mouse is over
self.xdata = None # x coord of mouse in data coords
self.ydata = None # y coord of mouse in data coords
self.modifiers = frozenset(modifiers if modifiers is not None else [])

if x is None or y is None:
# cannot check if event was in Axes if no (x, y) info
Expand Down Expand Up @@ -1387,7 +1390,9 @@ class MouseEvent(LocationEvent):
This key is currently obtained from the last 'key_press_event' or
'key_release_event' that occurred within the canvas. Thus, if the
last change of keyboard state occurred while the canvas did not have
focus, this attribute will be wrong.
focus, this attribute will be wrong. On the other hand, the
``modifiers`` attribute should always be correct, but it can only
report on modifier keys.

step : float
The number of scroll steps (positive for 'up', negative for 'down').
Expand All @@ -1409,8 +1414,9 @@ 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)
step=0, dblclick=False, guiEvent=None, *, modifiers=None):
super().__init__(
name, canvas, x, y, guiEvent=guiEvent, modifiers=modifiers)
if button in MouseButton.__members__.values():
button = MouseButton(button)
if name == "scroll_event" and button is None:
Expand Down
64 changes: 35 additions & 29 deletions lib/matplotlib/backends/_backend_tk.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,16 +273,19 @@ def _event_mpl_coords(self, event):
def motion_notify_event(self, event):
MouseEvent("motion_notify_event", self,
*self._event_mpl_coords(event),
modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

def enter_notify_event(self, event):
LocationEvent("figure_enter_event", self,
*self._event_mpl_coords(event),
modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

def leave_notify_event(self, event):
LocationEvent("figure_leave_event", self,
*self._event_mpl_coords(event),
modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

def button_press_event(self, event, dblclick=False):
Expand All @@ -294,6 +297,7 @@ def button_press_event(self, event, dblclick=False):
num = {2: 3, 3: 2}.get(num, num)
MouseEvent("button_press_event", self,
*self._event_mpl_coords(event), num, dblclick=dblclick,
modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

def button_dblclick_event(self, event):
Expand All @@ -305,13 +309,15 @@ def button_release_event(self, event):
num = {2: 3, 3: 2}.get(num, num)
MouseEvent("button_release_event", self,
*self._event_mpl_coords(event), num,
modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

def scroll_event(self, event):
num = getattr(event, 'num', None)
step = 1 if num == 4 else -1 if num == 5 else 0
MouseEvent("scroll_event", self,
*self._event_mpl_coords(event), step=step,
modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

def scroll_event_windows(self, event):
Expand All @@ -325,12 +331,11 @@ def scroll_event_windows(self, event):
- self._tkcanvas.canvasy(event.y_root - w.winfo_rooty()))
step = event.delta / 120
MouseEvent("scroll_event", self,
x, y, step=step, guiEvent=event)._process()

def _get_key(self, event):
unikey = event.char
key = cbook._unikey_or_keysym_to_mplkey(unikey, event.keysym)
x, y, step=step, modifiers=self._mpl_modifiers(event),
guiEvent=event)._process()

@staticmethod
def _mpl_modifiers(event, *, exclude=None):
# add modifier keys to the key string. Bit details originate from
# http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
# BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
Expand All @@ -339,32 +344,33 @@ def _get_key(self, event):
# In general, the modifier key is excluded from the modifier flag,
# however this is not the case on "darwin", so double check that
# we aren't adding repeat modifier flags to a modifier key.
if sys.platform == 'win32':
modifiers = [(2, 'ctrl', 'control'),
(17, 'alt', 'alt'),
(0, 'shift', 'shift'),
]
elif sys.platform == 'darwin':
modifiers = [(2, 'ctrl', 'control'),
(4, 'alt', 'alt'),
(0, 'shift', 'shift'),
(3, 'super', 'super'),
]
else:
modifiers = [(2, 'ctrl', 'control'),
(3, 'alt', 'alt'),
(0, 'shift', 'shift'),
(6, 'super', 'super'),
]
modifiers = [
("ctrl", 1 << 2, "control"),
("alt", 1 << 17, "alt"),
("shift", 1 << 0, "shift"),
] if sys.platform == "win32" else [
("ctrl", 1 << 2, "control"),
("alt", 1 << 4, "alt"),
("shift", 1 << 0, "shift"),
("super", 1 << 3, "super"),
] if sys.platform == "darwin" else [
("ctrl", 1 << 2, "control"),
("alt", 1 << 3, "alt"),
("shift", 1 << 0, "shift"),
("super", 1 << 6, "super"),
]
return [name for name, mask, key in modifiers
if event.state & mask and exclude != key]

def _get_key(self, event):
unikey = event.char
key = cbook._unikey_or_keysym_to_mplkey(unikey, event.keysym)
if key is not None:
# shift is not added to the keys as this is already accounted for
for bitmask, prefix, key_name in modifiers:
if event.state & (1 << bitmask) and key_name not in key:
if not (prefix == 'shift' and unikey):
key = '{0}+{1}'.format(prefix, key)

return key
mods = self._mpl_modifiers(event, exclude=key)
# shift is not added to the keys as this is already accounted for.
if "shift" in mods and unikey:
mods.remove("shift")
return "+".join([*mods, key])

def key_press(self, event):
KeyEvent("key_press_event", self,
Expand Down
42 changes: 28 additions & 14 deletions lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,23 @@ def _mpl_coords(self, event=None):

def scroll_event(self, widget, event):
step = 1 if event.direction == Gdk.ScrollDirection.UP else -1
MouseEvent("scroll_event", self, *self._mpl_coords(event), step=step,
MouseEvent("scroll_event", self,
*self._mpl_coords(event), step=step,
modifiers=self._mpl_modifiers(event.state),
guiEvent=event)._process()
return False # finish event propagation?

def button_press_event(self, widget, event):
MouseEvent("button_press_event", self,
*self._mpl_coords(event), event.button,
modifiers=self._mpl_modifiers(event.state),
guiEvent=event)._process()
return False # finish event propagation?

def button_release_event(self, widget, event):
MouseEvent("button_release_event", self,
*self._mpl_coords(event), event.button,
modifiers=self._mpl_modifiers(event.state),
guiEvent=event)._process()
return False # finish event propagation?

Expand All @@ -163,15 +167,22 @@ def key_release_event(self, widget, event):

def motion_notify_event(self, widget, event):
MouseEvent("motion_notify_event", self, *self._mpl_coords(event),
modifiers=self._mpl_modifiers(event.state),
guiEvent=event)._process()
return False # finish event propagation?

def enter_notify_event(self, widget, event):
gtk_mods = Gdk.Keymap.get_for_display(
self.get_display()).get_modifier_state()
LocationEvent("figure_enter_event", self, *self._mpl_coords(event),
modifiers=self._mpl_modifiers(gtk_mods),
guiEvent=event)._process()

def leave_notify_event(self, widget, event):
gtk_mods = Gdk.Keymap.get_for_display(
self.get_display()).get_modifier_state()
LocationEvent("figure_leave_event", self, *self._mpl_coords(event),
modifiers=self._mpl_modifiers(gtk_mods),
guiEvent=event)._process()

def size_allocate(self, widget, allocation):
Expand All @@ -182,22 +193,25 @@ def size_allocate(self, widget, allocation):
ResizeEvent("resize_event", self)._process()
self.draw_idle()

@staticmethod
def _mpl_modifiers(event_state, *, exclude=None):
modifiers = [
("ctrl", Gdk.ModifierType.CONTROL_MASK, "control"),
("alt", Gdk.ModifierType.MOD1_MASK, "alt"),
("shift", Gdk.ModifierType.SHIFT_MASK, "shift"),
("super", Gdk.ModifierType.MOD4_MASK, "super"),
]
return [name for name, mask, key in modifiers
if exclude != key and event_state & mask]

def _get_key(self, event):
unikey = chr(Gdk.keyval_to_unicode(event.keyval))
key = cbook._unikey_or_keysym_to_mplkey(
unikey,
Gdk.keyval_name(event.keyval))
modifiers = [
(Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
(Gdk.ModifierType.MOD1_MASK, 'alt'),
(Gdk.ModifierType.SHIFT_MASK, 'shift'),
(Gdk.ModifierType.MOD4_MASK, 'super'),
]
for key_mask, prefix in modifiers:
if event.state & key_mask:
if not (prefix == 'shift' and unikey.isprintable()):
key = f'{prefix}+{key}'
return key
unikey, Gdk.keyval_name(event.keyval))
mods = self._mpl_modifiers(event.state, exclude=key)
if "shift" in mods and unikey.isprintable():
mods.remove("shift")
return "+".join([*mods, key])

def _update_device_pixel_ratio(self, *args, **kwargs):
# We need to be careful in cases with mixed resolution displays if
Expand Down
91 changes: 60 additions & 31 deletions lib/matplotlib/backends/backend_gtk4.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,44 +109,58 @@ def _mpl_coords(self, xy=None):
return x, y

def scroll_event(self, controller, dx, dy):
MouseEvent("scroll_event", self,
*self._mpl_coords(), step=dy)._process()
MouseEvent(
"scroll_event", self, *self._mpl_coords(), step=dy,
modifiers=self._mpl_modifiers(controller),
)._process()
return True

def button_press_event(self, controller, n_press, x, y):
MouseEvent("button_press_event", self,
*self._mpl_coords((x, y)), controller.get_current_button()
)._process()
MouseEvent(
"button_press_event", self, *self._mpl_coords((x, y)),
controller.get_current_button(),
modifiers=self._mpl_modifiers(controller),
)._process()
self.grab_focus()

def button_release_event(self, controller, n_press, x, y):
MouseEvent("button_release_event", self,
*self._mpl_coords((x, y)), controller.get_current_button()
)._process()
MouseEvent(
"button_release_event", self, *self._mpl_coords((x, y)),
controller.get_current_button(),
modifiers=self._mpl_modifiers(controller),
)._process()

def key_press_event(self, controller, keyval, keycode, state):
KeyEvent("key_press_event", self,
self._get_key(keyval, keycode, state), *self._mpl_coords()
)._process()
KeyEvent(
"key_press_event", self, self._get_key(keyval, keycode, state),
*self._mpl_coords(),
)._process()
return True

def key_release_event(self, controller, keyval, keycode, state):
KeyEvent("key_release_event", self,
self._get_key(keyval, keycode, state), *self._mpl_coords()
)._process()
KeyEvent(
"key_release_event", self, self._get_key(keyval, keycode, state),
*self._mpl_coords(),
)._process()
return True

def motion_notify_event(self, controller, x, y):
MouseEvent("motion_notify_event", self,
*self._mpl_coords((x, y)))._process()

def leave_notify_event(self, controller):
LocationEvent("figure_leave_event", self,
*self._mpl_coords())._process()
MouseEvent(
"motion_notify_event", self, *self._mpl_coords((x, y)),
modifiers=self._mpl_modifiers(controller),
)._process()

def enter_notify_event(self, controller, x, y):
LocationEvent("figure_enter_event", self,
*self._mpl_coords((x, y)))._process()
LocationEvent(
"figure_enter_event", self, *self._mpl_coords((x, y)),
modifiers=self._mpl_modifiers(),
)._process()

def leave_notify_event(self, controller):
LocationEvent(
"figure_leave_event", self, *self._mpl_coords(),
modifiers=self._mpl_modifiers(),
)._process()

def resize_event(self, area, width, height):
self._update_device_pixel_ratio()
Expand All @@ -157,22 +171,37 @@ def resize_event(self, area, width, height):
ResizeEvent("resize_event", self)._process()
self.draw_idle()

def _mpl_modifiers(self, controller=None):
if controller is None:
surface = self.get_native().get_surface()
is_over, x, y, event_state = surface.get_device_position(
self.get_display().get_default_seat().get_pointer())
else:
event_state = controller.get_current_event_state()
mod_table = [
("ctrl", Gdk.ModifierType.CONTROL_MASK),
("alt", Gdk.ModifierType.ALT_MASK),
("shift", Gdk.ModifierType.SHIFT_MASK),
("super", Gdk.ModifierType.SUPER_MASK),
]
return [name for name, mask in mod_table if event_state & mask]

def _get_key(self, keyval, keycode, state):
unikey = chr(Gdk.keyval_to_unicode(keyval))
key = cbook._unikey_or_keysym_to_mplkey(
unikey,
Gdk.keyval_name(keyval))
modifiers = [
(Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
(Gdk.ModifierType.ALT_MASK, 'alt'),
(Gdk.ModifierType.SHIFT_MASK, 'shift'),
(Gdk.ModifierType.SUPER_MASK, 'super'),
("ctrl", Gdk.ModifierType.CONTROL_MASK, "control"),
("alt", Gdk.ModifierType.ALT_MASK, "alt"),
("shift", Gdk.ModifierType.SHIFT_MASK, "shift"),
("super", Gdk.ModifierType.SUPER_MASK, "super"),
]
for key_mask, prefix in modifiers:
if state & key_mask:
if not (prefix == 'shift' and unikey.isprintable()):
key = f'{prefix}+{key}'
return key
mods = [
mod for mod, mask, mod_key in modifiers
if (mod_key != key and state & mask
and not (mod == "shift" and unikey.isprintable()))]
return "+".join([*mods, key])

def _update_device_pixel_ratio(self, *args, **kwargs):
# We need to be careful in cases with mixed resolution displays if
Expand Down
Loading