Skip to content

Commit 4ea0405

Browse files
committed
Added mouse event modifiers
1 parent 4c33d97 commit 4ea0405

File tree

7 files changed

+136
-61
lines changed

7 files changed

+136
-61
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,10 +1606,15 @@ class MouseEvent(LocationEvent):
16061606
key : None, or str
16071607
the key depressed when the mouse event triggered (see
16081608
:class:`KeyEvent`)
1609+
Note: .key is only processed if the FigureCanvas received keyboard
1610+
focus. For safer retrieval of modifier keys use .modifiers instead
16091611
16101612
step : scalar
16111613
number of scroll steps (positive for 'up', negative for 'down')
16121614
1615+
modifiers : set
1616+
modifier keys depressed when mouse event is triggered
1617+
16131618
Examples
16141619
--------
16151620
Usage::
@@ -1620,17 +1625,18 @@ def on_press(event):
16201625
cid = fig.canvas.mpl_connect('button_press_event', on_press)
16211626
16221627
"""
1623-
x = None # x position - pixels from left of canvas
1624-
y = None # y position - pixels from right of canvas
1625-
button = None # button pressed None, 1, 2, 3
1626-
dblclick = None # whether or not the event is the result of a double click
1627-
inaxes = None # the Axes instance if mouse us over axes
1628-
xdata = None # x coord of mouse in data coords
1629-
ydata = None # y coord of mouse in data coords
1630-
step = None # scroll steps for scroll events
1628+
x = None # x position - pixels from left of canvas
1629+
y = None # y position - pixels from right of canvas
1630+
button = None # button pressed None, 1, 2, 3
1631+
dblclick = None # whether or not the event is the result of a double click
1632+
inaxes = None # the Axes instance if mouse us over axes
1633+
xdata = None # x coord of mouse in data coords
1634+
ydata = None # y coord of mouse in data coords
1635+
step = None # scroll steps for scroll events
1636+
modifiers = None # depressed modifier keys
16311637

16321638
def __init__(self, name, canvas, x, y, button=None, key=None,
1633-
step=0, dblclick=False, guiEvent=None):
1639+
step=0, dblclick=False, guiEvent=None, modifiers=None):
16341640
"""
16351641
x, y in figure coords, 0,0 = bottom, left
16361642
button pressed None, 1, 2, 3, 'up', 'down'
@@ -1640,6 +1646,7 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
16401646
self.key = key
16411647
self.step = step
16421648
self.dblclick = dblclick
1649+
self.modifiers = modifiers
16431650

16441651
def __str__(self):
16451652
return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%s " +
@@ -1921,7 +1928,7 @@ def scroll_event(self, x, y, step, guiEvent=None):
19211928
step=step, guiEvent=guiEvent)
19221929
self.callbacks.process(s, mouseevent)
19231930

1924-
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
1931+
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None, modifiers=None):
19251932
"""
19261933
Backend derived classes should call this function on any mouse
19271934
button press. x,y are the canvas coords: 0,0 is lower, left.
@@ -1932,8 +1939,9 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
19321939
"""
19331940
self._button = button
19341941
s = 'button_press_event'
1942+
19351943
mouseevent = MouseEvent(s, self, x, y, button, self._key,
1936-
dblclick=dblclick, guiEvent=guiEvent)
1944+
dblclick=dblclick, guiEvent=guiEvent, modifiers=modifiers)
19371945
self.callbacks.process(s, mouseevent)
19381946

19391947
def button_release_event(self, x, y, button, guiEvent=None):

lib/matplotlib/backends/backend_gtk.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ class FigureCanvasGTK (gtk.DrawingArea, FigureCanvasBase):
163163
65406 : 'alt',
164164
65289 : 'tab',
165165
}
166+
modifier_keys = [[gdk.MOD4_MASK, 'super'],
167+
[gdk.MOD1_MASK, 'alt'],
168+
[gdk.CONTROL_MASK, 'ctrl'],
169+
[gdk.SHIFT_MASK, 'shift']]
166170

167171
# Setting this as a static constant prevents
168172
# this resulting expression from leaking
@@ -250,7 +254,9 @@ def button_press_event(self, widget, event):
250254
del self.last_downclick[event.button] # we do not want to eat more than one event.
251255
return False # eat.
252256
self.last_downclick[event.button] = current_time
253-
FigureCanvasBase.button_press_event(self, x, y, event.button, dblclick=dblclick, guiEvent=event)
257+
modifiers = {prefix for key_mask, prefix in modifier_keys if event.state & key_mask}
258+
FigureCanvasBase.button_press_event(self, x, y, event.button,
259+
dblclick=dblclick, guiEvent=event, modifiers=modifiers)
254260
return False # finish event propagation?
255261

256262
def button_release_event(self, widget, event):

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase):
151151
65439 : 'dec',
152152
65421 : 'enter',
153153
}
154+
modifier_keys = [[gdk.MOD4_MASK, 'super'],
155+
[gdk.MOD1_MASK, 'alt'],
156+
[gdk.CONTROL_MASK, 'ctrl'],
157+
[gdk.SHIFT_MASK, 'shift']]
154158

155159
# Setting this as a static constant prevents
156160
# this resulting expression from leaking
@@ -212,7 +216,8 @@ def button_press_event(self, widget, event):
212216
x = event.x
213217
# flipy so y=0 is bottom of canvas
214218
y = self.get_allocation().height - event.y
215-
FigureCanvasBase.button_press_event(self, x, y, event.button, guiEvent=event)
219+
modifiers = {prefix for key_mask, prefix in modifier_keys if event.state & key_mask}
220+
FigureCanvasBase.button_press_event(self, x, y, event.button, guiEvent=event, modifiers=modifiers)
216221
return False # finish event propagation?
217222

218223
def button_release_event(self, widget, event):

lib/matplotlib/backends/backend_qt5.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,17 +237,23 @@ def mouseEventCoords(self, pos):
237237
def mousePressEvent(self, event):
238238
x, y = self.mouseEventCoords(event.pos())
239239
button = self.buttond.get(event.button())
240+
modifiers = {name for name, mod_key, qt_key in MODIFIER_KEYS
241+
if event.modifiers() & mod_key}
240242
if button is not None:
241243
FigureCanvasBase.button_press_event(self, x, y, button,
242-
guiEvent=event)
244+
guiEvent=event,
245+
modifiers=modifiers)
243246

244247
def mouseDoubleClickEvent(self, event):
245248
x, y = self.mouseEventCoords(event.pos())
246249
button = self.buttond.get(event.button())
250+
modifiers = {name for name, mod_key, qt_key in MODIFIER_KEYS
251+
if event.modifiers() & mod_key}
247252
if button is not None:
248253
FigureCanvasBase.button_press_event(self, x, y,
249254
button, dblclick=True,
250-
guiEvent=event)
255+
guiEvent=event,
256+
modifiers=modifiers)
251257

252258
def mouseMoveEvent(self, event):
253259
x, y = self.mouseEventCoords(event)

lib/matplotlib/backends/backend_tkagg.py

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,30 @@ def filter_destroy(evt):
209209
self.close_event()
210210
root.bind("<Destroy>", filter_destroy, "+")
211211

212+
# Dictionary for adding modifier keys to the key string.
213+
# Bit details originate from
214+
# http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
215+
# BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
216+
# BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
217+
# BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
218+
# In general, the modifier key is excluded from the modifier flag,
219+
# however this is not the case on "darwin", so double check that
220+
# we aren't adding repeat modifier flags to a modifier key.
221+
if sys.platform == 'win32':
222+
self.MOD_KEYS = [(17, 'alt', 'alt'),
223+
(2, 'ctrl', 'control'),
224+
]
225+
elif sys.platform == 'darwin':
226+
self.MOD_KEYS = [(3, 'super', 'super'),
227+
(4, 'alt', 'alt'),
228+
(2, 'ctrl', 'control'),
229+
]
230+
else:
231+
self.MOD_KEYS = [(6, 'super', 'super'),
232+
(3, 'alt', 'alt'),
233+
(2, 'ctrl', 'control'),
234+
]
235+
212236
self._master = master
213237
self._tkcanvas.focus_set()
214238

@@ -344,16 +368,18 @@ def button_press_event(self, event, dblclick=False):
344368
# flipy so y=0 is bottom of canvas
345369
y = self.figure.bbox.height - event.y
346370
num = getattr(event, 'num', None)
371+
modifiers = self._get_modifiers(event)
347372

348373
if sys.platform=='darwin':
349374
# 2 and 3 were reversed on the OSX platform I
350375
# tested under tkagg
351376
if num==2: num=3
352377
elif num==3: num=2
353378

354-
FigureCanvasBase.button_press_event(self, x, y, num, dblclick=dblclick, guiEvent=event)
379+
FigureCanvasBase.button_press_event(self, x, y, num, dblclick=dblclick,
380+
guiEvent=event, modifiers=modifiers)
355381

356-
def button_dblclick_event(self,event):
382+
def button_dblclick_event(self, event):
357383
self.button_press_event(event,dblclick=True)
358384

359385
def button_release_event(self, event):
@@ -404,37 +430,19 @@ def _get_key(self, event):
404430
else:
405431
key = None
406432

407-
# add modifier keys to the key string. Bit details originate from
408-
# http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
409-
# BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
410-
# BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
411-
# BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
412-
# In general, the modifier key is excluded from the modifier flag,
413-
# however this is not the case on "darwin", so double check that
414-
# we aren't adding repeat modifier flags to a modifier key.
415-
if sys.platform == 'win32':
416-
modifiers = [(17, 'alt', 'alt'),
417-
(2, 'ctrl', 'control'),
418-
]
419-
elif sys.platform == 'darwin':
420-
modifiers = [(3, 'super', 'super'),
421-
(4, 'alt', 'alt'),
422-
(2, 'ctrl', 'control'),
423-
]
424-
else:
425-
modifiers = [(6, 'super', 'super'),
426-
(3, 'alt', 'alt'),
427-
(2, 'ctrl', 'control'),
428-
]
429-
430433
if key is not None:
431434
# note, shift is not added to the keys as this is already accounted for
432-
for bitmask, prefix, key_name in modifiers:
435+
for bitmask, prefix, key_name in self.MOD_KEYS:
433436
if event.state & (1 << bitmask) and key_name not in key:
434437
key = '{0}+{1}'.format(prefix, key)
435438

436439
return key
437440

441+
def _get_modifiers(self, event):
442+
modifiers = {prefix for bitmask, prefix, key_name in self.MOD_KEYS
443+
if event.state & (1 << bitmask)}
444+
return modifiers
445+
438446
def key_press(self, event):
439447
key = self._get_key(event)
440448
FigureCanvasBase.key_press_event(self, key, guiEvent=event)

lib/matplotlib/backends/backend_wx.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,19 @@ def _get_key(self, evt):
10041004

10051005
return key
10061006

1007+
def _get_modifiers(self, evt):
1008+
"""
1009+
Extracts modifier keys from a wx MouseEvent
1010+
"""
1011+
MOD_KEYS = {"AltDown": "alt",
1012+
"CmdDown": "ctrl",
1013+
"ControlDown": "ctrl",
1014+
"ShiftDown": "shift"
1015+
}
1016+
modifiers = {prefix for attr, prefix in MOD_KEYS
1017+
if getattr(evt, attr)()}
1018+
return modifiers
1019+
10071020
def _onKeyDown(self, evt):
10081021
"""Capture key press."""
10091022
key = self._get_key(evt)
@@ -1034,16 +1047,21 @@ def _onRightButtonDown(self, evt):
10341047
y = self.figure.bbox.height - evt.GetY()
10351048
evt.Skip()
10361049
self._set_capture(True)
1037-
FigureCanvasBase.button_press_event(self, x, y, 3, guiEvent=evt)
1050+
modifiers = self._get_modifiers(evt)
1051+
FigureCanvasBase.button_press_event(self, x, y, 3,
1052+
guiEvent=evt,
1053+
modifiers=modifiers)
10381054

10391055
def _onRightButtonDClick(self, evt):
10401056
"""Start measuring on an axis."""
10411057
x = evt.GetX()
10421058
y = self.figure.bbox.height - evt.GetY()
10431059
evt.Skip()
10441060
self._set_capture(True)
1061+
modifiers = self._get_modifiers(evt)
10451062
FigureCanvasBase.button_press_event(self, x, y, 3,
1046-
dblclick=True, guiEvent=evt)
1063+
dblclick=True, guiEvent=evt,
1064+
modifiers=modifiers)
10471065

10481066
def _onRightButtonUp(self, evt):
10491067
"""End measuring on an axis."""
@@ -1059,16 +1077,21 @@ def _onLeftButtonDown(self, evt):
10591077
y = self.figure.bbox.height - evt.GetY()
10601078
evt.Skip()
10611079
self._set_capture(True)
1062-
FigureCanvasBase.button_press_event(self, x, y, 1, guiEvent=evt)
1080+
modifiers = self._get_modifiers(evt)
1081+
FigureCanvasBase.button_press_event(self, x, y, 1,
1082+
guiEvent=evt,
1083+
modifiers=modifiers)
10631084

10641085
def _onLeftButtonDClick(self, evt):
10651086
"""Start measuring on an axis."""
10661087
x = evt.GetX()
10671088
y = self.figure.bbox.height - evt.GetY()
10681089
evt.Skip()
10691090
self._set_capture(True)
1091+
modifiers = self._get_modifiers(evt)
10701092
FigureCanvasBase.button_press_event(self, x, y, 1,
1071-
dblclick=True, guiEvent=evt)
1093+
dblclick=True, guiEvent=evt,
1094+
modifiers=modifiers)
10721095

10731096
def _onLeftButtonUp(self, evt):
10741097
"""End measuring on an axis."""
@@ -1086,16 +1109,21 @@ def _onMiddleButtonDown(self, evt):
10861109
y = self.figure.bbox.height - evt.GetY()
10871110
evt.Skip()
10881111
self._set_capture(True)
1089-
FigureCanvasBase.button_press_event(self, x, y, 2, guiEvent=evt)
1112+
modifiers = self._get_modifiers(evt)
1113+
FigureCanvasBase.button_press_event(self, x, y, 2,
1114+
guiEvent=evt,
1115+
modifiers=modifiers)
10901116

10911117
def _onMiddleButtonDClick(self, evt):
10921118
"""Start measuring on an axis."""
10931119
x = evt.GetX()
10941120
y = self.figure.bbox.height - evt.GetY()
10951121
evt.Skip()
10961122
self._set_capture(True)
1123+
modifiers = self._get_modifiers(evt)
10971124
FigureCanvasBase.button_press_event(self, x, y, 2,
1098-
dblclick=True, guiEvent=evt)
1125+
dblclick=True, guiEvent=evt,
1126+
modifiers=modifiers)
10991127

11001128
def _onMiddleButtonUp(self, evt):
11011129
"""End measuring on an axis."""

src/_macosx.m

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,21 +2332,23 @@ - (void)mouseDown:(NSEvent *)event
23322332
location = [self convertPoint: location fromView: nil];
23332333
x = location.x * device_scale;
23342334
y = location.y * device_scale;
2335+
2336+
unsigned int modifier = [event modifierFlags];
2337+
PyObject* modifiers = PySet_New(NULL);
2338+
if (modifier & NSControlKeyMask)
2339+
PySet_Add(modifiers, PyString_FromString("ctrl"));
2340+
if (modifier & NSAlternateKeyMask)
2341+
PySet_Add(modifiers, PyString_FromString("alt"));
2342+
if (modifier & NSShiftKeyMask)
2343+
PySet_Add(modifiers, PyString_FromString("shift"));
2344+
if (modifier & NSCommandKeyMask)
2345+
PySet_Add(modifiers, PyString_FromString("cmd"));
2346+
23352347
switch ([event type])
23362348
{ case NSLeftMouseDown:
2337-
{ unsigned int modifier = [event modifierFlags];
2338-
if (modifier & NSControlKeyMask)
2339-
/* emulate a right-button click */
2340-
num = 3;
2341-
else if (modifier & NSAlternateKeyMask)
2342-
/* emulate a middle-button click */
2343-
num = 2;
2344-
else
2345-
{
2346-
num = 1;
2347-
if ([NSCursor currentCursor]==[NSCursor openHandCursor])
2349+
{ if ([NSCursor currentCursor]==[NSCursor openHandCursor])
23482350
[[NSCursor closedHandCursor] set];
2349-
}
2351+
num = 1;
23502352
break;
23512353
}
23522354
case NSOtherMouseDown: num = 2; break;
@@ -2357,7 +2359,8 @@ - (void)mouseDown:(NSEvent *)event
23572359
dblclick = 1;
23582360
}
23592361
gstate = PyGILState_Ensure();
2360-
result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
2362+
result = PyObject_CallMethod(canvas, "button_press_event", "iiii",
2363+
x, y, num, dblclick, Py_None, modifiers);
23612364
if(result)
23622365
Py_DECREF(result);
23632366
else
@@ -2445,7 +2448,18 @@ - (void)rightMouseDown:(NSEvent *)event
24452448
if ([event clickCount] == 2) {
24462449
dblclick = 1;
24472450
}
2448-
result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
2451+
unsigned int modifier = [event modifierFlags];
2452+
PyObject* modifiers = PySet_New(NULL);
2453+
if (modifier & NSControlKeyMask)
2454+
PySet_Add(modifiers, PyString_FromString("ctrl"));
2455+
if (modifier & NSAlternateKeyMask)
2456+
PySet_Add(modifiers, PyString_FromString("alt"));
2457+
if (modifier & NSShiftKeyMask)
2458+
PySet_Add(modifiers, PyString_FromString("shift"));
2459+
if (modifier & NSCommandKeyMask)
2460+
PySet_Add(modifiers, PyString_FromString("cmd"));
2461+
result = PyObject_CallMethod(canvas, "button_press_event", "iiii",
2462+
x, y, num, dblclick, Py_None, modifiers);
24492463
if(result)
24502464
Py_DECREF(result);
24512465
else

0 commit comments

Comments
 (0)