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
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: