From cd30d5bc53492950ae21275b610f0b570043af47 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sun, 22 Feb 2015 00:14:36 +0100 Subject: [PATCH 01/10] 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 edcc81f95f89..084c12d74ff9 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -40,6 +40,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 @@ -2536,6 +2537,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 62622715322a..853bf4da137c 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 from matplotlib.cbook import is_string_like, is_writable_file_like @@ -374,6 +374,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): """ @@ -887,3 +966,4 @@ def error_msg_gtk(msg, parent=None): 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 526c2b4e15801811aef3a728b99384741ddae2c4 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Mon, 23 Feb 2015 00:22:14 +0100 Subject: [PATCH 02/10] 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(-) create mode 100644 lib/matplotlib/backend_managers.py diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index 2ead0e88cac8..3f3b00827b9b 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): """ @@ -134,7 +189,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 084c12d74ff9..a165ad35ec22 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -40,7 +40,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 @@ -133,6 +132,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. @@ -2462,7 +2488,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') @@ -2537,20 +2566,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): """ @@ -2591,112 +2616,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 new file mode 100644 index 000000000000..b90a5dd4b619 --- /dev/null +++ b/lib/matplotlib/backend_managers.py @@ -0,0 +1,133 @@ +from matplotlib import is_interactive +from matplotlib import rcParams +from matplotlib.figure import Figure +from matplotlib import cbook +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 + + +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 853bf4da137c..1a36ccd93362 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 from matplotlib.cbook import is_string_like, is_writable_file_like @@ -67,6 +68,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: @@ -420,26 +432,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 c0f66c9f27cd..fbc020378a15 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -534,6 +534,17 @@ def process(self, s, *args, **kwargs): proxy(*args, **kwargs) +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 a13d87ee2ba0..00404f4cc026 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 61a709039d7e270771bf7845f9e7c43a120f6f9f Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Wed, 25 Feb 2015 01:42:44 +0100 Subject: [PATCH 03/10] 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 986870c78140..49e8c1769f31 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1358,11 +1358,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 @@ -1377,7 +1384,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 798a62ada20128d50b180a9370eb40f5faf2c2c2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Thu, 26 Feb 2015 17:54:59 +0100 Subject: [PATCH 04/10] 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 d4e8c837f78cb684d9cecf579b6c30fc6ce58f57 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 15:02:51 +0100 Subject: [PATCH 05/10] 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 a165ad35ec22..f79f8cac0a39 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -38,6 +38,7 @@ import warnings import time import io +import weakref import numpy as np import matplotlib.cbook as cbook @@ -1691,7 +1692,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 @@ -1705,6 +1706,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): """ @@ -2449,6 +2451,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): """ @@ -2592,6 +2607,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 b90a5dd4b619..a182ef0b636e 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -15,25 +15,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) @@ -49,8 +49,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 @@ -96,38 +94,37 @@ 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 + 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 1a36ccd93362..b2093182a758 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -193,9 +193,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 49e8c1769f31..ec3024d4a900 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1358,7 +1358,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 @@ -1369,7 +1369,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 b4426551dddbc81de37ab72e595541c80349b3cb Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 16:09:23 +0100 Subject: [PATCH 06/10] 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 a182ef0b636e..f0f7d9e302eb 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -23,7 +23,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 c36a253a68ea9f282b5b639d94c268457f5fae0e Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 17:37:53 +0100 Subject: [PATCH 07/10] 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 f79f8cac0a39..d3bbc843cb83 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2628,7 +2628,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 f0f7d9e302eb..0bfb808d5cd0 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -31,12 +31,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 b2093182a758..38ef303fe20c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -409,15 +409,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 e64aaf21c251e61691bf60663c130fe5674ae3ac Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Fri, 27 Feb 2015 19:29:36 +0100 Subject: [PATCH 08/10] 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 0bfb808d5cd0..b248679965ce 100644 --- a/lib/matplotlib/backend_managers.py +++ b/lib/matplotlib/backend_managers.py @@ -63,10 +63,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 4df69232b3211063c63261495a8be5ce516b35e2 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 28 Feb 2015 12:04:29 +0100 Subject: [PATCH 09/10] 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 38ef303fe20c..647344322b7e 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -386,14 +386,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 @@ -406,11 +406,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() @@ -424,34 +424,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): @@ -966,4 +959,3 @@ def error_msg_gtk(msg, parent=None): FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 -Window = WindowGTK3 From df541e3405627f4952b6b4102b3d5ab0462893d3 Mon Sep 17 00:00:00 2001 From: OceanWolf Date: Sat, 28 Feb 2015 20:34:41 +0100 Subject: [PATCH 10/10] Wx Refactor, this fixes Segmentation Fault bug. --- lib/matplotlib/backends/backend_wx.py | 97 ++++++++++++++++++++++-- lib/matplotlib/backends/backend_wxagg.py | 13 ++-- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 652b47bcae16..b51fecc2273d 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -127,10 +127,10 @@ def bind(actor,event,action,id=None): import matplotlib from matplotlib import verbose -from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ - FigureCanvasBase, FigureManagerBase, NavigationToolbar2, \ - cursors, TimerBase -from matplotlib.backend_bases import ShowBase +from matplotlib.backend_bases import (RendererBase, GraphicsContextBase, + FigureCanvasBase, FigureManagerBase, NavigationToolbar2, + cursors, TimerBase, WindowBase) +from matplotlib.backend_bases import MainLoopBase, ShowBase from matplotlib.backend_bases import _has_pil from matplotlib._pylab_helpers import Gcf @@ -701,7 +701,7 @@ class FigureCanvasWx(FigureCanvasBase, wx.Panel): wx.WXK_NUMPAD_DELETE : 'delete', } - def __init__(self, parent, id, figure): + def __init__(self, figure, id=-1, parent=None, manager=None): """ Initialise a FigureWx instance. @@ -710,8 +710,14 @@ def __init__(self, parent, id, figure): EVT_SIZE (Resize event) EVT_PAINT (Paint event) """ - - FigureCanvasBase.__init__(self, figure) + if manager is not None: + parent = manager.window + elif isinstance(parent, Figure) or isinstance(figure, wx.Window): + # User calls bad API, a deprecation warning should get issued here. + temp = parent + parent = figure + figure = temp + FigureCanvasBase.__init__(self, figure, manager) # Set preferred window size hint - helps the sizer (if one is # connected) l,b,w,h = figure.bbox.bounds @@ -1211,6 +1217,10 @@ def _onEnter(self, evt): """Mouse has entered the window.""" FigureCanvasBase.enter_notify_event(self, guiEvent = evt) + def destroy(self, *args, **kwargs): + pass # Destroy throws a sefault atm on ctrl+w if we destroy this. + #self.Destroy(*args, **kwargs) + ######################################################################## # @@ -1246,6 +1256,27 @@ def draw_if_interactive(): if figManager is not None: figManager.canvas.draw_idle() + +class MainLoop(MainLoopBase): + def __init__(self): + MainLoopBase.__init__(self) + wxapp = wx.GetApp() + if wxapp is None: + wxapp = wx.App(False) + wxapp.SetExitOnFrameDelete(True) + self._app = wxapp + + def begin(self): + needmain = not wx.App.IsMainLoopRunning() + if needmain: + wxapp = wx.GetApp() + if wxapp is not None: + wxapp.MainLoop() + + def end(self): + if hasattr(self, '_app'): + del self._app + class Show(ShowBase): def mainloop(self): needmain = not wx.App.IsMainLoopRunning() @@ -1283,6 +1314,52 @@ def new_figure_manager_given_figure(num, figure): return figmgr +class Window(WindowBase, wx.Frame): + def __init__(self, title): + # On non-Windows platform, explicitly set the position - fix + # positioning bug on some Linux platforms + if wx.Platform == '__WXMSW__': + pos = wx.DefaultPosition + else: + pos = wx.Point(20,20) + + WindowBase.__init__(self, title) + wx.Frame.__init__(self, parent=None, id=-1, pos=pos, title=title) + + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.sizer) + + bind(self, wx.EVT_CLOSE, self.destroy_event) + + def add_element_to_window(self, element, expand, fill, pad, side='bottom'): + element.Fit() + self.sizer.Add(element, int(expand), wx.EXPAND) + w, h = element.GetSizeTuple() + if isinstance(element, NavigationToolbar2): + statbar = StatusBarWx(self) + self.SetStatusBar(statbar) + element.set_status_bar(statbar) + wsbar, hsbar = statbar.GetSizeTuple() + h += hsbar + return h + + def show(self): + self.Show() + + def destroy(self, *args, **kwargs): + self.Destroy(*args, **kwargs) + + def get_window_title(self): + return self.GetTitle() + + def set_window_title(self, title): + self.SetTitle(title) + + def resize(self, width, height): + #Set the canvas size in pixels + self.SetSize((width, height)) + + class FigureFrameWx(wx.Frame): def __init__(self, num, fig): # On non-Windows platform, explicitly set the position - fix @@ -1593,7 +1670,7 @@ def __init__(self, targetfig): class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar): - def __init__(self, canvas): + def __init__(self, canvas, window=None): wx.ToolBar.__init__(self, canvas.GetParent(), -1) NavigationToolbar2.__init__(self, canvas) self.canvas = canvas @@ -1753,6 +1830,9 @@ def set_history_buttons(self): self.EnableTool(self.wx_ids['Back'], can_backward) self.EnableTool(self.wx_ids['Forward'], can_forward) + def destroy(self): + self.Destroy() + class StatusBarWx(wx.StatusBar): """ @@ -1863,3 +1943,4 @@ def OnPrintPage(self, page): FigureCanvas = FigureCanvasWx FigureManager = FigureManagerWx Toolbar = NavigationToolbar2Wx +Toolbar2 = NavigationToolbar2Wx diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index 5dd01030560b..f386d6cb6115 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -8,9 +8,9 @@ from .backend_agg import FigureCanvasAgg from . import backend_wx # already uses wxversion.ensureMinimal('2.8') -from .backend_wx import FigureManagerWx, FigureCanvasWx, \ - FigureFrameWx, DEBUG_MSG, NavigationToolbar2Wx, error_msg_wx, \ - draw_if_interactive, show, Toolbar, backend_version +from .backend_wx import (FigureManagerWx, FigureCanvasWx, Window, MainLoop, + FigureFrameWx, DEBUG_MSG, NavigationToolbar2Wx, error_msg_wx, + draw_if_interactive, show, Toolbar, backend_version) import wx @@ -105,7 +105,7 @@ def new_figure_manager(num, *args, **kwargs): # in order to expose the Figure constructor to the pylab # interface we need to create the figure here DEBUG_MSG("new_figure_manager()", 3, None) - backend_wx._create_wx_app() + backend_wx._create_wx_app() # TODO looks odd FigureClass = kwargs.pop('FigureClass', Figure) fig = FigureClass(*args, **kwargs) @@ -116,8 +116,8 @@ def new_figure_manager_given_figure(num, figure): """ Create a new figure manager instance for the given figure. """ - frame = FigureFrameWxAgg(num, figure) - figmgr = frame.get_figure_manager() + frame = FigureFrameWxAgg(num, figure) # TODO looks odd + figmgr = frame.get_figure_manager() # TODO again if matplotlib.is_interactive(): figmgr.frame.Show() return figmgr @@ -192,3 +192,4 @@ def _WX28_clipped_agg_as_bitmap(agg, bbox): FigureCanvas = FigureCanvasWxAgg FigureManager = FigureManagerWx +Toolbar2 = NavigationToolbar2WxAgg