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() pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy