FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Separately track modifier keys for mouse events. by anntzer · Pull Request #23473 · matplotlib/matplotlib · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .js  (1) .m  (1) .py  (8) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
14 changes: 10 additions & 4 deletions lib/matplotlib/backend_bases.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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

Back | FazBrowse Home | New Git URL