Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Navigation decoupling using events #2

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 49 additions & 61 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -3212,7 +3212,6 @@ def __init__(self, manager):

self._tools = {}
self._keys = {}
self._instances = {}
self._toggled = None

# to process keypress event
Expand All @@ -3231,15 +3230,6 @@ def active_toggle(self):

return self._toggled

@property
def instances(self):
"""Active tools instances

**dictionary** : Contains the active instances that are registered
"""

return self._instances

def get_tool_keymap(self, name):
"""Get the keymap associated with a tool

Expand Down Expand Up @@ -3280,6 +3270,10 @@ def set_tool_keymap(self, name, *keys):
(k, self._keys[k], name))
self._keys[k] = name

def unregister_all(self):
for name in self._tools.keys():
self.unregister(name)

def unregister(self, name):
"""Unregister the tool from the active instances

Expand All @@ -3300,9 +3294,10 @@ def unregister(self, name):
"""

if self._toggled == name:
self._handle_toggle(name, from_toolbar=False)
if name in self._instances:
del self._instances[name]
self._handle_toggle(name)
if name in self._tools:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what case, name is not present in self._tools?

self._tools[name].destroy()
del self._tools[name]

def remove_tool(self, name):
"""Remove tool from the `Navigation`
Expand All @@ -3314,7 +3309,6 @@ def remove_tool(self, name):
"""

self.unregister(name)
del self._tools[name]
keys = [k for k, v in six.iteritems(self._keys) if v == name]
for k in keys:
del self._keys[k]
Expand Down Expand Up @@ -3361,24 +3355,12 @@ def add_tool(self, name, tool, position=None):
'not added')
return

self._tools[name] = tool_cls
self._tools[name] = tool_cls(self.canvas.figure, name)
if tool_cls.keymap is not None:
self.set_tool_keymap(name, tool_cls.keymap)

if self.toolbar and tool_cls.intoolbar:
# TODO: better search for images, they are not always in the
# datapath
basedir = os.path.join(rcParams['datapath'], 'images')
if tool_cls.image is not None:
fname = os.path.join(basedir, tool_cls.image)
else:
fname = None
toggle = issubclass(tool_cls, tools.ToolToggleBase)
self.toolbar._add_toolitem(name,
tool_cls.description,
fname,
position,
toggle)
self.toolbar.add_tool(name, self._tools[name], position)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want users adding tools to the toolbar directly? I think the way is through navigation, this method should be `self.toolbar._add_tool(name, self._tools[name], position)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, though not an issue for here after my rebase splitting.

When I put this patch in (a different PR) I shall put it in as private and as and when I have figured out my preferred way of adding tools (on paper it looks almost done), I can make it public again (for you to comment on), if it still feels right.


def _get_cls_to_instantiate(self, callback_class):
if isinstance(callback_class, six.string_types):
Expand All @@ -3400,21 +3382,17 @@ def trigger_tool(self, name):
Method to programatically "click" on Tools
"""

self._trigger_tool(name, None, False)
self._trigger_tool(name, None)

def _trigger_tool(self, name, event, from_toolbar):
def _trigger_tool(self, name, event):
if name not in self._tools:
raise AttributeError('%s not in Tools' % name)

tool = self._tools[name]
if issubclass(tool, tools.ToolToggleBase):
self._handle_toggle(name, event=event, from_toolbar=from_toolbar)
elif issubclass(tool, tools.ToolPersistentBase):
instance = self._get_instance(name)
instance.trigger(event)
if isinstance(tool, tools.ToolToggleBase):
self._handle_toggle(name, event=event)
else:
# Non persistent tools, are instantiated and forgotten
tool(self.canvas.figure, event)
tool.trigger(event)

def _key_press(self, event):
if event.key is None or self.keypresslock.locked():
Expand All @@ -3423,15 +3401,13 @@ def _key_press(self, event):
name = self._keys.get(event.key, None)
if name is None:
return
self._trigger_tool(name, event, False)
self._trigger_tool(name, event)

def _get_instance(self, name):
if name not in self._instances:
instance = self._tools[name](self.canvas.figure, name)
# register instance
self._instances[name] = instance
if name not in self._tools:
warning.warn('Tool with name %s, not registered with Navigation' % name)

return self._instances[name]
return self._tools[name]

def _toolbar_callback(self, name):
"""Callback for the `Toolbar`
Expand All @@ -3446,14 +3422,9 @@ def _toolbar_callback(self, name):
toolbar
"""

self._trigger_tool(name, None, True)

def _handle_toggle(self, name, event=None, from_toolbar=False):
# toggle toolbar without callback
if not from_toolbar and self.toolbar:
self.toolbar._toggle(name, False)
self._trigger_tool(name, None)

instance = self._get_instance(name)
def _handle_toggle(self, name, event=None):
if self._toggled is None:
# first trigger of tool
self._toggled = name
Expand All @@ -3462,13 +3433,10 @@ def _handle_toggle(self, name, event=None, from_toolbar=False):
self._toggled = None
else:
# other tool is triggered so trigger toggled tool
if self.toolbar:
# untoggle the previous toggled tool
self.toolbar._toggle(self._toggled, False)
self._get_instance(self._toggled).trigger(event)
self._get_instance(self._toggled)._trigger(event)
self._toggled = name

instance.trigger(event)
self._get_instance(name)._trigger(event)

for a in self.canvas.figure.get_axes():
a.set_navigate_mode(self._toggled)
Expand All @@ -3492,7 +3460,7 @@ def _mouse_move(self, event):
self._last_cursor = self._default_cursor
else:
if self._toggled:
cursor = self._instances[self._toggled].cursor
cursor = self._tools[self._toggled].cursor
if cursor and self._last_cursor != cursor:
self.set_cursor(cursor)
self._last_cursor = cursor
Expand Down Expand Up @@ -3553,6 +3521,8 @@ def remove_rubberband(self, event, caller):
if not self.canvas.widgetlock.available(caller):
warnings.warn("%s doesn't own the canvas widgetlock" % caller)

def __del__(self):
self.unregister_all()

class ToolbarBase(object):
"""Base class for `Toolbar` implementation
Expand All @@ -3570,6 +3540,20 @@ def __init__(self, manager):
"""

self.manager = manager
self.change_events = {}

def add_tool(self, name, tool, position=None):
# TODO: better search for images, they are not always in the
# datapath
basedir = os.path.join(rcParams['datapath'], 'images')
if tool.image is not None:
fname = os.path.join(basedir, tool.image)
else:
fname = None
toggle = isinstance(tool, tools.ToolToggleBase)
self._add_toolitem(name, tool.description, fname, position, toggle)

self.change_events[name] = tool.mpl_connect('tool_state_changed_event', self._tool_state_change)

def _add_toolitem(self, name, description, image_file, position,
toggle):
Expand Down Expand Up @@ -3616,16 +3600,16 @@ def set_message(self, s):

pass

def _toggle(self, name, callback=False):
"""Toogle a button
def _set_state(self, name, toggled):
"""Set the state of a button

Parameters
----------
name : string
Name of the button to toggle
callback : bool
* `True`: call the button callback during toggle
* `False`: toggle the button without calling the callback
toggled : bool
* `True`: set the pressed/checked state of the button
* `False`: set the unpressed/unchecked state of the button

"""

Expand Down Expand Up @@ -3671,3 +3655,7 @@ def set_toolitem_visibility(self, name, visible):
"""

pass

def _tool_state_change(self, event):
if isinstance(event.tool, tools.ToolToggleBase):
self._set_state(event.tool.name, event.tool.toggled)
57 changes: 27 additions & 30 deletions lib/matplotlib/backend_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
:class:`ToolBase`
Simple tool that gets instantiated every time it is used

:class:`ToolPersistentBase`
Tool whose instance gets registered within `Navigation`

:class:`ToolToggleBase`
PersistentTool that has two states, only one Toggle tool can be
Tool that has two states, only one Toggle tool can be
active at any given time for the same `Navigation`
"""

Expand All @@ -26,6 +23,9 @@ class Cursors:
HAND, POINTER, SELECT_REGION, MOVE = list(range(4))
cursors = Cursors()

class ToolStateChangedEvent(object):
def __init__(self, tool):
self.tool = tool

class ToolBase(object):
"""Base tool class
Expand Down Expand Up @@ -65,11 +65,12 @@ class ToolBase(object):
cursor = None
"""Cursor to use when the tool is active"""

def __init__(self, figure, event=None):
def __init__(self, figure, name, event=None):
self._name = name
self.figure = None
self.navigation = None
self._callbacks = cbook.CallbackRegistry()
self.set_figure(figure)
self.trigger(event)

def trigger(self, event):
"""Called when this tool gets used
Expand All @@ -95,27 +96,6 @@ def set_figure(self, figure):
self.figure = figure
self.navigation = figure.canvas.manager.navigation


class ToolPersistentBase(ToolBase):
"""Persistent tool

Persistent Tools are kept alive after their initialization,
a reference of the instance is kept by `navigation`.

Notes
-----
The difference with `ToolBase` is that `trigger` method
is not called automatically at initialization
"""

def __init__(self, figure, name, event=None):
self._name = name
self.figure = None
self.navigation = None
self.set_figure(figure)
# persistent tools don't call trigger a at instantiation
# it will be called by Navigation

def unregister(self, *args):
"""Unregister the tool from the instances of Navigation

Expand All @@ -129,11 +109,28 @@ def unregister(self, *args):
# call this to unregister from navigation
self.navigation.unregister(self._name)

def destroy(self):
pass

@property
def name(self):
return self._name

def state_changed_event(self):
s = 'tool_state_changed_event'
event = ToolStateChangedEvent(self)
self._callbacks.process(s, event)

def mpl_connect(self, s, func):
return self._callbacks.connect(s, func)

def _trigger(self, event):
self.trigger(event)
self.state_changed_event()

class ToolToggleBase(ToolPersistentBase):
class ToolToggleBase(ToolBase):
"""Toggleable tool

This tool is a Persistent Tool that has a toggled state.
Every time it is triggered, it switches between enable and disable
"""

Expand Down Expand Up @@ -435,7 +432,7 @@ class ToolForward(ViewsPositionsBase):
_on_trigger = 'forward'


class ConfigureSubplotsBase(ToolPersistentBase):
class ConfigureSubplotsBase(ToolBase):
"""Base tool for the configuration of subplots"""

description = 'Configure subplots'
Expand Down
Loading