From 9813e4356e5ec8857be2f7e54cac2a24ab06be64 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 22 Feb 2015 00:14:36 +0100 Subject: [PATCH 01/58] 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 72c0d7f2f6fb..ca7c6f5111c7 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -48,6 +48,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 @@ -2609,6 +2610,165 @@ def _get_uniform_gridstate(ticks): 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 c084f6d6dccc..fe6088d4f987 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -29,7 +29,7 @@ from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( FigureCanvasBase, FigureManagerBase, GraphicsContextBase, - NavigationToolbar2, RendererBase, TimerBase, cursors) + NavigationToolbar2, RendererBase, TimerBase, cursors, WindowBase) from matplotlib.backend_bases import ( ShowBase, ToolContainerBase, StatusbarBase) from matplotlib.backend_managers import ToolManager @@ -361,6 +361,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): """ @@ -953,3 +1032,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 91cbffc5dc1428440a7ac93f9d8c4edd67cc08cf Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 23 Feb 2015 00:22:14 +0100 Subject: [PATCH 02/58] 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 | 35 ++--- lib/matplotlib/backends/backend_gtk3cairo.py | 11 +- lib/matplotlib/cbook/__init__.py | 11 ++ lib/matplotlib/pyplot.py | 23 +-- 8 files changed, 327 insertions(+), 163 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 ca7c6f5111c7..151f417f3b40 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -48,7 +48,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 @@ -143,6 +142,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. @@ -2483,7 +2509,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') @@ -2610,20 +2639,16 @@ def _get_uniform_gridstate(ticks): 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): """ @@ -2664,112 +2689,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 4335833d98b0..c1ad5f494a75 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)""" @@ -434,3 +546,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 c2b29d474d8d..67639978dee4 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -15,6 +15,46 @@ # Filter out line noise from importlib line. if not line.startswith(' File "', ''): - 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) @@ -93,3 +115,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 fe6088d4f987..6bc032caf0f8 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -27,11 +27,11 @@ import matplotlib from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import ( - FigureCanvasBase, FigureManagerBase, GraphicsContextBase, - NavigationToolbar2, RendererBase, TimerBase, cursors, WindowBase) -from matplotlib.backend_bases import ( - ShowBase, ToolContainerBase, StatusbarBase) +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 from matplotlib.cbook import is_writable_file_like from matplotlib.figure import Figure @@ -63,6 +63,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: @@ -407,26 +418,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/__init__.py b/lib/matplotlib/cbook/__init__.py index 9a143cda4cbb..357763a71763 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -367,6 +367,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 2f684efd68f6..572c88563b14 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -34,6 +34,7 @@ from matplotlib.cbook import deprecated 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 @@ -249,7 +250,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(): @@ -535,13 +539,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) @@ -554,7 +559,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 aa3ff59b10835598ee6760860a843065a84ec04c Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 25 Feb 2015 01:42:44 +0100 Subject: [PATCH 03/58] 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 7034ea64c0d0..a7fe8939fb70 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1643,11 +1643,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 @@ -1662,7 +1669,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 f046e01eb42511b511b5d611b9f470b22f9fb128 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 26 Feb 2015 17:54:59 +0100 Subject: [PATCH 04/58] 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 39ff708c8c256d10cb23fdd2ee9ee13d0a313349 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 15:02:51 +0100 Subject: [PATCH 05/58] 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 151f417f3b40..dd725163f448 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -46,6 +46,7 @@ import sys import time import warnings +import weakref import numpy as np import matplotlib.cbook as cbook @@ -1745,7 +1746,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) @@ -1760,6 +1761,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 @@ -2464,6 +2466,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): """ @@ -2665,6 +2680,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 c1ad5f494a75..ed39655d72e9 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)""" @@ -553,17 +551,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 6bc032caf0f8..59f979c0cd32 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -192,8 +192,8 @@ class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase): Gdk.EventMask.POINTER_MOTION_HINT_MASK| Gdk.EventMask.SCROLL_MASK) - def __init__(self, figure): - FigureCanvasBase.__init__(self, figure) + def __init__(self, figure, manager=None): + 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 a7fe8939fb70..701c1aa4a49b 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1643,7 +1643,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 @@ -1654,7 +1654,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 45c5fc2eb4b8f03c55bdf446518777ed806432ac Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 16:09:23 +0100 Subject: [PATCH 06/58] 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 ed39655d72e9..50352c9c0be6 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 ebf0c57abf69bde6fd6e44af0c284603782b2ce4 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 17:37:53 +0100 Subject: [PATCH 07/58] 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 dd725163f448..c1f68f6c48d9 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2701,7 +2701,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 50352c9c0be6..d384011c13c9 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 59f979c0cd32..ba475b066c75 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -395,15 +395,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 9d25441e9c326f59542a4aad0336641a4101c5b1 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 19:29:36 +0100 Subject: [PATCH 08/58] 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 d384011c13c9..e9d73b65a1b2 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 6ae52f216f6ea03a7798b75f239df560a7f040b4 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 28 Feb 2015 12:04:29 +0100 Subject: [PATCH 09/58] 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 ba475b066c75..7d3090819c8c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -372,14 +372,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 @@ -392,11 +392,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() @@ -410,34 +410,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): @@ -1031,4 +1024,3 @@ def error_msg_gtk(msg, parent=None): Toolbar = ToolbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 -Window = WindowGTK3 From 1a47d3bda6451c3e7961ea3089ba4c91ba3b2854 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 3 Mar 2015 11:17:32 +0100 Subject: [PATCH 10/58] 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 c1f68f6c48d9..aa7c9fcb6b80 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 @@ -2524,10 +2530,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') @@ -2662,6 +2668,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) @@ -2675,22 +2689,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' @@ -2698,16 +2741,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 e9d73b65a1b2..0ea3b9a92330 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): @@ -544,23 +589,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 67639978dee4..c9e9d3e8df0c 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -39,21 +39,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): @@ -102,7 +100,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 701c1aa4a49b..8e7e0bd7cc9d 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1649,12 +1649,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 572c88563b14..bf8371a9efb9 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -250,10 +250,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(): @@ -539,14 +539,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 bc173729cb4784858f869c5130f3a03ca0338e98 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 6 Apr 2015 23:55:22 +0200 Subject: [PATCH 11/58] 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 aa7c9fcb6b80..f1d7f4fbea7f 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2749,7 +2749,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. @@ -2766,6 +2766,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 0ea3b9a92330..9d51a68419df 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 7d3090819c8c..d88c0649bb62 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -372,7 +372,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) @@ -390,22 +390,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 @@ -417,7 +430,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 3fb46f211bed11181823bad2403beb7ac219297d Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 7 Apr 2015 21:05:36 +0200 Subject: [PATCH 12/58] 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 9d51a68419df..d18c17d33c26 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 c9e9d3e8df0c..fafa6816375f 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -36,22 +36,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 8e7e0bd7cc9d..c41fbc2959f0 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1649,7 +1649,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 bf8371a9efb9..8032968f2dbe 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -250,7 +250,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) @@ -539,7 +539,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 2758f3f3e76e62c5cd35f6ed6d4a492b36efb7bc Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 12 Apr 2015 19:30:25 +0200 Subject: [PATCH 13/58] 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 d18c17d33c26..a562ce051b68 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 d88c0649bb62..6482d9837cd8 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -372,6 +372,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) @@ -413,14 +417,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) @@ -1034,5 +1043,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 f354d3cab6aabe1c96bf514068692ae59850c53e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 12 Apr 2015 23:54:05 +0200 Subject: [PATCH 14/58] Improved new toolbar and updated tool_manager example accoridingly. --- .../user_interfaces/toolmanager_sgskip.py | 52 +++++++++++++++++-- lib/matplotlib/backends/backend_gtk3.py | 25 ++++++--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 0c77fe55e699..b133468e9b3a 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -19,8 +19,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): @@ -66,7 +68,6 @@ def disable(self, *args): self.set_lines_visibility(False) 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: @@ -74,14 +75,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('Show', 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`. @@ -89,10 +120,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('Show', '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 6482d9837cd8..0bbcb220e0c4 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -374,6 +374,7 @@ def stop_event_loop(self): _flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL] +flow_types = ['horizontal', 'vertical'] class Window(WindowBase, Gtk.Window): @@ -834,13 +835,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 = {} @@ -873,7 +873,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() @@ -902,9 +902,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 51c0598ad1fb15fc5771ed70935ce06bc32219d5 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 13 Apr 2015 08:05:54 +0200 Subject: [PATCH 15/58] 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 a562ce051b68..4a3c3d1f39c2 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 6b35d850997ceb93af7dcef62d098d56b936aa02 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 14 Apr 2015 19:51:34 +0200 Subject: [PATCH 16/58] 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 724b6227d9291e2a44614102b7a8c2ff08483dd6 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 15 Apr 2015 01:00:20 +0200 Subject: [PATCH 17/58] 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 | 23 ++--------------------- 4 files changed, 12 insertions(+), 31 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index f1d7f4fbea7f..3b86d5b3e3cb 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2801,12 +2801,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 4a3c3d1f39c2..4bdcc5f1c94a 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 66e43394e34f..5a012d0e80bf 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 @@ -384,7 +383,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 0bbcb220e0c4..82b8cdc8149e 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -421,6 +421,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) @@ -501,9 +502,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() @@ -511,14 +510,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) @@ -535,9 +526,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) @@ -579,14 +568,6 @@ def _get_toolbar(self): toolbar = None return toolbar - def _get_toolmanager(self): - # must be initialised after toolbar has been setted - if rcParams['toolbar'] == 'toolmanager': - toolmanager = ToolManager(self.canvas.figure) - else: - toolmanager = None - return toolmanager - def get_window_title(self): return self.window.get_title() From 0b42bbee9cc9547ae2d87702d66410d9cdd186de Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 17 Apr 2015 19:41:17 +0200 Subject: [PATCH 18/58] rename window method --- doc/devel/MEP/MEP27.rst | 2 +- examples/user_interfaces/toolmanager_sgskip.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_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index b133468e9b3a..b1f13f8152f8 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -133,8 +133,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 3b86d5b3e3cb..32018d0590d6 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2749,7 +2749,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. @@ -2761,11 +2761,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 4bdcc5f1c94a..0d04654ed120 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 82b8cdc8149e..1d709bb1177d 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -416,17 +416,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 ef65740944524ec9e486ded7d573b5851864d270 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 17 Apr 2015 20:29:11 +0200 Subject: [PATCH 19/58] 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 1d709bb1177d..117f4df96dc3 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -64,7 +64,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() @@ -377,7 +377,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) @@ -1037,6 +1037,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 fb5202aa5fad3eda3d3205b1c0aa3703d0fd8ff8 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 4 Jun 2015 02:11:29 +0200 Subject: [PATCH 20/58] 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 32018d0590d6..6df92fcb57b6 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2533,7 +2533,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 0d04654ed120..9ec89c024ae1 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 5a012d0e80bf..b8aad2b23e44 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -388,7 +388,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 26ce3e6ad7b904b294e20f4baea3ef755d920dc2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 7 Jun 2015 13:21:14 +0200 Subject: [PATCH 21/58] 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 6df92fcb57b6..e3986897fc74 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -156,6 +156,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 @@ -167,12 +168,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 1cfebc466ac951252b81c2d2f663511bf9099d2d Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 13 Jun 2015 20:07:50 +0200 Subject: [PATCH 22/58] 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 9ec89c024ae1..8229d3d454e6 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 bc32fb1ebac99bd2b3feff0ed7eb341c9b948310 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 13 Jun 2015 22:55:39 +0200 Subject: [PATCH 23/58] Improve example after new method --- examples/user_interfaces/toolmanager_sgskip.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index b1f13f8152f8..76290ba834fd 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -110,30 +110,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('Show', '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 81b9e69c22d4b19405b3d11ce0043c8168b556bc Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 13 Jun 2015 22:56:26 +0200 Subject: [PATCH 24/58] Clean up the code a bit --- examples/user_interfaces/toolmanager_sgskip.py | 2 +- lib/matplotlib/backend_managers.py | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 76290ba834fd..1019f67c83a7 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -128,7 +128,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 8229d3d454e6..91485eaae192 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 85f06681bef43fdef7636469a50b89598d62b226 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 15 Jun 2015 18:48:42 +0200 Subject: [PATCH 25/58] 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 e3986897fc74..c1659aa2d396 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2532,10 +2532,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 91485eaae192..4be389434f72 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 653514850507c8fb98304a4bb92e9fe9af1c0c15 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 22 Jun 2015 19:06:49 +0200 Subject: [PATCH 26/58] 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 4be389434f72..ad251b350b7c 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 fafa6816375f..1f0652222730 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -36,8 +36,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) @@ -70,11 +73,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 @@ -82,6 +81,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 @@ -101,13 +109,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 73e11227f0a04266873e093627f8b51185b3a4dc Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 22 Jun 2015 19:10:12 +0200 Subject: [PATCH 27/58] 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 c1659aa2d396..3ba758a855a2 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2481,11 +2481,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 592c49b63fe265d78d8b7a625d211dbf02732ba7 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Mon, 22 Jun 2015 14:52:52 -0400 Subject: [PATCH 28/58] 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 ad251b350b7c..b2a43592e91b 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 b5d7f5792ff13bb74a37c81108537e7e811ba041 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 23 Jun 2015 18:09:05 +0200 Subject: [PATCH 29/58] 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 3ba758a855a2..e0957707c3e5 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2274,7 +2274,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): @@ -2282,7 +2282,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 b842bdca89d0c7ac067ccafa2b509ab03c4d8597 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 25 Jun 2015 02:19:16 +0200 Subject: [PATCH 30/58] super --- lib/matplotlib/backend_bases.py | 10 ++++++---- lib/matplotlib/backend_managers.py | 2 +- lib/matplotlib/backends/backend_gtk3.py | 11 ++++------- lib/matplotlib/cbook/__init__.py | 1 + 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e0957707c3e5..5aff23c687a2 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2678,8 +2678,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): """ @@ -3370,7 +3370,8 @@ class ToolContainerBase(object): The tools with which this `ToolContainer` wants to communicate. """ - 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) @@ -3495,7 +3496,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 b2a43592e91b..04ee578d3f05 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 117f4df96dc3..d3b3d6011efe 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -378,9 +378,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: @@ -817,8 +816,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) @@ -905,8 +903,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/__init__.py b/lib/matplotlib/cbook/__init__.py index 357763a71763..11918686fb58 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -369,6 +369,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 92de15e8faf4641d90e7f87720c159638d448eb8 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 25 Jun 2015 10:56:01 -0400 Subject: [PATCH 31/58] 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 04ee578d3f05..86ce259b81a9 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 b619923761e9ddca9f324402eca49e5b33848fad Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 25 Jun 2015 19:04:33 +0200 Subject: [PATCH 32/58] 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 86ce259b81a9..6714568a4b3f 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -529,14 +529,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 a07e4827ee6def11d0a7f3e7d098942b20e64473 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 25 Jun 2015 18:56:34 -0400 Subject: [PATCH 33/58] 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 2dd889507523c5c09926fb89436a949ae40cee91 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 26 Jun 2015 03:32:40 +0200 Subject: [PATCH 34/58] super dooper --- lib/matplotlib/backend_managers.py | 4 ++-- lib/matplotlib/backends/backend_gtk3.py | 4 ++-- lib/matplotlib/cbook/__init__.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 6714568a4b3f..ffd442b5a35d 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 d3b3d6011efe..8ed472599ac5 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -815,8 +815,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/__init__.py b/lib/matplotlib/cbook/__init__.py index 11918686fb58..b436bed3980c 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -368,8 +368,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 ce9c8e7112f4d80ab769ea3d7782cc5c67f81968 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 26 Jun 2015 18:33:09 +0200 Subject: [PATCH 35/58] Revert old example and fix new one. --- .../user_interfaces/toolmanager_sgskip.py | 56 +++---------------- lib/matplotlib/backend_managers.py | 1 + 2 files changed, 8 insertions(+), 49 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 1019f67c83a7..c6f47625bd09 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -19,10 +19,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): @@ -68,6 +66,7 @@ def disable(self, *args): self.set_lines_visibility(False) 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: @@ -75,66 +74,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 ffd442b5a35d..c1102a170a64 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 5542c5a3df8752d7ed84a69cb171f6bf7b7d529c Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 27 Jun 2015 02:46:23 +0200 Subject: [PATCH 36/58] Improve MEP22 tool-searching method. --- lib/matplotlib/backend_bases.py | 7 ++++++- lib/matplotlib/backend_managers.py | 7 ++----- lib/matplotlib/backends/backend_gtk3.py | 7 ++++--- lib/matplotlib/backends/backend_gtk3agg.py | 4 ++-- lib/matplotlib/backends/backend_gtk3cairo.py | 4 ++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 5aff23c687a2..849d4e6ba24e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1754,7 +1754,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) @@ -1778,6 +1779,10 @@ def _idle_draw_cntx(self): yield self._is_idle_drawing = False + @property + def backend(self): + return self._backend + def is_saving(self): """ Returns whether the renderer is in the process of saving diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index c1102a170a64..750ff576a59e 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 @@ -530,10 +530,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 8ed472599ac5..48d5101a106f 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -127,7 +127,8 @@ 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,8 +193,8 @@ class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase): Gdk.EventMask.POINTER_MOTION_HINT_MASK| Gdk.EventMask.SCROLL_MASK) - def __init__(self, figure, manager=None): - FigureCanvasBase.__init__(self, figure, manager) + def __init__(self, *args, **kwargs): + 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 a975717b5553720ee244ed141231709140f78d2e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 27 Jun 2015 02:48:03 +0200 Subject: [PATCH 37/58] 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 b8aad2b23e44..23d06dcfab5d 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""" @@ -765,6 +767,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 48d5101a106f..5395a0ab7c51 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -912,38 +912,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]) @@ -1029,7 +997,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 ecf1f23bf12453e73535fcc9d0cf8cb28dd82c48 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 27 Jun 2015 03:34:23 +0200 Subject: [PATCH 38/58] 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 750ff576a59e..73e5b5a07f2d 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 4531085c5f68ee68df7d0876879087ef0ea103a9 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 03:44:32 +0200 Subject: [PATCH 39/58] 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 23d06dcfab5d..6f63848f38f4 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 @@ -759,6 +761,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 5395a0ab7c51..340c40aeb11f 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -439,6 +439,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) @@ -917,59 +918,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' @@ -997,7 +945,6 @@ def error_msg_gtk(msg, parent=None): dialog.destroy() -backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3 backend_tools.ToolSetCursor = SetCursorGTK3 backend_tools.ToolRubberband = RubberbandGTK3 From 87fbd593087d5d5aeda88b46b9d634322fb02b42 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 17:51:47 +0200 Subject: [PATCH 40/58] 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 849d4e6ba24e..c2c64cb2cbe0 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3499,6 +3499,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 340c40aeb11f..9723aca2a830 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -30,8 +30,7 @@ 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.cbook import is_writable_file_like from matplotlib.figure import Figure @@ -375,7 +374,6 @@ def stop_event_loop(self): _flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL] -flow_types = ['horizontal', 'vertical'] class WindowGTK3(WindowBase, Gtk.Window): @@ -419,9 +417,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) @@ -816,11 +822,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() @@ -883,11 +888,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 f7eb33dbd3c67218cc591e4385811dc60a56eb57 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 17:54:46 +0200 Subject: [PATCH 41/58] 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 9723aca2a830..c8e697bd9c94 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -462,6 +462,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 0a2aa5c3178a5563d8694830a5c26a75b215f59a Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 28 Jun 2015 18:32:54 +0200 Subject: [PATCH 42/58] 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 c8e697bd9c94..d1374d6050ca 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -949,9 +949,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 777b162ee08586cfaad9a15f7a8fd1d6c2032ce4 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 29 Jun 2015 02:42:36 +0200 Subject: [PATCH 43/58] 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 c2c64cb2cbe0..c85d7acbfcc0 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3500,6 +3500,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): @@ -3509,6 +3518,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 @@ -3524,6 +3536,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 73e5b5a07f2d..fbdd6080f530 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 468bdd74cbeec5691546b9dffa25d0cb8c62a334 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 29 Jun 2015 03:28:35 +0200 Subject: [PATCH 44/58] 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 c85d7acbfcc0..73f4d7b547c2 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1708,7 +1708,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. @@ -2756,7 +2810,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. @@ -2765,9 +2819,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. @@ -3499,51 +3550,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 fbdd6080f530..7a4ed826da85 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 d1374d6050ca..0f27218e9814 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -29,7 +29,7 @@ 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.cbook import is_writable_file_like @@ -414,7 +414,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) @@ -428,6 +428,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 209af6b1a287960da8108b1fee7f5717e67ea0de Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 29 Jun 2015 23:58:13 +0200 Subject: [PATCH 45/58] 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 73f4d7b547c2..acbd73b2ec64 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1837,6 +1837,9 @@ def _idle_draw_cntx(self): def backend(self): return self._backend + def focus(self): + pass + def is_saving(self): """ Returns whether the renderer is in the process of saving diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 7a4ed826da85..2bb5c5119655 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 6f63848f38f4..264fd7af27b4 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -790,7 +790,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 1f0652222730..697de1d92262 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -76,7 +76,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 0f27218e9814..ae2fe057bb57 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 c7e7be2e09e7..c2be970530b1 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): """ @@ -258,19 +224,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 06686c7c77b82dad4f005d60b46a93e6b6524081 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 19 Jul 2015 14:51:44 +0200 Subject: [PATCH 46/58] rcParam and Travis --- .travis.yml | 6 +++++- .travis/toolmgr_matplotlibrc | 1 + lib/matplotlib/figure.py | 2 +- lib/matplotlib/pyplot.py | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .travis/toolmgr_matplotlibrc diff --git a/.travis.yml b/.travis.yml index ca223edb2c9e..64c6772d336c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,7 @@ env: - PYTEST_ARGS="-rawR --maxfail=1 --timeout=300 --durations=25 --cov-report= --cov=lib -n $NPROC" - PYTHON_ARGS= - DELETE_FONT_CACHE= + - USENEWTOOLMANAGER=false matrix: include: @@ -59,7 +60,7 @@ matrix: - python: 3.5 env: BUILD_DOCS=true - python: 3.6 - env: DELETE_FONT_CACHE=1 INSTALL_PEP8=pytest-pep8 RUN_PEP8=--pep8 PANDAS=pandas + env: DELETE_FONT_CACHE=1 INSTALL_PEP8=pytest-pep8 RUN_PEP8=--pep8 PANDAS=pandas USENEWTOOLMANAGER=true - python: "nightly" env: PRE=--pre - os: osx @@ -181,6 +182,9 @@ before_script: export DISPLAY=:99.0 sh -e /etc/init.d/xvfb start fi + if [[ $USENEWTOOLMANAGER == true ]]; then + cp .travis/toolmgr_matplotlibrc matplotlibrc + fi script: ci/travis/test_script.sh 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 c41fbc2959f0..11d7cfde3264 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1649,7 +1649,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 8032968f2dbe..984cf6344814 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -250,7 +250,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) @@ -539,7 +539,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 2d93df825808da8b42326ac2a04300a5e8f472a1 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 20 Jul 2015 20:20:01 +0200 Subject: [PATCH 47/58] test always MEP27 --- lib/matplotlib/testing/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 5b9dee44f6eb..3688dbb2ae64 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -140,7 +140,12 @@ 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() + set_reproducibility_for_testing() + + if use_new_toolmanager: + rcParams['toolbar'] = 'toolmanager' From 6b1598223462344070f93d79311429786b236814 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 14:11:58 +0200 Subject: [PATCH 48/58] 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 2bb5c5119655..daf9e0c41a97 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 61f61f6f4283fae88d48e6b075d24466b080e468 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:32:00 +0200 Subject: [PATCH 49/58] 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 daf9e0c41a97..68de7b2160d9 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 697de1d92262..cbe002d758bb 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -41,7 +41,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) @@ -73,7 +73,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 71f4813a837dad8c7a3a88d25ae16bdeefd672c6 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:39:40 +0200 Subject: [PATCH 50/58] 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 d3064b600bac9c48dee8607745cde2c718c7e8ca Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:44:28 +0200 Subject: [PATCH 51/58] 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 acbd73b2ec64..ced6b8e83f63 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -174,7 +174,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 2ca898c9cd819b3736ce74ffdc8b8b554e8f4e1d Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 27 Jul 2015 17:50:03 +0200 Subject: [PATCH 52/58] 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 68de7b2160d9..af3bde6e5798 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 a567af65bdb98ab4f4ab895d703c7bb4bc27936f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 23 Sep 2015 00:25:14 +0200 Subject: [PATCH 53/58] 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 264fd7af27b4..d7f38781d332 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -389,7 +389,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 09fc887a7a5185693ca9451c5bf7c47ed21cdae9 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 4 Aug 2016 01:48:58 +0200 Subject: [PATCH 54/58] 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 ced6b8e83f63..d35a6aa309dc 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2336,7 +2336,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): @@ -2344,7 +2344,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 cbe002d758bb..e7e1f4e4efb2 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -15,10 +15,11 @@ # Filter out line noise from importlib line. if not line.startswith(' File " Date: Mon, 19 Jun 2017 17:28:15 -0400 Subject: [PATCH 55/58] STY: replace \ with () for line continuation --- lib/matplotlib/backends/backend_template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_template.py b/lib/matplotlib/backends/backend_template.py index c2be970530b1..ec21c83c0621 100644 --- a/lib/matplotlib/backends/backend_template.py +++ b/lib/matplotlib/backends/backend_template.py @@ -69,8 +69,8 @@ import six import matplotlib -from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ - WindowBase, FigureCanvasBase, MainLoopBase, ToolbarBase +from matplotlib.backend_bases import (RendererBase, GraphicsContextBase, + WindowBase, FigureCanvasBase, MainLoopBase, ToolbarBase) from matplotlib import backend_tools from matplotlib.figure import Figure from matplotlib.transforms import Bbox From 072c47ac7a2ce792217ed189dfbfa5562013a948 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 19 Jun 2017 17:48:11 -0400 Subject: [PATCH 56/58] ENH: make MainLoopBase a context manager --- lib/matplotlib/backend_bases.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d35a6aa309dc..46781ae6c444 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -151,12 +151,14 @@ def get_registered_canvas_class(format): 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 = {} _running = False + def __init__(self): MainLoopBase._instance_count.setdefault(self.__class__, 0) MainLoopBase._instance_count[self.__class__] += 1 @@ -177,6 +179,14 @@ def __del__(self): not is_interactive() and MainLoopBase._running): self.end() + def __enter__(self): + pass + + def __exit__(self, excp_type, excp_value, excp_traceback): + if not excp_type: + self.begin() + self.end() + class ShowBase(object): """ From 214b68418892a4635afa358fd904ea3b4f16cb20 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 24 Jun 2017 22:25:17 -0400 Subject: [PATCH 57/58] MNT: further simplify MainLoopBase If begin / end are _blocking_ then these changes make sense, if they are non-blocking then these changes do not make sense. This means we can not use the global counter on this class to detect when all of the open figures are closed. --- doc/devel/MEP/MEP27.rst | 5 +---- lib/matplotlib/backend_bases.py | 14 +------------- lib/matplotlib/backend_managers.py | 5 ----- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/doc/devel/MEP/MEP27.rst b/doc/devel/MEP/MEP27.rst index 8db8215a6563..571e8edad9e3 100644 --- a/doc/devel/MEP/MEP27.rst +++ b/doc/devel/MEP/MEP27.rst @@ -86,10 +86,7 @@ The description of this MEP gives us most of the solution: subclass the GUI specific window class to ensure backward compatibility (``manager.window == manager.window``). 2. Refactor the mainloop of ``ShowBase`` into ``MainLoopBase``, which - encapsulates the end of the loop as well. We give an instance of - ``MainLoop`` to ``FigureManager`` as a key unlock the exit method - (requiring all keys returned before the loop can die). Note this - opens the possibility for multiple backends to run concurrently. + encapsulates the end of the loop as well. 3. Now that ``FigureManagerBase`` has no backend specifics in it, to rename it to ``FigureManager``, and move to a new file ``backend_managers.py`` noting that: diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 46781ae6c444..a1dd035400c7 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -156,13 +156,6 @@ class MainLoopBase(object): It should not matter if this gets used as a singleton or not due to clever magic. """ - _instance_count = {} - _running = False - - def __init__(self): - MainLoopBase._instance_count.setdefault(self.__class__, 0) - MainLoopBase._instance_count[self.__class__] += 1 - def begin(self): pass @@ -172,12 +165,7 @@ def end(self): 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() and MainLoopBase._running): - self.end() + self.end() def __enter__(self): pass diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index af3bde6e5798..d28cf603a14a 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -85,7 +85,6 @@ def __init__(self, figure, num, **kwargs): self.window = None return - self._mainloop = self._backend.MainLoop() self.window = self._backend.Window('Figure %d' % num) self.window.mpl_connect('window_destroy_event', self.destroy) @@ -151,10 +150,6 @@ def destroy(self, *args): 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) From 35838c989c991f42aaede9dd3a367bafdb51b905 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 26 Jun 2017 23:17:09 -0400 Subject: [PATCH 58/58] WIP: This will get rebased out of existence --- lib/matplotlib/_pylab_helpers.py | 48 ++++++++++--------------- lib/matplotlib/backend_bases.py | 29 +++++++-------- lib/matplotlib/backend_managers.py | 5 ++- lib/matplotlib/backends/backend_gtk3.py | 9 +++-- lib/matplotlib/backends/backend_qt5.py | 3 +- 5 files changed, 42 insertions(+), 52 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index 4d7cdc6cf6fc..09cc863985c9 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -73,40 +73,30 @@ def show_all(cls, block=None): if not managers: return + # treat the first manager as proto-typical of all + m0 = managers[0] + + if block is None: + # webagg always needs to block because tornado is + # not integrated into the REPL + block = (not is_interactive()) or m0.backend_name == 'webagg' + for manager in managers: 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: - return + if block: + ml = m0.backend.MainLoop() + counter = {'open_windows': len(managers)} - # 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 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. - 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: - return + def on_window_close(ev): + counter['open_windows'] -= 1 + if counter['open_windows'] < 1: + ml.end() + + for manager in managers: + manager.mpl_connect('window_destroy_event', on_window_close) - # If not interactive we need to block - if not is_interactive() or manager.backend_name == 'webagg': - manager._mainloop() + ml.begin() @classmethod def destroy(cls, num): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index a1dd035400c7..edf6ca22d3a3 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -150,31 +150,28 @@ def get_registered_canvas_class(format): class MainLoopBase(object): - """This gets used as a key maintaining the event loop. + """Abstract base class for blocking GUI main loops - 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. + This provides a simple interface that backends can implement to + expose starting and stopping the event loop. + + This class is primarily used by `pyplot` in not in 'interactive mode'. """ def begin(self): - pass + '''Start blocking GUI event loop - def end(self): + This should start the GUI event loop and block. + ''' pass - def __call__(self): - MainLoopBase._running = True - self.begin() - self.end() + def end(self): + '''Stop the event loop. - def __enter__(self): + This will need to be called from a callback inside the event loop ( + which is arranged by `pyplot` if it is in use). + ''' pass - def __exit__(self, excp_type, excp_value, excp_traceback): - if not excp_type: - self.begin() - self.end() - class ShowBase(object): """ diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index d28cf603a14a..8fb6f321c568 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -20,8 +20,7 @@ from matplotlib import is_interactive from matplotlib import rcParams -from matplotlib.figure import Figure -from matplotlib.backends import get_backend, backend as backend_name +from matplotlib.backends import get_backend class FigureManagerEvent(object): @@ -186,7 +185,7 @@ def set_window_title(self, title): @property def backend(self): - return self._backend + return get_backend(self.backend_name) @property def backend_name(self): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index ae2fe057bb57..f8cab2e2aca5 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -26,7 +26,6 @@ raise ImportError("Gtk3 backend requires pygobject to be installed.") import matplotlib -from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import (RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase, WindowBase, MainLoopBase, ExpandableBase) @@ -43,7 +42,9 @@ Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) # the true dots per inch on the screen; should be display dependent -# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi +# see +# http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 +# for some info about screen dpi PIXELS_PER_INCH = 96 cursord = { @@ -58,7 +59,8 @@ def draw_if_interactive(): Is called after every pylab drawing command """ if matplotlib.is_interactive(): - figManager = Gcf.get_active() + from matplotlib._pylab_helpers import Gcf + figManager = Gcf.get_active() if figManager is not None: figManager.canvas.draw_idle() @@ -78,6 +80,7 @@ def mainloop(self): if Gtk.main_level() == 0: Gtk.main() + show = Show() diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 291c0caab68f..a5d6666e29b4 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -152,7 +152,8 @@ def mainloop(self): # allow KeyboardInterrupt exceptions to close the plot window. signal.signal(signal.SIGINT, signal.SIG_DFL) global qApp - qApp.exec_() + if qApp is not None: + qApp.exec_() show = Show()