From 7ee87af7739630fec81cebc753b1ce71735f2464 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Dec 2017 16:27:50 -0800 Subject: [PATCH 1/7] Explicit tool registration. --- lib/matplotlib/backend_managers.py | 16 +-- lib/matplotlib/backend_tools.py | 140 ++++++++++++------- lib/matplotlib/backends/backend_gtk3.py | 9 +- lib/matplotlib/backends/backend_gtk3agg.py | 4 + lib/matplotlib/backends/backend_gtk3cairo.py | 4 + lib/matplotlib/backends/backend_tkagg.py | 8 +- 6 files changed, 113 insertions(+), 68 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index ab9a503fab88..b6c8adf435d1 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -234,7 +234,7 @@ def remove_tool(self, name): del self._tools[name] - def add_tool(self, name, tool, *args, **kwargs): + def add_tool(self, tool_name): """ Add *tool* to `ToolManager` @@ -258,20 +258,18 @@ 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)) + tool_cls = tools.get_tool(tool_name, self.canvas.__module__) - if name in self._tools: + if tool_name in self._tools: warnings.warn('A "Tool class" with the same name already exists, ' 'not added') - return self._tools[name] + return self._tools[tool_name] - tool_obj = tool_cls(self, name, *args, **kwargs) - self._tools[name] = tool_obj + tool_obj = tool_cls(self, tool_name) + self._tools[tool_name] = tool_obj if tool_cls.default_keymap is not None: - self.update_keymap(name, tool_cls.default_keymap) + self.update_keymap(tool_name, tool_cls.default_keymap) # For toggle tools init the radio_group in self._toggled if isinstance(tool_obj, tools.ToolToggleBase): diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 4faf372d4517..d6a55c31611c 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -11,24 +11,60 @@ `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 rcParams +from matplotlib._pylab_helpers import Gcf +import matplotlib.cbook as cbook + class Cursors(object): """Simple namespace for cursor reference""" HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5)) cursors = Cursors() -# Views positions tool -_views_positions = 'viewpos' + +_registered_tools = {} +_tools_inheritance = {} + + +def register_tool(name, backend="", cls=None): + """Declares that class *cls* implements tool *name* for backend *backend*. + + Can be used as a class decorator. + """ + if cls is None: + return functools.partial(register_tool, name, backend) + if (name, backend) in _registered_tools: + raise KeyError("Tool {!r} is already registered for backend {!r}" + .format(name, backend)) + _registered_tools[name, backend] = cls + return cls + + +def _inherit_tools(child, parent): + """Declares that backend *child* should default to the tools of *parent*. + """ + _tools_inheritance[child] = parent + + +def get_tool(name, backend): + """Get the tool class for tool name *name* and backend *backend*. + """ + try: + return _registered_tools[name, backend] + except KeyError: + try: + return _registered_tools[name, _tools_inheritance[backend]] + except KeyError: + return _registered_tools[name, ""] class ToolBase(object): @@ -305,6 +341,7 @@ def set_cursor(self, cursor): raise NotImplementedError +@register_tool("position") class ToolCursorPosition(ToolBase): """ Send message with the current pointer position @@ -378,6 +415,7 @@ def remove_rubberband(self): pass +@register_tool("quit") class ToolQuit(ToolBase): """Tool to call the figure manager destroy method""" @@ -388,6 +426,7 @@ def trigger(self, sender, event, data=None): Gcf.destroy_fig(self.figure) +@register_tool("quit_all") class ToolQuitAll(ToolBase): """Tool to call the figure manager destroy method""" @@ -398,6 +437,7 @@ def trigger(self, sender, event, data=None): Gcf.destroy_all() +@register_tool("allnav") class ToolEnableAllNavigation(ToolBase): """Tool to enable all axes for toolmanager interaction""" @@ -414,6 +454,7 @@ def trigger(self, sender, event, data=None): a.set_navigate(True) +@register_tool("nav") class ToolEnableNavigation(ToolBase): """Tool to enable a specific axes for toolmanager interaction""" @@ -465,6 +506,7 @@ def _get_uniform_grid_state(ticks): return None +@register_tool("grid") class ToolGrid(_ToolGridBase): """Tool to toggle the major grids of the figure""" @@ -486,6 +528,7 @@ def _get_next_grid_states(self, ax): y_state, "major" if y_state else "both") +@register_tool("grid_minor") class ToolMinorGrid(_ToolGridBase): """Tool to toggle the major and minor grids of the figure""" @@ -506,6 +549,7 @@ def _get_next_grid_states(self, ax): return x_state, "both", y_state, "both" +@register_tool("fullscreen") class ToolFullScreen(ToolToggleBase): """Tool to toggle full screen""" @@ -536,16 +580,7 @@ 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("xscale") class ToolXScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the X axis""" @@ -556,6 +591,18 @@ def set_scale(self, ax, scale): ax.set_xscale(scale) +@register_tool("yscale") +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("viewpos") class ToolViewsPositions(ToolBase): """ Auxiliary Tool to handle changes in views and positions @@ -714,12 +761,13 @@ 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), + 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("home") class ToolHome(ViewsPositionsBase): """Restore the original view lim""" @@ -729,6 +777,7 @@ class ToolHome(ViewsPositionsBase): _on_trigger = 'home' +@register_tool("back") class ToolBack(ViewsPositionsBase): """Move back up the view lim stack""" @@ -738,6 +787,7 @@ class ToolBack(ViewsPositionsBase): _on_trigger = 'back' +@register_tool("forward") class ToolForward(ViewsPositionsBase): """Move forward in the view lim stack""" @@ -747,6 +797,7 @@ class ToolForward(ViewsPositionsBase): _on_trigger = 'forward' +@register_tool("subplots") class ConfigureSubplotsBase(ToolBase): """Base tool for the configuration of subplots""" @@ -754,6 +805,7 @@ class ConfigureSubplotsBase(ToolBase): image = 'subplots' +@register_tool("save") class SaveFigureBase(ToolBase): """Base tool for figure saving""" @@ -794,7 +846,7 @@ def disable(self, event): self.figure.canvas.mpl_disconnect(self._idScroll) def trigger(self, sender, event, data=None): - self.toolmanager.get_tool(_views_positions).add_figure(self.figure) + self.toolmanager.get_tool("viewpos").add_figure(self.figure) ToolToggleBase.trigger(self, sender, event, data) def scroll_zoom(self, event): @@ -818,14 +870,15 @@ 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("zoom") class ToolZoom(ZoomPanBase): """Zoom to rectangle""" @@ -843,7 +896,7 @@ 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.toolmanager.get_tool("viewpos").refresh_locators() self._xypress = None self._button_pressed = None self._ids_zoom = [] @@ -948,10 +1001,11 @@ 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("pan") class ToolPan(ZoomPanBase): """Pan axes with left mouse, zoom with right""" @@ -970,7 +1024,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 +1061,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 +1072,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', 'rubberband', +] """Default tools""" default_toolbar_tools = [['navigation', ['home', 'back', 'forward']], @@ -1052,13 +1093,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..b0668053f439 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -665,6 +665,7 @@ def get_filename_from_user (self): return filename, self.ext +@backend_tools.register_tool("rubberband") class RubberbandGTK3(backend_tools.RubberbandBase): def __init__(self, *args, **kwargs): backend_tools.RubberbandBase.__init__(self, *args, **kwargs) @@ -781,6 +782,7 @@ def set_message(self, s): self.push(self._context, s) +@backend_tools.register_tool("save", __name__) class SaveFigureGTK3(backend_tools.SaveFigureBase): def get_filechooser(self): @@ -812,11 +814,13 @@ def trigger(self, *args, **kwargs): error_msg_gtk(str(e), parent=self) +@backend_tools.register_tool("cursor", __name__) class SetCursorGTK3(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.get_property("window").set_cursor(cursord[cursor]) +@backend_tools.register_tool("subplots", __name__) class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) @@ -897,11 +901,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_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 53c625b8a50f..41ec18bff33a 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -6,6 +6,7 @@ import numpy as np import warnings +from .. import backend_tools from . import backend_agg, backend_gtk3 from .backend_cairo import cairo, HAS_CAIRO_CFFI from .backend_gtk3 import _BackendGTK3 @@ -96,6 +97,9 @@ class FigureManagerGTK3Agg(backend_gtk3.FigureManagerGTK3): pass +backend_tools._inherit_tools(__name__, backend_gtk3.__name__) + + @_BackendGTK3.export class _BackendGTK3Cairo(_BackendGTK3): FigureCanvas = FigureCanvasGTK3Agg diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 7b55f0e8007c..13349e7fbe02 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -3,6 +3,7 @@ import six +from .. import backend_tools from . import backend_cairo, backend_gtk3 from .backend_cairo import cairo, HAS_CAIRO_CFFI from .backend_gtk3 import _BackendGTK3 @@ -49,6 +50,9 @@ class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): pass +backend_tools._inherit_tools(__name__, backend_gtk3.__name__) + + @_BackendGTK3.export class _BackendGTK3Cairo(_BackendGTK3): FigureCanvas = FigureCanvasGTK3Cairo diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index fc2f4291be6a..cf15972afc9b 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -838,6 +838,7 @@ def hidetip(self): tw.destroy() +@backend_tools.register_tool("rubberband", __name__) class RubberbandTk(backend_tools.RubberbandBase): def __init__(self, *args, **kwargs): backend_tools.RubberbandBase.__init__(self, *args, **kwargs) @@ -857,6 +858,7 @@ def remove_rubberband(self): del self.lastrect +@backend_tools.register_tool("cursor", __name__) class SetCursorTk(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.manager.window.configure(cursor=cursord[cursor]) @@ -956,6 +958,7 @@ def set_message(self, s): self._message.set(s) +@backend_tools.register_tool("save", __name__) class SaveFigureTk(backend_tools.SaveFigureBase): def trigger(self, *args): from six.moves import tkinter_tkfiledialog, tkinter_messagebox @@ -1003,6 +1006,7 @@ def trigger(self, *args): tkinter_messagebox.showerror("Error saving file", str(e)) +@backend_tools.register_tool("subplots", __name__) class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) @@ -1031,10 +1035,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 From fc1e1a8683c34b5315a643e89c71226401d1425e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Dec 2017 18:25:50 -0800 Subject: [PATCH 2/7] Remove Rubberband tool and data kwarg to trigger. --- lib/matplotlib/backend_managers.py | 37 +++++-------- lib/matplotlib/backend_tools.py | 69 ++++++++---------------- lib/matplotlib/backends/backend_gtk3.py | 12 ++--- lib/matplotlib/backends/backend_tkagg.py | 11 ++-- 4 files changed, 45 insertions(+), 84 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index b6c8adf435d1..41596d20ef79 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -17,17 +17,16 @@ class ToolEvent(object): """Event for tool manipulation (add/remove)""" - def __init__(self, name, sender, tool, data=None): + def __init__(self, name, sender, 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, sender, tool, canvasevent=None): + ToolEvent.__init__(self, name, sender, tool) self.canvasevent = canvasevent @@ -234,7 +233,7 @@ def remove_tool(self, name): del self._tools[name] - def add_tool(self, tool_name): + def add_tool(self, tool_name, *args, **kwargs): """ Add *tool* to `ToolManager` @@ -265,7 +264,7 @@ def add_tool(self, tool_name): 'not added') return self._tools[tool_name] - tool_obj = tool_cls(self, tool_name) + tool_obj = tool_cls(self, tool_name, *args, **kwargs) self._tools[tool_name] = tool_obj if tool_cls.default_keymap is not None: @@ -282,7 +281,7 @@ def add_tool(self, tool_name): # If initially toggled if tool_obj.toggled: - self._handle_toggle(tool_obj, None, None, None) + self._handle_toggle(tool_obj, None, None) tool_obj.set_figure(self.figure) self._tool_added_event(tool_obj) @@ -293,7 +292,7 @@ def _tool_added_event(self, tool): event = ToolEvent(s, self, tool) self._callbacks.process(s, event) - def _handle_toggle(self, tool, sender, canvasevent, data): + def _handle_toggle(self, tool, sender, canvasevent): """ Toggle tools, need to untoggle prior to using other Toggle tool Called from trigger_tool @@ -305,8 +304,6 @@ def _handle_toggle(self, tool, sender, canvasevent, data): 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 @@ -329,10 +326,7 @@ 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], self, canvasevent) toggled = tool.name # Keep track of the toggled tool in the radio_group @@ -355,8 +349,7 @@ def _get_cls_to_instantiate(self, callback_class): else: return None - def trigger_tool(self, name, sender=None, canvasevent=None, - data=None): + def trigger_tool(self, name, sender=None, canvasevent=None): """ Trigger a tool and emit the tool_trigger_[name] event @@ -368,8 +361,6 @@ def trigger_tool(self, name, sender=None, canvasevent=None, 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: @@ -378,13 +369,13 @@ def trigger_tool(self, name, sender=None, canvasevent=None, if sender is None: sender = self - self._trigger_tool(name, sender, canvasevent, data) + self._trigger_tool(name, sender, canvasevent) s = 'tool_trigger_%s' % name - event = ToolTriggerEvent(s, sender, tool, canvasevent, data) + event = ToolTriggerEvent(s, sender, tool, canvasevent) self._callbacks.process(s, event) - def _trigger_tool(self, name, sender=None, canvasevent=None, data=None): + def _trigger_tool(self, name, sender=None, canvasevent=None): """ Trigger on a tool @@ -393,11 +384,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, sender, canvasevent) # Important!!! # This is where the Tool object gets triggered - tool.trigger(sender, canvasevent, data) + tool.trigger(sender, 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 d6a55c31611c..e99e96a145ec 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -145,7 +145,7 @@ def set_figure(self, figure): """ self._figure = figure - def trigger(self, sender, event, data=None): + def trigger(self, sender, event): """ Called when this tool gets used @@ -158,8 +158,6 @@ def trigger(self, sender, event, data=None): 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 @@ -211,7 +209,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, sender, event): """Calls `enable` or `disable` based on `toggled` value""" if self._toggled: self.disable(event) @@ -387,34 +385,6 @@ def send_message(self, event): 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 - - @register_tool("quit") class ToolQuit(ToolBase): """Tool to call the figure manager destroy method""" @@ -422,7 +392,7 @@ class ToolQuit(ToolBase): description = 'Quit the figure' default_keymap = rcParams['keymap.quit'] - def trigger(self, sender, event, data=None): + def trigger(self, sender, event): Gcf.destroy_fig(self.figure) @@ -433,7 +403,7 @@ class ToolQuitAll(ToolBase): description = 'Quit all figures' default_keymap = rcParams['keymap.quit_all'] - def trigger(self, sender, event, data=None): + def trigger(self, sender, event): Gcf.destroy_all() @@ -444,7 +414,7 @@ class ToolEnableAllNavigation(ToolBase): description = 'Enables all axes toolmanager' default_keymap = rcParams['keymap.all_axes'] - def trigger(self, sender, event, data=None): + def trigger(self, sender, event): if event.inaxes is None: return @@ -461,7 +431,7 @@ class ToolEnableNavigation(ToolBase): 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, sender, event): if event.inaxes is None: return @@ -477,7 +447,7 @@ class _ToolGridBase(ToolBase): _cycle = [(False, False), (True, False), (True, True), (False, True)] - def trigger(self, sender, event, data=None): + def trigger(self, sender, event): ax = event.inaxes if ax is None: return @@ -566,10 +536,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, sender, 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') @@ -760,7 +730,7 @@ class ViewsPositionsBase(ToolBase): _on_trigger = None - def trigger(self, sender, event, data=None): + def trigger(self, sender, event): self.toolmanager.get_tool("viewpos").add_figure(self.figure) getattr(self.toolmanager.get_tool("viewpos"), self._on_trigger)() @@ -845,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): + def trigger(self, sender, event): self.toolmanager.get_tool("viewpos").add_figure(self.figure) - ToolToggleBase.trigger(self, sender, event, data) + ToolToggleBase.trigger(self, event) def scroll_zoom(self, event): # https://gist.github.com/tacaswell/3144287 @@ -881,6 +851,8 @@ def scroll_zoom(self, event): @register_tool("zoom") class ToolZoom(ZoomPanBase): """Zoom to rectangle""" + # Subclasses should overrider _draw_rubberband and possibly + # _remove_rubberband. description = 'Zoom to rectangle' image = 'zoom_to_rect' @@ -892,10 +864,16 @@ 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._remove_rubberband() self.toolmanager.get_tool("viewpos").refresh_locators() self._xypress = None self._button_pressed = None @@ -956,8 +934,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""" @@ -1075,7 +1052,7 @@ def _mouse_move(self, event): default_tools = [ 'home', 'back', 'forward', 'zoom', 'pan', 'subplots', 'save', 'grid', 'grid_minor', 'fullscreen', 'quit', 'quit_all', 'allnav', 'nav', - 'xscale', 'yscale', 'position', 'viewpos', 'cursor', 'rubberband', + 'xscale', 'yscale', 'position', 'viewpos', 'cursor', ] """Default tools""" diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index b0668053f439..d30eb53ae0d5 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -665,13 +665,9 @@ def get_filename_from_user (self): return filename, self.ext -@backend_tools.register_tool("rubberband") -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("zoom", __name__) +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() @@ -869,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, sender, event): self.init_window() self.window.present() diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index cf15972afc9b..b18ef687c8cc 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -838,12 +838,9 @@ def hidetip(self): tw.destroy() -@backend_tools.register_tool("rubberband", __name__) -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("zoom", __name__) +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 @@ -852,7 +849,7 @@ 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 From 3a0ba93f314b02981b9619e56647fe4b6dd89c57 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Dec 2017 18:29:03 -0800 Subject: [PATCH 3/7] Remove sender argument to trigger. --- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backend_managers.py | 48 +++++++++--------------- lib/matplotlib/backend_tools.py | 30 +++++++-------- lib/matplotlib/backends/backend_gtk3.py | 4 +- lib/matplotlib/backends/backend_tkagg.py | 4 +- 5 files changed, 35 insertions(+), 53 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 1ad855a2944b..c5adb1f946de 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3282,7 +3282,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 41596d20ef79..a3499730eed5 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -17,16 +17,15 @@ class ToolEvent(object): """Event for tool manipulation (add/remove)""" - def __init__(self, name, sender, tool): + def __init__(self, name, tool): self.name = name - self.sender = sender self.tool = tool class ToolTriggerEvent(ToolEvent): """Event to inform that a tool has been triggered""" - def __init__(self, name, sender, tool, canvasevent=None): - ToolEvent.__init__(self, name, sender, tool) + def __init__(self, name, tool, canvasevent=None): + ToolEvent.__init__(self, name, tool) self.canvasevent = canvasevent @@ -36,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 @@ -149,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 @@ -228,7 +223,7 @@ 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] @@ -281,7 +276,7 @@ def add_tool(self, tool_name, *args, **kwargs): # If initially toggled if tool_obj.toggled: - self._handle_toggle(tool_obj, None, None) + self._handle_toggle(tool_obj, None) tool_obj.set_figure(self.figure) self._tool_added_event(tool_obj) @@ -289,10 +284,10 @@ def add_tool(self, tool_name, *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): + def _handle_toggle(self, tool, canvasevent): """ Toggle tools, need to untoggle prior to using other Toggle tool Called from trigger_tool @@ -300,8 +295,6 @@ def _handle_toggle(self, tool, sender, canvasevent): Parameters ---------- tool: Tool object - sender: object - Object that wishes to trigger the tool canvasevent : Event Original Canvas event or None """ @@ -326,7 +319,7 @@ def _handle_toggle(self, tool, sender, canvasevent): # Other tool in the radio_group is toggled else: # Untoggle previously toggled tool - self.trigger_tool(self._toggled[radio_group], self, canvasevent) + self.trigger_tool(self._toggled[radio_group], canvasevent) toggled = tool.name # Keep track of the toggled tool in the radio_group @@ -349,7 +342,7 @@ def _get_cls_to_instantiate(self, callback_class): else: return None - def trigger_tool(self, name, sender=None, canvasevent=None): + def trigger_tool(self, name, canvasevent=None): """ Trigger a tool and emit the tool_trigger_[name] event @@ -357,25 +350,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 """ tool = self.get_tool(name) if tool is None: return - - if sender is None: - sender = self - - self._trigger_tool(name, sender, canvasevent) - + self._trigger_tool(name, canvasevent) s = 'tool_trigger_%s' % name - event = ToolTriggerEvent(s, sender, tool, canvasevent) + event = ToolTriggerEvent(s, tool, canvasevent) self._callbacks.process(s, event) - def _trigger_tool(self, name, sender=None, canvasevent=None): + def _trigger_tool(self, name, canvasevent=None): """ Trigger on a tool @@ -384,11 +370,11 @@ def _trigger_tool(self, name, sender=None, canvasevent=None): tool = self.get_tool(name) if isinstance(tool, tools.ToolToggleBase): - self._handle_toggle(tool, sender, canvasevent) + self._handle_toggle(tool, canvasevent) # Important!!! # This is where the Tool object gets triggered - tool.trigger(sender, canvasevent) + 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 e99e96a145ec..fef1f9eddd97 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -145,7 +145,7 @@ def set_figure(self, figure): """ self._figure = figure - def trigger(self, sender, event): + def trigger(self, event): """ Called when this tool gets used @@ -156,12 +156,8 @@ def trigger(self, sender, event): ---------- event: `Event` The Canvas event that caused this tool to be called - sender: object - Object that requested the tool to be triggered """ - pass - @property def name(self): """Tool Id""" @@ -209,7 +205,7 @@ def __init__(self, *args, **kwargs): self._toggled = kwargs.pop('toggled', self.default_toggled) ToolBase.__init__(self, *args, **kwargs) - def trigger(self, sender, event): + def trigger(self, event): """Calls `enable` or `disable` based on `toggled` value""" if self._toggled: self.disable(event) @@ -252,7 +248,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 @@ -260,7 +256,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 @@ -382,7 +378,7 @@ def send_message(self, event): s += ' [%s]' % a.format_cursor_data(data) message = s - self.toolmanager.message_event(message, self) + self.toolmanager.message_event(message) @register_tool("quit") @@ -392,7 +388,7 @@ class ToolQuit(ToolBase): description = 'Quit the figure' default_keymap = rcParams['keymap.quit'] - def trigger(self, sender, event): + def trigger(self, event): Gcf.destroy_fig(self.figure) @@ -403,7 +399,7 @@ class ToolQuitAll(ToolBase): description = 'Quit all figures' default_keymap = rcParams['keymap.quit_all'] - def trigger(self, sender, event): + def trigger(self, event): Gcf.destroy_all() @@ -414,7 +410,7 @@ class ToolEnableAllNavigation(ToolBase): description = 'Enables all axes toolmanager' default_keymap = rcParams['keymap.all_axes'] - def trigger(self, sender, event): + def trigger(self, event): if event.inaxes is None: return @@ -431,7 +427,7 @@ class ToolEnableNavigation(ToolBase): description = 'Enables one axes toolmanager' default_keymap = (1, 2, 3, 4, 5, 6, 7, 8, 9) - def trigger(self, sender, event): + def trigger(self, event): if event.inaxes is None: return @@ -447,7 +443,7 @@ class _ToolGridBase(ToolBase): _cycle = [(False, False), (True, False), (True, True), (False, True)] - def trigger(self, sender, event): + def trigger(self, event): ax = event.inaxes if ax is None: return @@ -536,7 +532,7 @@ def disable(self, event): class AxisScaleBase(ToolToggleBase): """Base Tool to toggle between linear and logarithmic""" - def trigger(self, sender, event): + def trigger(self, event): if event.inaxes is None: return ToolToggleBase.trigger(self, event) @@ -730,7 +726,7 @@ class ViewsPositionsBase(ToolBase): _on_trigger = None - def trigger(self, sender, event): + def trigger(self, event): self.toolmanager.get_tool("viewpos").add_figure(self.figure) getattr(self.toolmanager.get_tool("viewpos"), self._on_trigger)() @@ -815,7 +811,7 @@ def disable(self, event): self.figure.canvas.mpl_disconnect(self._idRelease) self.figure.canvas.mpl_disconnect(self._idScroll) - def trigger(self, sender, event): + def trigger(self, event): self.toolmanager.get_tool("viewpos").add_figure(self.figure) ToolToggleBase.trigger(self, event) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index d30eb53ae0d5..648e731146f0 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -791,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() @@ -865,7 +865,7 @@ def destroy(self, *args): def _get_canvas(self, fig): return self.canvas.__class__(fig) - def trigger(self, sender, event): + def trigger(self, event): self.init_window() self.window.present() diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index b18ef687c8cc..81617806b7af 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -957,7 +957,7 @@ def set_message(self, s): @backend_tools.register_tool("save", __name__) 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() @@ -1009,7 +1009,7 @@ 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() From 6e3122a96e2bc9649796fc0e42ef4d82b452c679 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Dec 2017 18:41:20 -0800 Subject: [PATCH 4/7] Fix example. --- examples/user_interfaces/toolmanager_sgskip.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 9b7becd5e0a1..9347ba9faa22 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 +@register_tool("List") class ListTools(ToolBase): '''List all the tools controlled by the `ToolManager`''' # keyboard shortcut 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')) @@ -48,6 +49,7 @@ def trigger(self, *args, **kwargs): print("{0:12} {1:45}".format(str(group), str(active))) +@register_tool("Show") class GroupHideTool(ToolToggleBase): '''Show lines with a given gid''' default_keymap = 'G' @@ -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('List') +fig.canvas.manager.toolmanager.add_tool('Show', gid='mygroup') # Add an existing tool to new group `foo`. # It can be added as many times as we want From a1e88f4094403e4f08346e40e2b7161e5a8be00e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 6 Dec 2017 09:41:13 -0800 Subject: [PATCH 5/7] Remove _get_cls_to_instantiate. --- lib/matplotlib/backend_managers.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index a3499730eed5..70ec97b0bf5b 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -325,23 +325,6 @@ def _handle_toggle(self, tool, canvasevent): # 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, canvasevent=None): """ Trigger a tool and emit the tool_trigger_[name] event From 969f311dd4fbcc2e418cf92c7610593c91897d7a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 6 Dec 2017 11:07:29 -0800 Subject: [PATCH 6/7] Dispatch tool on canvas class. --- lib/matplotlib/backend_bases.py | 13 ++-- lib/matplotlib/backend_managers.py | 2 +- lib/matplotlib/backend_tools.py | 79 ++++++++------------ lib/matplotlib/backends/backend_gtk3.py | 8 +- lib/matplotlib/backends/backend_gtk3agg.py | 4 - lib/matplotlib/backends/backend_gtk3cairo.py | 4 - lib/matplotlib/backends/backend_tkagg.py | 8 +- 7 files changed, 50 insertions(+), 68 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index c5adb1f946de..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 diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 70ec97b0bf5b..8fd1edf20f87 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -252,7 +252,7 @@ def add_tool(self, tool_name, *args, **kwargs): matplotlib.backend_tools.ToolBase : The base class for tools. """ - tool_cls = tools.get_tool(tool_name, self.canvas.__module__) + tool_cls = tools.get_tool(tool_name, type(self.canvas)) if tool_name in self._tools: warnings.warn('A "Tool class" with the same name already exists, ' diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index fef1f9eddd97..cdbfdef75474 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -20,51 +20,38 @@ import numpy as np -from matplotlib import rcParams +from matplotlib import cbook, rcParams from matplotlib._pylab_helpers import Gcf -import matplotlib.cbook as cbook - - -class Cursors(object): - """Simple namespace for cursor reference""" - HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5)) -cursors = Cursors() +from matplotlib.backend_bases import FigureCanvasBase, cursors _registered_tools = {} -_tools_inheritance = {} -def register_tool(name, backend="", cls=None): - """Declares that class *cls* implements tool *name* for backend *backend*. +def register_tool(name, canvas_cls, cls=None): + """Declares that a class implements a tool for a certain canvas class. Can be used as a class decorator. """ if cls is None: - return functools.partial(register_tool, name, backend) - if (name, backend) in _registered_tools: - raise KeyError("Tool {!r} is already registered for backend {!r}" - .format(name, backend)) - _registered_tools[name, backend] = cls + return functools.partial(register_tool, name, canvas_cls) + if (name, canvas_cls) in _registered_tools: + raise KeyError("Tool {!r} is already registered for canvas class {!r}" + .format(name, canvas_cls.__name__)) + _registered_tools[name, canvas_cls] = cls return cls -def _inherit_tools(child, parent): - """Declares that backend *child* should default to the tools of *parent*. - """ - _tools_inheritance[child] = parent - - -def get_tool(name, backend): +def get_tool(name, canvas_cls): """Get the tool class for tool name *name* and backend *backend*. """ - try: - return _registered_tools[name, backend] - except KeyError: + for parent in canvas_cls.__mro__: try: - return _registered_tools[name, _tools_inheritance[backend]] + return _registered_tools[name, parent] except KeyError: - return _registered_tools[name, ""] + pass + raise KeyError("Tool {!r} is not implemented for canvas class {!r}" + .format(name, canvas_cls.__name__)) class ToolBase(object): @@ -335,7 +322,7 @@ def set_cursor(self, cursor): raise NotImplementedError -@register_tool("position") +@register_tool("position", FigureCanvasBase) class ToolCursorPosition(ToolBase): """ Send message with the current pointer position @@ -381,7 +368,7 @@ def send_message(self, event): self.toolmanager.message_event(message) -@register_tool("quit") +@register_tool("quit", FigureCanvasBase) class ToolQuit(ToolBase): """Tool to call the figure manager destroy method""" @@ -392,7 +379,7 @@ def trigger(self, event): Gcf.destroy_fig(self.figure) -@register_tool("quit_all") +@register_tool("quit_all", FigureCanvasBase) class ToolQuitAll(ToolBase): """Tool to call the figure manager destroy method""" @@ -403,7 +390,7 @@ def trigger(self, event): Gcf.destroy_all() -@register_tool("allnav") +@register_tool("allnav", FigureCanvasBase) class ToolEnableAllNavigation(ToolBase): """Tool to enable all axes for toolmanager interaction""" @@ -420,7 +407,7 @@ def trigger(self, event): a.set_navigate(True) -@register_tool("nav") +@register_tool("nav", FigureCanvasBase) class ToolEnableNavigation(ToolBase): """Tool to enable a specific axes for toolmanager interaction""" @@ -472,7 +459,7 @@ def _get_uniform_grid_state(ticks): return None -@register_tool("grid") +@register_tool("grid", FigureCanvasBase) class ToolGrid(_ToolGridBase): """Tool to toggle the major grids of the figure""" @@ -494,7 +481,7 @@ def _get_next_grid_states(self, ax): y_state, "major" if y_state else "both") -@register_tool("grid_minor") +@register_tool("grid_minor", FigureCanvasBase) class ToolMinorGrid(_ToolGridBase): """Tool to toggle the major and minor grids of the figure""" @@ -515,7 +502,7 @@ def _get_next_grid_states(self, ax): return x_state, "both", y_state, "both" -@register_tool("fullscreen") +@register_tool("fullscreen", FigureCanvasBase) class ToolFullScreen(ToolToggleBase): """Tool to toggle full screen""" @@ -546,7 +533,7 @@ def disable(self, event): self.figure.canvas.draw_idle() -@register_tool("xscale") +@register_tool("xscale", FigureCanvasBase) class ToolXScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the X axis""" @@ -557,7 +544,7 @@ def set_scale(self, ax, scale): ax.set_xscale(scale) -@register_tool("yscale") +@register_tool("yscale", FigureCanvasBase) class ToolYScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the Y axis""" @@ -568,7 +555,7 @@ def set_scale(self, ax, scale): ax.set_yscale(scale) -@register_tool("viewpos") +@register_tool("viewpos", FigureCanvasBase) class ToolViewsPositions(ToolBase): """ Auxiliary Tool to handle changes in views and positions @@ -733,7 +720,7 @@ def trigger(self, event): self.toolmanager.get_tool("viewpos").update_view() -@register_tool("home") +@register_tool("home", FigureCanvasBase) class ToolHome(ViewsPositionsBase): """Restore the original view lim""" @@ -743,7 +730,7 @@ class ToolHome(ViewsPositionsBase): _on_trigger = 'home' -@register_tool("back") +@register_tool("back", FigureCanvasBase) class ToolBack(ViewsPositionsBase): """Move back up the view lim stack""" @@ -753,7 +740,7 @@ class ToolBack(ViewsPositionsBase): _on_trigger = 'back' -@register_tool("forward") +@register_tool("forward", FigureCanvasBase) class ToolForward(ViewsPositionsBase): """Move forward in the view lim stack""" @@ -763,7 +750,7 @@ class ToolForward(ViewsPositionsBase): _on_trigger = 'forward' -@register_tool("subplots") +@register_tool("subplots", FigureCanvasBase) class ConfigureSubplotsBase(ToolBase): """Base tool for the configuration of subplots""" @@ -771,7 +758,7 @@ class ConfigureSubplotsBase(ToolBase): image = 'subplots' -@register_tool("save") +@register_tool("save", FigureCanvasBase) class SaveFigureBase(ToolBase): """Base tool for figure saving""" @@ -844,7 +831,7 @@ def scroll_zoom(self, event): self.toolmanager.get_tool("viewpos").push_current() -@register_tool("zoom") +@register_tool("zoom", FigureCanvasBase) class ToolZoom(ZoomPanBase): """Zoom to rectangle""" # Subclasses should overrider _draw_rubberband and possibly @@ -978,7 +965,7 @@ def _release(self, event): self._cancel_action() -@register_tool("pan") +@register_tool("pan", FigureCanvasBase) class ToolPan(ZoomPanBase): """Pan axes with left mouse, zoom with right""" diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 648e731146f0..8a3098dc3c19 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -665,7 +665,7 @@ def get_filename_from_user (self): return filename, self.ext -@backend_tools.register_tool("zoom", __name__) +@backend_tools.register_tool("zoom", FigureCanvasGTK3) class ToolZoom(backend_tools.ToolZoom): def _draw_rubberband(self, x0, y0, x1, y1): # 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/ @@ -778,7 +778,7 @@ def set_message(self, s): self.push(self._context, s) -@backend_tools.register_tool("save", __name__) +@backend_tools.register_tool("save", FigureCanvasGTK3) class SaveFigureGTK3(backend_tools.SaveFigureBase): def get_filechooser(self): @@ -810,13 +810,13 @@ def trigger(self, event): error_msg_gtk(str(e), parent=self) -@backend_tools.register_tool("cursor", __name__) +@backend_tools.register_tool("cursor", 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("subplots", __name__) +@backend_tools.register_tool("subplots", FigureCanvasGTK3) class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 41ec18bff33a..53c625b8a50f 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -6,7 +6,6 @@ import numpy as np import warnings -from .. import backend_tools from . import backend_agg, backend_gtk3 from .backend_cairo import cairo, HAS_CAIRO_CFFI from .backend_gtk3 import _BackendGTK3 @@ -97,9 +96,6 @@ class FigureManagerGTK3Agg(backend_gtk3.FigureManagerGTK3): pass -backend_tools._inherit_tools(__name__, backend_gtk3.__name__) - - @_BackendGTK3.export class _BackendGTK3Cairo(_BackendGTK3): FigureCanvas = FigureCanvasGTK3Agg diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 13349e7fbe02..7b55f0e8007c 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -3,7 +3,6 @@ import six -from .. import backend_tools from . import backend_cairo, backend_gtk3 from .backend_cairo import cairo, HAS_CAIRO_CFFI from .backend_gtk3 import _BackendGTK3 @@ -50,9 +49,6 @@ class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): pass -backend_tools._inherit_tools(__name__, backend_gtk3.__name__) - - @_BackendGTK3.export class _BackendGTK3Cairo(_BackendGTK3): FigureCanvas = FigureCanvasGTK3Cairo diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 81617806b7af..cf39225e21b7 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -838,7 +838,7 @@ def hidetip(self): tw.destroy() -@backend_tools.register_tool("zoom", __name__) +@backend_tools.register_tool("zoom", FigureCanvasTkAgg) class ZoomTk(backend_tools.ToolZoom): def _draw_rubberband(self, x0, y0, x1, y1): height = self.figure.canvas.figure.bbox.height @@ -855,7 +855,7 @@ def _remove_rubberband(self): del self.lastrect -@backend_tools.register_tool("cursor", __name__) +@backend_tools.register_tool("cursor", FigureCanvasTkAgg) class SetCursorTk(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.manager.window.configure(cursor=cursord[cursor]) @@ -955,7 +955,7 @@ def set_message(self, s): self._message.set(s) -@backend_tools.register_tool("save", __name__) +@backend_tools.register_tool("save", FigureCanvasTkAgg) class SaveFigureTk(backend_tools.SaveFigureBase): def trigger(self, event): from six.moves import tkinter_tkfiledialog, tkinter_messagebox @@ -1003,7 +1003,7 @@ def trigger(self, event): tkinter_messagebox.showerror("Error saving file", str(e)) -@backend_tools.register_tool("subplots", __name__) +@backend_tools.register_tool("subplots", FigureCanvasTkAgg) class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) From 5eae7bf9aebdae01b0861ac218fa2273e1f0cc1f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 6 Dec 2017 11:22:22 -0800 Subject: [PATCH 7/7] Name as tool attribute. --- .../user_interfaces/toolmanager_sgskip.py | 10 +-- lib/matplotlib/backend_managers.py | 25 ++++-- lib/matplotlib/backend_tools.py | 89 +++++++++++-------- lib/matplotlib/backends/backend_gtk3.py | 8 +- lib/matplotlib/backends/backend_tkagg.py | 8 +- 5 files changed, 83 insertions(+), 57 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 9347ba9faa22..ff8b49075893 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -22,10 +22,10 @@ from matplotlib.backend_tools import ToolBase, ToolToggleBase, register_tool -@register_tool("List") -class ListTools(ToolBase): +class ListTool(ToolBase): '''List all the tools controlled by the `ToolManager`''' # keyboard shortcut + name = "List" default_keymap = 'm' description = 'List Tools' @@ -49,9 +49,9 @@ def trigger(self, event): print("{0:12} {1:45}".format(str(group), str(active))) -@register_tool("Show") class GroupHideTool(ToolToggleBase): '''Show lines with a given gid''' + name = "Show" default_keymap = 'G' description = 'Show by gid' default_toggled = True @@ -81,8 +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') -fig.canvas.manager.toolmanager.add_tool('Show', 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_managers.py b/lib/matplotlib/backend_managers.py index 8fd1edf20f87..beeb49d9e3e6 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -228,7 +228,7 @@ def remove_tool(self, name): del self._tools[name] - def add_tool(self, tool_name, *args, **kwargs): + def add_tool(self, tool_cls_or_name, *args, **kwargs): """ Add *tool* to `ToolManager` @@ -238,8 +238,8 @@ def add_tool(self, tool_name, *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. @@ -252,18 +252,25 @@ def add_tool(self, tool_name, *args, **kwargs): matplotlib.backend_tools.ToolBase : The base class for tools. """ - tool_cls = tools.get_tool(tool_name, type(self.canvas)) + 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 tool_name in self._tools: + if name in self._tools: warnings.warn('A "Tool class" with the same name already exists, ' 'not added') - return self._tools[tool_name] + return self._tools[name] - tool_obj = tool_cls(self, tool_name, *args, **kwargs) - self._tools[tool_name] = tool_obj + tool_obj = tool_cls(self, *args, **kwargs) + self._tools[name] = tool_obj if tool_cls.default_keymap is not None: - self.update_keymap(tool_name, tool_cls.default_keymap) + self.update_keymap(name, tool_cls.default_keymap) # For toggle tools init the radio_group in self._toggled if isinstance(tool_obj, tools.ToolToggleBase): diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index cdbfdef75474..786c6c3341ce 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -28,18 +28,18 @@ _registered_tools = {} -def register_tool(name, canvas_cls, cls=None): +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 cls is None: - return functools.partial(register_tool, name, canvas_cls) - if (name, canvas_cls) in _registered_tools: + 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(name, canvas_cls.__name__)) - _registered_tools[name, canvas_cls] = cls - return cls + .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): @@ -67,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 @@ -96,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 @@ -145,11 +147,6 @@ def trigger(self, event): The Canvas event that caused this tool to be called """ - @property - def name(self): - """Tool Id""" - return self._name - def destroy(self): """ Destroy the tool @@ -257,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 @@ -322,13 +321,15 @@ def set_cursor(self, cursor): raise NotImplementedError -@register_tool("position", FigureCanvasBase) +@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) @@ -368,10 +369,11 @@ def send_message(self, event): self.toolmanager.message_event(message) -@register_tool("quit", FigureCanvasBase) +@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'] @@ -379,10 +381,11 @@ def trigger(self, event): Gcf.destroy_fig(self.figure) -@register_tool("quit_all", FigureCanvasBase) +@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'] @@ -390,10 +393,11 @@ def trigger(self, event): Gcf.destroy_all() -@register_tool("allnav", FigureCanvasBase) +@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'] @@ -407,10 +411,11 @@ def trigger(self, event): a.set_navigate(True) -@register_tool("nav", FigureCanvasBase) +@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) @@ -459,10 +464,11 @@ def _get_uniform_grid_state(ticks): return None -@register_tool("grid", FigureCanvasBase) +@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'] @@ -481,10 +487,11 @@ def _get_next_grid_states(self, ax): y_state, "major" if y_state else "both") -@register_tool("grid_minor", FigureCanvasBase) +@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'] @@ -502,10 +509,11 @@ def _get_next_grid_states(self, ax): return x_state, "both", y_state, "both" -@register_tool("fullscreen", FigureCanvasBase) +@register_tool(FigureCanvasBase) class ToolFullScreen(ToolToggleBase): """Tool to toggle full screen""" + name = "fullscreen" description = 'Toogle Fullscreen mode' default_keymap = rcParams['keymap.fullscreen'] @@ -533,10 +541,11 @@ def disable(self, event): self.figure.canvas.draw_idle() -@register_tool("xscale", FigureCanvasBase) +@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'] @@ -544,10 +553,11 @@ def set_scale(self, ax, scale): ax.set_xscale(scale) -@register_tool("yscale", FigureCanvasBase) +@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'] @@ -555,7 +565,7 @@ def set_scale(self, ax, scale): ax.set_yscale(scale) -@register_tool("viewpos", FigureCanvasBase) +@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() @@ -720,48 +732,53 @@ def trigger(self, event): self.toolmanager.get_tool("viewpos").update_view() -@register_tool("home", FigureCanvasBase) +@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("back", FigureCanvasBase) +@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("forward", FigureCanvasBase) +@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("subplots", FigureCanvasBase) +@register_tool(FigureCanvasBase) class ConfigureSubplotsBase(ToolBase): """Base tool for the configuration of subplots""" + name = "subplots" description = 'Configure subplots' image = 'subplots' -@register_tool("save", FigureCanvasBase) +@register_tool(FigureCanvasBase) class SaveFigureBase(ToolBase): """Base tool for figure saving""" + name = "save" description = 'Save the figure' image = 'filesave' default_keymap = rcParams['keymap.save'] @@ -831,12 +848,13 @@ def scroll_zoom(self, event): self.toolmanager.get_tool("viewpos").push_current() -@register_tool("zoom", FigureCanvasBase) +@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'] @@ -965,10 +983,11 @@ def _release(self, event): self._cancel_action() -@register_tool("pan", FigureCanvasBase) +@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' diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 8a3098dc3c19..a6fa8deaae9f 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -665,7 +665,7 @@ def get_filename_from_user (self): return filename, self.ext -@backend_tools.register_tool("zoom", FigureCanvasGTK3) +@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/ @@ -778,7 +778,7 @@ def set_message(self, s): self.push(self._context, s) -@backend_tools.register_tool("save", FigureCanvasGTK3) +@backend_tools.register_tool(FigureCanvasGTK3) class SaveFigureGTK3(backend_tools.SaveFigureBase): def get_filechooser(self): @@ -810,13 +810,13 @@ def trigger(self, event): error_msg_gtk(str(e), parent=self) -@backend_tools.register_tool("cursor", FigureCanvasGTK3) +@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("subplots", FigureCanvasGTK3) +@backend_tools.register_tool(FigureCanvasGTK3) class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index cf39225e21b7..dc8bfdc8ce11 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -838,7 +838,7 @@ def hidetip(self): tw.destroy() -@backend_tools.register_tool("zoom", FigureCanvasTkAgg) +@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 @@ -855,7 +855,7 @@ def _remove_rubberband(self): del self.lastrect -@backend_tools.register_tool("cursor", FigureCanvasTkAgg) +@backend_tools.register_tool(FigureCanvasTkAgg) class SetCursorTk(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.manager.window.configure(cursor=cursord[cursor]) @@ -955,7 +955,7 @@ def set_message(self, s): self._message.set(s) -@backend_tools.register_tool("save", FigureCanvasTkAgg) +@backend_tools.register_tool(FigureCanvasTkAgg) class SaveFigureTk(backend_tools.SaveFigureBase): def trigger(self, event): from six.moves import tkinter_tkfiledialog, tkinter_messagebox @@ -1003,7 +1003,7 @@ def trigger(self, event): tkinter_messagebox.showerror("Error saving file", str(e)) -@backend_tools.register_tool("subplots", FigureCanvasTkAgg) +@backend_tools.register_tool(FigureCanvasTkAgg) class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase): def __init__(self, *args, **kwargs): backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs)