Skip to content

added modifier key tracking in MouseEvents #6159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,10 +1606,15 @@ class MouseEvent(LocationEvent):
key : None, or str
the key depressed when the mouse event triggered (see
:class:`KeyEvent`)
Note: .key is only processed if the FigureCanvas received keyboard
focus. For safer retrieval of modifier keys use .modifiers instead

step : scalar
number of scroll steps (positive for 'up', negative for 'down')

modifiers : set
modifier keys depressed when mouse event is triggered

Examples
--------
Usage::
Expand All @@ -1620,17 +1625,18 @@ def on_press(event):
cid = fig.canvas.mpl_connect('button_press_event', on_press)

"""
x = None # x position - pixels from left of canvas
y = None # y position - pixels from right of canvas
button = None # button pressed None, 1, 2, 3
dblclick = None # whether or not the event is the result of a double click
inaxes = None # the Axes instance if mouse us over axes
xdata = None # x coord of mouse in data coords
ydata = None # y coord of mouse in data coords
step = None # scroll steps for scroll events
x = None # x position - pixels from left of canvas
y = None # y position - pixels from right of canvas
button = None # button pressed None, 1, 2, 3
dblclick = None # whether or not the event is the result of a double click
inaxes = None # the Axes instance if mouse us over axes
xdata = None # x coord of mouse in data coords
ydata = None # y coord of mouse in data coords
step = None # scroll steps for scroll events
modifiers = None # depressed modifier keys

def __init__(self, name, canvas, x, y, button=None, key=None,
step=0, dblclick=False, guiEvent=None):
step=0, dblclick=False, guiEvent=None, modifiers=None):
"""
x, y in figure coords, 0,0 = bottom, left
button pressed None, 1, 2, 3, 'up', 'down'
Expand All @@ -1640,6 +1646,7 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
self.key = key
self.step = step
self.dblclick = dblclick
self.modifiers = modifiers

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

def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
def button_press_event(self, x, y, button, dblclick=False, guiEvent=None, modifiers=None):
"""
Backend derived classes should call this function on any mouse
button press. x,y are the canvas coords: 0,0 is lower, left.
Expand All @@ -1932,8 +1939,9 @@ def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
"""
self._button = button
s = 'button_press_event'

mouseevent = MouseEvent(s, self, x, y, button, self._key,
dblclick=dblclick, guiEvent=guiEvent)
dblclick=dblclick, guiEvent=guiEvent, modifiers=modifiers)
self.callbacks.process(s, mouseevent)

def button_release_event(self, x, y, button, guiEvent=None):
Expand Down
8 changes: 7 additions & 1 deletion lib/matplotlib/backends/backend_gtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ class FigureCanvasGTK (gtk.DrawingArea, FigureCanvasBase):
65406 : 'alt',
65289 : 'tab',
}
modifier_keys = [[gdk.MOD4_MASK, 'super'],
[gdk.MOD1_MASK, 'alt'],
[gdk.CONTROL_MASK, 'ctrl'],
[gdk.SHIFT_MASK, 'shift']]

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

def button_release_event(self, widget, event):
Expand Down
7 changes: 6 additions & 1 deletion lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase):
65439 : 'dec',
65421 : 'enter',
}
modifier_keys = [[gdk.MOD4_MASK, 'super'],
[gdk.MOD1_MASK, 'alt'],
[gdk.CONTROL_MASK, 'ctrl'],
[gdk.SHIFT_MASK, 'shift']]

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

def button_release_event(self, widget, event):
Expand Down
10 changes: 8 additions & 2 deletions lib/matplotlib/backends/backend_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,23 @@ def mouseEventCoords(self, pos):
def mousePressEvent(self, event):
x, y = self.mouseEventCoords(event.pos())
button = self.buttond.get(event.button())
modifiers = {name for name, mod_key, qt_key in MODIFIER_KEYS
if event.modifiers() & mod_key}
if button is not None:
FigureCanvasBase.button_press_event(self, x, y, button,
guiEvent=event)
guiEvent=event,
modifiers=modifiers)

def mouseDoubleClickEvent(self, event):
x, y = self.mouseEventCoords(event.pos())
button = self.buttond.get(event.button())
modifiers = {name for name, mod_key, qt_key in MODIFIER_KEYS
if event.modifiers() & mod_key}
if button is not None:
FigureCanvasBase.button_press_event(self, x, y,
button, dblclick=True,
guiEvent=event)
guiEvent=event,
modifiers=modifiers)

def mouseMoveEvent(self, event):
x, y = self.mouseEventCoords(event)
Expand Down
60 changes: 34 additions & 26 deletions lib/matplotlib/backends/backend_tkagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,30 @@ def filter_destroy(evt):
self.close_event()
root.bind("<Destroy>", filter_destroy, "+")

# Dictionary for adding 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;
# BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
# BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
# 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':
self.MOD_KEYS = [(17, 'alt', 'alt'),
(2, 'ctrl', 'control'),
]
elif sys.platform == 'darwin':
self.MOD_KEYS = [(3, 'super', 'super'),
(4, 'alt', 'alt'),
(2, 'ctrl', 'control'),
]
else:
self.MOD_KEYS = [(6, 'super', 'super'),
(3, 'alt', 'alt'),
(2, 'ctrl', 'control'),
]

self._master = master
self._tkcanvas.focus_set()

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

if sys.platform=='darwin':
# 2 and 3 were reversed on the OSX platform I
# tested under tkagg
if num==2: num=3
elif num==3: num=2

FigureCanvasBase.button_press_event(self, x, y, num, dblclick=dblclick, guiEvent=event)
FigureCanvasBase.button_press_event(self, x, y, num, dblclick=dblclick,
guiEvent=event, modifiers=modifiers)

def button_dblclick_event(self,event):
def button_dblclick_event(self, event):
self.button_press_event(event,dblclick=True)

def button_release_event(self, event):
Expand Down Expand Up @@ -404,37 +430,19 @@ def _get_key(self, event):
else:
key = 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;
# BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
# BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
# 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 = [(17, 'alt', 'alt'),
(2, 'ctrl', 'control'),
]
elif sys.platform == 'darwin':
modifiers = [(3, 'super', 'super'),
(4, 'alt', 'alt'),
(2, 'ctrl', 'control'),
]
else:
modifiers = [(6, 'super', 'super'),
(3, 'alt', 'alt'),
(2, 'ctrl', 'control'),
]

if key is not None:
# note, shift is not added to the keys as this is already accounted for
for bitmask, prefix, key_name in modifiers:
for bitmask, prefix, key_name in self.MOD_KEYS:
if event.state & (1 << bitmask) and key_name not in key:
key = '{0}+{1}'.format(prefix, key)

return key

def _get_modifiers(self, event):
modifiers = {prefix for bitmask, prefix, key_name in self.MOD_KEYS
if event.state & (1 << bitmask)}
return modifiers

def key_press(self, event):
key = self._get_key(event)
FigureCanvasBase.key_press_event(self, key, guiEvent=event)
Expand Down
40 changes: 34 additions & 6 deletions lib/matplotlib/backends/backend_wx.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,19 @@ def _get_key(self, evt):

return key

def _get_modifiers(self, evt):
"""
Extracts modifier keys from a wx MouseEvent
"""
MOD_KEYS = {"AltDown": "alt",
"CmdDown": "ctrl",
"ControlDown": "ctrl",
"ShiftDown": "shift"
}
modifiers = {prefix for attr, prefix in MOD_KEYS
if getattr(evt, attr)()}
return modifiers

def _onKeyDown(self, evt):
"""Capture key press."""
key = self._get_key(evt)
Expand Down Expand Up @@ -1034,16 +1047,21 @@ def _onRightButtonDown(self, evt):
y = self.figure.bbox.height - evt.GetY()
evt.Skip()
self._set_capture(True)
FigureCanvasBase.button_press_event(self, x, y, 3, guiEvent=evt)
modifiers = self._get_modifiers(evt)
FigureCanvasBase.button_press_event(self, x, y, 3,
guiEvent=evt,
modifiers=modifiers)

def _onRightButtonDClick(self, evt):
"""Start measuring on an axis."""
x = evt.GetX()
y = self.figure.bbox.height - evt.GetY()
evt.Skip()
self._set_capture(True)
modifiers = self._get_modifiers(evt)
FigureCanvasBase.button_press_event(self, x, y, 3,
dblclick=True, guiEvent=evt)
dblclick=True, guiEvent=evt,
modifiers=modifiers)

def _onRightButtonUp(self, evt):
"""End measuring on an axis."""
Expand All @@ -1059,16 +1077,21 @@ def _onLeftButtonDown(self, evt):
y = self.figure.bbox.height - evt.GetY()
evt.Skip()
self._set_capture(True)
FigureCanvasBase.button_press_event(self, x, y, 1, guiEvent=evt)
modifiers = self._get_modifiers(evt)
FigureCanvasBase.button_press_event(self, x, y, 1,
guiEvent=evt,
modifiers=modifiers)

def _onLeftButtonDClick(self, evt):
"""Start measuring on an axis."""
x = evt.GetX()
y = self.figure.bbox.height - evt.GetY()
evt.Skip()
self._set_capture(True)
modifiers = self._get_modifiers(evt)
FigureCanvasBase.button_press_event(self, x, y, 1,
dblclick=True, guiEvent=evt)
dblclick=True, guiEvent=evt,
modifiers=modifiers)

def _onLeftButtonUp(self, evt):
"""End measuring on an axis."""
Expand All @@ -1086,16 +1109,21 @@ def _onMiddleButtonDown(self, evt):
y = self.figure.bbox.height - evt.GetY()
evt.Skip()
self._set_capture(True)
FigureCanvasBase.button_press_event(self, x, y, 2, guiEvent=evt)
modifiers = self._get_modifiers(evt)
FigureCanvasBase.button_press_event(self, x, y, 2,
guiEvent=evt,
modifiers=modifiers)

def _onMiddleButtonDClick(self, evt):
"""Start measuring on an axis."""
x = evt.GetX()
y = self.figure.bbox.height - evt.GetY()
evt.Skip()
self._set_capture(True)
modifiers = self._get_modifiers(evt)
FigureCanvasBase.button_press_event(self, x, y, 2,
dblclick=True, guiEvent=evt)
dblclick=True, guiEvent=evt,
modifiers=modifiers)

def _onMiddleButtonUp(self, evt):
"""End measuring on an axis."""
Expand Down
42 changes: 28 additions & 14 deletions src/_macosx.m
Original file line number Diff line number Diff line change
Expand Up @@ -2332,21 +2332,23 @@ - (void)mouseDown:(NSEvent *)event
location = [self convertPoint: location fromView: nil];
x = location.x * device_scale;
y = location.y * device_scale;

unsigned int modifier = [event modifierFlags];
PyObject* modifiers = PySet_New(NULL);
if (modifier & NSControlKeyMask)
PySet_Add(modifiers, PyString_FromString("ctrl"));
if (modifier & NSAlternateKeyMask)
PySet_Add(modifiers, PyString_FromString("alt"));
if (modifier & NSShiftKeyMask)
PySet_Add(modifiers, PyString_FromString("shift"));
if (modifier & NSCommandKeyMask)
PySet_Add(modifiers, PyString_FromString("cmd"));

switch ([event type])
{ case NSLeftMouseDown:
{ unsigned int modifier = [event modifierFlags];
if (modifier & NSControlKeyMask)
/* emulate a right-button click */
num = 3;
else if (modifier & NSAlternateKeyMask)
/* emulate a middle-button click */
num = 2;
else
{
num = 1;
if ([NSCursor currentCursor]==[NSCursor openHandCursor])
{ if ([NSCursor currentCursor]==[NSCursor openHandCursor])
[[NSCursor closedHandCursor] set];
}
num = 1;
break;
}
case NSOtherMouseDown: num = 2; break;
Expand All @@ -2357,7 +2359,8 @@ - (void)mouseDown:(NSEvent *)event
dblclick = 1;
}
gstate = PyGILState_Ensure();
result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
result = PyObject_CallMethod(canvas, "button_press_event", "iiii",
x, y, num, dblclick, Py_None, modifiers);
if(result)
Py_DECREF(result);
else
Expand Down Expand Up @@ -2445,7 +2448,18 @@ - (void)rightMouseDown:(NSEvent *)event
if ([event clickCount] == 2) {
dblclick = 1;
}
result = PyObject_CallMethod(canvas, "button_press_event", "iiii", x, y, num, dblclick);
unsigned int modifier = [event modifierFlags];
PyObject* modifiers = PySet_New(NULL);
if (modifier & NSControlKeyMask)
PySet_Add(modifiers, PyString_FromString("ctrl"));
if (modifier & NSAlternateKeyMask)
PySet_Add(modifiers, PyString_FromString("alt"));
if (modifier & NSShiftKeyMask)
PySet_Add(modifiers, PyString_FromString("shift"));
if (modifier & NSCommandKeyMask)
PySet_Add(modifiers, PyString_FromString("cmd"));
result = PyObject_CallMethod(canvas, "button_press_event", "iiii",
x, y, num, dblclick, Py_None, modifiers);
if(result)
Py_DECREF(result);
else
Expand Down