diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 9b7becd5e0a1..ff8b49075893 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -19,16 +19,17 @@ matplotlib.use('GTK3Cairo') matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt -from matplotlib.backend_tools import ToolBase, ToolToggleBase +from matplotlib.backend_tools import ToolBase, ToolToggleBase, register_tool -class ListTools(ToolBase): +class ListTool(ToolBase): '''List all the tools controlled by the `ToolManager`''' # keyboard shortcut + name = "List" default_keymap = 'm' description = 'List Tools' - def trigger(self, *args, **kwargs): + def trigger(self, event): print('_' * 80) print("{0:12} {1:45} {2}".format( 'Name (id)', 'Tool description', 'Keymap')) @@ -50,6 +51,7 @@ def trigger(self, *args, **kwargs): class GroupHideTool(ToolToggleBase): '''Show lines with a given gid''' + name = "Show" default_keymap = 'G' description = 'Show by gid' default_toggled = True @@ -58,10 +60,10 @@ def __init__(self, *args, **kwargs): self.gid = kwargs.pop('gid') ToolToggleBase.__init__(self, *args, **kwargs) - def enable(self, *args): + def enable(self, event): self.set_lines_visibility(True) - def disable(self, *args): + def disable(self, event): self.set_lines_visibility(False) def set_lines_visibility(self, state): @@ -79,9 +81,8 @@ def set_lines_visibility(self, state): plt.plot([3, 2, 1], gid='mygroup') # Add the custom tools that we created -fig.canvas.manager.toolmanager.add_tool('List', ListTools) -fig.canvas.manager.toolmanager.add_tool('Show', GroupHideTool, gid='mygroup') - +fig.canvas.manager.toolmanager.add_tool(ListTool) +fig.canvas.manager.toolmanager.add_tool(GroupHideTool, gid='mygroup') # Add an existing tool to new group `foo`. # It can be added as many times as we want diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 1ad855a2944b..3cb28e20c055 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -51,8 +51,8 @@ import numpy as np from matplotlib import ( - backend_tools as tools, cbook, colors, textpath, tight_bbox, transforms, - widgets, get_backend, is_interactive, rcParams) + cbook, colors, textpath, tight_bbox, transforms, widgets, get_backend, + is_interactive, rcParams) from matplotlib._pylab_helpers import Gcf from matplotlib.transforms import Bbox, TransformedBbox, Affine2D from matplotlib.path import Path @@ -91,6 +91,12 @@ } +class Cursors(object): + """Simple namespace for cursor reference""" + HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5)) +cursors = Cursors() + + def register_backend(format, backend, description=None): """ Register a backend for saving to a given file format. @@ -2704,9 +2710,6 @@ def set_window_title(self, title): """ -cursors = tools.cursors - - class NavigationToolbar2(object): """ Base class for the navigation cursor, version 2 @@ -3282,7 +3285,7 @@ def trigger_tool(self, name): name : String Name (id) of the tool triggered from within the container """ - self.toolmanager.trigger_tool(name, sender=self) + self.toolmanager.trigger_tool(name) def add_toolitem(self, name, group, position, image, description, toggle): """ diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index ab9a503fab88..beeb49d9e3e6 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -17,17 +17,15 @@ class ToolEvent(object): """Event for tool manipulation (add/remove)""" - def __init__(self, name, sender, tool, data=None): + def __init__(self, name, tool): self.name = name - self.sender = sender self.tool = tool - self.data = data class ToolTriggerEvent(ToolEvent): """Event to inform that a tool has been triggered""" - def __init__(self, name, sender, tool, canvasevent=None, data=None): - ToolEvent.__init__(self, name, sender, tool, data) + def __init__(self, name, tool, canvasevent=None): + ToolEvent.__init__(self, name, tool) self.canvasevent = canvasevent @@ -37,9 +35,8 @@ class ToolManagerMessageEvent(object): Messages usually get displayed to the user by the toolbar """ - def __init__(self, name, sender, message): + def __init__(self, name, message): self.name = name - self.sender = sender self.message = message @@ -150,13 +147,10 @@ def toolmanager_disconnect(self, cid): """ return self._callbacks.disconnect(cid) - def message_event(self, message, sender=None): + def message_event(self, message): """ Emit a `ToolManagerMessageEvent`""" - if sender is None: - sender = self - s = 'tool_message_event' - event = ToolManagerMessageEvent(s, sender, message) + event = ToolManagerMessageEvent(s, message) self._callbacks.process(s, event) @property @@ -229,12 +223,12 @@ def remove_tool(self, name): self._remove_keys(name) s = 'tool_removed_event' - event = ToolEvent(s, self, tool) + event = ToolEvent(s, tool) self._callbacks.process(s, event) del self._tools[name] - def add_tool(self, name, tool, *args, **kwargs): + def add_tool(self, tool_cls_or_name, *args, **kwargs): """ Add *tool* to `ToolManager` @@ -244,8 +238,8 @@ def add_tool(self, name, tool, *args, **kwargs): Parameters ---------- - name : str - Name of the tool, treated as the ID, has to be unique + tool_cls_or_name : callable or name + Class or name of the tool, treated as the ID, has to be unique tool : class_like, i.e. str or type Reference to find the class of the Tool to added. @@ -258,16 +252,21 @@ def add_tool(self, name, tool, *args, **kwargs): matplotlib.backend_tools.ToolBase : The base class for tools. """ - tool_cls = self._get_cls_to_instantiate(tool) - if not tool_cls: - raise ValueError('Impossible to find class for %s' % str(tool)) + if callable(tool_cls_or_name): + tool_cls = tool_cls_or_name + elif isinstance(tool_cls_or_name, six.string_types): + tool_cls = tools.get_tool(tool_cls_or_name, type(self.canvas)) + else: + raise TypeError( + "'tool_cls_or_name' must be a callable or a tool name") + name = tool_cls.name if name in self._tools: warnings.warn('A "Tool class" with the same name already exists, ' 'not added') return self._tools[name] - tool_obj = tool_cls(self, name, *args, **kwargs) + tool_obj = tool_cls(self, *args, **kwargs) self._tools[name] = tool_obj if tool_cls.default_keymap is not None: @@ -284,7 +283,7 @@ def add_tool(self, name, tool, *args, **kwargs): # If initially toggled if tool_obj.toggled: - self._handle_toggle(tool_obj, None, None, None) + self._handle_toggle(tool_obj, None) tool_obj.set_figure(self.figure) self._tool_added_event(tool_obj) @@ -292,10 +291,10 @@ def add_tool(self, name, tool, *args, **kwargs): def _tool_added_event(self, tool): s = 'tool_added_event' - event = ToolEvent(s, self, tool) + event = ToolEvent(s, tool) self._callbacks.process(s, event) - def _handle_toggle(self, tool, sender, canvasevent, data): + def _handle_toggle(self, tool, canvasevent): """ Toggle tools, need to untoggle prior to using other Toggle tool Called from trigger_tool @@ -303,12 +302,8 @@ def _handle_toggle(self, tool, sender, canvasevent, data): Parameters ---------- tool: Tool object - sender: object - Object that wishes to trigger the tool canvasevent : Event Original Canvas event or None - data : Object - Extra data to pass to the tool when triggering """ radio_group = tool.radio_group @@ -331,34 +326,13 @@ def _handle_toggle(self, tool, sender, canvasevent, data): # Other tool in the radio_group is toggled else: # Untoggle previously toggled tool - self.trigger_tool(self._toggled[radio_group], - self, - canvasevent, - data) + self.trigger_tool(self._toggled[radio_group], canvasevent) toggled = tool.name # Keep track of the toggled tool in the radio_group self._toggled[radio_group] = toggled - def _get_cls_to_instantiate(self, callback_class): - # Find the class that corresponds to the tool - if isinstance(callback_class, six.string_types): - # FIXME: make more complete searching structure - if callback_class in globals(): - callback_class = globals()[callback_class] - else: - mod = 'backend_tools' - current_module = __import__(mod, - globals(), locals(), [mod], 1) - - callback_class = getattr(current_module, callback_class, False) - if callable(callback_class): - return callback_class - else: - return None - - def trigger_tool(self, name, sender=None, canvasevent=None, - data=None): + def trigger_tool(self, name, canvasevent=None): """ Trigger a tool and emit the tool_trigger_[name] event @@ -366,27 +340,18 @@ def trigger_tool(self, name, sender=None, canvasevent=None, ---------- name : string Name of the tool - sender: object - Object that wishes to trigger the tool canvasevent : Event Original Canvas event or None - data : Object - Extra data to pass to the tool when triggering """ tool = self.get_tool(name) if tool is None: return - - if sender is None: - sender = self - - self._trigger_tool(name, sender, canvasevent, data) - + self._trigger_tool(name, canvasevent) s = 'tool_trigger_%s' % name - event = ToolTriggerEvent(s, sender, tool, canvasevent, data) + event = ToolTriggerEvent(s, tool, canvasevent) self._callbacks.process(s, event) - def _trigger_tool(self, name, sender=None, canvasevent=None, data=None): + def _trigger_tool(self, name, canvasevent=None): """ Trigger on a tool @@ -395,11 +360,11 @@ def _trigger_tool(self, name, sender=None, canvasevent=None, data=None): tool = self.get_tool(name) if isinstance(tool, tools.ToolToggleBase): - self._handle_toggle(tool, sender, canvasevent, data) + self._handle_toggle(tool, canvasevent) # Important!!! # This is where the Tool object gets triggered - tool.trigger(sender, canvasevent, data) + tool.trigger(canvasevent) def _key_press(self, event): if event.key is None or self.keypresslock.locked(): diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 4faf372d4517..786c6c3341ce 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -11,24 +11,47 @@ `matplotlib.backend_managers.ToolManager` """ - -from matplotlib import rcParams -from matplotlib._pylab_helpers import Gcf -import matplotlib.cbook as cbook -from weakref import WeakKeyDictionary import six + +import functools import time import warnings +from weakref import WeakKeyDictionary + import numpy as np +from matplotlib import cbook, rcParams +from matplotlib._pylab_helpers import Gcf +from matplotlib.backend_bases import FigureCanvasBase, cursors + -class Cursors(object): - """Simple namespace for cursor reference""" - HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5)) -cursors = Cursors() +_registered_tools = {} -# Views positions tool -_views_positions = 'viewpos' + +def register_tool(canvas_cls, tool_cls=None): + """Declares that a class implements a tool for a certain canvas class. + + Can be used as a class decorator. + """ + if tool_cls is None: + return functools.partial(register_tool, canvas_cls) + if (tool_cls.name, canvas_cls) in _registered_tools: + raise KeyError("Tool {!r} is already registered for canvas class {!r}" + .format(tool_cls.name, canvas_cls.__name__)) + _registered_tools[tool_cls.name, canvas_cls] = tool_cls + return tool_cls + + +def get_tool(name, canvas_cls): + """Get the tool class for tool name *name* and backend *backend*. + """ + for parent in canvas_cls.__mro__: + try: + return _registered_tools[name, parent] + except KeyError: + pass + raise KeyError("Tool {!r} is not implemented for canvas class {!r}" + .format(name, canvas_cls.__name__)) class ToolBase(object): @@ -44,9 +67,12 @@ class ToolBase(object): ToolManager that controls this Tool figure: `FigureCanvas` Figure instance that is affected by this Tool - name: String - Used as **Id** of the tool, has to be unique among tools of the same - ToolManager + """ + + # name = None + """Tool **id**, must be unique. + + **String**. """ default_keymap = None @@ -73,11 +99,10 @@ class ToolBase(object): `name` is used as a label in the toolbar button """ - def __init__(self, toolmanager, name): + def __init__(self, toolmanager): warnings.warn('Treat the new Tool classes introduced in v1.5 as ' + 'experimental for now, the API will likely change in ' + 'version 2.1, and some tools might change name') - self._name = name self._toolmanager = toolmanager self._figure = None @@ -109,7 +134,7 @@ def set_figure(self, figure): """ self._figure = figure - def trigger(self, sender, event, data=None): + def trigger(self, event): """ Called when this tool gets used @@ -120,19 +145,8 @@ def trigger(self, sender, event, data=None): ---------- event: `Event` The Canvas event that caused this tool to be called - sender: object - Object that requested the tool to be triggered - data: object - Extra data """ - pass - - @property - def name(self): - """Tool Id""" - return self._name - def destroy(self): """ Destroy the tool @@ -175,7 +189,7 @@ def __init__(self, *args, **kwargs): self._toggled = kwargs.pop('toggled', self.default_toggled) ToolBase.__init__(self, *args, **kwargs) - def trigger(self, sender, event, data=None): + def trigger(self, event): """Calls `enable` or `disable` based on `toggled` value""" if self._toggled: self.disable(event) @@ -218,7 +232,7 @@ def set_figure(self, figure): toggled = self.toggled if toggled: if self.figure: - self.trigger(self, None) + self.trigger(None) else: # if no figure the internal state is not changed # we change it here so next call to trigger will change it back @@ -226,7 +240,7 @@ def set_figure(self, figure): ToolBase.set_figure(self, figure) if toggled: if figure: - self.trigger(self, None) + self.trigger(None) else: # if there is no figure, triggen wont change the internal state # we change it back @@ -240,6 +254,8 @@ class SetCursorBase(ToolBase): This tool, keeps track of all `ToolToggleBase` derived tools, and calls set_cursor when a tool gets triggered """ + name = "cursor" + def __init__(self, *args, **kwargs): ToolBase.__init__(self, *args, **kwargs) self._idDrag = None @@ -305,12 +321,15 @@ def set_cursor(self, cursor): raise NotImplementedError +@register_tool(FigureCanvasBase) class ToolCursorPosition(ToolBase): """ Send message with the current pointer position This tool runs in the background reporting the position of the cursor """ + name = "position" + def __init__(self, *args, **kwargs): self._idDrag = None ToolBase.__init__(self, *args, **kwargs) @@ -347,64 +366,42 @@ def send_message(self, event): s += ' [%s]' % a.format_cursor_data(data) message = s - self.toolmanager.message_event(message, self) - - -class RubberbandBase(ToolBase): - """Draw and remove rubberband""" - def trigger(self, sender, event, data): - """Call `draw_rubberband` or `remove_rubberband` based on data""" - if not self.figure.canvas.widgetlock.available(sender): - return - if data is not None: - self.draw_rubberband(*data) - else: - self.remove_rubberband() - - def draw_rubberband(self, *data): - """ - Draw rubberband - - This method must get implemented per backend - """ - raise NotImplementedError - - def remove_rubberband(self): - """ - Remove rubberband - - This method should get implemented per backend - """ - pass + self.toolmanager.message_event(message) +@register_tool(FigureCanvasBase) class ToolQuit(ToolBase): """Tool to call the figure manager destroy method""" + name = "quit" description = 'Quit the figure' default_keymap = rcParams['keymap.quit'] - def trigger(self, sender, event, data=None): + def trigger(self, event): Gcf.destroy_fig(self.figure) +@register_tool(FigureCanvasBase) class ToolQuitAll(ToolBase): """Tool to call the figure manager destroy method""" + name = "quit_all" description = 'Quit all figures' default_keymap = rcParams['keymap.quit_all'] - def trigger(self, sender, event, data=None): + def trigger(self, event): Gcf.destroy_all() +@register_tool(FigureCanvasBase) class ToolEnableAllNavigation(ToolBase): """Tool to enable all axes for toolmanager interaction""" + name = "allnav" description = 'Enables all axes toolmanager' default_keymap = rcParams['keymap.all_axes'] - def trigger(self, sender, event, data=None): + def trigger(self, event): if event.inaxes is None: return @@ -414,13 +411,15 @@ def trigger(self, sender, event, data=None): a.set_navigate(True) +@register_tool(FigureCanvasBase) class ToolEnableNavigation(ToolBase): """Tool to enable a specific axes for toolmanager interaction""" + name = "nav" description = 'Enables one axes toolmanager' default_keymap = (1, 2, 3, 4, 5, 6, 7, 8, 9) - def trigger(self, sender, event, data=None): + def trigger(self, event): if event.inaxes is None: return @@ -436,7 +435,7 @@ class _ToolGridBase(ToolBase): _cycle = [(False, False), (True, False), (True, True), (False, True)] - def trigger(self, sender, event, data=None): + def trigger(self, event): ax = event.inaxes if ax is None: return @@ -465,9 +464,11 @@ def _get_uniform_grid_state(ticks): return None +@register_tool(FigureCanvasBase) class ToolGrid(_ToolGridBase): """Tool to toggle the major grids of the figure""" + name = "grid" description = 'Toogle major grids' default_keymap = rcParams['keymap.grid'] @@ -486,9 +487,11 @@ def _get_next_grid_states(self, ax): y_state, "major" if y_state else "both") +@register_tool(FigureCanvasBase) class ToolMinorGrid(_ToolGridBase): """Tool to toggle the major and minor grids of the figure""" + name = "grid_minor" description = 'Toogle major and minor grids' default_keymap = rcParams['keymap.grid_minor'] @@ -506,9 +509,11 @@ def _get_next_grid_states(self, ax): return x_state, "both", y_state, "both" +@register_tool(FigureCanvasBase) class ToolFullScreen(ToolToggleBase): """Tool to toggle full screen""" + name = "fullscreen" description = 'Toogle Fullscreen mode' default_keymap = rcParams['keymap.fullscreen'] @@ -522,10 +527,10 @@ def disable(self, event): class AxisScaleBase(ToolToggleBase): """Base Tool to toggle between linear and logarithmic""" - def trigger(self, sender, event, data=None): + def trigger(self, event): if event.inaxes is None: return - ToolToggleBase.trigger(self, sender, event, data) + ToolToggleBase.trigger(self, event) def enable(self, event): self.set_scale(event.inaxes, 'log') @@ -536,19 +541,11 @@ def disable(self, event): self.figure.canvas.draw_idle() -class ToolYScale(AxisScaleBase): - """Tool to toggle between linear and logarithmic scales on the Y axis""" - - description = 'Toogle Scale Y axis' - default_keymap = rcParams['keymap.yscale'] - - def set_scale(self, ax, scale): - ax.set_yscale(scale) - - +@register_tool(FigureCanvasBase) class ToolXScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the X axis""" + name = "xscale" description = 'Toogle Scale X axis' default_keymap = rcParams['keymap.xscale'] @@ -556,6 +553,19 @@ def set_scale(self, ax, scale): ax.set_xscale(scale) +@register_tool(FigureCanvasBase) +class ToolYScale(AxisScaleBase): + """Tool to toggle between linear and logarithmic scales on the Y axis""" + + name = "yscale" + description = 'Toogle Scale Y axis' + default_keymap = rcParams['keymap.yscale'] + + def set_scale(self, ax, scale): + ax.set_yscale(scale) + + +@register_tool(FigureCanvasBase) class ToolViewsPositions(ToolBase): """ Auxiliary Tool to handle changes in views and positions @@ -570,6 +580,8 @@ class ToolViewsPositions(ToolBase): * `ToolForward` """ + name = "viewpos" + def __init__(self, *args, **kwargs): self.views = WeakKeyDictionary() self.positions = WeakKeyDictionary() @@ -713,50 +725,60 @@ class ViewsPositionsBase(ToolBase): _on_trigger = None - def trigger(self, sender, event, data=None): - self.toolmanager.get_tool(_views_positions).add_figure(self.figure) - getattr(self.toolmanager.get_tool(_views_positions), + def trigger(self, event): + self.toolmanager.get_tool("viewpos").add_figure(self.figure) + getattr(self.toolmanager.get_tool("viewpos"), self._on_trigger)() - self.toolmanager.get_tool(_views_positions).update_view() + self.toolmanager.get_tool("viewpos").update_view() +@register_tool(FigureCanvasBase) class ToolHome(ViewsPositionsBase): """Restore the original view lim""" + name = "home" description = 'Reset original view' image = 'home' default_keymap = rcParams['keymap.home'] _on_trigger = 'home' +@register_tool(FigureCanvasBase) class ToolBack(ViewsPositionsBase): """Move back up the view lim stack""" + name = "back" description = 'Back to previous view' image = 'back' default_keymap = rcParams['keymap.back'] _on_trigger = 'back' +@register_tool(FigureCanvasBase) class ToolForward(ViewsPositionsBase): """Move forward in the view lim stack""" + name = "forward" description = 'Forward to next view' image = 'forward' default_keymap = rcParams['keymap.forward'] _on_trigger = 'forward' +@register_tool(FigureCanvasBase) class ConfigureSubplotsBase(ToolBase): """Base tool for the configuration of subplots""" + name = "subplots" description = 'Configure subplots' image = 'subplots' +@register_tool(FigureCanvasBase) class SaveFigureBase(ToolBase): """Base tool for figure saving""" + name = "save" description = 'Save the figure' image = 'filesave' default_keymap = rcParams['keymap.save'] @@ -793,9 +815,9 @@ def disable(self, event): self.figure.canvas.mpl_disconnect(self._idRelease) self.figure.canvas.mpl_disconnect(self._idScroll) - def trigger(self, sender, event, data=None): - self.toolmanager.get_tool(_views_positions).add_figure(self.figure) - ToolToggleBase.trigger(self, sender, event, data) + def trigger(self, event): + self.toolmanager.get_tool("viewpos").add_figure(self.figure) + ToolToggleBase.trigger(self, event) def scroll_zoom(self, event): # https://gist.github.com/tacaswell/3144287 @@ -818,17 +840,21 @@ def scroll_zoom(self, event): # If last scroll was done within the timing threshold, delete the # previous view if (time.time()-self.lastscroll) < self.scrollthresh: - self.toolmanager.get_tool(_views_positions).back() + self.toolmanager.get_tool("viewpos").back() self.figure.canvas.draw_idle() # force re-draw self.lastscroll = time.time() - self.toolmanager.get_tool(_views_positions).push_current() + self.toolmanager.get_tool("viewpos").push_current() +@register_tool(FigureCanvasBase) class ToolZoom(ZoomPanBase): """Zoom to rectangle""" + # Subclasses should overrider _draw_rubberband and possibly + # _remove_rubberband. + name = "zoom" description = 'Zoom to rectangle' image = 'zoom_to_rect' default_keymap = rcParams['keymap.zoom'] @@ -839,11 +865,17 @@ def __init__(self, *args): ZoomPanBase.__init__(self, *args) self._ids_zoom = [] + def _draw_rubberband(self, x1, y1, x2, y2): + raise NotImplementedError + + def _remove_rubberband(self): + pass + def _cancel_action(self): for zoom_id in self._ids_zoom: self.figure.canvas.mpl_disconnect(zoom_id) - self.toolmanager.trigger_tool('rubberband', self) - self.toolmanager.get_tool(_views_positions).refresh_locators() + self._remove_rubberband() + self.toolmanager.get_tool("viewpos").refresh_locators() self._xypress = None self._button_pressed = None self._ids_zoom = [] @@ -903,8 +935,7 @@ def _mouse_move(self, event): y1, y2 = a.bbox.intervaly elif self._zoom_mode == "y": x1, x2 = a.bbox.intervalx - self.toolmanager.trigger_tool( - 'rubberband', self, data=(x1, y1, x2, y2)) + self._draw_rubberband(x1, y1, x2, y2) def _release(self, event): """the release mouse button callback in zoom to rect mode""" @@ -948,13 +979,15 @@ def _release(self, event): self._zoom_mode, twinx, twiny) self._zoom_mode = None - self.toolmanager.get_tool(_views_positions).push_current() + self.toolmanager.get_tool("viewpos").push_current() self._cancel_action() +@register_tool(FigureCanvasBase) class ToolPan(ZoomPanBase): """Pan axes with left mouse, zoom with right""" + name = "pan" default_keymap = rcParams['keymap.pan'] description = 'Pan axes with left mouse, zoom with right' image = 'move' @@ -970,7 +1003,7 @@ def _cancel_action(self): self._xypress = [] self.figure.canvas.mpl_disconnect(self._idDrag) self.toolmanager.messagelock.release(self) - self.toolmanager.get_tool(_views_positions).refresh_locators() + self.toolmanager.get_tool("viewpos").refresh_locators() def _press(self, event): if event.button == 1: @@ -1007,7 +1040,7 @@ def _release(self, event): self._cancel_action() return - self.toolmanager.get_tool(_views_positions).push_current() + self.toolmanager.get_tool("viewpos").push_current() self._cancel_action() def _mouse_move(self, event): @@ -1018,24 +1051,11 @@ def _mouse_move(self, event): self.toolmanager.canvas.draw_idle() -default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward, - 'zoom': ToolZoom, 'pan': ToolPan, - 'subplots': 'ToolConfigureSubplots', - 'save': 'ToolSaveFigure', - 'grid': ToolGrid, - 'grid_minor': ToolMinorGrid, - 'fullscreen': ToolFullScreen, - 'quit': ToolQuit, - 'quit_all': ToolQuitAll, - 'allnav': ToolEnableAllNavigation, - 'nav': ToolEnableNavigation, - 'xscale': ToolXScale, - 'yscale': ToolYScale, - 'position': ToolCursorPosition, - _views_positions: ToolViewsPositions, - 'cursor': 'ToolSetCursor', - 'rubberband': 'ToolRubberband', - } +default_tools = [ + 'home', 'back', 'forward', 'zoom', 'pan', 'subplots', 'save', + 'grid', 'grid_minor', 'fullscreen', 'quit', 'quit_all', 'allnav', 'nav', + 'xscale', 'yscale', 'position', 'viewpos', 'cursor', +] """Default tools""" default_toolbar_tools = [['navigation', ['home', 'back', 'forward']], @@ -1052,13 +1072,12 @@ def add_tools_to_manager(toolmanager, tools=default_tools): ---------- toolmanager: ToolManager `backend_managers.ToolManager` object that will get the tools added - tools : {str: class_like}, optional - The tools to add in a {name: tool} dict, see `add_tool` for more - info. + tools : List[str], optional + The tools to add. """ - for name, tool in six.iteritems(tools): - toolmanager.add_tool(name, tool) + for tool_name in tools: + toolmanager.add_tool(tool_name) def add_tools_to_container(container, tools=default_toolbar_tools): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 070056c090e1..a6fa8deaae9f 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -665,12 +665,9 @@ def get_filename_from_user (self): return filename, self.ext -class RubberbandGTK3(backend_tools.RubberbandBase): - def __init__(self, *args, **kwargs): - backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - self.ctx = None - - def draw_rubberband(self, x0, y0, x1, y1): +@backend_tools.register_tool(FigureCanvasGTK3) +class ToolZoom(backend_tools.ToolZoom): + def _draw_rubberband(self, x0, y0, x1, y1): # 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/ # Recipe/189744' self.ctx = self.figure.canvas.get_property("window").cairo_create() @@ -781,6 +778,7 @@ def set_message(self, s): self.push(self._context, s) +@backend_tools.register_tool(FigureCanvasGTK3) class SaveFigureGTK3(backend_tools.SaveFigureBase): def get_filechooser(self): @@ -793,7 +791,7 @@ def get_filechooser(self): fc.set_current_name(self.figure.canvas.get_default_filename()) return fc - def trigger(self, *args, **kwargs): + def trigger(self, event): chooser = self.get_filechooser() fname, format_ = chooser.get_filename_from_user() chooser.destroy() @@ -812,11 +810,13 @@ def trigger(self, *args, **kwargs): error_msg_gtk(str(e), parent=self) +@backend_tools.register_tool(FigureCanvasGTK3) class SetCursorGTK3(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.get_property("window").set_cursor(cursord[cursor]) +@backend_tools.register_tool(FigureCanvasGTK3) class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) @@ -865,7 +865,7 @@ def destroy(self, *args): def _get_canvas(self, fig): return self.canvas.__class__(fig) - def trigger(self, sender, event, data=None): + def trigger(self, event): self.init_window() self.window.present() @@ -897,11 +897,6 @@ def error_msg_gtk(msg, parent=None): dialog.destroy() -backend_tools.ToolSaveFigure = SaveFigureGTK3 -backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3 -backend_tools.ToolSetCursor = SetCursorGTK3 -backend_tools.ToolRubberband = RubberbandGTK3 - Toolbar = ToolbarGTK3 diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index fc2f4291be6a..dc8bfdc8ce11 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -838,11 +838,9 @@ def hidetip(self): tw.destroy() -class RubberbandTk(backend_tools.RubberbandBase): - def __init__(self, *args, **kwargs): - backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - - def draw_rubberband(self, x0, y0, x1, y1): +@backend_tools.register_tool(FigureCanvasTkAgg) +class ZoomTk(backend_tools.ToolZoom): + def _draw_rubberband(self, x0, y0, x1, y1): height = self.figure.canvas.figure.bbox.height y0 = height - y0 y1 = height - y1 @@ -851,12 +849,13 @@ def draw_rubberband(self, x0, y0, x1, y1): self.lastrect = self.figure.canvas._tkcanvas.create_rectangle( x0, y0, x1, y1) - def remove_rubberband(self): + def _remove_rubberband(self): if hasattr(self, "lastrect"): self.figure.canvas._tkcanvas.delete(self.lastrect) del self.lastrect +@backend_tools.register_tool(FigureCanvasTkAgg) class SetCursorTk(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.manager.window.configure(cursor=cursord[cursor]) @@ -956,8 +955,9 @@ def set_message(self, s): self._message.set(s) +@backend_tools.register_tool(FigureCanvasTkAgg) class SaveFigureTk(backend_tools.SaveFigureBase): - def trigger(self, *args): + def trigger(self, event): from six.moves import tkinter_tkfiledialog, tkinter_messagebox filetypes = self.figure.canvas.get_supported_filetypes().copy() default_filetype = self.figure.canvas.get_default_filetype() @@ -1003,12 +1003,13 @@ def trigger(self, *args): tkinter_messagebox.showerror("Error saving file", str(e)) +@backend_tools.register_tool(FigureCanvasTkAgg) class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) self.window = None - def trigger(self, *args): + def trigger(self, event): self.init_window() self.window.lift() @@ -1031,10 +1032,6 @@ def destroy(self, *args, **kwargs): self.window = None -backend_tools.ToolSaveFigure = SaveFigureTk -backend_tools.ToolConfigureSubplots = ConfigureSubplotsTk -backend_tools.ToolSetCursor = SetCursorTk -backend_tools.ToolRubberband = RubberbandTk Toolbar = ToolbarTk