From 4ea04054b3a2f882a0066e66f474f16aadd4e23c Mon Sep 17 00:00:00 2001 From: Hankun Zhao Date: Sat, 12 Aug 2017 23:06:14 -0700 Subject: [PATCH] Added mouse event modifiers --- lib/matplotlib/backend_bases.py | 30 +++++++----- lib/matplotlib/backends/backend_gtk.py | 8 +++- lib/matplotlib/backends/backend_gtk3.py | 7 ++- lib/matplotlib/backends/backend_qt5.py | 10 +++- lib/matplotlib/backends/backend_tkagg.py | 60 ++++++++++++++---------- lib/matplotlib/backends/backend_wx.py | 40 +++++++++++++--- src/_macosx.m | 42 +++++++++++------ 7 files changed, 136 insertions(+), 61 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 3d0e9f583319..869b887784c4 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -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:: @@ -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' @@ -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 " + @@ -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. @@ -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): diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index b3f7ec504bca..c518400c5cb6 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -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 @@ -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): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a5f223a38753..cd4a4ad1f151 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -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 @@ -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): diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 2345d2fe1640..2864e011c119 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -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) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index f6190d4f369e..7694686d8c2e 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -209,6 +209,30 @@ def filter_destroy(evt): self.close_event() root.bind("", 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() @@ -344,6 +368,7 @@ 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 @@ -351,9 +376,10 @@ def button_press_event(self, event, dblclick=False): 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): @@ -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) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index f7df0707e637..00a8ad95ff23 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -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) @@ -1034,7 +1047,10 @@ 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.""" @@ -1042,8 +1058,10 @@ def _onRightButtonDClick(self, evt): 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.""" @@ -1059,7 +1077,10 @@ 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.""" @@ -1067,8 +1088,10 @@ def _onLeftButtonDClick(self, evt): 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.""" @@ -1086,7 +1109,10 @@ 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.""" @@ -1094,8 +1120,10 @@ def _onMiddleButtonDClick(self, evt): 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.""" diff --git a/src/_macosx.m b/src/_macosx.m index 1e5c49c39cf5..23985ce5ba4f 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -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; @@ -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 @@ -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