From 01b0e784d4fffc297cc85704ba1f7fdaa4e75e9e Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 12 Sep 2013 15:57:48 -0400 Subject: [PATCH 01/20] refactoring FigureManagerBase to include include self in all calls to parent --- lib/matplotlib/backends/backend_gtk3.py | 233 ++++++++++++++++++- lib/matplotlib/backends/backend_gtk3cairo.py | 6 +- lib/matplotlib/rcsetup.py | 1 + matplotlibrc.template | 5 + 4 files changed, 241 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 740d8bb0e872..5c2189d8c061 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -361,7 +361,7 @@ def stop_event_loop(self): FigureCanvas = FigureCanvasGTK3 -class FigureManagerGTK3(FigureManagerBase): +class SingleFigureManagerGTK3(FigureManagerBase): """ Public attributes @@ -476,6 +476,237 @@ def resize(self, width, height): self.window.resize(width, height) +class FigureManagerBase2(FigureManagerBase): + def __init__(self, parent, canvas, num): + FigureManagerBase.__init__(self, canvas, num) + self.parent = parent + + def show(self): + try: + self.parent.show(self) + except AttributeError: + pass + + def destroy(self): + try: + self.parent.destroy(self) + except AttributeError: + pass + + def full_screen_toggle(self): + try: + self.parent.full_screen_toggle(self) + except AttributeError: + pass + + def resize(self, w, h): + try: + self.parent.resize(self, w, h) + except AttributeError: + pass + +# def key_press(self, event): +# key_press_handler(event, self.canvas, self.canvas.toolbar) + + def show_popup(self, msg): + try: + self.parent.show_popup(self, msg) + except AttributeError: + pass + + def get_window_title(self): + try: + self.parent.get_window_title(self) + except AttributeError: + return 'image' + + def set_window_title(self, title): + try: + self.parent.set_window_title(self.title) + except AttributeError: + pass + +class TabbedFigureManagerGTK3(FigureManagerBase): + _canvas = {} + _labels = {} + _w_min = 0 + _h_min = 0 + _managers = {} + def __init__(self, *args): + print ('calling init', args) + + self.window = Gtk.Window() + self.set_window_title("Figuremanager") + 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.vbox.show() + + self.notebook = Gtk.Notebook() + + self.notebook.set_scrollable(True) + vp = Gtk.Viewport() + vp.add(self.notebook) + self.vbox.pack_start(vp, True, True, 0) +# self.vbox.pack_start(self.notebook, True, True, 0) + self.window.add(self.vbox) + + self.notebook.connect('switch-page', self._on_switch_page) + + + + + self.toolbar = self._get_toolbar() + + if self.toolbar is not None: + self.toolbar.show() + self.vbox.pack_end(self.toolbar, False, False, 0) + + size_request = self.window.size_request() + self._h_def = size_request.height + self._w_def = size_request.width + + + def destroy_window(*args): + for num in self._canvas.keys(): + Gcf.destroy(num) + self.window.connect("destroy", destroy_window) + self.window.connect("delete_event", destroy_window) + + self.vbox.show_all() + + if matplotlib.is_interactive(): + self.window.show() + + + + def destroy(self, *args): + if _debug: print('FigureManagerGTK3.%s' % fn_name()) + if not self._canvas: + self.vbox.destroy() + self.window.destroy() + if self.toolbar: + self.toolbar.destroy() +# + if Gcf.get_num_fig_managers() == 0 and \ + not matplotlib.is_interactive() and \ + Gtk.main_level() >= 1: + Gtk.main_quit() + + def _on_remove_canvas(self, btn, num): + + canvas = self._canvas[num] + id_ = self.notebook.page_num(canvas) + if id_ > -1: + del self._canvas[num] + del self._labels[num] + self.notebook.remove_page(id_) + Gcf.destroy(num) + + def set_window_title(self, title): + pass + + def _on_switch_page(self, *args): + pass + + def _get_toolbar(self): + # must be inited after the window, drawingArea and figure + # attrs are set + if rcParams['toolbar'] == 'toolbar2': +# toolbar = NavigationToolbar2GTK3 (canvas, self.window) + toolbar = None + else: + toolbar = None + return toolbar + + def _add_canvas(self, canvas, num): + #I don't like to call init of the base several times, + #I could just set the variables that it needs and never call init.... + FigureManagerBase.__init__(self, canvas, num) + + manager = FigureManagerBase2(self, canvas, num) + + + + title = 'Fig %d' % num + box = Gtk.Box() + box.set_orientation(Gtk.Orientation.HORIZONTAL) + box.set_spacing(5) + + label = Gtk.Label(title) + box.pack_start(label, True, True, 0) + + # close button + button = Gtk.Button() + + button.set_relief(Gtk.ReliefStyle.NONE) + button.set_focus_on_click(False) + button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) + + + + box.pack_end(button, False, False, 0) + + box.show_all() + self.notebook.append_page(canvas, box) + canvas.show() + + button.connect("clicked", self._on_remove_canvas, num) + + self._canvas[num] = canvas + self._labels[num] = label + + + # calculate size for window + w = int (self.canvas.figure.bbox.width) + h = int (self.canvas.figure.bbox.height) + + if w > self._w_min: + self._w_min = w + if h > self._h_min: + self._h_min = h + + + self.window.set_default_size (self._w_def + self._w_min, self._h_def + self._h_min) + + if self.toolbar: + self.toolbar.add_canvas(canvas) + + 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 show(self): + # show the figure window + self.window.show() + + + def __call__(self, canvas, num): + self._add_canvas(canvas, num) + return self + + +if rcParams['backend.gtk3.tabbed']: + FigureManagerGTK3 = TabbedFigureManagerGTK3() +else: + FigureManagerGTK3 = SingleFigureManagerGTK3 + + + class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): def __init__(self, canvas, window): self.win = window diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 4421cd0e2fd4..53a28689491b 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -40,9 +40,9 @@ def on_draw_event(self, widget, ctx): return False # finish event propagation? -class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): - pass - +#class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): +# pass +FigureManagerGTK3Cairo = backend_gtk3.FigureManagerGTK3 def new_figure_manager(num, *args, **kwargs): """ diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 60d6e3c12e13..70c3a1438e02 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -478,6 +478,7 @@ def __call__(self, s): # present 'backend_fallback': [True, validate_bool], # agg is certainly present 'backend.qt4': ['PyQt4', validate_qt4], + 'backend.gtk3.tabbed': [False, validate_bool], 'webagg.port': [8988, validate_int], 'webagg.open_in_browser': [True, validate_bool], 'webagg.port_retries': [50, validate_int], diff --git a/matplotlibrc.template b/matplotlibrc.template index 44f94fdfd95d..2595709719a5 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -36,6 +36,11 @@ backend : %(backend)s # the underlying Qt4 toolkit. #backend.qt4 : PyQt4 # PyQt4 | PySide +# If you are using one of the GTK3 backends (GTK3Agg or GTK3Cairo) +# you can set to use only one window with tabbed figures instead of +# multiple windows one for each figure +#backend.gtk3.tabbed : True + # Note that this can be overridden by the environment variable # QT_API used by Enthought Tool Suite (ETS); valid values are # "pyqt" and "pyside". The "pyqt" setting has the side effect of From ec262ad9ec74c3d15b68c9bdf17862dff067c502 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Fri, 13 Sep 2013 18:32:14 -0400 Subject: [PATCH 02/20] Modified to use container for figure managers and for toolbars --- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backends/backend_gtk3.py | 460 +++++++++++++++---- lib/matplotlib/backends/backend_gtk3cairo.py | 7 +- 3 files changed, 365 insertions(+), 104 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 39489fb1ae5b..8ef23af3e755 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2150,7 +2150,7 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', bbox_inches = kwargs.pop("bbox_inches", None) if bbox_inches is None: bbox_inches = rcParams['savefig.bbox'] - + if bbox_inches: # call adjust_bbox to save only the given area if bbox_inches == "tight": diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 5c2189d8c061..420f57dc2cb0 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -2,6 +2,7 @@ unicode_literals) import six +import weakref import os, sys def fn_name(): return sys._getframe(1).f_code.co_name @@ -44,8 +45,8 @@ def fn_name(): return sys._getframe(1).f_code.co_name backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) +#_debug = False _debug = False -#_debug = True # the true dots per inch on the screen; should be display dependent # see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi @@ -361,7 +362,7 @@ def stop_event_loop(self): FigureCanvas = FigureCanvasGTK3 -class SingleFigureManagerGTK3(FigureManagerBase): +class FigureManagerGTK3(FigureManagerBase): """ Public attributes @@ -476,32 +477,44 @@ def resize(self, width, height): self.window.resize(width, height) -class FigureManagerBase2(FigureManagerBase): - def __init__(self, parent, canvas, num): +class TabbedFigureManagerGTK3(FigureManagerBase): + parent = None + + @classmethod + def initialize(cls): + if cls.parent is None: + cls.parent = TabbedFigureContainerGTK3() + + + def __init__(self, canvas, num): + self.initialize() FigureManagerBase.__init__(self, canvas, num) - self.parent = parent + self.toolbar = self.parent.get_manager_toolbar(self) + self.parent.add_figure_manager(self) + self.canvas.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() + canvas.figure.add_axobserver(notify_axes_change) + def show(self): try: - self.parent.show(self) + self.parent.show_manager(self) except AttributeError: pass def destroy(self): - try: - self.parent.destroy(self) - except AttributeError: - pass - - def full_screen_toggle(self): - try: - self.parent.full_screen_toggle(self) - except AttributeError: - pass + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) + + self.canvas.destroy() + self.parent.remove_manager(self) def resize(self, w, h): try: - self.parent.resize(self, w, h) + self.parent.resize_manager(self, w, h) except AttributeError: pass @@ -515,28 +528,22 @@ def show_popup(self, msg): pass def get_window_title(self): - try: - self.parent.get_window_title(self) - except AttributeError: - return 'image' + return self.parent.get_manager_title(self) def set_window_title(self, title): - try: - self.parent.set_window_title(self.title) - except AttributeError: - pass + self.parent.set_manager_title(self, title) -class TabbedFigureManagerGTK3(FigureManagerBase): - _canvas = {} + +class TabbedFigureContainerGTK3(object): + _managers = [] _labels = {} _w_min = 0 _h_min = 0 - _managers = {} + def __init__(self, *args): - print ('calling init', args) - + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.window = Gtk.Window() - self.set_window_title("Figuremanager") + self.window.set_title("Figuremanager") try: self.window.set_icon_from_file(window_icon) except (SystemExit, KeyboardInterrupt): @@ -556,15 +563,11 @@ def __init__(self, *args): self.notebook = Gtk.Notebook() self.notebook.set_scrollable(True) - vp = Gtk.Viewport() - vp.add(self.notebook) - self.vbox.pack_start(vp, True, True, 0) -# self.vbox.pack_start(self.notebook, True, True, 0) - self.window.add(self.vbox) self.notebook.connect('switch-page', self._on_switch_page) - - + + self.vbox.pack_start(self.notebook, True, True, 0) + self.window.add(self.vbox) self.toolbar = self._get_toolbar() @@ -579,7 +582,8 @@ def __init__(self, *args): def destroy_window(*args): - for num in self._canvas.keys(): + nums = [manager.num for manager in self._managers] + for num in nums: Gcf.destroy(num) self.window.connect("destroy", destroy_window) self.window.connect("delete_event", destroy_window) @@ -588,56 +592,67 @@ def destroy_window(*args): if matplotlib.is_interactive(): self.window.show() - - + + def _on_switch_page(self, notebook, pointer, num): + canvas = self.notebook.get_nth_page(num) + self.toolbar.switch_toolbar(canvas.toolbar) def destroy(self, *args): - if _debug: print('FigureManagerGTK3.%s' % fn_name()) - if not self._canvas: - self.vbox.destroy() - self.window.destroy() - if self.toolbar: - self.toolbar.destroy() + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) + + self.vbox.destroy() + self.window.destroy() + if self.toolbar: + self.toolbar.destroy() # if Gcf.get_num_fig_managers() == 0 and \ not matplotlib.is_interactive() and \ Gtk.main_level() >= 1: Gtk.main_quit() - def _on_remove_canvas(self, btn, num): - - canvas = self._canvas[num] + def remove_manager(self, manager): + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) + if manager not in self._managers: + raise AttributeError('This container does not control the given figure manager') + canvas = manager.canvas id_ = self.notebook.page_num(canvas) if id_ > -1: - del self._canvas[num] - del self._labels[num] + del self._labels[manager.num] self.notebook.remove_page(id_) - Gcf.destroy(num) + self._managers.remove(manager) - def set_window_title(self, title): - pass - - def _on_switch_page(self, *args): - pass + if self.notebook.get_n_pages() == 0: + self.destroy() + + def set_manager_title(self, manager, title): + self._labels[manager.num].set_text(title) + + def get_manager_title(self, manager): + self._labels[manager.num].get_text() def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set if rcParams['toolbar'] == 'toolbar2': -# toolbar = NavigationToolbar2GTK3 (canvas, self.window) - toolbar = None + toolbar = TabbedContainerNavigationToolbar2GTK3(self.window) else: toolbar = None return toolbar - def _add_canvas(self, canvas, num): - #I don't like to call init of the base several times, - #I could just set the variables that it needs and never call init.... - FigureManagerBase.__init__(self, canvas, num) - - manager = FigureManagerBase2(self, canvas, num) - - + + def get_manager_toolbar(self, manager): + if self.toolbar is None: + return None + toolbar = TabbedNavigationToolbar(manager.canvas, self.toolbar) +# self.toolbar.add_manager(manager) + return toolbar + + def add_figure_manager(self, figure_manager): + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) + if figure_manager in self._managers: + raise AttributeError ('Impossible to add two times the same figure manager') + canvas = figure_manager.canvas + num = figure_manager.num title = 'Fig %d' % num box = Gtk.Box() @@ -645,6 +660,9 @@ def _add_canvas(self, canvas, num): box.set_spacing(5) label = Gtk.Label(title) + self._labels[num] = label + self._managers.append(figure_manager) + box.pack_start(label, True, True, 0) # close button @@ -652,60 +670,302 @@ def _add_canvas(self, canvas, num): button.set_relief(Gtk.ReliefStyle.NONE) button.set_focus_on_click(False) - button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) - - - - box.pack_end(button, False, False, 0) - + button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) + box.pack_end(button, False, False, 0) box.show_all() self.notebook.append_page(canvas, box) canvas.show() - button.connect("clicked", self._on_remove_canvas, num) - - self._canvas[num] = canvas - self._labels[num] = label - + def _remove(btn): + Gcf.destroy(num) + button.connect("clicked", _remove) + # calculate size for window - w = int (self.canvas.figure.bbox.width) - h = int (self.canvas.figure.bbox.height) + w = int (canvas.figure.bbox.width) + h = int (canvas.figure.bbox.height) if w > self._w_min: self._w_min = w if h > self._h_min: - self._h_min = h - + self._h_min = h self.window.set_default_size (self._w_def + self._w_min, self._h_def + self._h_min) - if self.toolbar: - self.toolbar.add_canvas(canvas) + canvas.grab_focus() - 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 show_manager(self, manager): + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) + self.show() + canvas = manager.canvas + id_ = self.notebook.page_num(canvas) + self.notebook.set_current_page(id_) def show(self): - # show the figure window - self.window.show() + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) + self.window.show_all() - def __call__(self, canvas, num): - self._add_canvas(canvas, num) - return self - if rcParams['backend.gtk3.tabbed']: - FigureManagerGTK3 = TabbedFigureManagerGTK3() -else: - FigureManagerGTK3 = SingleFigureManagerGTK3 + FigureManagerGTK3 = TabbedFigureManagerGTK3 + + +class TabbedNavigationToolbar(NavigationToolbar2): + def __init__(self, canvas, parent): + self.parent = parent + NavigationToolbar2.__init__(self, canvas) + + def _init_toolbar(self): + self.parent.add_toolbar(self) + self.ctx = None + def set_message(self, s): + self.parent.message.set_label(s) + def set_cursor(self, cursor): + self.canvas.get_property("window").set_cursor(cursord[cursor]) + #self.canvas.set_cursor(cursord[cursor]) + + def release(self, event): + try: del self._pixmapBack + except AttributeError: pass + + def dynamic_update(self): + # legacy method; new method is canvas.draw_idle + self.canvas.draw_idle() + + def draw_rubberband(self, event, x0, y0, x1, y1): + 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' + self.ctx = self.canvas.get_property("window").cairo_create() + + # todo: instead of redrawing the entire figure, copy the part of + # the figure that was covered by the previous rubberband rectangle + self.canvas.draw() + + height = self.canvas.figure.bbox.height + y1 = height - y1 + y0 = height - y0 + w = abs(x1 - x0) + h = abs(y1 - y0) + rect = [int(val) for val in (min(x0,x1), min(y0, y1), w, h)] + + self.ctx.new_path() + self.ctx.set_line_width(0.5) + self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) + self.ctx.set_source_rgb(0, 0, 0) + self.ctx.stroke() + + +class TabbedContainerNavigationToolbar2GTK3(Gtk.Toolbar): + _toolbars = [] + toolitems = list(NavigationToolbar2.toolitems) + extra_items = [('SaveAll', 'Save all figures', 'filesave', 'save_all_figures'), ] + #It is more clear to have toggle buttons for these two options + _toggle = {'Pan': None, 'Zoom': None} + _destroy_on_switch = [] + + def __init__(self, window): + self.win = window + Gtk.Toolbar.__init__(self) + self.toolitems.extend(self.extra_items) + self._add_buttons() + self._current = None + + def _add_buttons(self): + self.set_style(Gtk.ToolbarStyle.ICONS) + basedir = os.path.join(rcParams['datapath'], 'images') + for text, tooltip_text, image_file, callback in self.toolitems: + if text is None: + self.insert(Gtk.SeparatorToolItem(), -1) + continue + fname = os.path.join(basedir, image_file + '.png') + image = Gtk.Image() + image.set_from_file(fname) + if text in self._toggle: + tbutton = Gtk.ToggleToolButton() + self._toggle[text] = tbutton + tbutton.connect('toggled', self._toggled) + else: + tbutton = Gtk.ToolButton() + #attach to _toggled so it untoggles the toggled button + tbutton.connect('clicked', self._toggled) + tbutton.set_label(text) + tbutton.set_icon_widget(image) + self.insert(tbutton, -1) + + tbutton.connect('clicked', getattr(self, callback)) + + tbutton.set_tooltip_text(tooltip_text) + + toolitem = Gtk.SeparatorToolItem() + self.insert(toolitem, -1) + toolitem.set_draw(False) + toolitem.set_expand(True) + + toolitem = Gtk.ToolItem() + self.insert(toolitem, -1) + self.message = Gtk.Label() + toolitem.add(self.message) + + self.show_all() + + def switch_toolbar(self, toolbar): + if toolbar not in self._toolbars: + raise AttributeError('This container does not control the given toolbar') + + print(self._destroy_on_switch) + + for w in self._destroy_on_switch: + o = w() + if o is None: + continue + + try: + o.destroy() + except: + pass + self._destroy_on_switch = [] + + #For these two actions we have to unselect and reselect + d = {'PAN': 'pan', 'ZOOM': 'zoom'} + action = d.get(self._current._active, False) + if action: + getattr(self._current, action)() + getattr(toolbar, action)() + self._current = toolbar + + + def home(self, *args): + self._current.home(*args) + + def back(self, *args): + self._current.back(*args) + + def forward(self, *args): + self._current.forward(*args) + + def pan(self, *args): + self._current.pan(*args) + + def zoom(self, *args): + self._current.zoom(*args) + + def save_all_figures(self, *args): + pass + + def add_toolbar(self, toolbar): + print ('adding toolbar', toolbar) + self._toolbars.append(toolbar) + self._current = toolbar + + def _toggled(self, btn): + #Untoggle other toggled buttons + for i in self._toggle.values(): + if i is not btn: + if i.get_active(): + i.handler_block_by_func(self._toggled) + i.set_active(False) + i.handler_unblock_by_func(self._toggled) + + def get_filechooser(self, title, current_name): + fc = FileChooserDialog( + title=title, + parent=self.win, + path=os.path.expanduser(rcParams.get('savefig.directory', '')), + filetypes=self._current.canvas.get_supported_filetypes(), + default_filetype=self._current.canvas.get_default_filetype()) + fc.set_current_name(current_name) + return fc + + def _do_save_figure(self, canvas, fname, format): + if fname: + startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) + if startpath == '': + # explicitly missing key or empty str signals to use cwd + rcParams['savefig.directory'] = startpath + else: + # save dir for next time + rcParams['savefig.directory'] = os.path.dirname(six.text_type(fname)) + + try: + canvas.print_figure(fname, format=format) + except Exception as e: + error_msg_gtk(str(e), parent=self) + + + def save_figure(self, *args): + current_name = self._current.canvas.get_default_filename() + chooser = self.get_filechooser('Save the figure', current_name) + + #We want to make sure the user is not confused + #if changes tabs while saving dialog is open + self._destroy_on_switch.append(weakref.ref(chooser)) + fname, format = chooser.get_filename_from_user() + chooser.destroy() + self._do_save_figure(self._current.canvas, fname, format) + + + + + def save_all_figures(self, *args): + start = self._current.canvas.get_default_filename() + fname_end = '.' + self._current.canvas.get_default_filetype() + current_name = start[:-len(fname_end)] + chooser = self.get_filechooser('Save all figures', current_name) + + #We want to make sure the user is not confused + #if changes tabs while saving dialog is open + self._destroy_on_switch.append(weakref.ref(chooser)) + fname, format = chooser.get_filename_from_user() + chooser.destroy() + + if not fname: + return + + for canvas in [t.canvas for t in self._toolbars]: + #surface.write_to_png (fobj) in backend_cairo.py + #doesn't work correclty with other kind of strings + fn = str('%s_%.3d.%s' % (fname, canvas.manager.num, format)) + self._do_save_figure(canvas, fn, format) + + def configure_subplots(self, button): + toolfig = Figure(figsize=(6,3)) + canvas = self._get_canvas(toolfig) + toolfig.subplots_adjust(top=0.9) + tool = SubplotTool(self._current.canvas.figure, toolfig) + + w = int (toolfig.bbox.width) + h = int (toolfig.bbox.height) + + window = Gtk.Window() + try: + window.set_icon_from_file(window_icon) + except (SystemExit, KeyboardInterrupt): + # re-raise exit type Exceptions + raise + except: + # we presumably already logged a message on the + # failure of the main plot, don't keep reporting + pass + window.set_title("Subplot Configuration Tool") + window.set_default_size(w, h) + vbox = Gtk.Box() + vbox.set_property("orientation", Gtk.Orientation.VERTICAL) + window.add(vbox) + vbox.show() + + canvas.show() + vbox.pack_start(canvas, True, True, 0) + window.show() + + self._destroy_on_switch.append(weakref.ref(window)) + print (self._destroy_on_switch) + + def _get_canvas(self, fig): + return self._current.canvas.__class__(fig) + + class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): def __init__(self, canvas, window): diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 53a28689491b..80ea68bf6616 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -40,14 +40,15 @@ def on_draw_event(self, widget, ctx): return False # finish event propagation? -#class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): -# pass -FigureManagerGTK3Cairo = backend_gtk3.FigureManagerGTK3 +class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): + pass + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ + FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) From 4e1d8c456e8891318affd764ee51b4694601c7c8 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Sat, 14 Sep 2013 21:32:05 -0400 Subject: [PATCH 03/20] Adding standaone savefiguredialog --- examples/pylab_examples/multiple_figs_demo.py | 17 +- lib/matplotlib/backends/backend_gtk3.py | 475 +++++++++--------- 2 files changed, 249 insertions(+), 243 deletions(-) diff --git a/examples/pylab_examples/multiple_figs_demo.py b/examples/pylab_examples/multiple_figs_demo.py index ebd288021a52..ad71f863d15f 100644 --- a/examples/pylab_examples/multiple_figs_demo.py +++ b/examples/pylab_examples/multiple_figs_demo.py @@ -1,24 +1,29 @@ #!/usr/bin/env python # Working with multiple figure windows and subplots + +import matplotlib +matplotlib.use('gtk3agg') +# from matplotlib.backends.backend_gtk3 import SaveFiguresDialogGTK3 +matplotlib.rcParams['backend.gtk3.tabbed'] = True from pylab import * t = arange(0.0, 2.0, 0.01) -s1 = sin(2*pi*t) -s2 = sin(4*pi*t) +s1 = sin(2 * pi * t) +s2 = sin(4 * pi * t) figure(1) subplot(211) -plot(t,s1) +plot(t, s1) subplot(212) -plot(t,2*s1) +plot(t, 2 * s1) figure(2) -plot(t,s2) +plot(t, s2) # now switch back to figure 1 and make some changes figure(1) subplot(211) -plot(t,s2, 'gs') +plot(t, s2, 'gs') setp(gca(), 'xticklabels', []) figure(1) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 420f57dc2cb0..9bbde57ab6b7 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -45,7 +45,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) -#_debug = False +# _debug = False _debug = False # the true dots per inch on the screen; should be display dependent @@ -64,7 +64,7 @@ def draw_if_interactive(): Is called after every pylab drawing command """ if matplotlib.is_interactive(): - figManager = Gcf.get_active() + figManager = Gcf.get_active() if figManager is not None: figManager.canvas.draw_idle() @@ -171,13 +171,13 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): # Setting this as a static constant prevents # this resulting expression from leaking - event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK | + event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.EXPOSURE_MASK | - Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | - Gdk.EventMask.ENTER_NOTIFY_MASK | - Gdk.EventMask.LEAVE_NOTIFY_MASK | + Gdk.EventMask.EXPOSURE_MASK | + Gdk.EventMask.KEY_PRESS_MASK | + Gdk.EventMask.KEY_RELEASE_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK | + Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK) @@ -186,20 +186,20 @@ def __init__(self, figure): FigureCanvasBase.__init__(self, figure) GObject.GObject.__init__(self) - self._idle_draw_id = 0 - self._need_redraw = True - self._lastCursor = None + self._idle_draw_id = 0 + self._need_redraw = True + self._lastCursor = None - self.connect('scroll_event', self.scroll_event) - self.connect('button_press_event', self.button_press_event) + self.connect('scroll_event', self.scroll_event) + self.connect('button_press_event', self.button_press_event) self.connect('button_release_event', self.button_release_event) - self.connect('configure_event', self.configure_event) - self.connect('draw', self.on_draw_event) - self.connect('key_press_event', self.key_press_event) - self.connect('key_release_event', self.key_release_event) - self.connect('motion_notify_event', self.motion_notify_event) - self.connect('leave_notify_event', self.leave_notify_event) - self.connect('enter_notify_event', self.enter_notify_event) + self.connect('configure_event', self.configure_event) + self.connect('draw', self.on_draw_event) + self.connect('key_press_event', self.key_press_event) + self.connect('key_release_event', self.key_release_event) + self.connect('motion_notify_event', self.motion_notify_event) + self.connect('leave_notify_event', self.leave_notify_event) + self.connect('enter_notify_event', self.enter_notify_event) self.set_events(self.__class__.event_mask) @@ -209,7 +209,7 @@ def __init__(self, figure): self._idle_event_id = GObject.idle_add(self.idle_event) def destroy(self): - #Gtk.DrawingArea.destroy(self) + # Gtk.DrawingArea.destroy(self) self.close_event() GObject.source_remove(self._idle_event_id) if self._idle_draw_id != 0: @@ -220,7 +220,7 @@ def scroll_event(self, widget, event): x = event.x # flipy so y=0 is bottom of canvas y = self.get_allocation().height - event.y - if event.direction==Gdk.ScrollDirection.UP: + if event.direction == Gdk.ScrollDirection.UP: step = 1 else: step = -1 @@ -300,11 +300,11 @@ def configure_event(self, widget, event): return w, h = event.width, event.height if w < 3 or h < 3: - return # empty fig + return # empty fig # resize the figure (in inches) dpi = self.figure.dpi - self.figure.set_size_inches (w/dpi, h/dpi) + self.figure.set_size_inches (w / dpi, h / dpi) self._need_redraw = True return False # finish event propagation? @@ -352,13 +352,13 @@ def flush_events(self): Gdk.flush() Gdk.threads_leave() - def start_event_loop(self,timeout): - FigureCanvasBase.start_event_loop_default(self,timeout) - start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__ + def start_event_loop(self, timeout): + FigureCanvasBase.start_event_loop_default(self, timeout) + start_event_loop.__doc__ = FigureCanvasBase.start_event_loop_default.__doc__ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ + stop_event_loop.__doc__ = FigureCanvasBase.stop_event_loop_default.__doc__ FigureCanvas = FigureCanvasGTK3 @@ -434,9 +434,9 @@ def destroy(self, *args): self.canvas.destroy() if self.toolbar: self.toolbar.destroy() - self.__dict__.clear() #Is this needed? Other backends don't have it. + self.__dict__.clear() # Is this needed? Other backends don't have it. - if Gcf.get_num_fig_managers()==0 and \ + if Gcf.get_num_fig_managers() == 0 and \ not matplotlib.is_interactive() and \ Gtk.main_level() >= 1: Gtk.main_quit() @@ -471,75 +471,74 @@ def set_window_title(self, 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) + # _, _, 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 TabbedFigureManagerGTK3(FigureManagerBase): +class TabbedFigureManagerBase(FigureManagerBase): parent = None - + @classmethod def initialize(cls): if cls.parent is None: cls.parent = TabbedFigureContainerGTK3() - def __init__(self, canvas, num): - self.initialize() + self.initialize() FigureManagerBase.__init__(self, canvas, num) self.toolbar = self.parent.get_manager_toolbar(self) - self.parent.add_figure_manager(self) + self.parent.add_manager(self) self.canvas.show() - - + self.window = self.parent.window + def notify_axes_change(fig): 'this will be called whenever the current axes is changed' if self.toolbar is not None: self.toolbar.update() canvas.figure.add_axobserver(notify_axes_change) - def show(self): - try: - self.parent.show_manager(self) - except AttributeError: - pass - + self.parent.show_manager(self) + def destroy(self): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) - self.canvas.destroy() self.parent.remove_manager(self) def resize(self, w, h): - try: - self.parent.resize_manager(self, w, h) - except AttributeError: - pass + self.parent.resize_manager(self, w, h) # def key_press(self, event): # key_press_handler(event, self.canvas, self.canvas.toolbar) def show_popup(self, msg): - try: - self.parent.show_popup(self, msg) - except AttributeError: - pass + self.parent.show_popup(self, msg) + # Here is a little bit counter intuitive, but for exising code + # one expects the set/get _window_title methods, to change the title + # of the window of the figure. + # Because we have many figures, the mainwindow is the window + # that includes (control) all the figures def get_window_title(self): return self.parent.get_manager_title(self) def set_window_title(self, title): self.parent.set_manager_title(self, title) + def get_mainwindow_title(self): + return self.parent.get_window_title() + + def set_mainwindow_title(self, title): + self.parent.set_window_title(title) + class TabbedFigureContainerGTK3(object): _managers = [] _labels = {} _w_min = 0 _h_min = 0 - + def __init__(self, *args): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.window = Gtk.Window() @@ -555,21 +554,20 @@ def __init__(self, *args): # 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.vbox.show() - + self.notebook = Gtk.Notebook() - + self.notebook.set_scrollable(True) - + self.notebook.connect('switch-page', self._on_switch_page) self.vbox.pack_start(self.notebook, True, True, 0) self.window.add(self.vbox) - - + self.toolbar = self._get_toolbar() if self.toolbar is not None: @@ -580,23 +578,22 @@ def __init__(self, *args): self._h_def = size_request.height self._w_def = size_request.width - def destroy_window(*args): nums = [manager.num for manager in self._managers] for num in nums: Gcf.destroy(num) self.window.connect("destroy", destroy_window) self.window.connect("delete_event", destroy_window) - + self.vbox.show_all() - + if matplotlib.is_interactive(): self.window.show() - + def _on_switch_page(self, notebook, pointer, num): canvas = self.notebook.get_nth_page(num) - self.toolbar.switch_toolbar(canvas.toolbar) - + self.toolbar.switch_toolbar(canvas.toolbar) + def destroy(self, *args): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) @@ -608,9 +605,9 @@ def destroy(self, *args): if Gcf.get_num_fig_managers() == 0 and \ not matplotlib.is_interactive() and \ Gtk.main_level() >= 1: - Gtk.main_quit() - - def remove_manager(self, manager): + Gtk.main_quit() + + def remove_manager(self, manager): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) if manager not in self._managers: raise AttributeError('This container does not control the given figure manager') @@ -620,16 +617,22 @@ def remove_manager(self, manager): del self._labels[manager.num] self.notebook.remove_page(id_) self._managers.remove(manager) - + if self.notebook.get_n_pages() == 0: self.destroy() - + def set_manager_title(self, manager, title): self._labels[manager.num].set_text(title) - + def get_manager_title(self, manager): self._labels[manager.num].get_text() - + + def set_window_title(self, title): + self.window.set_title(title) + + def get_window_title(self): + return self.window.get_title() + def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set @@ -637,17 +640,15 @@ def _get_toolbar(self): toolbar = TabbedContainerNavigationToolbar2GTK3(self.window) else: toolbar = None - return toolbar - - + return toolbar + def get_manager_toolbar(self, manager): if self.toolbar is None: return None toolbar = TabbedNavigationToolbar(manager.canvas, self.toolbar) -# self.toolbar.add_manager(manager) return toolbar - - def add_figure_manager(self, figure_manager): + + def add_manager(self, figure_manager): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) if figure_manager in self._managers: raise AttributeError ('Impossible to add two times the same figure manager') @@ -658,64 +659,63 @@ def add_figure_manager(self, figure_manager): box = Gtk.Box() box.set_orientation(Gtk.Orientation.HORIZONTAL) box.set_spacing(5) - + label = Gtk.Label(title) self._labels[num] = label self._managers.append(figure_manager) - + box.pack_start(label, True, True, 0) - + # close button button = Gtk.Button() - + button.set_relief(Gtk.ReliefStyle.NONE) button.set_focus_on_click(False) - button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) - box.pack_end(button, False, False, 0) + button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) + box.pack_end(button, False, False, 0) box.show_all() self.notebook.append_page(canvas, box) canvas.show() - + def _remove(btn): Gcf.destroy(num) button.connect("clicked", _remove) - + # calculate size for window - w = int (canvas.figure.bbox.width) - h = int (canvas.figure.bbox.height) - + w = int(canvas.figure.bbox.width) + h = int(canvas.figure.bbox.height) + if w > self._w_min: self._w_min = w if h > self._h_min: - self._h_min = h - + self._h_min = h + self.window.set_default_size (self._w_def + self._w_min, self._h_def + self._h_min) canvas.grab_focus() - + def show_manager(self, manager): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.show() canvas = manager.canvas id_ = self.notebook.page_num(canvas) self.notebook.set_current_page(id_) - + def show(self): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) - self.window.show_all() - - + self.window.show_all() + if rcParams['backend.gtk3.tabbed']: - FigureManagerGTK3 = TabbedFigureManagerGTK3 + FigureManagerGTK3 = TabbedFigureManagerBase class TabbedNavigationToolbar(NavigationToolbar2): def __init__(self, canvas, parent): self.parent = parent NavigationToolbar2.__init__(self, canvas) - + def _init_toolbar(self): self.parent.add_toolbar(self) self.ctx = None @@ -725,7 +725,7 @@ def set_message(self, s): def set_cursor(self, cursor): self.canvas.get_property("window").set_cursor(cursord[cursor]) - #self.canvas.set_cursor(cursord[cursor]) + # self.canvas.set_cursor(cursord[cursor]) def release(self, event): try: del self._pixmapBack @@ -748,7 +748,7 @@ def draw_rubberband(self, event, x0, y0, x1, y1): y0 = height - y0 w = abs(x1 - x0) h = abs(y1 - y0) - rect = [int(val) for val in (min(x0,x1), min(y0, y1), w, h)] + rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] self.ctx.new_path() self.ctx.set_line_width(0.5) @@ -761,17 +761,17 @@ class TabbedContainerNavigationToolbar2GTK3(Gtk.Toolbar): _toolbars = [] toolitems = list(NavigationToolbar2.toolitems) extra_items = [('SaveAll', 'Save all figures', 'filesave', 'save_all_figures'), ] - #It is more clear to have toggle buttons for these two options - _toggle = {'Pan': None, 'Zoom': None} + # It is more clear to have toggle buttons for these two options + _toggle = {'Pan': None, 'Zoom': None} _destroy_on_switch = [] - + def __init__(self, window): self.win = window Gtk.Toolbar.__init__(self) self.toolitems.extend(self.extra_items) self._add_buttons() self._current = None - + def _add_buttons(self): self.set_style(Gtk.ToolbarStyle.ICONS) basedir = os.path.join(rcParams['datapath'], 'images') @@ -786,16 +786,16 @@ def _add_buttons(self): tbutton = Gtk.ToggleToolButton() self._toggle[text] = tbutton tbutton.connect('toggled', self._toggled) - else: + else: tbutton = Gtk.ToolButton() - #attach to _toggled so it untoggles the toggled button + # attach to _toggled so it untoggles the toggled button tbutton.connect('clicked', self._toggled) tbutton.set_label(text) tbutton.set_icon_widget(image) self.insert(tbutton, -1) - + tbutton.connect('clicked', getattr(self, callback)) - + tbutton.set_tooltip_text(tooltip_text) toolitem = Gtk.SeparatorToolItem() @@ -809,134 +809,76 @@ def _add_buttons(self): toolitem.add(self.message) self.show_all() - + def switch_toolbar(self, toolbar): if toolbar not in self._toolbars: raise AttributeError('This container does not control the given toolbar') - + print(self._destroy_on_switch) - + for w in self._destroy_on_switch: o = w() if o is None: continue - + try: o.destroy() except: pass self._destroy_on_switch = [] - - #For these two actions we have to unselect and reselect - d = {'PAN': 'pan', 'ZOOM': 'zoom'} + + # For these two actions we have to unselect and reselect + d = {'PAN': 'pan', 'ZOOM': 'zoom'} action = d.get(self._current._active, False) if action: getattr(self._current, action)() getattr(toolbar, action)() - self._current = toolbar - - + self._current = toolbar + def home(self, *args): self._current.home(*args) - + def back(self, *args): self._current.back(*args) - + def forward(self, *args): self._current.forward(*args) - + def pan(self, *args): self._current.pan(*args) def zoom(self, *args): self._current.zoom(*args) - - def save_all_figures(self, *args): - pass - + def add_toolbar(self, toolbar): - print ('adding toolbar', toolbar) self._toolbars.append(toolbar) self._current = toolbar - + def _toggled(self, btn): - #Untoggle other toggled buttons + # Untoggle other toggled buttons for i in self._toggle.values(): if i is not btn: if i.get_active(): i.handler_block_by_func(self._toggled) i.set_active(False) - i.handler_unblock_by_func(self._toggled) - - def get_filechooser(self, title, current_name): - fc = FileChooserDialog( - title=title, - parent=self.win, - path=os.path.expanduser(rcParams.get('savefig.directory', '')), - filetypes=self._current.canvas.get_supported_filetypes(), - default_filetype=self._current.canvas.get_default_filetype()) - fc.set_current_name(current_name) - return fc - - def _do_save_figure(self, canvas, fname, format): - if fname: - startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) - if startpath == '': - # explicitly missing key or empty str signals to use cwd - rcParams['savefig.directory'] = startpath - else: - # save dir for next time - rcParams['savefig.directory'] = os.path.dirname(six.text_type(fname)) - - try: - canvas.print_figure(fname, format=format) - except Exception as e: - error_msg_gtk(str(e), parent=self) - + i.handler_unblock_by_func(self._toggled) def save_figure(self, *args): - current_name = self._current.canvas.get_default_filename() - chooser = self.get_filechooser('Save the figure', current_name) - - #We want to make sure the user is not confused - #if changes tabs while saving dialog is open - self._destroy_on_switch.append(weakref.ref(chooser)) - fname, format = chooser.get_filename_from_user() - chooser.destroy() - self._do_save_figure(self._current.canvas, fname, format) + figure = self._current.canvas.figure + SaveFiguresDialogGTK3(figure) - - - def save_all_figures(self, *args): - start = self._current.canvas.get_default_filename() - fname_end = '.' + self._current.canvas.get_default_filetype() - current_name = start[:-len(fname_end)] - chooser = self.get_filechooser('Save all figures', current_name) - - #We want to make sure the user is not confused - #if changes tabs while saving dialog is open - self._destroy_on_switch.append(weakref.ref(chooser)) - fname, format = chooser.get_filename_from_user() - chooser.destroy() - - if not fname: - return + figures = [toolbar.canvas.figure for toolbar in self._toolbars] + SaveFiguresDialogGTK3(*figures) - for canvas in [t.canvas for t in self._toolbars]: - #surface.write_to_png (fobj) in backend_cairo.py - #doesn't work correclty with other kind of strings - fn = str('%s_%.3d.%s' % (fname, canvas.manager.num, format)) - self._do_save_figure(canvas, fn, format) - def configure_subplots(self, button): - toolfig = Figure(figsize=(6,3)) + toolfig = Figure(figsize=(6, 3)) canvas = self._get_canvas(toolfig) toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self._current.canvas.figure, toolfig) + tool = SubplotTool(self._current.canvas.figure, toolfig) - w = int (toolfig.bbox.width) - h = int (toolfig.bbox.height) + w = int(toolfig.bbox.width) + h = int(toolfig.bbox.height) window = Gtk.Window() try: @@ -958,14 +900,72 @@ def configure_subplots(self, button): canvas.show() vbox.pack_start(canvas, True, True, 0) window.show() - - self._destroy_on_switch.append(weakref.ref(window)) - print (self._destroy_on_switch) def _get_canvas(self, fig): return self._current.canvas.__class__(fig) - - + + +class SaveFiguresDialogGTK3(object): + def __init__(self, *figs): + self.figures = figs + self.ref_canvas = figs[0].canvas + self.current_name = self.ref_canvas.get_default_filename() + self.title = 'Save %d Figures' % len(figs) + + if len(figs) > 1: + fname_end = '.' + self.ref_canvas.get_default_filetype() + self.current_name = self.current_name[:-len(fname_end)] + self.show() + + def show(self): + chooser = self.get_filechooser() + fname, format_ = chooser.get_filename_from_user() + chooser.destroy() + if not fname: + return + self.save_figures(fname, format_) + + def save_figures(self, basename, format_): + figs = self.figures + startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) + if startpath == '': + # explicitly missing key or empty str signals to use cwd + rcParams['savefig.directory'] = startpath + else: + # save dir for next time + rcParams['savefig.directory'] = os.path.dirname(six.text_type(basename)) + + # Get rid of the extension including the point + extension = '.' + format_ + if basename.endswith(extension): + basename = basename[:-len(extension)] + + # In the case of multiple figures, we have to insert a + # "figure identifier" in the filename name + n = len(figs) + if n == 1: + figure_identifier = ('',) + else: + figure_identifier = [str('_%.3d' % figs[i].canvas.manager.num) for i in range(n)] + + for i in range(n): + canvas = figs[i].canvas + fname = str('%s%s%s' % (basename, figure_identifier[i], extension)) + try: + canvas.print_figure(fname, format=format_) + except Exception as e: + error_msg_gtk(str(e), parent=canvas.manager.window) + + def get_filechooser(self): + fc = FileChooserDialog( + title=self.title, + parent=self.ref_canvas.manager.window, + path=os.path.expanduser(rcParams.get('savefig.directory', '')), + filetypes=self.ref_canvas.get_supported_filetypes(), + default_filetype=self.ref_canvas.get_default_filetype()) + fc.set_current_name(self.current_name) + return fc + class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): def __init__(self, canvas, window): @@ -979,7 +979,7 @@ def set_message(self, s): def set_cursor(self, cursor): self.canvas.get_property("window").set_cursor(cursord[cursor]) - #self.canvas.set_cursor(cursord[cursor]) + # self.canvas.set_cursor(cursord[cursor]) def release(self, event): try: del self._pixmapBack @@ -1002,7 +1002,7 @@ def draw_rubberband(self, event, x0, y0, x1, y1): y0 = height - y0 w = abs(x1 - x0) h = abs(y1 - y0) - rect = [int(val) for val in (min(x0,x1), min(y0, y1), w, h)] + rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] self.ctx.new_path() self.ctx.set_line_width(0.5) @@ -1012,11 +1012,11 @@ def draw_rubberband(self, event, x0, y0, x1, y1): def _init_toolbar(self): self.set_style(Gtk.ToolbarStyle.ICONS) - basedir = os.path.join(rcParams['datapath'],'images') + basedir = os.path.join(rcParams['datapath'], 'images') for text, tooltip_text, image_file, callback in self.toolitems: if text is None: - self.insert( Gtk.SeparatorToolItem(), -1 ) + self.insert(Gtk.SeparatorToolItem(), -1) continue fname = os.path.join(basedir, image_file + '.png') image = Gtk.Image() @@ -1068,10 +1068,10 @@ def save_figure(self, *args): error_msg_gtk(str(e), parent=self) def configure_subplots(self, button): - toolfig = Figure(figsize=(6,3)) + toolfig = Figure(figsize=(6, 3)) canvas = self._get_canvas(toolfig) toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self.canvas.figure, toolfig) + tool = SubplotTool(self.canvas.figure, toolfig) w = int (toolfig.bbox.width) h = int (toolfig.bbox.height) @@ -1107,14 +1107,14 @@ class FileChooserDialog(Gtk.FileChooserDialog): selected and presents the user with a menu of supported image formats """ def __init__ (self, - title = 'Save file', - parent = None, - action = Gtk.FileChooserAction.SAVE, - buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_SAVE, Gtk.ResponseType.OK), - path = None, - filetypes = [], - default_filetype = None + title='Save file', + parent=None, + action=Gtk.FileChooserAction.SAVE, + buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, Gtk.ResponseType.OK), + path=None, + filetypes=[], + default_filetype=None ): super (FileChooserDialog, self).__init__ (title, parent, action, buttons) @@ -1130,7 +1130,7 @@ def __init__ (self, hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0) liststore = Gtk.ListStore(GObject.TYPE_STRING) - cbox = Gtk.ComboBox() #liststore) + cbox = Gtk.ComboBox() # liststore) cbox.set_model(liststore) cell = Gtk.CellRendererText() cbox.pack_start(cell, True) @@ -1148,7 +1148,7 @@ def __init__ (self, cbox.set_active(default) self.ext = default_filetype - def cb_cbox_changed (cbox, data=None): + def cb_cbox_changed(cbox, data=None): """File extension changed""" head, filename = os.path.split(self.get_filename()) root, ext = os.path.splitext(filename) @@ -1161,13 +1161,13 @@ def cb_cbox_changed (cbox, data=None): elif ext == '': filename = filename.rstrip('.') + '.' + new_ext - self.set_current_name (filename) - cbox.connect ("changed", cb_cbox_changed) + self.set_current_name(filename) + cbox.connect("changed", cb_cbox_changed) hbox.show_all() self.set_extra_widget(hbox) - def get_filename_from_user (self): + def get_filename_from_user(self): while True: filename = None if self.run() != int(Gtk.ResponseType.OK): @@ -1177,6 +1177,7 @@ def get_filename_from_user (self): return filename, self.ext + class DialogLineprops: """ A GUI dialog for controlling lineprops @@ -1192,12 +1193,12 @@ class DialogLineprops: ) linestyles = [ls for ls in lines.Line2D.lineStyles if ls.strip()] - linestyled = dict([ (s,i) for i,s in enumerate(linestyles)]) + linestyled = dict([ (s, i) for i, s in enumerate(linestyles)]) - markers = [m for m in lines.Line2D.markers if cbook.is_string_like(m)] + markers = [m for m in lines.Line2D.markers if cbook.is_string_like(m)] - markerd = dict([(s,i) for i,s in enumerate(markers)]) + markerd = dict([(s, i) for i, s in enumerate(markers)]) def __init__(self, lines): import Gtk.glade @@ -1205,10 +1206,10 @@ def __init__(self, lines): datadir = matplotlib.get_data_path() gladefile = os.path.join(datadir, 'lineprops.glade') if not os.path.exists(gladefile): - raise IOError('Could not find gladefile lineprops.glade in %s'%datadir) + raise IOError('Could not find gladefile lineprops.glade in %s' % datadir) self._inited = False - self._updateson = True # suppress updates when setting widgets manually + self._updateson = True # suppress updates when setting widgets manually self.wtree = Gtk.glade.XML(gladefile, 'dialog_lineprops') self.wtree.signal_autoconnect(dict([(s, getattr(self, s)) for s in self.signals])) @@ -1240,7 +1241,7 @@ def show(self): self._updateson = False # flush the old cbox = self.cbox_lineprops - for i in range(self._lastcnt-1,-1,-1): + for i in range(self._lastcnt - 1, -1, -1): cbox.remove_text(i) # add the new @@ -1281,13 +1282,13 @@ def _update(self): button = self.wtree.get_widget('colorbutton_linestyle') color = button.get_color() - r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] - line.set_color((r,g,b)) + r, g, b = [val / 65535. for val in (color.red, color.green, color.blue)] + line.set_color((r, g, b)) button = self.wtree.get_widget('colorbutton_markerface') color = button.get_color() - r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] - line.set_markerfacecolor((r,g,b)) + r, g, b = [val / 65535. for val in (color.red, color.green, color.blue)] + line.set_markerfacecolor((r, g, b)) line.figure.canvas.draw() @@ -1305,13 +1306,13 @@ def on_combobox_lineprops_changed(self, item): if marker is None: marker = 'None' self.cbox_markers.set_active(self.markerd[marker]) - r,g,b = colorConverter.to_rgb(line.get_color()) - color = Gdk.Color(*[int(val*65535) for val in (r,g,b)]) + r, g, b = colorConverter.to_rgb(line.get_color()) + color = Gdk.Color(*[int(val * 65535) for val in (r, g, b)]) button = self.wtree.get_widget('colorbutton_linestyle') button.set_color(color) - r,g,b = colorConverter.to_rgb(line.get_markerfacecolor()) - color = Gdk.Color(*[int(val*65535) for val in (r,g,b)]) + r, g, b = colorConverter.to_rgb(line.get_markerfacecolor()) + color = Gdk.Color(*[int(val * 65535) for val in (r, g, b)]) button = self.wtree.get_widget('colorbutton_markerface') button.set_color(color) self._updateson = True @@ -1346,18 +1347,18 @@ def on_dialog_lineprops_cancelbutton_clicked(self, button): def error_msg_gtk(msg, parent=None): - if parent is not None: # find the toplevel Gtk.Window + if parent is not None: # find the toplevel Gtk.Window parent = parent.get_toplevel() if not parent.is_toplevel(): parent = None if not is_string_like(msg): - msg = ','.join(map(str,msg)) + msg = ','.join(map(str, msg)) dialog = Gtk.MessageDialog( - parent = parent, - type = Gtk.MessageType.ERROR, - buttons = Gtk.ButtonsType.OK, - message_format = msg) + parent=parent, + type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + message_format=msg) dialog.run() dialog.destroy() From a7e99681df1a259ea5efe9c31baba9c6d61e4142 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Sun, 15 Sep 2013 14:44:12 -0400 Subject: [PATCH 04/20] Subplot tool and save as external clasess, working on weakref to keep track --- lib/matplotlib/backends/backend_gtk3.py | 191 ++++++++++++++---------- 1 file changed, 115 insertions(+), 76 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 9bbde57ab6b7..26aba4f785ad 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -45,7 +45,6 @@ def fn_name(): return sys._getframe(1).f_code.co_name backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) -# _debug = False _debug = False # the true dots per inch on the screen; should be display dependent @@ -477,19 +476,25 @@ def resize(self, width, height): self.window.resize(width, height) -class TabbedFigureManagerBase(FigureManagerBase): +class ChildFigureManager(FigureManagerBase): parent = None + _parent_class = None @classmethod def initialize(cls): if cls.parent is None: - cls.parent = TabbedFigureContainerGTK3() + cls.parent = cls._parent_class() def __init__(self, canvas, num): self.initialize() FigureManagerBase.__init__(self, canvas, num) - self.toolbar = self.parent.get_manager_toolbar(self) - self.parent.add_manager(self) + + if self.parent.toolbar is None: + self.toolbar = None + else: + self.toolbar = ChildNavigationToolbar(self.canvas, self.parent.toolbar) + self.parent.add_child(self) + self.canvas.show() self.window = self.parent.window @@ -499,12 +504,12 @@ def notify_axes_change(fig): canvas.figure.add_axobserver(notify_axes_change) def show(self): - self.parent.show_manager(self) + self.parent.show_child(self) def destroy(self): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.canvas.destroy() - self.parent.remove_manager(self) + self.parent.remove_child(self) def resize(self, w, h): self.parent.resize_manager(self, w, h) @@ -521,10 +526,10 @@ def show_popup(self, msg): # Because we have many figures, the mainwindow is the window # that includes (control) all the figures def get_window_title(self): - return self.parent.get_manager_title(self) + return self.parent.get_child_title(self) def set_window_title(self, title): - self.parent.set_manager_title(self, title) + self.parent.set_child_title(self, title) def get_mainwindow_title(self): return self.parent.get_window_title() @@ -533,8 +538,8 @@ def set_mainwindow_title(self, title): self.parent.set_window_title(title) -class TabbedFigureContainerGTK3(object): - _managers = [] +class MultiFigureManagerGTK3(object): + _children = [] _labels = {} _w_min = 0 _h_min = 0 @@ -579,7 +584,7 @@ def __init__(self, *args): self._w_def = size_request.width def destroy_window(*args): - nums = [manager.num for manager in self._managers] + nums = [manager.num for manager in self._children] for num in nums: Gcf.destroy(num) self.window.connect("destroy", destroy_window) @@ -591,6 +596,8 @@ def destroy_window(*args): self.window.show() def _on_switch_page(self, notebook, pointer, num): + if self.toolbar is None: + return canvas = self.notebook.get_nth_page(num) self.toolbar.switch_toolbar(canvas.toolbar) @@ -601,59 +608,66 @@ def destroy(self, *args): self.window.destroy() if self.toolbar: self.toolbar.destroy() -# + if Gcf.get_num_fig_managers() == 0 and \ not matplotlib.is_interactive() and \ Gtk.main_level() >= 1: Gtk.main_quit() - def remove_manager(self, manager): + def remove_child(self, child): + '''Remove the child from the multi figure, if it was the last one, destroy itself''' if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) - if manager not in self._managers: - raise AttributeError('This container does not control the given figure manager') - canvas = manager.canvas + if child not in self._children: + raise AttributeError('This container does not control the given figure child') + canvas = child.canvas id_ = self.notebook.page_num(canvas) if id_ > -1: - del self._labels[manager.num] + del self._labels[child.num] self.notebook.remove_page(id_) - self._managers.remove(manager) + self._children.remove(child) if self.notebook.get_n_pages() == 0: self.destroy() - def set_manager_title(self, manager, title): - self._labels[manager.num].set_text(title) + def set_child_title(self, child, title): + """ + Set the title text of the container containing the figure. + """ + self._labels[child.num].set_text(title) - def get_manager_title(self, manager): - self._labels[manager.num].get_text() + def get_child_title(self, child): + """ + Get the title text of the container containing the figure + """ + self._labels[child.num].get_text() def set_window_title(self, title): + """ + Set the title text of the multi-figure-manager window. + """ self.window.set_title(title) def get_window_title(self): + """ + Get the title text of the multi-figure-manager window. + """ return self.window.get_title() def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set if rcParams['toolbar'] == 'toolbar2': - toolbar = TabbedContainerNavigationToolbar2GTK3(self.window) + toolbar = MultiFigureNavigationToolbar2GTK3(self.window) else: toolbar = None return toolbar - def get_manager_toolbar(self, manager): - if self.toolbar is None: - return None - toolbar = TabbedNavigationToolbar(manager.canvas, self.toolbar) - return toolbar - - def add_manager(self, figure_manager): + def add_child(self, child): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) - if figure_manager in self._managers: - raise AttributeError ('Impossible to add two times the same figure manager') - canvas = figure_manager.canvas - num = figure_manager.num + if child in self._children: + raise AttributeError('Impossible to add two times the same child') + canvas = child.canvas + num = child.num title = 'Fig %d' % num box = Gtk.Box() @@ -662,7 +676,7 @@ def add_manager(self, figure_manager): label = Gtk.Label(title) self._labels[num] = label - self._managers.append(figure_manager) + self._children.append(child) box.pack_start(label, True, True, 0) @@ -691,27 +705,30 @@ def _remove(btn): if h > self._h_min: self._h_min = h - self.window.set_default_size (self._w_def + self._w_min, self._h_def + self._h_min) + self.window.set_default_size(self._w_def + self._w_min, self._h_def + self._h_min) canvas.grab_focus() - def show_manager(self, manager): + def show_child(self, child): + """Find the appropiate child container and show it""" if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.show() - canvas = manager.canvas + canvas = child.canvas id_ = self.notebook.page_num(canvas) self.notebook.set_current_page(id_) def show(self): + """Show the multi-figure-manager""" if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.window.show_all() if rcParams['backend.gtk3.tabbed']: - FigureManagerGTK3 = TabbedFigureManagerBase + ChildFigureManager._parent_class = MultiFigureManagerGTK3 + FigureManagerGTK3 = ChildFigureManager -class TabbedNavigationToolbar(NavigationToolbar2): +class ChildNavigationToolbar(NavigationToolbar2): def __init__(self, canvas, parent): self.parent = parent NavigationToolbar2.__init__(self, canvas) @@ -721,7 +738,7 @@ def _init_toolbar(self): self.ctx = None def set_message(self, s): - self.parent.message.set_label(s) + self.parent.set_child_message(self, s) def set_cursor(self, cursor): self.canvas.get_property("window").set_cursor(cursord[cursor]) @@ -757,13 +774,14 @@ def draw_rubberband(self, event, x0, y0, x1, y1): self.ctx.stroke() -class TabbedContainerNavigationToolbar2GTK3(Gtk.Toolbar): +class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar): _toolbars = [] toolitems = list(NavigationToolbar2.toolitems) extra_items = [('SaveAll', 'Save all figures', 'filesave', 'save_all_figures'), ] # It is more clear to have toggle buttons for these two options _toggle = {'Pan': None, 'Zoom': None} - _destroy_on_switch = [] +# _destroy_on_switch = weakref.WeakValueDictionary() + _destroy_on_switch = {} def __init__(self, window): self.win = window @@ -815,17 +833,19 @@ def switch_toolbar(self, toolbar): raise AttributeError('This container does not control the given toolbar') print(self._destroy_on_switch) - - for w in self._destroy_on_switch: - o = w() - if o is None: - continue - - try: - o.destroy() - except: - pass - self._destroy_on_switch = [] + for k, v in self._destroy_on_switch.items(): + print(k, v) +# +# for w in self._destroy_on_switch: +# o = w() +# if o is None: +# continue +# +# try: +# o.destroy() +# except: +# pass +# self._destroy_on_switch = [] # For these two actions we have to unselect and reselect d = {'PAN': 'pan', 'ZOOM': 'zoom'} @@ -865,24 +885,26 @@ def _toggled(self, btn): def save_figure(self, *args): figure = self._current.canvas.figure - SaveFiguresDialogGTK3(figure) + dialog = SaveFiguresDialogGTK3(figure) def save_all_figures(self, *args): figures = [toolbar.canvas.figure for toolbar in self._toolbars] SaveFiguresDialogGTK3(*figures) def configure_subplots(self, button): - toolfig = Figure(figsize=(6, 3)) - canvas = self._get_canvas(toolfig) - toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self._current.canvas.figure, toolfig) + ConfigureSubplotsGTK3(self._current.canvas.figure) +# self._destroy_on_switch['conf'] = weakref.proxy(cf) +# self.hola = weakref.proxy(cf) - w = int(toolfig.bbox.width) - h = int(toolfig.bbox.height) + def set_child_message(self, child, text): + self.message.set_label(text) - window = Gtk.Window() + +class ConfigureSubplotsGTK3(Gtk.Window): + def __init__(self, figure): + Gtk.Window.__init__(self) try: - window.set_icon_from_file(window_icon) + self.set_icon_from_file(window_icon) except (SystemExit, KeyboardInterrupt): # re-raise exit type Exceptions raise @@ -890,25 +912,42 @@ def configure_subplots(self, button): # we presumably already logged a message on the # failure of the main plot, don't keep reporting pass - window.set_title("Subplot Configuration Tool") - window.set_default_size(w, h) - vbox = Gtk.Box() - vbox.set_property("orientation", Gtk.Orientation.VERTICAL) - window.add(vbox) - vbox.show() + self.set_title("Subplot Configuration Tool") + self.vbox = Gtk.Box() + self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL) + self.add(self.vbox) + self.vbox.show() + + self.set_figures(figure) - canvas.show() - vbox.pack_start(canvas, True, True, 0) - window.show() + def set_figures(self, figure): + children = self.vbox.get_children() + for child in children: + child.destroy() + del children - def _get_canvas(self, fig): - return self._current.canvas.__class__(fig) + toolfig = Figure(figsize=(6, 3)) + canvas = figure.canvas.__class__(toolfig) + + toolfig.subplots_adjust(top=0.9) + SubplotTool(figure, toolfig) + + w = int(toolfig.bbox.width) + h = int(toolfig.bbox.height) + + self.set_default_size(w, h) + + canvas.show() + self.vbox.pack_start(canvas, True, True, 0) + self.show() class SaveFiguresDialogGTK3(object): def __init__(self, *figs): + ref_figure = figs[0] self.figures = figs - self.ref_canvas = figs[0].canvas + + self.ref_canvas = ref_figure.canvas self.current_name = self.ref_canvas.get_default_filename() self.title = 'Save %d Figures' % len(figs) From 6c13b8342a0e0c7435e1278786b20eaf2c797853 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Mon, 16 Sep 2013 12:03:48 -0400 Subject: [PATCH 05/20] Added external buttons, trying to figure out how to keep track of them --- lib/matplotlib/backends/backend_gtk3.py | 239 ++++++++++++++++++------ 1 file changed, 183 insertions(+), 56 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 26aba4f785ad..4f3ac63f2d74 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -774,48 +774,150 @@ def draw_rubberband(self, event, x0, y0, x1, y1): self.ctx.stroke() + + + + class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar): _toolbars = [] - toolitems = list(NavigationToolbar2.toolitems) - extra_items = [('SaveAll', 'Save all figures', 'filesave', 'save_all_figures'), ] - # It is more clear to have toggle buttons for these two options - _toggle = {'Pan': None, 'Zoom': None} + + toolitems = ({'text': 'Home', + 'tooltip_text': 'Reset original view', + 'stock_image': 'home', + 'callback': 'home'}, + + {'text': 'Back', + 'tooltip_text': 'Back to previous view', + 'stock_image': 'back', + 'callback': 'back'}, + + {'text': 'Forward', + 'tooltip_text': 'Forward to next view', + 'stock_image': 'forward', + 'callback': 'forward'}, + + None, + + {'text': 'Pan', + 'tooltip_text': 'Pan axes with left mouse, zoom with right', + 'stock_image': 'move', + 'callback': 'pan', + 'toggle': True}, + + {'text': 'Zoom', + 'tooltip_text': 'Zoom to rectangle', + 'stock_image': 'zoom_to_rect', + 'callback': 'zoom', + 'toggle': True}, + + None, + ) + + external_toolitems = [{'text': 'Subplots', + 'tooltip_text': 'Configure subplots', + 'stock_image': 'subplots', + 'callback_class': 'ConfigureSubplotsGTK3', + 'all_figures': False}, + + {'text': 'Save', + 'tooltip_text': 'Save the figure', + 'stock_image': 'filesave', + 'callback_class': 'SaveFiguresDialogGTK3', + 'all_figures': False + } + + ] +# {'text': +# 'tooltip_text': +# 'stock_image': +# 'callback': +# }, +# +# +# ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'), +# (), + +# extra_items = [('SaveAll', 'Save all figures', 'filesave', 'save_all_figures'), ] +# # It is more clear to have toggle buttons for these two options + _toggle = [] # _destroy_on_switch = weakref.WeakValueDictionary() - _destroy_on_switch = {} + _inform_at_switch = weakref.WeakValueDictionary() def __init__(self, window): self.win = window - Gtk.Toolbar.__init__(self) - self.toolitems.extend(self.extra_items) - self._add_buttons() + +# self.toolitems.extend(self.extra_items) + self.init_toolbar() + for pos, btn in enumerate(self.external_toolitems): + self.add_button(btn, pos) self._current = None - def _add_buttons(self): - self.set_style(Gtk.ToolbarStyle.ICONS) - basedir = os.path.join(rcParams['datapath'], 'images') - for text, tooltip_text, image_file, callback in self.toolitems: - if text is None: - self.insert(Gtk.SeparatorToolItem(), -1) - continue - fname = os.path.join(basedir, image_file + '.png') + def add_button(self, btn, pos): + pos = len(self.toolitems) + pos + tbutton = self._add_button(btn, pos) + if not tbutton: + return + tbutton.connect('clicked', self._external_callback, btn) + + def _external_callback(self, btn, btn_info): + cs = self._inform_at_switch.get(btn, None) + if cs is not None: + cs.show() + return + + if btn_info.get('all_figures', False): + figures = [toolbar.canvas.figure for toolbar in self._toolbars] + else: + figures = (self._current.canvas.figure, ) + + cls = btn_info['callback_class'] + if isinstance(cls, basestring): + cls = globals()[cls] + cs = cls() + self._inform_at_switch[btn] = cs + cs.set_figures(*figures) + + print('externalcallback') + for v in self._inform_at_switch.values(): + print (v) + + def _add_button(self, btn, pos): + if btn is None: + self.insert(Gtk.SeparatorToolItem(), pos) + return None + + stock_image = btn.get('stock_image', False) + if stock_image: + basedir = os.path.join(rcParams['datapath'], 'images') + fname = os.path.join(basedir, stock_image + '.png') image = Gtk.Image() image.set_from_file(fname) - if text in self._toggle: - tbutton = Gtk.ToggleToolButton() - self._toggle[text] = tbutton - tbutton.connect('toggled', self._toggled) - else: - tbutton = Gtk.ToolButton() - # attach to _toggled so it untoggles the toggled button - tbutton.connect('clicked', self._toggled) - tbutton.set_label(text) - tbutton.set_icon_widget(image) - self.insert(tbutton, -1) - - tbutton.connect('clicked', getattr(self, callback)) - - tbutton.set_tooltip_text(tooltip_text) - + + toggle = btn.get('toggle', False) + if toggle: + tbutton = Gtk.ToggleToolButton() + self._toggle.append(tbutton) + tbutton.connect('toggled', self._something_clicked) + else: + tbutton = Gtk.ToolButton() + # attach to _something_clicked so it untoggles the toggled button + tbutton.connect('clicked', self._something_clicked) + + tbutton.set_label(btn['text']) + tbutton.set_icon_widget(image) + tbutton.set_tooltip_text(btn.get('tooltip_text', None)) + self.insert(tbutton, pos) + return tbutton + + def init_toolbar(self): + Gtk.Toolbar.__init__(self) + self.set_style(Gtk.ToolbarStyle.ICONS) + + for btn in self.toolitems: + tbutton = self._add_button(btn, -1) + if tbutton: + tbutton.connect('clicked', getattr(self, btn['callback'])) + toolitem = Gtk.SeparatorToolItem() self.insert(toolitem, -1) toolitem.set_draw(False) @@ -832,9 +934,13 @@ def switch_toolbar(self, toolbar): if toolbar not in self._toolbars: raise AttributeError('This container does not control the given toolbar') - print(self._destroy_on_switch) - for k, v in self._destroy_on_switch.items(): - print(k, v) + print('onswitch') + for v in self._inform_at_switch.values(): + print (v) + +# print(self._destroy_on_switch) +# for k, v in self._destroy_on_switch.items(): +# print(k, v) # # for w in self._destroy_on_switch: # o = w() @@ -874,14 +980,15 @@ def add_toolbar(self, toolbar): self._toolbars.append(toolbar) self._current = toolbar - def _toggled(self, btn): - # Untoggle other toggled buttons - for i in self._toggle.values(): + def _something_clicked(self, btn): + #when something is clicked, untoggle all toggle buttons + #if it is a toggle button, untoggle the other toggle buttons + for i in self._toggle: if i is not btn: if i.get_active(): - i.handler_block_by_func(self._toggled) + i.handler_block_by_func(self._something_clicked) i.set_active(False) - i.handler_unblock_by_func(self._toggled) + i.handler_unblock_by_func(self._something_clicked) def save_figure(self, *args): figure = self._current.canvas.figure @@ -900,11 +1007,31 @@ def set_child_message(self, child, text): self.message.set_label(text) -class ConfigureSubplotsGTK3(Gtk.Window): - def __init__(self, figure): - Gtk.Window.__init__(self) +class ToolBase(object): + def __init__(self, *figures): + self.init_tool() + if figures: + self.set_figures(*figures) + + def init_tool(self): + pass + + def set_figures(self, *figures): + raise NotImplementedError + + def destroy(self): + pass + + def show(self): + pass + + +class ConfigureSubplotsGTK3(ToolBase): + def init_tool(self): + self.window = Gtk.Window() + try: - self.set_icon_from_file(window_icon) + self.window.set_icon_from_file(window_icon) except (SystemExit, KeyboardInterrupt): # re-raise exit type Exceptions raise @@ -912,13 +1039,11 @@ def __init__(self, figure): # we presumably already logged a message on the # failure of the main plot, don't keep reporting pass - self.set_title("Subplot Configuration Tool") + self.window.set_title("Subplot Configuration Tool") self.vbox = Gtk.Box() self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL) - self.add(self.vbox) + self.window.add(self.vbox) self.vbox.show() - - self.set_figures(figure) def set_figures(self, figure): children = self.vbox.get_children() @@ -935,15 +1060,17 @@ def set_figures(self, figure): w = int(toolfig.bbox.width) h = int(toolfig.bbox.height) - self.set_default_size(w, h) + self.window.set_default_size(w, h) canvas.show() self.vbox.pack_start(canvas, True, True, 0) - self.show() + self.window.show() -class SaveFiguresDialogGTK3(object): - def __init__(self, *figs): +class SaveFiguresDialogGTK3(ToolBase): + + def set_figures(self, *figs): + print('savefigure', figs) ref_figure = figs[0] self.figures = figs @@ -957,14 +1084,14 @@ def __init__(self, *figs): self.show() def show(self): - chooser = self.get_filechooser() + chooser = self._get_filechooser() fname, format_ = chooser.get_filename_from_user() chooser.destroy() if not fname: return - self.save_figures(fname, format_) + self._save_figures(fname, format_) - def save_figures(self, basename, format_): + def _save_figures(self, basename, format_): figs = self.figures startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) if startpath == '': @@ -995,7 +1122,7 @@ def save_figures(self, basename, format_): except Exception as e: error_msg_gtk(str(e), parent=canvas.manager.window) - def get_filechooser(self): + def _get_filechooser(self): fc = FileChooserDialog( title=self.title, parent=self.ref_canvas.manager.window, From 61d8427b870b1eff8323fbc221430d21e7e37509 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Tue, 17 Sep 2013 19:42:18 -0400 Subject: [PATCH 06/20] Cleanup, commit to show what can be done --- examples/pylab_examples/multiple_figs_demo.py | 17 +- lib/matplotlib/backends/backend_gtk3.py | 524 +++++++++++------- 2 files changed, 346 insertions(+), 195 deletions(-) diff --git a/examples/pylab_examples/multiple_figs_demo.py b/examples/pylab_examples/multiple_figs_demo.py index ad71f863d15f..7fd89cf177c8 100644 --- a/examples/pylab_examples/multiple_figs_demo.py +++ b/examples/pylab_examples/multiple_figs_demo.py @@ -3,10 +3,21 @@ import matplotlib matplotlib.use('gtk3agg') -# from matplotlib.backends.backend_gtk3 import SaveFiguresDialogGTK3 matplotlib.rcParams['backend.gtk3.tabbed'] = True from pylab import * +from matplotlib.backends.backend_gtk3 import ToolBase +class SampleNonGuiTool(ToolBase): + def set_figures(self, *figures): + #stupid routine that says how many axes and lines are in each + #figure + for figure in figures: + title = figure.canvas.get_window_title() + print(title) + lines = [line for ax in figure.axes for line in ax.lines] + print('Axes: %d Lines: %d' % (len(figure.axes), len(lines))) + + t = arange(0.0, 2.0, 0.01) s1 = sin(2 * pi * t) s2 = sin(4 * pi * t) @@ -31,4 +42,8 @@ figure(2) savefig('fig2') +#figure(2).canvas.toolbar.add_tool(SampleNonGuiTool, text='Stats') + + + show() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 4f3ac63f2d74..a8a86d69d218 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -477,6 +477,13 @@ def resize(self, width, height): class ChildFigureManager(FigureManagerBase): + #to acces from figure instance + #figure.canvas.manager + # + #This is an intermediate class and just exposes the figure manager functionality to + #parent. In general there is no need to subclass it. + #To change the figure manager functionality, subclass MultiFigureManagerBase + parent = None _parent_class = None @@ -485,6 +492,10 @@ def initialize(cls): if cls.parent is None: cls.parent = cls._parent_class() + @classmethod + def set_figure_manager(cls, parent_class): + cls._parent_class = parent_class + def __init__(self, canvas, num): self.initialize() FigureManagerBase.__init__(self, canvas, num) @@ -507,6 +518,7 @@ def show(self): self.parent.show_child(self) def destroy(self): + #this method is called from Gcf.destroy(num) if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.canvas.destroy() self.parent.remove_child(self) @@ -514,17 +526,9 @@ def destroy(self): def resize(self, w, h): self.parent.resize_manager(self, w, h) -# def key_press(self, event): -# key_press_handler(event, self.canvas, self.canvas.toolbar) - def show_popup(self, msg): self.parent.show_popup(self, msg) - # Here is a little bit counter intuitive, but for exising code - # one expects the set/get _window_title methods, to change the title - # of the window of the figure. - # Because we have many figures, the mainwindow is the window - # that includes (control) all the figures def get_window_title(self): return self.parent.get_child_title(self) @@ -538,12 +542,81 @@ def set_mainwindow_title(self, title): self.parent.set_window_title(title) -class MultiFigureManagerGTK3(object): +class MultiFigureManagerBase(object): + def __init__(self): + #Create the main window, + #add the widget that will contain the children + #add the multi-figure-toolbar + raise NotImplementedError + + def switch_child(self, child): + #Call this method when you have located the child + #this just inform the multi-figure-toolbar that the child has changed + #For example in the gtk3 backend, this is called after finding + #the new selected tab + + if self.toolbar is None: + return + self.toolbar.switch_child(child.toolbar) + + def destroy(self): + pass + + def add_child(self, child): + #add the child to the multi-figure-manager + #this is is the place were you should add an individual close button for the child + #this close action should call Gcf.destroy(num) + raise NotImplementedError + + def remove_child(self, child): + #Remove the child from the control of this multi-figure-manager + #visually and logically + #do not destroy the child + raise NotImplementedError + + def show_child(self, child): + """Find the appropiate child container and show it""" + pass + + def set_child_title(self, child, title): + """ + Set the title text of the container containing the figure. + """ + pass + + def get_child_title(self, child): + """ + Get the title text of the container containing the figure + """ + pass + + def set_window_title(self, title): + """ + Set the title text of the multi-figure-manager window. + """ + pass + + def get_window_title(self): + """ + Get the title text of the multi-figure-manager window. + """ + pass + + def show(self): + """Show the multi-figure-manager""" + pass + + +class MultiFigureManagerGTK3(MultiFigureManagerBase): + #to acces from figure instance + #figure.canvas.manager.parent!!!!! + # + _children = [] _labels = {} _w_min = 0 _h_min = 0 - + def __init__(self, *args): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.window = Gtk.Window() @@ -562,7 +635,6 @@ def __init__(self, *args): self.vbox = Gtk.Box() self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL) -# self.vbox.show() self.notebook = Gtk.Notebook() @@ -596,12 +668,10 @@ def destroy_window(*args): self.window.show() def _on_switch_page(self, notebook, pointer, num): - if self.toolbar is None: - return canvas = self.notebook.get_nth_page(num) - self.toolbar.switch_toolbar(canvas.toolbar) + self.switch_child(canvas) - def destroy(self, *args): + def destroy(self): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.vbox.destroy() @@ -630,27 +700,15 @@ def remove_child(self, child): self.destroy() def set_child_title(self, child, title): - """ - Set the title text of the container containing the figure. - """ self._labels[child.num].set_text(title) def get_child_title(self, child): - """ - Get the title text of the container containing the figure - """ - self._labels[child.num].get_text() + return self._labels[child.num].get_text() def set_window_title(self, title): - """ - Set the title text of the multi-figure-manager window. - """ self.window.set_title(title) def get_window_title(self): - """ - Get the title text of the multi-figure-manager window. - """ return self.window.get_title() def _get_toolbar(self): @@ -700,6 +758,7 @@ def _remove(btn): w = int(canvas.figure.bbox.width) h = int(canvas.figure.bbox.height) + #we have to put the size of the window as the maximum canvas size if w > self._w_min: self._w_min = w if h > self._h_min: @@ -710,7 +769,6 @@ def _remove(btn): canvas.grab_focus() def show_child(self, child): - """Find the appropiate child container and show it""" if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.show() canvas = child.canvas @@ -718,17 +776,22 @@ def show_child(self, child): self.notebook.set_current_page(id_) def show(self): - """Show the multi-figure-manager""" if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.window.show_all() if rcParams['backend.gtk3.tabbed']: - ChildFigureManager._parent_class = MultiFigureManagerGTK3 + ChildFigureManager.set_figure_manager(MultiFigureManagerGTK3) FigureManagerGTK3 = ChildFigureManager class ChildNavigationToolbar(NavigationToolbar2): + #to acces from figure instance + #figure.canvas.toolbar + # + #There is no need to subclass this, if you want to change the toolbar, + #change multi-figure-toolbar + def __init__(self, canvas, parent): self.parent = parent NavigationToolbar2.__init__(self, canvas) @@ -773,127 +836,243 @@ def draw_rubberband(self, event, x0, y0, x1, y1): self.ctx.set_source_rgb(0, 0, 0) self.ctx.stroke() - - - - - -class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar): - _toolbars = [] + def add_tool(self, *args, **kwargs): + self.parent.add_tool(*args, **kwargs) + +class MultiFigureToolbarBase(object): + #to acces from figure instance + #figure.canvas.toolbar.parent + # + # + #The mandatory things you have to implement are + #add_button, + #connect_button + #init_toolbar + #save_figure + # toolitems = ({'text': 'Home', 'tooltip_text': 'Reset original view', - 'stock_image': 'home', + 'image': 'home', 'callback': 'home'}, {'text': 'Back', 'tooltip_text': 'Back to previous view', - 'stock_image': 'back', + 'image': 'back', 'callback': 'back'}, {'text': 'Forward', 'tooltip_text': 'Forward to next view', - 'stock_image': 'forward', + 'image': 'forward', 'callback': 'forward'}, None, {'text': 'Pan', 'tooltip_text': 'Pan axes with left mouse, zoom with right', - 'stock_image': 'move', + 'image': 'move', 'callback': 'pan', 'toggle': True}, {'text': 'Zoom', 'tooltip_text': 'Zoom to rectangle', - 'stock_image': 'zoom_to_rect', + 'image': 'zoom_to_rect', 'callback': 'zoom', 'toggle': True}, + {'text': 'Save', + 'tooltip_text': 'Save the figure', + 'image': 'filesave', + 'callback': 'save_figure'}, + None, ) - - external_toolitems = [{'text': 'Subplots', + external_toolitems = ({'text': 'Subplots', 'tooltip_text': 'Configure subplots', - 'stock_image': 'subplots', - 'callback_class': 'ConfigureSubplotsGTK3', - 'all_figures': False}, - - {'text': 'Save', - 'tooltip_text': 'Save the figure', - 'stock_image': 'filesave', - 'callback_class': 'SaveFiguresDialogGTK3', - 'all_figures': False - } + 'image': 'subplots', + 'callback': 'ConfigureSubplotsGTK3'}, - ] -# {'text': -# 'tooltip_text': -# 'stock_image': -# 'callback': -# }, -# -# -# ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'), -# (), - -# extra_items = [('SaveAll', 'Save all figures', 'filesave', 'save_all_figures'), ] -# # It is more clear to have toggle buttons for these two options - _toggle = [] -# _destroy_on_switch = weakref.WeakValueDictionary() - _inform_at_switch = weakref.WeakValueDictionary() - - def __init__(self, window): - self.win = window - -# self.toolitems.extend(self.extra_items) + {'text': 'Save All', + 'tooltip_text': 'Save all figures', + 'image': 'saveall_icon', + 'callback': 'SaveFiguresDialogGTK3'}, + ) + + _external_instances = weakref.WeakValueDictionary() + _toolbars = [] + + def __init__(self): self.init_toolbar() + + for pos, btn in enumerate(self.toolitems): + if btn is None: + self.add_separator(pos=pos) + continue + callback = btn.pop('callback') + tbutton = self.add_button(pos=pos, **btn) + if tbutton: + self.connect_button(tbutton, 'clicked', callback) + for pos, btn in enumerate(self.external_toolitems): - self.add_button(btn, pos) + callback = btn.pop('callback') + self.add_tool(callback, pos=pos, **btn) + + self.add_separator(len(self.external_toolitems) + len(self.toolitems)) self._current = None - - def add_button(self, btn, pos): + + + def add_tool(self, callback, pos=0, **kwargs): + #this method called from the exterior and from the interior + #will add a tool to the toolbar + #this tool, will behave like normal button + #the first time it is clicked, it will get all the figures + #after that, if it is clicked again, it will call the show method + #if the _current changes (switch the active figure from the manager) + #the set_figures method is invoked again pos = len(self.toolitems) + pos - tbutton = self._add_button(btn, pos) + tbutton = self.add_button(pos=pos, **kwargs) if not tbutton: return - tbutton.connect('clicked', self._external_callback, btn) - def _external_callback(self, btn, btn_info): - cs = self._inform_at_switch.get(btn, None) - if cs is not None: - cs.show() - return + self.connect_button(tbutton, 'clicked', '_external_callback', callback) - if btn_info.get('all_figures', False): - figures = [toolbar.canvas.figure for toolbar in self._toolbars] - else: - figures = (self._current.canvas.figure, ) + def connect_button(self, button, action, callback, *args): + #This is specific to each library, + #The idea is to get rid of different formating between libraries and + #be able to call internal functions with clicks, selects, etc... + # + #In Gtk for example + #def connect_button(self, button, action, callback, *args): + # def mcallback(btn, *args): + # cb = args[0] + # other = args[1:] + # getattr(self, cb)(*other) + # + # button.connect(action, mcallback, callback, *args) + + raise NotImplementedError + + def _external_callback(self, callback): + #This handles the invokation of external classes + #this callback class should take only *figures as arguments + #and preform its work on those figures + #the instance of this callback is added to _external_instances + #as a weakreference to inform them of the switch and destroy + + id_ = id(callback) + + if id_ in self._external_instances: + self._external_instances[id_].show() + return + + figures = self.get_figures() + cls = self._get_cls_to_instantiate(callback) + + external_instance = cls(*figures) - cls = btn_info['callback_class'] - if isinstance(cls, basestring): - cls = globals()[cls] - cs = cls() - self._inform_at_switch[btn] = cs - cs.set_figures(*figures) + self._external_instances[id_] = external_instance - print('externalcallback') - for v in self._inform_at_switch.values(): - print (v) + def _get_cls_to_instantiate(self, callback_class): + #very basic mthod to get the class to instantiate + #do we want something complex like django for models? + if isinstance(callback_class, basestring): + return globals()[callback_class] + return callback_class + + def home(self, *args): + self._current.home(*args) + + def back(self, *args): + self._current.back(*args) + + def forward(self, *args): + self._current.forward(*args) + + def pan(self, *args): + self._current.pan(*args) + + def zoom(self, *args): + self._current.zoom(*args) + + def add_toolbar(self, toolbar): + #this method is called from the child toolbar + self._toolbars.append(toolbar) + self._current = toolbar + + def get_figures(self): + #return an array of figures, with the current as the firstone + figures = [self._current.canvas.figure] + others = [toolbar.canvas.figure for toolbar in self._toolbars if toolbar is not self._current] + figures.extend(others) + return figures + + def add_button(self, text='_', pos=-1, + tooltip_text='', image=None, + toggle=False): + #This should create the button in the toolbar + raise NotImplementedError + + def add_separator(self, pos=0): + pass + + def switch_child(self, toolbar): + #when multi-figure-manager switches child (figure) + #this toolbar needs to switch to, so it controls the correct one + #if there are external instances (tools) inform them of the switch + #by invoking instance.set_figures(*figures) + + if toolbar not in self._toolbars: + raise AttributeError('This container does not control the given toolbar') - def _add_button(self, btn, pos): - if btn is None: - self.insert(Gtk.SeparatorToolItem(), pos) - return None - - stock_image = btn.get('stock_image', False) - if stock_image: - basedir = os.path.join(rcParams['datapath'], 'images') - fname = os.path.join(basedir, stock_image + '.png') - image = Gtk.Image() - image.set_from_file(fname) + # For these two actions we have to unselect and reselect + d = {'PAN': 'pan', 'ZOOM': 'zoom'} + action = d.get(self._current._active, False) + if action: + getattr(self._current, action)() + getattr(toolbar, action)() + self._current = toolbar + + figures = self.get_figures() + for v in self._external_instances.values(): +# print('setting', v) + v.set_figures(*figures) + + def set_child_message(self, child, text): + #if the child toolbar has a message + pass + + +class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar, MultiFigureToolbarBase): + _toggle = [] + + def __init__(self, window): + self.win = window + MultiFigureToolbarBase.__init__(self) + + def connect_button(self, button, action, callback, *args): + def mcallback(btn, *args): + cb = args[0] + other = args[1:] + getattr(self, cb)(*other) + + button.connect(action, mcallback, callback, *args) + + def add_button(self, text='_', pos=-1, + tooltip_text='', image=None, + toggle=False): + + timage = None + if image: + timage = Gtk.Image() + if os.path.isfile(image): + timage.set_from_file(image) + else: + #FIXME: add the possibility to load from inline string + basedir = os.path.join(rcParams['datapath'], 'images') + fname = os.path.join(basedir, image + '.png') + timage.set_from_file(fname) + - toggle = btn.get('toggle', False) if toggle: tbutton = Gtk.ToggleToolButton() self._toggle.append(tbutton) @@ -903,23 +1082,24 @@ def _add_button(self, btn, pos): # attach to _something_clicked so it untoggles the toggled button tbutton.connect('clicked', self._something_clicked) - tbutton.set_label(btn['text']) - tbutton.set_icon_widget(image) - tbutton.set_tooltip_text(btn.get('tooltip_text', None)) + tbutton.set_label(text) + if timage: + tbutton.set_icon_widget(timage) + tbutton.set_tooltip_text(tooltip_text) self.insert(tbutton, pos) return tbutton + def add_separator(self, pos=-1): + toolitem = Gtk.SeparatorToolItem() + self.insert(toolitem, pos) + return toolitem + def init_toolbar(self): Gtk.Toolbar.__init__(self) self.set_style(Gtk.ToolbarStyle.ICONS) - for btn in self.toolitems: - tbutton = self._add_button(btn, -1) - if tbutton: - tbutton.connect('clicked', getattr(self, btn['callback'])) - - toolitem = Gtk.SeparatorToolItem() - self.insert(toolitem, -1) + + toolitem = self.add_separator() toolitem.set_draw(False) toolitem.set_expand(True) @@ -930,59 +1110,13 @@ def init_toolbar(self): self.show_all() - def switch_toolbar(self, toolbar): - if toolbar not in self._toolbars: - raise AttributeError('This container does not control the given toolbar') - - print('onswitch') - for v in self._inform_at_switch.values(): - print (v) - -# print(self._destroy_on_switch) -# for k, v in self._destroy_on_switch.items(): -# print(k, v) -# -# for w in self._destroy_on_switch: -# o = w() -# if o is None: -# continue -# -# try: -# o.destroy() -# except: -# pass -# self._destroy_on_switch = [] - - # For these two actions we have to unselect and reselect - d = {'PAN': 'pan', 'ZOOM': 'zoom'} - action = d.get(self._current._active, False) - if action: - getattr(self._current, action)() - getattr(toolbar, action)() - self._current = toolbar - - def home(self, *args): - self._current.home(*args) - - def back(self, *args): - self._current.back(*args) - - def forward(self, *args): - self._current.forward(*args) - - def pan(self, *args): - self._current.pan(*args) - - def zoom(self, *args): - self._current.zoom(*args) - - def add_toolbar(self, toolbar): - self._toolbars.append(toolbar) - self._current = toolbar - + def save_figure(self, *args): + sd = SaveFiguresDialogGTK3(self.get_figures()[0]) + def _something_clicked(self, btn): #when something is clicked, untoggle all toggle buttons #if it is a toggle button, untoggle the other toggle buttons + #I added this because zoom and pan are toggle now, and they are exclusive for i in self._toggle: if i is not btn: if i.get_active(): @@ -990,42 +1124,40 @@ def _something_clicked(self, btn): i.set_active(False) i.handler_unblock_by_func(self._something_clicked) - def save_figure(self, *args): - figure = self._current.canvas.figure - dialog = SaveFiguresDialogGTK3(figure) - - def save_all_figures(self, *args): - figures = [toolbar.canvas.figure for toolbar in self._toolbars] - SaveFiguresDialogGTK3(*figures) - - def configure_subplots(self, button): - ConfigureSubplotsGTK3(self._current.canvas.figure) -# self._destroy_on_switch['conf'] = weakref.proxy(cf) -# self.hola = weakref.proxy(cf) - def set_child_message(self, child, text): self.message.set_label(text) class ToolBase(object): + #basic structure for the external tools that work with + #multi-figure-toolbar def __init__(self, *figures): self.init_tool() + if figures: self.set_figures(*figures) def init_tool(self): + #do some initialization work as create windows and stuff pass def set_figures(self, *figures): + #this is the main work, many non gui tools use only this one + #make sure it receives an array *figures. The toolbar caller + #always sent an array with all the figures + #the first figure of the array is the current figure (toolbar point of view) + #if it uses only the fisrt one, use it as figure = figures[0] raise NotImplementedError - def destroy(self): + def destroy(self, *args): + #called when we want to kill the tool from the creator (toolbar) pass def show(self): + #called when need to bring to focus this specific tool pass - - + + class ConfigureSubplotsGTK3(ToolBase): def init_tool(self): self.window = Gtk.Window() @@ -1044,13 +1176,17 @@ def init_tool(self): self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL) self.window.add(self.vbox) self.vbox.show() + self.window.connect('destroy', self.destroy) - def set_figures(self, figure): + def reset(self, *args): children = self.vbox.get_children() for child in children: - child.destroy() + self.vbox.remove(child) del children + def set_figures(self, *figures): + self.reset() + figure = figures[0] toolfig = Figure(figsize=(6, 3)) canvas = figure.canvas.__class__(toolfig) @@ -1066,11 +1202,13 @@ def set_figures(self, figure): self.vbox.pack_start(canvas, True, True, 0) self.window.show() + def show(self): + self.window.present() + class SaveFiguresDialogGTK3(ToolBase): def set_figures(self, *figs): - print('savefigure', figs) ref_figure = figs[0] self.figures = figs @@ -1081,16 +1219,14 @@ def set_figures(self, *figs): if len(figs) > 1: fname_end = '.' + self.ref_canvas.get_default_filetype() self.current_name = self.current_name[:-len(fname_end)] - self.show() - - def show(self): + chooser = self._get_filechooser() fname, format_ = chooser.get_filename_from_user() chooser.destroy() if not fname: return self._save_figures(fname, format_) - + def _save_figures(self, basename, format_): figs = self.figures startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) From b9204e55014c1f29fa55b35f80195e780bfa6526 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Tue, 17 Sep 2013 19:47:15 -0400 Subject: [PATCH 07/20] Forgot to include external button --- examples/pylab_examples/multiple_figs_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pylab_examples/multiple_figs_demo.py b/examples/pylab_examples/multiple_figs_demo.py index 7fd89cf177c8..d9ed9190c1ca 100644 --- a/examples/pylab_examples/multiple_figs_demo.py +++ b/examples/pylab_examples/multiple_figs_demo.py @@ -42,7 +42,7 @@ def set_figures(self, *figures): figure(2) savefig('fig2') -#figure(2).canvas.toolbar.add_tool(SampleNonGuiTool, text='Stats') +figure(2).canvas.toolbar.add_tool(SampleNonGuiTool, text='Stats') From 0f6023e317e7363ae565eec7251cf7e8a7df8a3b Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 18 Sep 2013 11:54:57 -0400 Subject: [PATCH 08/20] Placing stuff in backend_bases.py and renaming rc param to single_window --- examples/pylab_examples/multiple_figs_demo.py | 2 +- lib/matplotlib/backend_bases.py | 450 +++++++- lib/matplotlib/backends/backend_gtk3.py | 989 ++++++------------ lib/matplotlib/backends/backend_gtk3cairo.py | 1 - lib/matplotlib/rcsetup.py | 3 +- matplotlibrc.template | 2 +- 6 files changed, 745 insertions(+), 702 deletions(-) diff --git a/examples/pylab_examples/multiple_figs_demo.py b/examples/pylab_examples/multiple_figs_demo.py index d9ed9190c1ca..fe4008306a04 100644 --- a/examples/pylab_examples/multiple_figs_demo.py +++ b/examples/pylab_examples/multiple_figs_demo.py @@ -6,7 +6,7 @@ matplotlib.rcParams['backend.gtk3.tabbed'] = True from pylab import * -from matplotlib.backends.backend_gtk3 import ToolBase +from matplotlib.backend_bases import ToolBase class SampleNonGuiTool(ToolBase): def set_figures(self, *figures): #stupid routine that says how many axes and lines are in each diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8ef23af3e755..8acf12b322ae 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -37,6 +37,7 @@ import warnings import time import io +import weakref import numpy as np import matplotlib.cbook as cbook @@ -2150,7 +2151,7 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', bbox_inches = kwargs.pop("bbox_inches", None) if bbox_inches is None: bbox_inches = rcParams['savefig.bbox'] - + if bbox_inches: # call adjust_bbox to save only the given area if bbox_inches == "tight": @@ -2627,6 +2628,138 @@ def set_window_title(self, title): pass +class ChildFigureManager(FigureManagerBase): + #to acces from figure instance + #figure.canvas.manager + # + #This is an intermediate class and just exposes the figure manager functionality to + #parent. In general there is no need to subclass it. + #To change the figure manager functionality, subclass MultiFigureManagerBase + + parent = None + _parent_class = None + + @classmethod + def initialize(cls): + if cls.parent is None: + cls.parent = cls._parent_class() + + @classmethod + def set_figure_manager(cls, parent_class): + cls._parent_class = parent_class + + def __init__(self, canvas, num): + self.initialize() + FigureManagerBase.__init__(self, canvas, num) + + if self.parent.toolbar is None: + self.toolbar = None + else: + self.toolbar = ChildNavigationToolbar(self.canvas, self.parent.toolbar) + self.parent.add_child(self) + + self.canvas.show() + self.window = self.parent.window + + def notify_axes_change(fig): + 'this will be called whenever the current axes is changed' + if self.toolbar is not None: self.toolbar.update() + canvas.figure.add_axobserver(notify_axes_change) + + def show(self): + self.parent.show_child(self) + + def destroy(self): + #this method is called from Gcf.destroy(num) + self.canvas.destroy() + self.parent.remove_child(self) + if self.toolbar: + self.toolbar.remove() + + def resize(self, w, h): + self.parent.resize_manager(self, w, h) + + def show_popup(self, msg): + self.parent.show_popup(self, msg) + + def get_window_title(self): + return self.parent.get_child_title(self) + + def set_window_title(self, title): + self.parent.set_child_title(self, title) + + def get_mainwindow_title(self): + return self.parent.get_window_title() + + def set_mainwindow_title(self, title): + self.parent.set_window_title(title) + + +class MultiFigureManagerBase(object): + def __init__(self): + #Create the main window, + #add the widget that will contain the children + #add the multi-figure-toolbar + raise NotImplementedError + + def switch_child(self, child): + #Call this method when you have located the child + #this just inform the multi-figure-toolbar that the child has changed + #For example in the gtk3 backend, this is called after finding + #the new selected tab + + if self.toolbar is None: + return + self.toolbar.switch_child(child.toolbar) + + def destroy(self): + pass + + def add_child(self, child): + #add the child to the multi-figure-manager + #this is is the place were you should add an individual close button for the child + #this close action should call Gcf.destroy(num) + raise NotImplementedError + + def remove_child(self, child): + #Remove the child from the control of this multi-figure-manager + #visually and logically + #do not destroy the child + raise NotImplementedError + + def show_child(self, child): + """Find the appropiate child container and show it""" + pass + + def set_child_title(self, child, title): + """ + Set the title text of the container containing the figure. + """ + pass + + def get_child_title(self, child): + """ + Get the title text of the container containing the figure + """ + pass + + def set_window_title(self, title): + """ + Set the title text of the multi-figure-manager window. + """ + pass + + def get_window_title(self): + """ + Get the title text of the multi-figure-manager window. + """ + pass + + def show(self): + """Show the multi-figure-manager""" + pass + + class Cursors: # this class is only used as a simple namespace HAND, POINTER, SELECT_REGION, MOVE = list(range(4)) @@ -3207,3 +3340,318 @@ def zoom(self, *args): def set_history_buttons(self): """Enable or disable back/forward button""" pass + + +class ChildNavigationToolbar(NavigationToolbar2): + #to acces from figure instance + #figure.canvas.toolbar + # + #There is no need to subclass this, if you want to change the toolbar, + #change multi-figure-toolbar + + def __init__(self, canvas, parent): + self.parent = parent + NavigationToolbar2.__init__(self, canvas) + + def _init_toolbar(self): + self.parent.add_toolbar(self) + self.ctx = None + + def remove(self): + #called by ChildFigureManager.destroy method + self.parent.remove_toolbar(self) + + def set_message(self, s): + self.parent.set_child_message(self, s) + + def set_cursor(self, cursor): + self.parent.set_child_cursor(self, cursor) +# self.canvas.get_property("window").set_cursor(cursord[cursor]) + + def release(self, event): + try: del self._pixmapBack + except AttributeError: pass + + def dynamic_update(self): + # legacy method; new method is canvas.draw_idle + self.canvas.draw_idle() + + def draw_rubberband(self, event, x0, y0, x1, y1): + 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' + self.ctx = self.canvas.get_property("window").cairo_create() + + # todo: instead of redrawing the entire figure, copy the part of + # the figure that was covered by the previous rubberband rectangle + self.canvas.draw() + + height = self.canvas.figure.bbox.height + y1 = height - y1 + y0 = height - y0 + w = abs(x1 - x0) + h = abs(y1 - y0) + rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] + + self.ctx.new_path() + self.ctx.set_line_width(0.5) + self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) + self.ctx.set_source_rgb(0, 0, 0) + self.ctx.stroke() + + def add_tool(self, *args, **kwargs): + self.parent.add_tool(*args, **kwargs) + + +class MultiFigureToolbarBase(object): + #to acces from figure instance + #figure.canvas.toolbar.parent + # + # + #The mandatory things you have to implement are + #add_button, + #connect_button + #init_toolbar + #save_figure + # + toolitems = ({'text': 'Home', + 'tooltip_text': 'Reset original view', + 'image': 'home', + 'callback': 'home'}, + + {'text': 'Back', + 'tooltip_text': 'Back to previous view', + 'image': 'back', + 'callback': 'back'}, + + {'text': 'Forward', + 'tooltip_text': 'Forward to next view', + 'image': 'forward', + 'callback': 'forward'}, + + None, + + {'text': 'Pan', + 'tooltip_text': 'Pan axes with left mouse, zoom with right', + 'image': 'move', + 'callback': 'pan', + 'toggle': True}, + + {'text': 'Zoom', + 'tooltip_text': 'Zoom to rectangle', + 'image': 'zoom_to_rect', + 'callback': 'zoom', + 'toggle': True}, + + {'text': 'Save', + 'tooltip_text': 'Save the figure', + 'image': 'filesave', + 'callback': 'save_figure'}, + + None, + ) + external_toolitems = ( +# {'text': 'Subplots', +# 'tooltip_text': 'Configure subplots', +# 'image': 'subplots', +# 'callback': 'ConfigureSubplotsGTK3'}, +# +# {'text': 'Save All', +# 'tooltip_text': 'Save all figures', +# 'image': 'saveall_icon', +# 'callback': 'SaveFiguresDialogGTK3'}, + ) + + _external_instances = weakref.WeakValueDictionary() + _toolbars = [] + + def __init__(self): + self.init_toolbar() + + for pos, btn in enumerate(self.toolitems): + if btn is None: + self.add_separator(pos=pos) + continue + callback = btn.pop('callback') + tbutton = self.add_button(pos=pos, **btn) + if tbutton: + self.connect_button(tbutton, 'clicked', callback) + + for pos, btn in enumerate(self.external_toolitems): + callback = btn.pop('callback') + self.add_tool(callback, pos=pos, **btn) + + self.add_separator(len(self.external_toolitems) + len(self.toolitems)) + self._current = None + + + def add_tool(self, callback, pos=0, **kwargs): + #this method called from the exterior and from the interior + #will add a tool to the toolbar + #this tool, will behave like normal button + #the first time it is clicked, it will get all the figures + #after that, if it is clicked again, it will call the show method + #if the _current changes (switch the active figure from the manager) + #the set_figures method is invoked again + pos = len(self.toolitems) + pos + tbutton = self.add_button(pos=pos, **kwargs) + if not tbutton: + return + + self.connect_button(tbutton, 'clicked', '_external_callback', callback) + + def connect_button(self, button, action, callback, *args): + #This is specific to each library, + #The idea is to get rid of different formating between libraries and + #be able to call internal functions with clicks, selects, etc... + # + #In Gtk for example + #def connect_button(self, button, action, callback, *args): + # def mcallback(btn, *args): + # cb = args[0] + # other = args[1:] + # getattr(self, cb)(*other) + # + # button.connect(action, mcallback, callback, *args) + + raise NotImplementedError + + def _external_callback(self, callback): + #This handles the invokation of external classes + #this callback class should take only *figures as arguments + #and preform its work on those figures + #the instance of this callback is added to _external_instances + #as a weakreference to inform them of the switch and destroy + + id_ = id(callback) + + if id_ in self._external_instances: + self._external_instances[id_].show() + return + + figures = self.get_figures() + cls = self._get_cls_to_instantiate(callback) + if not cls: + self.set_message('Not available') + return + + external_instance = cls(*figures) + + self._external_instances[id_] = external_instance + + def _get_cls_to_instantiate(self, callback_class): + if isinstance(callback_class, basestring): + #FIXME: make more complete searching structure + if callback_class in globals(): + return globals()[callback_class] + + mod = self.__class__.__module__ + current_module = __import__(mod, + globals(),locals(), [mod],0) + + return getattr(current_module, callback_class, False) + + return callback_class + + def home(self, *args): + self._current.home(*args) + + def back(self, *args): + self._current.back(*args) + + def forward(self, *args): + self._current.forward(*args) + + def pan(self, *args): + self._current.pan(*args) + + def zoom(self, *args): + self._current.zoom(*args) + + def add_toolbar(self, toolbar): + #this method is called from the child toolbar + self._toolbars.append(toolbar) + self._current = toolbar + + def remove_toolbar(self, toolbar): + self._toolbars.remove(toolbar) + if toolbar is self._current: + self._current = None + + def get_figures(self): + #return an array of figures, with the current as the firstone + figures = [self._current.canvas.figure] + others = [toolbar.canvas.figure for toolbar in self._toolbars if toolbar is not self._current] + figures.extend(others) + return figures + + def add_button(self, text='_', pos=-1, + tooltip_text='', image=None, + toggle=False): + #This should create the button in the toolbar + raise NotImplementedError + + def add_separator(self, pos=0): + pass + + def switch_child(self, toolbar): + #when multi-figure-manager switches child (figure) + #this toolbar needs to switch to, so it controls the correct one + #if there are external instances (tools) inform them of the switch + #by invoking instance.set_figures(*figures) + + if toolbar not in self._toolbars: + raise AttributeError('This container does not control the given toolbar') + + # For these two actions we have to unselect and reselect + d = {'PAN': 'pan', 'ZOOM': 'zoom'} + action = d.get(self._current._active, False) + if action: + getattr(self._current, action)() + getattr(toolbar, action)() + self._current = toolbar + + figures = self.get_figures() + for v in self._external_instances.values(): +# print('setting', v) + v.set_figures(*figures) + + def set_child_message(self, child, text): + #In general the mssage from the child are displayed the + #same as message from the parent + self.set_message(text) + + def set_child_cursor(self, child, cursor): + pass + + def set_message(self, text): + pass + + +class ToolBase(object): + #basic structure for the external tools that work with + #multi-figure-toolbar + def __init__(self, *figures): + self.init_tool() + + if figures: + self.set_figures(*figures) + + def init_tool(self): + #do some initialization work as create windows and stuff + pass + + def set_figures(self, *figures): + #this is the main work, many non gui tools use only this one + #make sure it receives an array *figures. The toolbar caller + #always sent an array with all the figures + #the first figure of the array is the current figure (toolbar point of view) + #if it uses only the fisrt one, use it as figure = figures[0] + raise NotImplementedError + + def destroy(self, *args): + #called when we want to kill the tool from the creator (toolbar) + pass + + def show(self): + #called when need to bring to focus this specific tool + pass + diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a8a86d69d218..2d0d11e4395d 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -2,7 +2,6 @@ unicode_literals) import six -import weakref import os, sys def fn_name(): return sys._getframe(1).f_code.co_name @@ -30,7 +29,8 @@ 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, \ + MultiFigureManagerBase, MultiFigureToolbarBase, ToolBase, ChildFigureManager from matplotlib.backend_bases import ShowBase from matplotlib.cbook import is_string_like, is_writable_file_like @@ -46,6 +46,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) _debug = False +#_debug = True # the true dots per inch on the screen; should be display dependent # see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi @@ -63,7 +64,7 @@ def draw_if_interactive(): Is called after every pylab drawing command """ if matplotlib.is_interactive(): - figManager = Gcf.get_active() + figManager = Gcf.get_active() if figManager is not None: figManager.canvas.draw_idle() @@ -170,13 +171,13 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): # Setting this as a static constant prevents # this resulting expression from leaking - event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK | + event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.EXPOSURE_MASK | - Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | - Gdk.EventMask.ENTER_NOTIFY_MASK | - Gdk.EventMask.LEAVE_NOTIFY_MASK | + Gdk.EventMask.EXPOSURE_MASK | + Gdk.EventMask.KEY_PRESS_MASK | + Gdk.EventMask.KEY_RELEASE_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK | + Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK) @@ -185,20 +186,20 @@ def __init__(self, figure): FigureCanvasBase.__init__(self, figure) GObject.GObject.__init__(self) - self._idle_draw_id = 0 - self._need_redraw = True - self._lastCursor = None + self._idle_draw_id = 0 + self._need_redraw = True + self._lastCursor = None - self.connect('scroll_event', self.scroll_event) - self.connect('button_press_event', self.button_press_event) + self.connect('scroll_event', self.scroll_event) + self.connect('button_press_event', self.button_press_event) self.connect('button_release_event', self.button_release_event) - self.connect('configure_event', self.configure_event) - self.connect('draw', self.on_draw_event) - self.connect('key_press_event', self.key_press_event) - self.connect('key_release_event', self.key_release_event) - self.connect('motion_notify_event', self.motion_notify_event) - self.connect('leave_notify_event', self.leave_notify_event) - self.connect('enter_notify_event', self.enter_notify_event) + self.connect('configure_event', self.configure_event) + self.connect('draw', self.on_draw_event) + self.connect('key_press_event', self.key_press_event) + self.connect('key_release_event', self.key_release_event) + self.connect('motion_notify_event', self.motion_notify_event) + self.connect('leave_notify_event', self.leave_notify_event) + self.connect('enter_notify_event', self.enter_notify_event) self.set_events(self.__class__.event_mask) @@ -208,7 +209,7 @@ def __init__(self, figure): self._idle_event_id = GObject.idle_add(self.idle_event) def destroy(self): - # Gtk.DrawingArea.destroy(self) + #Gtk.DrawingArea.destroy(self) self.close_event() GObject.source_remove(self._idle_event_id) if self._idle_draw_id != 0: @@ -219,7 +220,7 @@ def scroll_event(self, widget, event): x = event.x # flipy so y=0 is bottom of canvas y = self.get_allocation().height - event.y - if event.direction == Gdk.ScrollDirection.UP: + if event.direction==Gdk.ScrollDirection.UP: step = 1 else: step = -1 @@ -299,11 +300,11 @@ def configure_event(self, widget, event): return w, h = event.width, event.height if w < 3 or h < 3: - return # empty fig + return # empty fig # resize the figure (in inches) dpi = self.figure.dpi - self.figure.set_size_inches (w / dpi, h / dpi) + self.figure.set_size_inches (w/dpi, h/dpi) self._need_redraw = True return False # finish event propagation? @@ -351,13 +352,13 @@ def flush_events(self): Gdk.flush() Gdk.threads_leave() - def start_event_loop(self, timeout): - FigureCanvasBase.start_event_loop_default(self, timeout) - start_event_loop.__doc__ = FigureCanvasBase.start_event_loop_default.__doc__ + def start_event_loop(self,timeout): + FigureCanvasBase.start_event_loop_default(self,timeout) + start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__ = FigureCanvasBase.stop_event_loop_default.__doc__ + stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ FigureCanvas = FigureCanvasGTK3 @@ -433,9 +434,9 @@ def destroy(self, *args): self.canvas.destroy() if self.toolbar: self.toolbar.destroy() - self.__dict__.clear() # Is this needed? Other backends don't have it. + self.__dict__.clear() #Is this needed? Other backends don't have it. - if Gcf.get_num_fig_managers() == 0 and \ + if Gcf.get_num_fig_managers()==0 and \ not matplotlib.is_interactive() and \ Gtk.main_level() >= 1: Gtk.main_quit() @@ -470,143 +471,12 @@ def set_window_title(self, 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) + #_, _, 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 ChildFigureManager(FigureManagerBase): - #to acces from figure instance - #figure.canvas.manager - # - #This is an intermediate class and just exposes the figure manager functionality to - #parent. In general there is no need to subclass it. - #To change the figure manager functionality, subclass MultiFigureManagerBase - - parent = None - _parent_class = None - - @classmethod - def initialize(cls): - if cls.parent is None: - cls.parent = cls._parent_class() - - @classmethod - def set_figure_manager(cls, parent_class): - cls._parent_class = parent_class - - def __init__(self, canvas, num): - self.initialize() - FigureManagerBase.__init__(self, canvas, num) - - if self.parent.toolbar is None: - self.toolbar = None - else: - self.toolbar = ChildNavigationToolbar(self.canvas, self.parent.toolbar) - self.parent.add_child(self) - - self.canvas.show() - self.window = self.parent.window - - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolbar is not None: self.toolbar.update() - canvas.figure.add_axobserver(notify_axes_change) - - def show(self): - self.parent.show_child(self) - - def destroy(self): - #this method is called from Gcf.destroy(num) - if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) - self.canvas.destroy() - self.parent.remove_child(self) - - def resize(self, w, h): - self.parent.resize_manager(self, w, h) - - def show_popup(self, msg): - self.parent.show_popup(self, msg) - - def get_window_title(self): - return self.parent.get_child_title(self) - - def set_window_title(self, title): - self.parent.set_child_title(self, title) - - def get_mainwindow_title(self): - return self.parent.get_window_title() - - def set_mainwindow_title(self, title): - self.parent.set_window_title(title) - - -class MultiFigureManagerBase(object): - def __init__(self): - #Create the main window, - #add the widget that will contain the children - #add the multi-figure-toolbar - raise NotImplementedError - - def switch_child(self, child): - #Call this method when you have located the child - #this just inform the multi-figure-toolbar that the child has changed - #For example in the gtk3 backend, this is called after finding - #the new selected tab - - if self.toolbar is None: - return - self.toolbar.switch_child(child.toolbar) - - def destroy(self): - pass - - def add_child(self, child): - #add the child to the multi-figure-manager - #this is is the place were you should add an individual close button for the child - #this close action should call Gcf.destroy(num) - raise NotImplementedError - - def remove_child(self, child): - #Remove the child from the control of this multi-figure-manager - #visually and logically - #do not destroy the child - raise NotImplementedError - - def show_child(self, child): - """Find the appropiate child container and show it""" - pass - - def set_child_title(self, child, title): - """ - Set the title text of the container containing the figure. - """ - pass - - def get_child_title(self, child): - """ - Get the title text of the container containing the figure - """ - pass - - def set_window_title(self, title): - """ - Set the title text of the multi-figure-manager window. - """ - pass - - def get_window_title(self): - """ - Get the title text of the multi-figure-manager window. - """ - pass - - def show(self): - """Show the multi-figure-manager""" - pass - - class MultiFigureManagerGTK3(MultiFigureManagerBase): #to acces from figure instance #figure.canvas.manager.parent!!!!! @@ -780,32 +650,24 @@ def show(self): self.window.show_all() -if rcParams['backend.gtk3.tabbed']: +if rcParams['backend.single_window']: ChildFigureManager.set_figure_manager(MultiFigureManagerGTK3) FigureManagerGTK3 = ChildFigureManager -class ChildNavigationToolbar(NavigationToolbar2): - #to acces from figure instance - #figure.canvas.toolbar - # - #There is no need to subclass this, if you want to change the toolbar, - #change multi-figure-toolbar - - def __init__(self, canvas, parent): - self.parent = parent +class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): + def __init__(self, canvas, window): + self.win = window + GObject.GObject.__init__(self) NavigationToolbar2.__init__(self, canvas) - - def _init_toolbar(self): - self.parent.add_toolbar(self) self.ctx = None def set_message(self, s): - self.parent.set_child_message(self, s) + self.message.set_label(s) def set_cursor(self, cursor): self.canvas.get_property("window").set_cursor(cursord[cursor]) - # self.canvas.set_cursor(cursord[cursor]) + #self.canvas.set_cursor(cursord[cursor]) def release(self, event): try: del self._pixmapBack @@ -828,7 +690,7 @@ def draw_rubberband(self, event, x0, y0, x1, y1): y0 = height - y0 w = abs(x1 - x0) h = abs(y1 - y0) - rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] + rect = [int(val) for val in (min(x0,x1), min(y0, y1), w, h)] self.ctx.new_path() self.ctx.set_line_width(0.5) @@ -836,213 +698,110 @@ def draw_rubberband(self, event, x0, y0, x1, y1): self.ctx.set_source_rgb(0, 0, 0) self.ctx.stroke() - def add_tool(self, *args, **kwargs): - self.parent.add_tool(*args, **kwargs) - - -class MultiFigureToolbarBase(object): - #to acces from figure instance - #figure.canvas.toolbar.parent - # - # - #The mandatory things you have to implement are - #add_button, - #connect_button - #init_toolbar - #save_figure - # - toolitems = ({'text': 'Home', - 'tooltip_text': 'Reset original view', - 'image': 'home', - 'callback': 'home'}, - - {'text': 'Back', - 'tooltip_text': 'Back to previous view', - 'image': 'back', - 'callback': 'back'}, - - {'text': 'Forward', - 'tooltip_text': 'Forward to next view', - 'image': 'forward', - 'callback': 'forward'}, - - None, - - {'text': 'Pan', - 'tooltip_text': 'Pan axes with left mouse, zoom with right', - 'image': 'move', - 'callback': 'pan', - 'toggle': True}, - - {'text': 'Zoom', - 'tooltip_text': 'Zoom to rectangle', - 'image': 'zoom_to_rect', - 'callback': 'zoom', - 'toggle': True}, - - {'text': 'Save', - 'tooltip_text': 'Save the figure', - 'image': 'filesave', - 'callback': 'save_figure'}, - - None, - ) - external_toolitems = ({'text': 'Subplots', - 'tooltip_text': 'Configure subplots', - 'image': 'subplots', - 'callback': 'ConfigureSubplotsGTK3'}, - - {'text': 'Save All', - 'tooltip_text': 'Save all figures', - 'image': 'saveall_icon', - 'callback': 'SaveFiguresDialogGTK3'}, - ) - - _external_instances = weakref.WeakValueDictionary() - _toolbars = [] - - def __init__(self): - self.init_toolbar() - - for pos, btn in enumerate(self.toolitems): - if btn is None: - self.add_separator(pos=pos) + def _init_toolbar(self): + self.set_style(Gtk.ToolbarStyle.ICONS) + basedir = os.path.join(rcParams['datapath'],'images') + + for text, tooltip_text, image_file, callback in self.toolitems: + if text is None: + self.insert( Gtk.SeparatorToolItem(), -1 ) continue - callback = btn.pop('callback') - tbutton = self.add_button(pos=pos, **btn) - if tbutton: - self.connect_button(tbutton, 'clicked', callback) - - for pos, btn in enumerate(self.external_toolitems): - callback = btn.pop('callback') - self.add_tool(callback, pos=pos, **btn) - - self.add_separator(len(self.external_toolitems) + len(self.toolitems)) - self._current = None - - - def add_tool(self, callback, pos=0, **kwargs): - #this method called from the exterior and from the interior - #will add a tool to the toolbar - #this tool, will behave like normal button - #the first time it is clicked, it will get all the figures - #after that, if it is clicked again, it will call the show method - #if the _current changes (switch the active figure from the manager) - #the set_figures method is invoked again - pos = len(self.toolitems) + pos - tbutton = self.add_button(pos=pos, **kwargs) - if not tbutton: - return - - self.connect_button(tbutton, 'clicked', '_external_callback', callback) - - def connect_button(self, button, action, callback, *args): - #This is specific to each library, - #The idea is to get rid of different formating between libraries and - #be able to call internal functions with clicks, selects, etc... - # - #In Gtk for example - #def connect_button(self, button, action, callback, *args): - # def mcallback(btn, *args): - # cb = args[0] - # other = args[1:] - # getattr(self, cb)(*other) - # - # button.connect(action, mcallback, callback, *args) - - raise NotImplementedError - - def _external_callback(self, callback): - #This handles the invokation of external classes - #this callback class should take only *figures as arguments - #and preform its work on those figures - #the instance of this callback is added to _external_instances - #as a weakreference to inform them of the switch and destroy - - id_ = id(callback) + fname = os.path.join(basedir, image_file + '.png') + image = Gtk.Image() + image.set_from_file(fname) + tbutton = Gtk.ToolButton() + tbutton.set_label(text) + tbutton.set_icon_widget(image) + self.insert(tbutton, -1) + tbutton.connect('clicked', getattr(self, callback)) + tbutton.set_tooltip_text(tooltip_text) - if id_ in self._external_instances: - self._external_instances[id_].show() - return - - figures = self.get_figures() - cls = self._get_cls_to_instantiate(callback) - - external_instance = cls(*figures) - - self._external_instances[id_] = external_instance - - def _get_cls_to_instantiate(self, callback_class): - #very basic mthod to get the class to instantiate - #do we want something complex like django for models? - if isinstance(callback_class, basestring): - return globals()[callback_class] - return callback_class - - def home(self, *args): - self._current.home(*args) + toolitem = Gtk.SeparatorToolItem() + self.insert(toolitem, -1) + toolitem.set_draw(False) + toolitem.set_expand(True) + + toolitem = Gtk.ToolItem() + self.insert(toolitem, -1) + self.message = Gtk.Label() + toolitem.add(self.message) + + self.show_all() + + def get_filechooser(self): + fc = FileChooserDialog( + title='Save the figure', + parent=self.win, + path=os.path.expanduser(rcParams.get('savefig.directory', '')), + filetypes=self.canvas.get_supported_filetypes(), + default_filetype=self.canvas.get_default_filetype()) + fc.set_current_name(self.canvas.get_default_filename()) + return fc - def back(self, *args): - self._current.back(*args) + def save_figure(self, *args): + chooser = self.get_filechooser() + fname, format = chooser.get_filename_from_user() + chooser.destroy() + if fname: + startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) + if startpath == '': + # explicitly missing key or empty str signals to use cwd + rcParams['savefig.directory'] = startpath + else: + # save dir for next time + rcParams['savefig.directory'] = os.path.dirname(six.text_type(fname)) + try: + self.canvas.print_figure(fname, format=format) + except Exception as e: + error_msg_gtk(str(e), parent=self) - def forward(self, *args): - self._current.forward(*args) + def configure_subplots(self, button): + toolfig = Figure(figsize=(6,3)) + canvas = self._get_canvas(toolfig) + toolfig.subplots_adjust(top=0.9) + tool = SubplotTool(self.canvas.figure, toolfig) - def pan(self, *args): - self._current.pan(*args) + w = int (toolfig.bbox.width) + h = int (toolfig.bbox.height) - def zoom(self, *args): - self._current.zoom(*args) - def add_toolbar(self, toolbar): - #this method is called from the child toolbar - self._toolbars.append(toolbar) - self._current = toolbar - - def get_figures(self): - #return an array of figures, with the current as the firstone - figures = [self._current.canvas.figure] - others = [toolbar.canvas.figure for toolbar in self._toolbars if toolbar is not self._current] - figures.extend(others) - return figures - - def add_button(self, text='_', pos=-1, - tooltip_text='', image=None, - toggle=False): - #This should create the button in the toolbar - raise NotImplementedError - - def add_separator(self, pos=0): - pass - - def switch_child(self, toolbar): - #when multi-figure-manager switches child (figure) - #this toolbar needs to switch to, so it controls the correct one - #if there are external instances (tools) inform them of the switch - #by invoking instance.set_figures(*figures) - - if toolbar not in self._toolbars: - raise AttributeError('This container does not control the given toolbar') - - # For these two actions we have to unselect and reselect - d = {'PAN': 'pan', 'ZOOM': 'zoom'} - action = d.get(self._current._active, False) - if action: - getattr(self._current, action)() - getattr(toolbar, action)() - self._current = toolbar - - figures = self.get_figures() - for v in self._external_instances.values(): -# print('setting', v) - v.set_figures(*figures) + window = Gtk.Window() + try: + window.set_icon_from_file(window_icon) + except (SystemExit, KeyboardInterrupt): + # re-raise exit type Exceptions + raise + except: + # we presumably already logged a message on the + # failure of the main plot, don't keep reporting + pass + window.set_title("Subplot Configuration Tool") + window.set_default_size(w, h) + vbox = Gtk.Box() + vbox.set_property("orientation", Gtk.Orientation.VERTICAL) + window.add(vbox) + vbox.show() + + canvas.show() + vbox.pack_start(canvas, True, True, 0) + window.show() + + def _get_canvas(self, fig): + return self.canvas.__class__(fig) - def set_child_message(self, child, text): - #if the child toolbar has a message - pass + +class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar, MultiFigureToolbarBase): + external_toolitems = ({'text': 'Subplots', + 'tooltip_text': 'Configure subplots', + 'image': 'subplots', + 'callback': 'ConfigureSubplotsGTK3'}, + + {'text': 'SaveAll', + 'tooltip_text': 'Save all figures', + 'callback': 'SaveFiguresDialogGTK3'}, + ) -class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar, MultiFigureToolbarBase): _toggle = [] def __init__(self, window): @@ -1124,303 +883,30 @@ def _something_clicked(self, btn): i.set_active(False) i.handler_unblock_by_func(self._something_clicked) - def set_child_message(self, child, text): + def set_message(self, text): self.message.set_label(text) + def set_child_cursor(self, child, cursor): + child.canvas.get_property("window").set_cursor(cursord[cursor]) -class ToolBase(object): - #basic structure for the external tools that work with - #multi-figure-toolbar - def __init__(self, *figures): - self.init_tool() - - if figures: - self.set_figures(*figures) - - def init_tool(self): - #do some initialization work as create windows and stuff - pass - def set_figures(self, *figures): - #this is the main work, many non gui tools use only this one - #make sure it receives an array *figures. The toolbar caller - #always sent an array with all the figures - #the first figure of the array is the current figure (toolbar point of view) - #if it uses only the fisrt one, use it as figure = figures[0] - raise NotImplementedError - - def destroy(self, *args): - #called when we want to kill the tool from the creator (toolbar) - pass - - def show(self): - #called when need to bring to focus this specific tool - pass - - -class ConfigureSubplotsGTK3(ToolBase): - def init_tool(self): - self.window = Gtk.Window() - - try: - self.window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: - # we presumably already logged a message on the - # failure of the main plot, don't keep reporting - pass - self.window.set_title("Subplot Configuration Tool") - 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) - - def reset(self, *args): - children = self.vbox.get_children() - for child in children: - self.vbox.remove(child) - del children - - def set_figures(self, *figures): - self.reset() - figure = figures[0] - toolfig = Figure(figsize=(6, 3)) - canvas = figure.canvas.__class__(toolfig) - - toolfig.subplots_adjust(top=0.9) - SubplotTool(figure, toolfig) - - w = int(toolfig.bbox.width) - h = int(toolfig.bbox.height) - - self.window.set_default_size(w, h) - - canvas.show() - self.vbox.pack_start(canvas, True, True, 0) - self.window.show() - - def show(self): - self.window.present() - - -class SaveFiguresDialogGTK3(ToolBase): - - def set_figures(self, *figs): - ref_figure = figs[0] - self.figures = figs - - self.ref_canvas = ref_figure.canvas - self.current_name = self.ref_canvas.get_default_filename() - self.title = 'Save %d Figures' % len(figs) - - if len(figs) > 1: - fname_end = '.' + self.ref_canvas.get_default_filetype() - self.current_name = self.current_name[:-len(fname_end)] - - chooser = self._get_filechooser() - fname, format_ = chooser.get_filename_from_user() - chooser.destroy() - if not fname: - return - self._save_figures(fname, format_) - - def _save_figures(self, basename, format_): - figs = self.figures - startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) - if startpath == '': - # explicitly missing key or empty str signals to use cwd - rcParams['savefig.directory'] = startpath - else: - # save dir for next time - rcParams['savefig.directory'] = os.path.dirname(six.text_type(basename)) - - # Get rid of the extension including the point - extension = '.' + format_ - if basename.endswith(extension): - basename = basename[:-len(extension)] - - # In the case of multiple figures, we have to insert a - # "figure identifier" in the filename name - n = len(figs) - if n == 1: - figure_identifier = ('',) - else: - figure_identifier = [str('_%.3d' % figs[i].canvas.manager.num) for i in range(n)] - - for i in range(n): - canvas = figs[i].canvas - fname = str('%s%s%s' % (basename, figure_identifier[i], extension)) - try: - canvas.print_figure(fname, format=format_) - except Exception as e: - error_msg_gtk(str(e), parent=canvas.manager.window) - - def _get_filechooser(self): - fc = FileChooserDialog( - title=self.title, - parent=self.ref_canvas.manager.window, - path=os.path.expanduser(rcParams.get('savefig.directory', '')), - filetypes=self.ref_canvas.get_supported_filetypes(), - default_filetype=self.ref_canvas.get_default_filetype()) - fc.set_current_name(self.current_name) - return fc - - -class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): - def __init__(self, canvas, window): - self.win = window - GObject.GObject.__init__(self) - NavigationToolbar2.__init__(self, canvas) - self.ctx = None - - def set_message(self, s): - self.message.set_label(s) - - def set_cursor(self, cursor): - self.canvas.get_property("window").set_cursor(cursord[cursor]) - # self.canvas.set_cursor(cursord[cursor]) - - def release(self, event): - try: del self._pixmapBack - except AttributeError: pass - - def dynamic_update(self): - # legacy method; new method is canvas.draw_idle - self.canvas.draw_idle() - - def draw_rubberband(self, event, x0, y0, x1, y1): - 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' - self.ctx = self.canvas.get_property("window").cairo_create() - - # todo: instead of redrawing the entire figure, copy the part of - # the figure that was covered by the previous rubberband rectangle - self.canvas.draw() - - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - w = abs(x1 - x0) - h = abs(y1 - y0) - rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] - - self.ctx.new_path() - self.ctx.set_line_width(0.5) - self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) - self.ctx.set_source_rgb(0, 0, 0) - self.ctx.stroke() - - def _init_toolbar(self): - self.set_style(Gtk.ToolbarStyle.ICONS) - basedir = os.path.join(rcParams['datapath'], 'images') - - for text, tooltip_text, image_file, callback in self.toolitems: - if text is None: - self.insert(Gtk.SeparatorToolItem(), -1) - continue - fname = os.path.join(basedir, image_file + '.png') - image = Gtk.Image() - image.set_from_file(fname) - tbutton = Gtk.ToolButton() - tbutton.set_label(text) - tbutton.set_icon_widget(image) - self.insert(tbutton, -1) - tbutton.connect('clicked', getattr(self, callback)) - tbutton.set_tooltip_text(tooltip_text) - - toolitem = Gtk.SeparatorToolItem() - self.insert(toolitem, -1) - toolitem.set_draw(False) - toolitem.set_expand(True) - - toolitem = Gtk.ToolItem() - self.insert(toolitem, -1) - self.message = Gtk.Label() - toolitem.add(self.message) - - self.show_all() - - def get_filechooser(self): - fc = FileChooserDialog( - title='Save the figure', - parent=self.win, - path=os.path.expanduser(rcParams.get('savefig.directory', '')), - filetypes=self.canvas.get_supported_filetypes(), - default_filetype=self.canvas.get_default_filetype()) - fc.set_current_name(self.canvas.get_default_filename()) - return fc - - def save_figure(self, *args): - chooser = self.get_filechooser() - fname, format = chooser.get_filename_from_user() - chooser.destroy() - if fname: - startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) - if startpath == '': - # explicitly missing key or empty str signals to use cwd - rcParams['savefig.directory'] = startpath - else: - # save dir for next time - rcParams['savefig.directory'] = os.path.dirname(six.text_type(fname)) - try: - self.canvas.print_figure(fname, format=format) - except Exception as e: - error_msg_gtk(str(e), parent=self) - - def configure_subplots(self, button): - toolfig = Figure(figsize=(6, 3)) - canvas = self._get_canvas(toolfig) - toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self.canvas.figure, toolfig) - - w = int (toolfig.bbox.width) - h = int (toolfig.bbox.height) - - - window = Gtk.Window() - try: - window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: - # we presumably already logged a message on the - # failure of the main plot, don't keep reporting - pass - window.set_title("Subplot Configuration Tool") - window.set_default_size(w, h) - vbox = Gtk.Box() - vbox.set_property("orientation", Gtk.Orientation.VERTICAL) - window.add(vbox) - vbox.show() - - canvas.show() - vbox.pack_start(canvas, True, True, 0) - window.show() - - def _get_canvas(self, fig): - return self.canvas.__class__(fig) - - -class FileChooserDialog(Gtk.FileChooserDialog): - """GTK+ file selector which remembers the last file/directory - selected and presents the user with a menu of supported image formats - """ - def __init__ (self, - title='Save file', - parent=None, - action=Gtk.FileChooserAction.SAVE, - buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_SAVE, Gtk.ResponseType.OK), - path=None, - filetypes=[], - default_filetype=None - ): - super (FileChooserDialog, self).__init__ (title, parent, action, - buttons) - self.set_default_response (Gtk.ResponseType.OK) +class FileChooserDialog(Gtk.FileChooserDialog): + """GTK+ file selector which remembers the last file/directory + selected and presents the user with a menu of supported image formats + """ + def __init__ (self, + title = 'Save file', + parent = None, + action = Gtk.FileChooserAction.SAVE, + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, Gtk.ResponseType.OK), + path = None, + filetypes = [], + default_filetype = None + ): + super (FileChooserDialog, self).__init__ (title, parent, action, + buttons) + self.set_default_response (Gtk.ResponseType.OK) if not path: path = os.getcwd() + os.sep @@ -1432,7 +918,7 @@ def __init__ (self, hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0) liststore = Gtk.ListStore(GObject.TYPE_STRING) - cbox = Gtk.ComboBox() # liststore) + cbox = Gtk.ComboBox() #liststore) cbox.set_model(liststore) cell = Gtk.CellRendererText() cbox.pack_start(cell, True) @@ -1450,7 +936,7 @@ def __init__ (self, cbox.set_active(default) self.ext = default_filetype - def cb_cbox_changed(cbox, data=None): + def cb_cbox_changed (cbox, data=None): """File extension changed""" head, filename = os.path.split(self.get_filename()) root, ext = os.path.splitext(filename) @@ -1463,13 +949,13 @@ def cb_cbox_changed(cbox, data=None): elif ext == '': filename = filename.rstrip('.') + '.' + new_ext - self.set_current_name(filename) - cbox.connect("changed", cb_cbox_changed) + self.set_current_name (filename) + cbox.connect ("changed", cb_cbox_changed) hbox.show_all() self.set_extra_widget(hbox) - def get_filename_from_user(self): + def get_filename_from_user (self): while True: filename = None if self.run() != int(Gtk.ResponseType.OK): @@ -1495,12 +981,12 @@ class DialogLineprops: ) linestyles = [ls for ls in lines.Line2D.lineStyles if ls.strip()] - linestyled = dict([ (s, i) for i, s in enumerate(linestyles)]) + linestyled = dict([ (s,i) for i,s in enumerate(linestyles)]) - markers = [m for m in lines.Line2D.markers if cbook.is_string_like(m)] + markers = [m for m in lines.Line2D.markers if cbook.is_string_like(m)] - markerd = dict([(s, i) for i, s in enumerate(markers)]) + markerd = dict([(s,i) for i,s in enumerate(markers)]) def __init__(self, lines): import Gtk.glade @@ -1508,10 +994,10 @@ def __init__(self, lines): datadir = matplotlib.get_data_path() gladefile = os.path.join(datadir, 'lineprops.glade') if not os.path.exists(gladefile): - raise IOError('Could not find gladefile lineprops.glade in %s' % datadir) + raise IOError('Could not find gladefile lineprops.glade in %s'%datadir) self._inited = False - self._updateson = True # suppress updates when setting widgets manually + self._updateson = True # suppress updates when setting widgets manually self.wtree = Gtk.glade.XML(gladefile, 'dialog_lineprops') self.wtree.signal_autoconnect(dict([(s, getattr(self, s)) for s in self.signals])) @@ -1543,7 +1029,7 @@ def show(self): self._updateson = False # flush the old cbox = self.cbox_lineprops - for i in range(self._lastcnt - 1, -1, -1): + for i in range(self._lastcnt-1,-1,-1): cbox.remove_text(i) # add the new @@ -1584,13 +1070,13 @@ def _update(self): button = self.wtree.get_widget('colorbutton_linestyle') color = button.get_color() - r, g, b = [val / 65535. for val in (color.red, color.green, color.blue)] - line.set_color((r, g, b)) + r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] + line.set_color((r,g,b)) button = self.wtree.get_widget('colorbutton_markerface') color = button.get_color() - r, g, b = [val / 65535. for val in (color.red, color.green, color.blue)] - line.set_markerfacecolor((r, g, b)) + r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] + line.set_markerfacecolor((r,g,b)) line.figure.canvas.draw() @@ -1608,13 +1094,13 @@ def on_combobox_lineprops_changed(self, item): if marker is None: marker = 'None' self.cbox_markers.set_active(self.markerd[marker]) - r, g, b = colorConverter.to_rgb(line.get_color()) - color = Gdk.Color(*[int(val * 65535) for val in (r, g, b)]) + r,g,b = colorConverter.to_rgb(line.get_color()) + color = Gdk.Color(*[int(val*65535) for val in (r,g,b)]) button = self.wtree.get_widget('colorbutton_linestyle') button.set_color(color) - r, g, b = colorConverter.to_rgb(line.get_markerfacecolor()) - color = Gdk.Color(*[int(val * 65535) for val in (r, g, b)]) + r,g,b = colorConverter.to_rgb(line.get_markerfacecolor()) + color = Gdk.Color(*[int(val*65535) for val in (r,g,b)]) button = self.wtree.get_widget('colorbutton_markerface') button.set_color(color) self._updateson = True @@ -1640,6 +1126,117 @@ def on_dialog_lineprops_cancelbutton_clicked(self, button): self.dlg.hide() +class ConfigureSubplotsGTK3(ToolBase): + def init_tool(self): + self.window = Gtk.Window() + + try: + self.window.set_icon_from_file(window_icon) + except (SystemExit, KeyboardInterrupt): + # re-raise exit type Exceptions + raise + except: + # we presumably already logged a message on the + # failure of the main plot, don't keep reporting + pass + self.window.set_title("Subplot Configuration Tool") + 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) + + def reset(self, *args): + children = self.vbox.get_children() + for child in children: + self.vbox.remove(child) + del children + + def set_figures(self, *figures): + self.reset() + figure = figures[0] + toolfig = Figure(figsize=(6, 3)) + canvas = figure.canvas.__class__(toolfig) + + toolfig.subplots_adjust(top=0.9) + SubplotTool(figure, toolfig) + + w = int(toolfig.bbox.width) + h = int(toolfig.bbox.height) + + self.window.set_default_size(w, h) + + canvas.show() + self.vbox.pack_start(canvas, True, True, 0) + self.window.show() + + def show(self): + self.window.present() + + +class SaveFiguresDialogGTK3(ToolBase): + + def set_figures(self, *figs): + ref_figure = figs[0] + self.figures = figs + + self.ref_canvas = ref_figure.canvas + self.current_name = self.ref_canvas.get_default_filename() + self.title = 'Save %d Figures' % len(figs) + + if len(figs) > 1: + fname_end = '.' + self.ref_canvas.get_default_filetype() + self.current_name = self.current_name[:-len(fname_end)] + + chooser = self._get_filechooser() + fname, format_ = chooser.get_filename_from_user() + chooser.destroy() + if not fname: + return + self._save_figures(fname, format_) + + def _save_figures(self, basename, format_): + figs = self.figures + startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) + if startpath == '': + # explicitly missing key or empty str signals to use cwd + rcParams['savefig.directory'] = startpath + else: + # save dir for next time + rcParams['savefig.directory'] = os.path.dirname(six.text_type(basename)) + + # Get rid of the extension including the point + extension = '.' + format_ + if basename.endswith(extension): + basename = basename[:-len(extension)] + + # In the case of multiple figures, we have to insert a + # "figure identifier" in the filename name + n = len(figs) + if n == 1: + figure_identifier = ('',) + else: + figure_identifier = [str('_%.3d' % figs[i].canvas.manager.num) for i in range(n)] + + for i in range(n): + canvas = figs[i].canvas + fname = str('%s%s%s' % (basename, figure_identifier[i], extension)) + try: + canvas.print_figure(fname, format=format_) + except Exception as e: + error_msg_gtk(str(e), parent=canvas.manager.window) + + def _get_filechooser(self): + fc = FileChooserDialog( + title=self.title, + parent=self.ref_canvas.manager.window, + path=os.path.expanduser(rcParams.get('savefig.directory', '')), + filetypes=self.ref_canvas.get_supported_filetypes(), + default_filetype=self.ref_canvas.get_default_filetype()) + fc.set_current_name(self.current_name) + return fc + + # Define the file to use as the GTk icon if sys.platform == 'win32': icon_filename = 'matplotlib.png' @@ -1649,18 +1246,18 @@ def on_dialog_lineprops_cancelbutton_clicked(self, button): def error_msg_gtk(msg, parent=None): - if parent is not None: # find the toplevel Gtk.Window + if parent is not None: # find the toplevel Gtk.Window parent = parent.get_toplevel() if not parent.is_toplevel(): parent = None if not is_string_like(msg): - msg = ','.join(map(str, msg)) + msg = ','.join(map(str,msg)) dialog = Gtk.MessageDialog( - parent=parent, - type=Gtk.MessageType.ERROR, - buttons=Gtk.ButtonsType.OK, - message_format=msg) + parent = parent, + type = Gtk.MessageType.ERROR, + buttons = Gtk.ButtonsType.OK, + message_format = msg) dialog.run() dialog.destroy() diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 80ea68bf6616..4421cd0e2fd4 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -48,7 +48,6 @@ def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ - FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 70c3a1438e02..ed5ac9f1a90d 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -475,10 +475,9 @@ def __call__(self, s): # a map from key -> value, converter defaultParams = { 'backend': ['Agg', validate_backend], # agg is certainly - # present + 'backend.single_window': [False, validate_bool], # present 'backend_fallback': [True, validate_bool], # agg is certainly present 'backend.qt4': ['PyQt4', validate_qt4], - 'backend.gtk3.tabbed': [False, validate_bool], 'webagg.port': [8988, validate_int], 'webagg.open_in_browser': [True, validate_bool], 'webagg.port_retries': [50, validate_int], diff --git a/matplotlibrc.template b/matplotlibrc.template index 2595709719a5..09a3267b85ac 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -39,7 +39,7 @@ backend : %(backend)s # If you are using one of the GTK3 backends (GTK3Agg or GTK3Cairo) # you can set to use only one window with tabbed figures instead of # multiple windows one for each figure -#backend.gtk3.tabbed : True +#backend.single_window : True # Note that this can be overridden by the environment variable # QT_API used by Enthought Tool Suite (ETS); valid values are From b48e903b0add739c7bfc09a093225028ace56938 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 18 Sep 2013 12:04:27 -0400 Subject: [PATCH 09/20] renaming rcparam in demo --- examples/pylab_examples/multiple_figs_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pylab_examples/multiple_figs_demo.py b/examples/pylab_examples/multiple_figs_demo.py index fe4008306a04..c49e197375a7 100644 --- a/examples/pylab_examples/multiple_figs_demo.py +++ b/examples/pylab_examples/multiple_figs_demo.py @@ -3,7 +3,7 @@ import matplotlib matplotlib.use('gtk3agg') -matplotlib.rcParams['backend.gtk3.tabbed'] = True +matplotlib.rcParams['backend.single_window'] = True from pylab import * from matplotlib.backend_bases import ToolBase From a7ab97625a41ba674c8c8802f22c3217205bb41b Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 18 Sep 2013 17:15:14 -0400 Subject: [PATCH 10/20] adding remove_tool and move_tool --- lib/matplotlib/backend_bases.py | 35 ++++++++----- lib/matplotlib/backends/backend_gtk3.py | 66 ++++++++++++++++++++----- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8acf12b322ae..37b54cef5a61 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3399,6 +3399,12 @@ def draw_rubberband(self, event, x0, y0, x1, y1): def add_tool(self, *args, **kwargs): self.parent.add_tool(*args, **kwargs) + + def remove_tool(self, pos): + self.parent.remove_tool(pos) + + def move_tool(self, pos_ini, pos_fin): + self.parent.move_tool(pos_ini, pos_fin) class MultiFigureToolbarBase(object): @@ -3449,15 +3455,6 @@ class MultiFigureToolbarBase(object): None, ) external_toolitems = ( -# {'text': 'Subplots', -# 'tooltip_text': 'Configure subplots', -# 'image': 'subplots', -# 'callback': 'ConfigureSubplotsGTK3'}, -# -# {'text': 'Save All', -# 'tooltip_text': 'Save all figures', -# 'image': 'saveall_icon', -# 'callback': 'SaveFiguresDialogGTK3'}, ) _external_instances = weakref.WeakValueDictionary() @@ -3477,9 +3474,11 @@ def __init__(self): for pos, btn in enumerate(self.external_toolitems): callback = btn.pop('callback') - self.add_tool(callback, pos=pos, **btn) + i_pos = pos + len(self.toolitems) + self.add_tool(callback, pos=i_pos, **btn) self.add_separator(len(self.external_toolitems) + len(self.toolitems)) + self.add_message() self._current = None @@ -3489,15 +3488,22 @@ def add_tool(self, callback, pos=0, **kwargs): #this tool, will behave like normal button #the first time it is clicked, it will get all the figures #after that, if it is clicked again, it will call the show method - #if the _current changes (switch the active figure from the manager) + #if _current changes (switch the active figure from the manager) #the set_figures method is invoked again - pos = len(self.toolitems) + pos tbutton = self.add_button(pos=pos, **kwargs) if not tbutton: return self.connect_button(tbutton, 'clicked', '_external_callback', callback) + def remove_tool(self, pos): + #remote item from the toolbar, + pass + + def move_tool(self, pos_ini, pos_fin): + #move item in the toolbar + pass + def connect_button(self, button, action, callback, *args): #This is specific to each library, #The idea is to get rid of different formating between libraries and @@ -3625,6 +3631,11 @@ def set_child_cursor(self, child, cursor): def set_message(self, text): pass + def add_message(self): + #set the message area + #with the possibility to add buttons from the exterior + #it may cause problems with the space too reduced + pass class ToolBase(object): #basic structure for the external tools that work with diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 2d0d11e4395d..35264eba8ae4 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -518,9 +518,10 @@ def __init__(self, *args): self.toolbar = self._get_toolbar() if self.toolbar is not None: + self._replace_toolbar_message() self.toolbar.show() self.vbox.pack_end(self.toolbar, False, False, 0) - + size_request = self.window.size_request() self._h_def = size_request.height self._w_def = size_request.width @@ -537,6 +538,23 @@ def destroy_window(*args): if matplotlib.is_interactive(): self.window.show() + def _replace_toolbar_message(self): + #This is needed because of additional buttons take too muchs space + if not self.toolbar: + return + + box = Gtk.Box() + box.set_property("orientation", Gtk.Orientation.HORIZONTAL) + + message = Gtk.Label() + box.pack_end(message, False, False, 0) + self.toolbar.message = message + self.vbox.pack_end(box, False, True, 0) + + sep = Gtk.Separator() + sep.set_property("orientation", Gtk.Orientation.HORIZONTAL) + self.vbox.pack_end(sep, False, True, 0) + def _on_switch_page(self, notebook, pointer, num): canvas = self.notebook.get_nth_page(num) self.switch_child(canvas) @@ -800,8 +818,6 @@ class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar, MultiFigureToolbarBase): 'tooltip_text': 'Save all figures', 'callback': 'SaveFiguresDialogGTK3'}, ) - - _toggle = [] def __init__(self, window): @@ -819,18 +835,25 @@ def mcallback(btn, *args): def add_button(self, text='_', pos=-1, tooltip_text='', image=None, toggle=False): - timage = None if image: timage = Gtk.Image() - if os.path.isfile(image): - timage.set_from_file(image) + if isinstance(image, basestring): + if os.path.isfile(image): + timage.set_from_file(image) + else: + basedir = os.path.join(rcParams['datapath'], 'images') + fname = os.path.join(basedir, image + '.png') + timage.set_from_file(fname) else: - #FIXME: add the possibility to load from inline string - basedir = os.path.join(rcParams['datapath'], 'images') - fname = os.path.join(basedir, image + '.png') - timage.set_from_file(fname) - + #FIXME: there is something wrong or even more probable + #something I misunderstood with the way new_from_inline works +# try: +# from gi.repository import GdkPixbuf +# pixbuf = GdkPixbuf.Pixbuf.new_from_inline(image, False) +# except: +# + timage = False if toggle: tbutton = Gtk.ToggleToolButton() @@ -847,6 +870,22 @@ def add_button(self, text='_', pos=-1, tbutton.set_tooltip_text(tooltip_text) self.insert(tbutton, pos) return tbutton + + def remove_tool(self, pos): + widget = self.get_nth_item(pos) + if not widget: + self.set_message('Impossible to remove tool %d' % pos) + return + self.remove(widget) + + def move_tool(self, pos_ini, pos_fin): + widget = self.get_nth_item(pos_ini) + if not widget: + self.set_message('Impossible to remove tool %d' % pos_ini) + return + self.remove(widget) + self.insert(widget, pos_fin) + def add_separator(self, pos=-1): toolitem = Gtk.SeparatorToolItem() @@ -862,13 +901,14 @@ def init_toolbar(self): toolitem.set_draw(False) toolitem.set_expand(True) + self.show_all() + + def add_message(self): toolitem = Gtk.ToolItem() self.insert(toolitem, -1) self.message = Gtk.Label() toolitem.add(self.message) - self.show_all() - def save_figure(self, *args): sd = SaveFiguresDialogGTK3(self.get_figures()[0]) From a582aeb962c886914fc376a85e0f1aeec2d59a70 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 19 Sep 2013 10:12:40 -0400 Subject: [PATCH 11/20] Adding passthought **kwargs to add_tool, default value for buttons now in ToolBase --- lib/matplotlib/backend_bases.py | 82 ++++++++++++++++--------- lib/matplotlib/backends/backend_gtk3.py | 10 ++- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 37b54cef5a61..51c9889b4978 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2677,7 +2677,7 @@ def destroy(self): self.toolbar.remove() def resize(self, w, h): - self.parent.resize_manager(self, w, h) + self.parent.resize_child(self, w, h) def show_popup(self, msg): self.parent.show_popup(self, msg) @@ -3396,15 +3396,12 @@ def draw_rubberband(self, event, x0, y0, x1, y1): self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) self.ctx.set_source_rgb(0, 0, 0) self.ctx.stroke() - - def add_tool(self, *args, **kwargs): - self.parent.add_tool(*args, **kwargs) - - def remove_tool(self, pos): - self.parent.remove_tool(pos) - def move_tool(self, pos_ini, pos_fin): - self.parent.move_tool(pos_ini, pos_fin) + def __getattr__(self, name): + #we suposse everything else that we want from this child + #belongs into the parent + return getattr(self.parent, name) + class MultiFigureToolbarBase(object): @@ -3482,7 +3479,7 @@ def __init__(self): self._current = None - def add_tool(self, callback, pos=0, **kwargs): + def add_tool(self, callback, **kwargs): #this method called from the exterior and from the interior #will add a tool to the toolbar #this tool, will behave like normal button @@ -3490,11 +3487,27 @@ def add_tool(self, callback, pos=0, **kwargs): #after that, if it is clicked again, it will call the show method #if _current changes (switch the active figure from the manager) #the set_figures method is invoked again - tbutton = self.add_button(pos=pos, **kwargs) + + cls = self._get_cls_to_instantiate(callback) + if not cls: + self.set_message('Not available') + return + + #if not passed directly from the call, look for them in the class + text = kwargs.pop('text', cls.text) + tooltip_text = kwargs.pop('tooltip_text', cls.tooltip_text) + pos = kwargs.pop('pos', cls.pos) + image = kwargs.pop('image', cls.image) + toggle = kwargs.pop('toggle', cls.toggle) + + tbutton = self.add_button(pos=pos, text=text, + tooltip_text=tooltip_text, + image=image, + toggle=toggle) if not tbutton: return - self.connect_button(tbutton, 'clicked', '_external_callback', callback) + self.connect_button(tbutton, 'clicked', '_external_callback', cls, **kwargs) def remove_tool(self, pos): #remote item from the toolbar, @@ -3504,23 +3517,21 @@ def move_tool(self, pos_ini, pos_fin): #move item in the toolbar pass - def connect_button(self, button, action, callback, *args): + def connect_button(self, button, action, callback, *args, **kwargs): #This is specific to each library, #The idea is to get rid of different formating between libraries and #be able to call internal functions with clicks, selects, etc... # - #In Gtk for example - #def connect_button(self, button, action, callback, *args): - # def mcallback(btn, *args): - # cb = args[0] - # other = args[1:] - # getattr(self, cb)(*other) + #In Gtk3 for example + #def connect_button(self, button, action, callback, *args, **kwargs): + # def mcallback(btn, cb, args, kwargs): + # getattr(self, cb)(*args, **kwargs) # - # button.connect(action, mcallback, callback, *args) - + # button.connect(action, mcallback, callback, args, kwargs) + raise NotImplementedError - def _external_callback(self, callback): + def _external_callback(self, callback, **kwargs): #This handles the invokation of external classes #this callback class should take only *figures as arguments #and preform its work on those figures @@ -3534,12 +3545,8 @@ def _external_callback(self, callback): return figures = self.get_figures() - cls = self._get_cls_to_instantiate(callback) - if not cls: - self.set_message('Not available') - return - external_instance = cls(*figures) + external_instance = callback(*figures, **kwargs) self._external_instances[id_] = external_instance @@ -3637,17 +3644,32 @@ def add_message(self): #it may cause problems with the space too reduced pass + class ToolBase(object): #basic structure for the external tools that work with #multi-figure-toolbar - def __init__(self, *figures): - self.init_tool() + + #Default values for the tool when added to the toolbar + pos=-1 + text='_' + tooltip_text='' + image=None + toggle=False + + #Be careful with this arguments, + #when adding the tool to the toolbar, we make sure to pass *figures and **kwargs from + #the user + def __init__(self, *figures, **kwargs): + self.init_tool(**kwargs) if figures: self.set_figures(*figures) - def init_tool(self): + def init_tool(self, **kwargs): #do some initialization work as create windows and stuff + #kwargs are the keyword paramters given by the user + if kwargs: + raise TypeError('init_tool() got an unexpected keyword arguments %s' % str(kwargs)) pass def set_figures(self, *figures): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 35264eba8ae4..606451702b4a 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -824,13 +824,11 @@ def __init__(self, window): self.win = window MultiFigureToolbarBase.__init__(self) - def connect_button(self, button, action, callback, *args): - def mcallback(btn, *args): - cb = args[0] - other = args[1:] - getattr(self, cb)(*other) + def connect_button(self, button, action, callback, *args, **kwargs): + def mcallback(btn, cb, args, kwargs): + getattr(self, cb)(*args, **kwargs) - button.connect(action, mcallback, callback, *args) + button.connect(action, mcallback, callback, args, kwargs) def add_button(self, text='_', pos=-1, tooltip_text='', image=None, From 375f32ecde8185643da5413dd760dca6f8314a9c Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Fri, 20 Sep 2013 13:20:37 -0400 Subject: [PATCH 12/20] adding tool for lineproperties --- lib/matplotlib/backends/backend_gtk3.py | 35 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 606451702b4a..79dbc42f4c38 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -836,22 +836,18 @@ def add_button(self, text='_', pos=-1, timage = None if image: timage = Gtk.Image() - if isinstance(image, basestring): - if os.path.isfile(image): - timage.set_from_file(image) - else: - basedir = os.path.join(rcParams['datapath'], 'images') - fname = os.path.join(basedir, image + '.png') - timage.set_from_file(fname) + + if os.path.isfile(image): + timage.set_from_file(image) else: - #FIXME: there is something wrong or even more probable - #something I misunderstood with the way new_from_inline works -# try: + basedir = os.path.join(rcParams['datapath'], 'images') + fname = os.path.join(basedir, image + '.png') + if os.path.isfile(fname): + timage.set_from_file(fname) + else: # from gi.repository import GdkPixbuf # pixbuf = GdkPixbuf.Pixbuf.new_from_inline(image, False) -# except: -# - timage = False + timage = False if toggle: tbutton = Gtk.ToggleToolButton() @@ -1164,6 +1160,19 @@ def on_dialog_lineprops_cancelbutton_clicked(self, button): self.dlg.hide() +class Lineprops(ToolBase): + def init_tool(self, **kwargs): + self.dialog = DialogLineprops([]) + + def set_figures(self, *figures): + figure = figures[0] + lines = [] + + for alines in [ax.lines for ax in figure.get_axes()]: + lines.extend(alines) + print (lines) + + class ConfigureSubplotsGTK3(ToolBase): def init_tool(self): self.window = Gtk.Window() From 5ec539f963c81a87be1a0f52b9fad88de053f557 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Tue, 24 Sep 2013 13:24:29 -0400 Subject: [PATCH 13/20] adding saveall images from http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/document-save-all.png --- lib/matplotlib/mpl-data/images/saveall.png | Bin 0 -> 1681 bytes lib/matplotlib/mpl-data/images/saveall.svg | 6539 ++++++++++++++++++++ lib/matplotlib/mpl-data/images/saveall.xpm | 107 + 3 files changed, 6646 insertions(+) create mode 100644 lib/matplotlib/mpl-data/images/saveall.png create mode 100644 lib/matplotlib/mpl-data/images/saveall.svg create mode 100644 lib/matplotlib/mpl-data/images/saveall.xpm diff --git a/lib/matplotlib/mpl-data/images/saveall.png b/lib/matplotlib/mpl-data/images/saveall.png new file mode 100644 index 0000000000000000000000000000000000000000..b7fd88a83ed1e853c8a3d3e61da4364518da2e74 GIT binary patch literal 1681 zcma)+e>l@y9LK*dF;eIkk~X?3w`Q9aGe4)vj})yf$z8HFYhqhA#w{_Fsb%t8Tpl4c z-EmVZP5Ln@S(bWAn_s2h$&Zv+iu+xjo_p^f_j&Glp3ixo=bZQJocB3@oXp@YzVnQh z8UX+>&)*ME0M$ZU5Ph(J%5$FtMW3?C7Y{1fG#g~epcu0KLOB4S8?P;$q(`X=?PNR~ zM+fyg4^t(;V-z@ss*X&679fepm)TinVwR&+eKiyfikp)j zF;6EIV&B!0xA)``!rkLsHhc77wy8qc?aO1nCIpk-rRh>kb1=5L29_Au`7j?ZQX4r4 zlIn-=*-G+;RDEptsFXFA^t&~+4PMiblSca1=&Aias}mR(}yW%oE}=<<9F=kfC0T7JZJfVu#H@2)! zSe$~Ye_z<$Co85h7kX{4elqM{tYQZ*9<5kW`Yj1rrUW9DB6KX@9bag(arIzrd@I!DuI(Ty)e&}sf@ z2C~6_0lHH13vflUBA>7lAlbF16x^)LFbVx*J7%;JI+IQ$Z5uiY8NZ=JA|LN{6($%2 zbjVYdE%tYLy~bEArCwF-j>KehPBW}1M{|WXdRHX!9@IozS`K>iLsR^j!7nmng-|jm z4PS9#(U<0$m@q;>h=SF<#9B#=+Z>R2FLFaianA&i&MO129>FI zk^Zj`zBJ~oI;(jk)8@YDY3=xz&Zs!ey4KY&x_#l83BMdGIQxJnueQ3xv_-LN4uQtd zx-Oabf{J}%Y9`R;!l8Q9FqRU8P&XGBjh00%1=q2i~Q^bg2S-d%I-@d^Y&_>Yb9Y=rbY$Yyz6<33-`6S&+`S+=W{uI^pH-B`QvG&kBjvVW z>)h63;5TH)_1VFt#?m6VH25B{100b^7dQ$9N3AEKoUuqpEZP~41P90rlRVK6K9SG31kpdZ18k0i<_Hwyw zECTWUEJi$y%8H3$F*y#bSUMtl*1|zh65*T(1lj?KfU#m>|Az?l4Gu&&I-n3?A~))f zjI;6n@d`*Z9OXhpA+hTnvFk8!q!aiGD|cSr1Ok@YjN4d|TwX*h4dC&34h-f#4mE;J z(?%;9c3TRX7S5XDVyQGBC?SzfW5WEYVEXqKk~iS;s=t@RNrb>3SJiID`tF7^wC?YB T9dQ=7g9za7vjs2l-ktgv_2S*@ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/mpl-data/images/saveall.svg b/lib/matplotlib/mpl-data/images/saveall.svg new file mode 100644 index 000000000000..5aff096cb7b8 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/saveall.svg @@ -0,0 +1,6539 @@ + + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/saveall.xpm b/lib/matplotlib/mpl-data/images/saveall.xpm new file mode 100644 index 000000000000..8af136b807ab --- /dev/null +++ b/lib/matplotlib/mpl-data/images/saveall.xpm @@ -0,0 +1,107 @@ +/* XPM */ +static char *document_save_all[] = { +/* columns rows colors chars-per-pixel */ +"24 24 77 1 ", +" c #1C1A1B", +". c #262525", +"X c #2D2A2B", +"o c #322E2F", +"O c #322F30", +"+ c #353233", +"@ c #393536", +"# c #3B3738", +"$ c #3D3A3B", +"% c #413E3F", +"& c #423F40", +"* c #454243", +"= c #484546", +"- c #494748", +"; c #4D4B4B", +": c #504D4E", +"> c #545253", +", c #585657", +"< c #595758", +"1 c #5C5B5C", +"2 c #605E5F", +"3 c #5E5E60", +"4 c #646364", +"5 c #686667", +"6 c #696768", +"7 c #6B6A6B", +"8 c #706E6F", +"9 c #6D6F73", +"0 c #6F7074", +"q c #747374", +"w c #7C7B7B", +"e c #817F80", +"r c #838283", +"t c #8D8D8D", +"y c #8C9196", +"u c #929292", +"i c #999999", +"p c #9B9FA5", +"a c #9EA2A6", +"s c #9EA4AB", +"d c #A3A3A3", +"f c #A2A6AB", +"g c #A7A8AB", +"h c #ABACAD", +"j c #A4ACB3", +"k c #B5B5B5", +"l c #B8B7B7", +"z c #B0B6BD", +"x c #B3B9BE", +"c c #B9B8B8", +"v c #B5BCC2", +"b c #BABFC4", +"n c #BFC2C5", +"m c #BEC3C8", +"M c #BEC9D3", +"N c #C2C4C7", +"B c #C2C5C9", +"V c #C6C9CD", +"C c #C1CBD4", +"Z c #CFD5DB", +"A c #D1D6DB", +"S c #D5D8DF", +"D c #DCE0E7", +"F c #D6E1EB", +"G c #DCE4EC", +"H c #DFE8F0", +"J c #E2E5EC", +"K c #E4E9EF", +"L c #E8EAEF", +"P c #E4EBF2", +"I c #EAEEF5", +"U c #EDF1F6", +"Y c #ECF1F8", +"T c #F0F4F7", +"R c #F2F5F9", +"E c #F6F9FB", +"W c None", +/* pixels */ +"WWWW75eCDDDDGJJJJKLLJg8q", +"WWWW>;wZYYYYIYIYTTTRUh57", +"WWWW44rZYITIYYTTRTLUUh45", +"74qxZASDGJJKLLLILNerAh24", +"1;6mIYYYIYYYYYTRYB77Vh14", +"2,7NYRRYRRRRREEEEB76mg14", +"445BRERERREEEEEERN64mf13", +"124NREEEEEEEEERRUm53ma<1", +"114nUURYRRRTTYYIIn33va><", +"113nIIIIPIPPPPPPHv33jy><", +"<<3xHHPHKHHHHHGGGx3<73>,", +"><3xGGGGGGGFFFFFFz1<>;;>", +">>1sCCCCCCCCCMMMMs<,;-::", +">:>509990090099905>>*$;;", +":::;:;;;;;;;;;;::::>-%=-", +";::;,5777666764-==;:=%*%", +"-;--qhrwwkklcch=%$-;*==%", +"===*wh*#@gkkllh;*%=-%*&%", +"&*&%8d*#+dhhhhd;*%**@@$@", +"%$%$7i%@oiddddi-%*-&OXOO", +"##@@4t@X.tiiiiu%#%*%. ", +"@+@+1t=#%ttuttt#O+%#WWWW", +"WXOo Date: Tue, 24 Sep 2013 20:04:01 -0400 Subject: [PATCH 14/20] Adding image to saveall tool, changing order to have saveall side to side with savefile --- lib/matplotlib/backend_bases.py | 1 - lib/matplotlib/backends/backend_gtk3.py | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 51c9889b4978..fff4093d3047 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3670,7 +3670,6 @@ def init_tool(self, **kwargs): #kwargs are the keyword paramters given by the user if kwargs: raise TypeError('init_tool() got an unexpected keyword arguments %s' % str(kwargs)) - pass def set_figures(self, *figures): #this is the main work, many non gui tools use only this one diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 79dbc42f4c38..c69363cfea5a 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -809,15 +809,14 @@ def _get_canvas(self, fig): class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar, MultiFigureToolbarBase): - external_toolitems = ({'text': 'Subplots', - 'tooltip_text': 'Configure subplots', - 'image': 'subplots', - 'callback': 'ConfigureSubplotsGTK3'}, - - {'text': 'SaveAll', + external_toolitems = ({'text': 'SaveAll', 'tooltip_text': 'Save all figures', 'callback': 'SaveFiguresDialogGTK3'}, - ) + {'text': 'Subplots', + 'tooltip_text': 'Configure subplots', + 'image': 'subplots', + 'callback': 'ConfigureSubplotsGTK3'} + ) _toggle = [] def __init__(self, window): @@ -1222,6 +1221,8 @@ def show(self): class SaveFiguresDialogGTK3(ToolBase): + image = 'saveall' + register = True def set_figures(self, *figs): ref_figure = figs[0] From 0f3abfe94df6bd1de340113c97c9c3400df095a3 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 26 Sep 2013 15:26:11 -0400 Subject: [PATCH 15/20] Fixing new buttons not showing after toolbar init --- lib/matplotlib/backends/backend_gtk3.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index c69363cfea5a..bb501b2a3778 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -862,6 +862,7 @@ def add_button(self, text='_', pos=-1, tbutton.set_icon_widget(timage) tbutton.set_tooltip_text(tooltip_text) self.insert(tbutton, pos) + tbutton.show() return tbutton def remove_tool(self, pos): From a6bc104aab9d185df8a9cb7cb9a79d53613b1613 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 26 Sep 2013 16:05:17 -0400 Subject: [PATCH 16/20] Saveall image uses same original filesave, the colors and motive match --- lib/matplotlib/mpl-data/images/saveall.png | Bin 1681 -> 1218 bytes lib/matplotlib/mpl-data/images/saveall.svg | 39314 ++++++++++++++++--- lib/matplotlib/mpl-data/images/saveall.xpm | 267 +- 3 files changed, 32988 insertions(+), 6593 deletions(-) diff --git a/lib/matplotlib/mpl-data/images/saveall.png b/lib/matplotlib/mpl-data/images/saveall.png index b7fd88a83ed1e853c8a3d3e61da4364518da2e74..fcdfd4f112946081bb07ca54527f84a346485513 100644 GIT binary patch delta 1163 zcmV;61a$k64Z;bKBq9WJLP=Bz2nYy#2xN!=000SaNLh0L00Kn-00Kn;tmX53lMV(a ze*`~CL_t(YiKSIbY*kehUHjbgK5f&Mwmd!-3YCwvenOC71XR?7gdq;p5Ia+2qQpd< znV^nLOiYX;aiWP13^NiZ5W*)h7*GOH`ILfCTCh(9UTt67chCMfxVL?uHVkmzoaCH) z&fV*-z1QA{%nY^GIOl|* zIro)saQ+C1KDEkARE%}sg=uGn=^w!_SOLa zc$DIq=)foMY%~a(Vma$F*&3pM}6yiGw>oJQ@X2QZT7AQdOuom@3p1 zt+}j)J7$M}yF7Jf=th8V0=@gq!LNV4dUGHm!lJQ@*?x>Tg9A*V}$)9mv>3b7Z2(fox6$Ue$2qk84mSdNaec&mm?;WC|9U8%Ix` z!Pt$_svNDF0|030>csZVkHMV7z2G3^FkT6Ds_|lmN?@=ETofhY5rL>+%f^0;uUQ2a zNf?SkEeVktkjq3##nm`0f0t)wtfqRss#0J^U_mD96Xzlj7MwnJ5r2=~O6krfx*Ff+ z&JJwb)L#ccwF>}V%}|L0L=>h5NfXMx0>e`cuGo_;X%szwqR zEUAK6{+DxWSbNok0jCT?5ZJt94{l!75VOpw2LKRaPzd+`5`bkgKqlMpvi|4N9K>}m zmm|iW-8(RReFRhGY2@=+bS>^kT81zrRoUTF7f{M9zCVM;8w~)69C{z_!H)+I;m<4A zv36ZAUU_YI(wjm+e+U5_0zen$vr5wt<+AF$Ki7C4%o(%5Y9uPcU=DLN5}0f5%&KMb z`ASnq`(28nlg>wPE(ky{FmoLM2Tq*_h%iKW!2;`Bx#Zi!AHDwm?AiggU>(i_NF~fH z30UBT{%oH4-iHb|$L~$_2#30DVMd|Q2~$O3Y1h2)FxLYZT@iY^mrZwewCu0L{P@^0 z|I>-fH*ei8btH|7kh?SH7G)zcRfr`$3T9@eP)%BfsbV_Hnd@G`0MOi&k9R%U_r!-U dy)ax0@E^{aDA}lulKlVx002ovPDHLkV1gl)8UO$Q literal 1681 zcma)+e>l@y9LK*dF;eIkk~X?3w`Q9aGe4)vj})yf$z8HFYhqhA#w{_Fsb%t8Tpl4c z-EmVZP5Ln@S(bWAn_s2h$&Zv+iu+xjo_p^f_j&Glp3ixo=bZQJocB3@oXp@YzVnQh z8UX+>&)*ME0M$ZU5Ph(J%5$FtMW3?C7Y{1fG#g~epcu0KLOB4S8?P;$q(`X=?PNR~ zM+fyg4^t(;V-z@ss*X&679fepm)TinVwR&+eKiyfikp)j zF;6EIV&B!0xA)``!rkLsHhc77wy8qc?aO1nCIpk-rRh>kb1=5L29_Au`7j?ZQX4r4 zlIn-=*-G+;RDEptsFXFA^t&~+4PMiblSca1=&Aias}mR(}yW%oE}=<<9F=kfC0T7JZJfVu#H@2)! zSe$~Ye_z<$Co85h7kX{4elqM{tYQZ*9<5kW`Yj1rrUW9DB6KX@9bag(arIzrd@I!DuI(Ty)e&}sf@ z2C~6_0lHH13vflUBA>7lAlbF16x^)LFbVx*J7%;JI+IQ$Z5uiY8NZ=JA|LN{6($%2 zbjVYdE%tYLy~bEArCwF-j>KehPBW}1M{|WXdRHX!9@IozS`K>iLsR^j!7nmng-|jm z4PS9#(U<0$m@q;>h=SF<#9B#=+Z>R2FLFaianA&i&MO129>FI zk^Zj`zBJ~oI;(jk)8@YDY3=xz&Zs!ey4KY&x_#l83BMdGIQxJnueQ3xv_-LN4uQtd zx-Oabf{J}%Y9`R;!l8Q9FqRU8P&XGBjh00%1=q2i~Q^bg2S-d%I-@d^Y&_>Yb9Y=rbY$Yyz6<33-`6S&+`S+=W{uI^pH-B`QvG&kBjvVW z>)h63;5TH)_1VFt#?m6VH25B{100b^7dQ$9N3AEKoUuqpEZP~41P90rlRVK6K9SG31kpdZ18k0i<_Hwyw zECTWUEJi$y%8H3$F*y#bSUMtl*1|zh65*T(1lj?KfU#m>|Az?l4Gu&&I-n3?A~))f zjI;6n@d`*Z9OXhpA+hTnvFk8!q!aiGD|cSr1Ok@YjN4d|TwX*h4dC&34h-f#4mE;J z(?%;9c3TRX7S5XDVyQGBC?SzfW5WEYVEXqKk~iS;s=t@RNrb>3SJiID`tF7^wC?YB T9dQ=7g9za7vjs2l-ktgv_2S*@ diff --git a/lib/matplotlib/mpl-data/images/saveall.svg b/lib/matplotlib/mpl-data/images/saveall.svg index 5aff096cb7b8..66260e8f6b18 100644 --- a/lib/matplotlib/mpl-data/images/saveall.svg +++ b/lib/matplotlib/mpl-data/images/saveall.svg @@ -1,6539 +1,32873 @@ - - - - - - - - - unsorted - - - - - Open Clip Art Library, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons, Source: Oxygen Icons - - - - - - - - - - - - - + + + + + image/svg+xml - - - en + + - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - + + + + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + - - - + + - - - - - + + + + + + + + - - + + + - - - + + + + + - - - + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + + - - - - - + + + - - - - - - - - - + + - - + + + + + - - + + + - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + + + + - - \ No newline at end of file + diff --git a/lib/matplotlib/mpl-data/images/saveall.xpm b/lib/matplotlib/mpl-data/images/saveall.xpm index 8af136b807ab..d3b03d7028d1 100644 --- a/lib/matplotlib/mpl-data/images/saveall.xpm +++ b/lib/matplotlib/mpl-data/images/saveall.xpm @@ -1,107 +1,168 @@ /* XPM */ -static char *document_save_all[] = { +static char *saveall[] = { /* columns rows colors chars-per-pixel */ -"24 24 77 1 ", -" c #1C1A1B", -". c #262525", -"X c #2D2A2B", -"o c #322E2F", -"O c #322F30", -"+ c #353233", -"@ c #393536", -"# c #3B3738", -"$ c #3D3A3B", -"% c #413E3F", -"& c #423F40", -"* c #454243", -"= c #484546", -"- c #494748", -"; c #4D4B4B", -": c #504D4E", -"> c #545253", -", c #585657", -"< c #595758", -"1 c #5C5B5C", -"2 c #605E5F", -"3 c #5E5E60", -"4 c #646364", -"5 c #686667", -"6 c #696768", -"7 c #6B6A6B", -"8 c #706E6F", -"9 c #6D6F73", -"0 c #6F7074", -"q c #747374", -"w c #7C7B7B", -"e c #817F80", -"r c #838283", -"t c #8D8D8D", -"y c #8C9196", -"u c #929292", -"i c #999999", -"p c #9B9FA5", -"a c #9EA2A6", -"s c #9EA4AB", -"d c #A3A3A3", -"f c #A2A6AB", -"g c #A7A8AB", -"h c #ABACAD", -"j c #A4ACB3", -"k c #B5B5B5", -"l c #B8B7B7", -"z c #B0B6BD", -"x c #B3B9BE", -"c c #B9B8B8", -"v c #B5BCC2", -"b c #BABFC4", -"n c #BFC2C5", -"m c #BEC3C8", -"M c #BEC9D3", -"N c #C2C4C7", -"B c #C2C5C9", -"V c #C6C9CD", -"C c #C1CBD4", -"Z c #CFD5DB", -"A c #D1D6DB", -"S c #D5D8DF", -"D c #DCE0E7", -"F c #D6E1EB", -"G c #DCE4EC", -"H c #DFE8F0", -"J c #E2E5EC", -"K c #E4E9EF", -"L c #E8EAEF", -"P c #E4EBF2", -"I c #EAEEF5", -"U c #EDF1F6", -"Y c #ECF1F8", -"T c #F0F4F7", -"R c #F2F5F9", -"E c #F6F9FB", -"W c None", +"24 24 138 2 ", +" c #1D2A43", +". c #3B495E", +"X c #004468", +"o c #0C4768", +"O c #034B6E", +"+ c #194C6B", +"@ c #075375", +"# c #085476", +"$ c #0A5678", +"% c #0B5A7B", +"& c #165777", +"* c #185676", +"= c #1E5876", +"- c #155778", +"; c #155878", +": c #1C5B7A", +"> c #294962", +", c #3E4D62", +"< c #34526A", +"1 c #39556B", +"2 c #3D596E", +"3 c #2F5670", +"4 c #265A77", +"5 c #295874", +"6 c #205D7C", +"7 c #2C5D78", +"8 c #315A75", +"9 c #3E5B71", +"0 c #325F7A", +"q c #25607F", +"w c #3F647A", +"e c #495457", +"r c #515454", +"t c #65564C", +"y c #675A4C", +"u c #695B4D", +"i c #746452", +"p c #786551", +"a c #414F64", +"s c #425E74", +"d c #4E656E", +"f c #42667B", +"g c #526273", +"h c #6E6663", +"j c #84654F", +"k c #906D53", +"l c #A66F49", +"z c #D7A463", +"x c #D9A663", +"c c #DDAA65", +"v c #E0AE65", +"b c #266280", +"n c #2A6583", +"m c #2D6986", +"M c #2B6C89", +"N c #336E8A", +"B c #33718C", +"V c #3A748D", +"C c #357591", +"Z c #3D7691", +"A c #3C7B95", +"S c #446C84", +"D c #4D7086", +"F c #487389", +"G c #52748A", +"H c #5A7A8E", +"J c #427A94", +"K c #487F99", +"L c #537F95", +"P c #627284", +"I c #6C7D8C", +"U c #44809A", +"Y c #4B839C", +"T c #588097", +"R c #5C849A", +"E c #56899E", +"W c #75828E", +"Q c #668799", +"! c #6C8C9D", +"~ c #748592", +"^ c #768892", +"/ c #7A8D97", +"( c #7B8C9A", +") c #7F919B", +"_ c #5B8DA2", +"` c #6F90A0", +"' c #7493A3", +"] c #7A96A5", +"[ c #7998A7", +"{ c #7C9BAB", +"} c #6E9EB1", +"| c #8A888F", +" . c #8D8D93", +".. c #908F96", +"X. c #82959F", +"o. c #8D929A", +"O. c #93939B", +"+. c #99979D", +"@. c #8397A1", +"#. c #8699A3", +"$. c #899CA6", +"%. c #809AA8", +"&. c #8B9EA9", +"*. c #9397A0", +"=. c #9699A1", +"-. c #9B9CA2", +";. c #A09DA2", +":. c #83A0AF", +">. c #8EA2AC", +",. c #91A3AD", +"<. c #9DA0A8", +"1. c #83A2B0", +"2. c #8FAAB8", +"3. c #92A6B2", +"4. c #95A9B3", +"5. c #98ACB6", +"6. c #9CAEB9", +"7. c #9EB1BD", +"8. c #A3A0A5", +"9. c #A3A4AA", +"0. c #A6A8AF", +"q. c #ACA8AC", +"w. c #AAACB3", +"e. c #AEB0B6", +"r. c #A0B3BD", +"t. c #B2B4BA", +"y. c #A3B7C1", +"u. c #AABAC4", +"i. c #ACBFC8", +"p. c #AFC2CC", +"a. c #B2C5CE", +"s. c #B6C7D1", +"d. c #B7C9D2", +"f. c #BACCD5", +"g. c #C0D1D9", +"h. c #D6DADF", +"j. c #E9EBED", +"k. c None", /* pixels */ -"WWWW75eCDDDDGJJJJKLLJg8q", -"WWWW>;wZYYYYIYIYTTTRUh57", -"WWWW44rZYITIYYTTRTLUUh45", -"74qxZASDGJJKLLLILNerAh24", -"1;6mIYYYIYYYYYTRYB77Vh14", -"2,7NYRRYRRRRREEEEB76mg14", -"445BRERERREEEEEERN64mf13", -"124NREEEEEEEEERRUm53ma<1", -"114nUURYRRRTTYYIIn33va><", -"113nIIIIPIPPPPPPHv33jy><", -"<<3xHHPHKHHHHHGGGx3<73>,", -"><3xGGGGGGGFFFFFFz1<>;;>", -">>1sCCCCCCCCCMMMMs<,;-::", -">:>509990090099905>>*$;;", -":::;:;;;;;;;;;;::::>-%=-", -";::;,5777666764-==;:=%*%", -"-;--qhrwwkklcch=%$-;*==%", -"===*wh*#@gkkllh;*%=-%*&%", -"&*&%8d*#+dhhhhd;*%**@@$@", -"%$%$7i%@oiddddi-%*-&OXOO", -"##@@4t@X.tiiiiu%#%*%. ", -"@+@+1t=#%ttuttt#O+%#WWWW", -"WXOo * * * - J X X # ", +"M * * * * 3 i.< a 5.3.,.$.@.> * * * ; J X O % ", +"M * * * * < u., a 4.,.&.$.X.> * * * ; K # % k.", +"M * * * * < y., , ,.&.#.X.X.> * * * - } k.k.k.", +"M * * * * < 7., , >.$.@.) / > * * - - } k.k.k.", +"Y ; * * * < 6.g . g $.#.) / ^ > * * - C k.k.k.k.", +"k.K M M M 8 s 9 9 9 2 2 1 1 < 3 B B A k.k.k.k.k." }; From 2c06a643cf88cca601ddeebee78f16d9c4fdb03d Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 23 Oct 2013 13:47:55 -0400 Subject: [PATCH 17/20] rebase for review --- examples/pylab_examples/multiple_figs_demo.py | 32 +- .../multifigure_backend_gtk3.py | 84 + lib/matplotlib/backend_bases.py | 1586 +++++++++++----- lib/matplotlib/backends/backend_gtk3.py | 1292 ++++++++------ lib/matplotlib/backends/backend_gtk3agg.py | 11 +- lib/matplotlib/backends/backend_gtk3cairo.py | 11 +- .../mpl-data/images/axes_editor.png | Bin 0 -> 1025 bytes .../mpl-data/images/axes_editor.svg | 644 +++++++ .../mpl-data/images/axes_editor.xpm | 39 + .../mpl-data/images/line_editor.png | Bin 0 -> 1586 bytes .../mpl-data/images/line_editor.svg | 1589 +++++++++++++++++ .../mpl-data/images/line_editor.xpm | 187 ++ lib/matplotlib/mpl-data/images/saveall.ppm | Bin 0 -> 1741 bytes 13 files changed, 4488 insertions(+), 987 deletions(-) create mode 100644 examples/user_interfaces/multifigure_backend_gtk3.py create mode 100644 lib/matplotlib/mpl-data/images/axes_editor.png create mode 100644 lib/matplotlib/mpl-data/images/axes_editor.svg create mode 100644 lib/matplotlib/mpl-data/images/axes_editor.xpm create mode 100644 lib/matplotlib/mpl-data/images/line_editor.png create mode 100644 lib/matplotlib/mpl-data/images/line_editor.svg create mode 100644 lib/matplotlib/mpl-data/images/line_editor.xpm create mode 100644 lib/matplotlib/mpl-data/images/saveall.ppm diff --git a/examples/pylab_examples/multiple_figs_demo.py b/examples/pylab_examples/multiple_figs_demo.py index c49e197375a7..ebd288021a52 100644 --- a/examples/pylab_examples/multiple_figs_demo.py +++ b/examples/pylab_examples/multiple_figs_demo.py @@ -1,40 +1,24 @@ #!/usr/bin/env python # Working with multiple figure windows and subplots - -import matplotlib -matplotlib.use('gtk3agg') -matplotlib.rcParams['backend.single_window'] = True from pylab import * -from matplotlib.backend_bases import ToolBase -class SampleNonGuiTool(ToolBase): - def set_figures(self, *figures): - #stupid routine that says how many axes and lines are in each - #figure - for figure in figures: - title = figure.canvas.get_window_title() - print(title) - lines = [line for ax in figure.axes for line in ax.lines] - print('Axes: %d Lines: %d' % (len(figure.axes), len(lines))) - - t = arange(0.0, 2.0, 0.01) -s1 = sin(2 * pi * t) -s2 = sin(4 * pi * t) +s1 = sin(2*pi*t) +s2 = sin(4*pi*t) figure(1) subplot(211) -plot(t, s1) +plot(t,s1) subplot(212) -plot(t, 2 * s1) +plot(t,2*s1) figure(2) -plot(t, s2) +plot(t,s2) # now switch back to figure 1 and make some changes figure(1) subplot(211) -plot(t, s2, 'gs') +plot(t,s2, 'gs') setp(gca(), 'xticklabels', []) figure(1) @@ -42,8 +26,4 @@ def set_figures(self, *figures): figure(2) savefig('fig2') -figure(2).canvas.toolbar.add_tool(SampleNonGuiTool, text='Stats') - - - show() diff --git a/examples/user_interfaces/multifigure_backend_gtk3.py b/examples/user_interfaces/multifigure_backend_gtk3.py new file mode 100644 index 000000000000..d7d1631245e0 --- /dev/null +++ b/examples/user_interfaces/multifigure_backend_gtk3.py @@ -0,0 +1,84 @@ +import matplotlib +matplotlib.use('GTK3Agg') +matplotlib.rcParams['backend.single_window'] = True +import matplotlib.pyplot as plt +from matplotlib.backend_bases import ToolBase +import numpy as np + +x = np.arange(100) + +#Create 4 figures +fig1 = plt.figure() +ax1 = fig1.add_subplot(111) +ax1.plot(x, x ** 2, marker='o', label='hey', picker=5) +ax1.legend(loc = 'lower left') + +fig2 = plt.figure() +ax2 = fig2.add_subplot(111) +ax2.plot(x , np.sqrt(x)) +ax2.set_xlabel('x') +ax2.set_ylabel('y') +#In the axes control tool, there is a second axes for this subplot, check it out :) +ax22 = ax2.twinx() +ax22.plot(x, -np.sqrt(x), picker=5, marker='x', label='in second axis') +ax22.set_ylabel('Minus x') + +d=5 +fig3 = plt.figure() +ax3 = fig3.add_subplot(111) +ax3.plot(x[::d], (x ** 3)[::d], 'ro-', label='Line label') + +fig4 = plt.figure() +ax41 = fig4.add_subplot(211) +ax41.plot(x, x + 5, label='add 5') + +ax42 = fig4.add_subplot(212) +ax42.plot(x, np.log(x + 15), label='add 15') + +################### +#Figure management +#Change the figure1 tab label +fig1.canvas.manager.set_window_title('My first Figure') + +#Change the figure manager window title +fig1.canvas.manager.set_mainwindow_title('The powerful window manager') + +#Detach figure3 from the rest +fig3.canvas.manager.detach() + +#Put the figure4 in the same manager as fig3 +fig4.canvas.manager.reparent(fig3) + +#Control the parent from the figure instantiation with the parent argument +#To place it in the same parent as fig1 we have several options +#parent=fig1 +#parent=fig1.canvas.manager +#parent=fig2.canvas.manager.parent +fig5 = plt.figure(parent=fig1) +ax5 = fig5.add_subplot(111) +ax5.plot(x , x**4) +#if we want it in a separate window +#parent=False + + +################### +#Toolbar management +class SampleNonGuiTool(ToolBase): + text = 'Stats' + def set_figures(self, *figures): + #stupid routine that says how many axes are in each figure + for figure in figures: + title = figure.canvas.get_window_title() + print('Figure "%s": Has %d axes' % (title, len(figure.axes))) + +#Add simple SampleNonGuiTool to the toolbar of fig1-fig2 +fig1.canvas.manager.toolbar.add_tool(SampleNonGuiTool) + +#Lets reorder the buttons in the fig3-fig4 toolbar +#Back? who needs back? my mom always told me, don't look back, +fig3.canvas.manager.toolbar.remove_tool(1) + +#Move home somewhere nicer, I always wanted to live close to a rainbow +fig3.canvas.manager.toolbar.move_tool(0, 8) + +plt.show() \ No newline at end of file diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index fff4093d3047..e0330d668344 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -25,6 +25,28 @@ the 'show' callable is then set to Show.__call__, inherited from ShowBase. +The following classes, are the classes that define a multi-figure-manager, this is a variant +of figure manager, that allows to have multiple figures under the same GUI interface + +:class:`ChildFigureManager` + The class that initializes the gui and the Navigation (toolbar state), + this is the class that the canvas sees as the manager + +:class:`MultiFigureManagerBase` + The base class for gui interface that allows to have several canvas groupped + under the control of the same window and same toolbar + +:class:`Navigation` + Class that holds the navigation state (or toolbar state) for one specific canvas. + This class is attached to `ChildFigureManager.navigation` + +:class:`MultiFigureToolbarBase` + The base class that defines the GUI interface for the toolbar of the `MultiFigureManagerBase` + It allows to swtich the control from one canvas to another. + +:class:`ToolBase` + The base class for tools that can be added to a derivate of `MultiFigureToolbarBase` + """ from __future__ import (absolute_import, division, print_function, @@ -2628,138 +2650,6 @@ def set_window_title(self, title): pass -class ChildFigureManager(FigureManagerBase): - #to acces from figure instance - #figure.canvas.manager - # - #This is an intermediate class and just exposes the figure manager functionality to - #parent. In general there is no need to subclass it. - #To change the figure manager functionality, subclass MultiFigureManagerBase - - parent = None - _parent_class = None - - @classmethod - def initialize(cls): - if cls.parent is None: - cls.parent = cls._parent_class() - - @classmethod - def set_figure_manager(cls, parent_class): - cls._parent_class = parent_class - - def __init__(self, canvas, num): - self.initialize() - FigureManagerBase.__init__(self, canvas, num) - - if self.parent.toolbar is None: - self.toolbar = None - else: - self.toolbar = ChildNavigationToolbar(self.canvas, self.parent.toolbar) - self.parent.add_child(self) - - self.canvas.show() - self.window = self.parent.window - - def notify_axes_change(fig): - 'this will be called whenever the current axes is changed' - if self.toolbar is not None: self.toolbar.update() - canvas.figure.add_axobserver(notify_axes_change) - - def show(self): - self.parent.show_child(self) - - def destroy(self): - #this method is called from Gcf.destroy(num) - self.canvas.destroy() - self.parent.remove_child(self) - if self.toolbar: - self.toolbar.remove() - - def resize(self, w, h): - self.parent.resize_child(self, w, h) - - def show_popup(self, msg): - self.parent.show_popup(self, msg) - - def get_window_title(self): - return self.parent.get_child_title(self) - - def set_window_title(self, title): - self.parent.set_child_title(self, title) - - def get_mainwindow_title(self): - return self.parent.get_window_title() - - def set_mainwindow_title(self, title): - self.parent.set_window_title(title) - - -class MultiFigureManagerBase(object): - def __init__(self): - #Create the main window, - #add the widget that will contain the children - #add the multi-figure-toolbar - raise NotImplementedError - - def switch_child(self, child): - #Call this method when you have located the child - #this just inform the multi-figure-toolbar that the child has changed - #For example in the gtk3 backend, this is called after finding - #the new selected tab - - if self.toolbar is None: - return - self.toolbar.switch_child(child.toolbar) - - def destroy(self): - pass - - def add_child(self, child): - #add the child to the multi-figure-manager - #this is is the place were you should add an individual close button for the child - #this close action should call Gcf.destroy(num) - raise NotImplementedError - - def remove_child(self, child): - #Remove the child from the control of this multi-figure-manager - #visually and logically - #do not destroy the child - raise NotImplementedError - - def show_child(self, child): - """Find the appropiate child container and show it""" - pass - - def set_child_title(self, child, title): - """ - Set the title text of the container containing the figure. - """ - pass - - def get_child_title(self, child): - """ - Get the title text of the container containing the figure - """ - pass - - def set_window_title(self, title): - """ - Set the title text of the multi-figure-manager window. - """ - pass - - def get_window_title(self): - """ - Get the title text of the multi-figure-manager window. - """ - pass - - def show(self): - """Show the multi-figure-manager""" - pass - - class Cursors: # this class is only used as a simple namespace HAND, POINTER, SELECT_REGION, MOVE = list(range(4)) @@ -3342,348 +3232,1210 @@ def set_history_buttons(self): pass -class ChildNavigationToolbar(NavigationToolbar2): - #to acces from figure instance - #figure.canvas.toolbar - # - #There is no need to subclass this, if you want to change the toolbar, - #change multi-figure-toolbar - - def __init__(self, canvas, parent): - self.parent = parent - NavigationToolbar2.__init__(self, canvas) +class ChildFigureManager(FigureManagerBase): + """Entry point for multi-figure-manager backend - def _init_toolbar(self): - self.parent.add_toolbar(self) - self.ctx = None + Extension of `FigureManagerBase` to allow the canvas, to be attached and detached from its GUI interface - def remove(self): - #called by ChildFigureManager.destroy method - self.parent.remove_toolbar(self) + The `parent` is the GUI interface, that is responsible for everything related to display and external controls + This `ChildFigureManager` is responsible for parent instantiation and assignment. - def set_message(self, s): - self.parent.set_child_message(self, s) + Attributes + ---------- + parent : MultiFigureManager + Instance derivate of `MultiFigureManagerBase` that serves as container for several canvas + navigation : `Navigation` + The navigation state of the `canvas` + canvas : FigureManagerCanvas + Canvas that is managed by this `ChildFigureManager` - def set_cursor(self, cursor): - self.parent.set_child_cursor(self, cursor) -# self.canvas.get_property("window").set_cursor(cursord[cursor]) + Examples + ---------- + To access this instance from a figure instance use - def release(self, event): - try: del self._pixmapBack - except AttributeError: pass + >>> figure.canvas.manager - def dynamic_update(self): - # legacy method; new method is canvas.draw_idle - self.canvas.draw_idle() + In Gtk3 the interaction with this class is limited to - def draw_rubberband(self, event, x0, y0, x1, y1): - 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' - self.ctx = self.canvas.get_property("window").cairo_create() + >>> class FigureManagerGTK3(ChildFigureManager): + >>> parent_class = MultiFigureManagerGTK3 - # todo: instead of redrawing the entire figure, copy the part of - # the figure that was covered by the previous rubberband rectangle - self.canvas.draw() + Notes + ---------- + To change the figure manager functionality, subclass `MultiFigureManagerBase` - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - w = abs(x1 - x0) - h = abs(y1 - y0) - rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] + In general it is not necessary to overrride this class. + """ + _parent = None + parent_class = None + """multi-figure-manager class that will holds this child""" - self.ctx.new_path() - self.ctx.set_line_width(0.5) - self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) - self.ctx.set_source_rgb(0, 0, 0) - self.ctx.stroke() - - def __getattr__(self, name): - #we suposse everything else that we want from this child - #belongs into the parent - return getattr(self.parent, name) + navigation_class = None + """Navigation class that will be instantiated as navigation for this child""" - - -class MultiFigureToolbarBase(object): - #to acces from figure instance - #figure.canvas.toolbar.parent - # - # - #The mandatory things you have to implement are - #add_button, - #connect_button - #init_toolbar - #save_figure - # - toolitems = ({'text': 'Home', - 'tooltip_text': 'Reset original view', - 'image': 'home', - 'callback': 'home'}, - - {'text': 'Back', - 'tooltip_text': 'Back to previous view', - 'image': 'back', - 'callback': 'back'}, - - {'text': 'Forward', - 'tooltip_text': 'Forward to next view', - 'image': 'forward', - 'callback': 'forward'}, - - None, - - {'text': 'Pan', - 'tooltip_text': 'Pan axes with left mouse, zoom with right', - 'image': 'move', - 'callback': 'pan', - 'toggle': True}, - - {'text': 'Zoom', - 'tooltip_text': 'Zoom to rectangle', - 'image': 'zoom_to_rect', - 'callback': 'zoom', - 'toggle': True}, - - {'text': 'Save', - 'tooltip_text': 'Save the figure', - 'image': 'filesave', - 'callback': 'save_figure'}, - - None, - ) - external_toolitems = ( - ) - - _external_instances = weakref.WeakValueDictionary() - _toolbars = [] - - def __init__(self): - self.init_toolbar() - - for pos, btn in enumerate(self.toolitems): - if btn is None: - self.add_separator(pos=pos) - continue - callback = btn.pop('callback') - tbutton = self.add_button(pos=pos, **btn) - if tbutton: - self.connect_button(tbutton, 'clicked', callback) - - for pos, btn in enumerate(self.external_toolitems): - callback = btn.pop('callback') - i_pos = pos + len(self.toolitems) - self.add_tool(callback, pos=i_pos, **btn) - - self.add_separator(len(self.external_toolitems) + len(self.toolitems)) - self.add_message() - self._current = None - - - def add_tool(self, callback, **kwargs): - #this method called from the exterior and from the interior - #will add a tool to the toolbar - #this tool, will behave like normal button - #the first time it is clicked, it will get all the figures - #after that, if it is clicked again, it will call the show method - #if _current changes (switch the active figure from the manager) - #the set_figures method is invoked again - - cls = self._get_cls_to_instantiate(callback) - if not cls: - self.set_message('Not available') - return - - #if not passed directly from the call, look for them in the class - text = kwargs.pop('text', cls.text) - tooltip_text = kwargs.pop('tooltip_text', cls.tooltip_text) - pos = kwargs.pop('pos', cls.pos) - image = kwargs.pop('image', cls.image) - toggle = kwargs.pop('toggle', cls.toggle) - - tbutton = self.add_button(pos=pos, text=text, - tooltip_text=tooltip_text, - image=image, - toggle=toggle) - if not tbutton: - return - - self.connect_button(tbutton, 'clicked', '_external_callback', cls, **kwargs) - - def remove_tool(self, pos): - #remote item from the toolbar, - pass - - def move_tool(self, pos_ini, pos_fin): - #move item in the toolbar - pass - - def connect_button(self, button, action, callback, *args, **kwargs): - #This is specific to each library, - #The idea is to get rid of different formating between libraries and - #be able to call internal functions with clicks, selects, etc... - # - #In Gtk3 for example - #def connect_button(self, button, action, callback, *args, **kwargs): - # def mcallback(btn, cb, args, kwargs): - # getattr(self, cb)(*args, **kwargs) - # - # button.connect(action, mcallback, callback, args, kwargs) - - raise NotImplementedError - - def _external_callback(self, callback, **kwargs): - #This handles the invokation of external classes - #this callback class should take only *figures as arguments - #and preform its work on those figures - #the instance of this callback is added to _external_instances - #as a weakreference to inform them of the switch and destroy - - id_ = id(callback) + @classmethod + def get_parent(cls, parent=None): + """Get the parent instance - if id_ in self._external_instances: - self._external_instances[id_].show() - return - - figures = self.get_figures() - - external_instance = callback(*figures, **kwargs) - - self._external_instances[id_] = external_instance + Parameters + ---------- + parent: None (defatult), False, `Figure`, `ChildFigureManager`, `MultiFigureManagerBase` + Used to determine wich parent to set and if necessary instantiate + + Notes + ---------- + if `parent` is: + - False: `parent_class` is instantiated every time (default in rcParams) + - None or True: `parent_class` is instantiated the first time + and reused everytime after + - `Figure`, `ChildFigureManager`, `MultiFigureManagerBase`: Try to extract the parent from + the given instance + """ + + #Force new parent for the child + if parent is False: + new_parent = cls.parent_class() + + #New parent only if there is no previous parent + elif parent in (None, True): + if cls._parent is None or cls._parent() is None: + new_parent = cls.parent_class() + else: + new_parent = cls._parent() + #fig2 = plt.figure(parent=fig1.canvas.manager) + elif isinstance(parent, ChildFigureManager): + new_parent = parent.parent + + #fig2 = plt.figure(parent=fig1.canvas.manager.parent) + elif isinstance(parent, MultiFigureManagerBase): + new_parent = parent + + else: + #fig2 = plt.figure(parent=fig1) + try: + parent = parent.canvas.manager.parent + except AttributeError: + raise AttributeError('%s is not a Figure, ChildFigureManager or a MultiFigureManager' % parent) + else: + new_parent = parent - def _get_cls_to_instantiate(self, callback_class): - if isinstance(callback_class, basestring): - #FIXME: make more complete searching structure - if callback_class in globals(): - return globals()[callback_class] - - mod = self.__class__.__module__ - current_module = __import__(mod, - globals(),locals(), [mod],0) + #keep the reference only if there are children with this parent + cls._parent = weakref.ref(new_parent) + return new_parent - return getattr(current_module, callback_class, False) + def __init__(self, canvas, num, parent=None): + self.parent = self.get_parent(parent) + FigureManagerBase.__init__(self, canvas, num) - return callback_class - - def home(self, *args): - self._current.home(*args) + if self.navigation_class is None: + self.navigation_class = Navigation + self.navigation = self.navigation_class(self.canvas) + self.navigation.set_toolbar(self.parent.toolbar) + self.parent.add_child(self) + self.canvas.show() - def back(self, *args): - self._current.back(*args) + def notify_axes_change(fig): + 'this will be called whenever the current axes is changed' + if self.navigation is not None: self.navigation.update() + canvas.figure.add_axobserver(notify_axes_change) - def forward(self, *args): - self._current.forward(*args) + def reparent(self, parent): + """Change the multi-figure-manager controlling this child - def pan(self, *args): - self._current.pan(*args) + Change the control and visual location of the manager from one multi-figure-manager + to another - def zoom(self, *args): - self._current.zoom(*args) + Parameters + ---------- + parent: + Instance from where to extract the parent + + See Also + -------- + get_parent: Used to get the new parent + + Examples + ---------- + To reparent (group) fig2 in the same parent of fig1 + + >>> fig2.canvas.manager.reparent(fig1) + + Notes + ---------- + Not supported by all backends (tk,...) + """ + + self.navigation.detach() + self.parent.remove_child(self) + + self.parent = self.get_parent(parent) + self.navigation.set_toolbar(self.parent.toolbar) + self.parent.add_child(self) + self.canvas.show() + + def detach(self): + """Remove this child from current parent instantiating a new(empty) one + + Notes + ---------- + Not supported by all backends (tk,...) + + Examples + ---------- + To detach a figure instance + + >>> figure.canvas.manager.detach() + + """ + + parent = self.get_parent(parent=False) + self.reparent(parent) + self.parent.show() + + def show(self): + """Ask `parent` to show this child + """ + + self.parent.show_child(self) + + def destroy(self): + """Remove from parent and from toolbar, and destroy the canvas + + Notes: + ---------- + This method is called from Gcf.destroy(num) + """ + + self.navigation.detach() + self.parent.remove_child(self) + del self.parent + del self.navigation + #For some reason there is not destroy in canvas base + try: + self.canvas.destroy() + except AttributeError: + pass + + def resize(self, w, h): + """Ask the `parent` to resize the space available for this canvas + """ + + self.parent.resize_child(self, w, h) + + def show_popup(self, msg): + """Ask `parent` to Pop up a message to the user + + Parameters + ---------- + msg : string + Text to show + """ + self.parent.show_popup(self, msg) + + def get_window_title(self): + """Get the title of the window/tab/... containing this canvas + """ + return self.parent.get_child_title(self) + + def set_window_title(self, title): + """Set the title of the window/tab/... containing this canvas + """ + self.parent.set_child_title(self, title) + + def get_mainwindow_title(self): + """Get the title of the `parent` window + """ + return self.parent.get_window_title() + + def set_mainwindow_title(self, title): + """Set the title of the `parent` window + """ + self.parent.set_window_title(title) + + def __getattr__(self, name): + #There are some parent attributes that we want to reflect as ours + if name in ('toolbar', 'window', 'full_screen_toggle'): + return getattr(self.parent, name) + raise AttributeError('Unknown attribute %s' % name) + + +class MultiFigureManagerBase(object): + """Base class for the multi-figure-manager + + This class defines the basic methods that the backend specific GUI interface implementation + has to have. + + .. note:: This class is instantiated automatically by `ChildFigureManager.get_parent` and does not + passes any argument + + .. warning:: The `__init__` method should not receive any argument + + Notes + ---------- + The mandatory methods for a specific backend are + + - `__init__` : Creation of window, notebooks, etc.. and addition of multi-figure-toolbar if relevant + - `destroy` + - `add_child` + - `remove_child` + """ + def __init__(self): + raise NotImplementedError + + def switch_child(self, child): + """Method to inform the toolbar that the active canvas has changed + + Parameters + ---------- + child : `ChildFigureManager` + Instance of `ChildFigureManager` to set as active in the toolbar + + Notes + ---------- + There is no need to override this method, just to make sure to invoke it when + changing the active child + + Examples + ---------- + In the gtk3 backend, this is called when the user selects a new tab after finding the new selected tab + + >>> self.notebook.connect('switch-page', self._on_switch_page) + >>> ... + >>> def _on_switch_page(self, notebook, pointer, num): + >>> canvas = self.notebook.get_nth_page(num) + >>> self.switch_child(canvas.manager) + """ + + #Here we invoke switch_navigation with child.canvas.toolbar instead os child.navigation + #because for canvas, navigation is the toolbar + if self.toolbar is None: + return + self.toolbar.switch_navigation(child.canvas.toolbar) + + def destroy(self): + """Destroy all the gui stuff + """ + pass + + def add_child(self, child): + """Add child + + Add a child to this multi-figure-manager + + Parameters + ---------- + child : `ChildFigureManager` + Instance of `ChildFigureManager` that will be controlled by this instance + + Notes + ---------- + This method involves adding the canvas to a container (notebook tab, tree branch, panning window, etc...), + providing individual close and detach buttons + + - close button : should call Gcf.destroy(num) + - detach button : should call `child.detach` on the child parameter + + This method is called from `ChildFigureManager.__init__` and `ChildFigureManager.reparent` + + This method is not called as answer to a user interaction with the GUI + + """ + raise NotImplementedError + + def remove_child(self, child): + """Remove child + + Remove the child from the control of this multi-figure-manager without destroying it. + + Parameters + ---------- + child : `ChildFigureManager` + Instance of `ChildFigureManager` that will be remove from its control + + Notes + ---------- + This method involves removing the container that holds the child + + .. warning:: Do not call destroy on the child, it may be relocated to another parent + """ + #Remove the child from the control of this multi-figure-manager + #visually and logically + #do not destroy the child + raise NotImplementedError + + def show_child(self, child): + """Find the appropiate child container and show it""" + pass + + def set_child_title(self, child, title): + """ + Set the title text of the container (notebook tab/tree branch name/etc...) containing the figure. + """ + pass + + def get_child_title(self, child): + """ + Get the title text of the container (notebook tab/tree branch name/etc...) containing the figure + """ + pass + + def set_window_title(self, title): + """ + Set the title text of the multi-figure-manager window. + """ + pass + + def get_window_title(self): + """ + Get the title text of the multi-figure-manager window. + """ + pass + + def show(self): + """Show the multi-figure-manager""" + pass + + def full_screen_toggle(self): + """Toggle full screen mode""" + pass + + +class MultiFigureToolbarBase(object): + """Base class for the multi-figure-manager-toolbar + + This class defines the basic methods that the backend specific implementation + has to have. + + Notes + ---------- + The mandatory methods for a specific backend are + + - `add_toolitem` + - `connect_toolitem` + - `init_toolbar` + - `save_figure` + - `save_all_figures` + + The suggested methods to implement are + + - `remove_tool` + - `move_tool` + - `set_visible_tool` + + + Each implementation defines it's own system of coordinates, that use the `pos` + argument (used in different methods) to refer to the exact placement of each toolitem + + Examples + ---------- + To access this instance from a figure isntance + + >>> figure.canvas.toolbar.toolbar + + Some undefined attributes of `Navigation` call this class via + `Navigation.__getattr__`, most of the time it can be accesed directly with + + >>> figure.canvas.toolbar + """ + toolitems = ({'text': 'Home', + 'tooltip_text': 'Reset original view', + 'image': 'home', + 'callback': 'home'}, + + {'text': 'Back', + 'tooltip_text': 'Back to previous view', + 'image': 'back', + 'callback': 'back'}, + + {'text': 'Forward', + 'tooltip_text': 'Forward to next view', + 'image': 'forward', + 'callback': 'forward'}, + + None, + + {'text': 'Pan', + 'tooltip_text': 'Pan axes with left mouse, zoom with right', + 'image': 'move', + 'callback': 'pan'}, + + {'text': 'Zoom', + 'tooltip_text': 'Zoom to rectangle', + 'image': 'zoom_to_rect', + 'callback': 'zoom'}, + + {'text': 'Save', + 'tooltip_text': 'Save the figure', + 'image': 'filesave', + 'callback': 'save_figure'}, + + {'text': 'SaveAll', + 'tooltip_text': 'Save all figures', + 'image': 'saveall', + 'callback': 'save_all_figures'}, + + None, + ) + """toolitems=({}) + + List of Dictionnaries containing the default toolitems to add to the toolbar + + Each dict element of contains + - text : Text or name for the tool + - tooltip_text : Tooltip text + - image : Image to use + - callback : Function callback definied in this class or derivates + """ + external_toolitems = () + """List of Dictionnaries containing external tools to add to the toolbar + + Each item has the same structure of `toolitems` items but with callback being a + string or class pointing to a `ToolBase` derivate + + Examples + ---------- + In Gtk3 backend + + >>> external_toolitems = ({'text': 'Subplots', + >>> 'tooltip_text': 'Configure subplots', + >>> 'image': 'subplots', + >>> 'callback': 'ConfigureSubplotsGTK3'}, + >>> {'callback': 'LinesProperties'}, + >>> {'callback': 'AxesProperties'} + >>> ) + + """ + + def __init__(self): + self._external_instances = {} + self._navigations = [] + self.init_toolbar() + self.add_message() + + for pos, item in enumerate(self.toolitems): + if item is None: + self.add_separator(pos=pos) + continue + btn = item.copy() + callback = btn.pop('callback') + tbutton = self.add_toolitem(pos=pos, **btn) + if tbutton: + self.connect_toolitem(tbutton, callback) + #we need this reference to hide it when only one figure + if btn['text'] == 'SaveAll': + self.__save_all_toolitem = tbutton + + for pos, item in enumerate(self.external_toolitems): + btn = item.copy() + callback = btn.pop('callback') + i_pos = pos + len(self.toolitems) + self.add_tool(callback, pos=i_pos, **btn) + + self.add_separator(len(self.external_toolitems) + len(self.toolitems)) + + self._current = None + + def init_toolbar(self): + """Initialized the toolbar + + Creates the frame to place the toolitems + """ + raise NotImplementedError + + def add_tool(self, callback, **kwargs): + """Add toolitem to the toolbar and connect it to the callback + + The optional arguments are the same strcture as the elements of `external_toolitems` + + .. note:: It is not recommended to override this method when implementing a + specifc backend toolbar + + This method calls `add_toolitem` to place the item in the toolbar and `connect_toolitem` + to handle the callback + + Parameters + ---------- + callback : String or class that is a derivate of `ToolBase` + + Examples + ---------- + If `SampleTool` is defined (derivate of `ToolBase`) + + >>> fig2.canvas.toolbar.add_tool(SampleTool, text='Stats') + + Will add the `SampleTool` to the toolbar + + Notes + ---------- + The first time this tool is activated it will instantiate the callback class and call set_figures, + if activated again, will call the show method of the callback class + + If the active figure changes (switch the active figure from the manager) + the set_figures method of the callback class is invoked again. + + """ + + cls = self._get_cls_to_instantiate(callback) + if not cls: + self.set_message('%s Not found' % callback) + return + + #if not passed directly from the call, look for them in the class + text = kwargs.pop('text', cls.text) + tooltip_text = kwargs.pop('tooltip_text', cls.tooltip_text) + pos = kwargs.pop('pos', cls.pos) + image = kwargs.pop('image', cls.image) + + tbutton = self.add_toolitem(pos=pos, text=text, + tooltip_text=tooltip_text, + image=image) + if not tbutton: + return + + self.connect_toolitem(tbutton, '_external_callback', cls, **kwargs) + + def remove_tool(self, pos): + """Remove the tool located at given position + + .. note:: It is recommended to implement this method + + Parameters + ---------- + pos : backend specific + Position (coordinates) where the tool to remove is located + """ + #remote item from the toolbar, + pass + + def set_visible_tool(self, toolitem, visible): + """Toggle the visibility of a toolitem + + Parameters + ---------- + toolitem: backend specific + toolitem returned by `add_toolitem` + visible: bool + if true set visible, + if false set invisible + + Notes + ---------- + This method is used to automatically hide save_all button when + there is only one figure. It is called from `add_navigation` and + `remove_navigation` + """ + + pass + + def move_tool(self, pos_ini, pos_fin): + """Move the tool between to positions + + .. note:: It is recommended to implement this method + + Parameters + ---------- + pos_ini : backend specific + Position (coordinates) where the tool to is located + pos_fin : backend specific + New position (coordinates) where the tool will reside + """ + #move item in the toolbar + pass + + def connect_toolitem(self, toolitem, callback, *args, **kwargs): + """Connect the tooitem to the callback + + This is backend specific, takes the arguments and connect the added tool to + the callback passing *args and **kwargs to the callback + + The action is the 'clicked' or whatever name in the backend for the activation of the tool + + Parameters + ---------- + toolitem : backend specific + Toolitem returned by `add_toolitem` + callback : method + Method that will be called when the toolitem is activated + + Examples + ---------- + In Gtk3 this method is implemented as + + >>> def connect_toolitem(self, button, callback, *args, **kwargs): + >>> def mcallback(btn, cb, args, kwargs): + >>> getattr(self, cb)(*args, **kwargs) + >>> + >>> button.connect('clicked', mcallback, callback, args, kwargs) + + Notes + ---------- + The need for this method is to get rid of all the backend specific signal handling + """ + + raise NotImplementedError + + def _external_callback(self, callback, **kwargs): + #This handles the invocation of external classes + #this callback class should take only *figures as arguments + #and preform its work on those figures + #the instance of this callback is added to _external_instances + #to inform them of the switch and destroy + + id_ = id(callback) + + if id_ in self._external_instances: + self._external_instances[id_].show() + return + + figures = self.get_figures() + + external_instance = callback(*figures, **kwargs) + if external_instance.register: +# print('register', id_) + external_instance.unregister = lambda *a, **kw: self.unregister_external(id_) + self._external_instances[id_] = external_instance + + def unregister_external(self, id_): + """Unregister an external tool instance from the toolbar + + Notes + ---------- + It is not recommended to override this method when implementing a + specifc backend toolbar + + When registering an external tool, this method replaces the external the method + `ToolBase.unregister` and it is called during `ToolBase.destroy` + + Parameters + ---------- + id_ : int + Id of the callback class for the external tool + """ + if id_ in self._external_instances: +# print ('unregister', id_) + del self._external_instances[id_] + + def _get_cls_to_instantiate(self, callback_class): + if isinstance(callback_class, basestring): + #FIXME: make more complete searching structure + if callback_class in globals(): + return globals()[callback_class] + + mod = self.__class__.__module__ + current_module = __import__(mod, + globals(), locals(), [mod], 0) + + return getattr(current_module, callback_class, False) + + return callback_class + + def __getattr__(self, name): + #The callbacks are handled directly by navigation + #A general getattr from _current may get caught in an infinite loop + #Navigation has a getattr poiting to his class + cbs = [it['callback'] for it in self.toolitems if it is not None] + if name in cbs: + return getattr(self._current, name) + raise AttributeError('Unknown attribute %s' % name) + + def add_navigation(self, navigation): + """Add the `Navigation` under the control of this toolbar + + .. note:: It is not recommended to override this method when implementing a + specifc backend toolbar + + Parameters + ---------- + navigation : `Navigation` + Instance of `Navigation` to add + + Notes + ---------- + This method is called from the child `Navigation.set_toolbar`, during creation and reasignment + """ + + self._navigations.append(navigation) + self._current = navigation + state = len(self._navigations) > 1 + self.set_visible_tool(self.__save_all_toolitem, state) + + def remove_navigation(self, navigation): + """Remove the `Navigation` from the control of this toolbar + + .. note:: It is not recommended to override this method when implementing a + specifc backend toolbar + + Parameters + ---------- + child : `Navigation` + Instance of `Navigation` to remove + + Notes + ---------- + This method is called from `Navigation.detach` + """ + + self._navigations.remove(navigation) + if navigation is self._current: + self._current = None + + state = len(self._navigations) > 1 + self.set_visible_tool(self.__save_all_toolitem, state) - def add_toolbar(self, toolbar): - #this method is called from the child toolbar - self._toolbars.append(toolbar) - self._current = toolbar - - def remove_toolbar(self, toolbar): - self._toolbars.remove(toolbar) - if toolbar is self._current: - self._current = None - def get_figures(self): - #return an array of figures, with the current as the firstone - figures = [self._current.canvas.figure] - others = [toolbar.canvas.figure for toolbar in self._toolbars if toolbar is not self._current] + """Return the figures under the control of this toolbar + + The firsrt figure in the list is the active figure + + .. note:: It is not recommended to override this method when implementing a + specifc backend toolbar + + Returns + ---------- + list + List of figures that are controlled by this toolbar + """ + + figures = [] + if self._current: + figures = [self._current.canvas.figure] + others = [navigation.canvas.figure for navigation in self._navigations if navigation is not self._current] figures.extend(others) return figures - - def add_button(self, text='_', pos=-1, - tooltip_text='', image=None, - toggle=False): - #This should create the button in the toolbar + + def add_toolitem(self, text='_', pos=-1, + tooltip_text='', image=None): + + """Add toolitem to the toolbar + + Parameters + ---------- + pos : backend specific, optional + Position to add the tool, depends on the specific backend how the position is handled + it can be an int, dict, etc... + text : string, optional + Text for the tool + tooltip_text : string, optional + Text for the tooltip + image : string, optional + Reference to an image file to be used to represent the tool + + Returns + ------- + toolitem: Toolitem created, backend specific + + Notes + ---------- + There is no need to call this method directly, it is called from `add_tool` + """ + raise NotImplementedError - - def add_separator(self, pos=0): + + def add_separator(self, pos=-1): + """Add a separator to the toolbar + + Separator is a 'generic' word to describe any kind of item other than toolitem that will be added + to the toolbar, for example an extra container to acomodate more tools + + Parameters + ---------- + pos : backend specific, optional + Position to add the separator, depends on the specific backend how the position is handled + it can be an int, dict, etc... + + """ pass - - def switch_child(self, toolbar): + + def switch_navigation(self, navigation): + """Switch the current navigation under control + + .. note:: It is not recommended to override this method when implementing a + specifc backend toolbar + + Parameters + ---------- + navigation : `Navigation` + Navigation that will be controlled + + Notes + ---------- + When the multi-figure-manager switches child, this toolbar needs to switch too, so it controls + the correct figure + + If there are external instances (tools) inform them of the switch + by invoking instance.set_figures(*figures) + """ + #when multi-figure-manager switches child (figure) #this toolbar needs to switch to, so it controls the correct one #if there are external instances (tools) inform them of the switch #by invoking instance.set_figures(*figures) - - if toolbar not in self._toolbars: - raise AttributeError('This container does not control the given toolbar') - + + if navigation not in self._navigations: + raise AttributeError('This container does not control the given child') + # For these two actions we have to unselect and reselect - d = {'PAN': 'pan', 'ZOOM': 'zoom'} - action = d.get(self._current._active, False) - if action: + if self._current and self._current._active in ('PAN', 'ZOOM'): + action = self._current._active.lower() getattr(self._current, action)() - getattr(toolbar, action)() - self._current = toolbar - + getattr(navigation, action)() + self._current = navigation + figures = self.get_figures() for v in self._external_instances.values(): -# print('setting', v) v.set_figures(*figures) - - def set_child_message(self, child, text): - #In general the mssage from the child are displayed the - #same as message from the parent + + def set_navigation_message(self, navigation, text): + """Set the message from the child + + Parameters + ---------- + navigation : `ChildNavigationToolbar` + Navigation that emits the message + text : string + Text to be displayed + + Notes + ---------- + In general the message from the navigation are displayed the + same as message from the toolbar via `set_message`, overwritting this method + the message can be displayed otherwise + """ + self.set_message(text) - - def set_child_cursor(self, child, cursor): + + def set_navigation_cursor(self, navigation, cursor): + """Set the cursor for the navigation + + Parameters + ---------- + navigation : `Navigation` + Navigation that will get the new cursor + cursor : backend specific + Cursor to be used with this navigation + + Notes + ---------- + Called from `Navigation.set_cursor` + """ pass def set_message(self, text): + """Set message + + Parameters + ---------- + text : string + Text to be displayed + + Notes + ---------- + The message is displayed in the container created by `add_message` + """ pass - + def add_message(self): - #set the message area - #with the possibility to add buttons from the exterior - #it may cause problems with the space too reduced + """Add message container + + The message in this container will be setted by `set_message` and `set_navigation_message` + """ pass + + def save_all_figures(self): + """Save all figures""" + + raise NotImplementedError + + +class Navigation(NavigationToolbar2): + """Holder for navigation information + + In a multi-figure-manager backend, the canvas navigation information is stored here and + the controls belongs to a derivate of `MultiFigureToolbarBase`. + + In general it is not necessary to overrride this class. If you need to change the toolbar + change the backend derivate of `MultiFigureToolbarBase` + + The `toolbar` is responsible for everything related to external controls, + and this is responsible for parent assignment and holding navigation state information. + + There is no need to instantiate this class, this will be done automatically from + `ChildFigureManager` + + Attributes + ---------- + toolbar : MultiFigureToolbar + Instance derivate of `MultiFigureToolbarBase` that serves as container for several `Navigation` + + Examples + ---------- + To access this instance from a figure instance use + + >>> figure.canvas.toolbar + + Notes + ---------- + Every call to this toolbar that is not defined in `NavigationToolbar2` or here will be passed to + `toolbar` via `__getattr__` + + For the canvas there is no concept of navigation, so when it calls the toolbar it pass + throught this class first + """ + + def __init__(self, canvas): + self.toolbar = None + NavigationToolbar2.__init__(self, canvas) + + #The method should be called _init_navigation but... + def _init_toolbar(self): + self.ctx = None + + def set_toolbar(self, toolbar): + """Add itself to the given toolbar + + Parameters + ---------- + toolbar: MultiFigureToolbar + Derivate of `MultiFigureToolbarBase` + + """ + if self.toolbar is not None: + self.detach() + self.toolbar = toolbar + if toolbar is not None: + self.toolbar.add_navigation(self) + + def detach(self): + """Remove this instance from the control of `toolbar` + + Notes + ---------- + This method is called from `ChildFigureManager.destroy`, `ChildFigureManager.reparent` + and `ChildFigureManager.detach` + """ + #called by ChildFigureManager.destroy method + if self.toolbar is not None: + self.toolbar.remove_navigation(self) + self.toolbar = None + + def set_message(self, s): + """Display message from this child + + Parameters + ---------- + s: string + Message to be displayed + """ + if self.toolbar is not None: + self.toolbar.set_navigation_message(self, s) + + def set_cursor(self, cursor): + """Set the cursor to display + + Parameters + ---------- + cursor: cursor + """ + if self.toolbar is not None: + self.toolbar.set_navigation_cursor(self, cursor) +# self.canvas.get_property("window").set_cursor(cursord[cursor]) + + def release(self, event): + """See: `NavigationToolbar2.release`""" + try: del self._pixmapBack + except AttributeError: pass + + def dynamic_update(self): + """See: `NavigationToolbar2.dynamic_update`""" + # legacy method; new method is canvas.draw_idle + self.canvas.draw_idle() + + def draw_rubberband(self, event, x0, y0, x1, y1): + """ + See: `NavigationToolbar2.draw_rubberband` + + Notes + ---------- + Adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744 + """ + + self.ctx = self.canvas.get_property("window").cairo_create() + + # todo: instead of redrawing the entire figure, copy the part of + # the figure that was covered by the previous rubberband rectangle + self.canvas.draw() + + height = self.canvas.figure.bbox.height + y1 = height - y1 + y0 = height - y0 + w = abs(x1 - x0) + h = abs(y1 - y0) + rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] + + self.ctx.new_path() + self.ctx.set_line_width(0.5) + self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) + self.ctx.set_source_rgb(0, 0, 0) + self.ctx.stroke() + + def __getattr__(self, name): + #we suposse everything else that we want from this child + #belongs into the toolbar + if self.toolbar is not None: + return getattr(self.toolbar, name) + raise AttributeError('Unknown %s attribute' % name) + + +class ToolBase(object): + """Base class for tools that can be added to a multi-figure-toolbar + + Establish the basic frame for tools + + The only mandatory method is `set_figures` + + The optional methods are + - `init_tool` + - `destroy` + - `show` + + Attributes + ---------- + `image`: string + `register`: bool + `pos`: int (backend specific) + `tooltip_text`: string + `text`: string + Examples + ---------- + To define a New Tool called SampleNonGuiTool that just prints the number of + lines and axes per figure -class ToolBase(object): - #basic structure for the external tools that work with - #multi-figure-toolbar + >>> from matplotlib.backend_bases import ToolBase + class SampleNonGuiTool(ToolBase): + text = 'stats' + def set_figures(self, *figures): + for figure in figures: + title = figure.canvas.get_window_title() + print(title) + lines = [line for ax in figure.axes for line in ax.lines] + print('Axes: %d Lines: %d' % (len(figure.axes), len(lines))) + + To call this Tool on two figure instances + + >>> SampleNonGuiTool(fig3, fig2) - #Default values for the tool when added to the toolbar - pos=-1 - text='_' - tooltip_text='' - image=None - toggle=False + To add this tool to the toolbar - #Be careful with this arguments, - #when adding the tool to the toolbar, we make sure to pass *figures and **kwargs from - #the user + >>> fig.canvas.toolbar.add_tool(SampleNonGuiTool) + """ + + pos = -1 #: Position (coordinates) for the tool in the toolbar + text = '_' #: Text for tool in the toolbar + tooltip_text = '' #: Tooltip text for the tool in the toolbar + image = None #: Image to be used for the tool in the toolbar + register = False + """Register the tool with the toolbar + + Set to True if this tool is registered by the toolbar and updated at each + figure switch, the toolbar overwrites the `unregister` method to be called at destroy + """ + def __init__(self, *figures, **kwargs): + """ + Parameters + ---------- + *figures : list, optional + List of figures that are going to be used by this tool + **kwargs : optional + Optional arguments that are going to be passed directly to `init_tool` + """ + self.init_tool(**kwargs) - + if figures: self.set_figures(*figures) - + def init_tool(self, **kwargs): + """Perform the tool creation + + Do some initialization work as create windows and stuff + + Parameters + ---------- + **kwargs : optional + keyword arguments to be consumed during the creation of the tool + If the tool is added after toolbar creation, pass this arguments during the call + to `MultiFigureToolbarBase.add_tool` + + Examples + ---------- + If wanting to add the `backends.backend_gtk3.LinesProperties` + + >>> from matplotlib.backends.backend_gtk3 import LinesProperties + >>> fig.canvas.toolbar.add_tool(LinesProperties, pick=False) + + This pick argument is used in the `backends.backend_gtk3.LinesProperties.init_tool` + to prevent the tool to connect to the pick event + """ + #do some initialization work as create windows and stuff #kwargs are the keyword paramters given by the user if kwargs: raise TypeError('init_tool() got an unexpected keyword arguments %s' % str(kwargs)) def set_figures(self, *figures): - #this is the main work, many non gui tools use only this one - #make sure it receives an array *figures. The toolbar caller - #always sent an array with all the figures - #the first figure of the array is the current figure (toolbar point of view) - #if it uses only the fisrt one, use it as figure = figures[0] + """Set the figures to be used by the tool + + .. warning:: Do not modify the signature of this method + + Parameters + ---------- + *figures : list of figures + + Notes + ---------- + This is the main work, many non gui tools use only this method. + + Make sure it receives an array *figures. The toolbar caller + always sends an array with all the figures + + The first figure of the array is the current figure (from the toolbar point of view) + if it uses only the fisrt one, use it as figure = figures[0] + """ + raise NotImplementedError - + def destroy(self, *args): - #called when we want to kill the tool from the creator (toolbar) - pass - + """Destroy the tool + + Perform the destroy action of the tool, + + .. note:: This method should call `unregister` + + """ + self.unregister() + def show(self): - #called when need to bring to focus this specific tool + """Bring to focus the tool + + Examples + ---------- + In Gtk3 this is normally implented as + + >>> self.window.show_all() + >>> self.window.present() + """ + pass + + def unregister(self, *args): + """Unregister the tool with the toolbar + + .. warning:: Never override this method + + Notes + ---------- + This method is overriden by `MultiFigureToolbarBase` derivate during the initialization of + this tool + """ pass - diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index bb501b2a3778..586c24dd885c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, division, print_function, - unicode_literals) + unicode_literals) import six @@ -39,6 +39,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name from matplotlib.widgets import SubplotTool from matplotlib import lines +from matplotlib import markers from matplotlib import cbook from matplotlib import verbose from matplotlib import rcParams @@ -362,135 +363,20 @@ def stop_event_loop(self): FigureCanvas = FigureCanvasGTK3 -class FigureManagerGTK3(FigureManagerBase): - """ - Public attributes - - canvas : The FigureCanvas instance - num : The Figure number - toolbar : The Gtk.Toolbar (gtk only) - vbox : The Gtk.VBox containing the canvas and toolbar (gtk only) - window : The Gtk.Window (gtk only) - """ - def __init__(self, canvas, num): - if _debug: print('FigureManagerGTK3.%s' % fn_name()) - FigureManagerBase.__init__(self, canvas, num) - - self.window = Gtk.Window() - self.set_window_title("Figure %d" % num) - 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.canvas.show() - - self.vbox.pack_start(self.canvas, True, True, 0) - - self.toolbar = self._get_toolbar(canvas) - - # calculate size for window - w = int (self.canvas.figure.bbox.width) - h = int (self.canvas.figure.bbox.height) - - if self.toolbar is not None: - self.toolbar.show() - self.vbox.pack_end(self.toolbar, False, False, 0) - size_request = self.toolbar.size_request() - h += size_request.height - - self.window.set_default_size (w, h) - - def destroy(*args): - Gcf.destroy(num) - self.window.connect("destroy", destroy) - self.window.connect("delete_event", destroy) - 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 destroy(self, *args): - if _debug: print('FigureManagerGTK3.%s' % fn_name()) - self.vbox.destroy() - self.window.destroy() - self.canvas.destroy() - if self.toolbar: - self.toolbar.destroy() - self.__dict__.clear() #Is this needed? Other backends don't have it. - - if Gcf.get_num_fig_managers()==0 and \ - not matplotlib.is_interactive() and \ - Gtk.main_level() >= 1: - Gtk.main_quit() - - def show(self): - # show the figure window - self.window.show() - - def full_screen_toggle (self): - self._full_screen_flag = not self._full_screen_flag - if self._full_screen_flag: - self.window.fullscreen() - else: - self.window.unfullscreen() - _full_screen_flag = False - - - 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 MultiFigureManagerGTK3(MultiFigureManagerBase): #to acces from figure instance #figure.canvas.manager.parent!!!!! - # - - _children = [] - _labels = {} - _w_min = 0 - _h_min = 0 - + def __init__(self, *args): + self._children = [] + self._labels = {} + self._w_min = 0 + self._h_min = 0 + if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) self.window = Gtk.Window() - self.window.set_title("Figuremanager") + self.window.set_title("MultiFiguremanager") try: self.window.set_icon_from_file(window_icon) except (SystemExit, KeyboardInterrupt): @@ -511,6 +397,7 @@ def __init__(self, *args): self.notebook.set_scrollable(True) self.notebook.connect('switch-page', self._on_switch_page) + self.notebook.set_show_tabs(False) self.vbox.pack_start(self.notebook, True, True, 0) self.window.add(self.vbox) @@ -518,13 +405,8 @@ def __init__(self, *args): self.toolbar = self._get_toolbar() if self.toolbar is not None: - self._replace_toolbar_message() - self.toolbar.show() + self.toolbar.show_all() self.vbox.pack_end(self.toolbar, False, False, 0) - - size_request = self.window.size_request() - self._h_def = size_request.height - self._w_def = size_request.width def destroy_window(*args): nums = [manager.num for manager in self._children] @@ -538,26 +420,9 @@ def destroy_window(*args): if matplotlib.is_interactive(): self.window.show() - def _replace_toolbar_message(self): - #This is needed because of additional buttons take too muchs space - if not self.toolbar: - return - - box = Gtk.Box() - box.set_property("orientation", Gtk.Orientation.HORIZONTAL) - - message = Gtk.Label() - box.pack_end(message, False, False, 0) - self.toolbar.message = message - self.vbox.pack_end(box, False, True, 0) - - sep = Gtk.Separator() - sep.set_property("orientation", Gtk.Orientation.HORIZONTAL) - self.vbox.pack_end(sep, False, True, 0) - def _on_switch_page(self, notebook, pointer, num): canvas = self.notebook.get_nth_page(num) - self.switch_child(canvas) + self.switch_child(canvas.manager) def destroy(self): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) @@ -587,6 +452,45 @@ def remove_child(self, child): if self.notebook.get_n_pages() == 0: self.destroy() + self._tabs_changed() + + def _tabs_changed(self): + #Everytime we change the tabs configuration (add/remove) + #we have to check to hide tabs and saveall button(if less than two) + #we have to resize because the space used by tabs is not 0 + + #hide tabs and saveall button if only one tab + if self.notebook.get_n_pages() < 2: + self.notebook.set_show_tabs(False) + notebook_w = 0 + notebook_h = 0 + else: + self.notebook.set_show_tabs(True) + size_request = self.notebook.size_request() + notebook_h = size_request.height + notebook_w = size_request.width + + #if there are no children max will fail, so try/except + try: + canvas_w = max([int(manager.canvas.figure.bbox.width) for manager in self._children]) + canvas_h = max([int(manager.canvas.figure.bbox.height) for manager in self._children]) + except ValueError: + canvas_w = 0 + canvas_h = 0 + + if self.toolbar is not None: + size_request = self.toolbar.size_request() + toolbar_h = size_request.height + toolbar_w = size_request.width + else: + toolbar_h = 0 + toolbar_w = 0 + + w = max(canvas_w, notebook_w, toolbar_w) + h = canvas_h + notebook_h + toolbar_h + if w and h: + self.window.resize(w, h) + def set_child_title(self, child, title): self._labels[child.num].set_text(title) @@ -628,33 +532,35 @@ def add_child(self, child): # close button button = Gtk.Button() - + button.set_tooltip_text('Close') button.set_relief(Gtk.ReliefStyle.NONE) button.set_focus_on_click(False) button.add(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) box.pack_end(button, False, False, 0) - box.show_all() - self.notebook.append_page(canvas, box) - canvas.show() def _remove(btn): Gcf.destroy(num) button.connect("clicked", _remove) - # calculate size for window - w = int(canvas.figure.bbox.width) - h = int(canvas.figure.bbox.height) + # Detach button + button = Gtk.Button() + button.set_tooltip_text('Detach') + button.set_relief(Gtk.ReliefStyle.NONE) + button.set_focus_on_click(False) + button.add(Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)) + box.pack_end(button, False, False, 0) - #we have to put the size of the window as the maximum canvas size - if w > self._w_min: - self._w_min = w - if h > self._h_min: - self._h_min = h + def _detach(btn): + child.detach() + button.connect("clicked", _detach) - self.window.set_default_size(self._w_def + self._w_min, self._h_def + self._h_min) + box.show_all() + canvas.show() - canvas.grab_focus() + self.notebook.append_page(canvas, box) + self._tabs_changed() + self.show_child(child) def show_child(self, child): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) @@ -665,177 +571,42 @@ def show_child(self, child): def show(self): if _debug: print('%s.%s' % (self.__class__.__name__, fn_name())) - self.window.show_all() - - -if rcParams['backend.single_window']: - ChildFigureManager.set_figure_manager(MultiFigureManagerGTK3) - FigureManagerGTK3 = ChildFigureManager - - -class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar): - def __init__(self, canvas, window): - self.win = window - GObject.GObject.__init__(self) - NavigationToolbar2.__init__(self, canvas) - self.ctx = None - - def set_message(self, s): - self.message.set_label(s) - - def set_cursor(self, cursor): - self.canvas.get_property("window").set_cursor(cursord[cursor]) - #self.canvas.set_cursor(cursord[cursor]) - - def release(self, event): - try: del self._pixmapBack - except AttributeError: pass - - def dynamic_update(self): - # legacy method; new method is canvas.draw_idle - self.canvas.draw_idle() - - def draw_rubberband(self, event, x0, y0, x1, y1): - 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744' - self.ctx = self.canvas.get_property("window").cairo_create() - - # todo: instead of redrawing the entire figure, copy the part of - # the figure that was covered by the previous rubberband rectangle - self.canvas.draw() - - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - w = abs(x1 - x0) - h = abs(y1 - y0) - rect = [int(val) for val in (min(x0,x1), min(y0, y1), w, h)] - - self.ctx.new_path() - self.ctx.set_line_width(0.5) - self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) - self.ctx.set_source_rgb(0, 0, 0) - self.ctx.stroke() - - def _init_toolbar(self): - self.set_style(Gtk.ToolbarStyle.ICONS) - basedir = os.path.join(rcParams['datapath'],'images') - - for text, tooltip_text, image_file, callback in self.toolitems: - if text is None: - self.insert( Gtk.SeparatorToolItem(), -1 ) - continue - fname = os.path.join(basedir, image_file + '.png') - image = Gtk.Image() - image.set_from_file(fname) - tbutton = Gtk.ToolButton() - tbutton.set_label(text) - tbutton.set_icon_widget(image) - self.insert(tbutton, -1) - tbutton.connect('clicked', getattr(self, callback)) - tbutton.set_tooltip_text(tooltip_text) - - toolitem = Gtk.SeparatorToolItem() - self.insert(toolitem, -1) - toolitem.set_draw(False) - toolitem.set_expand(True) - - toolitem = Gtk.ToolItem() - self.insert(toolitem, -1) - self.message = Gtk.Label() - toolitem.add(self.message) - - self.show_all() - - def get_filechooser(self): - fc = FileChooserDialog( - title='Save the figure', - parent=self.win, - path=os.path.expanduser(rcParams.get('savefig.directory', '')), - filetypes=self.canvas.get_supported_filetypes(), - default_filetype=self.canvas.get_default_filetype()) - fc.set_current_name(self.canvas.get_default_filename()) - return fc - - def save_figure(self, *args): - chooser = self.get_filechooser() - fname, format = chooser.get_filename_from_user() - chooser.destroy() - if fname: - startpath = os.path.expanduser(rcParams.get('savefig.directory', '')) - if startpath == '': - # explicitly missing key or empty str signals to use cwd - rcParams['savefig.directory'] = startpath - else: - # save dir for next time - rcParams['savefig.directory'] = os.path.dirname(six.text_type(fname)) - try: - self.canvas.print_figure(fname, format=format) - except Exception as e: - error_msg_gtk(str(e), parent=self) - - def configure_subplots(self, button): - toolfig = Figure(figsize=(6,3)) - canvas = self._get_canvas(toolfig) - toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self.canvas.figure, toolfig) - - w = int (toolfig.bbox.width) - h = int (toolfig.bbox.height) - +# self.window.show_all() + self.window.show() - window = Gtk.Window() - try: - window.set_icon_from_file(window_icon) - except (SystemExit, KeyboardInterrupt): - # re-raise exit type Exceptions - raise - except: - # we presumably already logged a message on the - # failure of the main plot, don't keep reporting - pass - window.set_title("Subplot Configuration Tool") - window.set_default_size(w, h) - vbox = Gtk.Box() - vbox.set_property("orientation", Gtk.Orientation.VERTICAL) - window.add(vbox) - vbox.show() - canvas.show() - vbox.pack_start(canvas, True, True, 0) - window.show() +class FigureManagerGTK3(ChildFigureManager): + parent_class = MultiFigureManagerGTK3 - def _get_canvas(self, fig): - return self.canvas.__class__(fig) - -class MultiFigureNavigationToolbar2GTK3(Gtk.Toolbar, MultiFigureToolbarBase): - external_toolitems = ({'text': 'SaveAll', - 'tooltip_text': 'Save all figures', - 'callback': 'SaveFiguresDialogGTK3'}, - {'text': 'Subplots', +class MultiFigureNavigationToolbar2GTK3(Gtk.Box, MultiFigureToolbarBase): + external_toolitems = ({'text': 'Subplots', 'tooltip_text': 'Configure subplots', 'image': 'subplots', - 'callback': 'ConfigureSubplotsGTK3'} + 'callback': 'ConfigureSubplotsGTK3'}, + {'callback': 'LinesProperties'}, + {'callback': 'AxesProperties'} ) - _toggle = [] - + def __init__(self, window): - self.win = window + self.win = window MultiFigureToolbarBase.__init__(self) - def connect_button(self, button, action, callback, *args, **kwargs): + def set_visible_tool(self, toolitem, visible): + toolitem.set_visible(visible) + + def connect_toolitem(self, button, callback, *args, **kwargs): def mcallback(btn, cb, args, kwargs): getattr(self, cb)(*args, **kwargs) - button.connect(action, mcallback, callback, args, kwargs) - - def add_button(self, text='_', pos=-1, - tooltip_text='', image=None, - toggle=False): + button.connect('clicked', mcallback, callback, args, kwargs) + + def add_toolitem(self, text='_', pos=-1, + tooltip_text='', image=None): timage = None if image: timage = Gtk.Image() - + if os.path.isfile(image): timage.set_from_file(image) else: @@ -844,84 +615,77 @@ def add_button(self, text='_', pos=-1, if os.path.isfile(fname): timage.set_from_file(fname) else: + #TODO: Add the right mechanics to pass the image from string # from gi.repository import GdkPixbuf # pixbuf = GdkPixbuf.Pixbuf.new_from_inline(image, False) timage = False - - if toggle: - tbutton = Gtk.ToggleToolButton() - self._toggle.append(tbutton) - tbutton.connect('toggled', self._something_clicked) - else: - tbutton = Gtk.ToolButton() - # attach to _something_clicked so it untoggles the toggled button - tbutton.connect('clicked', self._something_clicked) - + + tbutton = Gtk.ToolButton() + tbutton.set_label(text) if timage: tbutton.set_icon_widget(timage) tbutton.set_tooltip_text(tooltip_text) - self.insert(tbutton, pos) + self._toolbar.insert(tbutton, pos) tbutton.show() return tbutton - + def remove_tool(self, pos): - widget = self.get_nth_item(pos) + widget = self._toolbar.get_nth_item(pos) if not widget: self.set_message('Impossible to remove tool %d' % pos) return - self.remove(widget) + self._toolbar.remove(widget) def move_tool(self, pos_ini, pos_fin): - widget = self.get_nth_item(pos_ini) + widget = self._toolbar.get_nth_item(pos_ini) if not widget: self.set_message('Impossible to remove tool %d' % pos_ini) return - self.remove(widget) - self.insert(widget, pos_fin) - + self._toolbar.remove(widget) + self._toolbar.insert(widget, pos_fin) def add_separator(self, pos=-1): toolitem = Gtk.SeparatorToolItem() - self.insert(toolitem, pos) + self._toolbar.insert(toolitem, pos) return toolitem def init_toolbar(self): - Gtk.Toolbar.__init__(self) - self.set_style(Gtk.ToolbarStyle.ICONS) - - - toolitem = self.add_separator() - toolitem.set_draw(False) - toolitem.set_expand(True) + Gtk.Box.__init__(self) + self.set_property("orientation", Gtk.Orientation.VERTICAL) + self._toolbar = Gtk.Toolbar() + self._toolbar.set_style(Gtk.ToolbarStyle.ICONS) + self.pack_start(self._toolbar, False, False, 0) self.show_all() def add_message(self): - toolitem = Gtk.ToolItem() - self.insert(toolitem, -1) + box = Gtk.Box() + box.set_property("orientation", Gtk.Orientation.HORIZONTAL) + sep = Gtk.Separator() + sep.set_property("orientation", Gtk.Orientation.VERTICAL) + box.pack_start(sep, False, True, 0) self.message = Gtk.Label() - toolitem.add(self.message) + box.pack_end(self.message, False, False, 0) + box.show_all() + self.pack_end(box, False, False, 5) + + sep = Gtk.Separator() + sep.set_property("orientation", Gtk.Orientation.HORIZONTAL) + self.pack_end(sep, False, True, 0) + self.show_all() def save_figure(self, *args): - sd = SaveFiguresDialogGTK3(self.get_figures()[0]) - - def _something_clicked(self, btn): - #when something is clicked, untoggle all toggle buttons - #if it is a toggle button, untoggle the other toggle buttons - #I added this because zoom and pan are toggle now, and they are exclusive - for i in self._toggle: - if i is not btn: - if i.get_active(): - i.handler_block_by_func(self._something_clicked) - i.set_active(False) - i.handler_unblock_by_func(self._something_clicked) + SaveFiguresDialogGTK3(self.get_figures()[0]) + + def save_all_figures(self, *args): + SaveFiguresDialogGTK3(*self.get_figures()) def set_message(self, text): self.message.set_label(text) - def set_child_cursor(self, child, cursor): - child.canvas.get_property("window").set_cursor(cursord[cursor]) + def set_navigation_cursor(self, navigation, cursor): + navigation.canvas.get_property("window").set_cursor(cursord[cursor]) class FileChooserDialog(Gtk.FileChooserDialog): @@ -1000,180 +764,9 @@ def get_filename_from_user (self): return filename, self.ext -class DialogLineprops: - """ - A GUI dialog for controlling lineprops - """ - signals = ( - 'on_combobox_lineprops_changed', - 'on_combobox_linestyle_changed', - 'on_combobox_marker_changed', - 'on_colorbutton_linestyle_color_set', - 'on_colorbutton_markerface_color_set', - 'on_dialog_lineprops_okbutton_clicked', - 'on_dialog_lineprops_cancelbutton_clicked', - ) - - linestyles = [ls for ls in lines.Line2D.lineStyles if ls.strip()] - linestyled = dict([ (s,i) for i,s in enumerate(linestyles)]) - - - markers = [m for m in lines.Line2D.markers if cbook.is_string_like(m)] - - markerd = dict([(s,i) for i,s in enumerate(markers)]) - - def __init__(self, lines): - import Gtk.glade - - datadir = matplotlib.get_data_path() - gladefile = os.path.join(datadir, 'lineprops.glade') - if not os.path.exists(gladefile): - raise IOError('Could not find gladefile lineprops.glade in %s'%datadir) - - self._inited = False - self._updateson = True # suppress updates when setting widgets manually - self.wtree = Gtk.glade.XML(gladefile, 'dialog_lineprops') - self.wtree.signal_autoconnect(dict([(s, getattr(self, s)) for s in self.signals])) - - self.dlg = self.wtree.get_widget('dialog_lineprops') - - self.lines = lines - - cbox = self.wtree.get_widget('combobox_lineprops') - cbox.set_active(0) - self.cbox_lineprops = cbox - - cbox = self.wtree.get_widget('combobox_linestyles') - for ls in self.linestyles: - cbox.append_text(ls) - cbox.set_active(0) - self.cbox_linestyles = cbox - - cbox = self.wtree.get_widget('combobox_markers') - for m in self.markers: - cbox.append_text(m) - cbox.set_active(0) - self.cbox_markers = cbox - self._lastcnt = 0 - self._inited = True - - - def show(self): - 'populate the combo box' - self._updateson = False - # flush the old - cbox = self.cbox_lineprops - for i in range(self._lastcnt-1,-1,-1): - cbox.remove_text(i) - - # add the new - for line in self.lines: - cbox.append_text(line.get_label()) - cbox.set_active(0) - - self._updateson = True - self._lastcnt = len(self.lines) - self.dlg.show() - - def get_active_line(self): - 'get the active line' - ind = self.cbox_lineprops.get_active() - line = self.lines[ind] - return line - - def get_active_linestyle(self): - 'get the active lineinestyle' - ind = self.cbox_linestyles.get_active() - ls = self.linestyles[ind] - return ls - - def get_active_marker(self): - 'get the active lineinestyle' - ind = self.cbox_markers.get_active() - m = self.markers[ind] - return m - - def _update(self): - 'update the active line props from the widgets' - if not self._inited or not self._updateson: return - line = self.get_active_line() - ls = self.get_active_linestyle() - marker = self.get_active_marker() - line.set_linestyle(ls) - line.set_marker(marker) - - button = self.wtree.get_widget('colorbutton_linestyle') - color = button.get_color() - r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] - line.set_color((r,g,b)) - - button = self.wtree.get_widget('colorbutton_markerface') - color = button.get_color() - r, g, b = [val/65535. for val in (color.red, color.green, color.blue)] - line.set_markerfacecolor((r,g,b)) - - line.figure.canvas.draw() - - def on_combobox_lineprops_changed(self, item): - 'update the widgets from the active line' - if not self._inited: return - self._updateson = False - line = self.get_active_line() - - ls = line.get_linestyle() - if ls is None: ls = 'None' - self.cbox_linestyles.set_active(self.linestyled[ls]) - - marker = line.get_marker() - if marker is None: marker = 'None' - self.cbox_markers.set_active(self.markerd[marker]) - - r,g,b = colorConverter.to_rgb(line.get_color()) - color = Gdk.Color(*[int(val*65535) for val in (r,g,b)]) - button = self.wtree.get_widget('colorbutton_linestyle') - button.set_color(color) - - r,g,b = colorConverter.to_rgb(line.get_markerfacecolor()) - color = Gdk.Color(*[int(val*65535) for val in (r,g,b)]) - button = self.wtree.get_widget('colorbutton_markerface') - button.set_color(color) - self._updateson = True - - def on_combobox_linestyle_changed(self, item): - self._update() - - def on_combobox_marker_changed(self, item): - self._update() - - def on_colorbutton_linestyle_color_set(self, button): - self._update() - - def on_colorbutton_markerface_color_set(self, button): - 'called colorbutton marker clicked' - self._update() - - def on_dialog_lineprops_okbutton_clicked(self, button): - self._update() - self.dlg.hide() - - def on_dialog_lineprops_cancelbutton_clicked(self, button): - self.dlg.hide() - - -class Lineprops(ToolBase): - def init_tool(self, **kwargs): - self.dialog = DialogLineprops([]) - - def set_figures(self, *figures): - figure = figures[0] - lines = [] - - for alines in [ax.lines for ax in figure.get_axes()]: - lines.extend(alines) - print (lines) - - class ConfigureSubplotsGTK3(ToolBase): + register = True + def init_tool(self): self.window = Gtk.Window() @@ -1222,9 +815,6 @@ def show(self): class SaveFiguresDialogGTK3(ToolBase): - image = 'saveall' - register = True - def set_figures(self, *figs): ref_figure = figs[0] self.figures = figs @@ -1285,6 +875,638 @@ def _get_filechooser(self): fc.set_current_name(self.current_name) return fc + +class LinesProperties(ToolBase): + text = 'Lines' + tooltip_text = 'Change line properties' + register = True + image = 'line_editor' + + _linestyle = [(k, ' '.join(v.split('_')[2:])) for k, v in lines.Line2D.lineStyles.items() if k.strip()] + _drawstyle = [(k, ' '.join(v.split('_')[2:])) for k, v in lines.Line2D.drawStyles.items()] + _marker = [(k, v) for k, v in markers.MarkerStyle.markers.items() if (k not in (None, '', ' '))] + + _pick_event = None + + def show(self): + self.window.show_all() + self.window.present() + + def init_tool(self, pick=True): + self._line = None + self._pick = pick + + self.window = Gtk.Window(title='Line properties handler') + + try: + self.window.set_icon_from_file(window_icon) + except (SystemExit, KeyboardInterrupt): + # re-raise exit type Exceptions + raise + except: + pass + + self.window.connect('destroy', self.destroy) + + vbox = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL, + column_spacing=5, row_spacing=10, border_width=10) + + self._lines_store = Gtk.ListStore(int, str) + self.line_combo = Gtk.ComboBox.new_with_model(self._lines_store) + renderer_text = Gtk.CellRendererText() + self.line_combo.pack_start(renderer_text, True) + self.line_combo.add_attribute(renderer_text, "text", 1) + self.line_combo.connect("changed", self._on_line_changed) + vbox.attach(self.line_combo, 0, 0, 2, 1) + + vbox.attach_next_to(Gtk.HSeparator(), self.line_combo, Gtk.PositionType.BOTTOM, 2, 1) + + self._visible = Gtk.CheckButton() + self._visible.connect("toggled", self._on_visible_toggled) + + visible = Gtk.Label('Visible ') + vbox.add(visible) + vbox.attach_next_to(self._visible, visible, Gtk.PositionType.RIGHT, 1, 1) + + self.label = Gtk.Entry() + self.label.connect('activate', self._on_label_activate) + + label = Gtk.Label('Label') + vbox.add(label) + vbox.attach_next_to(self.label, label, Gtk.PositionType.RIGHT, 1, 1) + + vbox.attach_next_to(Gtk.HSeparator(), label, Gtk.PositionType.BOTTOM, 2, 1) + vbox.add(Gtk.Label('Line', use_markup=True)) + + style = Gtk.Label('Style') + vbox.add(style) + + drawstyle = Gtk.Label('Draw Style') + vbox.add(drawstyle) + + linewidth = Gtk.Label('Width') + vbox.add(linewidth) + + color = Gtk.Label('Color') + vbox.add(color) + + vbox.attach_next_to(Gtk.HSeparator(), color, Gtk.PositionType.BOTTOM, 2, 1) + vbox.add(Gtk.Label('Marker', use_markup=True)) + + marker = Gtk.Label('Style') + vbox.add(marker) + + markersize = Gtk.Label('Size') + vbox.add(markersize) + + markerfacecolor = Gtk.Label('Face Color') + vbox.add(markerfacecolor) + + markeredgecolor = Gtk.Label('Edge Color') + vbox.add(markeredgecolor) + + for attr, pos in (('linewidth', linewidth), ('markersize', markersize)): + button = Gtk.SpinButton(numeric=True, digits=1) + adjustment = Gtk.Adjustment(0, 0, 100, 0.1, 10, 0) + button.set_adjustment(adjustment) + button.connect('value-changed', self._on_size_changed, attr) + vbox.attach_next_to(button, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, attr, button) + + for attr, pos in (('color', color), + ('markerfacecolor', markerfacecolor), + ('markeredgecolor', markeredgecolor)): + button = Gtk.ColorButton() + button.connect('color-set', self._on_color_set, attr) + vbox.attach_next_to(button, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, attr, button) + + for attr, pos in (('linestyle', style), + ('marker', marker), + ('drawstyle', drawstyle)): + store = Gtk.ListStore(int, str) + for i, v in enumerate(getattr(self, '_' + attr)): + store.append([i, v[1]]) + combo = Gtk.ComboBox.new_with_model(store) + renderer_text = Gtk.CellRendererText() + combo.pack_start(renderer_text, True) + combo.add_attribute(renderer_text, "text", 1) + combo.connect("changed", self._on_combo_changed, attr) + vbox.attach_next_to(combo, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, attr + '_combo', combo) + + self.window.add(vbox) + self.window.show_all() + + def _on_combo_changed(self, combo, attr): + if not self._line: + return + + tree_iter = combo.get_active_iter() + if tree_iter is None: + return + store = combo.get_model() + id_ = store[tree_iter][0] + getattr(self._line, 'set_' + attr)(getattr(self, '_' + attr)[id_][0]) + self._redraw() + + def _on_size_changed(self, button, attr): + if not self._line: + return + + getattr(self._line, 'set_' + attr)(getattr(self, attr).get_value()) + self._redraw() + + def _on_color_set(self, button, attr): + if not self._line: + return + + color = button.get_color() + r, g, b = [val / 65535. for val in (color.red, color.green, color.blue)] + getattr(self._line, 'set_' + attr)((r, g, b)) + self._redraw() + + def _on_label_activate(self, *args): + if not self._line: + return + self._line.set_label(self.label.get_text()) + self._redraw() + + def _on_line_changed(self, combo): + tree_iter = combo.get_active_iter() + if tree_iter is None: + self.line = None + return + + id_ = self._lines_store[tree_iter][0] + line = self.lines[id_] + self._fill(line) + + def _on_visible_toggled(self, *args): + if self._line: + self._line.set_visible(self._visible.get_active()) + self._redraw() + + def set_figures(self, *figures): + self._line = None + self.figure = figures[0] + self.lines = self._get_lines() + + self._lines_store.clear() + + for i, l in enumerate(self.lines): + self._lines_store.append([i, l.get_label()]) + + if self._pick: + if self._pick_event: + self.figure.canvas.mpl_disconnect(self._pick_event) + self._pick_event = self.figure.canvas.mpl_connect('pick_event', self._on_pick) + + def _on_pick(self, event): + artist = event.artist + if not isinstance(artist, matplotlib.lines.Line2D): + return + + try: + i = self.lines.index(artist) + except ValueError: + return + self.line_combo.set_active(i) + + def _get_lines(self): + lines = set() + for ax in self.figure.get_axes(): + for line in ax.get_lines(): + lines.add(line) + + #It is easier to find the lines if they are ordered by label + lines = list(lines) + labels = [line.get_label() for line in lines] + a = [line for (_label, line) in sorted(zip(labels, lines))] + return a + + def _fill(self, line=None): + self._line = line + if line is None: + return + + self._visible.set_active(line.get_visible()) + self.label.set_text(line.get_label()) + + for attr in ('linewidth', 'markersize'): + getattr(self, attr).set_value(getattr(line, 'get_' + attr)()) + + for attr in ('linestyle', 'marker', 'drawstyle'): + v = getattr(line, 'get_' + attr)() + for i, val in enumerate(getattr(self, '_' + attr)): + if val[0] == v: + getattr(self, attr + '_combo').set_active(i) + break + + for attr in ('color', 'markerfacecolor', 'markeredgecolor'): + r, g, b = colorConverter.to_rgb(getattr(line, 'get_' + attr)()) + color = Gdk.Color(*[int(val * 65535) for val in (r, g, b)]) + getattr(self, attr).set_color(color) + + def _redraw(self): + if self._line: + self._line.figure.canvas.draw() + + def destroy(self, *args): + if self._pick_event: + self.figure.canvas.mpl_disconnect(self._pick_event) + + self.unregister() + + +class AxesProperties(ToolBase): + """Manage the axes properties + + Subclass of `ToolBase` for axes management + """ + + + text = 'Axes' + tooltip_text = 'Change axes properties' + register = True + image = 'axes_editor' + + _release_event = None + + def show(self): + self.window.show_all() + self.window.present() + + def init_tool(self, release=True): + self._line = None + self._release = release + + self.window = Gtk.Window(title='Line properties handler') + + try: + self.window.set_icon_from_file(window_icon) + except (SystemExit, KeyboardInterrupt): + # re-raise exit type Exceptions + raise + except: + pass + + self.window.connect('destroy', self.destroy) + + vbox = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL, + column_spacing=5, row_spacing=10, border_width=10) + + l = Gtk.Label('Subplots', use_markup=True) + vbox.add(l) + + self._subplot_store = Gtk.ListStore(int, str) + self._subplot_combo = Gtk.ComboBox.new_with_model(self._subplot_store) + renderer_text = Gtk.CellRendererText() + self._subplot_combo.pack_start(renderer_text, True) + self._subplot_combo.add_attribute(renderer_text, "text", 1) + self._subplot_combo.connect("changed", self._on_subplot_changed) + vbox.attach_next_to(self._subplot_combo, l, Gtk.PositionType.BOTTOM, 2, 1) + + vbox.attach_next_to(Gtk.HSeparator(), self._subplot_combo, Gtk.PositionType.BOTTOM, 2, 1) + l = Gtk.Label('Axes', use_markup=True) + vbox.add(l) +# vbox.attach_next_to(Gtk.HSeparator(), l, Gtk.PositionType.TOP, 2, 1) + + self._axes_store = Gtk.ListStore(int, str) + self._axes_combo = Gtk.ComboBox.new_with_model(self._axes_store) + renderer_text = Gtk.CellRendererText() + self._axes_combo.pack_start(renderer_text, True) + self._axes_combo.add_attribute(renderer_text, "text", 1) + self._axes_combo.connect("changed", self._on_axes_changed) + vbox.attach_next_to(self._axes_combo, l, Gtk.PositionType.BOTTOM, 2, 1) + + self._title = Gtk.Entry() + self._title.connect('activate', self._on_title_activate) + title = Gtk.Label('Title') + vbox.add(title) + vbox.attach_next_to(self._title, title, Gtk.PositionType.RIGHT, 1, 1) + + self._legend = Gtk.CheckButton() + self._legend.connect("toggled", self._on_legend_toggled) + + legend = Gtk.Label('Legend') + vbox.add(legend) + vbox.attach_next_to(self._legend, legend, Gtk.PositionType.RIGHT, 1, 1) + + vbox.attach_next_to(Gtk.HSeparator(), legend, Gtk.PositionType.BOTTOM, 2, 1) + l = Gtk.Label('X', use_markup=True) + vbox.add(l) + + xaxis = Gtk.Label('Visible') + vbox.add(xaxis) + + xlabel = Gtk.Label('Label') + vbox.add(xlabel) + + xmin = Gtk.Label('Min') + vbox.add(xmin) + + xmax = Gtk.Label('Max') + vbox.add(xmax) + + xscale = Gtk.Label('Scale') + vbox.add(xscale) + + xgrid = Gtk.Label('Grid') + vbox.add(xgrid) + + vbox.attach_next_to(Gtk.HSeparator(), xgrid, Gtk.PositionType.BOTTOM, 2, 1) + l = Gtk.Label('Y', use_markup=True) + vbox.add(l) + + yaxis = Gtk.Label('Visible') + vbox.add(yaxis) + + ylabel = Gtk.Label('Label') + vbox.add(ylabel) + + ymin = Gtk.Label('Min') + vbox.add(ymin) + + ymax = Gtk.Label('Max') + vbox.add(ymax) + + yscale = Gtk.Label('Scale') + vbox.add(yscale) + + ygrid = Gtk.Label('Grid') + vbox.add(ygrid) + + for attr, pos in (('xaxis', xaxis), ('yaxis', yaxis)): + checkbox = Gtk.CheckButton() + checkbox.connect("toggled", self._on_axis_visible, attr) + vbox.attach_next_to(checkbox, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, '_' + attr, checkbox) + + for attr, pos in (('xlabel', xlabel), ('ylabel', ylabel)): + entry = Gtk.Entry() + entry.connect('activate', self._on_label_activate, attr) + vbox.attach_next_to(entry, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, '_' + attr, entry) + + for attr, pos in (('x_min', xmin,), ('x_max', xmax), ('y_min', ymin), ('y_max', ymax)): + entry = Gtk.Entry() + entry.connect('activate', self._on_limit_activate, attr) + vbox.attach_next_to(entry, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, '_' + attr, entry) + + for attr, pos in (('xscale', xscale), ('yscale', yscale)): + hbox = Gtk.Box(spacing=6) + log_ = Gtk.RadioButton.new_with_label_from_widget(None, "Log") + lin_ = Gtk.RadioButton.new_with_label_from_widget(log_, "Linear") + log_.connect("toggled", self._on_scale_toggled, attr, "log") + lin_.connect("toggled", self._on_scale_toggled, attr, "linear") + + hbox.pack_start(log_, False, False, 0) + hbox.pack_start(lin_, False, False, 0) + vbox.attach_next_to(hbox, pos, Gtk.PositionType.RIGHT, 1, 1) + setattr(self, '_' + attr, {'log': log_, 'linear': lin_}) + + for attr, pos in (('x', xgrid), ('y', ygrid)): + combo = Gtk.ComboBoxText() + for k in ('None', 'Major', 'Minor', 'Both'): + combo.append_text(k) + vbox.attach_next_to(combo, pos, Gtk.PositionType.RIGHT, 1, 1) + combo.connect("changed", self._on_grid_changed, attr) + setattr(self, '_' + attr + 'grid', combo) + + self.window.add(vbox) + self.window.show_all() + + def _on_grid_changed(self, combo, attr): + if self._ax is None: + return + + marker = combo.get_active_text() + self._ax.grid(False, axis=attr, which='both') + + if marker != 'None': + self._ax.grid(False, axis=attr, which='both') + self._ax.grid(True, axis=attr, which=marker) + + self._redraw() + + def _on_scale_toggled(self, button, attr, scale): + if self._ax is None: + return + + getattr(self._ax, 'set_' + attr)(scale) + self._redraw() + + def _on_limit_activate(self, entry, attr): + if self._ax is None: + return + + direction = attr.split('_')[0] + min_ = getattr(self, '_' + direction + '_min').get_text() + max_ = getattr(self, '_' + direction + '_max').get_text() + + try: + min_ = float(min_) + max_ = float(max_) + except: + min_, max_ = getattr(self._ax, 'get_' + direction + 'lim')() + getattr(self, '_' + direction + '_min').set_text(str(min_)) + getattr(self, '_' + direction + '_max').set_text(str(max_)) + return + + getattr(self._ax, 'set_' + direction + 'lim')(min_, max_) + self._redraw() + + def _on_axis_visible(self, button, attr): + if self._ax is None: + return + + axis = getattr(self._ax, 'get_' + attr)() + axis.set_visible(getattr(self, '_' + attr).get_active()) + self._redraw() + + def _on_label_activate(self, entry, attr): + if self._ax is None: + return + + getattr(self._ax, 'set_' + attr)(getattr(self, '_' + attr).get_text()) + self._redraw() + + def _on_legend_toggled(self, *args): + if self._ax is None: + return + + legend = self._ax.get_legend() + if not legend: + legend = self._ax.legend(loc='best', shadow=True) + + if legend: + legend.set_visible(self._legend.get_active()) + #Put the legend always draggable, + #Maybe a bad idea, but fix the problem of possition + try: + legend.draggable(True) + except: + pass + + self._redraw() + + def _on_title_activate(self, *args): + if self._ax is None: + return + self._ax.set_title(self._title.get_text()) + self._redraw() + + def _on_axes_changed(self, combo): + self._ax = None + if self._axes is None: + return + + tree_iter = combo.get_active_iter() + if tree_iter is None: + return + + id_ = self._axes_store[tree_iter][0] + ax = self._axes[id_] + + self._fill(ax) + + def _fill(self, ax=None): + if ax is None: + self._ax = None + return + + self._title.set_text(ax.get_title()) + + self._legend.set_active(bool(ax.get_legend()) and ax.get_legend().get_visible()) + + for attr in ('xlabel', 'ylabel'): + t = getattr(ax, 'get_' + attr)() + getattr(self, '_' + attr).set_text(t) + + for attr in ('xaxis', 'yaxis'): + axis = getattr(ax, 'get_' + attr)() + getattr(self, '_' + attr).set_active(axis.get_visible()) + + for attr in ('x', 'y'): + min_, max_ = getattr(ax, 'get_' + attr + 'lim')() + getattr(self, '_' + attr + '_min').set_text(str(min_)) + getattr(self, '_' + attr + '_max').set_text(str(max_)) + + for attr in ('xscale', 'yscale'): + scale = getattr(ax, 'get_' + attr)() + getattr(self, '_' + attr)[scale].set_active(True) + + for attr in ('x', 'y'): + axis = getattr(ax, 'get_' + attr + 'axis')() + if axis._gridOnMajor and not axis._gridOnMinor: + gridon = 'Major' + elif not axis._gridOnMajor and axis._gridOnMinor: + gridon = 'Minor' + elif axis._gridOnMajor and axis._gridOnMinor: + gridon = 'Both' + else: + gridon = 'None' + + combo = getattr(self, '_' + attr + 'grid') + model = combo.get_model() + for i in range(len(model)): + if model[i][0] == gridon: + combo.set_active(i) + break + self._ax = ax + + def _on_subplot_changed(self, combo): + self._axes = None + self._ax = None + self._axes_store.clear() + + tree_iter = combo.get_active_iter() + if tree_iter is None: + return + + id_ = self._subplot_store[tree_iter][0] + self._axes = self._subplots[id_][1] + + for i in range(len(self._axes)): + self._axes_store.append([i, 'Axes %d' % i]) + + self._axes_combo.set_active(0) + + def set_figures(self, *figures): + self._ax = None + self.figure = figures[0] + self._subplots = self._get_subplots() + + self._subplot_store.clear() + + for i, l in enumerate(self._subplots): + self._subplot_store.append([i, str(l[0])]) + + self._subplot_combo.set_active(0) + + if self._release: + if self._release_event: + self.figure.canvas.mpl_disconnect(self._release_event) + self._release_event = self.figure.canvas.mpl_connect('button_release_event', self._on_release) + + def _on_release(self, event): + try: + ax = event.inaxes.axes + except: + return + + ax_subplot = [subplot[0] for subplot in self._subplots if ax in subplot[1]][0] + current_subplot = [subplot[0] for subplot in self._subplots if self._ax in subplot[1]][0] + if ax_subplot == current_subplot: + return + + for i, subplot in enumerate(self._subplots): + if subplot[0] == ax_subplot: + self._subplot_combo.set_active(i) + break + + def _get_subplots(self): + axes = {} + alone = [] + rem = [] + for ax in self.figure.get_axes(): + try: + axes.setdefault(ax.get_geometry(), []).append(ax) + except AttributeError: + alone.append(ax) + + #try to find if share something with one of the axes with geometry + for ax in alone: + for ax2 in [i for sl in axes.values() for i in sl]: + if ((ax in ax2.get_shared_x_axes().get_siblings(ax2)) or + (ax in ax2.get_shared_y_axes().get_siblings(ax2))): + axes[ax2.get_geometry()].append(ax) + rem.append(ax) + + for ax in rem: + alone.remove(ax) + + for i, ax in enumerate(alone): + axes[i] = [ax, ] + + return [(k, axes[k]) for k in sorted(axes.keys())] +# return axes + + def destroy(self, *args): + if self._release_event: + self.figure.canvas.mpl_disconnect(self._release_event) + + self.unregister() + + def _redraw(self): + if self._ax: + self._ax.figure.canvas.draw() + + + + # Define the file to use as the GTk icon if sys.platform == 'win32': diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 0c10426c14df..575a6bef2419 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, division, print_function, - unicode_literals) + unicode_literals) import six @@ -11,7 +11,7 @@ from . import backend_agg from . import backend_gtk3 from matplotlib.figure import Figure -from matplotlib import transforms +from matplotlib import transforms, rcParams if six.PY3: warnings.warn("The Gtk3Agg backend is not known to work on Python 3.x.") @@ -94,16 +94,17 @@ def new_figure_manager(num, *args, **kwargs): Create a new figure manager instance """ FigureClass = kwargs.pop('FigureClass', Figure) + parent = kwargs.pop('parent', rcParams['backend.single_window']) thisFig = FigureClass(*args, **kwargs) - return new_figure_manager_given_figure(num, thisFig) + return new_figure_manager_given_figure(num, thisFig, parent) -def new_figure_manager_given_figure(num, figure): +def new_figure_manager_given_figure(num, figure, parent): """ Create a new figure manager instance for the given figure. """ canvas = FigureCanvasGTK3Agg(figure) - manager = FigureManagerGTK3Agg(canvas, num) + manager = FigureManagerGTK3Agg(canvas, num, parent) return manager diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 4421cd0e2fd4..3a0ecbf6b9af 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -1,11 +1,13 @@ from __future__ import (absolute_import, division, print_function, - unicode_literals) + unicode_literals) import six from . import backend_gtk3 from . import backend_cairo from matplotlib.figure import Figure +from matplotlib import rcParams + class RendererGTK3Cairo(backend_cairo.RendererCairo): def set_context(self, ctx): @@ -49,16 +51,17 @@ def new_figure_manager(num, *args, **kwargs): Create a new figure manager instance """ FigureClass = kwargs.pop('FigureClass', Figure) + parent = kwargs.pop('parent', rcParams['backend.single_window']) thisFig = FigureClass(*args, **kwargs) - return new_figure_manager_given_figure(num, thisFig) + return new_figure_manager_given_figure(num, thisFig, parent) -def new_figure_manager_given_figure(num, figure): +def new_figure_manager_given_figure(num, figure, parent): """ Create a new figure manager instance for the given figure. """ canvas = FigureCanvasGTK3Cairo(figure) - manager = FigureManagerGTK3Cairo(canvas, num) + manager = FigureManagerGTK3Cairo(canvas, num, parent) return manager diff --git a/lib/matplotlib/mpl-data/images/axes_editor.png b/lib/matplotlib/mpl-data/images/axes_editor.png new file mode 100644 index 0000000000000000000000000000000000000000..c97e1a5936c70129057ebc2586f24790fae7a0a9 GIT binary patch literal 1025 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf2>S zXAoxG@waFJkRe&(8c`CQpH@mmtT}V`<;yxP>~==kz;8| zMt%`PdQwhev0i3bVqUA!8f~DYB1qD?C^fMpGe1uuBr`Xa!N|bSP}jg**T^iy(9+7# z%*x12LFcYIP_Yxly3(TLRECU_k^(Dz{rrN|yyTqBg2bW{kd^u=sU?Y-Ir@gCMg~Tf z`emufCHY0V6}m~4x(KU#Ii7t4n&t_%I=`T@C^J2ygdw0bDJL^o!6iR8F*8pAYJ4)- zWk6%{b93|aiosR@Ri)+?rxxoMfOVvRHRwkk{&$LjfoYeCBtLPL_lOs&ec-F$+SurF&2_?dF*k)qw@#HtQO6Z7 zo(b_TohoTwGZscGEGo8+wKyIVU41mqs-R;2XPJA&7UzG;yR-!GZRy|$;g6jDk#VEk znwq22W(K{o2Ex?B z6?u&Pi1&;=S=kzKE*zGPL0aM)G?xZ73Eh&++@`(s(UhRdJ$WZWy(~8@nZsp%;C}gp zh#Lpim8rAk@;sQcd)*Zi`|^E-i~d-B+Y~+{_|mkZianACBW}qkD@y;EwWZ?(XX%UL zzAct+3{xu4s%^V_CjFngz033DF7LFoJ-gyhc@!XPlyw_~cyv)qv9)r(Edv05{+ z=sewK-K!~P$4>+ubl^$2qTJ6D^OiASsjpem^;-vjaQ9lOvA?= zc3=M5^Uam9H#p|-ZFX+gpZ>nvxT&JnVv)-P78TYHeWDLNc*9<=-o + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Open Clip Art Library + + + + 2011-07-26T19:16:26 + + http://openclipart.org/detail/152029/vector-x-by-gblas.ivan + + + gblas.ivan + + + + + algebra + axis + calculus + clip art + clipart + formula + mathematics + plot + plotting + vector + x + y + + + + + + + + + + + diff --git a/lib/matplotlib/mpl-data/images/axes_editor.xpm b/lib/matplotlib/mpl-data/images/axes_editor.xpm new file mode 100644 index 000000000000..0f4ad1bfb3a0 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/axes_editor.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static char *axes_editor[] = { +/* columns rows colors chars-per-pixel */ +"24 24 9 1 ", +" c black", +". c #7B4D43", +"X c #BE5516", +"o c #9E5632", +"O c #AD5421", +"+ c #AF5829", +"@ c #B95B24", +"# c #0043A5", +"$ c None", +/* pixels */ +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$.$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$O$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$#$$$o$$$$", +"$$$$$$ $$$$+$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$@$$$$X$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$ $$ $$ $$ $$", +" ", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$", +"$$$$$$ $$$$$$$$$$$$$$$$$" +}; diff --git a/lib/matplotlib/mpl-data/images/line_editor.png b/lib/matplotlib/mpl-data/images/line_editor.png new file mode 100644 index 0000000000000000000000000000000000000000..1b4b97144dbb0266575723cf7b6b453442d1cea7 GIT binary patch literal 1586 zcmXYxdpy%?9LIlV#k5#SHpE;ib((3Vu~T!~*j$sXNXpDFewl5ypO%ojhB`@F6CEkM zy0~QM=uSG-k)(1Dx#UEyr9wwKqn_96`+Q%|^L>9m-_Ps$<4N7=;k;Z!PXhpenS141;{=E4iSieK#oWR!Xh?V5uvQ;EmUZrm@9&W zFeKmu_IM%4=R!O@#1-%@Y>K>#m6Y)UtS((GA`)RMChkHyBO-Tm>1>T;MF>PMC@>E> zG3S*kuSB6{9JzQig>!-2d};FIwZ`rxuJrmIrv!bM|%#UXOZuPLItsB_L%UY>=(T?T5EyLqnaY*c>ANR++ ze?B7!G&kt6;XIRF1_HOMdimryvc4NM#qIUAl?2CMsn@>YXOc|IJ(Mx1meQA({IJix zRwxg!b~9hqDP#NI^2hx(cjdMa>!IlOfk)e~!WC=+^G3QgTiXGztIFClUUKJxjDpZg zsY9Ba4y)49xnAX^W3=5`haIC<2N-K!ZV<%QQwp-(Q&y*`w_Rfwo}031E~+5`@gZ+2 zqT2d$ySfto+;G)RXJdXvFsl0v-AoaarE=|?VM@7rO%W=RS2lfoFej7xVOn3?@_3_b zYEs(U_fkiy4pee$^0}wAt-QVIf|JCP{N=l0nplBqC1cXYE=5$vXYG!o(Y>r#QQ_g` z>-QzT?`ymjMBg#KkNn4=_Dh2XkBQ4)C#YQ)_!jHsk)e;_g!hMHnbqOku^}|;i;q)uA4B>m|VnwheLE67@N?())TX5>86uK&xd&B#z4&?i>US#+pzk|Xw`@flC56@?ulIHCj7*DLdGK4W;;G z*smCM9C(GDr0`~EA3YEm9V<6XGAdPVoKgSU+?;R=om*C%2gJNvmQSR|&2$PGro6B;maXg|YW zjeelU-xWPS`q{wc$1rZBc*kVVj?7w#bXCA^WM`Y{f54%6Yf*S@Zu960?HHhOY)X5= zJO~s&!3r@C(OD|w)1MlowRN4d!;f_lj{`p64s`gT^}B+}C-#+o3sbH1~yivyDZ)Zz8eE7us%rvHQD zjL5FH#>3HD*J%0!vAY8MxBKmunHm$^F}k{j)rZIG?L0k4TI_ne4E@V`=B(jfTQ{oy UfUv95MEMN@Ou7fH)FB|@f3G#LB>(^b literal 0 HcmV?d00001 diff --git a/lib/matplotlib/mpl-data/images/line_editor.svg b/lib/matplotlib/mpl-data/images/line_editor.svg new file mode 100644 index 000000000000..a9214997d730 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/line_editor.svg @@ -0,0 +1,1589 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Open Clip Art Library + + + + 2007-02-22T17:59:32 + A pencil icon. + http://openclipart.org/detail/3297/pencil-by-barretr + + + barretr + + + + + clip art + clipart + color + drawing + icon + office + pencil + school + writing + yellow + + + + + + + + + + + diff --git a/lib/matplotlib/mpl-data/images/line_editor.xpm b/lib/matplotlib/mpl-data/images/line_editor.xpm new file mode 100644 index 000000000000..f690468bd832 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/line_editor.xpm @@ -0,0 +1,187 @@ +/* XPM */ +static char *line_editor[] = { +/* columns rows colors chars-per-pixel */ +"24 24 157 2 ", +" c #615C48", +". c #686665", +"X c #6E6C6A", +"o c #6E6C6B", +"O c #787573", +"+ c #787877", +"@ c #860000", +"# c #8C0000", +"$ c #920000", +"% c #B90000", +"& c #BD0E0E", +"* c #A11919", +"= c #922222", +"- c #952828", +"; c #C01B1B", +": c #C11D1D", +"> c red", +", c #C32424", +"< c #C32626", +"1 c #AE4141", +"2 c #A64848", +"3 c #B75959", +"4 c #B65A5A", +"5 c #B75F5F", +"6 c #85787D", +"7 c #B76464", +"8 c #C06060", +"9 c #C86767", +"0 c #C96767", +"q c #D16E6E", +"w c #D26E6E", +"e c #C27777", +"r c #C77E7E", +"t c #C87D7D", +"y c #DA7575", +"u c #DB7575", +"i c #E37C7C", +"p c #00CC00", +"a c #00CD00", +"s c #02CC02", +"d c #0BCB0B", +"f c #0CCB0C", +"g c #FF8B00", +"h c DarkOrange", +"j c #FF8D00", +"k c #FF9905", +"l c #FF9906", +"z c #FF9B07", +"x c #FF9C07", +"c c #FF9C08", +"v c #FF9C0A", +"b c #FF9D0C", +"n c #FFA700", +"m c #FFA60C", +"M c #FFA60D", +"N c #FFA60E", +"B c #FFA80F", +"V c #FFA810", +"C c #FFA811", +"Z c #FFA812", +"A c #FFAC1D", +"S c #F9B43C", +"D c #98FF00", +"F c #99FF00", +"G c #9AFF00", +"H c #FFDA26", +"J c #FFDA28", +"K c #FFDA2A", +"L c #FFDB2B", +"P c #FFDB2D", +"I c #FCD92E", +"U c #FFDB2F", +"Y c #F9C146", +"T c #FFCB40", +"R c #FFCB41", +"E c #FFCB42", +"W c #FFCC42", +"Q c #FFCC43", +"! c #FFCC44", +"~ c #FFCD44", +"^ c #FFCD45", +"/ c #FFCF4F", +"( c #FFDD40", +") c #FFE54E", +"_ c #FFE75A", +"` c #FFE85E", +"' c #FFE860", +"] c #FFE863", +"[ c #FFE967", +"{ c #FFE969", +"} c #FFEA6D", +"| c #FFE071", +" . c #FFE072", +".. c #FFE073", +"X. c #FFE074", +"o. c #FFE175", +"O. c #FFE179", +"+. c #330098", +"@. c #340099", +"#. c #360499", +"$. c #3D0E9A", +"%. c #0033CC", +"&. c #1341C9", +"*. c #1341CA", +"=. c #1541CA", +"-. c #1B47C8", +";. c #D1138E", +":. c #CD0A97", +">. c #CC0098", +",. c #CD0099", +"<. c #009898", +"1. c #009999", +"2. c #0B9A9A", +"3. c #0A9B9B", +"4. c #0F9C9C", +"5. c #119D9D", +"6. c #878483", +"7. c #918180", +"8. c #939090", +"9. c #979694", +"0. c #A38A89", +"q. c #B7A980", +"w. c #AAA9A8", +"e. c #ADABAA", +"r. c #CF8A8A", +"t. c #CC908F", +"y. c #D19191", +"u. c #D59797", +"i. c #D79B9A", +"p. c #EC8383", +"a. c #F58A8A", +"s. c #F58B8B", +"d. c #FD9191", +"f. c #C7BDA6", +"g. c #EBAFAD", +"h. c #E6B0B0", +"j. c #FFE391", +"k. c #FCEA91", +"l. c #FFE69F", +"z. c #FFE7A2", +"x. c #FFEEA1", +"c. c #FFEFA2", +"v. c #FFEFA3", +"b. c #FFEFA4", +"n. c #FFEFA5", +"m. c #F9E9B0", +"M. c #FFEBB1", +"N. c #FFEBB2", +"B. c #C9C9C8", +"V. c #CBC9C9", +"C. c #FFEFC1", +"Z. c #FFEFC2", +"A. c #FFF3D1", +"S. c #FFF7E1", +"D. c #FFF7E2", +"F. c #FFFBF1", +"G. c None", +/* pixels */ +"G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.d.a.G.G.G.", +"G.G.G.G.G.G.G.G.%.%.G.G.G.@.+.G.G.G.d.a.p.i G.G.", +"G.G.G.G.G.G.G.G.%.%.=.G.G.$.#.+.G.0.g.p.i u w G.", +"G.G.G.G.G.G.G.G.*.-.*.G.G.+.+.G.6.e.V.h.u w 9 5 ", +"G.G.G.G.1.<.G.G.G.G.G.G.G.G.G.I k.B.w.9.i.0 8 4 ", +"G.G.G.1.<.2.5.G.G.G.G.G.G.G.H } c.m.8.X O t.4 G.", +"G.G.G.G.2.4.G.G.G.G.G.G.G.J { c. .T Y X . 7.G.G.", +"G.G.G.G.G.G.G.G.G.G.G.G.J [ c. .T M n S 6 G.G.G.", +"G.p p f G.G.G.G.G.G.G.P ] n. .W M n g v :.,.G.G.", +"G.p p f G.G.G.G.G.G.P ` v. .W M n g v ;.>.,.G.G.", +"G.p p p G.G.G.G.G.I ` n.o.W M n g v G.G.>.,.G.G.", +"G.G.G.G.G.G.G.G.U _ n.o.! V n j c G.G.G.G.G.G.G.", +"G.G.G.G.G.G.G.( ) n.o.^ V n j l G.G.G.G.G.G.G.G.", +"G.G D G G.G.G.F.S.O.^ V n j l G.G.G.G.G.> > G.G.", +"G.D D D G.G.G.S.A./ V n j l G.G.G.G.G.> > > G.G.", +"G.G D G G.G.S.A.C.M.l.v l G.G.G.G.G.G.> > > G.G.", +"G.G.G.G.G.G.f.C.M.z.j.A G.G.G.G.G.G.G.G.G.G.G.G.", +"G.G.G.G.G.G.+ q.z.j.G.G.G.G.G.G.G.G.G.G.G.G.G.G.", +"G.G.G.G.G.G. G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.", +"G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.", +"G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.", +"G.G.e r r.u.y.r = G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.", +"# % & : < , ; & % @ G.G.G.G.G.G.G.G.G.G.G.G.G.G.", +"G.$ * 1 5 7 2 - G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G." +}; diff --git a/lib/matplotlib/mpl-data/images/saveall.ppm b/lib/matplotlib/mpl-data/images/saveall.ppm new file mode 100644 index 0000000000000000000000000000000000000000..7ef7ced9c9cd70d68745362707564e35d4bbb23e GIT binary patch literal 1741 zcma)+`%hD67{^`q!zQ}@W~M{bC7V&iiRB_F6uG!TXp6mA+ENfX97gGt-Z?F{qkyG7 zz27)(547|`DV19BHk3IxHrz%un&_rexBa**`x~}bE?wEOxRcKhZ}L9*LKw)Al+JT95nM&@zIy=$;Zy&Tdf z98wdbb?X_`R;D_Sg=95A@)lcD)UMvqsRIelZI_Pp6bs=~cp!xNaIZVya*o?=UaQq*Haks5 zTV0QaCO4%QmfoN%ay{Fa>e@a-0Y}ZY#uio{U0r@KcW1>gb?N-%oHmd|uo<;)TIr1| z+)`O3oFrEAI5d_0j3 zM`!U+0t-gHSlHzYIo!C_6+rEnV#vCmqx$XlKL}@$HZ;8D)Owz3jhChOF?Alw=;Rj< zp4?n{bZz;8EqOy1OlkZ{)#@~)jw!J{B(zM(9N~fS>D_I}L6&@1tCEX`Upuv)rwZ}3 z4C76Dj5LDZy7&0{w+}Bb-6Kmz%4j@5DV0}xy0>HlB7`8mcz(NfCsVnjRZSiVW<8Yy z?nWKfp!GHzf;YZ- zb3UeW)@wXOm9uHce`WdprLR^}pWh`*MvpNBbhswHg^}cdF zC30JNzokTEDwd%2T2C$FBC3Jec3f@+RLU$BQnW&9J|;1h1H@wlp)sGUS({JYz(yX1 zk|47l;TsS0Mhklk`@8h}xY~DFYEbq{z?^eE#r>9|p5X(~(EFXb|1Vj~Q=60LLoxb6 zr|!Lu!98urE~e_0F|q|9Pp+p(Xx_`!=CcRZ3#gJ^B>sP~lPcNdQM=aPouND=FlDd6 z;V8Awj3)(ymM-aVA7UD`1_n&yiVJS3!7kFF2O7Jw#V_;ZcnbMr*(-1$&;;P=Yba1b zVSQ~&_xS8p_jJZKl``SeqvKNtnl^b(u6kZ?7J+^EZ%^Xp*FvyV2=YXe%+~tT|L?;s${1Jn9^1Q=;aC06J zp2@DkLcTG#FW?EyFIF{o0uPaN&Js!*12Ka)q;mui)Vs;UQs;V_NAP^k%l~r?;K9?M zQ9Gq4nmXu^d@L9n_JwuMAY$<;jn1s+omN$e%&woimCd8SeY-)Af$k0o044%kT6b1a q+b$z=`>Q#UYPPtFB|Od)RG#5i(0NCglB4}7-~{3xzPOl`fWu$O5&J^` literal 0 HcmV?d00001 From cf80c97ffa2b10037c0505e436748a815ce96c72 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 23 Oct 2013 15:56:17 -0400 Subject: [PATCH 18/20] pep8 correction --- .../user_interfaces/multifigure_backend_gtk3.py | 14 ++++++++------ lib/matplotlib/backends/backend_gtk3.py | 2 +- lib/matplotlib/backends/backend_gtk3agg.py | 2 +- lib/matplotlib/backends/backend_gtk3cairo.py | 9 +++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/user_interfaces/multifigure_backend_gtk3.py b/examples/user_interfaces/multifigure_backend_gtk3.py index d7d1631245e0..b1af63febbf7 100644 --- a/examples/user_interfaces/multifigure_backend_gtk3.py +++ b/examples/user_interfaces/multifigure_backend_gtk3.py @@ -11,19 +11,20 @@ fig1 = plt.figure() ax1 = fig1.add_subplot(111) ax1.plot(x, x ** 2, marker='o', label='hey', picker=5) -ax1.legend(loc = 'lower left') +ax1.legend(loc='lower left') fig2 = plt.figure() ax2 = fig2.add_subplot(111) -ax2.plot(x , np.sqrt(x)) +ax2.plot(x, np.sqrt(x)) ax2.set_xlabel('x') ax2.set_ylabel('y') -#In the axes control tool, there is a second axes for this subplot, check it out :) +#In the axes control tool, +#there is a second axes for this subplot, check it out :) ax22 = ax2.twinx() ax22.plot(x, -np.sqrt(x), picker=5, marker='x', label='in second axis') ax22.set_ylabel('Minus x') -d=5 +d = 5 fig3 = plt.figure() ax3 = fig3.add_subplot(111) ax3.plot(x[::d], (x ** 3)[::d], 'ro-', label='Line label') @@ -56,7 +57,7 @@ #parent=fig2.canvas.manager.parent fig5 = plt.figure(parent=fig1) ax5 = fig5.add_subplot(111) -ax5.plot(x , x**4) +ax5.plot(x, x**4) #if we want it in a separate window #parent=False @@ -65,6 +66,7 @@ #Toolbar management class SampleNonGuiTool(ToolBase): text = 'Stats' + def set_figures(self, *figures): #stupid routine that says how many axes are in each figure for figure in figures: @@ -81,4 +83,4 @@ def set_figures(self, *figures): #Move home somewhere nicer, I always wanted to live close to a rainbow fig3.canvas.manager.toolbar.move_tool(0, 8) -plt.show() \ No newline at end of file +plt.show() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 586c24dd885c..cdd3d51c6eb6 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, division, print_function, - unicode_literals) + unicode_literals) import six diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 575a6bef2419..a0dfebbfaa85 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, division, print_function, - unicode_literals) + unicode_literals) import six diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 3a0ecbf6b9af..64e8f5174a2d 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, division, print_function, - unicode_literals) + unicode_literals) import six @@ -24,8 +24,8 @@ def _renderer_init(self): self._renderer = RendererGTK3Cairo(self.figure.dpi) def _render_figure(self, width, height): - self._renderer.set_width_height (width, height) - self.figure.draw (self._renderer) + self._renderer.set_width_height(width, height) + self.figure.draw(self._renderer) def on_draw_event(self, widget, ctx): """ GtkDrawable draw event, like expose_event in GTK 2.X @@ -35,7 +35,8 @@ def on_draw_event(self, widget, ctx): #if self._need_redraw: self._renderer.set_context(ctx) allocation = self.get_allocation() - x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height + x, y = allocation.x, allocation.y + w, h = allocation.width, allocation.height self._render_figure(w, h) #self._need_redraw = False From 7033c46a6a70f2cd81b265562b0d5c4a9268ab0b Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 23 Oct 2013 19:42:45 -0400 Subject: [PATCH 19/20] removing backedn_gtk3cairo.py from test_coding_standards --- lib/matplotlib/tests/test_coding_standards.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/tests/test_coding_standards.py b/lib/matplotlib/tests/test_coding_standards.py index 58f95c758eac..4edb3974467a 100644 --- a/lib/matplotlib/tests/test_coding_standards.py +++ b/lib/matplotlib/tests/test_coding_standards.py @@ -106,7 +106,6 @@ '*/matplotlib/backends/backend_gdk.py', '*/matplotlib/backends/backend_gtk.py', '*/matplotlib/backends/backend_gtk3.py', - '*/matplotlib/backends/backend_gtk3cairo.py', '*/matplotlib/backends/backend_gtkagg.py', '*/matplotlib/backends/backend_gtkcairo.py', '*/matplotlib/backends/backend_macosx.py', From 84a19112b4a7cffcf55263f849b63f6220815927 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Mon, 28 Oct 2013 14:10:32 -0400 Subject: [PATCH 20/20] splitting toolbar and figuremanager examples --- .../multifigure_backend_gtk3.py | 48 ++++--------------- .../reconfigurable_toolbar_gtk3.py | 38 +++++++++++++++ 2 files changed, 47 insertions(+), 39 deletions(-) create mode 100644 examples/user_interfaces/reconfigurable_toolbar_gtk3.py diff --git a/examples/user_interfaces/multifigure_backend_gtk3.py b/examples/user_interfaces/multifigure_backend_gtk3.py index b1af63febbf7..7bc59fada293 100644 --- a/examples/user_interfaces/multifigure_backend_gtk3.py +++ b/examples/user_interfaces/multifigure_backend_gtk3.py @@ -2,7 +2,8 @@ matplotlib.use('GTK3Agg') matplotlib.rcParams['backend.single_window'] = True import matplotlib.pyplot as plt -from matplotlib.backend_bases import ToolBase + + import numpy as np x = np.arange(100) @@ -10,36 +11,26 @@ #Create 4 figures fig1 = plt.figure() ax1 = fig1.add_subplot(111) -ax1.plot(x, x ** 2, marker='o', label='hey', picker=5) -ax1.legend(loc='lower left') +ax1.plot(x, x) fig2 = plt.figure() ax2 = fig2.add_subplot(111) ax2.plot(x, np.sqrt(x)) -ax2.set_xlabel('x') -ax2.set_ylabel('y') -#In the axes control tool, -#there is a second axes for this subplot, check it out :) -ax22 = ax2.twinx() -ax22.plot(x, -np.sqrt(x), picker=5, marker='x', label='in second axis') -ax22.set_ylabel('Minus x') - -d = 5 + + fig3 = plt.figure() ax3 = fig3.add_subplot(111) -ax3.plot(x[::d], (x ** 3)[::d], 'ro-', label='Line label') +ax3.plot(x, x ** 2) fig4 = plt.figure() -ax41 = fig4.add_subplot(211) -ax41.plot(x, x + 5, label='add 5') +ax4 = fig4.add_subplot(111) +ax4.plot(x, x ** 3) -ax42 = fig4.add_subplot(212) -ax42.plot(x, np.log(x + 15), label='add 15') ################### #Figure management #Change the figure1 tab label -fig1.canvas.manager.set_window_title('My first Figure') +fig1.canvas.manager.set_window_title('Just a line') #Change the figure manager window title fig1.canvas.manager.set_mainwindow_title('The powerful window manager') @@ -62,25 +53,4 @@ #parent=False -################### -#Toolbar management -class SampleNonGuiTool(ToolBase): - text = 'Stats' - - def set_figures(self, *figures): - #stupid routine that says how many axes are in each figure - for figure in figures: - title = figure.canvas.get_window_title() - print('Figure "%s": Has %d axes' % (title, len(figure.axes))) - -#Add simple SampleNonGuiTool to the toolbar of fig1-fig2 -fig1.canvas.manager.toolbar.add_tool(SampleNonGuiTool) - -#Lets reorder the buttons in the fig3-fig4 toolbar -#Back? who needs back? my mom always told me, don't look back, -fig3.canvas.manager.toolbar.remove_tool(1) - -#Move home somewhere nicer, I always wanted to live close to a rainbow -fig3.canvas.manager.toolbar.move_tool(0, 8) - plt.show() diff --git a/examples/user_interfaces/reconfigurable_toolbar_gtk3.py b/examples/user_interfaces/reconfigurable_toolbar_gtk3.py new file mode 100644 index 000000000000..266b2665361c --- /dev/null +++ b/examples/user_interfaces/reconfigurable_toolbar_gtk3.py @@ -0,0 +1,38 @@ +import matplotlib +matplotlib.use('GTK3Agg') +matplotlib.rcParams['backend.single_window'] = True +import matplotlib.pyplot as plt +from matplotlib.backend_bases import ToolBase +import numpy as np + +x = np.arange(100) +#Create 4 figures +fig1 = plt.figure() +ax1 = fig1.add_subplot(111) +ax1.plot(x, x) + + +################### +#Toolbar management + +#Lets reorder the buttons in the fig3-fig4 toolbar +#Back? who needs back? my mom always told me, don't look back, +fig1.canvas.manager.toolbar.remove_tool(1) + +#Move home somewhere nicer +fig1.canvas.manager.toolbar.move_tool(0, 8) + + +class SampleNonGuiTool(ToolBase): + text = 'Stats' + + def set_figures(self, *figures): + #stupid routine that says how many axes are in each figure + for figure in figures: + title = figure.canvas.get_window_title() + print('Figure "%s": Has %d axes' % (title, len(figure.axes))) + +#Add simple SampleNonGuiTool to the toolbar of fig1-fig2 +fig1.canvas.manager.toolbar.add_tool(SampleNonGuiTool) + +plt.show()