-
Notifications
You must be signed in to change notification settings - Fork 1
Navigation indepenent of toolbar. #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2605f99
7683b0b
5dd6099
e5d47c1
5276dd9
94bc085
6a0b3cc
2cde421
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remind me why we want to allow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because passing we only want to pass classes and not objects to tool = navigation.add_tool('my tool', MyTool)
tool.set_foo(foo)
tool.set_bar(bar) rather than navigation.add_tool('my tool', MyTool, foo=foo, bar=bar) It just feels nicer and more python and more user friendly. I don't think this will affect tools from getting merged with master at all. If they require other stuff set, then they wouldn't get added via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, you convinced me |
||
"""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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this ensures that. I allowed someone to three options to add a tool to the toolbar.
In this third case, we want to ensure that the tool belongs to THIS navigation, in case it was not set before (or got transferred here from another navigation) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really don't like the idea of opening the door to transfter tools between navigations..... I don't know.... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but what do you think of the main-use case, someone calling: my_tool = MyTool(None, 'foo')
navigation.add_tool('foo', my_tool) My thoughts went along the lines of we allow this, then we want to make sure that the navigation gets set to this one. If you think the user should do this, then could make make navigation optional until we make it so in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Navigation is not really optional because the user is not supposed to instantiate the tool by himself, it's navigation that does it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, in that case, I would like to add args and kwargs to |
||
|
||
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,67 +3557,88 @@ 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) | ||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why allow to toggle when triggered by self? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try running the example and see... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, multiple times the same tool? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. I don't see any reason to outright ban it, i.e. putting in safeguards to prevent it. So we might as well go the whole hog and allow it. It doesn't use up any extra lines of code, and we gain an extra feature, compared to banning it which would add more lines of code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, agree |
||
|
||
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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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']], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happen with the idea of the ordereddict? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the other PR... I put this one forward first to show my plans for getting rid of this altogether: if group is None:
return A convergence of multiple PRs working on the same bit of code to implement different new features ;). |
||
['zoompan', ['zoom', 'pan']], | ||
['layout', ['subplots']], | ||
['io', ['save']]] | ||
"""Default tools in the toolbar""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,29 +802,29 @@ 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) | ||
|
||
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]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ups, sorry, forget about this comment. I didn't realize we were allowing multiple times the same tool |
||
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): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better, if we add
List
instead ofzoom
, it might be confusing to see two times the same toolThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you like, though I thought the example should show off (quirky?) possibilities...