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

Skip to content

Commit ecc7f36

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 49724bf commit ecc7f36

File tree

10 files changed

+266
-129
lines changed

10 files changed

+266
-129
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", 1 << 2, "control"),
348+
("alt", 1 << 17, "alt"),
349+
("shift", 1 << 0, "shift"),
350+
] if sys.platform == "win32" else [
351+
("ctrl", 1 << 2, "control"),
352+
("alt", 1 << 4, "alt"),
353+
("shift", 1 << 0, "shift"),
354+
("super", 1 << 3, "super"),
355+
] if sys.platform == "darwin" else [
356+
("ctrl", 1 << 2, "control"),
357+
("alt", 1 << 3, "alt"),
358+
("shift", 1 << 0, "shift"),
359+
("super", 1 << 6, "super"),
360+
]
361+
return [name for name, mask, key in modifiers
362+
if event.state & mask 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, exclude=key)
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: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,23 @@ def _mpl_coords(self, event=None):
133133

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

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

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

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

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

169174
def enter_notify_event(self, widget, event):
175+
gtk_mods = Gdk.Keymap.get_for_display(
176+
self.get_display()).get_modifier_state()
170177
LocationEvent("figure_enter_event", self, *self._mpl_coords(event),
178+
modifiers=self._mpl_modifiers(gtk_mods),
171179
guiEvent=event)._process()
172180

173181
def leave_notify_event(self, widget, event):
182+
gtk_mods = Gdk.Keymap.get_for_display(
183+
self.get_display()).get_modifier_state()
174184
LocationEvent("figure_leave_event", self, *self._mpl_coords(event),
185+
modifiers=self._mpl_modifiers(gtk_mods),
175186
guiEvent=event)._process()
176187

177188
def size_allocate(self, widget, allocation):
@@ -182,22 +193,25 @@ def size_allocate(self, widget, allocation):
182193
ResizeEvent("resize_event", self)._process()
183194
self.draw_idle()
184195

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

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

lib/matplotlib/backends/backend_gtk4.py

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -109,44 +109,58 @@ def _mpl_coords(self, xy=None):
109109
return x, y
110110

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

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

122126
def button_release_event(self, controller, n_press, x, y):
123-
MouseEvent("button_release_event", self,
124-
*self._mpl_coords((x, y)), controller.get_current_button()
125-
)._process()
127+
MouseEvent(
128+
"button_release_event", self, *self._mpl_coords((x, y)),
129+
controller.get_current_button(),
130+
modifiers=self._mpl_modifiers(controller),
131+
)._process()
126132

127133
def key_press_event(self, controller, keyval, keycode, state):
128-
KeyEvent("key_press_event", self,
129-
self._get_key(keyval, keycode, state), *self._mpl_coords()
130-
)._process()
134+
KeyEvent(
135+
"key_press_event", self, self._get_key(keyval, keycode, state),
136+
*self._mpl_coords(),
137+
)._process()
131138
return True
132139

133140
def key_release_event(self, controller, keyval, keycode, state):
134-
KeyEvent("key_release_event", self,
135-
self._get_key(keyval, keycode, state), *self._mpl_coords()
136-
)._process()
141+
KeyEvent(
142+
"key_release_event", self, self._get_key(keyval, keycode, state),
143+
*self._mpl_coords(),
144+
)._process()
137145
return True
138146

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

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

151165
def resize_event(self, area, width, height):
152166
self._update_device_pixel_ratio()
@@ -157,22 +171,37 @@ def resize_event(self, area, width, height):
157171
ResizeEvent("resize_event", self)._process()
158172
self.draw_idle()
159173

174+
def _mpl_modifiers(self, controller=None):
175+
if controller is None:
176+
surface = self.get_native().get_surface()
177+
is_over, x, y, event_state = surface.get_device_position(
178+
self.get_display().get_default_seat().get_pointer())
179+
else:
180+
event_state = controller.get_current_event_state()
181+
mod_table = [
182+
("ctrl", Gdk.ModifierType.CONTROL_MASK),
183+
("alt", Gdk.ModifierType.ALT_MASK),
184+
("shift", Gdk.ModifierType.SHIFT_MASK),
185+
("super", Gdk.ModifierType.SUPER_MASK),
186+
]
187+
return [name for name, mask in mod_table if event_state & mask]
188+
160189
def _get_key(self, keyval, keycode, state):
161190
unikey = chr(Gdk.keyval_to_unicode(keyval))
162191
key = cbook._unikey_or_keysym_to_mplkey(
163192
unikey,
164193
Gdk.keyval_name(keyval))
165194
modifiers = [
166-
(Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
167-
(Gdk.ModifierType.ALT_MASK, 'alt'),
168-
(Gdk.ModifierType.SHIFT_MASK, 'shift'),
169-
(Gdk.ModifierType.SUPER_MASK, 'super'),
195+
("ctrl", Gdk.ModifierType.CONTROL_MASK, "control"),
196+
("alt", Gdk.ModifierType.ALT_MASK, "alt"),
197+
("shift", Gdk.ModifierType.SHIFT_MASK, "shift"),
198+
("super", Gdk.ModifierType.SUPER_MASK, "super"),
170199
]
171-
for key_mask, prefix in modifiers:
172-
if state & key_mask:
173-
if not (prefix == 'shift' and unikey.isprintable()):
174-
key = f'{prefix}+{key}'
175-
return key
200+
mods = [
201+
mod for mod, mask, mod_key in modifiers
202+
if (mod_key != key and state & mask
203+
and not (mod == "shift" and unikey.isprintable()))]
204+
return "+".join([*mods, key])
176205

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

0 commit comments

Comments
 (0)