From 24caf2bccb930a7838475f0b95a3bb114756d88d Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 22 Feb 2015 00:14:36 +0100 Subject: [PATCH 01/55] Refactor pass 1. Refactoring Gcf out of specific backend (backend_gtk3.py) --- lib/matplotlib/backend_bases.py | 160 +++++++++++++++++++ lib/matplotlib/backends/backend_gtk3.py | 82 +++++++++- lib/matplotlib/backends/backend_gtk3cairo.py | 8 +- 3 files changed, 245 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d5335dfed6f0..84799fb60f6e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -47,6 +47,7 @@ import warnings import numpy as np +import matplotlib # temporary )assuming we refactor where marked below) import matplotlib.cbook as cbook import matplotlib.colors as colors import matplotlib.transforms as transforms @@ -2570,6 +2571,165 @@ def key_press_handler(event, canvas, toolbar=None): class NonGuiException(Exception): pass +class WindowEvent(object): + def __init__(self, name, window): + self.name = name + self.window = window + +class WindowBase(object): + def __init__(self, title): + self._callbacks = cbook.CallbackRegistry() + + def mpl_connect(self, s, func): + return self._callbacks.connect(s, func) + + def mpl_disconnect(self, cid): + return self._callbacks.disconnect(cid) + + def show(self): + """ + For GUI backends, show the figure window and redraw. + For non-GUI backends, raise an exception to be caught + by :meth:`~matplotlib.figure.Figure.show`, for an + optional warning. + """ + raise NonGuiException() + + def destroy(self): + pass + + def set_fullscreen(self, fullscreen): + pass + + def resize(self, w, h): + """"For gui backends, resize the window (in pixels).""" + pass + + def get_window_title(self): + """ + Get the title text of the window containing the figure. + Return None for non-GUI backends (e.g., a PS backend). + """ + return 'image' + + def set_window_title(self, title): + """ + Set the title text of the window containing the figure. Note that + this has no effect for non-GUI backends (e.g., a PS backend). + """ + pass + + def add_element_to_window(self, element, expand, fill, padding, from_start=False): + """ Adds a gui widget to the window. + This has no effect for non-GUI backends + """ + pass + + def terminate_backend(self): + """Method to terminate the usage of the backend + """ + # TODO refactor me out on second pass + pass + + def destroy_event(self, *args): + s = 'window_destroy_event' + event = WindowEvent(s, self) + self._callbacks.process(s, event) + + +class FigureManager(object): + def __init__(self, canvas, num, classes): + self._classes = classes + self.canvas = canvas + canvas.manager = self + self.num = num + + self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', + self.key_press) + + self.window = classes['Window']('Figure %d' % num) + self.window.mpl_connect('window_destroy_event', self._destroy) + + w = int(self.canvas.figure.bbox.width) + h = int(self.canvas.figure.bbox.height) + + self.window.add_element_to_window(self.canvas, True, True, 0, True) + + self.toolbar = self._get_toolbar(canvas) + if self.toolbar is not None: + h += self.window.add_element_to_window(self.toolbar, False, False, 0) + + self.window.set_default_size(w,h) + + # Refactor this? If so, delete import matplotlib from above. + if matplotlib.is_interactive(): + self.window.show() + + def notify_axes_change(fig): + 'this will be called whenever the current axes is changed' + if self.toolbar is not None: self.toolbar.update() + self.canvas.figure.add_axobserver(notify_axes_change) + + self.canvas.grab_focus() + + def key_press(self, event): + """ + Implement the default mpl key bindings defined at + :ref:`key-event-handling` + """ + key_press_handler(event, self.canvas, self.canvas.toolbar) + + def _destroy(self, event): + Gcf.destroy(self.num) # TODO refactor me out of here on second pass! + + def destroy(self, *args): + self.window.destroy() + self.canvas.destroy() + if self.toolbar: + self.toolbar.destroy() + + # TODO refactor out on second pass + if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive(): + self.window.terminate_backend() + + def show(self): + self.window.show() + + def full_screen_toggle(self): + self._full_screen_flag = not self._full_screen_flag + self.window.set_fullscreen(self._full_screen_flag) + + def resize(self, w, h): + self.window.resize(w,h) + + def get_window_title(self): + """ + Get the title text of the window containing the figure. + Return None for non-GUI backends (e.g., a PS backend). + """ + return self.window.get_window_title() + + def set_window_title(self, title): + """ + Set the title text of the window containing the figure. Note that + this has no effect for non-GUI backends (e.g., a PS backend). + """ + self.window.set_window_title(title) + + def show_popup(self, msg): + """ + Display message in a popup -- GUI only + """ + pass + + def _get_toolbar(self, canvas): + # must be inited after the window, drawingArea and figure + # attrs are set + if rcParams['toolbar'] == 'toolbar2': + toolbar = self._classes['Toolbar2'](canvas, self.window) + else: + toolbar = None + return toolbar class FigureManagerBase(object): """ diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 1aee5c75f590..579fbb5a7064 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -29,7 +29,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name import matplotlib from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase + FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase, WindowBase from matplotlib.backend_bases import (ShowBase, ToolContainerBase, StatusbarBase) from matplotlib.backend_managers import ToolManager @@ -373,6 +373,85 @@ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ +class WindowGTK3(WindowBase): + def __init__(self, title): + WindowBase.__init__(self, title) + self.window = Gtk.Window() + self.set_window_title(title) + + try: + self.window.set_icon_from_file(window_icon) + except (SystemExit, KeyboardInterrupt): + # re-raise exit type Exceptions + raise + except: + # some versions of gtk throw a glib.GError but not + # all, so I am not sure how to catch it. I am unhappy + # doing a blanket catch here, but am not sure what a + # better way is - JDH + verbose.report('Could not load matplotlib icon: %s' % sys.exc_info()[1]) + + self.vbox = Gtk.Box() + self.vbox.set_property('orientation', Gtk.Orientation.VERTICAL) + self.window.add(self.vbox) + self.vbox.show() + + self.window.connect('destroy', self.destroy_event) # TODO create in base + self.window.connect('delete_event', self.destroy_event) + + def add_element_to_window(self, element, expand, fill, padding, from_start=False): + element.show() + if from_start: + self.vbox.pack_start(element, expand, fill, padding) + else: + self.vbox.pack_end(element, False, False, 0) + size_request = element.size_request() + return size_request.height + + def set_default_size(self, width, height): + self.window.set_default_size(width, height) + + def show(self): + # show the figure window + self.window.show() + + def destroy(self): + self.vbox.destroy() + self.window.destroy() + + # TODO refactor out on second pass. + def terminate_backend(self): + if Gtk.main_level() >= 1: + Gtk.main_quit() + + def set_fullscreen(self, fullscreen): + if fullscreen: + self.window.fullscreen() + else: + self.window.unfullscreen() + + def _get_toolbar(self, canvas): + # must be inited after the window, drawingArea and figure + # attrs are set + if rcParams['toolbar'] == 'toolbar2': + toolbar = NavigationToolbar2GTK3 (canvas, self.window) + else: + toolbar = None + return toolbar + + def get_window_title(self): + return self.window.get_title() + + def set_window_title(self, title): + self.window.set_title(title) + + def resize(self, width, height): + 'set the canvas size in pixels' + #_, _, cw, ch = self.canvas.allocation + #_, _, ww, wh = self.window.allocation + #self.window.resize (width-cw+ww, height-ch+wh) + self.window.resize(width, height) + class FigureManagerGTK3(FigureManagerBase): """ @@ -965,3 +1044,4 @@ def error_msg_gtk(msg, parent=None): Toolbar = ToolbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 +Window = WindowGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index da8f099be7f6..33bd8d054af6 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -7,6 +7,7 @@ from . import backend_cairo from .backend_cairo import cairo, HAS_CAIRO_CFFI from matplotlib.figure import Figure +from matplotlib.backend_bases import FigureManager class RendererGTK3Cairo(backend_cairo.RendererCairo): def set_context(self, ctx): @@ -51,7 +52,6 @@ def on_draw_event(self, widget, ctx): class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): pass - def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance @@ -66,10 +66,10 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasGTK3Cairo(figure) - manager = FigureManagerGTK3Cairo(canvas, num) + manager = FigureManager(canvas, num, classes) return manager - +classes = {'Window': backend_gtk3.WindowGTK3, + 'Toolbar2': backend_gtk3.NavigationToolbar2GTK3} FigureCanvas = FigureCanvasGTK3Cairo -FigureManager = FigureManagerGTK3Cairo show = backend_gtk3.show From 2b05d38208659396c49cd58f6adb0f460210e2aa Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 23 Feb 2015 00:22:14 +0100 Subject: [PATCH 02/55] Refactor Pass 2. Refactored Gcf out of all backend code. --- lib/matplotlib/_pylab_helpers.py | 60 +++++++- lib/matplotlib/backend_bases.py | 145 +++++-------------- lib/matplotlib/backend_managers.py | 133 +++++++++++++++++ lib/matplotlib/backends/__init__.py | 72 ++++++--- lib/matplotlib/backends/backend_gtk3.py | 30 ++-- lib/matplotlib/backends/backend_gtk3cairo.py | 11 +- lib/matplotlib/cbook.py | 11 ++ lib/matplotlib/pyplot.py | 23 +-- 8 files changed, 325 insertions(+), 160 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index c5ea8cc6bb60..b9d9954b4950 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -9,6 +9,8 @@ import gc import atexit +from matplotlib import is_interactive + def error_msg(msg): print(msg, file=sys.stderr) @@ -35,6 +37,16 @@ class Gcf(object): _activeQue = [] figs = {} + @classmethod + def add_figure_manager(cls, manager): + cls.figs[manager.num] = manager + try: # TODO remove once all backends converted to use the new manager. + manager.mpl_connect('window_destroy_event', cls.destroy_cbk) + except: + pass + + cls.set_active(manager) + @classmethod def get_fig_manager(cls, num): """ @@ -46,6 +58,49 @@ def get_fig_manager(cls, num): cls.set_active(manager) return manager + @classmethod + def show_all(cls, block=None): + """ + Show all figures. If *block* is not None, then + it is a boolean that overrides all other factors + determining whether show blocks by calling mainloop(). + The other factors are: + it does not block if run inside ipython's "%pylab" mode + it does not block in interactive mode. + """ + managers = cls.get_all_fig_managers() + if not managers: + return + + for manager in managers: + manager.show() + + if block is not None: + if block: + manager.mainloop() + return + + from matplotlib import pyplot + try: + ipython_pylab = not pyplot.show._needmain + # IPython versions >= 0.10 tack the _needmain + # attribute onto pyplot.show, and always set + # it to False, when in %pylab mode. + ipython_pylab = ipython_pylab and get_backend() != 'WebAgg' + # TODO: The above is a hack to get the WebAgg backend + # working with ipython's `%pylab` mode until proper + # integration is implemented. + except AttributeError: + ipython_pylab = False + + # Leave the following as a separate step in case we + # want to control this behavior with an rcParam. + if ipython_pylab: + block = False + + if not is_interactive() or get_backend() == 'WebAgg': + manager.mainloop() + @classmethod def destroy(cls, num): """ @@ -137,7 +192,6 @@ def set_active(cls, manager): if m != manager: cls._activeQue.append(m) cls._activeQue.append(manager) - cls.figs[manager.num] = manager @classmethod def draw_all(cls, force=False): @@ -149,4 +203,8 @@ def draw_all(cls, force=False): if force or f_mgr.canvas.figure.stale: f_mgr.canvas.draw_idle() + @classmethod + def destroy_cbk(cls, event): + cls.destroy(event.figure_manager.num) + atexit.register(Gcf.destroy_all) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 84799fb60f6e..d5aaa3f947f8 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -47,7 +47,6 @@ import warnings import numpy as np -import matplotlib # temporary )assuming we refactor where marked below) import matplotlib.cbook as cbook import matplotlib.colors as colors import matplotlib.transforms as transforms @@ -134,6 +133,33 @@ def get_registered_canvas_class(format): return backend_class +class MainLoopBase(object): + """This gets used as a key maintaining the event loop. + Backends should only need to override begin and end. + It should not matter if this gets used as a singleton or not due to + clever magic. + """ + _instance_count = {} + def __init__(self): + MainLoopBase._instance_count.setdefault(self.__class__, 0) + MainLoopBase._instance_count[self.__class__] += 1 + + def begin(self): + pass + + def end(self): + pass + + def __call__(self): + self.begin() + + def __del__(self): + MainLoopBase._instance_count[self.__class__] -= 1 + if (MainLoopBase._instance_count[self.__class__] <= 0 and + not is_interactive()): + self.end() + + class ShowBase(object): """ Simple base class to generate a show() callable in backends. @@ -2496,7 +2522,10 @@ def key_press_handler(event, canvas, toolbar=None): # quit the figure (defaut key 'ctrl+w') if event.key in quit_keys: - Gcf.destroy_fig(canvas.figure) + if isinstance(canvas.manager.mainloop, MainLoopBase): # If new no Gcf. + canvas.manager._destroy('window_destroy_event') + else: + Gcf.destroy_fig(canvas.figure) if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') @@ -2571,20 +2600,16 @@ def key_press_handler(event, canvas, toolbar=None): class NonGuiException(Exception): pass + class WindowEvent(object): def __init__(self, name, window): self.name = name self.window = window -class WindowBase(object): - def __init__(self, title): - self._callbacks = cbook.CallbackRegistry() - - def mpl_connect(self, s, func): - return self._callbacks.connect(s, func) - def mpl_disconnect(self, cid): - return self._callbacks.disconnect(cid) +class WindowBase(cbook.EventEmitter): + def __init__(self, title): + cbook.EventEmitter.__init__(self) def show(self): """ @@ -2625,112 +2650,12 @@ def add_element_to_window(self, element, expand, fill, padding, from_start=False """ pass - def terminate_backend(self): - """Method to terminate the usage of the backend - """ - # TODO refactor me out on second pass - pass - def destroy_event(self, *args): s = 'window_destroy_event' event = WindowEvent(s, self) self._callbacks.process(s, event) -class FigureManager(object): - def __init__(self, canvas, num, classes): - self._classes = classes - self.canvas = canvas - canvas.manager = self - self.num = num - - self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', - self.key_press) - - self.window = classes['Window']('Figure %d' % num) - self.window.mpl_connect('window_destroy_event', self._destroy) - - w = int(self.canvas.figure.bbox.width) - h = int(self.canvas.figure.bbox.height) - - self.window.add_element_to_window(self.canvas, True, True, 0, True) - - self.toolbar = self._get_toolbar(canvas) - if self.toolbar is not None: - h += self.window.add_element_to_window(self.toolbar, False, False, 0) - - self.window.set_default_size(w,h) - - # Refactor this? If so, delete import matplotlib from above. - if matplotlib.is_interactive(): - self.window.show() - - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolbar is not None: self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) - - self.canvas.grab_focus() - - def key_press(self, event): - """ - Implement the default mpl key bindings defined at - :ref:`key-event-handling` - """ - key_press_handler(event, self.canvas, self.canvas.toolbar) - - def _destroy(self, event): - Gcf.destroy(self.num) # TODO refactor me out of here on second pass! - - def destroy(self, *args): - self.window.destroy() - self.canvas.destroy() - if self.toolbar: - self.toolbar.destroy() - - # TODO refactor out on second pass - if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive(): - self.window.terminate_backend() - - def show(self): - self.window.show() - - def full_screen_toggle(self): - self._full_screen_flag = not self._full_screen_flag - self.window.set_fullscreen(self._full_screen_flag) - - def resize(self, w, h): - self.window.resize(w,h) - - def get_window_title(self): - """ - Get the title text of the window containing the figure. - Return None for non-GUI backends (e.g., a PS backend). - """ - return self.window.get_window_title() - - def set_window_title(self, title): - """ - Set the title text of the window containing the figure. Note that - this has no effect for non-GUI backends (e.g., a PS backend). - """ - self.window.set_window_title(title) - - def show_popup(self, msg): - """ - Display message in a popup -- GUI only - """ - pass - - def _get_toolbar(self, canvas): - # must be inited after the window, drawingArea and figure - # attrs are set - if rcParams['toolbar'] == 'toolbar2': - toolbar = self._classes['Toolbar2'](canvas, self.window) - else: - toolbar = None - return toolbar - class FigureManagerBase(object): """ Helper class for pyplot mode, wraps everything up into a neat bundle diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 9fcd6176c07f..b4a566bac4e8 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -14,6 +14,118 @@ from matplotlib.rcsetup import validate_stringlist import matplotlib.backend_tools as tools +from matplotlib import is_interactive +from matplotlib import rcParams +from matplotlib.figure import Figure +from matplotlib.backend_bases import key_press_handler +from matplotlib.backends import get_backends +(FigureCanvas, Window, Toolbar2, MainLoop, + old_new_figure_manager) = get_backends() + + +class FigureManagerEvent(object): + def __init__(self, s, fm): + self.name = s + self.figure_manager = fm + + +class FigureManager(cbook.EventEmitter): + def __init__(self, canvas, num): + cbook.EventEmitter.__init__(self) + self.canvas = canvas + canvas.manager = self + self.num = num + + self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', + self.key_press) + + self.mainloop = MainLoop() + self.window = Window('Figure %d' % num) + self.window.mpl_connect('window_destroy_event', self._destroy) + + w = int(self.canvas.figure.bbox.width) + h = int(self.canvas.figure.bbox.height) + + self.window.add_element_to_window(self.canvas, True, True, 0, True) + + self.toolbar = self._get_toolbar(canvas) + if self.toolbar is not None: + h += self.window.add_element_to_window(self.toolbar, + False, False, 0) + + self.window.set_default_size(w, h) + + if is_interactive(): + self.window.show() + + def notify_axes_change(fig): + 'this will be called whenever the current axes is changed' + if self.toolbar is not None: + self.toolbar.update() + self.canvas.figure.add_axobserver(notify_axes_change) + + self.canvas.grab_focus() + + def key_press(self, event): + """ + Implement the default mpl key bindings defined at + :ref:`key-event-handling` + """ + key_press_handler(event, self.canvas, self.canvas.toolbar) + + def _destroy(self, event=None): + # Callback from the when the window wants to destroy itself + s = 'window_destroy_event' + event = FigureManagerEvent(s, self) + self._callbacks.process(s, event) + + def destroy(self, *args): + self.window.destroy() + self.canvas.destroy() + if self.toolbar: + self.toolbar.destroy() + + self.mainloop.__del__() + + def show(self): + self.window.show() + + def full_screen_toggle(self): + self._full_screen_flag = not self._full_screen_flag + self.window.set_fullscreen(self._full_screen_flag) + + def resize(self, w, h): + self.window.resize(w, h) + + def get_window_title(self): + """ + Get the title text of the window containing the figure. + Return None for non-GUI backends (e.g., a PS backend). + """ + return self.window.get_window_title() + + def set_window_title(self, title): + """ + Set the title text of the window containing the figure. Note that + this has no effect for non-GUI backends (e.g., a PS backend). + """ + self.window.set_window_title(title) + + def show_popup(self, msg): + """ + Display message in a popup -- GUI only + """ + pass + + def _get_toolbar(self, canvas): + # must be inited after the window, drawingArea and figure + # attrs are set + if rcParams['toolbar'] == 'toolbar2': + toolbar = Toolbar2(canvas, self.window) + else: + toolbar = None + return toolbar + class ToolEvent(object): """Event for tool manipulation (add/remove)""" @@ -430,3 +542,24 @@ def get_tool(self, name, warn=True): warnings.warn("ToolManager does not control tool %s" % name) return None return self._tools[name] + + +def new_figure_manager(num, *args, **kwargs): + """ + Create a new figure manager instance + """ + show = kwargs.pop('show', None) + if old_new_figure_manager is None: + FigureClass = kwargs.pop('FigureClass', Figure) + thisFig = FigureClass(*args, **kwargs) + manager = new_figure_manager_given_figure(num, thisFig) + else: # TODO remove once Gcf removed from backends. + manager = old_new_figure_manager(num, *args, **kwargs) + manager.mainloop = MainLoop + return manager + + +def new_figure_manager_given_figure(num, figure): + canvas = FigureCanvas(figure) + manager = FigureManager(canvas, num) + return manager diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 68c3a91b1c59..6008e31229c9 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -10,6 +10,46 @@ backend = matplotlib.get_backend() +def get_backend_name(name=None): + '''converts the name of the backend into the module to load + name : str, optional + + Parameters + ---------- + The name of the backend to use. If `None`, falls back to + ``matplotlib.get_backend()`` (which return ``rcParams['backend']``) + ''' + + if name is None: + # validates, to match all_backends + name = matplotlib.get_backend() + if name.startswith('module://'): + backend_name = name[9:] + else: + backend_name = 'matplotlib.backends.backend_' + name.lower() + + return backend_name + + +def get_backends(): + backend_name = get_backend_name() + _temp = __import__(backend_name, globals(), locals(), + ['Window', 'Toolbar2', 'FigureCanvas', 'MainLoop', + 'new_figure_manager'], 0) + FigureCanvas = _temp.FigureCanvas + try: + Window = _temp.Window + Toolbar2 = _temp.Toolbar2 + MainLoop = _temp.MainLoop + old_new_figure_manager = None + except AttributeError: + Window = None + Toolbar2 = None + MainLoop = getattr(_temp, 'show', do_nothing_show) + old_new_figure_manager = _temp.new_figure_manager + + return FigureCanvas, Window, Toolbar2, MainLoop, old_new_figure_manager + def pylab_setup(name=None): '''return new_figure_manager, draw_if_interactive and show for pyplot @@ -39,16 +79,7 @@ def pylab_setup(name=None): ''' # Import the requested backend into a generic module object - if name is None: - # validates, to match all_backends - name = matplotlib.get_backend() - if name.startswith('module://'): - backend_name = name[9:] - else: - backend_name = 'backend_' + name - backend_name = backend_name.lower() # until we banish mixed case - backend_name = 'matplotlib.backends.%s' % backend_name.lower() - + backend_name = get_backend_name(name) # the last argument is specifies whether to use absolute or relative # imports. 0 means only perform absolute imports. backend_mod = __import__(backend_name, globals(), locals(), @@ -60,22 +91,13 @@ def pylab_setup(name=None): # image backends like pdf, agg or svg do not need to do anything # for "show" or "draw_if_interactive", so if they are not defined # by the backend, just do nothing - def do_nothing_show(*args, **kwargs): - frame = inspect.currentframe() - fname = frame.f_back.f_code.co_filename - if fname in ('', ''): - warnings.warn(""" -Your currently selected backend, '%s' does not support show(). -Please select a GUI backend in your matplotlibrc file ('%s') -or with matplotlib.use()""" % - (name, matplotlib.matplotlib_fname())) def do_nothing(*args, **kwargs): pass backend_version = getattr(backend_mod, 'backend_version', 'unknown') - show = getattr(backend_mod, 'show', do_nothing_show) + show = None if hasattr(backend_mod, 'show') else do_nothing_show draw_if_interactive = getattr(backend_mod, 'draw_if_interactive', do_nothing) @@ -88,3 +110,13 @@ def do_nothing(*args, **kwargs): global backend backend = name return backend_mod, new_figure_manager, draw_if_interactive, show + +def do_nothing_show(*args, **kwargs): + frame = inspect.currentframe() + fname = frame.f_back.f_code.co_filename + if fname in ('', ''): + warnings.warn(""" +Your currently selected backend, '%s' does not support show(). +Please select a GUI backend in your matplotlibrc file ('%s') +or with matplotlib.use()""" % + (name, matplotlib.matplotlib_fname())) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 579fbb5a7064..284219a466b7 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -28,8 +28,9 @@ def fn_name(): return sys._getframe(1).f_code.co_name import matplotlib from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase, WindowBase +from matplotlib.backend_bases import (RendererBase, GraphicsContextBase, + FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, + TimerBase, WindowBase, MainLoopBase) from matplotlib.backend_bases import (ShowBase, ToolContainerBase, StatusbarBase) from matplotlib.backend_managers import ToolManager @@ -66,6 +67,17 @@ def draw_if_interactive(): if figManager is not None: figManager.canvas.draw_idle() + +class MainLoop(MainLoopBase): + def begin(self): + if Gtk.main_level() == 0: + Gtk.main() + + def end(self): + if Gtk.main_level() >= 1: + Gtk.main_quit() + + class Show(ShowBase): def mainloop(self): if Gtk.main_level() == 0: @@ -419,26 +431,12 @@ def destroy(self): self.vbox.destroy() self.window.destroy() - # TODO refactor out on second pass. - def terminate_backend(self): - if Gtk.main_level() >= 1: - Gtk.main_quit() - def set_fullscreen(self, fullscreen): if fullscreen: self.window.fullscreen() else: self.window.unfullscreen() - def _get_toolbar(self, canvas): - # must be inited after the window, drawingArea and figure - # attrs are set - if rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2GTK3 (canvas, self.window) - else: - toolbar = None - return toolbar - def get_window_title(self): return self.window.get_title() diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 33bd8d054af6..ce61bf26adcf 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -7,7 +7,6 @@ from . import backend_cairo from .backend_cairo import cairo, HAS_CAIRO_CFFI from matplotlib.figure import Figure -from matplotlib.backend_bases import FigureManager class RendererGTK3Cairo(backend_cairo.RendererCairo): def set_context(self, ctx): @@ -52,6 +51,7 @@ def on_draw_event(self, widget, ctx): class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): pass + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance @@ -66,10 +66,13 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasGTK3Cairo(figure) - manager = FigureManager(canvas, num, classes) + manager = FigureManagerGTK3Cairo(canvas, num) return manager -classes = {'Window': backend_gtk3.WindowGTK3, - 'Toolbar2': backend_gtk3.NavigationToolbar2GTK3} + FigureCanvas = FigureCanvasGTK3Cairo +FigureManager = FigureManagerGTK3Cairo +Window = backend_gtk3.WindowGTK3 +Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 +MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 0216ccb9c501..73ef91e64a7d 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -543,6 +543,17 @@ def process(self, s, *args, **kwargs): self._remove_proxy(proxy) +class EventEmitter(object): + def __init__(self): + self._callbacks = CallbackRegistry() + + def mpl_connect(self, s, func): + return self._callbacks.connect(s, func) + + def mpl_disconnect(self, cid): + return self._callbacks.disconnect(cid) + + class silent_list(list): """ override repr when returning a list of matplotlib artists to diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 02056a030427..425e925836ff 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -33,6 +33,7 @@ _string_to_bool) from matplotlib import docstring from matplotlib.backend_bases import FigureCanvasBase +import matplotlib.backend_managers as backend_managers from matplotlib.figure import Figure, figaspect from matplotlib.gridspec import GridSpec from matplotlib.image import imread as _imread @@ -248,7 +249,10 @@ def show(*args, **kw): described above. """ global _show - return _show(*args, **kw) + if _show is None: + return _pylab_helpers.Gcf.show_all(*args, **kw) + else: + _show(*args, **kw) def isinteractive(): @@ -524,13 +528,14 @@ def figure(num=None, # autoincrement if None, else integer from 1-N if get_backend().lower() == 'ps': dpi = 72 - figManager = new_figure_manager(num, figsize=figsize, - dpi=dpi, - facecolor=facecolor, - edgecolor=edgecolor, - frameon=frameon, - FigureClass=FigureClass, - **kwargs) + figManager = backend_managers.new_figure_manager(num, figsize=figsize, + dpi=dpi, + facecolor=facecolor, + edgecolor=edgecolor, + frameon=frameon, + FigureClass=FigureClass, + show=_show, + **kwargs) if figLabel: figManager.set_window_title(figLabel) @@ -543,7 +548,7 @@ def make_active(event): cid = figManager.canvas.mpl_connect('button_press_event', make_active) figManager._cidgcf = cid - _pylab_helpers.Gcf.set_active(figManager) + _pylab_helpers.Gcf.add_figure_manager(figManager) fig = figManager.canvas.figure fig.number = num From f4fc354f8e39450674cff269703d78160433da1a Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 25 Feb 2015 01:42:44 +0100 Subject: [PATCH 03/55] Quick fix to figure for safe unpickling. --- lib/matplotlib/figure.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 89b47984f417..c43f2b91c7a3 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1556,11 +1556,18 @@ def __setstate__(self, state): if restore_to_pylab: # lazy import to avoid circularity + # XXX clean on removal of Gcf from backends import matplotlib.pyplot as plt import matplotlib._pylab_helpers as pylab_helpers + import matplotlib.backend_managers as managers allnums = plt.get_fignums() num = max(allnums) + 1 if allnums else 1 - mgr = plt._backend_mod.new_figure_manager_given_figure(num, self) + if managers.old_new_figure_manager: + mgr = plt._backend_mod.new_figure_manager_given_figure(num, + self) + mgr.mainloop = plt._show + else: + mgr = managers.new_figure_manager_given_figure(num, self) # XXX The following is a copy and paste from pyplot. Consider # factoring to pylab_helpers @@ -1575,7 +1582,7 @@ def make_active(event): mgr._cidgcf = mgr.canvas.mpl_connect('button_press_event', make_active) - pylab_helpers.Gcf.set_active(mgr) + pylab_helpers.Gcf.add_figure_manager(mgr) self.number = num plt.draw_if_interactive() From 0b31e3ad2fa0955dc52f4245d709fcc824c6cc0b Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 26 Feb 2015 17:54:59 +0100 Subject: [PATCH 04/55] GTK3Agg --- lib/matplotlib/backends/backend_gtk3agg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index c3eb1da68be3..24a93ce4b4be 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -121,4 +121,7 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Agg FigureManager = FigureManagerGTK3Agg +Window = backend_gtk3.WindowGTK3 +Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 +MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show From 6fb452e1c63dd6b1561db546a51db3b5afbabdae Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 15:02:51 +0100 Subject: [PATCH 05/55] Refactored making `FigureManager` a *figure* manager, plus added missing methods. --- lib/matplotlib/backend_bases.py | 20 +++++++++- lib/matplotlib/backend_managers.py | 39 +++++++++----------- lib/matplotlib/backends/backend_gtk3.py | 4 +- lib/matplotlib/backends/backend_gtk3agg.py | 4 +- lib/matplotlib/backends/backend_gtk3cairo.py | 4 +- lib/matplotlib/figure.py | 4 +- 6 files changed, 45 insertions(+), 30 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d5aaa3f947f8..270214c40bd0 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -45,6 +45,7 @@ import sys import time import warnings +import weakref import numpy as np import matplotlib.cbook as cbook @@ -1692,7 +1693,7 @@ class FigureCanvasBase(object): register_backend('tiff', 'matplotlib.backends.backend_agg', 'Tagged Image File Format') - def __init__(self, figure): + def __init__(self, figure, manager=None): self._is_idle_drawing = True self._is_saving = False figure.set_canvas(self) @@ -1707,6 +1708,7 @@ def __init__(self, figure): self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick) self.mouse_grabber = None # the axes currently grabbing mouse self.toolbar = None # NavigationToolbar2 will set me + self.manager = manager self._is_idle_drawing = False @contextmanager @@ -2480,6 +2482,19 @@ def stop_event_loop_default(self): """ self._looping = False + def destroy(self): + pass + + @property + def manager(self): + if self._manager is not None: + return self._manager() + + @manager.setter + def manager(self, manager): + if manager is not None: + self._manager = weakref.ref(manager) + def key_press_handler(event, canvas, toolbar=None): """ @@ -2626,6 +2641,9 @@ def destroy(self): def set_fullscreen(self, fullscreen): pass + def set_default_size(self, w, h): + self.resize(w, h) + def resize(self, w, h): """"For gui backends, resize the window (in pixels).""" pass diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index b4a566bac4e8..bda1d6ae78de 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -30,25 +30,25 @@ def __init__(self, s, fm): class FigureManager(cbook.EventEmitter): - def __init__(self, canvas, num): + def __init__(self, figure, num): cbook.EventEmitter.__init__(self) - self.canvas = canvas - canvas.manager = self self.num = num - self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', - self.key_press) - self.mainloop = MainLoop() self.window = Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self._destroy) + self.canvas = FigureCanvas(figure, self) + + self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', + self.key_press) + w = int(self.canvas.figure.bbox.width) h = int(self.canvas.figure.bbox.height) self.window.add_element_to_window(self.canvas, True, True, 0, True) - self.toolbar = self._get_toolbar(canvas) + self.toolbar = self._get_toolbar() if self.toolbar is not None: h += self.window.add_element_to_window(self.toolbar, False, False, 0) @@ -64,8 +64,6 @@ def notify_axes_change(fig): self.toolbar.update() self.canvas.figure.add_axobserver(notify_axes_change) - self.canvas.grab_focus() - def key_press(self, event): """ Implement the default mpl key bindings defined at @@ -111,21 +109,21 @@ def set_window_title(self, title): """ self.window.set_window_title(title) - def show_popup(self, msg): - """ - Display message in a popup -- GUI only - """ - pass - - def _get_toolbar(self, canvas): + def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set if rcParams['toolbar'] == 'toolbar2': - toolbar = Toolbar2(canvas, self.window) + toolbar = Toolbar2(self.canvas, self.window) else: toolbar = None return toolbar + def show_popup(self, msg): + """ + Display message in a popup -- GUI only + """ + pass + class ToolEvent(object): """Event for tool manipulation (add/remove)""" @@ -549,17 +547,16 @@ def new_figure_manager(num, *args, **kwargs): Create a new figure manager instance """ show = kwargs.pop('show', None) - if old_new_figure_manager is None: + if old_new_figure_manager is None: # Test if we can use the new code FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) manager = new_figure_manager_given_figure(num, thisFig) - else: # TODO remove once Gcf removed from backends. + else: # TODO remove once Gcf removed from backends. Default to old code. manager = old_new_figure_manager(num, *args, **kwargs) manager.mainloop = MainLoop return manager def new_figure_manager_given_figure(num, figure): - canvas = FigureCanvas(figure) - manager = FigureManager(canvas, num) + manager = FigureManager(figure, num) return manager diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 284219a466b7..b208dcbea098 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -192,9 +192,9 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): Gdk.EventMask.POINTER_MOTION_HINT_MASK| Gdk.EventMask.SCROLL_MASK) - def __init__(self, figure): + def __init__(self, figure, manager=None): if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - FigureCanvasBase.__init__(self, figure) + FigureCanvasBase.__init__(self, figure, manager) GObject.GObject.__init__(self) self._idle_draw_id = 0 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 24a93ce4b4be..5bc4bdcf0afd 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -21,8 +21,8 @@ class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3, backend_agg.FigureCanvasAgg): - def __init__(self, figure): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure) + def __init__(self, figure, manager=None): + backend_gtk3.FigureCanvasGTK3.__init__(self, figure, manager) self._bbox_queue = [] def _renderer_init(self): diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index ce61bf26adcf..d00ebdebd9c6 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -22,8 +22,8 @@ def set_context(self, ctx): class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3, backend_cairo.FigureCanvasCairo): - def __init__(self, figure): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure) + def __init__(self, figure, manager=None): + backend_gtk3.FigureCanvasGTK3.__init__(self, figure, manager) def _renderer_init(self): """use cairo renderer""" diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index c43f2b91c7a3..3d6cd565c3cc 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1556,7 +1556,7 @@ def __setstate__(self, state): if restore_to_pylab: # lazy import to avoid circularity - # XXX clean on removal of Gcf from backends + # TODO clean on removal of Gcf from backends import matplotlib.pyplot as plt import matplotlib._pylab_helpers as pylab_helpers import matplotlib.backend_managers as managers @@ -1567,7 +1567,7 @@ def __setstate__(self, state): self) mgr.mainloop = plt._show else: - mgr = managers.new_figure_manager_given_figure(num, self) + mgr = managers.FigureManager(self, num) # XXX The following is a copy and paste from pyplot. Consider # factoring to pylab_helpers From 0a868a2373cf9bb28995cc6e32a73d53e8202003 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 16:09:23 +0100 Subject: [PATCH 06/55] keyword --- lib/matplotlib/backend_managers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index bda1d6ae78de..1afe12ee0e38 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -38,7 +38,7 @@ def __init__(self, figure, num): self.window = Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self._destroy) - self.canvas = FigureCanvas(figure, self) + self.canvas = FigureCanvas(figure, manager=self) self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', self.key_press) From 61ba2b4c028ea62a603a40c718c3c52f72961d5c Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 17:37:53 +0100 Subject: [PATCH 07/55] Make add_element more general, and make sure the code complies with it. --- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backend_managers.py | 4 ++-- lib/matplotlib/backends/backend_gtk3.py | 12 +++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 270214c40bd0..58704b6b9393 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2662,7 +2662,7 @@ def set_window_title(self, title): """ pass - def add_element_to_window(self, element, expand, fill, padding, from_start=False): + def add_element_to_window(self, element, expand, fill, pad, side='bottom'): """ Adds a gui widget to the window. This has no effect for non-GUI backends """ diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 1afe12ee0e38..2d414a044431 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -46,12 +46,12 @@ def __init__(self, figure, num): w = int(self.canvas.figure.bbox.width) h = int(self.canvas.figure.bbox.height) - self.window.add_element_to_window(self.canvas, True, True, 0, True) + self.window.add_element_to_window(self.canvas, True, True, 0, 'top') self.toolbar = self._get_toolbar() if self.toolbar is not None: h += self.window.add_element_to_window(self.toolbar, - False, False, 0) + False, False, 0, 'bottom') self.window.set_default_size(w, h) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index b208dcbea098..cfe31e2a2267 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -408,15 +408,17 @@ def __init__(self, title): self.window.add(self.vbox) self.vbox.show() - self.window.connect('destroy', self.destroy_event) # TODO create in base + self.window.connect('destroy', self.destroy_event) self.window.connect('delete_event', self.destroy_event) - def add_element_to_window(self, element, expand, fill, padding, from_start=False): + def add_element_to_window(self, element, expand, fill, pad, side='bottom'): element.show() - if from_start: - self.vbox.pack_start(element, expand, fill, padding) + if side == 'top': + self.vbox.pack_start(element, expand, fill, pad) + elif side == 'bottom': + self.vbox.pack_end(element, expand, fill, pad) else: - self.vbox.pack_end(element, False, False, 0) + raise KeyError('Unknown value for side, %s' % side) size_request = element.size_request() return size_request.height From f0eb84cad004f0f7f2a58bb53ef84c124e4243d0 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 19:29:36 +0100 Subject: [PATCH 08/55] Better destroy order. --- lib/matplotlib/backend_managers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 2d414a044431..50c71f186d91 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -78,10 +78,10 @@ def _destroy(self, event=None): self._callbacks.process(s, event) def destroy(self, *args): - self.window.destroy() self.canvas.destroy() if self.toolbar: self.toolbar.destroy() + self.window.destroy() self.mainloop.__del__() From 8fe9cd7cbb8d676dec5a9f89db473f7eda819bd4 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 28 Feb 2015 12:04:29 +0100 Subject: [PATCH 09/55] GTK simplifications --- lib/matplotlib/backends/backend_gtk3.py | 34 ++++++++++--------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index cfe31e2a2267..ad0b93ebc370 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -385,14 +385,14 @@ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ -class WindowGTK3(WindowBase): +class WindowGTK3(WindowBase, Gtk.Window): def __init__(self, title): WindowBase.__init__(self, title) - self.window = Gtk.Window() + Gtk.Window.__init__(self) self.set_window_title(title) try: - self.window.set_icon_from_file(window_icon) + self.set_icon_from_file(window_icon) except (SystemExit, KeyboardInterrupt): # re-raise exit type Exceptions raise @@ -405,11 +405,11 @@ def __init__(self, title): self.vbox = Gtk.Box() self.vbox.set_property('orientation', Gtk.Orientation.VERTICAL) - self.window.add(self.vbox) + self.add(self.vbox) self.vbox.show() - self.window.connect('destroy', self.destroy_event) - self.window.connect('delete_event', self.destroy_event) + self.connect('destroy', self.destroy_event) + self.connect('delete_event', self.destroy_event) def add_element_to_window(self, element, expand, fill, pad, side='bottom'): element.show() @@ -423,34 +423,27 @@ def add_element_to_window(self, element, expand, fill, pad, side='bottom'): return size_request.height def set_default_size(self, width, height): - self.window.set_default_size(width, height) + Gtk.Window.set_default_size(self, width, height) def show(self): # show the figure window - self.window.show() + Gtk.Window.show(self) def destroy(self): self.vbox.destroy() - self.window.destroy() + Gtk.Window.destroy(self) def set_fullscreen(self, fullscreen): if fullscreen: - self.window.fullscreen() + self.fullscreen() else: - self.window.unfullscreen() + self.unfullscreen() def get_window_title(self): - return self.window.get_title() + return self.get_title() def set_window_title(self, title): - self.window.set_title(title) - - def resize(self, width, height): - 'set the canvas size in pixels' - #_, _, cw, ch = self.canvas.allocation - #_, _, ww, wh = self.window.allocation - #self.window.resize (width-cw+ww, height-ch+wh) - self.window.resize(width, height) + self.set_title(title) class FigureManagerGTK3(FigureManagerBase): @@ -1044,4 +1037,3 @@ def error_msg_gtk(msg, parent=None): Toolbar = ToolbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 -Window = WindowGTK3 From 494e5f137def4aadd1004420978cb4311a161479 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 3 Mar 2015 11:17:32 +0100 Subject: [PATCH 10/55] Added doc and cleaned backend_managers, don't want our new file dirty. --- lib/matplotlib/_pylab_helpers.py | 5 +- lib/matplotlib/backend_bases.py | 83 ++++++++++++++++++++++++++--- lib/matplotlib/backend_managers.py | 79 +++++++++++++++++---------- lib/matplotlib/backends/__init__.py | 12 ++--- lib/matplotlib/figure.py | 7 ++- lib/matplotlib/pyplot.py | 24 +++++---- 6 files changed, 152 insertions(+), 58 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index b9d9954b4950..cbd7a341d84d 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -68,6 +68,7 @@ def show_all(cls, block=None): it does not block if run inside ipython's "%pylab" mode it does not block in interactive mode. """ + managers = cls.get_all_fig_managers() if not managers: return @@ -77,7 +78,7 @@ def show_all(cls, block=None): if block is not None: if block: - manager.mainloop() + manager._mainloop() return from matplotlib import pyplot @@ -99,7 +100,7 @@ def show_all(cls, block=None): block = False if not is_interactive() or get_backend() == 'WebAgg': - manager.mainloop() + manager._mainloop() @classmethod def destroy(cls, num): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 58704b6b9393..2ec84bb7ae22 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -20,6 +20,12 @@ pressed, x and y locations in pixel and :class:`~matplotlib.axes.Axes` coordinates. +:class:`WindowBase` + The base class to display a window. + +:class:`MainLoopBase` + The base class to start the GUI's main loop. + :class:`ShowBase` The base class for the Show class of each interactive backend; the 'show' callable is then set to Show.__call__, inherited from @@ -2537,10 +2543,10 @@ def key_press_handler(event, canvas, toolbar=None): # quit the figure (defaut key 'ctrl+w') if event.key in quit_keys: - if isinstance(canvas.manager.mainloop, MainLoopBase): # If new no Gcf. - canvas.manager._destroy('window_destroy_event') - else: + if isinstance(canvas.manager, FigureManagerBase): # Using old figman. Gcf.destroy_fig(canvas.figure) + else: + canvas.manager._destroy('window_destroy_event') if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') @@ -2623,6 +2629,14 @@ def __init__(self, name, window): class WindowBase(cbook.EventEmitter): + """The base class to show a window on screen. + + Parameters + ---------- + title : str + The title of the window. + """ + def __init__(self, title): cbook.EventEmitter.__init__(self) @@ -2636,22 +2650,51 @@ def show(self): raise NonGuiException() def destroy(self): + """Destroys the window""" pass def set_fullscreen(self, fullscreen): + """Whether to show the window fullscreen or not, GUI only. + + Parameters + ---------- + fullscreen : bool + True for yes, False for no. + """ pass - def set_default_size(self, w, h): - self.resize(w, h) + def set_default_size(self, width, height): + """Sets the default size of the window, defaults to a simple resize. - def resize(self, w, h): - """"For gui backends, resize the window (in pixels).""" + Parameters + ---------- + width : int + The default width (in pixels) of the window. + height : int + The default height (in pixels) of the window. + """ + self.resize(width, height) + + def resize(self, width, height): + """"For gui backends, resizes the window. + + Parameters + ---------- + width : int + The new width (in pixels) for the window. + height : int + The new height (in pixels) for the window. + """ pass def get_window_title(self): """ Get the title text of the window containing the figure. Return None for non-GUI backends (e.g., a PS backend). + + Returns + ------- + str : The window's title. """ return 'image' @@ -2659,16 +2702,40 @@ def set_window_title(self, title): """ Set the title text of the window containing the figure. Note that this has no effect for non-GUI backends (e.g., a PS backend). + + Parameters + ---------- + title : str + The title of the window. """ pass def add_element_to_window(self, element, expand, fill, pad, side='bottom'): """ Adds a gui widget to the window. - This has no effect for non-GUI backends + This has no effect for non-GUI backends and properties only apply + to those backends that support them, or have a suitable workaround. + + Parameters + ---------- + element : A gui element. + The element to add to the window + expand : bool + Whether the element should auto expand to fill as much space within + the window as possible. + fill : bool + If the element can expand, should it make the element bigger, + or go into extra padding? True, False respectfully. + pad : int + The extra amount of space in pixels to pad the element. """ pass def destroy_event(self, *args): + """Fires this event when the window wants to destroy itself. + + Note this method should hook up to the backend's internal window's + close event. + """ s = 'window_destroy_event' event = WindowEvent(s, self) self._callbacks.process(s, event) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 50c71f186d91..1ea37d6587a2 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -19,22 +19,60 @@ from matplotlib.figure import Figure from matplotlib.backend_bases import key_press_handler from matplotlib.backends import get_backends -(FigureCanvas, Window, Toolbar2, MainLoop, - old_new_figure_manager) = get_backends() +FigureCanvas, Window, Toolbar2, MainLoop = get_backends() class FigureManagerEvent(object): - def __init__(self, s, fm): - self.name = s - self.figure_manager = fm + """Event for when something happens to this figure manager. + i.e. the figure it controls gets closed + + Attributes + ---------- + signal : str + The name of the signal. + + figure_manager : FigureManager + The figure manager that fired the event. + """ + def __init__(self, signal, figure_manager): + self.name = signal + self.figure_manager = figure_manager class FigureManager(cbook.EventEmitter): + """ + The FigureManager creates and wraps the necessary components to display a + figure, namely the Window, FigureCanvas and Toolbar. It gets used whenever + you want the figure in a standalone window. + + Parameters + ---------- + figure : `matplotlib.figure.Figure` + The figure to manage. + + num : int + The figure number. + + Attributes + ---------- + + canvas : `matplotlib.backend_bases.FigureCanvasBase` + The GUI element on which we draw. + + toolbar : `matplotlib.backend_bases.NavigationToolbar2` + The toolbar used for interacting with the figure. + + window : `matplotlib.backend_bases.WindowBase` + The window that holds the canvas and toolbar. + + num : int + The figure number. + """ def __init__(self, figure, num): cbook.EventEmitter.__init__(self) self.num = num - self.mainloop = MainLoop() + self._mainloop = MainLoop() self.window = Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self._destroy) @@ -78,21 +116,28 @@ def _destroy(self, event=None): self._callbacks.process(s, event) def destroy(self, *args): + """Called to destroy this FigureManager, gets called by Gcf through + event magic. + """ self.canvas.destroy() if self.toolbar: self.toolbar.destroy() self.window.destroy() - self.mainloop.__del__() + self._mainloop.__del__() def show(self): + """Shows the figure""" self.window.show() def full_screen_toggle(self): + """Toggles whether we show fullscreen, alternatively call + `window.fullscreen()`""" self._full_screen_flag = not self._full_screen_flag self.window.set_fullscreen(self._full_screen_flag) def resize(self, w, h): + """"For gui backends, resize the window (in pixels).""" self.window.resize(w, h) def get_window_title(self): @@ -540,23 +585,3 @@ def get_tool(self, name, warn=True): warnings.warn("ToolManager does not control tool %s" % name) return None return self._tools[name] - - -def new_figure_manager(num, *args, **kwargs): - """ - Create a new figure manager instance - """ - show = kwargs.pop('show', None) - if old_new_figure_manager is None: # Test if we can use the new code - FigureClass = kwargs.pop('FigureClass', Figure) - thisFig = FigureClass(*args, **kwargs) - manager = new_figure_manager_given_figure(num, thisFig) - else: # TODO remove once Gcf removed from backends. Default to old code. - manager = old_new_figure_manager(num, *args, **kwargs) - manager.mainloop = MainLoop - return manager - - -def new_figure_manager_given_figure(num, figure): - manager = FigureManager(figure, num) - return manager diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 6008e31229c9..047d3cda9c8f 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -34,21 +34,19 @@ def get_backend_name(name=None): def get_backends(): backend_name = get_backend_name() _temp = __import__(backend_name, globals(), locals(), - ['Window', 'Toolbar2', 'FigureCanvas', 'MainLoop', - 'new_figure_manager'], 0) - FigureCanvas = _temp.FigureCanvas + ['Window', 'Toolbar2', 'FigureCanvas', 'MainLoop'], 0) try: Window = _temp.Window Toolbar2 = _temp.Toolbar2 + FigureCanvas = _temp.FigureCanvas MainLoop = _temp.MainLoop - old_new_figure_manager = None except AttributeError: Window = None Toolbar2 = None + FigureCanvas = None MainLoop = getattr(_temp, 'show', do_nothing_show) - old_new_figure_manager = _temp.new_figure_manager - return FigureCanvas, Window, Toolbar2, MainLoop, old_new_figure_manager + return FigureCanvas, Window, Toolbar2, MainLoop def pylab_setup(name=None): @@ -97,7 +95,7 @@ def do_nothing(*args, **kwargs): backend_version = getattr(backend_mod, 'backend_version', 'unknown') - show = None if hasattr(backend_mod, 'show') else do_nothing_show + show = getattr(backend_mod, 'show', do_nothing_show) draw_if_interactive = getattr(backend_mod, 'draw_if_interactive', do_nothing) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 3d6cd565c3cc..f97bbe972f31 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1562,12 +1562,11 @@ def __setstate__(self, state): import matplotlib.backend_managers as managers allnums = plt.get_fignums() num = max(allnums) + 1 if allnums else 1 - if managers.old_new_figure_manager: + if managers.Window is not None: # Can we use the new code? + mgr = managers.FigureManager(self, num) + else: mgr = plt._backend_mod.new_figure_manager_given_figure(num, self) - mgr.mainloop = plt._show - else: - mgr = managers.FigureManager(self, num) # XXX The following is a copy and paste from pyplot. Consider # factoring to pylab_helpers diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 425e925836ff..4c4837846fcd 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -249,10 +249,10 @@ def show(*args, **kw): described above. """ global _show - if _show is None: + if backend_managers.Window is not None: # Can we use the new code? return _pylab_helpers.Gcf.show_all(*args, **kw) else: - _show(*args, **kw) + _show(*args, **kw) def isinteractive(): @@ -528,14 +528,18 @@ def figure(num=None, # autoincrement if None, else integer from 1-N if get_backend().lower() == 'ps': dpi = 72 - figManager = backend_managers.new_figure_manager(num, figsize=figsize, - dpi=dpi, - facecolor=facecolor, - edgecolor=edgecolor, - frameon=frameon, - FigureClass=FigureClass, - show=_show, - **kwargs) + if backend_managers.Window is not None: # Can we use the new code? + fig = FigureClass(figsize=figsize, dpi=dpi, facecolor=facecolor, + edgecolor=edgecolor, frameon=frameon, **kwargs) + figManager = backend_managers.FigureManager(fig, num) + else: + figManager = new_figure_manager(num, figsize=figsize, + dpi=dpi, + facecolor=facecolor, + edgecolor=edgecolor, + frameon=frameon, + FigureClass=FigureClass, + **kwargs) if figLabel: figManager.set_window_title(figLabel) From ed1617892ffdcfd76ae3c9242d74909dd96fa163 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 6 Apr 2015 23:55:22 +0200 Subject: [PATCH 11/55] Improve layout! --- lib/matplotlib/backend_bases.py | 5 ++- lib/matplotlib/backend_managers.py | 4 +-- lib/matplotlib/backends/backend_gtk3.py | 36 +++++++++++++------- lib/matplotlib/backends/backend_gtk3agg.py | 2 +- lib/matplotlib/backends/backend_gtk3cairo.py | 2 +- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 2ec84bb7ae22..ed5418d81a73 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2710,7 +2710,7 @@ def set_window_title(self, title): """ pass - def add_element_to_window(self, element, expand, fill, pad, side='bottom'): + def add_element_to_window(self, element, expand, fill, pad, place): """ Adds a gui widget to the window. This has no effect for non-GUI backends and properties only apply to those backends that support them, or have a suitable workaround. @@ -2727,6 +2727,9 @@ def add_element_to_window(self, element, expand, fill, pad, side='bottom'): or go into extra padding? True, False respectfully. pad : int The extra amount of space in pixels to pad the element. + place : string + The location to place the element, either compass points north, + east, south, west, or center. """ pass diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 1ea37d6587a2..7567e7dde220 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -84,12 +84,12 @@ def __init__(self, figure, num): w = int(self.canvas.figure.bbox.width) h = int(self.canvas.figure.bbox.height) - self.window.add_element_to_window(self.canvas, True, True, 0, 'top') + self.window.add_element_to_window(self.canvas, True, True, 0, 'center') self.toolbar = self._get_toolbar() if self.toolbar is not None: h += self.window.add_element_to_window(self.toolbar, - False, False, 0, 'bottom') + False, False, 0, 'south') self.window.set_default_size(w, h) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index ad0b93ebc370..83365f77a235 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -385,7 +385,7 @@ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ -class WindowGTK3(WindowBase, Gtk.Window): +class Window(WindowBase, Gtk.Window): def __init__(self, title): WindowBase.__init__(self, title) Gtk.Window.__init__(self) @@ -403,22 +403,35 @@ def __init__(self, title): # better way is - JDH verbose.report('Could not load matplotlib icon: %s' % sys.exc_info()[1]) - self.vbox = Gtk.Box() - self.vbox.set_property('orientation', Gtk.Orientation.VERTICAL) - self.add(self.vbox) - self.vbox.show() + self._layout = {} + self._setup_box('_outer', Gtk.Orientation.VERTICAL, False, None) + self._setup_box('north', Gtk.Orientation.VERTICAL, False, '_outer') + self._setup_box('_middle', Gtk.Orientation.HORIZONTAL, True, '_outer') + self._setup_box('south', Gtk.Orientation.VERTICAL, False, '_outer') + + self._setup_box('west', Gtk.Orientation.HORIZONTAL, False, '_middle') + self._setup_box('center', Gtk.Orientation.VERTICAL, True, '_middle') + self._setup_box('east', Gtk.Orientation.HORIZONTAL, False, '_middle') + + self.add(self._layout['_outer']) self.connect('destroy', self.destroy_event) self.connect('delete_event', self.destroy_event) - def add_element_to_window(self, element, expand, fill, pad, side='bottom'): + def _setup_box(self, name, orientation, grow, parent): + self._layout[name] = Gtk.Box(orientation=orientation) + if parent: + self._layout[parent].pack_start(self._layout[name], grow, grow, 0) + self._layout[name].show() + + def add_element_to_window(self, element, expand, fill, pad, place): element.show() - if side == 'top': - self.vbox.pack_start(element, expand, fill, pad) - elif side == 'bottom': - self.vbox.pack_end(element, expand, fill, pad) + if place in ['north', 'west', 'center']: + self._layout[place].pack_start(element, expand, fill, pad) + elif place in ['south', 'east']: + self._layout[place].pack_end(element, expand, fill, pad) else: - raise KeyError('Unknown value for side, %s' % side) + raise KeyError('Unknown value for place, %s' % place) size_request = element.size_request() return size_request.height @@ -430,7 +443,6 @@ def show(self): Gtk.Window.show(self) def destroy(self): - self.vbox.destroy() Gtk.Window.destroy(self) def set_fullscreen(self, fullscreen): diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 5bc4bdcf0afd..1962da817062 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -121,7 +121,7 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Agg FigureManager = FigureManagerGTK3Agg -Window = backend_gtk3.WindowGTK3 +Window = backend_gtk3.Window Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index d00ebdebd9c6..9486d01ffa0c 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -72,7 +72,7 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Cairo FigureManager = FigureManagerGTK3Cairo -Window = backend_gtk3.WindowGTK3 +Window = backend_gtk3.Window Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show From 21b8f58dede8193b0b01649f34099f60b8c89628 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 7 Apr 2015 21:05:36 +0200 Subject: [PATCH 12/55] Move knowledge of the backend to the manager. --- lib/matplotlib/backend_managers.py | 12 ++++++------ lib/matplotlib/backends/__init__.py | 18 +++--------------- lib/matplotlib/figure.py | 2 +- lib/matplotlib/pyplot.py | 4 ++-- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 7567e7dde220..b3b4471a37d1 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -18,8 +18,7 @@ from matplotlib import rcParams from matplotlib.figure import Figure from matplotlib.backend_bases import key_press_handler -from matplotlib.backends import get_backends -FigureCanvas, Window, Toolbar2, MainLoop = get_backends() +from matplotlib.backends import get_backend class FigureManagerEvent(object): @@ -72,11 +71,12 @@ def __init__(self, figure, num): cbook.EventEmitter.__init__(self) self.num = num - self._mainloop = MainLoop() - self.window = Window('Figure %d' % num) + self._backend = get_backend() + self._mainloop = self._backend.MainLoop() + self.window = self._backend.Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self._destroy) - self.canvas = FigureCanvas(figure, manager=self) + self.canvas = self._backend.FigureCanvas(figure, manager=self) self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', self.key_press) @@ -158,7 +158,7 @@ def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set if rcParams['toolbar'] == 'toolbar2': - toolbar = Toolbar2(self.canvas, self.window) + toolbar = self._backend.Toolbar2(self.canvas, self.window) else: toolbar = None return toolbar diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 047d3cda9c8f..ca1b32c11bda 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -31,22 +31,10 @@ def get_backend_name(name=None): return backend_name -def get_backends(): +def get_backend(): backend_name = get_backend_name() - _temp = __import__(backend_name, globals(), locals(), - ['Window', 'Toolbar2', 'FigureCanvas', 'MainLoop'], 0) - try: - Window = _temp.Window - Toolbar2 = _temp.Toolbar2 - FigureCanvas = _temp.FigureCanvas - MainLoop = _temp.MainLoop - except AttributeError: - Window = None - Toolbar2 = None - FigureCanvas = None - MainLoop = getattr(_temp, 'show', do_nothing_show) - - return FigureCanvas, Window, Toolbar2, MainLoop + return __import__(backend_name, globals(), locals(), + [backend_name], 0) def pylab_setup(name=None): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f97bbe972f31..c2e34e6f474d 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1562,7 +1562,7 @@ def __setstate__(self, state): import matplotlib.backend_managers as managers allnums = plt.get_fignums() num = max(allnums) + 1 if allnums else 1 - if managers.Window is not None: # Can we use the new code? + if hasattr(plt._backend_mod, 'Window'): # Can we use MEP 27 code? mgr = managers.FigureManager(self, num) else: mgr = plt._backend_mod.new_figure_manager_given_figure(num, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 4c4837846fcd..be821a04e093 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -249,7 +249,7 @@ def show(*args, **kw): described above. """ global _show - if backend_managers.Window is not None: # Can we use the new code? + if hasattr(_backend_mod, 'Window'): # Can we use the new code? return _pylab_helpers.Gcf.show_all(*args, **kw) else: _show(*args, **kw) @@ -528,7 +528,7 @@ def figure(num=None, # autoincrement if None, else integer from 1-N if get_backend().lower() == 'ps': dpi = 72 - if backend_managers.Window is not None: # Can we use the new code? + if hasattr(_backend_mod, 'Window'): # Can we use the MEP 27 code? fig = FigureClass(figsize=figsize, dpi=dpi, facecolor=facecolor, edgecolor=edgecolor, frameon=frameon, **kwargs) figManager = backend_managers.FigureManager(fig, num) From cf42e3b69c892a64d7991a8b25956c5645bce965 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 12 Apr 2015 19:30:25 +0200 Subject: [PATCH 13/55] Incorporate MEP22 into MEP27 --- lib/matplotlib/backend_managers.py | 28 ++++++++++++++++++-- lib/matplotlib/backends/backend_gtk3.py | 12 ++++++++- lib/matplotlib/backends/backend_gtk3agg.py | 2 ++ lib/matplotlib/backends/backend_gtk3cairo.py | 2 ++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index b3b4471a37d1..8b978eb02b7c 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -1,4 +1,8 @@ """ +`FigureManager` + Class that pulls all of the standard GUI elements together, and manages + the interaction between them. + `ToolManager` Class that makes the bridge between user interaction (key press, toolbar clicks, ..) and the actions in response to the user inputs. @@ -86,7 +90,17 @@ def __init__(self, figure, num): self.window.add_element_to_window(self.canvas, True, True, 0, 'center') + self.toolmanager = self._get_toolmanager() self.toolbar = self._get_toolbar() + + if self.toolmanager: + tools.add_tools_to_manager(self.toolmanager) + if self.toolbar: + tools.add_tools_to_container(self.toolbar) + self.statusbar = self._backend.Statusbar(self.toolmanager) + h += self.window.add_element_to_window(self.statusbar, False, + False, 0, 'south') + if self.toolbar is not None: h += self.window.add_element_to_window(self.toolbar, False, False, 0, 'south') @@ -98,7 +112,7 @@ def __init__(self, figure, num): def notify_axes_change(fig): 'this will be called whenever the current axes is changed' - if self.toolbar is not None: + if self.toolmanager is None and self.toolbar is not None: self.toolbar.update() self.canvas.figure.add_axobserver(notify_axes_change) @@ -157,12 +171,22 @@ def set_window_title(self, title): def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set - if rcParams['toolbar'] == 'toolbar2': + if rcParams['toolbar'] == 'toolmanager': + toolbar = self._backend.Toolbar(self.toolmanager) + elif rcParams['toolbar'] == 'toolbar2': toolbar = self._backend.Toolbar2(self.canvas, self.window) else: toolbar = None return toolbar + def _get_toolmanager(self): + # must be initialised after toolbar has been setted + if rcParams['toolbar'] != 'toolbar2': + toolmanager = ToolManager(self.canvas) + else: + toolmanager = None + return toolmanager + def show_popup(self, msg): """ Display message in a popup -- GUI only diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 83365f77a235..27a075876ea3 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -385,6 +385,10 @@ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ + +_flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL] + + class Window(WindowBase, Gtk.Window): def __init__(self, title): WindowBase.__init__(self, title) @@ -426,14 +430,19 @@ def _setup_box(self, name, orientation, grow, parent): def add_element_to_window(self, element, expand, fill, pad, place): element.show() + + flow = _flow[not _flow.index(self._layout[place].get_orientation())] + separator = Gtk.Separator(orientation=flow) if place in ['north', 'west', 'center']: self._layout[place].pack_start(element, expand, fill, pad) + self._layout[place].pack_start(separator, False, False, 0) elif place in ['south', 'east']: self._layout[place].pack_end(element, expand, fill, pad) + self._layout[place].pack_end(separator, False, False, 0) else: raise KeyError('Unknown value for place, %s' % place) size_request = element.size_request() - return size_request.height + return size_request.height + separator.size_request().height def set_default_size(self, width, height): Gtk.Window.set_default_size(self, width, height) @@ -1047,5 +1056,6 @@ def error_msg_gtk(msg, parent=None): backend_tools.ToolRubberband = RubberbandGTK3 Toolbar = ToolbarGTK3 +Statusbar = StatusbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 1962da817062..990cbf752cbe 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -122,6 +122,8 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Agg FigureManager = FigureManagerGTK3Agg Window = backend_gtk3.Window +Toolbar = backend_gtk3.Toolbar +Statusbar = backend_gtk3.Statusbar Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 9486d01ffa0c..5c9dec3166fd 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -73,6 +73,8 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Cairo FigureManager = FigureManagerGTK3Cairo Window = backend_gtk3.Window +Toolbar = backend_gtk3.Toolbar +Statusbar = backend_gtk3.Statusbar Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show From f027b1613d81d0e6266b93e2d9eb3c8d406b9fa4 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 12 Apr 2015 23:54:05 +0200 Subject: [PATCH 14/55] Improved new toolbar and updated tool_manager example accoridingly. --- examples/user_interfaces/toolmanager.py | 52 ++++++++++++++++++++++--- lib/matplotlib/backends/backend_gtk3.py | 25 +++++++++--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index c91e76f8b3ac..1dbb33d85ee2 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -12,8 +12,10 @@ matplotlib.use('GTK3Cairo') matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt -from matplotlib.backend_tools import ToolBase, ToolToggleBase +from matplotlib.backend_tools import (ToolBase, ToolToggleBase, + add_tools_to_container) from gi.repository import Gtk, Gdk +from random import uniform class ListTools(ToolBase): @@ -60,7 +62,6 @@ def disable(self, *args): self.set_lines_visibility(True) def set_lines_visibility(self, state): - gr_lines = [] for ax in self.figure.get_axes(): for line in ax.get_lines(): if line.get_gid() == self.gid: @@ -68,14 +69,44 @@ def set_lines_visibility(self, state): self.figure.canvas.draw() +class LineTool(ToolBase): + description = 'Draw a random line' + + def __init__(self, *args, **kwargs): + self.color = kwargs.pop('color') + ToolBase.__init__(self, *args, **kwargs) + + def trigger(self, *args, **kwargs): + x0, y0, x1, y1 = (uniform(0, 2), uniform(1, 4), uniform(0, 2), + uniform(1, 4)) + fig = self.figure + fig.gca().plot([x0, x1], [y0, y1], color=self.color, gid=self.color) + fig.canvas.draw_idle() + + +class DotTool(ToolBase): + description = 'Draw a random dot' + + def __init__(self, *args, **kwargs): + self.color = kwargs.pop('color') + ToolBase.__init__(self, *args, **kwargs) + + def trigger(self, *args, **kwargs): + x0, y0 = uniform(0, 2), uniform(1, 4) + fig = self.figure + fig.gca().plot([x0], [y0], 'o', color=self.color, gid=self.color) + fig.canvas.draw_idle() + + fig = plt.figure() plt.plot([1, 2, 3], gid='mygroup') plt.plot([2, 3, 4], gid='unknown') plt.plot([3, 2, 1], gid='mygroup') # Add the custom tools that we created -fig.canvas.manager.toolmanager.add_tool('List', ListTools) -fig.canvas.manager.toolmanager.add_tool('Hide', GroupHideTool, gid='mygroup') +tool_mgr = fig.canvas.manager.toolmanager +tool_mgr.add_tool('List', ListTools) +tool_mgr.add_tool('Hide', GroupHideTool, gid='mygroup') # Add an existing tool to new group `foo`. @@ -83,10 +114,21 @@ def set_lines_visibility(self, state): fig.canvas.manager.toolbar.add_tool('zoom', 'foo') # Remove the forward button -fig.canvas.manager.toolmanager.remove_tool('forward') +tool_mgr.remove_tool('forward') # To add a custom tool to the toolbar at specific location inside # the navigation group fig.canvas.manager.toolbar.add_tool('Hide', 'navigation', 1) +for i, c in enumerate(['yellowgreen', 'forestgreen']): + sidebar = fig.canvas.manager._get_toolbar() + sidebar.set_flow('vertical') + tools = [['shapes', [tool_mgr.add_tool('L%s' % i, LineTool, color=c), + tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], + ['hide', [tool_mgr.add_tool('H%s' % i, GroupHideTool, gid=c)]]] + + fig.canvas.manager.window.add_element_to_window(sidebar, False, False, 0, + 'west') + add_tools_to_container(sidebar, tools) + plt.show() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 27a075876ea3..b89d0f97e4eb 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -387,6 +387,7 @@ def stop_event_loop(self): _flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL] +flow_types = ['horizontal', 'vertical'] class Window(WindowBase, Gtk.Window): @@ -848,13 +849,12 @@ def draw_rubberband(self, x0, y0, x1, y1): class ToolbarGTK3(ToolContainerBase, Gtk.Box): - def __init__(self, toolmanager): + def __init__(self, toolmanager, flow='horizontal'): ToolContainerBase.__init__(self, toolmanager) Gtk.Box.__init__(self) - self.set_property("orientation", Gtk.Orientation.VERTICAL) - self._toolarea = Gtk.Box() - self._toolarea.set_property('orientation', Gtk.Orientation.HORIZONTAL) + self.set_flow(flow) + self.pack_start(self._toolarea, False, False, 0) self._toolarea.show_all() self._groups = {} @@ -887,7 +887,7 @@ def _add_button(self, button, group, position): if group not in self._groups: if self._groups: self._add_separator() - toolbar = Gtk.Toolbar() + toolbar = Gtk.Toolbar(orientation=_flow[self._flow]) toolbar.set_style(Gtk.ToolbarStyle.ICONS) self._toolarea.pack_start(toolbar, False, False, 0) toolbar.show_all() @@ -916,9 +916,22 @@ def remove_toolitem(self, name): self._groups[group].remove(toolitem) del self._toolitems[name] + def set_flow(self, flow): + try: + self._flow = flow_types.index(flow) + except ValueError: + raise ValueError('Flow (%s), not in list %s' % (flow, flow_types)) + self.set_property("orientation", _flow[not self._flow]) + self._toolarea.set_property('orientation', _flow[self._flow]) + for item in self._toolarea: + if isinstance(item, Gtk.Separator): + item.set_property("orientation", _flow[not self._flow]) + else: + item.set_property("orientation", _flow[self._flow]) + def _add_separator(self): sep = Gtk.Separator() - sep.set_property("orientation", Gtk.Orientation.VERTICAL) + sep.set_property("orientation", _flow[not self._flow]) self._toolarea.pack_start(sep, False, True, 0) sep.show_all() From 24b7b7306551a38d32014b8c57a8c241679d2f65 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 13 Apr 2015 08:05:54 +0200 Subject: [PATCH 15/55] fullscreen --- lib/matplotlib/backend_managers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 8b978eb02b7c..256c9e815492 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -106,6 +106,7 @@ def __init__(self, figure, num): False, False, 0, 'south') self.window.set_default_size(w, h) + self._full_screen_flag = False if is_interactive(): self.window.show() From bc9912947935cb6a0cd49f5a8983e8c7cbd923c2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 14 Apr 2015 19:51:34 +0200 Subject: [PATCH 16/55] MEP update --- doc/devel/MEP/MEP27.rst | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/doc/devel/MEP/MEP27.rst b/doc/devel/MEP/MEP27.rst index 57b0540a4c91..aaf404df1c25 100644 --- a/doc/devel/MEP/MEP27.rst +++ b/doc/devel/MEP/MEP27.rst @@ -8,14 +8,16 @@ Status ====== -**Discussion** +**Progress** Branches and Pull requests ========================== Main PR (including GTK3): + + https://github.com/matplotlib/matplotlib/pull/4143 Backend specific branch diffs: + + https://github.com/OceanWolf/matplotlib/compare/backend-refactor...OceanWolf:backend-refactor-tkagg + https://github.com/OceanWolf/matplotlib/compare/backend-refactor...OceanWolf:backend-refactor-qt + https://github.com/OceanWolf/matplotlib/compare/backend-refactor...backend-refactor-wx @@ -79,7 +81,7 @@ The description of this MEP gives us most of the solution: 1. To remove the windowing aspect out of ``FigureManagerBase`` letting it simply wrap this new class along with the other backend classes. Create a new ``WindowBase`` class that can handle this - functionality, with pass-through methods (:arrow_right:) to + functionality, with pass-through methods (->) to ``WindowBase``. Classes that subclass ``WindowBase`` should also subclass the GUI specific window class to ensure backward compatibility (``manager.window == manager.window``). @@ -103,30 +105,30 @@ The description of this MEP gives us most of the solution: |FigureManagerBase(canvas, num) |FigureManager(figure, num) |``WindowBase(title)``|Notes | | | | | | +======================================+==============================+=====================+================================+ -|show | |show | | +|show |-> |show | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ |destroy |calls destroy on all |destroy | | | |components | | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ |full_screen_toggle |handles logic |set_fullscreen | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|resize | |resize | | +|resize |-> |resize | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|key_press |key_press | | | +|key_press |key_press |X | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|show_popup |show_poup | |Not used anywhere in mpl, and | +|show_popup |show_poup |X |Not used anywhere in mpl, and | | | | |does nothing. | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|get_window_title | |get_window_title | | +|get_window_title |-> |get_window_title | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|set_window_title | |set_window_title | | +|set_window_title |-> |set_window_title | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -| |_get_toolbar | |A common method to all | +|X |_get_toolbar |X |A common method to all | | | | |subclasses of FigureManagerBase | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -| | |set_default_size | | +|X |X |set_default_size | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -| | |add_element_to_window| | +|X |X |add_element_to_window| | +--------------------------------------+------------------------------+---------------------+--------------------------------+ @@ -135,14 +137,14 @@ The description of this MEP gives us most of the solution: +==========+============+=============+ |mainloop |begin | | +----------+------------+-------------+ -| |end |Gets called | +|X |end |Gets called | | | |automagically| | | |when no more | | | |instances of | | | |the subclass | | | |exist | +----------+------------+-------------+ -|__call__ | |Method moved | +|__call__ |X |Method moved | | | |to | | | |Gcf.show_all | +----------+------------+-------------+ @@ -191,6 +193,8 @@ in the same manner as everything else. | | |window, so this also | | | |breaks BC. | +-------------------------+-------------------------+-------------------------+ +|WebAgg |canvas | | ++-------------------------+-------------------------+-------------------------+ Alternatives From 7f7f05e6ca00a1321c957971fc7b6f15f98d3ee6 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 15 Apr 2015 01:00:20 +0200 Subject: [PATCH 17/55] Finish MEP22 conversion --- lib/matplotlib/backend_bases.py | 8 ++------ lib/matplotlib/backend_managers.py | 4 ++-- lib/matplotlib/backend_tools.py | 8 ++++++-- lib/matplotlib/backends/backend_gtk3.py | 25 ++----------------------- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index ed5418d81a73..7e17333f6b54 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2761,12 +2761,8 @@ def __init__(self, canvas, num): canvas.manager = self # store a pointer to parent self.num = num - if rcParams['toolbar'] != 'toolmanager': - self.key_press_handler_id = self.canvas.mpl_connect( - 'key_press_event', - self.key_press) - else: - self.key_press_handler_id = None + self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', + self.key_press) """ The returned id from connecting the default key handler via :meth:`FigureCanvasBase.mpl_connnect`. diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 256c9e815492..b176b37d8a57 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -83,7 +83,7 @@ def __init__(self, figure, num): self.canvas = self._backend.FigureCanvas(figure, manager=self) self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', - self.key_press) + self.key_press) if rcParams['toolbar'] != 'toolmanager' else None w = int(self.canvas.figure.bbox.width) h = int(self.canvas.figure.bbox.height) @@ -183,7 +183,7 @@ def _get_toolbar(self): def _get_toolmanager(self): # must be initialised after toolbar has been setted if rcParams['toolbar'] != 'toolbar2': - toolmanager = ToolManager(self.canvas) + toolmanager = ToolManager(self.canvas.figure) else: toolmanager = None return toolmanager diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index c896689bc610..c809a8f89400 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -13,7 +13,6 @@ from matplotlib import rcParams -from matplotlib._pylab_helpers import Gcf import matplotlib.cbook as cbook from weakref import WeakKeyDictionary import six @@ -353,7 +352,12 @@ class ToolQuit(ToolBase): default_keymap = rcParams['keymap.quit'] def trigger(self, sender, event, data=None): - Gcf.destroy_fig(self.figure) + try: + manager = self.figure.canvas.manager + except: + pass + else: + manager._destroy('window_destroy_event') class ToolQuitAll(ToolBase): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index b89d0f97e4eb..66ac5150b2e0 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -434,6 +434,7 @@ def add_element_to_window(self, element, expand, fill, pad, place): flow = _flow[not _flow.index(self._layout[place].get_orientation())] separator = Gtk.Separator(orientation=flow) + separator.show() if place in ['north', 'west', 'center']: self._layout[place].pack_start(element, expand, fill, pad) self._layout[place].pack_start(separator, False, False, 0) @@ -508,9 +509,7 @@ def __init__(self, canvas, num): w = int (self.canvas.figure.bbox.width) h = int (self.canvas.figure.bbox.height) - self.toolmanager = self._get_toolmanager() self.toolbar = self._get_toolbar() - self.statusbar = None def add_widget(child, expand, fill, padding): child.show() @@ -518,14 +517,6 @@ def add_widget(child, expand, fill, padding): size_request = child.size_request() return size_request.height - if self.toolmanager: - backend_tools.add_tools_to_manager(self.toolmanager) - if self.toolbar: - backend_tools.add_tools_to_container(self.toolbar) - self.statusbar = StatusbarGTK3(self.toolmanager) - h += add_widget(self.statusbar, False, False, 0) - h += add_widget(Gtk.HSeparator(), False, False, 0) - if self.toolbar is not None: self.toolbar.show() h += add_widget(self.toolbar, False, False, 0) @@ -542,9 +533,7 @@ def destroy(*args): def notify_axes_change(fig): 'this will be called whenever the current axes is changed' - if self.toolmanager is not None: - pass - elif self.toolbar is not None: + if self.toolbar is not None: self.toolbar.update() self.canvas.figure.add_axobserver(notify_axes_change) @@ -581,20 +570,10 @@ def _get_toolbar(self): # attrs are set if rcParams['toolbar'] == 'toolbar2': toolbar = NavigationToolbar2GTK3 (self.canvas, self.window) - elif rcParams['toolbar'] == 'toolmanager': - toolbar = ToolbarGTK3(self.toolmanager) else: toolbar = None return toolbar - def _get_toolmanager(self): - # must be initialised after toolbar has been setted - if rcParams['toolbar'] != 'toolbar2': - toolmanager = ToolManager(self.canvas.figure) - else: - toolmanager = None - return toolmanager - def get_window_title(self): return self.window.get_title() From 713abcb2e022b27a0cc891d746d67538d06de387 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 17 Apr 2015 19:41:17 +0200 Subject: [PATCH 18/55] rename window method --- doc/devel/MEP/MEP27.rst | 2 +- examples/user_interfaces/toolmanager.py | 3 +-- lib/matplotlib/backend_bases.py | 7 +------ lib/matplotlib/backend_managers.py | 8 +++----- lib/matplotlib/backends/backend_gtk3.py | 6 +++--- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/doc/devel/MEP/MEP27.rst b/doc/devel/MEP/MEP27.rst index aaf404df1c25..8e71c5ef0a9f 100644 --- a/doc/devel/MEP/MEP27.rst +++ b/doc/devel/MEP/MEP27.rst @@ -128,7 +128,7 @@ The description of this MEP gives us most of the solution: +--------------------------------------+------------------------------+---------------------+--------------------------------+ |X |X |set_default_size | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|X |X |add_element_to_window| | +|X |X |add_element | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index 1dbb33d85ee2..24fc5d83adf6 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -127,8 +127,7 @@ def trigger(self, *args, **kwargs): tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], ['hide', [tool_mgr.add_tool('H%s' % i, GroupHideTool, gid=c)]]] - fig.canvas.manager.window.add_element_to_window(sidebar, False, False, 0, - 'west') + fig.canvas.manager.window.add_element(sidebar, False, 'west') add_tools_to_container(sidebar, tools) plt.show() diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 7e17333f6b54..2275f4dd1d37 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2710,7 +2710,7 @@ def set_window_title(self, title): """ pass - def add_element_to_window(self, element, expand, fill, pad, place): + def add_element(self, element, expand, place): """ Adds a gui widget to the window. This has no effect for non-GUI backends and properties only apply to those backends that support them, or have a suitable workaround. @@ -2722,11 +2722,6 @@ def add_element_to_window(self, element, expand, fill, pad, place): expand : bool Whether the element should auto expand to fill as much space within the window as possible. - fill : bool - If the element can expand, should it make the element bigger, - or go into extra padding? True, False respectfully. - pad : int - The extra amount of space in pixels to pad the element. place : string The location to place the element, either compass points north, east, south, west, or center. diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index b176b37d8a57..8eeafe8dc699 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -88,7 +88,7 @@ def __init__(self, figure, num): w = int(self.canvas.figure.bbox.width) h = int(self.canvas.figure.bbox.height) - self.window.add_element_to_window(self.canvas, True, True, 0, 'center') + self.window.add_element(self.canvas, True, 'center') self.toolmanager = self._get_toolmanager() self.toolbar = self._get_toolbar() @@ -98,12 +98,10 @@ def __init__(self, figure, num): if self.toolbar: tools.add_tools_to_container(self.toolbar) self.statusbar = self._backend.Statusbar(self.toolmanager) - h += self.window.add_element_to_window(self.statusbar, False, - False, 0, 'south') + h += self.window.add_element(self.statusbar, False, 'south') if self.toolbar is not None: - h += self.window.add_element_to_window(self.toolbar, - False, False, 0, 'south') + h += self.window.add_element(self.toolbar, False, 'south') self.window.set_default_size(w, h) self._full_screen_flag = False diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 66ac5150b2e0..7394d0e8f575 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -429,17 +429,17 @@ def _setup_box(self, name, orientation, grow, parent): self._layout[parent].pack_start(self._layout[name], grow, grow, 0) self._layout[name].show() - def add_element_to_window(self, element, expand, fill, pad, place): + def add_element(self, element, expand, place): element.show() flow = _flow[not _flow.index(self._layout[place].get_orientation())] separator = Gtk.Separator(orientation=flow) separator.show() if place in ['north', 'west', 'center']: - self._layout[place].pack_start(element, expand, fill, pad) + self._layout[place].pack_start(element, expand, expand, 0) self._layout[place].pack_start(separator, False, False, 0) elif place in ['south', 'east']: - self._layout[place].pack_end(element, expand, fill, pad) + self._layout[place].pack_end(element, expand, expand, 0) self._layout[place].pack_end(separator, False, False, 0) else: raise KeyError('Unknown value for place, %s' % place) From 80adaaf428792d0cf8a45fbed08167c14e96792b Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 17 Apr 2015 20:29:11 +0200 Subject: [PATCH 19/55] Add backend anme to widgets --- lib/matplotlib/backends/backend_gtk3.py | 5 ++--- lib/matplotlib/backends/backend_gtk3agg.py | 8 ++++---- lib/matplotlib/backends/backend_gtk3cairo.py | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 7394d0e8f575..e9b0531698e7 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -68,7 +68,7 @@ def draw_if_interactive(): figManager.canvas.draw_idle() -class MainLoop(MainLoopBase): +class MainLoopGTK3(MainLoopBase): def begin(self): if Gtk.main_level() == 0: Gtk.main() @@ -390,7 +390,7 @@ def stop_event_loop(self): flow_types = ['horizontal', 'vertical'] -class Window(WindowBase, Gtk.Window): +class WindowGTK3(WindowBase, Gtk.Window): def __init__(self, title): WindowBase.__init__(self, title) Gtk.Window.__init__(self) @@ -1048,6 +1048,5 @@ def error_msg_gtk(msg, parent=None): backend_tools.ToolRubberband = RubberbandGTK3 Toolbar = ToolbarGTK3 -Statusbar = StatusbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 990cbf752cbe..4627436c33d0 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -121,9 +121,9 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Agg FigureManager = FigureManagerGTK3Agg -Window = backend_gtk3.Window -Toolbar = backend_gtk3.Toolbar -Statusbar = backend_gtk3.Statusbar +Window = backend_gtk3.WindowGTK3 +Toolbar = backend_gtk3.ToolbarGTK3 +Statusbar = backend_gtk3.StatusbarGTK3 Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 -MainLoop = backend_gtk3.MainLoop +MainLoop = backend_gtk3.MainLoopGTK3 show = backend_gtk3.show diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 5c9dec3166fd..65934824e554 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -72,9 +72,9 @@ def new_figure_manager_given_figure(num, figure): FigureCanvas = FigureCanvasGTK3Cairo FigureManager = FigureManagerGTK3Cairo -Window = backend_gtk3.Window -Toolbar = backend_gtk3.Toolbar -Statusbar = backend_gtk3.Statusbar +Window = backend_gtk3.WindowGTK3 +Toolbar = backend_gtk3.ToolbarGTK3 +Statusbar = backend_gtk3.StatusbarGTK3 Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 -MainLoop = backend_gtk3.MainLoop +MainLoop = backend_gtk3.MainLoopGTK3 show = backend_gtk3.show From 4e5f69dace8c2f42f594486bc887a2703f8ce42e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 4 Jun 2015 02:11:29 +0200 Subject: [PATCH 20/55] Handle FigureManager destroy internaly without pyplot. --- lib/matplotlib/_pylab_helpers.py | 2 +- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backend_managers.py | 24 +++++++++++++++--------- lib/matplotlib/backend_tools.py | 2 +- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index cbd7a341d84d..cb31fe983054 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -124,7 +124,7 @@ def destroy(cls, num): cls._activeQue.append(f) del cls.figs[num] - manager.destroy() + manager.destroy() # Unneeded with MEP27 remove later gc.collect(1) @classmethod diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 2275f4dd1d37..6f9b0246e52a 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2546,7 +2546,7 @@ def key_press_handler(event, canvas, toolbar=None): if isinstance(canvas.manager, FigureManagerBase): # Using old figman. Gcf.destroy_fig(canvas.figure) else: - canvas.manager._destroy('window_destroy_event') + canvas.manager.destroy('window_destroy_event') if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 8eeafe8dc699..ee2287a7b3c7 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -78,7 +78,7 @@ def __init__(self, figure, num): self._backend = get_backend() self._mainloop = self._backend.MainLoop() self.window = self._backend.Window('Figure %d' % num) - self.window.mpl_connect('window_destroy_event', self._destroy) + self.window.mpl_connect('window_destroy_event', self.destroy) self.canvas = self._backend.FigureCanvas(figure, manager=self) @@ -122,23 +122,29 @@ def key_press(self, event): """ key_press_handler(event, self.canvas, self.canvas.toolbar) - def _destroy(self, event=None): - # Callback from the when the window wants to destroy itself - s = 'window_destroy_event' - event = FigureManagerEvent(s, self) - self._callbacks.process(s, event) - def destroy(self, *args): - """Called to destroy this FigureManager, gets called by Gcf through - event magic. + """Called to destroy this FigureManager. """ + + # Make sure we run this routine only once for the FigureManager + # This ensures the nasty __del__ fix below works. + if getattr(self, '_destroying', False): + return + + self._destroying = True self.canvas.destroy() if self.toolbar: self.toolbar.destroy() self.window.destroy() + # Fix as for some reason we have extra references to this# + # i.e. ``del self._mainloop`` doesn't work self._mainloop.__del__() + s = 'window_destroy_event' + event = FigureManagerEvent(s, self) + self._callbacks.process(s, event) + def show(self): """Shows the figure""" self.window.show() diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index c809a8f89400..3c9c827cf513 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -357,7 +357,7 @@ def trigger(self, sender, event, data=None): except: pass else: - manager._destroy('window_destroy_event') + manager.destroy('window_destroy_event') class ToolQuitAll(ToolBase): From 160ef5748675005f214a17f2df6b03f29aeed3a9 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 7 Jun 2015 13:21:14 +0200 Subject: [PATCH 21/55] Make functionality more consistant for embedded applications --- lib/matplotlib/backend_bases.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 6f9b0246e52a..577e3cc063f4 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -147,6 +147,7 @@ class MainLoopBase(object): clever magic. """ _instance_count = {} + _running = False def __init__(self): MainLoopBase._instance_count.setdefault(self.__class__, 0) MainLoopBase._instance_count[self.__class__] += 1 @@ -158,12 +159,13 @@ def end(self): pass def __call__(self): + MainLoopBase._running = True self.begin() def __del__(self): MainLoopBase._instance_count[self.__class__] -= 1 if (MainLoopBase._instance_count[self.__class__] <= 0 and - not is_interactive()): + not is_interactive() and MainLoopBase._running): self.end() From b6d6acce0d9b02b38d139114c28fa46d470dd912 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 13 Jun 2015 20:07:50 +0200 Subject: [PATCH 22/55] Backend getter method for FigureManager --- lib/matplotlib/backend_managers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index ee2287a7b3c7..4a03d84b72c5 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -173,6 +173,10 @@ def set_window_title(self, title): """ self.window.set_window_title(title) + @property + def backend(self): + return self._backend + def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set From ecd5038973759ddc156f01aa5e441f660625abb8 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 13 Jun 2015 22:55:39 +0200 Subject: [PATCH 23/55] Improve example after new method --- examples/user_interfaces/toolmanager.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index 24fc5d83adf6..d60c45a8f86d 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -104,30 +104,31 @@ def trigger(self, *args, **kwargs): plt.plot([3, 2, 1], gid='mygroup') # Add the custom tools that we created -tool_mgr = fig.canvas.manager.toolmanager +manager = fig.canvas.manager +tool_mgr = manager.toolmanager tool_mgr.add_tool('List', ListTools) tool_mgr.add_tool('Hide', GroupHideTool, gid='mygroup') # Add an existing tool to new group `foo`. # It can be added as many times as we want -fig.canvas.manager.toolbar.add_tool('zoom', 'foo') +manager.toolbar.add_tool('zoom', 'foo') # Remove the forward button tool_mgr.remove_tool('forward') # To add a custom tool to the toolbar at specific location inside # the navigation group -fig.canvas.manager.toolbar.add_tool('Hide', 'navigation', 1) +manager.toolbar.add_tool('Hide', 'navigation', 1) for i, c in enumerate(['yellowgreen', 'forestgreen']): - sidebar = fig.canvas.manager._get_toolbar() + sidebar = manager.backend.Toolbar(manager) sidebar.set_flow('vertical') tools = [['shapes', [tool_mgr.add_tool('L%s' % i, LineTool, color=c), tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], ['hide', [tool_mgr.add_tool('H%s' % i, GroupHideTool, gid=c)]]] - fig.canvas.manager.window.add_element(sidebar, False, 'west') + manager.window.add_element(sidebar, False, 'west') add_tools_to_container(sidebar, tools) plt.show() From f8e83fe9b47d18bb8de3e12423a11cc212a47d58 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 13 Jun 2015 22:56:26 +0200 Subject: [PATCH 24/55] Clean up the code a bit --- examples/user_interfaces/toolmanager.py | 2 +- lib/matplotlib/backend_managers.py | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index d60c45a8f86d..c7a24f0371fc 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -122,7 +122,7 @@ def trigger(self, *args, **kwargs): manager.toolbar.add_tool('Hide', 'navigation', 1) for i, c in enumerate(['yellowgreen', 'forestgreen']): - sidebar = manager.backend.Toolbar(manager) + sidebar = manager.backend.Toolbar(tool_mgr) sidebar.set_flow('vertical') tools = [['shapes', [tool_mgr.add_tool('L%s' % i, LineTool, color=c), tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 4a03d84b72c5..8e2c07ed2350 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -90,7 +90,7 @@ def __init__(self, figure, num): self.window.add_element(self.canvas, True, 'center') - self.toolmanager = self._get_toolmanager() + self.toolmanager = ToolManager(self.canvas.figure) self.toolbar = self._get_toolbar() if self.toolmanager: @@ -182,20 +182,10 @@ def _get_toolbar(self): # attrs are set if rcParams['toolbar'] == 'toolmanager': toolbar = self._backend.Toolbar(self.toolmanager) - elif rcParams['toolbar'] == 'toolbar2': - toolbar = self._backend.Toolbar2(self.canvas, self.window) else: toolbar = None return toolbar - def _get_toolmanager(self): - # must be initialised after toolbar has been setted - if rcParams['toolbar'] != 'toolbar2': - toolmanager = ToolManager(self.canvas.figure) - else: - toolmanager = None - return toolmanager - def show_popup(self, msg): """ Display message in a popup -- GUI only From c53b79a4b3baad651d6083f058e705d60ed4008e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 15 Jun 2015 18:48:42 +0200 Subject: [PATCH 25/55] Remove old code from backend_managers --- lib/matplotlib/backend_bases.py | 5 +---- lib/matplotlib/backend_managers.py | 11 ----------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 577e3cc063f4..0d28bb3c54cb 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2545,10 +2545,7 @@ def key_press_handler(event, canvas, toolbar=None): # quit the figure (defaut key 'ctrl+w') if event.key in quit_keys: - if isinstance(canvas.manager, FigureManagerBase): # Using old figman. - Gcf.destroy_fig(canvas.figure) - else: - canvas.manager.destroy('window_destroy_event') + Gcf.destroy_fig(canvas.figure) if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 8e2c07ed2350..b1ffdffa8e17 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -21,7 +21,6 @@ from matplotlib import is_interactive from matplotlib import rcParams from matplotlib.figure import Figure -from matplotlib.backend_bases import key_press_handler from matplotlib.backends import get_backend @@ -82,9 +81,6 @@ def __init__(self, figure, num): self.canvas = self._backend.FigureCanvas(figure, manager=self) - self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', - self.key_press) if rcParams['toolbar'] != 'toolmanager' else None - w = int(self.canvas.figure.bbox.width) h = int(self.canvas.figure.bbox.height) @@ -115,13 +111,6 @@ def notify_axes_change(fig): self.toolbar.update() self.canvas.figure.add_axobserver(notify_axes_change) - def key_press(self, event): - """ - Implement the default mpl key bindings defined at - :ref:`key-event-handling` - """ - key_press_handler(event, self.canvas, self.canvas.toolbar) - def destroy(self, *args): """Called to destroy this FigureManager. """ From 34c6b12fadc7381c29cf0704d35468fba125ea1c Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 22 Jun 2015 19:06:49 +0200 Subject: [PATCH 26/55] Cleanup --- lib/matplotlib/backend_managers.py | 11 +++++----- lib/matplotlib/backends/__init__.py | 32 ++++++++++++++--------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index b1ffdffa8e17..8ecf492abe22 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -89,12 +89,11 @@ def __init__(self, figure, num): self.toolmanager = ToolManager(self.canvas.figure) self.toolbar = self._get_toolbar() - if self.toolmanager: - tools.add_tools_to_manager(self.toolmanager) - if self.toolbar: - tools.add_tools_to_container(self.toolbar) - self.statusbar = self._backend.Statusbar(self.toolmanager) - h += self.window.add_element(self.statusbar, False, 'south') + tools.add_tools_to_manager(self.toolmanager) + if self.toolbar: + tools.add_tools_to_container(self.toolbar) + self.statusbar = self._backend.Statusbar(self.toolmanager) + h += self.window.add_element(self.statusbar, False, 'south') if self.toolbar is not None: h += self.window.add_element(self.toolbar, False, 'south') diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index ca1b32c11bda..854298e2f172 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -31,8 +31,11 @@ def get_backend_name(name=None): return backend_name -def get_backend(): - backend_name = get_backend_name() +def get_backend(name=None): + # Import the requested backend into a generic module object + # the last argument is specifies whether to use absolute or relative + # imports. 0 means only perform absolute imports. + backend_name = get_backend_name(name) return __import__(backend_name, globals(), locals(), [backend_name], 0) @@ -65,11 +68,7 @@ def pylab_setup(name=None): ''' # Import the requested backend into a generic module object - backend_name = get_backend_name(name) - # the last argument is specifies whether to use absolute or relative - # imports. 0 means only perform absolute imports. - backend_mod = __import__(backend_name, globals(), locals(), - [backend_name], 0) + backend_mod = get_backend(name) # Things we pull in from all backends new_figure_manager = backend_mod.new_figure_manager @@ -77,6 +76,15 @@ def pylab_setup(name=None): # image backends like pdf, agg or svg do not need to do anything # for "show" or "draw_if_interactive", so if they are not defined # by the backend, just do nothing + def do_nothing_show(*args, **kwargs): + frame = inspect.currentframe() + fname = frame.f_back.f_code.co_filename + if fname in ('', ''): + warnings.warn(""" +Your currently selected backend, '%s' does not support show(). +Please select a GUI backend in your matplotlibrc file ('%s') +or with matplotlib.use()""" % + (backend, matplotlib.matplotlib_fname())) def do_nothing(*args, **kwargs): pass @@ -96,13 +104,3 @@ def do_nothing(*args, **kwargs): global backend backend = name return backend_mod, new_figure_manager, draw_if_interactive, show - -def do_nothing_show(*args, **kwargs): - frame = inspect.currentframe() - fname = frame.f_back.f_code.co_filename - if fname in ('', ''): - warnings.warn(""" -Your currently selected backend, '%s' does not support show(). -Please select a GUI backend in your matplotlibrc file ('%s') -or with matplotlib.use()""" % - (name, matplotlib.matplotlib_fname())) From 8a4268ab9b232792abb246b42de2d582c801d907 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 22 Jun 2015 19:10:12 +0200 Subject: [PATCH 27/55] Explicity get set manager as None if appropiate. --- lib/matplotlib/backend_bases.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 0d28bb3c54cb..a86fb9b715b5 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2497,11 +2497,14 @@ def destroy(self): def manager(self): if self._manager is not None: return self._manager() + return None @manager.setter def manager(self, manager): if manager is not None: self._manager = weakref.ref(manager) + else: + self._manager = None def key_press_handler(event, canvas, toolbar=None): From 44df1995f0e8ee01e32b1a7b483be51e20829826 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Mon, 22 Jun 2015 14:52:52 -0400 Subject: [PATCH 28/55] figure attribute and canvas property --- lib/matplotlib/backend_managers.py | 31 +++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 8ecf492abe22..c53b48816a81 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -61,6 +61,9 @@ class FigureManager(cbook.EventEmitter): canvas : `matplotlib.backend_bases.FigureCanvasBase` The GUI element on which we draw. + figure : `matplotlib.figure.Figure` + The figure that holds the canvas + toolbar : `matplotlib.backend_bases.NavigationToolbar2` The toolbar used for interacting with the figure. @@ -79,14 +82,15 @@ def __init__(self, figure, num): self.window = self._backend.Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self.destroy) - self.canvas = self._backend.FigureCanvas(figure, manager=self) + self._figure = None + self._set_figure(figure) - w = int(self.canvas.figure.bbox.width) - h = int(self.canvas.figure.bbox.height) + w = int(self.figure.bbox.width) + h = int(self.figure.bbox.height) - self.window.add_element(self.canvas, True, 'center') + self.window.add_element(self.figure.canvas, True, 'center') - self.toolmanager = ToolManager(self.canvas.figure) + self.toolmanager = ToolManager(self.figure) self.toolbar = self._get_toolbar() tools.add_tools_to_manager(self.toolmanager) @@ -108,7 +112,20 @@ def notify_axes_change(fig): 'this will be called whenever the current axes is changed' if self.toolmanager is None and self.toolbar is not None: self.toolbar.update() - self.canvas.figure.add_axobserver(notify_axes_change) + self.figure.add_axobserver(notify_axes_change) + + @property + def figure(self): + return self._figure + + def _set_figure(self, figure): + if not figure.canvas: + self._backend.FigureCanvas(figure, manager=self) + self._figure = figure + + @property + def canvas(self): + return self._figure.canvas def destroy(self, *args): """Called to destroy this FigureManager. @@ -120,7 +137,7 @@ def destroy(self, *args): return self._destroying = True - self.canvas.destroy() + self.figure.canvas.destroy() if self.toolbar: self.toolbar.destroy() self.window.destroy() From 860a8ed5fd0f13e7fa4c9d0259dab722a798be5f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 23 Jun 2015 18:09:05 +0200 Subject: [PATCH 29/55] Fix FigureCanvasBase --- lib/matplotlib/backend_bases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index a86fb9b715b5..8d05b531fe92 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2288,7 +2288,7 @@ def get_window_title(self): Get the title text of the window containing the figure. Return None if there is no window (e.g., a PS backend). """ - if hasattr(self, "manager"): + if getattr(self, "manager", None): return self.manager.get_window_title() def set_window_title(self, title): @@ -2296,7 +2296,7 @@ def set_window_title(self, title): Set the title text of the window containing the figure. Note that this has no effect if there is no window (e.g., a PS backend). """ - if hasattr(self, "manager"): + if getattr(self, "manager", None): self.manager.set_window_title(title) def get_default_filename(self): From c44e74443f9c4f4e3b644f470cf244871d95cb3f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 25 Jun 2015 02:19:16 +0200 Subject: [PATCH 30/55] super --- lib/matplotlib/backend_bases.py | 10 ++++++---- lib/matplotlib/backend_managers.py | 2 +- lib/matplotlib/backends/backend_gtk3.py | 11 ++++------- lib/matplotlib/cbook.py | 1 + 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8d05b531fe92..eca8e45de86e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2639,8 +2639,8 @@ class WindowBase(cbook.EventEmitter): The title of the window. """ - def __init__(self, title): - cbook.EventEmitter.__init__(self) + def __init__(self, title, **kwargs): + super(WindowBase, self).__init__(**kwargs) def show(self): """ @@ -3349,7 +3349,8 @@ class ToolContainerBase(object): this `ToolContainer` wants to communicate with. """ - def __init__(self, toolmanager): + def __init__(self, toolmanager, **kwargs): + super(ToolContainerBase, self).__init__(**kwargs) self.toolmanager = toolmanager self.toolmanager.toolmanager_connect('tool_removed_event', self._remove_tool_cbk) @@ -3475,7 +3476,8 @@ def remove_toolitem(self, name): class StatusbarBase(object): """Base class for the statusbar""" - def __init__(self, toolmanager): + def __init__(self, toolmanager, **kwargs): + super(StatusbarBase, self).__init__(**kwargs) self.toolmanager = toolmanager self.toolmanager.toolmanager_connect('tool_message_event', self._message_cbk) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index c53b48816a81..a2dabc77fc14 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -74,7 +74,7 @@ class FigureManager(cbook.EventEmitter): The figure number. """ def __init__(self, figure, num): - cbook.EventEmitter.__init__(self) + super(FigureManager, self).__init__() self.num = num self._backend = get_backend() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index e9b0531698e7..2a7a6ff073d4 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -391,9 +391,8 @@ def stop_event_loop(self): class WindowGTK3(WindowBase, Gtk.Window): - def __init__(self, title): - WindowBase.__init__(self, title) - Gtk.Window.__init__(self) + def __init__(self, title, **kwargs): + super(WindowGTK3, self).__init__(title=title, **kwargs) self.set_window_title(title) try: @@ -829,8 +828,7 @@ def draw_rubberband(self, x0, y0, x1, y1): class ToolbarGTK3(ToolContainerBase, Gtk.Box): def __init__(self, toolmanager, flow='horizontal'): - ToolContainerBase.__init__(self, toolmanager) - Gtk.Box.__init__(self) + super(ToolbarGTK3, self).__init__(toolmanager=toolmanager) self._toolarea = Gtk.Box() self.set_flow(flow) @@ -917,8 +915,7 @@ def _add_separator(self): class StatusbarGTK3(StatusbarBase, Gtk.Statusbar): def __init__(self, *args, **kwargs): - StatusbarBase.__init__(self, *args, **kwargs) - Gtk.Statusbar.__init__(self) + super(StatusbarGTK3, self).__init__(*args, **kwargs) self._context = self.get_context_id('message') def set_message(self, s): diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 73ef91e64a7d..ed4ef4f20621 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -545,6 +545,7 @@ def process(self, s, *args, **kwargs): class EventEmitter(object): def __init__(self): + super(EventEmitter, self).__init__() self._callbacks = CallbackRegistry() def mpl_connect(self, s, func): From 2fe92156b41e28453141405f312fc877a332ef82 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 25 Jun 2015 10:56:01 -0400 Subject: [PATCH 31/55] figure setter --- lib/matplotlib/backend_managers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index a2dabc77fc14..f2b792d96f5a 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -82,8 +82,7 @@ def __init__(self, figure, num): self.window = self._backend.Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self.destroy) - self._figure = None - self._set_figure(figure) + self.figure = figure w = int(self.figure.bbox.width) h = int(self.figure.bbox.height) @@ -118,7 +117,11 @@ def notify_axes_change(fig): def figure(self): return self._figure - def _set_figure(self, figure): + @figure.setter + def figure(self, figure): + if hasattr(self, '_figure'): + raise NotImplementedError + if not figure.canvas: self._backend.FigureCanvas(figure, manager=self) self._figure = figure From 3b434eff95e9685acf8816ed426ee7a10f77bdd1 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 25 Jun 2015 19:04:33 +0200 Subject: [PATCH 32/55] Improve MEP22 Tool Searching Structure --- lib/matplotlib/backend_managers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index f2b792d96f5a..038d422f6de2 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -525,14 +525,20 @@ def _handle_toggle(self, tool, sender, canvasevent, data): def _get_cls_to_instantiate(self, callback_class): # Find the class that corresponds to the tool if isinstance(callback_class, six.string_types): + try: + backend = self.canvas.manager.backend + except: + backend = get_backend() + # FIXME: make more complete searching structure - if callback_class in globals(): + if hasattr(backend, callback_class): + callback_class = getattr(backend, callback_class) + elif callback_class in globals(): callback_class = globals()[callback_class] else: mod = 'backend_tools' current_module = __import__(mod, globals(), locals(), [mod], 1) - callback_class = getattr(current_module, callback_class, False) if callable(callback_class): return callback_class From 490629fb06af7bb32cdda21ff27f2c145ca54951 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 25 Jun 2015 18:56:34 -0400 Subject: [PATCH 33/55] adding example file --- examples/user_interfaces/gui_elements.py | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 examples/user_interfaces/gui_elements.py diff --git a/examples/user_interfaces/gui_elements.py b/examples/user_interfaces/gui_elements.py new file mode 100644 index 000000000000..06148c60ed57 --- /dev/null +++ b/examples/user_interfaces/gui_elements.py @@ -0,0 +1,50 @@ +'''This example demonstrates how to: +* Create new toolbars +* Create new windows +Using `matplotlib.backend_managers.ToolManager`, +`matplotlib.backend_bases.WindowBase` and +`matplotlib.backend_bases.ToolContainerBase` +''' + +from __future__ import print_function +import matplotlib +matplotlib.use('GTK3Cairo') +matplotlib.rcParams['toolbar'] = 'toolmanager' +import matplotlib.pyplot as plt + +fig = plt.figure() + +# Shortcuts to FigureManager and ToolManager +manager = fig.canvas.manager +tool_mgr = manager.toolmanager + +# Create a new toolbar +topbar = manager.backend.Toolbar(tool_mgr) +# The options are north, east, west and south +manager.window.add_element(topbar, False, 'north') + +# Remove some tools from the main toolbar and add them to the +# new sidebar +for tool in ('home', 'back', 'forward'): + manager.toolbar.remove_toolitem(tool) + topbar.add_tool(tool, None) + +plt.plot([1, 2, 3]) + +# Add a new window +win = manager.backend.Window('Extra tools') +# create a sidebar for the new window +sidebar = manager.backend.Toolbar(tool_mgr) +# set the direction of the sidebar +# the options are horizontal and vertical +sidebar.set_flow('vertical') +# add the sidebar to the new window +win.add_element(sidebar, False, 'west') + +# Add some tools to the new sidebar +for tool in ('home', 'back', 'forward', 'zoom', 'pan'): + sidebar.add_tool(tool, None) +# show the new window +win.show() + +plt.show() From 8eb987bc2e154d2dcd567aded65c393100e43230 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 26 Jun 2015 03:32:40 +0200 Subject: [PATCH 34/55] super dooper --- lib/matplotlib/backend_managers.py | 4 ++-- lib/matplotlib/backends/backend_gtk3.py | 4 ++-- lib/matplotlib/cbook.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 038d422f6de2..4d4b0ac60936 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -73,8 +73,8 @@ class FigureManager(cbook.EventEmitter): num : int The figure number. """ - def __init__(self, figure, num): - super(FigureManager, self).__init__() + def __init__(self, figure, num, **kwargs): + super(FigureManager, self).__init__(**kwargs) self.num = num self._backend = get_backend() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 2a7a6ff073d4..d6030c157096 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -827,8 +827,8 @@ def draw_rubberband(self, x0, y0, x1, y1): class ToolbarGTK3(ToolContainerBase, Gtk.Box): - def __init__(self, toolmanager, flow='horizontal'): - super(ToolbarGTK3, self).__init__(toolmanager=toolmanager) + def __init__(self, toolmanager, flow='horizontal', **kwargs): + super(ToolbarGTK3, self).__init__(toolmanager=toolmanager, **kwargs) self._toolarea = Gtk.Box() self.set_flow(flow) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ed4ef4f20621..cd40c4030f09 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -544,8 +544,8 @@ def process(self, s, *args, **kwargs): class EventEmitter(object): - def __init__(self): - super(EventEmitter, self).__init__() + def __init__(self, **kwargs): + super(EventEmitter, self).__init__(**kwargs) # call next class on MRO self._callbacks = CallbackRegistry() def mpl_connect(self, s, func): From 224a4f32582f07d393b5526bc567dfe3418d4e07 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 26 Jun 2015 18:33:09 +0200 Subject: [PATCH 35/55] Revert old example and fix new one. --- examples/user_interfaces/toolmanager.py | 56 ++++--------------------- lib/matplotlib/backend_managers.py | 1 + 2 files changed, 8 insertions(+), 49 deletions(-) diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index c7a24f0371fc..c91e76f8b3ac 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -12,10 +12,8 @@ matplotlib.use('GTK3Cairo') matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt -from matplotlib.backend_tools import (ToolBase, ToolToggleBase, - add_tools_to_container) +from matplotlib.backend_tools import ToolBase, ToolToggleBase from gi.repository import Gtk, Gdk -from random import uniform class ListTools(ToolBase): @@ -62,6 +60,7 @@ def disable(self, *args): self.set_lines_visibility(True) def set_lines_visibility(self, state): + gr_lines = [] for ax in self.figure.get_axes(): for line in ax.get_lines(): if line.get_gid() == self.gid: @@ -69,66 +68,25 @@ def set_lines_visibility(self, state): self.figure.canvas.draw() -class LineTool(ToolBase): - description = 'Draw a random line' - - def __init__(self, *args, **kwargs): - self.color = kwargs.pop('color') - ToolBase.__init__(self, *args, **kwargs) - - def trigger(self, *args, **kwargs): - x0, y0, x1, y1 = (uniform(0, 2), uniform(1, 4), uniform(0, 2), - uniform(1, 4)) - fig = self.figure - fig.gca().plot([x0, x1], [y0, y1], color=self.color, gid=self.color) - fig.canvas.draw_idle() - - -class DotTool(ToolBase): - description = 'Draw a random dot' - - def __init__(self, *args, **kwargs): - self.color = kwargs.pop('color') - ToolBase.__init__(self, *args, **kwargs) - - def trigger(self, *args, **kwargs): - x0, y0 = uniform(0, 2), uniform(1, 4) - fig = self.figure - fig.gca().plot([x0], [y0], 'o', color=self.color, gid=self.color) - fig.canvas.draw_idle() - - fig = plt.figure() plt.plot([1, 2, 3], gid='mygroup') plt.plot([2, 3, 4], gid='unknown') plt.plot([3, 2, 1], gid='mygroup') # Add the custom tools that we created -manager = fig.canvas.manager -tool_mgr = manager.toolmanager -tool_mgr.add_tool('List', ListTools) -tool_mgr.add_tool('Hide', GroupHideTool, gid='mygroup') +fig.canvas.manager.toolmanager.add_tool('List', ListTools) +fig.canvas.manager.toolmanager.add_tool('Hide', GroupHideTool, gid='mygroup') # Add an existing tool to new group `foo`. # It can be added as many times as we want -manager.toolbar.add_tool('zoom', 'foo') +fig.canvas.manager.toolbar.add_tool('zoom', 'foo') # Remove the forward button -tool_mgr.remove_tool('forward') +fig.canvas.manager.toolmanager.remove_tool('forward') # To add a custom tool to the toolbar at specific location inside # the navigation group -manager.toolbar.add_tool('Hide', 'navigation', 1) - -for i, c in enumerate(['yellowgreen', 'forestgreen']): - sidebar = manager.backend.Toolbar(tool_mgr) - sidebar.set_flow('vertical') - tools = [['shapes', [tool_mgr.add_tool('L%s' % i, LineTool, color=c), - tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], - ['hide', [tool_mgr.add_tool('H%s' % i, GroupHideTool, gid=c)]]] - - manager.window.add_element(sidebar, False, 'west') - add_tools_to_container(sidebar, tools) +fig.canvas.manager.toolbar.add_tool('Hide', 'navigation', 1) plt.show() diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 4d4b0ac60936..62aa6b046353 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -156,6 +156,7 @@ def destroy(self, *args): def show(self): """Shows the figure""" self.window.show() + self.canvas.grab_focus() def full_screen_toggle(self): """Toggles whether we show fullscreen, alternatively call From cdbd51b041c44f1fb0637d6f3fa5d7ad6179f9be Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 27 Jun 2015 02:46:23 +0200 Subject: [PATCH 36/55] Improve MEP22 tool-searching method. --- lib/matplotlib/backend_bases.py | 7 ++++++- lib/matplotlib/backend_managers.py | 7 ++----- lib/matplotlib/backends/backend_gtk3.py | 6 +++--- lib/matplotlib/backends/backend_gtk3agg.py | 4 ++-- lib/matplotlib/backends/backend_gtk3cairo.py | 4 ++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index eca8e45de86e..9d83c1cf57e0 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1701,7 +1701,8 @@ class FigureCanvasBase(object): register_backend('tiff', 'matplotlib.backends.backend_agg', 'Tagged Image File Format') - def __init__(self, figure, manager=None): + def __init__(self, figure, manager=None, backend=None, **kwargs): + self._backend = backend self._is_idle_drawing = True self._is_saving = False figure.set_canvas(self) @@ -1725,6 +1726,10 @@ def _idle_draw_cntx(self): yield self._is_idle_drawing = False + @property + def backend(self): + return self._backend + def is_saving(self): """ Returns `True` when the renderer is in the process of saving diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 62aa6b046353..bdb00e71f5c3 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -123,7 +123,7 @@ def figure(self, figure): raise NotImplementedError if not figure.canvas: - self._backend.FigureCanvas(figure, manager=self) + self._backend.FigureCanvas(figure, manager=self, backend=self.backend) self._figure = figure @property @@ -526,10 +526,7 @@ def _handle_toggle(self, tool, sender, canvasevent, data): def _get_cls_to_instantiate(self, callback_class): # Find the class that corresponds to the tool if isinstance(callback_class, six.string_types): - try: - backend = self.canvas.manager.backend - except: - backend = get_backend() + backend = self.canvas.backend # FIXME: make more complete searching structure if hasattr(backend, callback_class): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index d6030c157096..da8b811b8e93 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -127,7 +127,7 @@ def _on_timer(self): self._timer = None return False -class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): +class FigureCanvasGTK3(FigureCanvasBase, Gtk.DrawingArea): keyvald = {65507 : 'control', 65505 : 'shift', 65513 : 'alt', @@ -192,9 +192,9 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): Gdk.EventMask.POINTER_MOTION_HINT_MASK| Gdk.EventMask.SCROLL_MASK) - def __init__(self, figure, manager=None): + def __init__(self, *args, **kwargs): if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - FigureCanvasBase.__init__(self, figure, manager) + FigureCanvasBase.__init__(self, *args, **kwargs) GObject.GObject.__init__(self) self._idle_draw_id = 0 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 4627436c33d0..d6e8b8546ccd 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -21,8 +21,8 @@ class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3, backend_agg.FigureCanvasAgg): - def __init__(self, figure, manager=None): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure, manager) + def __init__(self, *args, **kwargs): + backend_gtk3.FigureCanvasGTK3.__init__(self, *args, **kwargs) self._bbox_queue = [] def _renderer_init(self): diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 65934824e554..8603f11c0b0d 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -22,8 +22,8 @@ def set_context(self, ctx): class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3, backend_cairo.FigureCanvasCairo): - def __init__(self, figure, manager=None): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure, manager) + def __init__(self, *args, **kwargs): + backend_gtk3.FigureCanvasGTK3.__init__(self, *args, **kwargs) def _renderer_init(self): """use cairo renderer""" From 50e3719892d0dfb32238f7c57f85c7dab2938371 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 27 Jun 2015 02:48:03 +0200 Subject: [PATCH 37/55] MEP22 Save Figure Tool --- lib/matplotlib/backend_tools.py | 39 ++++++++++++++++++++ lib/matplotlib/backends/backend_gtk3.py | 33 ----------------- lib/matplotlib/backends/backend_gtk3agg.py | 2 + lib/matplotlib/backends/backend_gtk3cairo.py | 2 + 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 3c9c827cf513..bffcc4fb2aac 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -19,6 +19,8 @@ import time import warnings +import os + class Cursors(object): """Simple namespace for cursor reference""" @@ -679,6 +681,43 @@ class SaveFigureBase(ToolBase): default_keymap = rcParams['keymap.save'] +class ToolSaveFigure(ToolBase): + """Saves the figure""" + + description = 'Save the figure' + image = 'filesave.png' + default_keymap = rcParams['keymap.save'] + + def get_filechooser(self): + fc = self.figure.canvas.backend.FileChooserDialog( + title='Save the figure', + parent=self.figure.canvas.manager.window, + path=os.path.expanduser(rcParams.get('savefig.directory', '')), + filetypes=self.figure.canvas.get_supported_filetypes(), + default_filetype=self.figure.canvas.get_default_filetype()) + fc.set_current_name(self.figure.canvas.get_default_filename()) + return fc + + def trigger(self, *args, **kwargs): + chooser = self.get_filechooser() + fname, format_ = chooser.get_filename_from_user() + chooser.destroy() + if fname: + startpath = os.path.expanduser( + rcParams.get('savefig.directory', '')) + if startpath == '': + # explicitly missing key or empty str signals to use cwd + rcParams['savefig.directory'] = startpath + else: + # save dir for next time + rcParams['savefig.directory'] = os.path.dirname( + six.text_type(fname)) + try: + self.figure.canvas.print_figure(fname, format=format_) + except Exception as e: + error_msg_gtk(str(e), parent=self) + + class ZoomPanBase(ToolToggleBase): """Base class for `ToolZoom` and `ToolPan`""" def __init__(self, *args): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index da8b811b8e93..952b9620023c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -923,38 +923,6 @@ def set_message(self, s): self.push(self._context, s) -class SaveFigureGTK3(backend_tools.SaveFigureBase): - - def get_filechooser(self): - fc = FileChooserDialog( - title='Save the figure', - parent=self.figure.canvas.manager.window, - path=os.path.expanduser(rcParams.get('savefig.directory', '')), - filetypes=self.figure.canvas.get_supported_filetypes(), - default_filetype=self.figure.canvas.get_default_filetype()) - fc.set_current_name(self.figure.canvas.get_default_filename()) - return fc - - def trigger(self, *args, **kwargs): - chooser = self.get_filechooser() - fname, format_ = chooser.get_filename_from_user() - chooser.destroy() - if fname: - startpath = os.path.expanduser( - rcParams.get('savefig.directory', '')) - if startpath == '': - # explicitly missing key or empty str signals to use cwd - rcParams['savefig.directory'] = startpath - else: - # save dir for next time - rcParams['savefig.directory'] = os.path.dirname( - six.text_type(fname)) - try: - self.figure.canvas.print_figure(fname, format=format_) - except Exception as e: - error_msg_gtk(str(e), parent=self) - - class SetCursorGTK3(backend_tools.SetCursorBase): def set_cursor(self, cursor): self.figure.canvas.get_property("window").set_cursor(cursord[cursor]) @@ -1039,7 +1007,6 @@ def error_msg_gtk(msg, parent=None): dialog.destroy() -backend_tools.ToolSaveFigure = SaveFigureGTK3 backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3 backend_tools.ToolSetCursor = SetCursorGTK3 backend_tools.ToolRubberband = RubberbandGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index d6e8b8546ccd..61987dffeb5b 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -124,6 +124,8 @@ def new_figure_manager_given_figure(num, figure): Window = backend_gtk3.WindowGTK3 Toolbar = backend_gtk3.ToolbarGTK3 Statusbar = backend_gtk3.StatusbarGTK3 +FileChooserDialog = backend_gtk3.FileChooserDialog + Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoopGTK3 show = backend_gtk3.show diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 8603f11c0b0d..b0e5c66301f8 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -75,6 +75,8 @@ def new_figure_manager_given_figure(num, figure): Window = backend_gtk3.WindowGTK3 Toolbar = backend_gtk3.ToolbarGTK3 Statusbar = backend_gtk3.StatusbarGTK3 +FileChooserDialog = backend_gtk3.FileChooserDialog + Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoopGTK3 show = backend_gtk3.show From 85be519ef41add8dad8dd7a633be5fc8be60792f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 27 Jun 2015 03:34:23 +0200 Subject: [PATCH 38/55] pep8 --- lib/matplotlib/backend_managers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index bdb00e71f5c3..baca4c618ab7 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -123,7 +123,8 @@ def figure(self, figure): raise NotImplementedError if not figure.canvas: - self._backend.FigureCanvas(figure, manager=self, backend=self.backend) + self._backend.FigureCanvas(figure, manager=self, + backend=self.backend) self._figure = figure @property From 7edaf5a91eee31418c177edd3e50c204cfd5ce42 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 03:44:32 +0200 Subject: [PATCH 39/55] Make ToolConfigureSubplots a generic tool --- lib/matplotlib/backend_tools.py | 39 ++++++++++++++++++ lib/matplotlib/backends/backend_gtk3.py | 55 +------------------------ 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index bffcc4fb2aac..a0d307e3f829 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -14,6 +14,8 @@ from matplotlib import rcParams import matplotlib.cbook as cbook +from matplotlib.widgets import SubplotTool + from weakref import WeakKeyDictionary import six import time @@ -673,6 +675,43 @@ class ConfigureSubplotsBase(ToolBase): image = 'subplots.png' +class ToolConfigureSubplots(ToolBase): + """Tool for the configuration of subplots""" + + description = 'Configure subplots' + image = 'subplots.png' + + def __init__(self, *args, **kwargs): + ToolBase.__init__(self, *args, **kwargs) + self.dialog = None + + def trigger(self, sender, event, data=None): + self.init_dialog() + self.dialog.show() + + def init_dialog(self): + if self.dialog: + return + + from matplotlib.figure import Figure + self.dialog = self.figure.canvas.backend.Window(self.description) + self.dialog.mpl_connect('window_destroy_event', self._window_destroy) + + tool_fig = Figure(figsize=(6, 3)) + self.tool_canvas = self.figure.canvas.__class__(tool_fig) + tool_fig.subplots_adjust(top=0.9) + SubplotTool(self.figure, tool_fig) + + w, h = int(tool_fig.bbox.width), int(tool_fig.bbox.height) + + self.dialog.add_element(self.tool_canvas, True, 'center') + self.dialog.set_default_size(w, h) + + def _window_destroy(self, *args, **kwargs): + self.tool_canvas.destroy() + self.dialog = None + + class SaveFigureBase(ToolBase): """Base tool for figure saving""" diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 952b9620023c..48880012fd43 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -451,6 +451,7 @@ def set_default_size(self, width, height): def show(self): # show the figure window Gtk.Window.show(self) + self.present() def destroy(self): Gtk.Window.destroy(self) @@ -928,59 +929,6 @@ def set_cursor(self, cursor): self.figure.canvas.get_property("window").set_cursor(cursord[cursor]) -class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window): - def __init__(self, *args, **kwargs): - backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs) - self.window = None - - def init_window(self): - if self.window: - return - self.window = Gtk.Window(title="Subplot Configuration Tool") - - try: - self.window.window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: - # we presumably already logged a message on the - # failure of the main plot, don't keep reporting - pass - - self.vbox = Gtk.Box() - self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL) - self.window.add(self.vbox) - self.vbox.show() - self.window.connect('destroy', self.destroy) - - toolfig = Figure(figsize=(6, 3)) - canvas = self.figure.canvas.__class__(toolfig) - - toolfig.subplots_adjust(top=0.9) - SubplotTool(self.figure, toolfig) - - w = int(toolfig.bbox.width) - h = int(toolfig.bbox.height) - - self.window.set_default_size(w, h) - - canvas.show() - self.vbox.pack_start(canvas, True, True, 0) - self.window.show() - - def destroy(self, *args): - self.window.destroy() - self.window = None - - def _get_canvas(self, fig): - return self.canvas.__class__(fig) - - def trigger(self, sender, event, data=None): - self.init_window() - self.window.present() - - # Define the file to use as the GTk icon if sys.platform == 'win32': icon_filename = 'matplotlib.png' @@ -1007,7 +955,6 @@ def error_msg_gtk(msg, parent=None): dialog.destroy() -backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3 backend_tools.ToolSetCursor = SetCursorGTK3 backend_tools.ToolRubberband = RubberbandGTK3 From 8e6e25245eb0024d0ac4f94213c583d53dcea43f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 17:51:47 +0200 Subject: [PATCH 40/55] Improve flow handling and make it a lot more generic --- examples/user_interfaces/gui_elements.py | 9 ++++--- lib/matplotlib/backend_bases.py | 32 ++++++++++++++++++++++++ lib/matplotlib/backends/backend_gtk3.py | 27 ++++++++++---------- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/examples/user_interfaces/gui_elements.py b/examples/user_interfaces/gui_elements.py index 06148c60ed57..3d8d297ab3b2 100644 --- a/examples/user_interfaces/gui_elements.py +++ b/examples/user_interfaces/gui_elements.py @@ -20,7 +20,8 @@ # Create a new toolbar topbar = manager.backend.Toolbar(tool_mgr) -# The options are north, east, west and south + +# Add it to the figure window, we can place it north, east, west and south manager.window.add_element(topbar, False, 'north') # Remove some tools from the main toolbar and add them to the @@ -33,17 +34,17 @@ # Add a new window win = manager.backend.Window('Extra tools') + # create a sidebar for the new window sidebar = manager.backend.Toolbar(tool_mgr) -# set the direction of the sidebar -# the options are horizontal and vertical -sidebar.set_flow('vertical') + # add the sidebar to the new window win.add_element(sidebar, False, 'west') # Add some tools to the new sidebar for tool in ('home', 'back', 'forward', 'zoom', 'pan'): sidebar.add_tool(tool, None) + # show the new window win.show() diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 9d83c1cf57e0..d2a2bfe595d5 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3479,6 +3479,38 @@ def remove_toolitem(self, name): raise NotImplementedError +class FlowBase(object): + flow_types = ['horizontal', 'vertical'] + + def __init__(self, flow='horizontal', flow_locked=False, **kwargs): + super(FlowBase, self).__init__(**kwargs) + self.flow_locked = flow_locked + self.flow = flow + + @property + def flow(self): + return FlowBase.flow_types[self._flow] + + @flow.setter + def flow(self, flow): + if self.flow_locked: + return + + try: + self._flow = FlowBase.flow_types.index(flow) + except ValueError: + raise ValueError('Flow (%s), not in list %s' % (flow, FlowBase.flow_types)) + + self._update_flow() + + def _update_flow(self): + raise NotImplementedError + + +class ToolbarBase(ToolContainerBase, FlowBase): + pass + + class StatusbarBase(object): """Base class for the statusbar""" def __init__(self, toolmanager, **kwargs): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 48880012fd43..a51280e74e37 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -31,8 +31,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name from matplotlib.backend_bases import (RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase, WindowBase, MainLoopBase) -from matplotlib.backend_bases import (ShowBase, ToolContainerBase, - StatusbarBase) +from matplotlib.backend_bases import ShowBase, ToolbarBase, StatusbarBase from matplotlib.backend_managers import ToolManager from matplotlib import backend_tools @@ -387,7 +386,6 @@ def stop_event_loop(self): _flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL] -flow_types = ['horizontal', 'vertical'] class WindowGTK3(WindowBase, Gtk.Window): @@ -431,9 +429,17 @@ def _setup_box(self, name, orientation, grow, parent): def add_element(self, element, expand, place): element.show() - flow = _flow[not _flow.index(self._layout[place].get_orientation())] + # Get the flow of the element (the opposite of the container) + flow_index = not _flow.index(self._layout[place].get_orientation()) + flow = _flow[flow_index] separator = Gtk.Separator(orientation=flow) separator.show() + + try: + element.flow = element.flow_types[flow_index] + except AttributeError: + pass + if place in ['north', 'west', 'center']: self._layout[place].pack_start(element, expand, expand, 0) self._layout[place].pack_start(separator, False, False, 0) @@ -827,11 +833,10 @@ def draw_rubberband(self, x0, y0, x1, y1): self.ctx.stroke() -class ToolbarGTK3(ToolContainerBase, Gtk.Box): - def __init__(self, toolmanager, flow='horizontal', **kwargs): - super(ToolbarGTK3, self).__init__(toolmanager=toolmanager, **kwargs) +class ToolbarGTK3(ToolbarBase, Gtk.Box): + def __init__(self, toolmanager, **kwargs): self._toolarea = Gtk.Box() - self.set_flow(flow) + super(ToolbarGTK3, self).__init__(toolmanager=toolmanager, **kwargs) self.pack_start(self._toolarea, False, False, 0) self._toolarea.show_all() @@ -894,11 +899,7 @@ def remove_toolitem(self, name): self._groups[group].remove(toolitem) del self._toolitems[name] - def set_flow(self, flow): - try: - self._flow = flow_types.index(flow) - except ValueError: - raise ValueError('Flow (%s), not in list %s' % (flow, flow_types)) + def _update_flow(self): self.set_property("orientation", _flow[not self._flow]) self._toolarea.set_property('orientation', _flow[self._flow]) for item in self._toolarea: From 72575cbed122582d1dc3ff30af4984f2d2b34ea5 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 17:54:46 +0200 Subject: [PATCH 41/55] Missing resize method --- lib/matplotlib/backends/backend_gtk3.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a51280e74e37..c9f38090818c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -474,6 +474,9 @@ def get_window_title(self): def set_window_title(self, title): self.set_title(title) + def resize(self, width, height): + Gtk.Window.resize(self, width, height) + class FigureManagerGTK3(FigureManagerBase): """ From 4a78246ab28da6deb9b7defd6581c8481fc94fdf Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 18:32:54 +0200 Subject: [PATCH 42/55] Convert to new structure for finding tools --- lib/matplotlib/backends/backend_gtk3.py | 3 --- lib/matplotlib/backends/backend_gtk3agg.py | 2 ++ lib/matplotlib/backends/backend_gtk3cairo.py | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index c9f38090818c..ad63cee878a4 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -959,9 +959,6 @@ def error_msg_gtk(msg, parent=None): dialog.destroy() -backend_tools.ToolSetCursor = SetCursorGTK3 -backend_tools.ToolRubberband = RubberbandGTK3 - Toolbar = ToolbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 61987dffeb5b..b770d320a136 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -125,6 +125,8 @@ def new_figure_manager_given_figure(num, figure): Toolbar = backend_gtk3.ToolbarGTK3 Statusbar = backend_gtk3.StatusbarGTK3 FileChooserDialog = backend_gtk3.FileChooserDialog +ToolSetCursor = backend_gtk3.SetCursorGTK3 +ToolRubberband = backend_gtk3.RubberbandGTK3 Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoopGTK3 diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index b0e5c66301f8..3ee797434596 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -76,6 +76,8 @@ def new_figure_manager_given_figure(num, figure): Toolbar = backend_gtk3.ToolbarGTK3 Statusbar = backend_gtk3.StatusbarGTK3 FileChooserDialog = backend_gtk3.FileChooserDialog +ToolSetCursor = backend_gtk3.SetCursorGTK3 +ToolRubberband = backend_gtk3.RubberbandGTK3 Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 MainLoop = backend_gtk3.MainLoopGTK3 From e300707a70b7fa90ee80b760ac0de74ae58bac69 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 29 Jun 2015 02:42:36 +0200 Subject: [PATCH 43/55] doc --- lib/matplotlib/backend_bases.py | 17 +++++++++++++++++ lib/matplotlib/backend_managers.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d2a2bfe595d5..b4a95aff3258 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3480,6 +3480,15 @@ def remove_toolitem(self, name): class FlowBase(object): + """ + Base mixin class for all GUI elements that can flow, aka laid out in + different directions. + + The MPL window class deals with the manipulation of this mixin, so users + don't actually need to interact with this class. + + Classes the implement this class must override the _update_flow method. + """ flow_types = ['horizontal', 'vertical'] def __init__(self, flow='horizontal', flow_locked=False, **kwargs): @@ -3489,6 +3498,9 @@ def __init__(self, flow='horizontal', flow_locked=False, **kwargs): @property def flow(self): + """ + The direction of flow, one of the strings in `flow_type`. + """ return FlowBase.flow_types[self._flow] @flow.setter @@ -3504,6 +3516,11 @@ def flow(self, flow): self._update_flow() def _update_flow(self): + """ + Classes that extend FlowBase must override this method. + You can use the internal property self._flow whereby + flow_types[self._flow] gives the current flow. + """ raise NotImplementedError diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index baca4c618ab7..2336f5680de6 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -64,7 +64,7 @@ class FigureManager(cbook.EventEmitter): figure : `matplotlib.figure.Figure` The figure that holds the canvas - toolbar : `matplotlib.backend_bases.NavigationToolbar2` + toolbar : `matplotlib.backend_bases.ToolbarBase` The toolbar used for interacting with the figure. window : `matplotlib.backend_bases.WindowBase` From ee76451e457f3d35b9d4d060e460837d4abdbb0e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 29 Jun 2015 03:28:35 +0200 Subject: [PATCH 44/55] Add ExpandableBase --- examples/user_interfaces/gui_elements.py | 4 +- lib/matplotlib/backend_bases.py | 106 ++++++++++++----------- lib/matplotlib/backend_managers.py | 6 +- lib/matplotlib/backends/backend_gtk3.py | 7 +- 4 files changed, 66 insertions(+), 57 deletions(-) diff --git a/examples/user_interfaces/gui_elements.py b/examples/user_interfaces/gui_elements.py index 3d8d297ab3b2..6604028bc8e6 100644 --- a/examples/user_interfaces/gui_elements.py +++ b/examples/user_interfaces/gui_elements.py @@ -22,7 +22,7 @@ topbar = manager.backend.Toolbar(tool_mgr) # Add it to the figure window, we can place it north, east, west and south -manager.window.add_element(topbar, False, 'north') +manager.window.add_element(topbar, 'north') # Remove some tools from the main toolbar and add them to the # new sidebar @@ -39,7 +39,7 @@ sidebar = manager.backend.Toolbar(tool_mgr) # add the sidebar to the new window -win.add_element(sidebar, False, 'west') +win.add_element(sidebar, 'west') # Add some tools to the new sidebar for tool in ('home', 'back', 'forward', 'zoom', 'pan'): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index b4a95aff3258..aa0c5411fd54 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1656,7 +1656,61 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): self.key = key -class FigureCanvasBase(object): +class ExpandableBase(object): + """ + Base class for GUI elements that can expand to fill the area given to them + by the encapsulating container (e.g. the main window). + + At the moment this class does not do anything apart from mark such classes, + but this may well change at a later date, PRs welcome. + """ + pass + +class FlowBase(object): + """ + Base mixin class for all GUI elements that can flow, aka laid out in + different directions. + + The MPL window class deals with the manipulation of this mixin, so users + don't actually need to interact with this class. + + Classes the implement this class must override the _update_flow method. + """ + flow_types = ['horizontal', 'vertical'] + + def __init__(self, flow='horizontal', flow_locked=False, **kwargs): + super(FlowBase, self).__init__(**kwargs) + self.flow_locked = flow_locked + self.flow = flow + + @property + def flow(self): + """ + The direction of flow, one of the strings in `flow_type`. + """ + return FlowBase.flow_types[self._flow] + + @flow.setter + def flow(self, flow): + if self.flow_locked: + return + + try: + self._flow = FlowBase.flow_types.index(flow) + except ValueError: + raise ValueError('Flow (%s), not in list %s' % (flow, FlowBase.flow_types)) + + self._update_flow() + + def _update_flow(self): + """ + Classes that extend FlowBase must override this method. + You can use the internal property self._flow whereby + flow_types[self._flow] gives the current flow. + """ + raise NotImplementedError + +class FigureCanvasBase(ExpandableBase): """ The canvas the figure renders into. @@ -2717,7 +2771,7 @@ def set_window_title(self, title): """ pass - def add_element(self, element, expand, place): + def add_element(self, element, place): """ Adds a gui widget to the window. This has no effect for non-GUI backends and properties only apply to those backends that support them, or have a suitable workaround. @@ -2726,9 +2780,6 @@ def add_element(self, element, expand, place): ---------- element : A gui element. The element to add to the window - expand : bool - Whether the element should auto expand to fill as much space within - the window as possible. place : string The location to place the element, either compass points north, east, south, west, or center. @@ -3479,51 +3530,6 @@ def remove_toolitem(self, name): raise NotImplementedError -class FlowBase(object): - """ - Base mixin class for all GUI elements that can flow, aka laid out in - different directions. - - The MPL window class deals with the manipulation of this mixin, so users - don't actually need to interact with this class. - - Classes the implement this class must override the _update_flow method. - """ - flow_types = ['horizontal', 'vertical'] - - def __init__(self, flow='horizontal', flow_locked=False, **kwargs): - super(FlowBase, self).__init__(**kwargs) - self.flow_locked = flow_locked - self.flow = flow - - @property - def flow(self): - """ - The direction of flow, one of the strings in `flow_type`. - """ - return FlowBase.flow_types[self._flow] - - @flow.setter - def flow(self, flow): - if self.flow_locked: - return - - try: - self._flow = FlowBase.flow_types.index(flow) - except ValueError: - raise ValueError('Flow (%s), not in list %s' % (flow, FlowBase.flow_types)) - - self._update_flow() - - def _update_flow(self): - """ - Classes that extend FlowBase must override this method. - You can use the internal property self._flow whereby - flow_types[self._flow] gives the current flow. - """ - raise NotImplementedError - - class ToolbarBase(ToolContainerBase, FlowBase): pass diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 2336f5680de6..e1c119dbaf02 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -87,7 +87,7 @@ def __init__(self, figure, num, **kwargs): w = int(self.figure.bbox.width) h = int(self.figure.bbox.height) - self.window.add_element(self.figure.canvas, True, 'center') + self.window.add_element(self.figure.canvas, 'center') self.toolmanager = ToolManager(self.figure) self.toolbar = self._get_toolbar() @@ -96,10 +96,10 @@ def __init__(self, figure, num, **kwargs): if self.toolbar: tools.add_tools_to_container(self.toolbar) self.statusbar = self._backend.Statusbar(self.toolmanager) - h += self.window.add_element(self.statusbar, False, 'south') + h += self.window.add_element(self.statusbar, 'south') if self.toolbar is not None: - h += self.window.add_element(self.toolbar, False, 'south') + h += self.window.add_element(self.toolbar, 'south') self.window.set_default_size(w, h) self._full_screen_flag = False diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index ad63cee878a4..8bbd2aba7027 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -30,7 +30,7 @@ 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, WindowBase, MainLoopBase) + TimerBase, WindowBase, MainLoopBase, ExpandableBase) from matplotlib.backend_bases import ShowBase, ToolbarBase, StatusbarBase from matplotlib.backend_managers import ToolManager from matplotlib import backend_tools @@ -426,7 +426,7 @@ def _setup_box(self, name, orientation, grow, parent): self._layout[parent].pack_start(self._layout[name], grow, grow, 0) self._layout[name].show() - def add_element(self, element, expand, place): + def add_element(self, element, place): element.show() # Get the flow of the element (the opposite of the container) @@ -440,6 +440,9 @@ def add_element(self, element, expand, place): except AttributeError: pass + # Determine if this element should fill all the space given to it + expand = isinstance(element, ExpandableBase) + if place in ['north', 'west', 'center']: self._layout[place].pack_start(element, expand, expand, 0) self._layout[place].pack_start(separator, False, False, 0) From 6f0c7ab2608ca18a6a4815fa520cdc96470e4d10 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 29 Jun 2015 23:58:13 +0200 Subject: [PATCH 45/55] Template Backend plus fix FigManager for non-GUI backends and add generic focus --- lib/matplotlib/backend_bases.py | 3 + lib/matplotlib/backend_managers.py | 31 +++++---- lib/matplotlib/backend_tools.py | 2 +- lib/matplotlib/backends/__init__.py | 2 +- lib/matplotlib/backends/backend_gtk3.py | 3 + lib/matplotlib/backends/backend_template.py | 72 +++++++-------------- 6 files changed, 51 insertions(+), 62 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index aa0c5411fd54..d3cd4f8323bc 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1784,6 +1784,9 @@ def _idle_draw_cntx(self): def backend(self): return self._backend + def focus(self): + pass + def is_saving(self): """ Returns `True` when the renderer is in the process of saving diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index e1c119dbaf02..3c6b4f232406 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -75,15 +75,19 @@ class FigureManager(cbook.EventEmitter): """ def __init__(self, figure, num, **kwargs): super(FigureManager, self).__init__(**kwargs) + self._backend = get_backend() + self.num = num + self.figure = figure + + self._is_gui = hasattr(self._backend, 'Window') + if not self._is_gui: + return - self._backend = get_backend() self._mainloop = self._backend.MainLoop() self.window = self._backend.Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self.destroy) - self.figure = figure - w = int(self.figure.bbox.width) h = int(self.figure.bbox.height) @@ -137,7 +141,7 @@ def destroy(self, *args): # Make sure we run this routine only once for the FigureManager # This ensures the nasty __del__ fix below works. - if getattr(self, '_destroying', False): + if getattr(self, '_destroying', False) or self._is_gui is False: return self._destroying = True @@ -157,7 +161,7 @@ def destroy(self, *args): def show(self): """Shows the figure""" self.window.show() - self.canvas.grab_focus() + self.canvas.focus() def full_screen_toggle(self): """Toggles whether we show fullscreen, alternatively call @@ -188,13 +192,16 @@ def backend(self): return self._backend def _get_toolbar(self): - # must be inited after the window, drawingArea and figure - # attrs are set - if rcParams['toolbar'] == 'toolmanager': - toolbar = self._backend.Toolbar(self.toolmanager) - else: - toolbar = None - return toolbar + try: + # must be inited after the window, drawingArea and figure + # attrs are set + if rcParams['toolbar'] == 'toolmanager': + toolbar = self._backend.Toolbar(self.toolmanager) + else: + toolbar = None + return toolbar + except: + return None def show_popup(self, msg): """ diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index a0d307e3f829..e636cf93b79f 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -704,7 +704,7 @@ def init_dialog(self): w, h = int(tool_fig.bbox.width), int(tool_fig.bbox.height) - self.dialog.add_element(self.tool_canvas, True, 'center') + self.dialog.add_element(self.tool_canvas, 'center') self.dialog.set_default_size(w, h) def _window_destroy(self, *args, **kwargs): diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 854298e2f172..1eb73521ab37 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -71,7 +71,7 @@ def pylab_setup(name=None): backend_mod = get_backend(name) # Things we pull in from all backends - new_figure_manager = backend_mod.new_figure_manager + new_figure_manager = getattr(backend_mod, 'new_figure_manager', None) # image backends like pdf, agg or svg do not need to do anything # for "show" or "draw_if_interactive", so if they are not defined diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 8bbd2aba7027..ec0ed23fdfd7 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -219,6 +219,9 @@ def __init__(self, *args, **kwargs): self._renderer_init() default_context = GLib.main_context_get_thread_default() or GLib.main_context_default() + def focus(self): + self.grab_focus() + def destroy(self): #Gtk.DrawingArea.destroy(self) self.close_event() diff --git a/lib/matplotlib/backends/backend_template.py b/lib/matplotlib/backends/backend_template.py index f3e483d2f73b..e6c820128dfe 100644 --- a/lib/matplotlib/backends/backend_template.py +++ b/lib/matplotlib/backends/backend_template.py @@ -16,7 +16,7 @@ Copy this to backend_xxx.py and replace all instances of 'template' with 'xxx'. Then implement the class methods and functions below, and add 'xxx' to the switchyard in matplotlib/backends/__init__.py and -'xxx' to the backends list in the validate_backend methon in +'xxx' to the backends list in the validate_backend method in matplotlib/__init__.py and you're off. You can use your backend with:: import matplotlib @@ -25,14 +25,14 @@ plot([1,2,3]) show() -matplotlib also supports external backends, so you can place you can -use any module in your PYTHONPATH with the syntax:: +matplotlib also supports external backends, by placing +any module in your PYTHONPATH and then using the syntax:: import matplotlib matplotlib.use('module://my_backend') where my_backend.py is your module name. This syntax is also -recognized in the rc file and in the -d argument in pylab, e.g.,:: +recognized in the rc file and also with the -d argument in pylab, e.g.,:: python simple_plot.py -dmodule://my_backend @@ -48,6 +48,7 @@ matplotlib/backends/backend_your_backend.py matplotlib/backend_bases.py + matplotlib/backend_managers.py matplotlib/backends/__init__.py matplotlib/__init__.py matplotlib/_pylab_helpers.py @@ -68,9 +69,9 @@ import six import matplotlib -from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ - FigureManagerBase, FigureCanvasBase + WindowBase, FigureCanvasBase, MainLoopBase, ToolbarBase +from matplotlib import backend_tools from matplotlib.figure import Figure from matplotlib.transforms import Bbox @@ -178,41 +179,6 @@ def draw_if_interactive(): """ pass -def show(): - """ - For image backends - is not required - For GUI backends - show() is usually the last line of a pylab script and - tells the backend that it is time to draw. In interactive mode, this may - be a do nothing func. See the GTK backend for an example of how to handle - interactive versus batch mode - """ - for manager in Gcf.get_all_fig_managers(): - # do something to display the GUI - pass - - -def new_figure_manager(num, *args, **kwargs): - """ - Create a new figure manager instance - """ - # if a main-level app must be created, this (and - # new_figure_manager_given_figure) is the usual place to - # do it -- see backend_wx, backend_wxagg and backend_tkagg for - # examples. Not all GUIs require explicit instantiation of a - # main-level app (egg backend_gtk, backend_gtkagg) for pylab - FigureClass = kwargs.pop('FigureClass', Figure) - thisFig = FigureClass(*args, **kwargs) - return new_figure_manager_given_figure(num, thisFig) - - -def new_figure_manager_given_figure(num, figure): - """ - Create a new figure manager instance for the given figure. - """ - canvas = FigureCanvasTemplate(figure) - manager = FigureManagerTemplate(canvas, num) - return manager - class FigureCanvasTemplate(FigureCanvasBase): """ @@ -256,19 +222,29 @@ def print_foo(self, filename, *args, **kwargs): def get_default_filetype(self): return 'foo' -class FigureManagerTemplate(FigureManagerBase): - """ - Wrap everything up into a window for the pylab interface - For non interactive backends, the base class does all the work - """ +class WindowTemplate(WindowBase): + def show(self): + pass + + +class RubberbandTemplate(backend_tools.RubberbandBase): + pass + + +class SetCursorTemplate(backend_tools.SetCursorBase): pass ######################################################################## # -# Now just provide the standard names that backend.__init__ is expecting +# Now just provide the standard names that backend.__init__ expects # ######################################################################## FigureCanvas = FigureCanvasTemplate -FigureManager = FigureManagerTemplate + +# Needed for a GUI +MainLoop = MainLoopBase +Window = WindowTemplate +ToolRubberband = RubberbandTemplate +ToolSetCursor = SetCursorTemplate From ae9bf5b7461f1dc770edafc7a932592da7a342d9 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 19 Jul 2015 14:51:44 +0200 Subject: [PATCH 46/55] rcParam and Travis --- .travis.yml | 8 ++++++++ .travis/toolmgr_matplotlibrc | 1 + lib/matplotlib/figure.py | 2 +- lib/matplotlib/pyplot.py | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 .travis/toolmgr_matplotlibrc diff --git a/.travis.yml b/.travis.yml index fc48b3ee568c..f30928b40602 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,7 @@ env: - NOSE_ARGS="--processes=$NPROC --process-timeout=300" - PYTHON_ARGS= - DELETE_FONT_CACHE= + - USENEWTOOLMANAGER=false matrix: include: @@ -60,6 +61,10 @@ matrix: env: TEST_ARGS=--pep8 - python: 3.5 env: BUILD_DOCS=true + - python: 2.7 + env: USENEWTOOLMANAGER=true MOCK=mock + - python: 3.4 + env: USENEWTOOLMANAGER=true - python: "nightly" env: PRE=--pre - os: osx @@ -144,6 +149,9 @@ script: # Travis VM to run out of memory (since so many copies of inkscape and # ghostscript are running at the same time). - | + if [[ $USENEWTOOLMANAGER == true ]]; then + cp .travis/toolmgr_matplotlibrc matplotlibrc + fi echo Testing import of tkagg backend MPLBACKEND="tkagg" python -c 'import matplotlib.pyplot as plt; print(plt.get_backend())' echo The following args are passed to nose $NOSE_ARGS diff --git a/.travis/toolmgr_matplotlibrc b/.travis/toolmgr_matplotlibrc new file mode 100644 index 000000000000..327b02794023 --- /dev/null +++ b/.travis/toolmgr_matplotlibrc @@ -0,0 +1 @@ +toolbar : toolmanager diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index c2e34e6f474d..eaa997085266 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1562,7 +1562,7 @@ def __setstate__(self, state): import matplotlib.backend_managers as managers allnums = plt.get_fignums() num = max(allnums) + 1 if allnums else 1 - if hasattr(plt._backend_mod, 'Window'): # Can we use MEP 27 code? + if rcParams['toolbar'] == 'toolmanager': mgr = managers.FigureManager(self, num) else: mgr = plt._backend_mod.new_figure_manager_given_figure(num, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index be821a04e093..f66ae24f1979 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -249,7 +249,7 @@ def show(*args, **kw): described above. """ global _show - if hasattr(_backend_mod, 'Window'): # Can we use the new code? + if rcParams['toolbar'] == 'toolmanager': return _pylab_helpers.Gcf.show_all(*args, **kw) else: _show(*args, **kw) @@ -528,7 +528,7 @@ def figure(num=None, # autoincrement if None, else integer from 1-N if get_backend().lower() == 'ps': dpi = 72 - if hasattr(_backend_mod, 'Window'): # Can we use the MEP 27 code? + if rcParams['toolbar'] == 'toolmanager': fig = FigureClass(figsize=figsize, dpi=dpi, facecolor=facecolor, edgecolor=edgecolor, frameon=frameon, **kwargs) figManager = backend_managers.FigureManager(fig, num) From fb004e063a11d4e05da71a80f08758ba2a0128bc Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 20 Jul 2015 19:25:30 +0200 Subject: [PATCH 47/55] test --- lib/matplotlib/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 6f69f8f46165..e25f2fb04205 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -812,6 +812,7 @@ def matplotlib_fname(): cwd = os.getcwd() fname = os.path.join(cwd, 'matplotlibrc') if os.path.exists(fname): + print('found mplrc file in cwd') return fname if 'MATPLOTLIBRC' in os.environ: From 1d2095bc91231a71369b4aff57d85704dcf42939 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 20 Jul 2015 20:20:01 +0200 Subject: [PATCH 48/55] test always MEP27 --- .travis.yml | 2 ++ lib/matplotlib/__init__.py | 1 - lib/matplotlib/testing/__init__.py | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f30928b40602..ba31973dcdd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -148,6 +148,8 @@ script: # The number of processes is hardcoded, because using too many causes the # Travis VM to run out of memory (since so many copies of inkscape and # ghostscript are running at the same time). + + # Load in the new toolbar rc file for tests - | if [[ $USENEWTOOLMANAGER == true ]]; then cp .travis/toolmgr_matplotlibrc matplotlibrc diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index e25f2fb04205..6f69f8f46165 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -812,7 +812,6 @@ def matplotlib_fname(): cwd = os.getcwd() fname = os.path.join(cwd, 'matplotlibrc') if os.path.exists(fname): - print('found mplrc file in cwd') return fname if 'MATPLOTLIBRC' in os.environ: diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 76de517f18a1..ade123b46cba 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -101,6 +101,10 @@ def setup(): # These settings *must* be hardcoded for running the comparison # tests and are not necessarily the default values as specified in # rcsetup.py + use_new_toolmanager = rcParams['toolbar'] == 'toolmanager' rcdefaults() # Start with all defaults set_font_settings_for_testing() + + if use_new_toolmanager: + rcParams['toolbar'] = 'toolmanager' From 24e43b374bdf37b1303640d7c403a78af00ba476 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 14:11:58 +0200 Subject: [PATCH 49/55] Fix FigureManager to allow pyplot to work for non GUI backends --- lib/matplotlib/backend_managers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 3c6b4f232406..d8788c349a18 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -82,6 +82,7 @@ def __init__(self, figure, num, **kwargs): self._is_gui = hasattr(self._backend, 'Window') if not self._is_gui: + self.window = None return self._mainloop = self._backend.MainLoop() @@ -185,7 +186,8 @@ def set_window_title(self, title): Set the title text of the window containing the figure. Note that this has no effect for non-GUI backends (e.g., a PS backend). """ - self.window.set_window_title(title) + if self.window: + self.window.set_window_title(title) @property def backend(self): From 208c3be13338330b4efbefa5bb99713acfd93233 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:32:00 +0200 Subject: [PATCH 50/55] Fix Gcf.show_all() --- lib/matplotlib/_pylab_helpers.py | 15 +++++++++------ lib/matplotlib/backend_managers.py | 8 ++++++-- lib/matplotlib/backends/__init__.py | 4 ++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index cb31fe983054..fcc19b4c0b59 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -76,18 +76,21 @@ def show_all(cls, block=None): for manager in managers: manager.show() - if block is not None: - if block: - manager._mainloop() + if block is True: + manager._mainloop() + return + elif block is False: return + # Hack: determine at runtime whether we are + # inside ipython in pylab mode. from matplotlib import pyplot try: ipython_pylab = not pyplot.show._needmain # IPython versions >= 0.10 tack the _needmain # attribute onto pyplot.show, and always set # it to False, when in %pylab mode. - ipython_pylab = ipython_pylab and get_backend() != 'WebAgg' + ipython_pylab = ipython_pylab and manager.backend_name != 'webagg' # TODO: The above is a hack to get the WebAgg backend # working with ipython's `%pylab` mode until proper # integration is implemented. @@ -97,9 +100,9 @@ def show_all(cls, block=None): # Leave the following as a separate step in case we # want to control this behavior with an rcParam. if ipython_pylab: - block = False + return - if not is_interactive() or get_backend() == 'WebAgg': + if not is_interactive() or manager.backend_name == 'webagg': manager._mainloop() @classmethod diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index d8788c349a18..f7b4f9da3680 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -21,7 +21,7 @@ from matplotlib import is_interactive from matplotlib import rcParams from matplotlib.figure import Figure -from matplotlib.backends import get_backend +from matplotlib.backends import get_backend, backend as backend_name class FigureManagerEvent(object): @@ -75,7 +75,7 @@ class FigureManager(cbook.EventEmitter): """ def __init__(self, figure, num, **kwargs): super(FigureManager, self).__init__(**kwargs) - self._backend = get_backend() + self._backend_name, self._backend = get_backend() self.num = num self.figure = figure @@ -193,6 +193,10 @@ def set_window_title(self, title): def backend(self): return self._backend + @property + def backend_name(self): + return self._backend_name + def _get_toolbar(self): try: # must be inited after the window, drawingArea and figure diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 1eb73521ab37..94ed62b1db15 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -36,7 +36,7 @@ def get_backend(name=None): # the last argument is specifies whether to use absolute or relative # imports. 0 means only perform absolute imports. backend_name = get_backend_name(name) - return __import__(backend_name, globals(), locals(), + return backend_name, __import__(backend_name, globals(), locals(), [backend_name], 0) @@ -68,7 +68,7 @@ def pylab_setup(name=None): ''' # Import the requested backend into a generic module object - backend_mod = get_backend(name) + backend_mod = get_backend(name)[1] # Things we pull in from all backends new_figure_manager = getattr(backend_mod, 'new_figure_manager', None) From a44ebd9bd0e914f534827d7cd36857687835c975 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:39:40 +0200 Subject: [PATCH 51/55] doc --- lib/matplotlib/_pylab_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index fcc19b4c0b59..ee508d38b0ef 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -102,6 +102,7 @@ def show_all(cls, block=None): if ipython_pylab: return + # If not interactive we need to block if not is_interactive() or manager.backend_name == 'webagg': manager._mainloop() From f8f9cf200b3c471b94a128f2a87fc39f16a69a0d Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:44:28 +0200 Subject: [PATCH 52/55] pep8 --- lib/matplotlib/backend_bases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d3cd4f8323bc..74b8780cbc20 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -165,7 +165,7 @@ def __call__(self): def __del__(self): MainLoopBase._instance_count[self.__class__] -= 1 if (MainLoopBase._instance_count[self.__class__] <= 0 and - not is_interactive() and MainLoopBase._running): + not is_interactive() and MainLoopBase._running): self.end() From 0e09a544d8e38a7a8d02a6190c95d23b44b4ddc3 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:50:03 +0200 Subject: [PATCH 53/55] remove show_popup --- doc/devel/MEP/MEP27.rst | 2 +- lib/matplotlib/backend_managers.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/doc/devel/MEP/MEP27.rst b/doc/devel/MEP/MEP27.rst index 8e71c5ef0a9f..8db8215a6563 100644 --- a/doc/devel/MEP/MEP27.rst +++ b/doc/devel/MEP/MEP27.rst @@ -116,7 +116,7 @@ The description of this MEP gives us most of the solution: +--------------------------------------+------------------------------+---------------------+--------------------------------+ |key_press |key_press |X | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|show_popup |show_poup |X |Not used anywhere in mpl, and | +|show_popup |X |X |Not used anywhere in mpl, and | | | | |does nothing. | +--------------------------------------+------------------------------+---------------------+--------------------------------+ |get_window_title |-> |get_window_title | | diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index f7b4f9da3680..5c4b49d63363 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -209,12 +209,6 @@ def _get_toolbar(self): except: return None - def show_popup(self, msg): - """ - Display message in a popup -- GUI only - """ - pass - class ToolEvent(object): """Event for tool manipulation (add/remove)""" From a38b6d790e8b2c158aab232a03c3e529408e537b Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 23 Sep 2015 00:25:14 +0200 Subject: [PATCH 54/55] AttributeError --- lib/matplotlib/backend_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index e636cf93b79f..8c5306a6848b 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -358,7 +358,7 @@ class ToolQuit(ToolBase): def trigger(self, sender, event, data=None): try: manager = self.figure.canvas.manager - except: + except AttributeError: pass else: manager.destroy('window_destroy_event') From 64f0c61bd661b91b865b3f45094e7d34ab55e6c2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 4 Aug 2016 01:48:58 +0200 Subject: [PATCH 55/55] Fixes for MEP27 --- lib/matplotlib/_pylab_helpers.py | 2 ++ lib/matplotlib/backend_bases.py | 4 ++-- lib/matplotlib/backends/__init__.py | 3 ++- lib/matplotlib/pyplot.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index ee508d38b0ef..4d7cdc6cf6fc 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -77,6 +77,8 @@ def show_all(cls, block=None): manager.show() if block is True: + # Start the mainloop on the last manager, so we don't have a + # mainloop starting for each manager. Not ideal, but works for now. manager._mainloop() return elif block is False: diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 74b8780cbc20..74771208579f 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2350,7 +2350,7 @@ def get_window_title(self): Get the title text of the window containing the figure. Return None if there is no window (e.g., a PS backend). """ - if getattr(self, "manager", None): + if self.manager is not None: return self.manager.get_window_title() def set_window_title(self, title): @@ -2358,7 +2358,7 @@ def set_window_title(self, title): Set the title text of the window containing the figure. Note that this has no effect if there is no window (e.g., a PS backend). """ - if getattr(self, "manager", None): + if self.manager is not None: self.manager.set_window_title(title) def get_default_filename(self): diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py index 94ed62b1db15..404ea5f3c4f7 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -10,10 +10,11 @@ backend = matplotlib.get_backend() + def get_backend_name(name=None): '''converts the name of the backend into the module to load name : str, optional - + Parameters ---------- The name of the backend to use. If `None`, falls back to diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index f66ae24f1979..6a4bc09dd98d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -252,7 +252,7 @@ def show(*args, **kw): if rcParams['toolbar'] == 'toolmanager': return _pylab_helpers.Gcf.show_all(*args, **kw) else: - _show(*args, **kw) + return _show(*args, **kw) def isinteractive(): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy