From ed4d33858ba5950111e7dc18568695104fb61ab0 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 23 Apr 2012 17:39:32 +0100 Subject: [PATCH 01/12] added a quit shortcut key for gui backends and fixed "toggle full screen" shortcut key which was broken. --- lib/matplotlib/backend_bases.py | 13 ++++++++----- lib/matplotlib/backends/backend_gtk.py | 2 +- lib/matplotlib/backends/backend_tkagg.py | 17 +++++++++-------- lib/matplotlib/rcsetup.py | 1 + matplotlibrc.template | 1 + 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 7dc841db4aa4..0c904c53aaf6 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2274,9 +2274,6 @@ def key_press_handler(event, canvas, toolbar=None): """ # these bindings happen whether you are over an axes or not - #if event.key == 'q': - # self.destroy() # how cruel to have to destroy oneself! - # return if event.key is None: return @@ -2289,6 +2286,7 @@ def key_press_handler(event, canvas, toolbar=None): pan_keys = rcParams['keymap.pan'] zoom_keys = rcParams['keymap.zoom'] save_keys = rcParams['keymap.save'] + quit_keys = rcParams['keymap.quit'] grid_keys = rcParams['keymap.grid'] toggle_yscale_keys = rcParams['keymap.yscale'] toggle_xscale_keys = rcParams['keymap.xscale'] @@ -2298,6 +2296,10 @@ def key_press_handler(event, canvas, toolbar=None): if event.key in fullscreen_keys: canvas.manager.full_screen_toggle() + # quit the figure (defaut key 'q') + if event.key in quit_keys: + Gcf.destroy_fig(canvas.figure) + if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') if event.key in home_keys: @@ -2322,7 +2324,8 @@ def key_press_handler(event, canvas, toolbar=None): if event.inaxes is None: return - # the mouse has to be over an axes to trigger these + # these bindings require the mouse to be over an axes to trigger + # switching on/off a grid in current axes (default key 'g') if event.key in grid_keys: event.inaxes.grid() @@ -2387,7 +2390,7 @@ def __init__(self, canvas, num): def destroy(self): pass - def full_screen_toggle (self): + def full_screen_toggle(self): pass def resize(self, w, h): diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index e4f6f31742f1..a96d5a5cbaf0 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -592,7 +592,7 @@ def show(self): # show the figure window self.window.show() - def full_screen_toggle (self): + def full_screen_toggle(self): self._full_screen_flag = not self._full_screen_flag if self._full_screen_flag: self.window.fullscreen() diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 8a78a44a9d81..1ee78a9a7b71 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -457,7 +457,7 @@ def __init__(self, canvas, num, window): self.window.wm_title("Figure %d" % num) self.canvas = canvas self._num = num - t1,t2,w,h = canvas.figure.bbox.bounds + _, _, w, h = canvas.figure.bbox.bounds w, h = int(w), int(h) self.window.minsize(int(w*3/4),int(h*3/4)) if matplotlib.rcParams['toolbar']=='classic': @@ -476,12 +476,9 @@ def notify_axes_change(fig): if self.toolbar != None: self.toolbar.update() self.canvas.figure.add_axobserver(notify_axes_change) - - # attach a show method to the figure for pylab ease of use self.canvas.figure.show = lambda *args: self.show() - def resize(self, width, height=None): # before 09-12-22, the resize method takes a single *event* # parameter. On the other hand, the resize method of other @@ -499,7 +496,6 @@ def resize(self, width, height=None): self.toolbar.configure(width=width) - def show(self): """ this function doesn't segfault but causes the @@ -518,7 +514,6 @@ def destroy(*args): self.canvas.draw_idle() self._shown = True - def destroy(self, *args): if self.window is not None: #self.toolbar.destroy() @@ -533,6 +528,12 @@ def destroy(self, *args): def set_window_title(self, title): self.window.wm_title(title) + def full_screen_toggle(self): + # cross platform way of maximizing a tk window + # http://devourer09.blogspot.co.uk/2009/07/maximizing-tkinter-app.html + self.window.attributes('-zoomed', '1') + + class AxisMenu: def __init__(self, master, naxes): self._master = master @@ -597,7 +598,7 @@ def select_all(self): class NavigationToolbar(Tk.Frame): """ - Public attriubutes + Public attributes canvas - the FigureCanvas (gtk.DrawingArea) win - the gtk.Window @@ -722,7 +723,7 @@ def update(self): class NavigationToolbar2TkAgg(NavigationToolbar2, Tk.Frame): """ - Public attriubutes + Public attributes canvas - the FigureCanvas (gtk.DrawingArea) win - the gtk.Window diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 6df1d4b081f2..17e7ea0729f8 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -582,6 +582,7 @@ def __call__(self, s): 'keymap.pan' : ['p', validate_stringlist], 'keymap.zoom' : ['o', validate_stringlist], 'keymap.save' : ['s', validate_stringlist], + 'keymap.quit' : ['q', validate_stringlist], 'keymap.grid' : ['g', validate_stringlist], 'keymap.yscale' : ['l', validate_stringlist], 'keymap.xscale' : [['k', 'L'], validate_stringlist], diff --git a/matplotlibrc.template b/matplotlibrc.template index 6ebaac28bae3..16f2657f5026 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -410,6 +410,7 @@ text.hinting_factor : 8 # Specifies the amount of softness for hinting in the #keymap.pan : p # pan mnemonic #keymap.zoom : o # zoom mnemonic #keymap.save : s # saving current figure +#keymap.quit : q # close the current figure #keymap.grid : g # switching on/off a grid in current axes #keymap.yscale : l # toggle scaling of y-axes ('log'/'linear') #keymap.xscale : L, k # toggle scaling of x-axes ('log'/'linear') From c887139224ac30ceb2c77d249acf80da8ac9713e Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 23 Apr 2012 18:25:34 +0100 Subject: [PATCH 02/12] Backend factorisation for tooltip sharing. --- lib/matplotlib/backend_bases.py | 20 +++++ lib/matplotlib/backends/backend_gtk.py | 15 +--- lib/matplotlib/backends/backend_gtk3.py | 15 +--- lib/matplotlib/backends/backend_qt.py | 18 +--- lib/matplotlib/backends/backend_qt4.py | 25 ++---- lib/matplotlib/backends/backend_tkagg.py | 107 ++++++++++++++++------- 6 files changed, 107 insertions(+), 93 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 0c904c53aaf6..eb9b0e14c2b3 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2467,6 +2467,26 @@ class NavigationToolbar2(object): That's it, we'll do the rest! """ + + # list of toolitems to add to the toolbar, format is: + # ( + # text, # the text of the button (often not visible to users) + # tooltip_text, # the tooltip shown on hover (where possible) + # image_file, # name of the image for the button (without the extension) + # name_of_method, # name of the method in NavigationToolbar2 to call + # ) + toolitems = ( + ('Home', 'Reset original view', 'home', 'home'), + ('Back', 'Back to previous view', 'back', 'back'), + ('Forward', 'Forward to next view', 'forward', 'forward'), + (None, None, None, None), + ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'), + ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'), + (None, None, None, None), + ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'), + ('Save', 'Save the figure', 'filesave', 'save_figure'), + ) + def __init__(self, canvas): self.canvas = canvas diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index a96d5a5cbaf0..9ec97b64dc89 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -624,19 +624,6 @@ def resize(self, width, height): class NavigationToolbar2GTK(NavigationToolbar2, gtk.Toolbar): - # list of toolitems to add to the toolbar, format is: - # text, tooltip_text, image_file, callback(str) - toolitems = ( - ('Home', 'Reset original view', 'home.png', 'home'), - ('Back', 'Back to previous view','back.png', 'back'), - ('Forward', 'Forward to next view','forward.png', 'forward'), - ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'), - ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'), - (None, None, None, None), - ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'), - ('Save', 'Save the figure','filesave.png', 'save_figure'), - ) - def __init__(self, canvas, window): self.win = window gtk.Toolbar.__init__(self) @@ -704,7 +691,7 @@ def _init_toolbar2_4(self): if text is None: self.insert( gtk.SeparatorToolItem(), -1 ) continue - fname = os.path.join(basedir, image_file) + fname = os.path.join(basedir, image_file + '.png') image = gtk.Image() image.set_from_file(fname) tbutton = gtk.ToolButton(image, text) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index b89564639789..a69cbcfff316 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -453,19 +453,6 @@ def resize(self, width, height): class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): - # list of toolitems to add to the toolbar, format is: - # text, tooltip_text, image_file, callback(str) - toolitems = ( - ('Home', 'Reset original view', 'home.png', 'home'), - ('Back', 'Back to previous view','back.png', 'back'), - ('Forward', 'Forward to next view','forward.png', 'forward'), - ('Pan', 'Pan axes with left mouse, zoom with right', 'move.png','pan'), - ('Zoom', 'Zoom to rectangle','zoom_to_rect.png', 'zoom'), - (None, None, None, None), - ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'), - ('Save', 'Save the figure','filesave.png', 'save_figure'), - ) - def __init__(self, canvas, window): self.win = window GObject.GObject.__init__(self) @@ -516,7 +503,7 @@ def _init_toolbar(self): if text is None: self.insert( Gtk.SeparatorToolItem(), -1 ) continue - fname = os.path.join(basedir, image_file) + fname = os.path.join(basedir, image_file + '.png') image = Gtk.Image() image.set_from_file(fname) tbutton = Gtk.ToolButton() diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 343bb3f033c1..21ab5fd4614a 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -295,20 +295,6 @@ def set_window_title(self, title): self.window.setCaption(title) class NavigationToolbar2QT( NavigationToolbar2, qt.QWidget ): - # list of toolitems to add to the toolbar, format is: - # text, tooltip_text, image_file, callback(str) - toolitems = ( - ('Home', 'Reset original view', 'home.ppm', 'home'), - ('Back', 'Back to previous view','back.ppm', 'back'), - ('Forward', 'Forward to next view','forward.ppm', 'forward'), - (None, None, None, None), - ('Pan', 'Pan axes with left mouse, zoom with right', 'move.ppm', 'pan'), - ('Zoom', 'Zoom to rectangle','zoom_to_rect.ppm', 'zoom'), - (None, None, None, None), - ('Subplots', 'Configure subplots','subplots.png', 'configure_subplots'), - ('Save', 'Save the figure','filesave.ppm', 'save_figure'), - ) - def __init__( self, canvas, parent ): self.canvas = canvas self.buttons = {} @@ -329,8 +315,8 @@ def _init_toolbar( self ): self.layout.addSpacing( 8 ) continue - fname = os.path.join( basedir, image_file ) - image = qt.QPixmap() + fname = os.path.join(basedir, image_file + '.ppm') + image = qt.QPixmap() image.load( fname ) button = qt.QPushButton( qt.QIconSet( image ), "", self ) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index b05138713fa2..1771503b954a 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -441,22 +441,15 @@ def _icon(self, name): def _init_toolbar(self): self.basedir = os.path.join(matplotlib.rcParams[ 'datapath' ],'images') - a = self.addAction(self._icon('home.png'), 'Home', self.home) - a.setToolTip('Reset original view') - a = self.addAction(self._icon('back.png'), 'Back', self.back) - a.setToolTip('Back to previous view') - a = self.addAction(self._icon('forward.png'), 'Forward', self.forward) - a.setToolTip('Forward to next view') - self.addSeparator() - a = self.addAction(self._icon('move.png'), 'Pan', self.pan) - a.setToolTip('Pan axes with left mouse, zoom with right') - a = self.addAction(self._icon('zoom_to_rect.png'), 'Zoom', self.zoom) - a.setToolTip('Zoom to rectangle') - self.addSeparator() - a = self.addAction(self._icon('subplots.png'), 'Subplots', - self.configure_subplots) - a.setToolTip('Configure subplots') - + # XXX pelson: use NavigationToolbar2.toolitems + for text, tooltip_text, image_file, callback in self.toolitems: + if text is None: + self.addSeparator() + else: + a = self.addAction(self._icon(image_file + '.png'), text, getattr(self, callback)) + if tooltip_text is not None: + a.setToolTip(tooltip_text) + if figureoptions is not None: a = self.addAction(self._icon("qt4_editor_options.png"), 'Customize', self.edit_parameters) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 1ee78a9a7b71..0862f77ec5b2 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -596,6 +596,7 @@ def select_all(self): a.set(1) self.set_active() + class NavigationToolbar(Tk.Frame): """ Public attributes @@ -626,39 +627,39 @@ def __init__(self, canvas, window): self.update() # Make axes menu self.bLeft = self._Button( - text="Left", file="stock_left.ppm", + text="Left", file="stock_left", command=lambda x=-1: self.panx(x)) self.bRight = self._Button( - text="Right", file="stock_right.ppm", + text="Right", file="stock_right", command=lambda x=1: self.panx(x)) self.bZoomInX = self._Button( - text="ZoomInX",file="stock_zoom-in.ppm", + text="ZoomInX",file="stock_zoom-in", command=lambda x=1: self.zoomx(x)) self.bZoomOutX = self._Button( - text="ZoomOutX", file="stock_zoom-out.ppm", + text="ZoomOutX", file="stock_zoom-out", command=lambda x=-1: self.zoomx(x)) self.bUp = self._Button( - text="Up", file="stock_up.ppm", + text="Up", file="stock_up", command=lambda y=1: self.pany(y)) self.bDown = self._Button( - text="Down", file="stock_down.ppm", + text="Down", file="stock_down", command=lambda y=-1: self.pany(y)) self.bZoomInY = self._Button( - text="ZoomInY", file="stock_zoom-in.ppm", + text="ZoomInY", file="stock_zoom-in", command=lambda y=1: self.zoomy(y)) self.bZoomOutY = self._Button( - text="ZoomOutY",file="stock_zoom-out.ppm", + text="ZoomOutY",file="stock_zoom-out", command=lambda y=-1: self.zoomy(y)) self.bSave = self._Button( - text="Save", file="stock_save_as.ppm", + text="Save", file="stock_save_as", command=self.save_figure) self.pack(side=Tk.BOTTOM, fill=Tk.X) @@ -763,9 +764,9 @@ def release(self, event): def set_cursor(self, cursor): self.window.configure(cursor=cursord[cursor]) - def _Button(self, text, file, command): - file = os.path.join(rcParams['datapath'], 'images', file) - im = Tk.PhotoImage(master=self, file=file) + def _Button(self, text, file, command, extension='.ppm'): + img_file = os.path.join(rcParams['datapath'], 'images', file + extension) + im = Tk.PhotoImage(master=self, file=img_file) b = Tk.Button( master=self, text=text, padx=2, pady=2, image=im, command=command) b._ntimage = im @@ -781,27 +782,16 @@ def _init_toolbar(self): self.update() # Make axes menu - self.bHome = self._Button( text="Home", file="home.ppm", - command=self.home) - - self.bBack = self._Button( text="Back", file="back.ppm", - command = self.back) - - self.bForward = self._Button(text="Forward", file="forward.ppm", - command = self.forward) - - self.bPan = self._Button( text="Pan", file="move.ppm", - command = self.pan) - - self.bZoom = self._Button( text="Zoom", - file="zoom_to_rect.ppm", - command = self.zoom) - - self.bsubplot = self._Button( text="Configure Subplots", file="subplots.ppm", - command = self.configure_subplots) - - self.bsave = self._Button( text="Save", file="filesave.ppm", - command = self.save_figure) + for text, tooltip_text, image_file, callback in self.toolitems: + if text is None: + # spacer, unhandled in Tk + pass + else: + button = self._Button(text=text, file=image_file, + command=getattr(self, callback)) + if tooltip_text is not None: + ToolTip.createToolTip(button, tooltip_text) + self.message = Tk.StringVar(master=self) self._message_label = Tk.Label(master=self, textvariable=self.message) self._message_label.pack(side=Tk.RIGHT) @@ -879,3 +869,54 @@ def dynamic_update(self): FigureManager = FigureManagerTkAgg + + +class ToolTip(object): + """ + Tooltip recipe from + http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387 + """ + @staticmethod + def createToolTip(widget, text): + toolTip = ToolTip(widget) + def enter(event): + toolTip.showtip(text) + def leave(event): + toolTip.hidetip() + widget.bind('', enter) + widget.bind('', leave) + + def __init__(self, widget): + self.widget = widget + self.tipwindow = None + self.id = None + self.x = self.y = 0 + + def showtip(self, text): + "Display text in tooltip window" + self.text = text + if self.tipwindow or not self.text: + return + x, y, _, _ = self.widget.bbox("insert") + x = x + self.widget.winfo_rootx() + 27 + y = y + self.widget.winfo_rooty() + self.tipwindow = tw = Tk.Toplevel(self.widget) + tw.wm_overrideredirect(1) + tw.wm_geometry("+%d+%d" % (x, y)) + try: + # For Mac OS + tw.tk.call("::tk::unsupported::MacWindowStyle", + "style", tw._w, + "help", "noActivates") + except Tk.TclError: + pass + label = Tk.Label(tw, text=self.text, justify=Tk.LEFT, + background="#ffffe0", relief=Tk.SOLID, borderwidth=1, + ) + label.pack(ipadx=1) + + def hidetip(self): + tw = self.tipwindow + self.tipwindow = None + if tw: + tw.destroy() \ No newline at end of file From 507e9aa75223261ca0c141056bda0ef109854f1a Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 23 Apr 2012 18:30:17 +0100 Subject: [PATCH 03/12] removed todo --- lib/matplotlib/backends/backend_qt4.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 1771503b954a..f2db06e518c6 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -441,7 +441,6 @@ def _icon(self, name): def _init_toolbar(self): self.basedir = os.path.join(matplotlib.rcParams[ 'datapath' ],'images') - # XXX pelson: use NavigationToolbar2.toolitems for text, tooltip_text, image_file, callback in self.toolitems: if text is None: self.addSeparator() From dc77096c23d7ee153c712d63d07dfa16be645ae0 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 23 Apr 2012 23:04:03 +0100 Subject: [PATCH 04/12] Fullscreen rather than maximize in Tk. Added fullscreen support to qt4 backend. --- lib/matplotlib/backends/backend_qt4.py | 7 ++++++- lib/matplotlib/backends/backend_tkagg.py | 7 +++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index f2db06e518c6..d5d739aded2d 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -383,6 +383,12 @@ def _show_message(self,s): # Fixes a PySide segfault. self.window.statusBar().showMessage(s) + def full_screen_toggle(self): + if self.window.isFullScreen(): + self.window.showNormal() + else: + self.window.showFullScreen() + def _widgetclosed( self ): if self.window._destroying: return self.window._destroying = True @@ -394,7 +400,6 @@ def _widgetclosed( self ): # Gcf can get destroyed before the Gcf.destroy # line is run, leading to a useless AttributeError. - def _get_toolbar(self, canvas, parent): # must be inited after the window, drawingArea and figure # attrs are set diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 0862f77ec5b2..64aeae19123f 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -529,9 +529,8 @@ def set_window_title(self, title): self.window.wm_title(title) def full_screen_toggle(self): - # cross platform way of maximizing a tk window - # http://devourer09.blogspot.co.uk/2009/07/maximizing-tkinter-app.html - self.window.attributes('-zoomed', '1') + is_fullscreen = bool(self.window.attributes('-fullscreen')) + self.window.attributes('-fullscreen', not is_fullscreen) class AxisMenu: @@ -919,4 +918,4 @@ def hidetip(self): tw = self.tipwindow self.tipwindow = None if tw: - tw.destroy() \ No newline at end of file + tw.destroy() From 3bbbd0a27a56f33dbc619e4c6806b0844b7d5e55 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 21 May 2012 14:24:17 +0100 Subject: [PATCH 05/12] added modifier key support to the tk backend (i.e. ctrl+key). Additional support will need to be added for qt, gtk and wx backends. --- lib/matplotlib/backend_bases.py | 6 +++--- lib/matplotlib/backends/backend_tkagg.py | 15 +++++++++++++-- lib/matplotlib/rcsetup.py | 6 +++--- matplotlibrc.template | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index eb9b0e14c2b3..060a9555e89e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1400,7 +1400,7 @@ class KeyEvent(LocationEvent): attributes, the following attributes are defined: *key* - the key pressed: None, chr(range(255), shift, win, or control + the key pressed: None, chr(range(255)), shift, win, or control This interface may change slightly when better support for modifier keys is included. @@ -2296,10 +2296,10 @@ def key_press_handler(event, canvas, toolbar=None): if event.key in fullscreen_keys: canvas.manager.full_screen_toggle() - # quit the figure (defaut key 'q') + # quit the figure (defaut key 'ctrl+w') if event.key in quit_keys: Gcf.destroy_fig(canvas.figure) - + if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') if event.key in home_keys: diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 64aeae19123f..027caafe82ba 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -399,13 +399,24 @@ def _get_key(self, event): val = event.keysym_num if val in self.keyvald: key = self.keyvald[val] - elif val<256: + elif val < 256: key = chr(val) 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; + if key is not None: + # note, shift is not added to the keys as this is already accounted for + for bitmask, prefix in [(2, 'ctrl'), (3, 'alt')]: + if event.state & (1 << bitmask): + key = '{}+{}'.format(prefix, key) + return key - def key_press(self, event): key = self._get_key(event) FigureCanvasBase.key_press_event(self, key, guiEvent=event) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 17e7ea0729f8..035d11e48c8c 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -575,14 +575,14 @@ def __call__(self, s): # recommend about 20000 to # enable. Experimental. # key-mappings - 'keymap.fullscreen' : ['f', validate_stringlist], + 'keymap.fullscreen' : [('f', 'ctrl+f'), validate_stringlist], 'keymap.home' : [['h', 'r', 'home'], validate_stringlist], 'keymap.back' : [['left', 'c', 'backspace'], validate_stringlist], 'keymap.forward' : [['right', 'v'], validate_stringlist], 'keymap.pan' : ['p', validate_stringlist], 'keymap.zoom' : ['o', validate_stringlist], - 'keymap.save' : ['s', validate_stringlist], - 'keymap.quit' : ['q', validate_stringlist], + 'keymap.save' : [('s', 'ctrl+s'), validate_stringlist], + 'keymap.quit' : [('ctrl+w', 'escape'), validate_stringlist], 'keymap.grid' : ['g', validate_stringlist], 'keymap.yscale' : ['l', validate_stringlist], 'keymap.xscale' : [['k', 'L'], validate_stringlist], diff --git a/matplotlibrc.template b/matplotlibrc.template index 16f2657f5026..780581a4a20b 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -410,7 +410,7 @@ text.hinting_factor : 8 # Specifies the amount of softness for hinting in the #keymap.pan : p # pan mnemonic #keymap.zoom : o # zoom mnemonic #keymap.save : s # saving current figure -#keymap.quit : q # close the current figure +#keymap.quit : ctrl+w # close the current figure #keymap.grid : g # switching on/off a grid in current axes #keymap.yscale : l # toggle scaling of y-axes ('log'/'linear') #keymap.xscale : L, k # toggle scaling of x-axes ('log'/'linear') From df0a11269f7b92173c20f8de4a95f9d4c4891a4c Mon Sep 17 00:00:00 2001 From: pelson Date: Tue, 22 May 2012 12:20:40 +0100 Subject: [PATCH 06/12] Several fixes to WX. Modifier keys implemented for qt, wx and gtk backends. --- lib/matplotlib/backend_bases.py | 4 -- lib/matplotlib/backends/backend_gtk.py | 11 ++-- lib/matplotlib/backends/backend_qt4.py | 32 +++++++++- lib/matplotlib/backends/backend_wx.py | 81 ++++++++++---------------- 4 files changed, 66 insertions(+), 62 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 060a9555e89e..e94f7a32b502 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2767,8 +2767,6 @@ def drag_zoom(self, event): self.draw_rubberband(event, x, y, lastx, lasty) - - def release_zoom(self, event): 'the release mouse button callback in zoom to rect mode' for zoom_id in self._ids_zoom: @@ -2894,8 +2892,6 @@ def draw(self): loc.refresh() self.canvas.draw() - - def _update_view(self): '''update the viewlim and position from the view and position stack for each axes diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 9ec97b64dc89..8224b4ba6570 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -322,14 +322,17 @@ def enter_notify_event(self, widget, event): def _get_key(self, event): if event.keyval in self.keyvald: key = self.keyvald[event.keyval] - elif event.keyval <256: + elif event.keyval < 256: key = chr(event.keyval) else: key = None - ctrl = event.state & gdk.CONTROL_MASK - shift = event.state & gdk.SHIFT_MASK - return key + for key_mask, prefix in ([gdk.CONTROL_MASK, 'ctrl'], + [gdk.MOD1_MASK, 'alt'], ): + if event.state & key_mask: + key = '{}+{}'.format(prefix, key) + + return key def configure_event(self, widget, event): diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index d5d739aded2d..b49611bf99bd 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -1,6 +1,7 @@ from __future__ import division, print_function import math import os +import signal import sys import matplotlib @@ -60,8 +61,10 @@ def _create_qApp(): class Show(ShowBase): def mainloop(self): - QtGui.qApp.exec_() + # allow KeyboardInterrupt exceptions to close the plot window. + signal.signal(signal.SIGINT, signal.SIG_DFL) + QtGui.qApp.exec_() show = Show() @@ -268,13 +271,36 @@ def minumumSizeHint( self ): def _get_key( self, event ): if event.isAutoRepeat(): return None + if event.key() < 256: - key = str(event.text()) + key = unicode(event.text()) + + # if the control key is being pressed, we don't get the correct + # characters, so interpret them directly from the event.key(). + # Unfortunately, this means that we cannot handle key's case + # since event.key() is not case sensitve, whereas event.text() is, + # Finally, since it is not possible to get the CapsLock state + # we cannot accurately compute the case of a pressed key when + # ctrl+shift+p is pressed. + if int(event.modifiers()) & QtCore.Qt.ControlModifier and event.key() < 256: + # we always get an uppercase charater + key = chr(event.key()) + # if shift is not being pressed, lowercase it (as mentioned, + # this does not take into account the CapsLock state) + if not int(event.modifiers()) & QtCore.Qt.ShiftModifier: + key = key.lower() + elif event.key() in self.keyvald: - key = self.keyvald[ event.key() ] + key = self.keyvald[event.key()] else: key = None + if key is not None: + for modifier, Qt_key, prefix in [(QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control, 'ctrl'), + (QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt, 'alt')]: + if event.key() != Qt_key and int(event.modifiers()) & modifier == modifier: + key = '{}+{}'.format(prefix, key) + return key def new_timer(self, *args, **kwargs): diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 64a060d58a8c..f65a3fcbb51f 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1242,13 +1242,18 @@ def _get_key(self, evt): keyval = evt.m_keyCode if keyval in self.keyvald: key = self.keyvald[keyval] - elif keyval <256: + elif keyval < 256: key = chr(keyval) + # wx always returns an uppercase, so make it lowercase if the shift + # key is not depressed (NOTE: this will not handle Caps Lock) + if not evt.ShiftDown(): + key = key.lower() else: key = None - # why is wx upcasing this? - if key is not None: key = key.lower() + for meth, prefix in ([evt.ControlDown, 'ctrl'], [evt.AltDown, 'alt'], ): + if meth(): + key = '{}+{}'.format(prefix, key) return key @@ -1476,6 +1481,7 @@ def __init__(self, num, fig): self.SetStatusBar(statbar) self.canvas = self.get_canvas(fig) self.canvas.SetInitialSize(wx.Size(fig.bbox.width, fig.bbox.height)) + self.canvas.SetFocus() self.sizer =wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.canvas, 1, wx.TOP | wx.LEFT | wx.EXPAND) # By adding toolbar in sizer, we are able to put it at the bottom @@ -1742,8 +1748,6 @@ def updateButtonText(self, lst): self.SetLabel("Axes: %s" % axis_txt[:-1]) - - cursord = { cursors.MOVE : wx.CURSOR_HAND, cursors.HAND : wx.CURSOR_HAND, @@ -1787,57 +1791,33 @@ def _init_toolbar(self): DEBUG_MSG("_init_toolbar", 1, self) self._parent = self.canvas.GetParent() - _NTB2_HOME =wx.NewId() - self._NTB2_BACK =wx.NewId() - self._NTB2_FORWARD =wx.NewId() - self._NTB2_PAN =wx.NewId() - self._NTB2_ZOOM =wx.NewId() - _NTB2_SAVE = wx.NewId() - _NTB2_SUBPLOT =wx.NewId() - - self.SetToolBitmapSize(wx.Size(24,24)) - - self.AddSimpleTool(_NTB2_HOME, _load_bitmap('home.png'), - 'Home', 'Reset original view') - self.AddSimpleTool(self._NTB2_BACK, _load_bitmap('back.png'), - 'Back', 'Back navigation view') - self.AddSimpleTool(self._NTB2_FORWARD, _load_bitmap('forward.png'), - 'Forward', 'Forward navigation view') - # todo: get new bitmap - self.AddCheckTool(self._NTB2_PAN, _load_bitmap('move.png'), - shortHelp='Pan', - longHelp='Pan with left, zoom with right') - self.AddCheckTool(self._NTB2_ZOOM, _load_bitmap('zoom_to_rect.png'), - shortHelp='Zoom', longHelp='Zoom to rectangle') - self.AddSeparator() - self.AddSimpleTool(_NTB2_SUBPLOT, _load_bitmap('subplots.png'), - 'Configure subplots', 'Configure subplot parameters') - self.AddSimpleTool(_NTB2_SAVE, _load_bitmap('filesave.png'), - 'Save', 'Save plot contents to file') - - bind(self, wx.EVT_TOOL, self.home, id=_NTB2_HOME) - bind(self, wx.EVT_TOOL, self.forward, id=self._NTB2_FORWARD) - bind(self, wx.EVT_TOOL, self.back, id=self._NTB2_BACK) - bind(self, wx.EVT_TOOL, self.zoom, id=self._NTB2_ZOOM) - bind(self, wx.EVT_TOOL, self.pan, id=self._NTB2_PAN) - bind(self, wx.EVT_TOOL, self.configure_subplot, id=_NTB2_SUBPLOT) - bind(self, wx.EVT_TOOL, self.save, id=_NTB2_SAVE) + self.wx_ids = {} + for text, tooltip_text, image_file, callback in self.toolitems: + if text is None: + self.AddSeparator() + continue + self.wx_ids[text] = wx.NewId() + if text in ['Pan', 'Zoom']: + self.AddCheckTool(self.wx_ids[text], _load_bitmap(image_file + '.png'), + shortHelp=text, longHelp=tooltip_text) + else: + self.AddSimpleTool(self.wx_ids[text], _load_bitmap(image_file + '.png'), + text, tooltip_text) + bind(self, wx.EVT_TOOL, getattr(self, callback), id=self.wx_ids[text]) self.Realize() - def zoom(self, *args): - self.ToggleTool(self._NTB2_PAN, False) + self.ToggleTool(self.wx_ids['Pan'], False) NavigationToolbar2.zoom(self, *args) def pan(self, *args): - self.ToggleTool(self._NTB2_ZOOM, False) + self.ToggleTool(self.wx_ids['Zoom'], False) NavigationToolbar2.pan(self, *args) - - def configure_subplot(self, evt): + def configure_subplots(self, evt): frame = wx.Frame(None, -1, "Configure subplots") toolfig = Figure((6,3)) @@ -1855,7 +1835,7 @@ def configure_subplot(self, evt): tool = SubplotTool(self.canvas.figure, toolfig) frame.Show() - def save(self, evt): + def save_figure(self, *args): # Fetch the required filename and file type. filetypes, exts, filter_index = self.canvas._get_imagesave_wildcards() default_file = "image." + self.canvas.get_default_filetype() @@ -1881,7 +1861,7 @@ def save(self, evt): os.path.join(dirname, filename), format=format) except Exception as e: error_msg_wx(str(e)) - + def set_cursor(self, cursor): cursor =wx.StockCursor(cursord[cursor]) self.canvas.SetCursor( cursor ) @@ -1948,8 +1928,8 @@ def set_message(self, s): def set_history_buttons(self): can_backward = (self._views._pos > 0) can_forward = (self._views._pos < len(self._views._elements) - 1) - self.EnableTool(self._NTB2_BACK, can_backward) - self.EnableTool(self._NTB2_FORWARD, can_forward) + self.EnableTool(self.wx_ids['Back'], can_backward) + self.EnableTool(self.wx_ids['Forward'], can_forward) class NavigationToolbarWx(wx.ToolBar): @@ -2149,13 +2129,12 @@ def _onMouseWheel(self, evt): direction = -1 self.button_fn(direction) - _onSave = NavigationToolbar2Wx.save + _onSave = NavigationToolbar2Wx.save_figure def _onClose(self, evt): self.GetParent().Destroy() - class StatusBarWx(wx.StatusBar): """ A status bar is added to _FigureFrame to allow measurements and the From 49d58fb3131553d2ac85d8929ff03f7c5f7a604f Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 28 May 2012 11:46:30 +0100 Subject: [PATCH 07/12] Doc improvements, removed escape for quit plot, added todos to backends I cannot test. --- doc/users/navigation_toolbar.rst | 23 ++-- lib/matplotlib/backend_bases.py | 131 ++++++++++----------- lib/matplotlib/backends/backend_fltkagg.py | 2 + lib/matplotlib/backends/backend_gtk.py | 6 +- lib/matplotlib/backends/backend_gtk3.py | 6 +- lib/matplotlib/backends/backend_macosx.py | 12 +- lib/matplotlib/backends/backend_qt.py | 2 + lib/matplotlib/backends/backend_qt4.py | 11 +- lib/matplotlib/backends/backend_tkagg.py | 2 +- lib/matplotlib/backends/backend_wx.py | 2 +- lib/matplotlib/rcsetup.py | 2 +- src/_macosx.m | 2 + 12 files changed, 104 insertions(+), 97 deletions(-) diff --git a/doc/users/navigation_toolbar.rst b/doc/users/navigation_toolbar.rst index 66848eab4e36..6e327621ba4b 100644 --- a/doc/users/navigation_toolbar.rst +++ b/doc/users/navigation_toolbar.rst @@ -83,23 +83,24 @@ Navigation Keyboard Shortcuts The following table holds all the default keys, which can be overwritten by use of your matplotlibrc (#keymap.\*). -================================== ============================================== +================================== ================================================= Command Keyboard Shortcut(s) -================================== ============================================== +================================== ================================================= Home/Reset **h** or **r** or **home** Back **c** or **left arrow** or **backspace** Forward **v** or **right arrow** Pan/Zoom **p** Zoom-to-rect **o** -Save **s** -Toggle fullscreen **f** -Constrain pan/zoom to x axis hold **x** -Constrain pan/zoom to y axis hold **y** -Preserve aspect ratio hold **CONTROL** -Toggle grid **g** -Toggle x axis scale (log/linear) **L** or **k** -Toggle y axis scale (log/linear) **l** -================================== ============================================== +Save **ctrl** + **s** +Toggle fullscreen **ctrl** + **f** +Close plot **ctrl** + **w** +Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse +Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse +Preserve aspect ratio hold **CONTROL** when panning/zooming with mouse +Toggle grid **g** when mouse is over an axes +Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes +Toggle y axis scale (log/linear) **l** when mouse is over an axes +================================== ================================================= If you are using :mod:`matplotlib.pyplot` the toolbar will be created automatically for every figure. If you are writing your own user diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e94f7a32b502..3b9db95ae0ed 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1190,6 +1190,7 @@ class CloseEvent(Event): def __init__(self, name, canvas, guiEvent=None): Event.__init__(self, name, canvas, guiEvent) + class LocationEvent(Event): """ An event that has a screen location @@ -1295,9 +1296,6 @@ def _update_enter_leave(self): LocationEvent.lastevent = self - - - class MouseEvent(LocationEvent): """ A mouse event ('button_press_event', 'button_release_event', 'scroll_event', @@ -1307,10 +1305,12 @@ class MouseEvent(LocationEvent): attributes, the following attributes are defined: *button* - button pressed None, 1, 2, 3, 'up', 'down' (up and down are used for scroll events) + button pressed None, 1, 2, 3, 'up', 'down' (up and down are used + for scroll events) *key* - the key pressed: None, chr(range(255), 'shift', 'win', or 'control' + the key depressed when the mouse event triggered (see + :class:`KeyEvent`) *step* number of scroll steps (positive for 'up', negative for 'down') @@ -1319,7 +1319,7 @@ class MouseEvent(LocationEvent): Example usage:: def on_press(event): - print 'you pressed', event.button, event.xdata, event.ydata + print('you pressed', event.button, event.xdata, event.ydata) cid = fig.canvas.mpl_connect('button_press_event', on_press) @@ -1346,8 +1346,11 @@ def __init__(self, name, canvas, x, y, button=None, key=None, self.dblclick = dblclick def __str__(self): - return "MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d dblclick=%s inaxes=%s"%\ - (self.x,self.y,str(self.xdata),str(self.ydata),self.button,self.dblclick,self.inaxes) + return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d dblclick=%s" + "inaxes=%s" % (self.x, self.y, str(self.xdata), str(self.ydata), + self.button, self.dblclick, self.inaxes) + ) + class PickEvent(Event): """ @@ -1377,7 +1380,7 @@ def on_pick(event): thisline = event.artist xdata, ydata = thisline.get_data() ind = event.ind - print 'on pick line:', zip(xdata[ind], ydata[ind]) + print('on pick line:', zip(xdata[ind], ydata[ind])) cid = fig.canvas.mpl_connect('pick_event', on_pick) @@ -1400,16 +1403,23 @@ class KeyEvent(LocationEvent): attributes, the following attributes are defined: *key* - the key pressed: None, chr(range(255)), shift, win, or control - - This interface may change slightly when better support for - modifier keys is included. - - + the key(s) pressed. Could be **None**, a single case sensitive ascii + character ("g", "G", "#", etc.), a special key + ("control", "shift", "f1", "up", etc.) or a + combination of the above (e.g. "ctrl+alt+g", "ctrl+alt+G"). + + .. note:: + + Modifier keys will be prefixed to the pressed key and will be in the + order "ctrl", "alt", "super". The exception to this rule is when the + pressed key is itself a modifier key, therefore "ctrl+alt" and + "alt+control" can both be valid key values. + + Example usage:: def on_key(event): - print 'you pressed', event.key, event.xdata, event.ydata + print('you pressed', event.key, event.xdata, event.ydata) cid = fig.canvas.mpl_connect('key_press_event', on_key) @@ -1419,7 +1429,6 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): self.key = key - class FigureCanvasBase(object): """ The canvas the figure renders into. @@ -1448,7 +1457,6 @@ class FigureCanvasBase(object): 'close_event' ] - def __init__(self, figure): figure.set_canvas(self) self.figure = figure @@ -1657,7 +1665,8 @@ 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) + mouseevent = MouseEvent(s, self, x, y, button, self._key, + dblclick=dblclick, guiEvent=guiEvent) self.callbacks.process(s, mouseevent) def button_release_event(self, x, y, button, guiEvent=None): @@ -1743,7 +1752,7 @@ def enter_notify_event(self, guiEvent=None, xy=None): self.callbacks.process('figure_enter_event', event) def idle_event(self, guiEvent=None): - 'call when GUI is idle' + """Called when GUI is idle.""" s = 'idle_event' event = IdleEvent(s, self, guiEvent=guiEvent) self.callbacks.process(s, event) @@ -1789,7 +1798,7 @@ def draw_cursor(self, event): def get_width_height(self): """ - return the figure width and height in points or pixels + Return the figure width and height in points or pixels (depending on the backend), truncated to integers """ return int(self.figure.bbox.width), int(self.figure.bbox.height) @@ -1911,7 +1920,6 @@ def get_supported_filetypes_grouped(self): groupings[name].sort() return groupings - def _get_print_method(self, format): method_name = 'print_%s' % format @@ -1936,7 +1944,6 @@ def _print_method(*args, **kwargs): return getattr(self, method_name) - def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', orientation='portrait', format=None, **kwargs): """ @@ -2078,9 +2085,6 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', #self.figure.canvas.draw() ## seems superfluous return result - - - def get_default_filetype(self): """ Get the default savefig file format as specified in rcParam @@ -2099,7 +2103,7 @@ def set_window_title(self, title): def switch_backends(self, FigureCanvasClass): """ - instantiate an instance of FigureCanvasClass + Instantiate an instance of FigureCanvasClass This is used for backend switching, eg, to instantiate a FigureCanvasPS from a FigureCanvasGTK. Note, deep copying is @@ -2148,7 +2152,7 @@ def func(event) Example usage:: def on_press(event): - print 'you pressed', event.button, event.xdata, event.ydata + print('you pressed', event.button, event.xdata, event.ydata) cid = canvas.mpl_connect('button_press_event', on_press) @@ -2158,7 +2162,7 @@ def on_press(event): def mpl_disconnect(self, cid): """ - disconnect callback id cid + Disconnect callback id cid Example usage:: @@ -2394,12 +2398,12 @@ def full_screen_toggle(self): pass def resize(self, w, h): - 'For gui backends: resize window in pixels' + """"For gui backends, resize the window (in pixels).""" pass def key_press(self, event): """ - implement the default mpl key bindings defined at + Implement the default mpl key bindings defined at :ref:`key-event-handling` """ key_press_handler(event, self.canvas, self.canvas.toolbar) @@ -2417,13 +2421,13 @@ def set_window_title(self, title): """ pass -# cursors -class Cursors: #namespace + +class Cursors: + # this class is only used as a simple namespace HAND, POINTER, SELECT_REGION, MOVE = range(4) cursors = Cursors() - class NavigationToolbar2(object): """ Base class for the navigation cursor, version 2 @@ -2487,7 +2491,6 @@ class NavigationToolbar2(object): ('Save', 'Save the figure', 'filesave', 'save_figure'), ) - def __init__(self, canvas): self.canvas = canvas canvas.toolbar = self @@ -2511,11 +2514,11 @@ def __init__(self, canvas): self.set_history_buttons() def set_message(self, s): - 'display a message on toolbar or in status bar' + """Display a message on toolbar or in status bar""" pass def back(self, *args): - 'move back up the view lim stack' + """move back up the view lim stack""" self._views.back() self._positions.back() self.set_history_buttons() @@ -2525,18 +2528,18 @@ def dynamic_update(self): pass def draw_rubberband(self, event, x0, y0, x1, y1): - 'draw a rectangle rubberband to indicate zoom limits' + """Draw a rectangle rubberband to indicate zoom limits""" pass def forward(self, *args): - 'move forward in the view lim stack' + """Move forward in the view lim stack""" self._views.forward() self._positions.forward() self.set_history_buttons() self._update_view() def home(self, *args): - 'restore the original view' + """Restore the original view""" self._views.home() self._positions.home() self.set_history_buttons() @@ -2565,8 +2568,6 @@ class implementation. raise NotImplementedError def mouse_move(self, event): - #print 'mouse_move', event.button - if not event.inaxes or not self._active: if self._lastCursor != cursors.POINTER: self.set_cursor(cursors.POINTER) @@ -2584,9 +2585,10 @@ def mouse_move(self, event): if event.inaxes and event.inaxes.get_navigate(): - try: s = event.inaxes.format_coord(event.xdata, event.ydata) - except ValueError: pass - except OverflowError: pass + try: + s = event.inaxes.format_coord(event.xdata, event.ydata) + except (ValueError, OverflowError): + pass else: if len(self.mode): self.set_message('%s, %s' % (self.mode, s)) @@ -2595,7 +2597,7 @@ def mouse_move(self, event): else: self.set_message(self.mode) def pan(self,*args): - 'Activate the pan/zoom tool. pan with left button, zoom with right' + """Activate the pan/zoom tool. pan with left button, zoom with right""" # set the pointer icon and button press funcs to the # appropriate callbacks @@ -2627,11 +2629,11 @@ def pan(self,*args): self.set_message(self.mode) def press(self, event): - 'this will be called whenver a mouse button is pressed' + """Called whenver a mouse button is pressed.""" pass def press_pan(self, event): - 'the press mouse button in pan/zoom mode callback' + """the press mouse button in pan/zoom mode callback""" if event.button == 1: self._button_pressed=1 @@ -2659,7 +2661,7 @@ def press_pan(self, event): self.press(event) def press_zoom(self, event): - 'the press mouse button in zoom to rect mode callback' + """the press mouse button in zoom to rect mode callback""" if event.button == 1: self._button_pressed=1 elif event.button == 3: @@ -2681,17 +2683,14 @@ def press_zoom(self, event): a.transData.frozen() )) id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom) - id2 = self.canvas.mpl_connect('key_press_event', self._switch_on_zoom_mode) id3 = self.canvas.mpl_connect('key_release_event', self._switch_off_zoom_mode) self._ids_zoom = id1, id2, id3 - self._zoom_mode = event.key - self.press(event) def _switch_on_zoom_mode(self, event): @@ -2703,7 +2702,7 @@ def _switch_off_zoom_mode(self, event): self.mouse_move(event) def push_current(self): - 'push the current view limits and position onto the stack' + """push the current view limits and position onto the stack""" lims = []; pos = [] for a in self.canvas.figure.get_axes(): xmin, xmax = a.get_xlim() @@ -2718,11 +2717,11 @@ def push_current(self): self.set_history_buttons() def release(self, event): - 'this will be called whenever mouse button is released' + """this will be called whenever mouse button is released""" pass def release_pan(self, event): - 'the release mouse button callback in pan/zoom mode' + """the release mouse button callback in pan/zoom mode""" if self._button_pressed is None: return @@ -2738,7 +2737,7 @@ def release_pan(self, event): self.draw() def drag_pan(self, event): - 'the drag callback in pan/zoom mode' + """the drag callback in pan/zoom mode""" for a, ind in self._xypress: #safer to use the recorded button at the press than current button: @@ -2747,7 +2746,7 @@ def drag_pan(self, event): self.dynamic_update() def drag_zoom(self, event): - 'the drag callback in zoom mode' + """the drag callback in zoom mode""" if self._xypress: x, y = event.x, event.y @@ -2768,7 +2767,7 @@ def drag_zoom(self, event): self.draw_rubberband(event, x, y, lastx, lasty) def release_zoom(self, event): - 'the release mouse button callback in zoom to rect mode' + """the release mouse button callback in zoom to rect mode""" for zoom_id in self._ids_zoom: self.canvas.mpl_disconnect(zoom_id) self._ids_zoom = [] @@ -2876,7 +2875,7 @@ def release_zoom(self, event): self.release(event) def draw(self): - 'redraw the canvases, update the locators' + """Redraw the canvases, update the locators""" for a in self.canvas.figure.get_axes(): xaxis = getattr(a, 'xaxis', None) yaxis = getattr(a, 'yaxis', None) @@ -2893,9 +2892,9 @@ def draw(self): self.canvas.draw() def _update_view(self): - '''update the viewlim and position from the view and + """Update the viewlim and position from the view and position stack for each axes - ''' + """ lims = self._views() if lims is None: return @@ -2911,9 +2910,8 @@ def _update_view(self): self.draw() - def save_figure(self, *args): - 'save the current figure' + """Save the current figure""" raise NotImplementedError def set_cursor(self, cursor): @@ -2924,13 +2922,13 @@ def set_cursor(self, cursor): pass def update(self): - 'reset the axes stack' + """Reset the axes stack""" self._views.clear() self._positions.clear() self.set_history_buttons() def zoom(self, *args): - 'activate zoom to rect mode' + """Activate zoom to rect mode""" if self._active == 'ZOOM': self._active = None else: @@ -2957,7 +2955,6 @@ def zoom(self, *args): self.set_message(self.mode) - def set_history_buttons(self): - 'enable or disable back/forward button' + """Enable or disable back/forward button""" pass diff --git a/lib/matplotlib/backends/backend_fltkagg.py b/lib/matplotlib/backends/backend_fltkagg.py index cf0d398854a8..fe31d0f81191 100644 --- a/lib/matplotlib/backends/backend_fltkagg.py +++ b/lib/matplotlib/backends/backend_fltkagg.py @@ -136,6 +136,8 @@ def handle(self, event): self._key=special_key[ikey] except: self._key=None + + # TODO: Handle ctrl, alt, super modifiers. FigureCanvasBase.key_press_event(self._source, self._key) return 1 elif event == Fltk.FL_KEYUP: diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 8224b4ba6570..837ecad3c3f6 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -327,12 +327,12 @@ def _get_key(self, event): else: key = None - for key_mask, prefix in ([gdk.CONTROL_MASK, 'ctrl'], - [gdk.MOD1_MASK, 'alt'], ): + for key_mask, prefix in ([gdk.MOD1_MASK, 'alt'], + [gdk.CONTROL_MASK, 'ctrl'],): if event.state & key_mask: key = '{}+{}'.format(prefix, key) - return key + return key def configure_event(self, widget, event): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a69cbcfff316..e7799d5ac917 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -259,16 +259,14 @@ def enter_notify_event(self, widget, event): def _get_key(self, event): if event.keyval in self.keyvald: key = self.keyvald[event.keyval] - elif event.keyval <256: + elif event.keyval < 256: key = chr(event.keyval) else: key = None - #ctrl = event.get_state() & Gdk.EventMask.CONTROL - #shift = event.get_state() & Gdk.EventMask.SHIFT + # TODO: Handle ctrl, alt, super modifiers. gtk backend has implemented. return key - def configure_event(self, widget, event): if _debug: print 'FigureCanvasGTK3.%s' % fn_name() if widget.get_property("window") is None: diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index e1fd5e331107..cfa31d15653c 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -20,12 +20,13 @@ import matplotlib from matplotlib.backends import _macosx + class Show(ShowBase): def mainloop(self): _macosx.show() - show = Show() + class RendererMac(RendererBase): """ The renderer handles drawing/rendering operations. Most of the renderer's @@ -136,7 +137,7 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): def draw_text(self, gc, x, y, s, prop, angle, ismath=False): if ismath: - self._draw_mathtext(gc, x, y, s, prop, angle) + self._draw_mathtext(gc, x, y, s, prop, angle) else: family = prop.get_family() weight = prop.get_weight() @@ -174,6 +175,7 @@ def points_to_pixels(self, points): def option_image_nocomposite(self): return True + class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase): """ The GraphicsContext wraps a Quartz graphics context. All methods @@ -211,6 +213,7 @@ def set_clip_path(self, path): path = path.get_fully_transformed_path() _macosx.GraphicsContext.set_clip_path(self, path) + ######################################################################## # # The following functions and classes are for pylab and implement @@ -245,6 +248,7 @@ def new_figure_manager(num, *args, **kwargs): manager = FigureManagerMac(canvas, num) return manager + class TimerMac(_macosx.Timer, TimerBase): ''' Subclass of :class:`backend_bases.TimerBase` that uses CoreFoundation @@ -262,7 +266,6 @@ class TimerMac(_macosx.Timer, TimerBase): # completely implemented at the C-level (in _macosx.Timer) - class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase): """ The canvas the figure renders into. Calls the draw and print fig @@ -346,6 +349,7 @@ def new_timer(self, *args, **kwargs): """ return TimerMac(*args, **kwargs) + class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): """ Wrap everything up into a window for the pylab interface @@ -377,6 +381,7 @@ def notify_axes_change(fig): def close(self): Gcf.destroy(self.num) + class NavigationToolbarMac(_macosx.NavigationToolbar): def __init__(self, canvas): @@ -444,6 +449,7 @@ def save_figure(self, *args): return self.canvas.print_figure(filename) + class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2): def __init__(self, canvas): diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 21ab5fd4614a..285523fdf6d6 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -181,6 +181,8 @@ def _get_key( self, event ): else: key = None + # TODO: Handle ctrl, alt, super modifiers. qt4 backend has implemented. + return key def flush_events(self): diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index b49611bf99bd..14238fd9c82a 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -282,7 +282,7 @@ def _get_key( self, event ): # Finally, since it is not possible to get the CapsLock state # we cannot accurately compute the case of a pressed key when # ctrl+shift+p is pressed. - if int(event.modifiers()) & QtCore.Qt.ControlModifier and event.key() < 256: + if int(event.modifiers()) & QtCore.Qt.ControlModifier: # we always get an uppercase charater key = chr(event.key()) # if shift is not being pressed, lowercase it (as mentioned, @@ -290,14 +290,13 @@ def _get_key( self, event ): if not int(event.modifiers()) & QtCore.Qt.ShiftModifier: key = key.lower() - elif event.key() in self.keyvald: - key = self.keyvald[event.key()] else: - key = None + key = self.keyvald.get(event.key()) if key is not None: - for modifier, Qt_key, prefix in [(QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control, 'ctrl'), - (QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt, 'alt')]: + # prepend the ctrl, alt, super keys if appropriate (sorted in that order) + for modifier, Qt_key, prefix in [(QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt, 'alt'), + (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control, 'ctrl')]: if event.key() != Qt_key and int(event.modifiers()) & modifier == modifier: key = '{}+{}'.format(prefix, key) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 027caafe82ba..e73f1a72f92e 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -411,7 +411,7 @@ def _get_key(self, event): # BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400; if key is not None: # note, shift is not added to the keys as this is already accounted for - for bitmask, prefix in [(2, 'ctrl'), (3, 'alt')]: + for bitmask, prefix in [(3, 'alt'), (2, 'ctrl'), ]: if event.state & (1 << bitmask): key = '{}+{}'.format(prefix, key) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index f65a3fcbb51f..288ae8fc9266 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1251,7 +1251,7 @@ def _get_key(self, evt): else: key = None - for meth, prefix in ([evt.ControlDown, 'ctrl'], [evt.AltDown, 'alt'], ): + for meth, prefix in ([evt.AltDown, 'alt'], [evt.ControlDown, 'ctrl'], ): if meth(): key = '{}+{}'.format(prefix, key) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 035d11e48c8c..13d1ff0e2748 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -582,7 +582,7 @@ def __call__(self, s): 'keymap.pan' : ['p', validate_stringlist], 'keymap.zoom' : ['o', validate_stringlist], 'keymap.save' : [('s', 'ctrl+s'), validate_stringlist], - 'keymap.quit' : [('ctrl+w', 'escape'), validate_stringlist], + 'keymap.quit' : ['ctrl+w', validate_stringlist], 'keymap.grid' : ['g', validate_stringlist], 'keymap.yscale' : ['l', validate_stringlist], 'keymap.xscale' : [['k', 'L'], validate_stringlist], diff --git a/src/_macosx.m b/src/_macosx.m index a2090daefd75..40d41580758f 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -5416,6 +5416,7 @@ - (void)keyDown:(NSEvent*)event { PyObject* result; const char* s = [self convertKeyEvent: event]; + /* TODO: Handle ctrl, alt, super modifiers. qt4 has implemented these. */ PyGILState_STATE gstate = PyGILState_Ensure(); if (s==NULL) { @@ -5437,6 +5438,7 @@ - (void)keyUp:(NSEvent*)event { PyObject* result; const char* s = [self convertKeyEvent: event]; + /* TODO: Handle ctrl, alt, super modifiers. qt4 has implemented these. */ PyGILState_STATE gstate = PyGILState_Ensure(); if (s==NULL) { From 74b648913f636565e07e7ad1ad56cbb544fb5e69 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 20 Jun 2012 11:02:29 +0100 Subject: [PATCH 08/12] Improved means of getting figure when fullscreen key is depressed. --- lib/matplotlib/backend_bases.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 3b9db95ae0ed..b4f72651343a 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1346,10 +1346,9 @@ def __init__(self, name, canvas, x, y, button=None, key=None, self.dblclick = dblclick def __str__(self): - return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d dblclick=%s" - "inaxes=%s" % (self.x, self.y, str(self.xdata), str(self.ydata), - self.button, self.dblclick, self.inaxes) - ) + return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d " + + "dblclick=%s inaxes=%s") % (self.x, self.y, self.xdata, + self.ydata, self.button, self.dblclick, self.inaxes) class PickEvent(Event): From 5fdd68adac02827bb28f17ea88d2a6996c4acb1d Mon Sep 17 00:00:00 2001 From: pelson Date: Sat, 23 Jun 2012 22:35:28 +0100 Subject: [PATCH 09/12] Tested Tkinter and PySide keys on osx (10.7) --- lib/matplotlib/backends/backend_qt4.py | 50 +++++++++++++++++++----- lib/matplotlib/backends/backend_tkagg.py | 37 ++++++++++++++++-- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 14238fd9c82a..30a33dd13ebf 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -123,6 +123,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ): keyvald = { QtCore.Qt.Key_Control : 'control', QtCore.Qt.Key_Shift : 'shift', QtCore.Qt.Key_Alt : 'alt', + QtCore.Qt.Key_Meta : 'super', QtCore.Qt.Key_Return : 'enter', QtCore.Qt.Key_Left : 'left', QtCore.Qt.Key_Up : 'up', @@ -146,6 +147,29 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ): QtCore.Qt.Key_PageUp : 'pageup', QtCore.Qt.Key_PageDown : 'pagedown', } + + # define the modifier keys which are to be collected on keyboard events. + # format is: [(modifier_flag, modifier_name, equivalent_key) + _modifier_keys = [ + (QtCore.Qt.MetaModifier, 'super', QtCore.Qt.Key_Meta), + (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt), + (QtCore.Qt.ControlModifier, 'ctrl', QtCore.Qt.Key_Control) + ] + + if sys.platform == 'darwin': + # in OSX, the control and super (aka cmd/apple) keys are switched, so + # switch them back. + keyvald.update({ + QtCore.Qt.Key_Control : 'super', # cmd/apple key + QtCore.Qt.Key_Meta : 'control', + }) + + _modifier_keys = [ + (QtCore.Qt.ControlModifier, 'super', QtCore.Qt.Key_Control), + (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt), + (QtCore.Qt.MetaModifier, 'ctrl', QtCore.Qt.Key_Meta), + ] + # map Qt button codes to MouseEvent's ones: buttond = {QtCore.Qt.LeftButton : 1, QtCore.Qt.MidButton : 2, @@ -153,6 +177,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ): # QtCore.Qt.XButton1 : None, # QtCore.Qt.XButton2 : None, } + def __init__( self, figure ): if DEBUG: print('FigureCanvasQt: ', figure) _create_qApp() @@ -295,8 +320,7 @@ def _get_key( self, event ): if key is not None: # prepend the ctrl, alt, super keys if appropriate (sorted in that order) - for modifier, Qt_key, prefix in [(QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt, 'alt'), - (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control, 'ctrl')]: + for modifier, prefix, Qt_key in self._modifier_keys: if event.key() != Qt_key and int(event.modifiers()) & modifier == modifier: key = '{}+{}'.format(prefix, key) @@ -368,6 +392,14 @@ def __init__( self, canvas, num ): self.canvas.setFocusPolicy( QtCore.Qt.StrongFocus ) self.canvas.setFocus() + if sys.platform == 'darwin': + # to make a qt window pop up on top on osx, osascript can be used + # this came from http://sourceforge.net/mailarchive/message.php?msg_id=23718545 + cmd = ("""/usr/bin/osascript -e 'tell app "Finder" to set """ + \ + """frontmost of process "%s" to true'""") % \ + os.path.basename(sys.executable) + os.system(cmd) + QtCore.QObject.connect( self.window, QtCore.SIGNAL( 'destroyed()' ), self._widgetclosed ) self.window._destroying = False @@ -398,9 +430,9 @@ def __init__( self, canvas, num ): self.canvas.figure.show = lambda *args: self.window.show() def notify_axes_change( fig ): - # This will be called whenever the current axes is changed - if self.toolbar is not None: - self.toolbar.update() + # This will be called whenever the current axes is changed + if self.toolbar is not None: + self.toolbar.update() self.canvas.figure.add_axobserver( notify_axes_change ) @QtCore.Slot() @@ -409,10 +441,10 @@ def _show_message(self,s): self.window.statusBar().showMessage(s) def full_screen_toggle(self): - if self.window.isFullScreen(): - self.window.showNormal() - else: - self.window.showFullScreen() + if self.window.isFullScreen(): + self.window.showNormal() + else: + self.window.showFullScreen() def _widgetclosed( self ): if self.window._destroying: return diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index e73f1a72f92e..1df2ea845deb 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -174,6 +174,17 @@ class FigureCanvasTkAgg(FigureCanvasAgg): 65439 : 'dec', 65421 : 'enter', } + + _keycode_lookup = { + 262145: 'control', + 524320: 'alt', + 524352: 'alt', + 1048584: 'super', + 1048592: 'super', + 131074: 'shift', + 131076: 'shift', + } + """_keycode_lookup is used for badly mapped keys on apple keyboards.""" def __init__(self, figure, master=None, resize_callback=None): FigureCanvasAgg.__init__(self, figure) @@ -217,7 +228,15 @@ def filter_destroy(evt): self._master = master self._tkcanvas.focus_set() - + + if sys.platform == 'darwin': + # to make a tkagg window pop up on top on osx, osascript can be used + # this came from http://sourceforge.net/mailarchive/message.php?msg_id=23718545 + cmd = ("""/usr/bin/osascript -e 'tell app "Finder" to set """ + \ + """frontmost of process "%s" to true'""") % \ + os.path.basename(sys.executable) + os.system(cmd) + def resize(self, event): width, height = event.width, event.height if self._resize_callback is not None: @@ -399,20 +418,30 @@ def _get_key(self, event): val = event.keysym_num if val in self.keyvald: key = self.keyvald[val] + elif val == 0 and sys.platform == 'darwin' and \ + event.keycode in self._keycode_lookup: + key = self._keycode_lookup[event.keycode] elif val < 256: key = chr(val) 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. + modifiers = (3, 'alt', 'alt'), (2, 'ctrl', 'control'), + if sys.platform == 'darwin': + modifiers = (3, 'super', 'super'), (4, '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 in [(3, 'alt'), (2, 'ctrl'), ]: - if event.state & (1 << bitmask): + for bitmask, prefix, key_name in modifiers: + if event.state & (1 << bitmask) and key_name not in key: key = '{}+{}'.format(prefix, key) return key From 677f430b8011b6e3f3777a70784dbdc65a5aa446 Mon Sep 17 00:00:00 2001 From: pelson Date: Sun, 24 Jun 2012 19:37:55 +0100 Subject: [PATCH 10/12] Added better 'super' key support. --- lib/matplotlib/backends/backend_gtk.py | 4 +++- lib/matplotlib/backends/backend_gtk3.py | 10 +++++++++- lib/matplotlib/backends/backend_tkagg.py | 14 +++++++++++--- lib/matplotlib/backends/backend_wx.py | 4 +++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 837ecad3c3f6..a1f822f7d295 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -327,7 +327,9 @@ def _get_key(self, event): else: key = None - for key_mask, prefix in ([gdk.MOD1_MASK, 'alt'], + for key_mask, prefix in ( + [gdk.MOD4_MASK, 'super'], + [gdk.MOD1_MASK, 'alt'], [gdk.CONTROL_MASK, 'ctrl'],): if event.state & key_mask: key = '{}+{}'.format(prefix, key) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index e7799d5ac917..0c5dd99aaf0a 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -264,7 +264,15 @@ def _get_key(self, event): else: key = None - # TODO: Handle ctrl, alt, super modifiers. gtk backend has implemented. + modifiers = [ + (Gdk.ModifierType.MOD4_MASK, 'super'), + (Gdk.ModifierType.MOD1_MASK, 'alt'), + (Gdk.ModifierType.CONTROL_MASK, 'ctrl'), + ] + for key_mask, prefix in modifiers: + if event.state & key_mask: + key = '{}+{}'.format(prefix, key) + return key def configure_event(self, widget, event): diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 1df2ea845deb..83dd06875198 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -127,6 +127,7 @@ class FigureCanvasTkAgg(FigureCanvasAgg): keyvald = {65507 : 'control', 65505 : 'shift', 65513 : 'alt', + 65515 : 'super', 65508 : 'control', 65506 : 'shift', 65514 : 'alt', @@ -184,7 +185,8 @@ class FigureCanvasTkAgg(FigureCanvasAgg): 131074: 'shift', 131076: 'shift', } - """_keycode_lookup is used for badly mapped keys on apple keyboards.""" + """_keycode_lookup is used for badly mapped (i.e. no event.key_sym set) + keys on apple keyboards.""" def __init__(self, figure, master=None, resize_callback=None): FigureCanvasAgg.__init__(self, figure) @@ -434,9 +436,15 @@ def _get_key(self, event): # 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. - modifiers = (3, 'alt', 'alt'), (2, 'ctrl', 'control'), + modifiers = [(6, 'super', 'super'), + (3, 'alt', 'alt'), + (2, 'ctrl', 'control'), + ] if sys.platform == 'darwin': - modifiers = (3, 'super', 'super'), (4, 'alt', 'alt'), (2, 'ctrl', 'control'), + modifiers = [(3, 'super', 'super'), + (4, 'alt', 'alt'), + (2, 'ctrl', 'control'), + ] if key is not None: # note, shift is not added to the keys as this is already accounted for diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 288ae8fc9266..4f942b931c69 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1251,7 +1251,9 @@ def _get_key(self, evt): else: key = None - for meth, prefix in ([evt.AltDown, 'alt'], [evt.ControlDown, 'ctrl'], ): + for meth, prefix in ( + [evt.AltDown, 'alt'], + [evt.ControlDown, 'ctrl'], ): if meth(): key = '{}+{}'.format(prefix, key) From 9dfd95318bd15434ab91f8c64c0b355d4163053a Mon Sep 17 00:00:00 2001 From: pelson Date: Sun, 24 Jun 2012 20:15:21 +0100 Subject: [PATCH 11/12] More osx tweaks --- lib/matplotlib/backends/backend_gtk.py | 7 +++++-- lib/matplotlib/backends/backend_qt4.py | 17 ++++++++++------- lib/matplotlib/rcsetup.py | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index a1f822f7d295..4ca9ca7deb48 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -189,6 +189,10 @@ class FigureCanvasGTK (gtk.DrawingArea, FigureCanvasBase): 65455 : '/', 65439 : 'dec', 65421 : 'enter', + 65511 : 'super', + 65512 : 'super', + 65406 : 'alt', + 65289 : 'tab', } # Setting this as a static constant prevents @@ -326,7 +330,7 @@ def _get_key(self, event): key = chr(event.keyval) else: key = None - + for key_mask, prefix in ( [gdk.MOD4_MASK, 'super'], [gdk.MOD1_MASK, 'alt'], @@ -336,7 +340,6 @@ def _get_key(self, event): return key - def configure_event(self, widget, event): if _debug: print('FigureCanvasGTK.%s' % fn_name()) if widget.window is None: diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 30a33dd13ebf..2109d8eeeb54 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -156,6 +156,8 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ): (QtCore.Qt.ControlModifier, 'ctrl', QtCore.Qt.Key_Control) ] + _ctrl_modifier = QtCore.Qt.ControlModifier + if sys.platform == 'darwin': # in OSX, the control and super (aka cmd/apple) keys are switched, so # switch them back. @@ -169,7 +171,9 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ): (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt), (QtCore.Qt.MetaModifier, 'ctrl', QtCore.Qt.Key_Meta), ] - + + _ctrl_modifier = QtCore.Qt.MetaModifier + # map Qt button codes to MouseEvent's ones: buttond = {QtCore.Qt.LeftButton : 1, QtCore.Qt.MidButton : 2, @@ -177,7 +181,7 @@ class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ): # QtCore.Qt.XButton1 : None, # QtCore.Qt.XButton2 : None, } - + def __init__( self, figure ): if DEBUG: print('FigureCanvasQt: ', figure) _create_qApp() @@ -299,16 +303,15 @@ def _get_key( self, event ): if event.key() < 256: key = unicode(event.text()) - # if the control key is being pressed, we don't get the correct # characters, so interpret them directly from the event.key(). # Unfortunately, this means that we cannot handle key's case - # since event.key() is not case sensitve, whereas event.text() is, + # since event.key() is not case sensitive, whereas event.text() is, # Finally, since it is not possible to get the CapsLock state # we cannot accurately compute the case of a pressed key when # ctrl+shift+p is pressed. - if int(event.modifiers()) & QtCore.Qt.ControlModifier: - # we always get an uppercase charater + if int(event.modifiers()) & self._ctrl_modifier: + # we always get an uppercase character key = chr(event.key()) # if shift is not being pressed, lowercase it (as mentioned, # this does not take into account the CapsLock state) @@ -322,7 +325,7 @@ def _get_key( self, event ): # prepend the ctrl, alt, super keys if appropriate (sorted in that order) for modifier, prefix, Qt_key in self._modifier_keys: if event.key() != Qt_key and int(event.modifiers()) & modifier == modifier: - key = '{}+{}'.format(prefix, key) + key = u'{}+{}'.format(prefix, key) return key diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 13d1ff0e2748..68fc2a8c482f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -574,7 +574,7 @@ def __call__(self, s): 'agg.path.chunksize' : [0, validate_int], # 0 to disable chunking; # recommend about 20000 to # enable. Experimental. - # key-mappings + # key-mappings (multi-character mappings should be a list/tuple) 'keymap.fullscreen' : [('f', 'ctrl+f'), validate_stringlist], 'keymap.home' : [['h', 'r', 'home'], validate_stringlist], 'keymap.back' : [['left', 'c', 'backspace'], validate_stringlist], @@ -582,7 +582,7 @@ def __call__(self, s): 'keymap.pan' : ['p', validate_stringlist], 'keymap.zoom' : ['o', validate_stringlist], 'keymap.save' : [('s', 'ctrl+s'), validate_stringlist], - 'keymap.quit' : ['ctrl+w', validate_stringlist], + 'keymap.quit' : [('ctrl+w', ), validate_stringlist], 'keymap.grid' : ['g', validate_stringlist], 'keymap.yscale' : ['l', validate_stringlist], 'keymap.xscale' : [['k', 'L'], validate_stringlist], From d7a4ae25703bf3dccf12f1bdbfe4b1c59ce8b433 Mon Sep 17 00:00:00 2001 From: pelson Date: Sun, 1 Jul 2012 22:06:48 +0100 Subject: [PATCH 12/12] Rolled back some focusing changes. --- lib/matplotlib/backends/backend_qt4.py | 8 -------- lib/matplotlib/backends/backend_tkagg.py | 10 +--------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 2109d8eeeb54..08ac77b253d3 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -395,14 +395,6 @@ def __init__( self, canvas, num ): self.canvas.setFocusPolicy( QtCore.Qt.StrongFocus ) self.canvas.setFocus() - if sys.platform == 'darwin': - # to make a qt window pop up on top on osx, osascript can be used - # this came from http://sourceforge.net/mailarchive/message.php?msg_id=23718545 - cmd = ("""/usr/bin/osascript -e 'tell app "Finder" to set """ + \ - """frontmost of process "%s" to true'""") % \ - os.path.basename(sys.executable) - os.system(cmd) - QtCore.QObject.connect( self.window, QtCore.SIGNAL( 'destroyed()' ), self._widgetclosed ) self.window._destroying = False diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 83dd06875198..254d3e275f68 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -230,15 +230,7 @@ def filter_destroy(evt): self._master = master self._tkcanvas.focus_set() - - if sys.platform == 'darwin': - # to make a tkagg window pop up on top on osx, osascript can be used - # this came from http://sourceforge.net/mailarchive/message.php?msg_id=23718545 - cmd = ("""/usr/bin/osascript -e 'tell app "Finder" to set """ + \ - """frontmost of process "%s" to true'""") % \ - os.path.basename(sys.executable) - os.system(cmd) - + def resize(self, event): width, height = event.width, event.height if self._resize_callback is not None: