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

Skip to content

Commit d625e11

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 d947ff4 commit d625e11

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
@@ -1298,11 +1298,13 @@ class LocationEvent(Event):
12981298
xdata, ydata : float or None
12991299
Data coordinates of the mouse within *inaxes*, or *None* if the mouse
13001300
is not over an Axes.
1301+
modifiers : frozenset
1302+
The keyboard modifiers currently being pressed (except for KeyEvent).
13011303
"""
13021304

13031305
lastevent = None # The last event processed so far.
13041306

1305-
def __init__(self, name, canvas, x, y, guiEvent=None):
1307+
def __init__(self, name, canvas, x, y, guiEvent=None, *, modifiers=None):
13061308
super().__init__(name, canvas, guiEvent=guiEvent)
13071309
# x position - pixels from left of canvas
13081310
self.x = int(x) if x is not None else x
@@ -1311,6 +1313,7 @@ def __init__(self, name, canvas, x, y, guiEvent=None):
13111313
self.inaxes = None # the Axes instance the mouse is over
13121314
self.xdata = None # x coord of mouse in data coords
13131315
self.ydata = None # y coord of mouse in data coords
1316+
self.modifiers = frozenset(modifiers if modifiers is not None else [])
13141317

13151318
if x is None or y is None:
13161319
# cannot check if event was in Axes if no (x, y) info
@@ -1369,7 +1372,9 @@ class MouseEvent(LocationEvent):
13691372
This key is currently obtained from the last 'key_press_event' or
13701373
'key_release_event' that occurred within the canvas. Thus, if the
13711374
last change of keyboard state occurred while the canvas did not have
1372-
focus, this attribute will be wrong.
1375+
focus, this attribute will be wrong. On the other hand, the
1376+
``modifiers`` attribute should always be correct, but it can only
1377+
report on modifier keys.
13731378
13741379
step : float
13751380
The number of scroll steps (positive for 'up', negative for 'down').
@@ -1391,8 +1396,9 @@ def on_press(event):
13911396
"""
13921397

13931398
def __init__(self, name, canvas, x, y, button=None, key=None,
1394-
step=0, dblclick=False, guiEvent=None):
1395-
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
1399+
step=0, dblclick=False, guiEvent=None, *, modifiers=None):
1400+
super().__init__(
1401+
name, canvas, x, y, guiEvent=guiEvent, modifiers=modifiers)
13961402
if button in MouseButton.__members__.values():
13971403
button = MouseButton(button)
13981404
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
@@ -275,16 +275,19 @@ def _event_mpl_coords(self, event):
275275
def motion_notify_event(self, event):
276276
MouseEvent("motion_notify_event", self,
277277
*self._event_mpl_coords(event),
278+
modifiers=self._mpl_modifiers(event),
278279
guiEvent=event)._process()
279280

280281
def enter_notify_event(self, event):
281282
LocationEvent("figure_enter_event", self,
282283
*self._event_mpl_coords(event),
284+
modifiers=self._mpl_modifiers(event),
283285
guiEvent=event)._process()
284286

285287
def leave_notify_event(self, event):
286288
LocationEvent("figure_leave_event", self,
287289
*self._event_mpl_coords(event),
290+
modifiers=self._mpl_modifiers(event),
288291
guiEvent=event)._process()
289292

290293
def button_press_event(self, event, dblclick=False):
@@ -296,6 +299,7 @@ def button_press_event(self, event, dblclick=False):
296299
num = {2: 3, 3: 2}.get(num, num)
297300
MouseEvent("button_press_event", self,
298301
*self._event_mpl_coords(event), num, dblclick=dblclick,
302+
modifiers=self._mpl_modifiers(event),
299303
guiEvent=event)._process()
300304

301305
def button_dblclick_event(self, event):
@@ -307,13 +311,15 @@ def button_release_event(self, event):
307311
num = {2: 3, 3: 2}.get(num, num)
308312
MouseEvent("button_release_event", self,
309313
*self._event_mpl_coords(event), num,
314+
modifiers=self._mpl_modifiers(event),
310315
guiEvent=event)._process()
311316

312317
def scroll_event(self, event):
313318
num = getattr(event, 'num', None)
314319
step = 1 if num == 4 else -1 if num == 5 else 0
315320
MouseEvent("scroll_event", self,
316321
*self._event_mpl_coords(event), step=step,
322+
modifiers=self._mpl_modifiers(event),
317323
guiEvent=event)._process()
318324

319325
def scroll_event_windows(self, event):
@@ -327,12 +333,10 @@ def scroll_event_windows(self, event):
327333
- self._tkcanvas.canvasy(event.y_root - w.winfo_rooty()))
328334
step = event.delta / 120
329335
MouseEvent("scroll_event", self,
330-
x, y, step=step, guiEvent=event)._process()
331-
332-
def _get_key(self, event):
333-
unikey = event.char
334-
key = cbook._unikey_or_keysym_to_mplkey(unikey, event.keysym)
336+
x, y, step=step, modifiers=self._mpl_modifiers(event),
337+
guiEvent=event)._process()
335338

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

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

371376
def key_press(self, event):
372377
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
@@ -152,19 +152,23 @@ def _mpl_coords(self, event=None):
152152

153153
def scroll_event(self, widget, event):
154154
step = 1 if event.direction == Gdk.ScrollDirection.UP else -1
155-
MouseEvent("scroll_event", self, *self._mpl_coords(event), step=step,
155+
MouseEvent("scroll_event", self,
156+
*self._mpl_coords(event), step=step,
157+
modifiers=self._mpl_modifiers(event.state),
156158
guiEvent=event)._process()
157159
return False # finish event propagation?
158160

159161
def button_press_event(self, widget, event):
160162
MouseEvent("button_press_event", self,
161163
*self._mpl_coords(event), event.button,
164+
modifiers=self._mpl_modifiers(event.state),
162165
guiEvent=event)._process()
163166
return False # finish event propagation?
164167

165168
def button_release_event(self, widget, event):
166169
MouseEvent("button_release_event", self,
167170
*self._mpl_coords(event), event.button,
171+
modifiers=self._mpl_modifiers(event.state),
168172
guiEvent=event)._process()
169173
return False # finish event propagation?
170174

@@ -182,15 +186,22 @@ def key_release_event(self, widget, event):
182186

183187
def motion_notify_event(self, widget, event):
184188
MouseEvent("motion_notify_event", self, *self._mpl_coords(event),
189+
modifiers=self._mpl_modifiers(event.state),
185190
guiEvent=event)._process()
186191
return False # finish event propagation?
187192

188193
def enter_notify_event(self, widget, event):
194+
gtk_mods = Gdk.Keymap.get_for_display(
195+
self.get_display()).get_modifier_state()
189196
LocationEvent("figure_enter_event", self, *self._mpl_coords(event),
197+
modifiers=self._mpl_modifiers(gtk_mods),
190198
guiEvent=event)._process()
191199

192200
def leave_notify_event(self, widget, event):
201+
gtk_mods = Gdk.Keymap.get_for_display(
202+
self.get_display()).get_modifier_state()
193203
LocationEvent("figure_leave_event", self, *self._mpl_coords(event),
204+
modifiers=self._mpl_modifiers(gtk_mods),
194205
guiEvent=event)._process()
195206

196207
def size_allocate(self, widget, allocation):
@@ -201,22 +212,24 @@ def size_allocate(self, widget, allocation):
201212
ResizeEvent("resize_event", self)._process()
202213
self.draw_idle()
203214

215+
@staticmethod
216+
def _mpl_modifiers(event_state):
217+
mod_table = [
218+
("ctrl", Gdk.ModifierType.CONTROL_MASK),
219+
("alt", Gdk.ModifierType.MOD1_MASK),
220+
("shift", Gdk.ModifierType.SHIFT_MASK),
221+
("super", Gdk.ModifierType.MOD4_MASK),
222+
]
223+
return [name for name, mask in mod_table if event_state & mask]
224+
204225
def _get_key(self, event):
205226
unikey = chr(Gdk.keyval_to_unicode(event.keyval))
206227
key = cbook._unikey_or_keysym_to_mplkey(
207-
unikey,
208-
Gdk.keyval_name(event.keyval))
209-
modifiers = [
210-
(Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
211-
(Gdk.ModifierType.MOD1_MASK, 'alt'),
212-
(Gdk.ModifierType.SHIFT_MASK, 'shift'),
213-
(Gdk.ModifierType.MOD4_MASK, 'super'),
214-
]
215-
for key_mask, prefix in modifiers:
216-
if event.state & key_mask:
217-
if not (prefix == 'shift' and unikey.isprintable()):
218-
key = f'{prefix}+{key}'
219-
return key
228+
unikey, Gdk.keyval_name(event.keyval))
229+
mods = self._mpl_modifiers(event.state)
230+
if "shift" in mods and unikey.isprintable():
231+
mods.remove("shift")
232+
return "+".join([*mods, key])
220233

221234
def _update_device_pixel_ratio(self, *args, **kwargs):
222235
# 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
@@ -111,44 +111,58 @@ def _mpl_coords(self, xy=None):
111111
return x, y
112112

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

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

124128
def button_release_event(self, controller, n_press, x, y):
125-
MouseEvent("button_release_event", self,
126-
*self._mpl_coords((x, y)), controller.get_current_button()
127-
)._process()
129+
MouseEvent(
130+
"button_release_event", self, *self._mpl_coords((x, y)),
131+
controller.get_current_button(),
132+
modifiers=self._mpl_modifiers(controller),
133+
)._process()
128134

129135
def key_press_event(self, controller, keyval, keycode, state):
130-
KeyEvent("key_press_event", self,
131-
self._get_key(keyval, keycode, state), *self._mpl_coords()
132-
)._process()
136+
KeyEvent(
137+
"key_press_event", self, self._get_key(keyval, keycode, state),
138+
*self._mpl_coords(),
139+
)._process()
133140
return True
134141

135142
def key_release_event(self, controller, keyval, keycode, state):
136-
KeyEvent("key_release_event", self,
137-
self._get_key(keyval, keycode, state), *self._mpl_coords()
138-
)._process()
143+
KeyEvent(
144+
"key_release_event", self, self._get_key(keyval, keycode, state),
145+
*self._mpl_coords(),
146+
)._process()
139147
return True
140148

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

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

153167
def resize_event(self, area, width, height):
154168
self._update_device_pixel_ratio()
@@ -159,6 +173,21 @@ def resize_event(self, area, width, height):
159173
ResizeEvent("resize_event", self)._process()
160174
self.draw_idle()
161175

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

0 commit comments

Comments
 (0)