diff --git a/examples/user_interfaces/navigation.py b/examples/user_interfaces/navigation.py index 8142082d98fd..4c4d320c176f 100644 --- a/examples/user_interfaces/navigation.py +++ b/examples/user_interfaces/navigation.py @@ -56,7 +56,7 @@ def trigger(self, *args, **kwargs): fig.canvas.manager.navigation.add_tool('List', ListTools) if matplotlib.rcParams['backend'] == 'GTK3Cairo': fig.canvas.manager.navigation.add_tool('copy', CopyToolGTK3) - +fig.canvas.manager.toolbar.add_tool('zoom', 'foo') # Uncomment to remove the forward button # fig.canvas.manager.navigation.remove_tool('forward') diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 9f6f9f364433..6681cb34a972 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -30,7 +30,7 @@ user interaction (key press, toolbar clicks, ..) and the actions in response to the user inputs. -:class:`ToolbarBase` +:class:`ToolContainerBase` The base class for the Toolbar class of each interactive backend. """ @@ -3381,23 +3381,19 @@ def remove_tool(self, name): del self._tools[name] def add_tools(self, tools): - """ Add multiple tools to `Navigation` + """ Add multiple tools to `NavigationBase` Parameters ---------- - tools : List - List in the form - [[group1, [(Tool1, name1), (Tool2, name2) ...]][group2...]] - where group1 is the name of the group where the - Tool1, Tool2... are going to be added, and name1, name2... are the - names of the tools + tools : {str: class_like} + The tools to add in a {name: tool} dict, see `add_tool` for more + info. """ - for group, grouptools in tools: - for position, tool in enumerate(grouptools): - self.add_tool(tool[1], tool[0], group, position) + for name, tool in six.iteritems(tools): + self.add_tool(name, tool) - def add_tool(self, name, tool, group=None, position=None): + def add_tool(self, name, tool, *args, **kwargs): """Add tool to `NavigationBase` Add a tool to the tools controlled by Navigation @@ -3408,14 +3404,18 @@ def add_tool(self, name, tool, group=None, position=None): Parameters ---------- - name : string + name : str Name of the tool, treated as the ID, has to be unique - tool : string or `matplotlib.backend_tools.ToolBase` derived class - Reference to find the class of the Tool to be added - group: String - Group to position the tool in - position : int or None (default) - Position within its group in the toolbar, if None, it goes at the end + tool : class_like, i.e. str or type + Reference to find the class of the Tool to added. + + Notes + ----- + args and kwargs get passed directly to the tools constructor. + + See Also + -------- + matplotlib.backend_tools.ToolBase : The base class for tools. """ tool_cls = self._get_cls_to_instantiate(tool) @@ -3426,9 +3426,10 @@ def add_tool(self, name, tool, group=None, position=None): if name in self._tools: warnings.warn('A tool_cls with the same name already exist, ' 'not added') - return + return self._tools[name] + + self._tools[name] = tool_cls(self, name, *args, **kwargs) - self._tools[name] = tool_cls(self, name) if tool_cls.keymap is not None: self.set_tool_keymap(name, tool_cls.keymap) @@ -3441,14 +3442,12 @@ def add_tool(self, name, tool, group=None, position=None): else: self._toggled.setdefault(tool_cls.radio_group, None) - self._tool_added_event(self._tools[name], group, position) + self._tool_added_event(self._tools[name]) + return self._tools[name] - def _tool_added_event(self, tool, group, position): + def _tool_added_event(self, tool): s = 'tool_added_event' - event = ToolEvent(s, - self, - tool, - data={'group': group, 'position': position}) + event = ToolEvent(s, self, tool) self._callbacks.process(s, event) def _handle_toggle(self, tool, sender, canvasevent, data): @@ -3558,37 +3557,38 @@ def tools(self): return self._tools - def get_tool(self, name): + def get_tool(self, name, warn=True): """Return the tool object, also accepts the actual tool for convenience Parameters ----------- - name : String, ToolBase + name : str, ToolBase Name of the tool, or the tool itself + warn : bool + If this method should give warnings. """ - if isinstance(name, tools.ToolBase): + if isinstance(name, tools.ToolBase) and name.name in self._tools: return name if name not in self._tools: - warnings.warn("%s is not a tool controlled by Navigation" % name) + if warn: + warnings.warn("Navigation does not control tool %s" % name) return None return self._tools[name] -class ToolbarBase(object): - """Base class for `Toolbar` implementation +class ToolContainerBase(object): + """Base class for all tool containers, e.g. toolbars. Attributes ---------- - manager : `FigureManager` object that integrates this `Toolbar` - navigation : `NavigationBase` object that hold the tools that - this `Toolbar` wants to communicate with + navigation : `NavigationBase` object that holds the tools that + this `ToolContainer` wants to communicate with. """ def __init__(self, navigation): self.navigation = navigation self.navigation.nav_connect('tool_message_event', self._message_cbk) - self.navigation.nav_connect('tool_added_event', self._add_tool_cbk) self.navigation.nav_connect('tool_removed_event', self._remove_tool_cbk) @@ -3596,29 +3596,49 @@ def _message_cbk(self, event): """Captures the 'tool_message_event' to set the message on the toolbar""" self.set_message(event.message) - def _tool_triggered_cbk(self, event): + def _tool_toggled_cbk(self, event): """Captures the 'tool-trigger-toolname This only gets used for toggled tools """ - if event.sender is self: - return + self.toggle_toolitem(event.tool.name, event.tool.toggled) + + def add_tools(self, tools): + """ Add multiple tools to the container. + + Parameters + ---------- + tools : list + List in the form + [[group1, [tool1, tool2 ...]], [group2, [...]]] + Where the tools given by tool1, and tool2 will display in group1. + See `add_tool` for details. + """ - self.toggle_toolitem(event.tool.name) - - def _add_tool_cbk(self, event): - """Captures 'tool_added_event' and adds the tool to the toolbar""" - image = self._get_image_filename(event.tool.image) - toggle = getattr(event.tool, 'toggled', None) is not None - self.add_toolitem(event.tool.name, - event.data['group'], - event.data['position'], - image, - event.tool.description, - toggle) + for group, grouptools in tools: + for position, tool in enumerate(grouptools): + self.add_tool(tool, group, position) + + def add_tool(self, tool, group, position=-1): + """Adds a tool to this container + + Parameters + ---------- + tool : tool_like + The tool to add, see `NavigationBase.get_tool`. + group : str + The name of the group to add this tool to. + position : int (optional) + The position within the group to place this tool. Defaults to end. + """ + tool = self.navigation.get_tool(tool) + image = self._get_image_filename(tool.image) + toggle = getattr(tool, 'toggled', None) is not None + self.add_toolitem(tool.name, group, position, + image, tool.description, toggle) if toggle: - self.navigation.nav_connect('tool_trigger_%s' % event.tool.name, - self._tool_triggered_cbk) + self.navigation.nav_connect('tool_trigger_%s' % tool.name, + self._tool_toggled_cbk) def _remove_tool_cbk(self, event): """Captures the 'tool_removed_event' signal and removes the tool""" @@ -3641,13 +3661,13 @@ def trigger_tool(self, name): Parameters ---------- name : String - Name(id) of the tool triggered from within the toolbar + Name(id) of the tool triggered from within the container """ self.navigation.tool_trigger_event(name, sender=self) def add_toolitem(self, name, group, position, image, description, toggle): - """Add a toolitem to the toolbar + """Add a toolitem to the container This method must get implemented per backend @@ -3687,18 +3707,20 @@ def set_message(self, s): pass - def toggle_toolitem(self, name): + def toggle_toolitem(self, name, toggled): """Toggle the toolitem without firing event Parameters ---------- name : String Id of the tool to toggle + toggled : bool + Whether to set this tool as toggled or not. """ raise NotImplementedError def remove_toolitem(self, name): - """Remove a toolitem from the `Toolbar` + """Remove a toolitem from the `ToolContainer` This method must get implemented per backend diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 97fbc0b680b3..16cd67f374f0 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -903,26 +903,25 @@ def _mouse_move(self, event): self.navigation.canvas.draw_idle() -tools = [['navigation', [(ToolHome, 'home'), - (ToolBack, 'back'), - (ToolForward, 'forward')]], - - ['zoompan', [(ToolZoom, 'zoom'), - (ToolPan, 'pan')]], - - ['layout', [('ToolConfigureSubplots', 'subplots'), ]], - - ['io', [('ToolSaveFigure', 'save'), ]], - - [None, [(ToolGrid, 'grid'), - (ToolFullScreen, 'fullscreen'), - (ToolQuit, 'quit'), - (ToolEnableAllNavigation, 'allnav'), - (ToolEnableNavigation, 'nav'), - (ToolXScale, 'xscale'), - (ToolYScale, 'yscale'), - (ToolCursorPosition, 'position'), - (ToolViewsPositions, 'viewpos'), - ('ToolSetCursor', 'cursor'), - ('ToolRubberband', 'rubberband')]]] +tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward, + 'zoom': ToolZoom, 'pan': ToolPan, + 'subplots': 'ToolConfigureSubplots', + 'save': 'ToolSaveFigure', + 'grid': ToolGrid, + 'fullscreen': ToolFullScreen, + 'quit': ToolQuit, + 'allnav': ToolEnableAllNavigation, + 'nav': ToolEnableNavigation, + 'xscale': ToolXScale, + 'yscale': ToolYScale, + 'position': ToolCursorPosition, + 'viewpos': ToolViewsPositions, + 'cursor': 'ToolSetCursor', + 'rubberband': 'ToolRubberband'} """Default tools""" + +toolbar_tools = [['navigation', ['home', 'back', 'forward']], + ['zoompan', ['zoom', 'pan']], + ['layout', ['subplots']], + ['io', ['save']]] +"""Default tools in the toolbar""" diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 5aa6474e4ee8..9d7f41b81c5f 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -30,9 +30,9 @@ def fn_name(): return sys._getframe(1).f_code.co_name from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase -from matplotlib.backend_bases import ShowBase, ToolbarBase, NavigationBase +from matplotlib.backend_bases import ShowBase, ToolContainerBase, NavigationBase from matplotlib.backend_tools import SaveFigureBase, ConfigureSubplotsBase, \ - tools, SetCursorBase, RubberbandBase + tools, toolbar_tools, SetCursorBase, RubberbandBase from matplotlib.cbook import is_string_like, is_writable_file_like from matplotlib.colors import colorConverter @@ -418,6 +418,7 @@ def __init__(self, canvas, num): self.toolbar = self._get_toolbar() if matplotlib.rcParams['toolbar'] == 'navigation': self.navigation.add_tools(tools) + self.toolbar.add_tools(toolbar_tools) # calculate size for window w = int (self.canvas.figure.bbox.width) @@ -752,9 +753,9 @@ def draw_rubberband(self, x0, y0, x1, y1): ToolRubberband = RubberbandGTK3 -class ToolbarGTK3(ToolbarBase, Gtk.Box): +class ToolbarGTK3(ToolContainerBase, Gtk.Box): def __init__(self, navigation): - ToolbarBase.__init__(self, navigation) + ToolContainerBase.__init__(self, navigation) Gtk.Box.__init__(self) self.set_property("orientation", Gtk.Orientation.VERTICAL) @@ -763,7 +764,6 @@ def __init__(self, navigation): self.pack_start(self._toolbar, False, False, 0) self._toolbar.show_all() self._toolitems = {} - self._signals = {} self._setup_message_area() def _setup_message_area(self): @@ -784,9 +784,6 @@ def _setup_message_area(self): def add_toolitem(self, name, group, position, image_file, description, toggle): - if group is None: - return - if toggle: tbutton = Gtk.ToggleToolButton() else: @@ -805,8 +802,8 @@ def add_toolitem(self, name, group, position, image_file, description, signal = tbutton.connect('clicked', self._call_tool, name) tbutton.set_tooltip_text(description) tbutton.show_all() - self._toolitems[name] = tbutton - self._signals[name] = signal + self._toolitems.setdefault(name, []) + self._toolitems[name].append((tbutton, signal)) def _call_tool(self, btn, name): self.trigger_tool(name) @@ -814,20 +811,20 @@ def _call_tool(self, btn, name): def set_message(self, s): self.message.set_label(s) - def toggle_toolitem(self, name): + def toggle_toolitem(self, name, toggled): if name not in self._toolitems: return - - status = self._toolitems[name].get_active() - self._toolitems[name].handler_block(self._signals[name]) - self._toolitems[name].set_active(not status) - self._toolitems[name].handler_unblock(self._signals[name]) + for toolitem, signal in self._toolitems[name]: + toolitem.handler_block(signal) + toolitem.set_active(toggled) + toolitem.handler_unblock(signal) def remove_toolitem(self, name): if name not in self._toolitems: self.set_message('%s Not in toolbar' % name) return - self._toolbar.remove(self._toolitems[name]) + for toolitem, signal in self._toolitems[name]: + self._toolbar.remove(toolitem) del self._toolitems[name] def add_separator(self, pos=-1):