From 905dc62ba47553c2e76b0ab4148596f834fe8810 Mon Sep 17 00:00:00 2001 From: Ocean Wolf Date: Fri, 1 Aug 2014 19:44:33 +0200 Subject: [PATCH] Simplified code logic, removing ToolPersistentBase and adding event logic! --- lib/matplotlib/backend_bases.py | 110 ++++++++++------------- lib/matplotlib/backend_tools.py | 57 ++++++------ lib/matplotlib/backends/backend_tkagg.py | 37 ++++++-- 3 files changed, 105 insertions(+), 99 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 96adc3160155..8389467ae2a9 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3212,7 +3212,6 @@ def __init__(self, manager): self._tools = {} self._keys = {} - self._instances = {} self._toggled = None # to process keypress event @@ -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 @@ -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 @@ -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: + self._tools[name].destroy() + del self._tools[name] def remove_tool(self, name): """Remove tool from the `Navigation` @@ -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] @@ -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) def _get_cls_to_instantiate(self, callback_class): if isinstance(callback_class, six.string_types): @@ -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(): @@ -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` @@ -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 @@ -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) @@ -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 @@ -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 @@ -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): @@ -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 """ @@ -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) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 442f9bf6fedb..20285363d30a 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -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` """ @@ -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 @@ -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 @@ -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 @@ -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 """ @@ -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' diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index bbaf35670cf0..7a7bdd33cc89 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -598,7 +598,12 @@ def show(self): def destroy(*args): self.window = None Gcf.destroy(self._num) + def finish_up(*args, **kwargs): + if self.navigation: + self.navigation.unregister_all() + self.window.destroy() self.canvas._tkcanvas.bind("", destroy) + self.window.protocol("WM_DELETE_WINDOW", finish_up) self.window.deiconify() # anim.py requires this self.window.update() @@ -968,13 +973,14 @@ def _Button(self, text, image_file, toggle): def _button_click(self, name): self.manager.navigation._toolbar_callback(name) - def _toggle(self, name, callback=False): + def _set_state(self, name, toggled): if name not in self._toolitems: self.set_message('%s Not in toolbar' % name) return - self._toolitems[name].toggle() - if callback: - self._button_click(name) + if toggled: + self._toolitems[name].select() + else: + self._toolitems[name].deselect() def _add_message(self): self.message = Tk.StringVar(master=self) @@ -1049,6 +1055,10 @@ def trigger(self, *args): class ConfigureSubplotsTk(ConfigureSubplotsBase): def __init__(self, *args, **kwargs): ConfigureSubplotsBase.__init__(self, *args, **kwargs) + self.window = None + self.is_open = False + + def _setup_window(self): toolfig = Figure(figsize=(6, 3)) self.window = Tk.Tk() @@ -1057,14 +1067,25 @@ def __init__(self, *args, **kwargs): _tool = SubplotTool(self.figure, toolfig) canvas.show() canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1) - self.window.protocol("WM_DELETE_WINDOW", self.destroy) + self.window.protocol("WM_DELETE_WINDOW", self.close) + self.is_open = True def trigger(self, event): - self.window.lift() + if not self.window: + self._setup_window() + + if self.is_open: + self.window.lift() + else: + self.window.deiconify() + + def close(self, *args, **kwargs): + self.window.withdraw() + self.is_open = False def destroy(self, *args, **kwargs): - self.unregister() - self.window.destroy() + if self.window: + self.window.destroy() SaveFigure = SaveFigureTk