-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
MEP22: Navigation by events #3652
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 1 commit
8cceed4
3118a5a
b4d5fcf
1e8af47
622cb95
d1a9de4
3f89d52
4f3c10b
6065daa
f6a2f19
05db3b6
c08fe56
b207a72
9266447
a53419a
704c717
5056729
e6a4e1e
8942c47
022de6f
2c9a195
cafe668
224f745
94c711e
67257e7
ffa65d6
6739ee0
d18206f
34a52c8
c2da483
44a9b0e
a2ed47f
0665890
411e6e2
d484ebd
75bf97b
6cc040b
0ff5997
af6734f
78513d2
377ff54
7dbbf58
dd66b57
67a414f
e415d8d
1213086
ba61dec
9f2ee2b
9da2b13
110253f
e2804ea
9a64b7e
64f947f
e8cd5d5
4bbcf4e
73a2661
1b83628
e4edd23
d4ac2fb
a7640ef
48a6971
8dafe09
a0695d0
328b169
aac4744
f09b9ef
def3a52
9ee7e25
5eae4e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,38 +4,39 @@ | |
matplotlib.rcParams['toolbar'] = 'navigation' | ||
import matplotlib.pyplot as plt | ||
from matplotlib.backend_tools import ToolBase | ||
from pydispatch import dispatcher | ||
|
||
|
||
# Create a simple tool to list all the tools | ||
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'd prefer if these comments be class docstrings docstrings instead |
||
class ListTools(ToolBase): | ||
# keyboard shortcut | ||
keymap = 'm' | ||
description = 'List Tools' | ||
|
||
def trigger(self, *args, **kwargs): | ||
tools = self.navigation.get_tools() | ||
|
||
print ('_' * 80) | ||
print ("{0:12} {1:45} {2}".format('Name (id)', | ||
'Tool description', | ||
'Keymap')) | ||
print ('_' * 80) | ||
print ('-' * 80) | ||
tools = self.navigation.tools | ||
for name in sorted(tools.keys()): | ||
keys = ', '.join(sorted(tools[name]['keymap'])) | ||
if not tools[name].description: | ||
continue | ||
keys = ', '.join(sorted(self.navigation.get_tool_keymap(name))) | ||
print ("{0:12} {1:45} {2}".format(name, | ||
tools[name]['description'], | ||
tools[name].description, | ||
keys)) | ||
print ('_' * 80) | ||
print ('_' * 80) | ||
|
||
|
||
# A simple example of copy canvas | ||
# ref: at https://github.com/matplotlib/matplotlib/issues/1987 | ||
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. same here, should be a class docstring |
||
class CopyToolGTK3(ToolBase): | ||
keymap = 'ctrl+c' | ||
description = 'Copy canvas' | ||
# It is not added to the toolbar as a button | ||
intoolbar = False | ||
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 think this need to get removed now? 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. Done, thanks |
||
|
||
def trigger(self, *args, **kwargs): | ||
from gi.repository import Gtk, Gdk | ||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) | ||
|
@@ -52,7 +53,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) | ||
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'd be fine with making this a GTK3Cairo-only example. Makes it a little bit simpler |
||
|
||
# Uncomment to remove the forward button | ||
# fig.canvas.manager.navigation.remove_tool('forward') | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3234,9 +3234,9 @@ def __init__(self, name, sender): | |
|
||
class ToolTriggerEvent(ToolEvent): | ||
"""Event to inform that a tool has been triggered""" | ||
def __init__(self, name, toolname, sender, canvasevent=None, data=None): | ||
def __init__(self, name, tool, sender, canvasevent=None, data=None): | ||
ToolEvent.__init__(self, name, sender) | ||
self.toolname = toolname | ||
self.tool = tool | ||
self.canvasevent = canvasevent | ||
self.data = data | ||
|
||
|
@@ -3254,10 +3254,10 @@ class NavigationBase(object): | |
|
||
Attributes | ||
---------- | ||
manager : `FigureManager` instance | ||
keypresslock : `LockDraw` to know if the `canvas` key_press_event is | ||
locked | ||
messagelock : `LockDraw` to know if the message is available to write | ||
manager: `FigureManager` instance | ||
keypresslock: `LockDraw` to know if the `canvas` key_press_event is | ||
locked | ||
messagelock: `LockDraw` to know if the message is available to write | ||
""" | ||
|
||
def __init__(self, manager): | ||
|
@@ -3270,7 +3270,7 @@ def __init__(self, manager): | |
self._tools = {} | ||
self._keys = {} | ||
self._toggled = None | ||
self.callbacks = cbook.CallbackRegistry() | ||
self._callbacks = cbook.CallbackRegistry() | ||
|
||
# to process keypress event | ||
self.keypresslock = widgets.LockDraw() | ||
|
@@ -3279,22 +3279,24 @@ def __init__(self, manager): | |
def mpl_connect(self, s, func): | ||
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 call this something different to not shadow the same function on the canvas objects. 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 used the same name, to have a consistent "event" interface. This does not shadow the canvas, this one has to be called specifically on the 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. It doesn't shadow at the code level, but it shadows at the user-brain-space level. I view the consistent interface as a negative as 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. Agree, done |
||
"""Connect event with string *s* to *func*. | ||
|
||
Parameters: | ||
Parameters | ||
----------- | ||
s: String | ||
s : String | ||
Name of the event | ||
|
||
The following events are recognized | ||
- 'tool_message_even' | ||
|
||
- 'tool_message_event' | ||
- 'tool_removed_event' | ||
- 'tool_added_event' | ||
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. This could be the source of the docs build failure http://docutils.sourceforge.net/docs/user/rst/quickref.html#bullet-lists 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.e. add a blank line here. |
||
For every tool added a new event is created | ||
- 'tool_trigger_TOOLNAME | ||
Where TOOLNAME is the id of the 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. Why do we need these tool specific event names? This information gets passed by 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. Indeed we have the |
||
func: function | ||
func : function | ||
Function to be called with signature | ||
def func(event) | ||
""" | ||
return self.callbacks.connect(s, func) | ||
return self._callbacks.connect(s, func) | ||
|
||
def mpl_disconnect(self, cid): | ||
"""Disconnect callback id cid | ||
|
@@ -3305,16 +3307,16 @@ def mpl_disconnect(self, cid): | |
#...later | ||
navigation.mpl_disconnect(cid) | ||
""" | ||
return self.callbacks.disconnect(cid) | ||
return self._callbacks.disconnect(cid) | ||
|
||
def message_event(self, message, sender=None): | ||
""" Send a tool_message_event event""" | ||
""" Emit a tool_message_event event""" | ||
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. "tool_message_event" --> ":class: 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. erm, I am not sure. The docstring needs a bit more cross-referencing. |
||
if sender is None: | ||
sender = self | ||
|
||
s = 'tool_message_event' | ||
event = NavigationEvent(s, sender, message=message) | ||
self.callbacks.process(s, event) | ||
self._callbacks.process(s, event) | ||
|
||
@property | ||
def active_toggle(self): | ||
|
@@ -3369,7 +3371,7 @@ def set_tool_keymap(self, name, *keys): | |
self._keys[k] = name | ||
|
||
def remove_tool(self, name): | ||
"""Remove tool from the `Navigation` | ||
"""Remove tool from `Navigation` | ||
|
||
Parameters | ||
---------- | ||
|
@@ -3387,7 +3389,7 @@ def remove_tool(self, name): | |
|
||
s = 'tool_removed_event' | ||
event = NavigationEvent(s, self, tool=tool) | ||
self.callbacks.process(s, event) | ||
self._callbacks.process(s, event) | ||
|
||
del self._tools[name] | ||
|
||
|
@@ -3396,33 +3398,37 @@ def add_tools(self, tools): | |
|
||
Parameters | ||
---------- | ||
tools : a list of tuples which contains the id of the tool and | ||
a either a reference to the tool Tool class itself, or None to | ||
insert a spacer. See :func:`add_tool`. | ||
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 | ||
""" | ||
|
||
for group, grouptools in tools: | ||
for position, tool in enumerate(grouptools): | ||
self.add_tool(tool[1], tool[0], group, position) | ||
|
||
def add_tool(self, name, tool, group=None, position=None): | ||
"""Add tool to `Navigation` | ||
"""Add tool to `NavigationBase` | ||
|
||
Add a tool to the tools controlled by Navigation | ||
If successful adds a new event `tool_trigger_name` where name is | ||
the name of the tool, this event is fired everytime | ||
|
||
If successful adds a new event `tool_trigger_name` where **name** is | ||
the **name** of the tool, this event is fired everytime | ||
the tool is triggered. | ||
|
||
Parameters | ||
---------- | ||
name : string | ||
Name of the tool, treated as the ID, has to be unique | ||
E377 | tool : string or `Tool` class | |
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 in the toolbar, if None, is positioned at the end | ||
Position within its group in the toolbar, if None, is positioned at the end | ||
""" | ||
|
||
tool_cls = self._get_cls_to_instantiate(tool) | ||
|
@@ -3447,7 +3453,7 @@ def _tool_added_event(self, tool, group, position): | |
tool=tool, | ||
group=group, | ||
position=position) | ||
self.callbacks.process(s, event) | ||
self._callbacks.process(s, event) | ||
|
||
def _handle_toggle(self, name, sender, canvasevent, data): | ||
# Toggle tools, need to be untoggled before other Toggle tool is used | ||
|
@@ -3466,8 +3472,6 @@ def _handle_toggle(self, name, sender, canvasevent, data): | |
for a in self.canvas.figure.get_axes(): | ||
a.set_navigate_mode(self._toggled) | ||
|
||
self._set_cursor(canvasevent) | ||
|
||
def _get_cls_to_instantiate(self, callback_class): | ||
# Find the class that corresponds to the tool | ||
if isinstance(callback_class, six.string_types): | ||
|
@@ -3485,17 +3489,17 @@ def _get_cls_to_instantiate(self, callback_class): | |
|
||
def tool_trigger_event(self, name, sender=None, canvasevent=None, | ||
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. This name is a bit too ambiguous, can it be 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. Agree, what do you think of just 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. 👍 to 'trigger_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. done |
||
data=None): | ||
"""Trigger a tool and fire the tool-trigger-[name] event | ||
"""Trigger a tool and emit the tool-trigger-[name] event | ||
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. shouldn't it be "tool_trigger_[name]"? |
||
|
||
Parameters | ||
---------- | ||
name : string | ||
Name of the tool | ||
sender: object | ||
Object that wish to trigger the tool | ||
canvasevent: Event | ||
canvasevent : Event | ||
Original Canvas event or None | ||
data: Object | ||
data : Object | ||
Extra data to pass to the tool when triggering | ||
""" | ||
if name not in self._tools: | ||
|
@@ -3508,8 +3512,8 @@ def tool_trigger_event(self, name, sender=None, canvasevent=None, | |
self._trigger_tool(name, sender, canvasevent, data) | ||
|
||
s = 'tool-trigger-%s' % name | ||
event = ToolTriggerEvent(s, name, sender, canvasevent, data) | ||
self.callbacks.process(s, event) | ||
event = ToolTriggerEvent(s, self._tools[name], sender, canvasevent, data) | ||
self._callbacks.process(s, event) | ||
|
||
def _trigger_tool(self, name, sender=None, canvasevent=None, data=None): | ||
"""Trigger on a tool | ||
|
@@ -3534,45 +3538,31 @@ def _key_press(self, event): | |
return | ||
self.tool_trigger_event(name, canvasevent=event) | ||
|
||
def get_tools(self): | ||
@property | ||
def tools(self): | ||
"""Return the tools controlled by `Navigation`""" | ||
|
||
d = {} | ||
for name in sorted(self._tools.keys()): | ||
tool = self._tools[name] | ||
keys = [k for k, i in six.iteritems(self._keys) if i == name] | ||
d[name] = {'obj': tool, | ||
'description': tool.description, | ||
'keymap': keys} | ||
return d | ||
return self._tools | ||
|
||
def get_tool(self, name): | ||
"""Return the tool object | ||
|
||
Parameters: | ||
Parameters | ||
----------- | ||
name: String | ||
name : String | ||
Name of the tool | ||
""" | ||
return self._tools[name] | ||
|
||
def _set_cursor(self, canvasevent): | ||
"""Sets the current cursor in ToolSetCursor""" | ||
|
||
if self._toggled: | ||
cursor = self._tools[self._toggled].cursor | ||
else: | ||
cursor = None | ||
|
||
self.tool_trigger_event('cursor', self, canvasevent, data=cursor) | ||
|
||
|
||
class ToolbarBase(object): | ||
"""Base class for `Toolbar` implementation | ||
|
||
Attributes | ||
---------- | ||
manager : `FigureManager` instance that integrates this `Toolbar` | ||
manager : `FigureManager` object that integrates this `Toolbar` | ||
navigation : `NavigationBase` object that hold the tools that | ||
this `Toolbar` wants to communicate with | ||
""" | ||
|
||
def __init__(self, manager): | ||
|
@@ -3596,12 +3586,12 @@ def _tool_triggered_cbk(self, event): | |
if event.sender is self: | ||
return | ||
|
||
self.toggle_toolitem(event.toolname) | ||
self.toggle_toolitem(event.tool.name) | ||
|
||
def _add_tool_cbk(self, event): | ||
"""Captures 'tool_added_event' and add the tool to the toolbar""" | ||
image = self._get_image_filename(event.tool.image) | ||
toggle = isinstance(event.tool, tools.ToolToggleBase) | ||
toggle = getattr(event.tool, 'toggled', None) is not None | ||
self.add_toolitem(event.tool.name, | ||
event.group, | ||
event.position, | ||
|
@@ -3641,6 +3631,8 @@ def trigger_tool(self, name): | |
def add_toolitem(self, name, group, position, image, description, toggle): | ||
"""Add a toolitem to the toolbar | ||
|
||
This method has to be implemented per backend | ||
|
||
The callback associated with the button click event, | ||
must be **EXACTLY** `self.trigger_tool(name)` | ||
|
||
|
@@ -3653,31 +3645,47 @@ def add_toolitem(self, name, group, position, image, description, toggle): | |
Name of the group that the tool belongs to | ||
position : Int | ||
Position of the tool whthin its group if -1 at the End | ||
image_file : string | ||
image_file : String | ||
Filename of the image for the button or `None` | ||
description : string | ||
description : String | ||
Description of the tool, used for the tooltips | ||
toggle : bool | ||
toggle : Bool | ||
* `True` : The button is a toggle (change the pressed/unpressed | ||
state between consecutive clicks) | ||
state between consecutive clicks) | ||
* `False` : The button is a normal button (returns to unpressed | ||
state after release) | ||
state after release) | ||
""" | ||
|
||
raise NotImplementedError | ||
|
||
def set_message(self, s): | ||
"""Display a message on toolbar or in status bar""" | ||
"""Display a message on toolbar or in status bar | ||
|
||
Parameters | ||
---------- | ||
s : String | ||
Message text | ||
""" | ||
|
||
pass | ||
|
||
def toggle_toolitem(self, name): | ||
"""Toggle the toolitem without firing event""" | ||
"""Toggle the toolitem without firing event | ||
|
||
Parameters | ||
---------- | ||
name : String | ||
Id of the tool to toggle | ||
""" | ||
raise NotImplementedError | ||
|
||
def remove_toolitem(self, name): | ||
"""Remove a toolitem from the `Toolbar` | ||
|
||
This method has to be implemented per backend | ||
|
||
Called when `tool_removed_event` is emited by `NavigationBase` | ||
|
||
Parameters | ||
---------- | ||
name : string | ||
|
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.
Since these are examples, I would suggest getting rid of extraneous commented out code, and also highlight important lines of code such as this one. For example, is it important that it gets called before importing pyplot? What is it for? Perhaps a short docstring at the top of this example would help explain its purpose/goal that it is demonstrating?