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 7dc841db4aa4..b4f72651343a 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,10 @@ 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): """ @@ -1377,7 +1379,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 +1402,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 +1428,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 +1456,6 @@ class FigureCanvasBase(object): 'close_event' ] - def __init__(self, figure): figure.set_canvas(self) self.figure = figure @@ -1657,7 +1664,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 +1751,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 +1797,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 +1919,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 +1943,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 +2084,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 +2102,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 +2151,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 +2161,7 @@ def on_press(event): def mpl_disconnect(self, cid): """ - disconnect callback id cid + Disconnect callback id cid Example usage:: @@ -2274,9 +2277,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 +2289,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 +2299,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 '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: @@ -2322,7 +2327,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,16 +2393,16 @@ 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): - '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) @@ -2414,13 +2420,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 @@ -2464,6 +2470,25 @@ 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 @@ -2488,11 +2513,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() @@ -2502,18 +2527,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() @@ -2542,8 +2567,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) @@ -2561,9 +2584,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)) @@ -2572,7 +2596,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 @@ -2604,11 +2628,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 @@ -2636,7 +2660,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: @@ -2658,17 +2682,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): @@ -2680,7 +2701,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() @@ -2695,11 +2716,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 @@ -2715,7 +2736,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: @@ -2724,7 +2745,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 @@ -2744,10 +2765,8 @@ 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 = [] @@ -2855,7 +2874,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) @@ -2871,12 +2890,10 @@ def draw(self): loc.refresh() 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 @@ -2892,9 +2909,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): @@ -2905,13 +2921,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: @@ -2938,7 +2954,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 e4f6f31742f1..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 @@ -322,16 +326,20 @@ 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 + + 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) + return key - def configure_event(self, widget, event): if _debug: print('FigureCanvasGTK.%s' % fn_name()) if widget.window is None: @@ -592,7 +600,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() @@ -624,19 +632,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 +699,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..0c5dd99aaf0a 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -259,15 +259,21 @@ 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 - return key + 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): if _debug: print 'FigureCanvasGTK3.%s' % fn_name() @@ -453,19 +459,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 +509,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_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 343bb3f033c1..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): @@ -295,20 +297,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 +317,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..08ac77b253d3 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() @@ -120,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', @@ -143,6 +147,33 @@ 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) + ] + + _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. + 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), + ] + + _ctrl_modifier = QtCore.Qt.MetaModifier + # map Qt button codes to MouseEvent's ones: buttond = {QtCore.Qt.LeftButton : 1, QtCore.Qt.MidButton : 2, @@ -150,6 +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() @@ -268,12 +300,32 @@ def minumumSizeHint( self ): def _get_key( self, event ): if event.isAutoRepeat(): return None + if event.key() < 256: - key = str(event.text()) - elif event.key() in self.keyvald: - key = self.keyvald[ event.key() ] + 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 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()) & 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) + if not int(event.modifiers()) & QtCore.Qt.ShiftModifier: + key = key.lower() + else: - key = None + key = self.keyvald.get(event.key()) + + if key is not None: + # 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 = u'{}+{}'.format(prefix, key) return key @@ -373,9 +425,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() @@ -383,6 +435,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 +452,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 @@ -441,22 +498,14 @@ 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') - + 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 8a78a44a9d81..254d3e275f68 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', @@ -174,6 +175,18 @@ 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 (i.e. no event.key_sym set) + keys on apple keyboards.""" def __init__(self, figure, master=None, resize_callback=None): FigureCanvasAgg.__init__(self, figure) @@ -217,7 +230,7 @@ def filter_destroy(evt): self._master = master self._tkcanvas.focus_set() - + def resize(self, event): width, height = event.width, event.height if self._resize_callback is not None: @@ -399,13 +412,40 @@ def _get_key(self, event): val = event.keysym_num if val in self.keyvald: key = self.keyvald[val] - elif val<256: + 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 = [(6, 'super', 'super'), + (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, key_name in modifiers: + if event.state & (1 << bitmask) and key_name not in key: + key = '{}+{}'.format(prefix, key) + return key - def key_press(self, event): key = self._get_key(event) FigureCanvasBase.key_press_event(self, key, guiEvent=event) @@ -457,7 +497,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 +516,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 +536,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 +554,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 +568,11 @@ def destroy(self, *args): def set_window_title(self, title): self.window.wm_title(title) + def full_screen_toggle(self): + is_fullscreen = bool(self.window.attributes('-fullscreen')) + self.window.attributes('-fullscreen', not is_fullscreen) + + class AxisMenu: def __init__(self, master, naxes): self._master = master @@ -595,9 +635,10 @@ def select_all(self): a.set(1) self.set_active() + class NavigationToolbar(Tk.Frame): """ - Public attriubutes + Public attributes canvas - the FigureCanvas (gtk.DrawingArea) win - the gtk.Window @@ -625,39 +666,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) @@ -722,7 +763,7 @@ def update(self): class NavigationToolbar2TkAgg(NavigationToolbar2, Tk.Frame): """ - Public attriubutes + Public attributes canvas - the FigureCanvas (gtk.DrawingArea) win - the gtk.Window @@ -762,9 +803,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 @@ -780,27 +821,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) @@ -878,3 +908,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() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 64a060d58a8c..4f942b931c69 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1242,13 +1242,20 @@ 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.AltDown, 'alt'], + [evt.ControlDown, 'ctrl'], ): + if meth(): + key = '{}+{}'.format(prefix, key) return key @@ -1476,6 +1483,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 +1750,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 +1793,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 +1837,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 +1863,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 +1930,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 +2131,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 diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 6df1d4b081f2..68fc2a8c482f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -574,14 +574,15 @@ def __call__(self, s): 'agg.path.chunksize' : [0, validate_int], # 0 to disable chunking; # recommend about 20000 to # enable. Experimental. - # key-mappings - 'keymap.fullscreen' : ['f', validate_stringlist], + # 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], 'keymap.forward' : [['right', 'v'], validate_stringlist], 'keymap.pan' : ['p', validate_stringlist], 'keymap.zoom' : ['o', validate_stringlist], - 'keymap.save' : ['s', validate_stringlist], + 'keymap.save' : [('s', 'ctrl+s'), 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/matplotlibrc.template b/matplotlibrc.template index 6ebaac28bae3..780581a4a20b 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 : 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') 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) {