Skip to content

Commit 1cddbef

Browse files
committed
Separately track modifier keys for mouse events.
Whether the event modifiers are directly available on enter/leave events depends on the backend, but all are handled here (except possibly for macos, which I haven't checked).
1 parent c2c1dbb commit 1cddbef

File tree

10 files changed

+255
-119
lines changed

10 files changed

+255
-119
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,11 +1316,13 @@ class LocationEvent(Event):
13161316
xdata, ydata : float or None
13171317
Data coordinates of the mouse within *inaxes*, or *None* if the mouse
13181318
is not over an Axes.
1319+
modifiers : frozenset
1320+
The keyboard modifiers currently being pressed (except for KeyEvent).
13191321
"""
13201322

13211323
lastevent = None # The last event processed so far.
13221324

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

13331336
if x is None or y is None:
13341337
# cannot check if event was in Axes if no (x, y) info
@@ -1387,7 +1390,9 @@ class MouseEvent(LocationEvent):
13871390
This key is currently obtained from the last 'key_press_event' or
13881391
'key_release_event' that occurred within the canvas. Thus, if the
13891392
last change of keyboard state occurred while the canvas did not have
1390-
focus, this attribute will be wrong.
1393+
focus, this attribute will be wrong. On the other hand, the
1394+
``modifiers`` attribute should always be correct, but it can only
1395+
report on modifier keys.
13911396
13921397
step : float
13931398
The number of scroll steps (positive for 'up', negative for 'down').
@@ -1409,8 +1414,9 @@ def on_press(event):
14091414
"""
14101415

14111416
def __init__(self, name, canvas, x, y, button=None, key=None,
1412-
step=0, dblclick=False, guiEvent=None):
1413-
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
1417+
step=0, dblclick=False, guiEvent=None, *, modifiers=None):
1418+
super().__init__(
1419+
name, canvas, x, y, guiEvent=guiEvent, modifiers=modifiers)
14141420
if button in MouseButton.__members__.values():
14151421
button = MouseButton(button)
14161422
if name == "scroll_event" and button is None:

lib/matplotlib/backends/_backend_tk.py

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,19 @@ def _event_mpl_coords(self, event):
273273
def motion_notify_event(self, event):
274274
MouseEvent("motion_notify_event", self,
275275
*self._event_mpl_coords(event),
276+
modifiers=self._mpl_modifiers(event),
276277
guiEvent=event)._process()
277278

278279
def enter_notify_event(self, event):
279280
LocationEvent("figure_enter_event", self,
280281
*self._event_mpl_coords(event),
282+
modifiers=self._mpl_modifiers(event),
281283
guiEvent=event)._process()
282284

283285
def leave_notify_event(self, event):
284286
LocationEvent("figure_leave_event", self,
285287
*self._event_mpl_coords(event),
288+
modifiers=self._mpl_modifiers(event),
286289
guiEvent=event)._process()
287290

288291
def button_press_event(self, event, dblclick=False):
@@ -294,6 +297,7 @@ def button_press_event(self, event, dblclick=False):
294297
num = {2: 3, 3: 2}.get(num, num)
295298
MouseEvent("button_press_event", self,
296299
*self._event_mpl_coords(event), num, dblclick=dblclick,
300+
modifiers=self._mpl_modifiers(event),
297301
guiEvent=event)._process()
298302

299303
def button_dblclick_event(self, event):
@@ -305,13 +309,15 @@ def button_release_event(self, event):
305309
num = {2: 3, 3: 2}.get(num, num)
306310
MouseEvent("button_release_event", self,
307311
*self._event_mpl_coords(event), num,
312+
modifiers=self._mpl_modifiers(event),
308313
guiEvent=event)._process()
309314

310315
def scroll_event(self, event):
311316
num = getattr(event, 'num', None)
312317
step = 1 if num == 4 else -1 if num == 5 else 0
313318
MouseEvent("scroll_event", self,
314319
*self._event_mpl_coords(event), step=step,
320+
modifiers=self._mpl_modifiers(event),
315321
guiEvent=event)._process()
316322

317323
def scroll_event_windows(self, event):
@@ -325,12 +331,10 @@ def scroll_event_windows(self, event):
325331
- self._tkcanvas.canvasy(event.y_root - w.winfo_rooty()))
326332
step = event.delta / 120
327333
MouseEvent("scroll_event", self,
328-
x, y, step=step, guiEvent=event)._process()
329-
330-
def _get_key(self, event):
331-
unikey = event.char
332-
key = cbook._unikey_or_keysym_to_mplkey(unikey, event.keysym)
334+
x, y, step=step, modifiers=self._mpl_modifiers(event),
335+
guiEvent=event)._process()
333336

337+
def _mpl_modifiers(self, event, *, exclude=None):
334338
# add modifier keys to the key string. Bit details originate from
335339
# http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
336340
# BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
@@ -339,32 +343,33 @@ def _get_key(self, event):
339343
# In general, the modifier key is excluded from the modifier flag,
340344
# however this is not the case on "darwin", so double check that
341345
# we aren't adding repeat modifier flags to a modifier key.
342-
if sys.platform == 'win32':
343-
modifiers = [(2, 'ctrl', 'control'),
344-
(17, 'alt', 'alt'),
345-
(0, 'shift', 'shift'),
346-
]
347-
elif sys.platform == 'darwin':
348-
modifiers = [(2, 'ctrl', 'control'),
349-
(4, 'alt', 'alt'),
350-
(0, 'shift', 'shift'),
351-
(3, 'super', 'super'),
352-
]
353-
else:
354-
modifiers = [(2, 'ctrl', 'control'),
355-
(3, 'alt', 'alt'),
356-
(0, 'shift', 'shift'),
357-
(6, 'super', 'super'),
358-
]
346+
modifiers = [
347+
("ctrl", 2, "control"),
348+
("alt", 17, "alt"),
349+
("shift", 0, "shift"),
350+
] if sys.platform == "win32" else [
351+
("ctrl", 2, "control"),
352+
("alt", 4, "alt"),
353+
("shift", 0, "shift"),
354+
("super", 3, "super"),
355+
] if sys.platform == "darwin" else [
356+
("ctrl", 2, "control"),
357+
("alt", 3, "alt"),
358+
("shift", 0, "shift"),
359+
("super", 6, "super"),
360+
]
361+
return [name for name, mod, key in modifiers
362+
if event.state & (1 << mod) and exclude != key]
359363

364+
def _get_key(self, event):
365+
unikey = event.char
366+
key = cbook._unikey_or_keysym_to_mplkey(unikey, event.keysym)
360367
if key is not None:
361-
# shift is not added to the keys as this is already accounted for
362-
for bitmask, prefix, key_name in modifiers:
363-
if event.state & (1 << bitmask) and key_name not in key:
364-
if not (prefix == 'shift' and unikey):
365-
key = '{0}+{1}'.format(prefix, key)
366-
367-
return key
368+
mods = self._mpl_modifiers(event)
369+
# shift is not added to the keys as this is already accounted for.
370+
if "shift" in mods and unikey:
371+
mods.remove("shift")
372+
return "+".join([*mods, key])
368373

369374
def key_press(self, event):
370375
KeyEvent("key_press_event", self,

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,23 @@ def _mpl_coords(self, event=None):
134134

135135
def scroll_event(self, widget, event):
136136
step = 1 if event.direction == Gdk.ScrollDirection.UP else -1
137-
MouseEvent("scroll_event", self, *self._mpl_coords(event), step=step,
137+
MouseEvent("scroll_event", self,
138+
*self._mpl_coords(event), step=step,
139+
modifiers=self._mpl_modifiers(event.state),
138140
guiEvent=event)._process()
139141
return False # finish event propagation?
140142

141143
def button_press_event(self, widget, event):
142144
MouseEvent("button_press_event", self,
143145
*self._mpl_coords(event), event.button,
146+
modifiers=self._mpl_modifiers(event.state),
144147
guiEvent=event)._process()
145148
return False # finish event propagation?
146149

147150
def button_release_event(self, widget, event):
148151
MouseEvent("button_release_event", self,
149152
*self._mpl_coords(event), event.button,
153+
modifiers=self._mpl_modifiers(event.state),
150154
guiEvent=event)._process()
151155
return False # finish event propagation?
152156

@@ -164,15 +168,22 @@ def key_release_event(self, widget, event):
164168

165169
def motion_notify_event(self, widget, event):
166170
MouseEvent("motion_notify_event", self, *self._mpl_coords(event),
171+
modifiers=self._mpl_modifiers(event.state),
167172
guiEvent=event)._process()
168173
return False # finish event propagation?
169174

170175
def enter_notify_event(self, widget, event):
176+
gtk_mods = Gdk.Keymap.get_for_display(
177+
self.get_display()).get_modifier_state()
171178
LocationEvent("figure_enter_event", self, *self._mpl_coords(event),
179+
modifiers=self._mpl_modifiers(gtk_mods),
172180
guiEvent=event)._process()
173181

174182
def leave_notify_event(self, widget, event):
183+
gtk_mods = Gdk.Keymap.get_for_display(
184+
self.get_display()).get_modifier_state()
175185
LocationEvent("figure_leave_event", self, *self._mpl_coords(event),
186+
modifiers=self._mpl_modifiers(gtk_mods),
176187
guiEvent=event)._process()
177188

178189
def size_allocate(self, widget, allocation):
@@ -183,22 +194,24 @@ def size_allocate(self, widget, allocation):
183194
ResizeEvent("resize_event", self)._process()
184195
self.draw_idle()
185196

197+
@staticmethod
198+
def _mpl_modifiers(event_state):
199+
mod_table = [
200+
("ctrl", Gdk.ModifierType.CONTROL_MASK),
201+
("alt", Gdk.ModifierType.MOD1_MASK),
202+
("shift", Gdk.ModifierType.SHIFT_MASK),
203+
("super", Gdk.ModifierType.MOD4_MASK),
204+
]
205+
return [name for name, mask in mod_table if event_state & mask]
206+
186207
def _get_key(self, event):
187208
unikey = chr(Gdk.keyval_to_unicode(event.keyval))
188209
key = cbook._unikey_or_keysym_to_mplkey(
189-
unikey,
190-
Gdk.keyval_name(event.keyval))
191-
modifiers = [
192-
(Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
193-
(Gdk.ModifierType.MOD1_MASK, 'alt'),
194-
(Gdk.ModifierType.SHIFT_MASK, 'shift'),
195-
(Gdk.ModifierType.MOD4_MASK, 'super'),
196-
]
197-
for key_mask, prefix in modifiers:
198-
if event.state & key_mask:
199-
if not (prefix == 'shift' and unikey.isprintable()):
200-
key = f'{prefix}+{key}'
201-
return key
210+
unikey, Gdk.keyval_name(event.keyval))
211+
mods = self._mpl_modifiers(event.state)
212+
if "shift" in mods and unikey.isprintable():
213+
mods.remove("shift")
214+
return "+".join([*mods, key])
202215

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

lib/matplotlib/backends/backend_gtk4.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -110,44 +110,58 @@ def _mpl_coords(self, xy=None):
110110
return x, y
111111

112112
def scroll_event(self, controller, dx, dy):
113-
MouseEvent("scroll_event", self,
114-
*self._mpl_coords(), step=dy)._process()
113+
MouseEvent(
114+
"scroll_event", self, *self._mpl_coords(), step=dy,
115+
modifiers=self._mpl_modifiers(controller),
116+
)._process()
115117
return True
116118

117119
def button_press_event(self, controller, n_press, x, y):
118-
MouseEvent("button_press_event", self,
119-
*self._mpl_coords((x, y)), controller.get_current_button()
120-
)._process()
120+
MouseEvent(
121+
"button_press_event", self, *self._mpl_coords((x, y)),
122+
controller.get_current_button(),
123+
modifiers=self._mpl_modifiers(controller),
124+
)._process()
121125
self.grab_focus()
122126

123127
def button_release_event(self, controller, n_press, x, y):
124-
MouseEvent("button_release_event", self,
125-
*self._mpl_coords((x, y)), controller.get_current_button()
126-
)._process()
128+
MouseEvent(
129+
"button_release_event", self, *self._mpl_coords((x, y)),
130+
controller.get_current_button(),
131+
modifiers=self._mpl_modifiers(controller),
132+
)._process()
127133

128134
def key_press_event(self, controller, keyval, keycode, state):
129-
KeyEvent("key_press_event", self,
130-
self._get_key(keyval, keycode, state), *self._mpl_coords()
131-
)._process()
135+
KeyEvent(
136+
"key_press_event", self, self._get_key(keyval, keycode, state),
137+
*self._mpl_coords(),
138+
)._process()
132139
return True
133140

134141
def key_release_event(self, controller, keyval, keycode, state):
135-
KeyEvent("key_release_event", self,
136-
self._get_key(keyval, keycode, state), *self._mpl_coords()
137-
)._process()
142+
KeyEvent(
143+
"key_release_event", self, self._get_key(keyval, keycode, state),
144+
*self._mpl_coords(),
145+
)._process()
138146
return True
139147

140148
def motion_notify_event(self, controller, x, y):
141-
MouseEvent("motion_notify_event", self,
142-
*self._mpl_coords((x, y)))._process()
143-
144-
def leave_notify_event(self, controller):
145-
LocationEvent("figure_leave_event", self,
146-
*self._mpl_coords())._process()
149+
MouseEvent(
150+
"motion_notify_event", self, *self._mpl_coords((x, y)),
151+
modifiers=self._mpl_modifiers(controller),
152+
)._process()
147153

148154
def enter_notify_event(self, controller, x, y):
149-
LocationEvent("figure_enter_event", self,
150-
*self._mpl_coords((x, y)))._process()
155+
LocationEvent(
156+
"figure_enter_event", self, *self._mpl_coords((x, y)),
157+
modifiers=self._mpl_modifiers(),
158+
)._process()
159+
160+
def leave_notify_event(self, controller):
161+
LocationEvent(
162+
"figure_leave_event", self, *self._mpl_coords(),
163+
modifiers=self._mpl_modifiers(),
164+
)._process()
151165

152166
def resize_event(self, area, width, height):
153167
self._update_device_pixel_ratio()
@@ -158,6 +172,21 @@ def resize_event(self, area, width, height):
158172
ResizeEvent("resize_event", self)._process()
159173
self.draw_idle()
160174

175+
def _mpl_modifiers(self, controller=None):
176+
if controller is None:
177+
surface = self.get_native().get_surface()
178+
is_over, x, y, event_state = surface.get_device_position(
179+
self.get_display().get_default_seat().get_pointer())
180+
else:
181+
event_state = controller.get_current_event_state()
182+
mod_table = [
183+
("ctrl", Gdk.ModifierType.CONTROL_MASK),
184+
("alt", Gdk.ModifierType.ALT_MASK),
185+
("shift", Gdk.ModifierType.SHIFT_MASK),
186+
("super", Gdk.ModifierType.SUPER_MASK),
187+
]
188+
return [name for name, mask in mod_table if event_state & mask]
189+
161190
def _get_key(self, keyval, keycode, state):
162191
unikey = chr(Gdk.keyval_to_unicode(keyval))
163192
key = cbook._unikey_or_keysym_to_mplkey(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy