From a638e30cd321c081d0f03cab3fedb652c4aeff44 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 22 Feb 2015 00:14:36 +0100 Subject: [PATCH 01/20] 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 2513419ef8c4..2684b2f9040b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -45,6 +45,7 @@ import io 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 @@ -2560,6 +2561,165 @@ def key_press_handler(event, canvas, toolbar=None): class NonGuiException(Exception): pass +class WindowEvent(object): + def __init__(self, name, window): + self.name = name + self.window = window + +class WindowBase(object): + def __init__(self, title): + self._callbacks = cbook.CallbackRegistry() + + def mpl_connect(self, s, func): + return self._callbacks.connect(s, func) + + def mpl_disconnect(self, cid): + return self._callbacks.disconnect(cid) + + def show(self): + """ + For GUI backends, show the figure window and redraw. + For non-GUI backends, raise an exception to be caught + by :meth:`~matplotlib.figure.Figure.show`, for an + optional warning. + """ + raise NonGuiException() + + def destroy(self): + pass + + def set_fullscreen(self, fullscreen): + pass + + def resize(self, w, h): + """"For gui backends, resize the window (in pixels).""" + pass + + def get_window_title(self): + """ + Get the title text of the window containing the figure. + Return None for non-GUI backends (e.g., a PS backend). + """ + return 'image' + + def set_window_title(self, title): + """ + Set the title text of the window containing the figure. Note that + this has no effect for non-GUI backends (e.g., a PS backend). + """ + pass + + def add_element_to_window(self, element, expand, fill, padding, from_start=False): + """ Adds a gui widget to the window. + This has no effect for non-GUI backends + """ + pass + + def terminate_backend(self): + """Method to terminate the usage of the backend + """ + # TODO refactor me out on second pass + pass + + def destroy_event(self, *args): + s = 'window_destroy_event' + event = WindowEvent(s, self) + self._callbacks.process(s, event) + + +class FigureManager(object): + def __init__(self, canvas, num, classes): + self._classes = classes + self.canvas = canvas + canvas.manager = self + self.num = num + + self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', + self.key_press) + + self.window = classes['Window']('Figure %d' % num) + self.window.mpl_connect('window_destroy_event', self._destroy) + + w = int(self.canvas.figure.bbox.width) + h = int(self.canvas.figure.bbox.height) + + self.window.add_element_to_window(self.canvas, True, True, 0, True) + + self.toolbar = self._get_toolbar(canvas) + if self.toolbar is not None: + h += self.window.add_element_to_window(self.toolbar, False, False, 0) + + self.window.set_default_size(w,h) + + # Refactor this? If so, delete import matplotlib from above. + if matplotlib.is_interactive(): + self.window.show() + + def notify_axes_change(fig): + 'this will be called whenever the current axes is changed' + if self.toolbar is not None: self.toolbar.update() + self.canvas.figure.add_axobserver(notify_axes_change) + + self.canvas.grab_focus() + + def key_press(self, event): + """ + Implement the default mpl key bindings defined at + :ref:`key-event-handling` + """ + key_press_handler(event, self.canvas, self.canvas.toolbar) + + def _destroy(self, event): + Gcf.destroy(self.num) # TODO refactor me out of here on second pass! + + def destroy(self, *args): + self.window.destroy() + self.canvas.destroy() + if self.toolbar: + self.toolbar.destroy() + + # TODO refactor out on second pass + if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive(): + self.window.terminate_backend() + + def show(self): + self.window.show() + + def full_screen_toggle(self): + self._full_screen_flag = not self._full_screen_flag + self.window.set_fullscreen(self._full_screen_flag) + + def resize(self, w, h): + self.window.resize(w,h) + + def get_window_title(self): + """ + Get the title text of the window containing the figure. + Return None for non-GUI backends (e.g., a PS backend). + """ + return self.window.get_window_title() + + def set_window_title(self, title): + """ + Set the title text of the window containing the figure. Note that + this has no effect for non-GUI backends (e.g., a PS backend). + """ + self.window.set_window_title(title) + + def show_popup(self, msg): + """ + Display message in a popup -- GUI only + """ + pass + + def _get_toolbar(self, canvas): + # must be inited after the window, drawingArea and figure + # attrs are set + if rcParams['toolbar'] == 'toolbar2': + toolbar = self._classes['Toolbar2'](canvas, self.window) + else: + toolbar = None + return toolbar class FigureManagerBase(object): """ diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 32d15fb4fbcc..d7b00cf353a3 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -29,7 +29,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name import matplotlib from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase + FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase, WindowBase from matplotlib.backend_bases import (ShowBase, ToolContainerBase, StatusbarBase) from matplotlib.backend_managers import ToolManager @@ -380,6 +380,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): """ @@ -1131,3 +1210,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 9307461259b7bf55a47382c2551c2cc5a523718b Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 23 Feb 2015 00:22:14 +0100 Subject: [PATCH 02/20] Refactor Pass 2. Refactored Gcf out of all backend code. --- lib/matplotlib/_pylab_helpers.py | 59 +++++++- lib/matplotlib/backend_bases.py | 145 +++++-------------- lib/matplotlib/backend_managers.py | 133 +++++++++++++++++ lib/matplotlib/backends/__init__.py | 52 ++++--- lib/matplotlib/backends/backend_gtk3.py | 30 ++-- lib/matplotlib/backends/backend_gtk3cairo.py | 11 +- lib/matplotlib/cbook.py | 11 ++ lib/matplotlib/pyplot.py | 23 +-- 8 files changed, 306 insertions(+), 158 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index 840f6bd8e652..d4fca6f38159 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,9 @@ def set_active(cls, manager): if m != manager: cls._activeQue.append(m) cls._activeQue.append(manager) - cls.figs[manager.num] = manager + @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 2684b2f9040b..095771e06771 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -45,7 +45,6 @@ import io 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 @@ -139,6 +138,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. @@ -2486,7 +2512,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') @@ -2561,20 +2590,16 @@ def key_press_handler(event, canvas, toolbar=None): class NonGuiException(Exception): pass + class WindowEvent(object): def __init__(self, name, window): self.name = name self.window = window -class WindowBase(object): - def __init__(self, title): - self._callbacks = cbook.CallbackRegistry() - - def mpl_connect(self, s, func): - return self._callbacks.connect(s, func) - def mpl_disconnect(self, cid): - return self._callbacks.disconnect(cid) +class WindowBase(cbook.EventEmitter): + def __init__(self, title): + cbook.EventEmitter.__init__(self) def show(self): """ @@ -2615,112 +2640,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 41d80c96a9d1..243fcbe7b4c5 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)""" @@ -389,3 +501,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 cf80dc0f9ff5..f43366d923b3 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -14,18 +14,32 @@ 'new_figure_manager', 'backend_version'] backend = matplotlib.get_backend() # validates, to match all_backends +if backend.startswith('module://'): + backend_name = backend[9:] +else: + backend_name = 'matplotlib.backends.backend_' + backend.lower() + +def get_backends(): + _temp = __import__(backend_name, globals(), locals(), + ['Window', 'Toolbar2', 'FigureCanvas', 'MainLoop', + 'new_figure_manager'], 0) + FigureCanvas = _temp.FigureCanvas + try: + Window = _temp.Window + Toolbar2 = _temp.Toolbar2 + MainLoop = _temp.MainLoop + old_new_figure_manager = None + except AttributeError: + Window = None + Toolbar2 = None + MainLoop = getattr(_temp, 'show', do_nothing_show) + old_new_figure_manager = _temp.new_figure_manager + + return FigureCanvas, Window, Toolbar2, MainLoop, old_new_figure_manager def pylab_setup(): 'return new_figure_manager, draw_if_interactive and show for pylab' # Import the requested backend into a generic module object - - if backend.startswith('module://'): - backend_name = backend[9:] - else: - backend_name = 'backend_'+backend - backend_name = backend_name.lower() # until we banish mixed case - backend_name = 'matplotlib.backends.%s'%backend_name.lower() - # the last argument is specifies whether to use absolute or relative # imports. 0 means only perform absolute imports. backend_mod = __import__(backend_name, @@ -37,18 +51,10 @@ def pylab_setup(): # 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 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) # Additional imports which only happen for certain backends. This section @@ -60,3 +66,13 @@ def do_nothing(*args, **kwargs): pass matplotlib.verbose.report('backend %s version %s' % (backend,backend_version)) 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()""" % + (backend, matplotlib.matplotlib_fname())) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index d7b00cf353a3..5af796b5a3fd 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -28,8 +28,9 @@ def fn_name(): return sys._getframe(1).f_code.co_name import matplotlib from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase, WindowBase +from matplotlib.backend_bases import (RendererBase, GraphicsContextBase, + FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, + TimerBase, WindowBase, MainLoopBase) from matplotlib.backend_bases import (ShowBase, ToolContainerBase, StatusbarBase) from matplotlib.backend_managers import ToolManager @@ -70,6 +71,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: @@ -426,26 +438,12 @@ def destroy(self): self.vbox.destroy() self.window.destroy() - # TODO refactor out on second pass. - def terminate_backend(self): - if Gtk.main_level() >= 1: - Gtk.main_quit() - def set_fullscreen(self, fullscreen): if fullscreen: self.window.fullscreen() else: self.window.unfullscreen() - def _get_toolbar(self, canvas): - # must be inited after the window, drawingArea and figure - # attrs are set - if rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2GTK3 (canvas, self.window) - else: - toolbar = None - return toolbar - def get_window_title(self): return self.window.get_title() diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 33bd8d054af6..ce61bf26adcf 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -7,7 +7,6 @@ from . import backend_cairo from .backend_cairo import cairo, HAS_CAIRO_CFFI from matplotlib.figure import Figure -from matplotlib.backend_bases import FigureManager class RendererGTK3Cairo(backend_cairo.RendererCairo): def set_context(self, ctx): @@ -52,6 +51,7 @@ def on_draw_event(self, widget, ctx): class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): pass + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance @@ -66,10 +66,13 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasGTK3Cairo(figure) - manager = FigureManager(canvas, num, classes) + manager = FigureManagerGTK3Cairo(canvas, num) return manager -classes = {'Window': backend_gtk3.WindowGTK3, - 'Toolbar2': backend_gtk3.NavigationToolbar2GTK3} + FigureCanvas = FigureCanvasGTK3Cairo +FigureManager = FigureManagerGTK3Cairo +Window = backend_gtk3.WindowGTK3 +Toolbar2 = backend_gtk3.NavigationToolbar2GTK3 +MainLoop = backend_gtk3.MainLoop show = backend_gtk3.show diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 814df14d9ab4..00b1b4c241ab 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -566,6 +566,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 Scheduler(threading.Thread): """ Base class for timeout and idle scheduling diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 1eef95867743..0111b0d72dce 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -31,6 +31,7 @@ from matplotlib.cbook import _string_to_bool from matplotlib import docstring from matplotlib.backend_bases import FigureCanvasBase +import matplotlib.backend_managers as backend_managers from matplotlib.figure import Figure, figaspect from matplotlib.gridspec import GridSpec from matplotlib.image import imread as _imread @@ -151,7 +152,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(): @@ -425,13 +429,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) @@ -444,7 +449,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) figManager.canvas.figure.number = num draw_if_interactive() From a5e52a0e2d167d74d7dea844542cc5c7272920b9 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 25 Feb 2015 01:42:44 +0100 Subject: [PATCH 03/20] 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 f14c8c8c64a6..4bc879d02f06 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1366,11 +1366,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 @@ -1385,7 +1392,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 d104283715a4025b91b635e4ae38347fd5e992c2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 26 Feb 2015 17:54:59 +0100 Subject: [PATCH 04/20] 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 26a872d2e135c108bf1654c96ad69d7ef5ae31e1 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 15:02:51 +0100 Subject: [PATCH 05/20] 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 095771e06771..7a141380b941 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -43,6 +43,7 @@ import warnings import time import io +import weakref import numpy as np import matplotlib.cbook as cbook @@ -1703,7 +1704,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): figure.set_canvas(self) self.figure = figure # a dictionary from event name to a dictionary that maps cid->func @@ -1717,6 +1718,7 @@ def __init__(self, figure): self.mouse_grabber = None # the axes currently grabbing mouse self.toolbar = None # NavigationToolbar2 will set me self._is_saving = False + self.manager = manager def is_saving(self): """ @@ -2473,6 +2475,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): """ @@ -2616,6 +2631,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 243fcbe7b4c5..1aa28672780a 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)""" @@ -508,17 +506,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 5af796b5a3fd..d882dd2c1df7 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -196,9 +196,9 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): Gdk.EventMask.POINTER_MOTION_HINT_MASK| Gdk.EventMask.SCROLL_MASK) - def __init__(self, figure): + def __init__(self, figure, manager=None): if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - FigureCanvasBase.__init__(self, figure) + FigureCanvasBase.__init__(self, figure, manager) GObject.GObject.__init__(self) self._idle_draw_id = 0 diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 24a93ce4b4be..5bc4bdcf0afd 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -21,8 +21,8 @@ class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3, backend_agg.FigureCanvasAgg): - def __init__(self, figure): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure) + def __init__(self, figure, manager=None): + backend_gtk3.FigureCanvasGTK3.__init__(self, figure, manager) self._bbox_queue = [] def _renderer_init(self): diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index ce61bf26adcf..d00ebdebd9c6 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -22,8 +22,8 @@ def set_context(self, ctx): class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3, backend_cairo.FigureCanvasCairo): - def __init__(self, figure): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure) + def __init__(self, figure, manager=None): + backend_gtk3.FigureCanvasGTK3.__init__(self, figure, manager) def _renderer_init(self): """use cairo renderer""" diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 4bc879d02f06..71dbcdb278d3 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1366,7 +1366,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 @@ -1377,7 +1377,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 8e18ebe77b15391e630807be9761d9435d06aba9 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 16:09:23 +0100 Subject: [PATCH 06/20] 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 1aa28672780a..f5a6953d3586 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 c3f34b71369c7ad5804c6dcc17954e2672bb0475 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 17:37:53 +0100 Subject: [PATCH 07/20] 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 7a141380b941..1d2813619875 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2652,7 +2652,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 f5a6953d3586..f94e4c4a5ad0 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 d882dd2c1df7..bb8e3acd1e0a 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -415,15 +415,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 d8a1bdf2d25d10c680aad88ae54e1685891d3d1d Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 19:29:36 +0100 Subject: [PATCH 08/20] 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 f94e4c4a5ad0..4db1dc692627 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 f2b7f087d26aecdb35c73d8b5098442738fbc0fd Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 28 Feb 2015 12:04:29 +0100 Subject: [PATCH 09/20] 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 bb8e3acd1e0a..933e2ec87ff8 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -392,14 +392,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 @@ -412,11 +412,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() @@ -430,34 +430,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): @@ -1210,4 +1203,3 @@ def error_msg_gtk(msg, parent=None): Toolbar = ToolbarGTK3 FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 -Window = WindowGTK3 From 0bda9db6de1c0cd9e504e75b3edff311e60250d2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 3 Mar 2015 11:17:32 +0100 Subject: [PATCH 10/20] 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 d4fca6f38159..a112b67f0806 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 1d2813619875..1cf50b6ce199 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 @@ -2527,10 +2533,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') @@ -2613,6 +2619,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) @@ -2626,22 +2640,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' @@ -2649,16 +2692,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 4db1dc692627..bdc77b5cc173 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): @@ -499,23 +544,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 f43366d923b3..a42816323501 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -21,21 +21,19 @@ def get_backends(): _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(): 'return new_figure_manager, draw_if_interactive and show for pylab' @@ -54,7 +52,7 @@ def pylab_setup(): def do_nothing(*args, **kwargs): pass 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) # Additional imports which only happen for certain backends. This section diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 71dbcdb278d3..44f5edd9d17b 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1372,12 +1372,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 0111b0d72dce..86d985aed58e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -152,10 +152,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(): @@ -429,14 +429,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 89fc1d5cc08e0f5445d51122a651b8036a2cb6b3 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 6 Apr 2015 23:55:22 +0200 Subject: [PATCH 11/20] 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 1cf50b6ce199..4e299754b384 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2700,7 +2700,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. @@ -2717,6 +2717,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 bdc77b5cc173..2d721dece517 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 933e2ec87ff8..db99166d5488 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -392,7 +392,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) @@ -410,22 +410,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 @@ -437,7 +450,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 7e1c677a82e726e8bfbee4119acf072c4ee8b650 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 7 Apr 2015 21:05:36 +0200 Subject: [PATCH 12/20] 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 2d721dece517..bf29621f2f8d 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 a42816323501..b1b6d3ee0f51 100644 --- a/lib/matplotlib/backends/__init__.py +++ b/lib/matplotlib/backends/__init__.py @@ -19,21 +19,9 @@ else: backend_name = 'matplotlib.backends.backend_' + backend.lower() -def get_backends(): - _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 +def get_backend(): + return __import__(backend_name, globals(), locals(), + [backend_name], 0) def pylab_setup(): 'return new_figure_manager, draw_if_interactive and show for pylab' diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 44f5edd9d17b..24dac783d100 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1372,7 +1372,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 86d985aed58e..fa5f7c597e43 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -152,7 +152,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) @@ -429,7 +429,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 99c19ea2e5c5bbe58aae30c73559642c10167b51 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 12 Apr 2015 19:30:25 +0200 Subject: [PATCH 13/20] 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 bf29621f2f8d..862c239404f4 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 db99166d5488..3a92f1da3bec 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -392,6 +392,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) @@ -433,14 +437,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) @@ -1213,5 +1222,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 3ed068fbe6943befcbb85d6798405eddd171ba43 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 12 Apr 2015 23:54:05 +0200 Subject: [PATCH 14/20] Improved new toolbar and updated tool_manager example accoridingly. --- examples/user_interfaces/toolmanager.py | 52 ++++++++++++++++++++++--- lib/matplotlib/backends/backend_gtk3.py | 25 +++++++++--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index 5240bab239c2..81f0fbbe2341 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -12,8 +12,10 @@ matplotlib.use('GTK3Cairo') matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt -from matplotlib.backend_tools import ToolBase, ToolToggleBase +from matplotlib.backend_tools import (ToolBase, ToolToggleBase, + add_tools_to_container) from gi.repository import Gtk, Gdk +from random import uniform class ListTools(ToolBase): @@ -60,7 +62,6 @@ def disable(self, *args): self.set_lines_visibility(True) def set_lines_visibility(self, state): - gr_lines = [] for ax in self.figure.get_axes(): for line in ax.get_lines(): if line.get_gid() == self.gid: @@ -68,14 +69,44 @@ def set_lines_visibility(self, state): self.figure.canvas.draw() +class LineTool(ToolBase): + description = 'Draw a random line' + + def __init__(self, *args, **kwargs): + self.color = kwargs.pop('color') + ToolBase.__init__(self, *args, **kwargs) + + def trigger(self, *args, **kwargs): + x0, y0, x1, y1 = (uniform(0, 2), uniform(1, 4), uniform(0, 2), + uniform(1, 4)) + fig = self.figure + fig.gca().plot([x0, x1], [y0, y1], color=self.color, gid=self.color) + fig.canvas.draw_idle() + + +class DotTool(ToolBase): + description = 'Draw a random dot' + + def __init__(self, *args, **kwargs): + self.color = kwargs.pop('color') + ToolBase.__init__(self, *args, **kwargs) + + def trigger(self, *args, **kwargs): + x0, y0 = uniform(0, 2), uniform(1, 4) + fig = self.figure + fig.gca().plot([x0], [y0], 'o', color=self.color, gid=self.color) + fig.canvas.draw_idle() + + fig = plt.figure() plt.plot([1, 2, 3], gid='mygroup') plt.plot([2, 3, 4], gid='unknown') plt.plot([3, 2, 1], gid='mygroup') # Add the custom tools that we created -fig.canvas.manager.toolmanager.add_tool('List', ListTools) -fig.canvas.manager.toolmanager.add_tool('Hide', GroupHideTool, gid='mygroup') +tool_mgr = fig.canvas.manager.toolmanager +tool_mgr.add_tool('List', ListTools) +tool_mgr.add_tool('Hide', GroupHideTool, gid='mygroup') # Add an existing tool to new group `foo`. @@ -83,10 +114,21 @@ def set_lines_visibility(self, state): fig.canvas.manager.toolbar.add_tool('zoom', 'foo') # Remove the forward button -fig.canvas.manager.toolmanager.remove_tool('forward') +tool_mgr.remove_tool('forward') # To add a custom tool to the toolbar at specific location inside # the navigation group fig.canvas.manager.toolbar.add_tool('Hide', 'navigation', 1) +for i, c in enumerate(['yellowgreen', 'forestgreen']): + sidebar = fig.canvas.manager._get_toolbar() + sidebar.set_flow('vertical') + tools = [['shapes', [tool_mgr.add_tool('L%s' % i, LineTool, color=c), + tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], + ['hide', [tool_mgr.add_tool('H%s' % i, GroupHideTool, gid=c)]]] + + fig.canvas.manager.window.add_element_to_window(sidebar, False, False, 0, + 'west') + add_tools_to_container(sidebar, tools) + plt.show() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 3a92f1da3bec..8a04cee84a48 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -394,6 +394,7 @@ def stop_event_loop(self): _flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL] +flow_types = ['horizontal', 'vertical'] class Window(WindowBase, Gtk.Window): @@ -854,13 +855,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 = {} @@ -893,7 +893,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() @@ -922,9 +922,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 e0a27c2d7d8cea0a547ff3331fc847672744bb8f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 13 Apr 2015 08:05:54 +0200 Subject: [PATCH 15/20] 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 862c239404f4..e80a9fa19495 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 b4660fcfef0526dedbb1d1f416d2880f51ef769e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Tue, 14 Apr 2015 19:51:34 +0200 Subject: [PATCH 16/20] 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 b4b7246d61858fe7e79a86d95f5cdd1a32bf4a87 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 15 Apr 2015 01:00:20 +0200 Subject: [PATCH 17/20] Finish MEP22 conversion --- lib/matplotlib/backend_bases.py | 8 ++------ lib/matplotlib/backend_managers.py | 2 +- lib/matplotlib/backend_tools.py | 8 ++++++-- lib/matplotlib/backends/backend_gtk3.py | 25 ++----------------------- 4 files changed, 11 insertions(+), 32 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 4e299754b384..80b81d9e002e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2751,12 +2751,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 e80a9fa19495..8f1f9a28fa06 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) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 1d952f2417da..9544719d097c 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 numpy as np @@ -318,7 +317,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 ToolEnableAllNavigation(ToolBase): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 8a04cee84a48..6c85656fcc37 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -441,6 +441,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) @@ -515,9 +516,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() @@ -525,14 +524,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) @@ -548,9 +539,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) @@ -587,20 +576,10 @@ def _get_toolbar(self): # attrs are set if rcParams['toolbar'] == 'toolbar2': toolbar = NavigationToolbar2GTK3 (self.canvas, self.window) - elif rcParams['toolbar'] == 'toolmanager': - toolbar = ToolbarGTK3(self.toolmanager) else: toolbar = None return toolbar - def _get_toolmanager(self): - # must be initialised after toolbar has been setted - if rcParams['toolbar'] != 'toolbar2': - toolmanager = ToolManager(self.canvas) - else: - toolmanager = None - return toolmanager - def get_window_title(self): return self.window.get_title() From 93a88918e6dfeda693b1640c019eb7a3462a2b8a Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 17 Apr 2015 19:41:17 +0200 Subject: [PATCH 18/20] rename window method --- doc/devel/MEP/MEP27.rst | 2 +- examples/user_interfaces/toolmanager.py | 3 +-- lib/matplotlib/backend_bases.py | 7 +------ lib/matplotlib/backend_managers.py | 8 +++----- lib/matplotlib/backends/backend_gtk3.py | 6 +++--- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/doc/devel/MEP/MEP27.rst b/doc/devel/MEP/MEP27.rst index aaf404df1c25..8e71c5ef0a9f 100644 --- a/doc/devel/MEP/MEP27.rst +++ b/doc/devel/MEP/MEP27.rst @@ -128,7 +128,7 @@ The description of this MEP gives us most of the solution: +--------------------------------------+------------------------------+---------------------+--------------------------------+ |X |X |set_default_size | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|X |X |add_element_to_window| | +|X |X |add_element | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ diff --git a/examples/user_interfaces/toolmanager.py b/examples/user_interfaces/toolmanager.py index 81f0fbbe2341..598b69281bf0 100644 --- a/examples/user_interfaces/toolmanager.py +++ b/examples/user_interfaces/toolmanager.py @@ -127,8 +127,7 @@ def trigger(self, *args, **kwargs): tool_mgr.add_tool('D%s' % i, DotTool, color=c)]], ['hide', [tool_mgr.add_tool('H%s' % i, GroupHideTool, gid=c)]]] - fig.canvas.manager.window.add_element_to_window(sidebar, False, False, 0, - 'west') + fig.canvas.manager.window.add_element(sidebar, False, 'west') add_tools_to_container(sidebar, tools) plt.show() diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 80b81d9e002e..560d61e59f52 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2700,7 +2700,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. @@ -2712,11 +2712,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 8f1f9a28fa06..38886a39b2fe 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 6c85656fcc37..1a6e0308e52d 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -436,17 +436,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 a334841ed190c1ba737cd62428db4cd571eaa71f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 17 Apr 2015 20:29:11 +0200 Subject: [PATCH 19/20] 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 1a6e0308e52d..aa30f61ff855 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -72,7 +72,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() @@ -397,7 +397,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) @@ -1214,6 +1214,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 6703e880ca65ca2865fdc8a0aa0a79f27352a4fe Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Fri, 29 May 2015 15:36:19 -0400 Subject: [PATCH 20/20] making figuremanager figure centric instead of canvas centric, figure is replaceable --- .../user_interfaces/reuse_figuremanager.py | 39 +++++++++++ lib/matplotlib/backend_bases.py | 22 ++++++ lib/matplotlib/backend_managers.py | 67 ++++++++++++++----- lib/matplotlib/backends/backend_gtk3.py | 21 ++++-- 4 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 examples/user_interfaces/reuse_figuremanager.py diff --git a/examples/user_interfaces/reuse_figuremanager.py b/examples/user_interfaces/reuse_figuremanager.py new file mode 100644 index 000000000000..e9f44540c076 --- /dev/null +++ b/examples/user_interfaces/reuse_figuremanager.py @@ -0,0 +1,39 @@ +import matplotlib +matplotlib.use('GTK3AGG') +matplotlib.rcParams['toolbar'] = 'toolmanager' +from matplotlib.figure import Figure +from matplotlib.backend_managers import FigureManager +from matplotlib.backends import get_backend +from matplotlib.backend_tools import ToolBase + + +backend = get_backend() + +fig1 = Figure() +canvas1 = backend.FigureCanvas(fig1) +ax1 = fig1.add_subplot(111) +ax1.plot([1, 2, 3]) + +fig2 = Figure() +canvas2 = backend.FigureCanvas(fig2) +ax2 = fig2.add_subplot(111) +ax2.plot([3, 2, 1]) + + +class SwitchFigure(ToolBase): + description = "change Figure" + default_keymap = 'f' + + def __init__(self, *args, **kwargs): + self.fig = kwargs.pop('newfig') + self.figmanager = kwargs.pop('figuremanager') + ToolBase.__init__(self, *args, **kwargs) + + def trigger(self, *args, **kwargs): + self.figmanager.figure = self.fig + +manager = FigureManager(fig1, 1) +manager.toolmanager.add_tool('f1', SwitchFigure, newfig=fig2, figuremanager=manager) +manager.show() +manager.mpl_connect('window_destroy_event', manager.destroy) +manager._mainloop() \ No newline at end of file diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 560d61e59f52..97410ae967c4 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2718,6 +2718,28 @@ def add_element(self, element, expand, place): """ pass + def replace_element(self, oldelement, newelement): + """Replaces a gui widget for another + + Parameters + ---------- + oldelement: A gui element. + The element to be replaced + newelement: A gui element. + The replacement element + """ + pass + + def remove_element(self, element): + """Removes a widget + + Parameters + ---------- + element: A gui element + Element to be removed from the gui + """ + pass + def destroy_event(self, *args): """Fires this event when the window wants to destroy itself. diff --git a/lib/matplotlib/backend_managers.py b/lib/matplotlib/backend_managers.py index 38886a39b2fe..dadeab311b6b 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -75,21 +75,18 @@ def __init__(self, figure, num): cbook.EventEmitter.__init__(self) self.num = num + self._h = 0 + self._w = 0 + self._figure = None + self.key_press_handler_id = None + 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 = 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) - - self.window.add_element(self.canvas, True, 'center') - + self.figure = figure self.toolmanager = self._get_toolmanager() self.toolbar = self._get_toolbar() @@ -98,29 +95,62 @@ 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(self.statusbar, False, 'south') + self._h += self.window.add_element(self.statusbar, + False, + 'south') if self.toolbar is not None: - h += self.window.add_element(self.toolbar, False, 'south') + self._h += self.window.add_element(self.toolbar, False, 'south') - self.window.set_default_size(w, h) + self.window.set_default_size(self._w, self._h) self._full_screen_flag = False if is_interactive(): self.window.show() + def _add_figure_to_window(self, figure): + self._w += int(figure.bbox.width) + self._h += int(figure.bbox.height) + self.window.add_element(figure.canvas, True, 'center') + self.window.set_default_size(self._w, self._h) + figure.canvas.show() + + @property + def figure(self): + return self._figure + + @figure.setter + def figure(self, figure): + if self.key_press_handler_id: + self.figure.canvas.mpl_disconnect(self.key_press_handler_id) + + if not figure.canvas: + self._backend.FigureCanvas(figure, manager=self) + + if rcParams['toolbar'] != 'toolmanager': + self.key_press_handler_id = figure.canvas.mpl_connect( + 'key_press_event', self.key_press) + + if not self._figure: + self._add_figure_to_window(figure) + else: + self.window.replace_element(self._figure.canvas, figure.canvas) + + self._figure = figure + # TODO: emit message replacing figure to be captured by toolmanager + 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) + 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) + key_press_handler(event, self.figure.canvas, self.toolbar) def _destroy(self, event=None): # Callback from the when the window wants to destroy itself @@ -132,7 +162,8 @@ def destroy(self, *args): """Called to destroy this FigureManager, gets called by Gcf through event magic. """ - self.canvas.destroy() + if self.figure and self.figure.canvas: + self.figure.canvas.destroy() if self.toolbar: self.toolbar.destroy() self.window.destroy() @@ -173,7 +204,7 @@ def _get_toolbar(self): if rcParams['toolbar'] == 'toolmanager': toolbar = self._backend.Toolbar(self.toolmanager) elif rcParams['toolbar'] == 'toolbar2': - toolbar = self._backend.Toolbar2(self.canvas, self.window) + toolbar = self._backend.Toolbar2(self.figure.canvas, self.window) else: toolbar = None return toolbar @@ -181,7 +212,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.figure.canvas) else: toolmanager = None return toolmanager diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index aa30f61ff855..8928007dc247 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -415,6 +415,7 @@ def __init__(self, title): # better way is - JDH verbose.report('Could not load matplotlib icon: %s' % sys.exc_info()[1]) + self._boxes = {} self._layout = {} self._setup_box('_outer', Gtk.Orientation.VERTICAL, False, None) self._setup_box('north', Gtk.Orientation.VERTICAL, False, '_outer') @@ -437,22 +438,34 @@ def _setup_box(self, name, orientation, grow, parent): self._layout[name].show() def add_element(self, element, expand, place): - element.show() + box = Gtk.Box() 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, expand, 0) + self._layout[place].pack_start(box, expand, expand, 0) self._layout[place].pack_start(separator, False, False, 0) elif place in ['south', 'east']: - self._layout[place].pack_end(element, expand, expand, 0) + self._layout[place].pack_end(box, expand, expand, 0) self._layout[place].pack_end(separator, False, False, 0) else: raise KeyError('Unknown value for place, %s' % place) - size_request = element.size_request() + size_request = box.size_request() + box.pack_start(element, expand=True, fill=True, padding=0) + element.show() + box.show() + self._boxes[element] = box return size_request.height + separator.size_request().height + def replace_element(self, original, new): + box = self._boxes.pop(original) + box.remove(original) + box.pack_start(new, expand=True, fill=True, padding=0) + new.show() + box.show() + self._boxes[new] = box + def set_default_size(self, width, height): Gtk.Window.set_default_size(self, width, height)