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) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy