Skip to content

Commit 3dbe623

Browse files
committed
Explicit tool registration.
1 parent 31e851a commit 3dbe623

File tree

6 files changed

+113
-68
lines changed

6 files changed

+113
-68
lines changed

lib/matplotlib/backend_managers.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def remove_tool(self, name):
234234

235235
del self._tools[name]
236236

237-
def add_tool(self, name, tool, *args, **kwargs):
237+
def add_tool(self, tool_name):
238238
"""
239239
Add *tool* to `ToolManager`
240240
@@ -258,20 +258,18 @@ def add_tool(self, name, tool, *args, **kwargs):
258258
matplotlib.backend_tools.ToolBase : The base class for tools.
259259
"""
260260

261-
tool_cls = self._get_cls_to_instantiate(tool)
262-
if not tool_cls:
263-
raise ValueError('Impossible to find class for %s' % str(tool))
261+
tool_cls = tools.get_tool(tool_name, self.canvas.__module__)
264262

265-
if name in self._tools:
263+
if tool_name in self._tools:
266264
warnings.warn('A "Tool class" with the same name already exists, '
267265
'not added')
268-
return self._tools[name]
266+
return self._tools[tool_name]
269267

270-
tool_obj = tool_cls(self, name, *args, **kwargs)
271-
self._tools[name] = tool_obj
268+
tool_obj = tool_cls(self, tool_name)
269+
self._tools[tool_name] = tool_obj
272270

273271
if tool_cls.default_keymap is not None:
274-
self.update_keymap(name, tool_cls.default_keymap)
272+
self.update_keymap(tool_name, tool_cls.default_keymap)
275273

276274
# For toggle tools init the radio_group in self._toggled
277275
if isinstance(tool_obj, tools.ToolToggleBase):

lib/matplotlib/backend_tools.py

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,60 @@
1111
`matplotlib.backend_managers.ToolManager`
1212
"""
1313

14-
15-
from matplotlib import rcParams
16-
from matplotlib._pylab_helpers import Gcf
17-
import matplotlib.cbook as cbook
18-
from weakref import WeakKeyDictionary
1914
import six
15+
16+
import functools
2017
import time
2118
import warnings
19+
from weakref import WeakKeyDictionary
20+
2221
import numpy as np
2322

23+
from matplotlib import rcParams
24+
from matplotlib._pylab_helpers import Gcf
25+
import matplotlib.cbook as cbook
26+
2427

2528
class Cursors(object):
2629
"""Simple namespace for cursor reference"""
2730
HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5))
2831
cursors = Cursors()
2932

30-
# Views positions tool
31-
_views_positions = 'viewpos'
33+
34+
_registered_tools = {}
35+
_tools_inheritance = {}
36+
37+
38+
def register_tool(name, backend="", cls=None):
39+
"""Declares that class *cls* implements tool *name* for backend *backend*.
40+
41+
Can be used as a class decorator.
42+
"""
43+
if cls is None:
44+
return functools.partial(register_tool, name, backend)
45+
if (name, backend) in _registered_tools:
46+
raise KeyError("Tool {!r} is already registered for backend {!r}"
47+
.format(name, backend))
48+
_registered_tools[name, backend] = cls
49+
return cls
50+
51+
52+
def _inherit_tools(child, parent):
53+
"""Declares that backend *child* should default to the tools of *parent*.
54+
"""
55+
_tools_inheritance[child] = parent
56+
57+
58+
def get_tool(name, backend):
59+
"""Get the tool class for tool name *name* and backend *backend*.
60+
"""
61+
try:
62+
return _registered_tools[name, backend]
63+
except KeyError:
64+
try:
65+
return _registered_tools[name, _tools_inheritance[backend]]
66+
except KeyError:
67+
return _registered_tools[name, ""]
3268

3369

3470
class ToolBase(object):
@@ -305,6 +341,7 @@ def set_cursor(self, cursor):
305341
raise NotImplementedError
306342

307343

344+
@register_tool("position")
308345
class ToolCursorPosition(ToolBase):
309346
"""
310347
Send message with the current pointer position
@@ -378,6 +415,7 @@ def remove_rubberband(self):
378415
pass
379416

380417

418+
@register_tool("quit")
381419
class ToolQuit(ToolBase):
382420
"""Tool to call the figure manager destroy method"""
383421

@@ -388,6 +426,7 @@ def trigger(self, sender, event, data=None):
388426
Gcf.destroy_fig(self.figure)
389427

390428

429+
@register_tool("quit_all")
391430
class ToolQuitAll(ToolBase):
392431
"""Tool to call the figure manager destroy method"""
393432

@@ -398,6 +437,7 @@ def trigger(self, sender, event, data=None):
398437
Gcf.destroy_all()
399438

400439

440+
@register_tool("allnav")
401441
class ToolEnableAllNavigation(ToolBase):
402442
"""Tool to enable all axes for toolmanager interaction"""
403443

@@ -414,6 +454,7 @@ def trigger(self, sender, event, data=None):
414454
a.set_navigate(True)
415455

416456

457+
@register_tool("nav")
417458
class ToolEnableNavigation(ToolBase):
418459
"""Tool to enable a specific axes for toolmanager interaction"""
419460

@@ -465,6 +506,7 @@ def _get_uniform_grid_state(ticks):
465506
return None
466507

467508

509+
@register_tool("grid")
468510
class ToolGrid(_ToolGridBase):
469511
"""Tool to toggle the major grids of the figure"""
470512

@@ -486,6 +528,7 @@ def _get_next_grid_states(self, ax):
486528
y_state, "major" if y_state else "both")
487529

488530

531+
@register_tool("grid_minor")
489532
class ToolMinorGrid(_ToolGridBase):
490533
"""Tool to toggle the major and minor grids of the figure"""
491534

@@ -506,6 +549,7 @@ def _get_next_grid_states(self, ax):
506549
return x_state, "both", y_state, "both"
507550

508551

552+
@register_tool("fullscreen")
509553
class ToolFullScreen(ToolToggleBase):
510554
"""Tool to toggle full screen"""
511555

@@ -536,16 +580,7 @@ def disable(self, event):
536580
self.figure.canvas.draw_idle()
537581

538582

539-
class ToolYScale(AxisScaleBase):
540-
"""Tool to toggle between linear and logarithmic scales on the Y axis"""
541-
542-
description = 'Toogle Scale Y axis'
543-
default_keymap = rcParams['keymap.yscale']
544-
545-
def set_scale(self, ax, scale):
546-
ax.set_yscale(scale)
547-
548-
583+
@register_tool("xscale")
549584
class ToolXScale(AxisScaleBase):
550585
"""Tool to toggle between linear and logarithmic scales on the X axis"""
551586

@@ -556,6 +591,18 @@ def set_scale(self, ax, scale):
556591
ax.set_xscale(scale)
557592

558593

594+
@register_tool("yscale")
595+
class ToolYScale(AxisScaleBase):
596+
"""Tool to toggle between linear and logarithmic scales on the Y axis"""
597+
598+
description = 'Toogle Scale Y axis'
599+
default_keymap = rcParams['keymap.yscale']
600+
601+
def set_scale(self, ax, scale):
602+
ax.set_yscale(scale)
603+
604+
605+
@register_tool("viewpos")
559606
class ToolViewsPositions(ToolBase):
560607
"""
561608
Auxiliary Tool to handle changes in views and positions
@@ -714,12 +761,13 @@ class ViewsPositionsBase(ToolBase):
714761
_on_trigger = None
715762

716763
def trigger(self, sender, event, data=None):
717-
self.toolmanager.get_tool(_views_positions).add_figure(self.figure)
718-
getattr(self.toolmanager.get_tool(_views_positions),
764+
self.toolmanager.get_tool("viewpos").add_figure(self.figure)
765+
getattr(self.toolmanager.get_tool("viewpos"),
719766
self._on_trigger)()
720-
self.toolmanager.get_tool(_views_positions).update_view()
767+
self.toolmanager.get_tool("viewpos").update_view()
721768

722769

770+
@register_tool("home")
723771
class ToolHome(ViewsPositionsBase):
724772
"""Restore the original view lim"""
725773

@@ -729,6 +777,7 @@ class ToolHome(ViewsPositionsBase):
729777
_on_trigger = 'home'
730778

731779

780+
@register_tool("back")
732781
class ToolBack(ViewsPositionsBase):
733782
"""Move back up the view lim stack"""
734783

@@ -738,6 +787,7 @@ class ToolBack(ViewsPositionsBase):
738787
_on_trigger = 'back'
739788

740789

790+
@register_tool("forward")
741791
class ToolForward(ViewsPositionsBase):
742792
"""Move forward in the view lim stack"""
743793

@@ -747,13 +797,15 @@ class ToolForward(ViewsPositionsBase):
747797
_on_trigger = 'forward'
748798

749799

800+
@register_tool("subplots")
750801
class ConfigureSubplotsBase(ToolBase):
751802
"""Base tool for the configuration of subplots"""
752803

753804
description = 'Configure subplots'
754805
image = 'subplots'
755806

756807

808+
@register_tool("save")
757809
class SaveFigureBase(ToolBase):
758810
"""Base tool for figure saving"""
759811

@@ -794,7 +846,7 @@ def disable(self, event):
794846
self.figure.canvas.mpl_disconnect(self._idScroll)
795847

796848
def trigger(self, sender, event, data=None):
797-
self.toolmanager.get_tool(_views_positions).add_figure(self.figure)
849+
self.toolmanager.get_tool("viewpos").add_figure(self.figure)
798850
ToolToggleBase.trigger(self, sender, event, data)
799851

800852
def scroll_zoom(self, event):
@@ -818,14 +870,15 @@ def scroll_zoom(self, event):
818870
# If last scroll was done within the timing threshold, delete the
819871
# previous view
820872
if (time.time()-self.lastscroll) < self.scrollthresh:
821-
self.toolmanager.get_tool(_views_positions).back()
873+
self.toolmanager.get_tool("viewpos").back()
822874

823875
self.figure.canvas.draw_idle() # force re-draw
824876

825877
self.lastscroll = time.time()
826-
self.toolmanager.get_tool(_views_positions).push_current()
878+
self.toolmanager.get_tool("viewpos").push_current()
827879

828880

881+
@register_tool("zoom")
829882
class ToolZoom(ZoomPanBase):
830883
"""Zoom to rectangle"""
831884

@@ -843,7 +896,7 @@ def _cancel_action(self):
843896
for zoom_id in self._ids_zoom:
844897
self.figure.canvas.mpl_disconnect(zoom_id)
845898
self.toolmanager.trigger_tool('rubberband', self)
846-
self.toolmanager.get_tool(_views_positions).refresh_locators()
899+
self.toolmanager.get_tool("viewpos").refresh_locators()
847900
self._xypress = None
848901
self._button_pressed = None
849902
self._ids_zoom = []
@@ -948,10 +1001,11 @@ def _release(self, event):
9481001
self._zoom_mode, twinx, twiny)
9491002

9501003
self._zoom_mode = None
951-
self.toolmanager.get_tool(_views_positions).push_current()
1004+
self.toolmanager.get_tool("viewpos").push_current()
9521005
self._cancel_action()
9531006

9541007

1008+
@register_tool("pan")
9551009
class ToolPan(ZoomPanBase):
9561010
"""Pan axes with left mouse, zoom with right"""
9571011

@@ -970,7 +1024,7 @@ def _cancel_action(self):
9701024
self._xypress = []
9711025
self.figure.canvas.mpl_disconnect(self._idDrag)
9721026
self.toolmanager.messagelock.release(self)
973-
self.toolmanager.get_tool(_views_positions).refresh_locators()
1027+
self.toolmanager.get_tool("viewpos").refresh_locators()
9741028

9751029
def _press(self, event):
9761030
if event.button == 1:
@@ -1007,7 +1061,7 @@ def _release(self, event):
10071061
self._cancel_action()
10081062
return
10091063

1010-
self.toolmanager.get_tool(_views_positions).push_current()
1064+
self.toolmanager.get_tool("viewpos").push_current()
10111065
self._cancel_action()
10121066

10131067
def _mouse_move(self, event):
@@ -1018,24 +1072,11 @@ def _mouse_move(self, event):
10181072
self.toolmanager.canvas.draw_idle()
10191073

10201074

1021-
default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward,
1022-
'zoom': ToolZoom, 'pan': ToolPan,
1023-
'subplots': 'ToolConfigureSubplots',
1024-
'save': 'ToolSaveFigure',
1025-
'grid': ToolGrid,
1026-
'grid_minor': ToolMinorGrid,
1027-
'fullscreen': ToolFullScreen,
1028-
'quit': ToolQuit,
1029-
'quit_all': ToolQuitAll,
1030-
'allnav': ToolEnableAllNavigation,
1031-
'nav': ToolEnableNavigation,
1032-
'xscale': ToolXScale,
1033-
'yscale': ToolYScale,
1034-
'position': ToolCursorPosition,
1035-
_views_positions: ToolViewsPositions,
1036-
'cursor': 'ToolSetCursor',
1037-
'rubberband': 'ToolRubberband',
1038-
}
1075+
default_tools = [
1076+
'home', 'back', 'forward', 'zoom', 'pan', 'subplots', 'save',
1077+
'grid', 'grid_minor', 'fullscreen', 'quit', 'quit_all', 'allnav', 'nav',
1078+
'xscale', 'yscale', 'position', 'viewpos', 'cursor', 'rubberband',
1079+
]
10391080
"""Default tools"""
10401081

10411082
default_toolbar_tools = [['navigation', ['home', 'back', 'forward']],
@@ -1052,13 +1093,12 @@ def add_tools_to_manager(toolmanager, tools=default_tools):
10521093
----------
10531094
toolmanager: ToolManager
10541095
`backend_managers.ToolManager` object that will get the tools added
1055-
tools : {str: class_like}, optional
1056-
The tools to add in a {name: tool} dict, see `add_tool` for more
1057-
info.
1096+
tools : List[str], optional
1097+
The tools to add.
10581098
"""
10591099

1060-
for name, tool in six.iteritems(tools):
1061-
toolmanager.add_tool(name, tool)
1100+
for tool_name in tools:
1101+
toolmanager.add_tool(tool_name)
10621102

10631103

10641104
def add_tools_to_container(container, tools=default_toolbar_tools):

0 commit comments

Comments
 (0)