From 1f6d86ff821aa3eb2c09c140bbdeb4c4f483ed6b Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 27 Nov 2013 14:45:02 -0500 Subject: [PATCH 1/3] removing pyplot pylab reference in _gui backend --- lib/matplotlib/backend_bases.py | 5 +- lib/matplotlib/backends/backend_gtk.py | 4 +- lib/matplotlib/backends/backend_gtk3.py | 812 +---------------- lib/matplotlib/backends/backend_gtk3_gui.py | 849 ++++++++++++++++++ lib/matplotlib/backends/backend_gtk3agg.py | 79 +- .../backends/backend_gtk3agg_gui.py | 91 ++ lib/matplotlib/backends/backend_gtk3cairo.py | 38 +- .../backends/backend_gtk3cairo_gui.py | 45 + lib/matplotlib/backends/backend_macosx.py | 3 +- lib/matplotlib/backends/backend_qt4.py | 3 +- lib/matplotlib/backends/backend_tkagg.py | 3 +- .../backends/backend_webagg_core.py | 3 +- lib/matplotlib/backends/backend_wx.py | 3 +- 13 files changed, 1015 insertions(+), 923 deletions(-) create mode 100644 lib/matplotlib/backends/backend_gtk3_gui.py create mode 100644 lib/matplotlib/backends/backend_gtk3agg_gui.py create mode 100644 lib/matplotlib/backends/backend_gtk3cairo_gui.py diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index c9212939b1a7..ad6c21df7a0b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2439,7 +2439,7 @@ def key_press_handler(event, canvas, toolbar=None): # quit the figure (defaut key 'ctrl+w') if event.key in quit_keys: - Gcf.destroy_fig(canvas.figure) + canvas.manager.destroy() if toolbar is not None: # home or reset mnemonic (default key 'h', 'home' and 'r') @@ -2525,10 +2525,9 @@ class FigureManagerBase: *num* The figure number """ - def __init__(self, canvas, num): + def __init__(self, canvas): self.canvas = canvas canvas.manager = self # store a pointer to parent - self.num = num self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', self.key_press) diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index c7c805972764..9361aaeafcd5 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -542,8 +542,8 @@ class FigureManagerGTK(FigureManagerBase): """ def __init__(self, canvas, num): if _debug: print('FigureManagerGTK.%s' % fn_name()) - FigureManagerBase.__init__(self, canvas, num) - + FigureManagerBase.__init__(self, canvas) + self.num = num self.window = gtk.Window() self.set_window_title("Figure %d" % num) if (window_icon): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index e915c29111e6..0aa09de8455d 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -3,9 +3,6 @@ import six -import os, sys -def fn_name(): return sys._getframe(1).f_code.co_name - try: import gi except ImportError: @@ -22,51 +19,27 @@ def fn_name(): return sys._getframe(1).f_code.co_name "to be installed.") try: - from gi.repository import Gtk, Gdk, GObject + from gi.repository import Gtk except ImportError: raise ImportError("Gtk3 backend requires pygobject to be installed.") import matplotlib from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase from matplotlib.backend_bases import ShowBase -from matplotlib.cbook import is_string_like, is_writable_file_like -from matplotlib.colors import colorConverter -from matplotlib.figure import Figure -from matplotlib.widgets import SubplotTool - -from matplotlib import lines -from matplotlib import cbook -from matplotlib import verbose -from matplotlib import rcParams - -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 -PIXELS_PER_INCH = 96 +from . backend_gtk3_gui import FigureManagerGTK3Gui, FigureCanvasGTK3Gui -cursord = { - cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR), - cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2), - cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR), - cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS), - } 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() + class Show(ShowBase): def mainloop(self): if Gtk.main_level() == 0: @@ -75,292 +48,7 @@ def mainloop(self): show = Show() -class TimerGTK3(TimerBase): - ''' - Subclass of :class:`backend_bases.TimerBase` that uses GTK3 for timer events. - - Attributes: - * interval: The time between timer events in milliseconds. Default - is 1000 ms. - * single_shot: Boolean flag indicating whether this timer should - operate as single shot (run once and then stop). Defaults to False. - * callbacks: Stores list of (func, args) tuples that will be called - upon timer events. This list can be manipulated directly, or the - functions add_callback and remove_callback can be used. - ''' - def _timer_start(self): - # Need to stop it, otherwise we potentially leak a timer id that will - # never be stopped. - self._timer_stop() - self._timer = GObject.timeout_add(self._interval, self._on_timer) - - def _timer_stop(self): - if self._timer is not None: - GObject.source_remove(self._timer) - self._timer = None - - def _timer_set_interval(self): - # Only stop and restart it if the timer has already been started - if self._timer is not None: - self._timer_stop() - self._timer_start() - - def _on_timer(self): - TimerBase._on_timer(self) - - # Gtk timeout_add() requires that the callback returns True if it - # is to be called again. - if len(self.callbacks) > 0 and not self._single: - return True - else: - self._timer = None - return False - -class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): - keyvald = {65507 : 'control', - 65505 : 'shift', - 65513 : 'alt', - 65508 : 'control', - 65506 : 'shift', - 65514 : 'alt', - 65361 : 'left', - 65362 : 'up', - 65363 : 'right', - 65364 : 'down', - 65307 : 'escape', - 65470 : 'f1', - 65471 : 'f2', - 65472 : 'f3', - 65473 : 'f4', - 65474 : 'f5', - 65475 : 'f6', - 65476 : 'f7', - 65477 : 'f8', - 65478 : 'f9', - 65479 : 'f10', - 65480 : 'f11', - 65481 : 'f12', - 65300 : 'scroll_lock', - 65299 : 'break', - 65288 : 'backspace', - 65293 : 'enter', - 65379 : 'insert', - 65535 : 'delete', - 65360 : 'home', - 65367 : 'end', - 65365 : 'pageup', - 65366 : 'pagedown', - 65438 : '0', - 65436 : '1', - 65433 : '2', - 65435 : '3', - 65430 : '4', - 65437 : '5', - 65432 : '6', - 65429 : '7', - 65431 : '8', - 65434 : '9', - 65451 : '+', - 65453 : '-', - 65450 : '*', - 65455 : '/', - 65439 : 'dec', - 65421 : 'enter', - } - - # Setting this as a static constant prevents - # this resulting expression from leaking - 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.POINTER_MOTION_MASK | - Gdk.EventMask.POINTER_MOTION_HINT_MASK) - - def __init__(self, figure): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - FigureCanvasBase.__init__(self, figure) - GObject.GObject.__init__(self) - - 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('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.set_events(self.__class__.event_mask) - - self.set_double_buffered(True) - self.set_can_focus(True) - self._renderer_init() - self._idle_event_id = GObject.idle_add(self.idle_event) - - def destroy(self): - #Gtk.DrawingArea.destroy(self) - self.close_event() - GObject.source_remove(self._idle_event_id) - if self._idle_draw_id != 0: - GObject.source_remove(self._idle_draw_id) - - def scroll_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - x = event.x - # flipy so y=0 is bottom of canvas - y = self.get_allocation().height - event.y - if event.direction==Gdk.ScrollDirection.UP: - step = 1 - else: - step = -1 - FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event) - return False # finish event propagation? - - def button_press_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - x = event.x - # flipy so y=0 is bottom of canvas - y = self.get_allocation().height - event.y - FigureCanvasBase.button_press_event(self, x, y, event.button, guiEvent=event) - return False # finish event propagation? - - def button_release_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - x = event.x - # flipy so y=0 is bottom of canvas - y = self.get_allocation().height - event.y - FigureCanvasBase.button_release_event(self, x, y, event.button, guiEvent=event) - return False # finish event propagation? - - def key_press_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - key = self._get_key(event) - if _debug: print("hit", key) - FigureCanvasBase.key_press_event(self, key, guiEvent=event) - return False # finish event propagation? - - def key_release_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - key = self._get_key(event) - if _debug: print("release", key) - FigureCanvasBase.key_release_event(self, key, guiEvent=event) - return False # finish event propagation? - - def motion_notify_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - if event.is_hint: - t, x, y, state = event.window.get_pointer() - else: - x, y, state = event.x, event.y, event.get_state() - - # flipy so y=0 is bottom of canvas - y = self.get_allocation().height - y - FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) - return False # finish event propagation? - - def leave_notify_event(self, widget, event): - FigureCanvasBase.leave_notify_event(self, event) - - def enter_notify_event(self, widget, event): - FigureCanvasBase.enter_notify_event(self, event) - - def _get_key(self, event): - if event.keyval in self.keyvald: - key = self.keyvald[event.keyval] - elif event.keyval < 256: - key = chr(event.keyval) - else: - key = None - - modifiers = [ - (Gdk.ModifierType.MOD4_MASK, 'super'), - (Gdk.ModifierType.MOD1_MASK, 'alt'), - (Gdk.ModifierType.CONTROL_MASK, 'ctrl'), - ] - for key_mask, prefix in modifiers: - if event.state & key_mask: - key = '{0}+{1}'.format(prefix, key) - - return key - - def configure_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) - if widget.get_property("window") is None: - return - w, h = event.width, event.height - if w < 3 or h < 3: - return # empty fig - - # resize the figure (in inches) - dpi = self.figure.dpi - self.figure.set_size_inches (w/dpi, h/dpi) - self._need_redraw = True - - return False # finish event propagation? - - def on_draw_event(self, widget, ctx): - # to be overwritten by GTK3Agg or GTK3Cairo - pass - - def draw(self): - self._need_redraw = True - if self.get_visible() and self.get_mapped(): - self.queue_draw() - # do a synchronous draw (its less efficient than an async draw, - # but is required if/when animation is used) - self.get_property("window").process_updates (False) - - def draw_idle(self): - def idle_draw(*args): - self.draw() - self._idle_draw_id = 0 - return False - if self._idle_draw_id == 0: - self._idle_draw_id = GObject.idle_add(idle_draw) - - def new_timer(self, *args, **kwargs): - """ - Creates a new backend-specific subclass of :class:`backend_bases.Timer`. - This is useful for getting periodic events through the backend's native - event loop. Implemented only for backends with GUIs. - - optional arguments: - - *interval* - Timer interval in milliseconds - *callbacks* - Sequence of (func, args, kwargs) where func(*args, **kwargs) will - be executed by the timer every *interval*. - """ - return TimerGTK3(*args, **kwargs) - - def flush_events(self): - Gdk.threads_enter() - while Gtk.events_pending(): - Gtk.main_iteration(True) - 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 stop_event_loop(self): - FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ - - -class FigureManagerGTK3(FigureManagerBase): +class FigureManagerGTK3(FigureManagerGTK3Gui): """ Public attributes @@ -371,45 +59,10 @@ class FigureManagerGTK3(FigureManagerBase): window : The Gtk.Window (gtk only) """ def __init__(self, canvas, num): - if _debug: print('FigureManagerGTK3.%s' % fn_name()) - FigureManagerBase.__init__(self, canvas, num) + FigureManagerGTK3Gui.__init__(self, canvas) - self.window = Gtk.Window() + self.num = num 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) @@ -418,458 +71,13 @@ def destroy(*args): 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. + FigureManagerGTK3Gui.destroy(self, *args) - 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() - 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 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) - - if not path: path = os.getcwd() + os.sep - - # create an extra widget to list supported image formats - self.set_current_folder (path) - self.set_current_name ('image.' + default_filetype) - - hbox = Gtk.Box(spacing=10) - hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0) - - liststore = Gtk.ListStore(GObject.TYPE_STRING) - cbox = Gtk.ComboBox() #liststore) - cbox.set_model(liststore) - cell = Gtk.CellRendererText() - cbox.pack_start(cell, True) - cbox.add_attribute(cell, 'text', 0) - hbox.pack_start(cbox, False, False, 0) - - self.filetypes = filetypes - self.sorted_filetypes = list(six.iteritems(filetypes)) - self.sorted_filetypes.sort() - default = 0 - for i, (ext, name) in enumerate(self.sorted_filetypes): - liststore.append(["%s (*.%s)" % (name, ext)]) - if ext == default_filetype: - default = i - cbox.set_active(default) - self.ext = default_filetype - - def cb_cbox_changed (cbox, data=None): - """File extension changed""" - head, filename = os.path.split(self.get_filename()) - root, ext = os.path.splitext(filename) - ext = ext[1:] - new_ext = self.sorted_filetypes[cbox.get_active()][0] - self.ext = new_ext - - if ext in self.filetypes: - filename = root + '.' + new_ext - elif ext == '': - filename = filename.rstrip('.') + '.' + new_ext - - 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): - while True: - filename = None - if self.run() != int(Gtk.ResponseType.OK): - break - filename = self.get_filename() - break - - 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() - - -# Define the file to use as the GTk icon -if sys.platform == 'win32': - icon_filename = 'matplotlib.png' -else: - icon_filename = 'matplotlib.svg' -window_icon = os.path.join(matplotlib.rcParams['datapath'], 'images', icon_filename) - - -def error_msg_gtk(msg, parent=None): - 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)) - - dialog = Gtk.MessageDialog( - parent = parent, - type = Gtk.MessageType.ERROR, - buttons = Gtk.ButtonsType.OK, - message_format = msg) - dialog.run() - dialog.destroy() - -FigureCanvas = FigureCanvasGTK3 -FigureManager = FigureManagerGTK3 +FigureCanvasGTK3 = FigureCanvasGTK3Gui diff --git a/lib/matplotlib/backends/backend_gtk3_gui.py b/lib/matplotlib/backends/backend_gtk3_gui.py new file mode 100644 index 000000000000..5ccc96660a9e --- /dev/null +++ b/lib/matplotlib/backends/backend_gtk3_gui.py @@ -0,0 +1,849 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import os +import sys + + +def fn_name(): + return sys._getframe(1).f_code.co_name + +try: + import gi +except ImportError: + raise ImportError("Gtk3 backend requires pygobject to be installed.") + +try: + gi.require_version("Gtk", "3.0") +except AttributeError: + raise ImportError( + "pygobject version too old -- it must have require_version") +except ValueError: + raise ImportError( + "Gtk3 backend requires the GObject introspection bindings for Gtk 3 " + "to be installed.") + +try: + from gi.repository import Gtk, Gdk, GObject +except ImportError: + raise ImportError("Gtk3 backend requires pygobject to be installed.") + +import matplotlib +from matplotlib.backend_bases import FigureManagerBase, FigureCanvasBase, \ + NavigationToolbar2, cursors, TimerBase + +from matplotlib.cbook import is_string_like +from matplotlib.colors import colorConverter +from matplotlib.figure import Figure +from matplotlib.widgets import SubplotTool + +from matplotlib import lines +from matplotlib import cbook +from matplotlib import verbose +from matplotlib import rcParams + +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 +PIXELS_PER_INCH = 96 + +cursord = { + cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR), + cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2), + cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR), + cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS), + } + + +class TimerGTK3(TimerBase): + ''' + Subclass of :class:`backend_bases.TimerBase` that uses GTK3 for timer events. + + Attributes: + * interval: The time between timer events in milliseconds. Default + is 1000 ms. + * single_shot: Boolean flag indicating whether this timer should + operate as single shot (run once and then stop). Defaults to False. + * callbacks: Stores list of (func, args) tuples that will be called + upon timer events. This list can be manipulated directly, or the + functions add_callback and remove_callback can be used. + ''' + def _timer_start(self): + # Need to stop it, otherwise we potentially leak a timer id that will + # never be stopped. + self._timer_stop() + self._timer = GObject.timeout_add(self._interval, self._on_timer) + + def _timer_stop(self): + if self._timer is not None: + GObject.source_remove(self._timer) + self._timer = None + + def _timer_set_interval(self): + # Only stop and restart it if the timer has already been started + if self._timer is not None: + self._timer_stop() + self._timer_start() + + def _on_timer(self): + TimerBase._on_timer(self) + + # Gtk timeout_add() requires that the callback returns True if it + # is to be called again. + if len(self.callbacks) > 0 and not self._single: + return True + else: + self._timer = None + return False + + +class FigureCanvasGTK3Gui(Gtk.DrawingArea, FigureCanvasBase): + keyvald = {65507 : 'control', + 65505 : 'shift', + 65513 : 'alt', + 65508 : 'control', + 65506 : 'shift', + 65514 : 'alt', + 65361 : 'left', + 65362 : 'up', + 65363 : 'right', + 65364 : 'down', + 65307 : 'escape', + 65470 : 'f1', + 65471 : 'f2', + 65472 : 'f3', + 65473 : 'f4', + 65474 : 'f5', + 65475 : 'f6', + 65476 : 'f7', + 65477 : 'f8', + 65478 : 'f9', + 65479 : 'f10', + 65480 : 'f11', + 65481 : 'f12', + 65300 : 'scroll_lock', + 65299 : 'break', + 65288 : 'backspace', + 65293 : 'enter', + 65379 : 'insert', + 65535 : 'delete', + 65360 : 'home', + 65367 : 'end', + 65365 : 'pageup', + 65366 : 'pagedown', + 65438 : '0', + 65436 : '1', + 65433 : '2', + 65435 : '3', + 65430 : '4', + 65437 : '5', + 65432 : '6', + 65429 : '7', + 65431 : '8', + 65434 : '9', + 65451 : '+', + 65453 : '-', + 65450 : '*', + 65455 : '/', + 65439 : 'dec', + 65421 : 'enter', + } + + # Setting this as a static constant prevents + # this resulting expression from leaking + 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.POINTER_MOTION_MASK | + Gdk.EventMask.POINTER_MOTION_HINT_MASK) + + def __init__(self, figure): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + FigureCanvasBase.__init__(self, figure) + GObject.GObject.__init__(self) + + 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('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.set_events(self.__class__.event_mask) + + self.set_double_buffered(True) + self.set_can_focus(True) + self._renderer_init() + self._idle_event_id = GObject.idle_add(self.idle_event) + + def destroy(self): + #Gtk.DrawingArea.destroy(self) + self.close_event() + GObject.source_remove(self._idle_event_id) + if self._idle_draw_id != 0: + GObject.source_remove(self._idle_draw_id) + + def scroll_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + x = event.x + # flipy so y=0 is bottom of canvas + y = self.get_allocation().height - event.y + if event.direction==Gdk.ScrollDirection.UP: + step = 1 + else: + step = -1 + FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event) + return False # finish event propagation? + + def button_press_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + x = event.x + # flipy so y=0 is bottom of canvas + y = self.get_allocation().height - event.y + FigureCanvasBase.button_press_event(self, x, y, event.button, guiEvent=event) + return False # finish event propagation? + + def button_release_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + x = event.x + # flipy so y=0 is bottom of canvas + y = self.get_allocation().height - event.y + FigureCanvasBase.button_release_event(self, x, y, event.button, guiEvent=event) + return False # finish event propagation? + + def key_press_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + key = self._get_key(event) + if _debug: print("hit", key) + FigureCanvasBase.key_press_event(self, key, guiEvent=event) + return False # finish event propagation? + + def key_release_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + key = self._get_key(event) + if _debug: print("release", key) + FigureCanvasBase.key_release_event(self, key, guiEvent=event) + return False # finish event propagation? + + def motion_notify_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + if event.is_hint: + t, x, y, state = event.window.get_pointer() + else: + x, y, state = event.x, event.y, event.get_state() + + # flipy so y=0 is bottom of canvas + y = self.get_allocation().height - y + FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) + return False # finish event propagation? + + def leave_notify_event(self, widget, event): + FigureCanvasBase.leave_notify_event(self, event) + + def enter_notify_event(self, widget, event): + FigureCanvasBase.enter_notify_event(self, event) + + def _get_key(self, event): + if event.keyval in self.keyvald: + key = self.keyvald[event.keyval] + elif event.keyval < 256: + key = chr(event.keyval) + else: + key = None + + modifiers = [ + (Gdk.ModifierType.MOD4_MASK, 'super'), + (Gdk.ModifierType.MOD1_MASK, 'alt'), + (Gdk.ModifierType.CONTROL_MASK, 'ctrl'), + ] + for key_mask, prefix in modifiers: + if event.state & key_mask: + key = '{0}+{1}'.format(prefix, key) + + return key + + def configure_event(self, widget, event): + if _debug: print('FigureCanvasGTK3.%s' % fn_name()) + if widget.get_property("window") is None: + return + w, h = event.width, event.height + if w < 3 or h < 3: + return # empty fig + + # resize the figure (in inches) + dpi = self.figure.dpi + self.figure.set_size_inches (w/dpi, h/dpi) + self._need_redraw = True + + return False # finish event propagation? + + def on_draw_event(self, widget, ctx): + # to be overwritten by GTK3Agg or GTK3Cairo + pass + + def draw(self): + self._need_redraw = True + if self.get_visible() and self.get_mapped(): + self.queue_draw() + # do a synchronous draw (its less efficient than an async draw, + # but is required if/when animation is used) + self.get_property("window").process_updates (False) + + def draw_idle(self): + def idle_draw(*args): + self.draw() + self._idle_draw_id = 0 + return False + if self._idle_draw_id == 0: + self._idle_draw_id = GObject.idle_add(idle_draw) + + def new_timer(self, *args, **kwargs): + """ + Creates a new backend-specific subclass of :class:`backend_bases.Timer`. + This is useful for getting periodic events through the backend's native + event loop. Implemented only for backends with GUIs. + + optional arguments: + + *interval* + Timer interval in milliseconds + *callbacks* + Sequence of (func, args, kwargs) where func(*args, **kwargs) will + be executed by the timer every *interval*. + """ + return TimerGTK3(*args, **kwargs) + + def flush_events(self): + Gdk.threads_enter() + while Gtk.events_pending(): + Gtk.main_iteration(True) + 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 stop_event_loop(self): + FigureCanvasBase.stop_event_loop_default(self) + stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ + + +class FigureManagerGTK3Gui(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): + if _debug: print('FigureManagerGTK3.%s' % fn_name()) + FigureManagerBase.__init__(self, canvas) + + self.window = Gtk.Window() + self.set_window_title("Figure") + 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 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() + + 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 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) + + if not path: path = os.getcwd() + os.sep + + # create an extra widget to list supported image formats + self.set_current_folder (path) + self.set_current_name ('image.' + default_filetype) + + hbox = Gtk.Box(spacing=10) + hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0) + + liststore = Gtk.ListStore(GObject.TYPE_STRING) + cbox = Gtk.ComboBox() #liststore) + cbox.set_model(liststore) + cell = Gtk.CellRendererText() + cbox.pack_start(cell, True) + cbox.add_attribute(cell, 'text', 0) + hbox.pack_start(cbox, False, False, 0) + + self.filetypes = filetypes + self.sorted_filetypes = list(six.iteritems(filetypes)) + self.sorted_filetypes.sort() + default = 0 + for i, (ext, name) in enumerate(self.sorted_filetypes): + liststore.append(["%s (*.%s)" % (name, ext)]) + if ext == default_filetype: + default = i + cbox.set_active(default) + self.ext = default_filetype + + def cb_cbox_changed (cbox, data=None): + """File extension changed""" + head, filename = os.path.split(self.get_filename()) + root, ext = os.path.splitext(filename) + ext = ext[1:] + new_ext = self.sorted_filetypes[cbox.get_active()][0] + self.ext = new_ext + + if ext in self.filetypes: + filename = root + '.' + new_ext + elif ext == '': + filename = filename.rstrip('.') + '.' + new_ext + + 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): + while True: + filename = None + if self.run() != int(Gtk.ResponseType.OK): + break + filename = self.get_filename() + break + + 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() + + +# Define the file to use as the GTk icon +if sys.platform == 'win32': + icon_filename = 'matplotlib.png' +else: + icon_filename = 'matplotlib.svg' +window_icon = os.path.join(matplotlib.rcParams['datapath'], 'images', icon_filename) + + +def error_msg_gtk(msg, parent=None): + 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)) + + dialog = Gtk.MessageDialog( + parent = parent, + type = Gtk.MessageType.ERROR, + buttons = Gtk.ButtonsType.OK, + message_format = msg) + dialog.run() + dialog.destroy() diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 2985087f07be..21353ad5d7cf 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -3,91 +3,18 @@ import six -import cairo -import numpy as np -import sys import warnings -from . import backend_agg +from . import backend_gtk3agg_gui from . import backend_gtk3 from matplotlib.figure import Figure -from matplotlib import transforms if six.PY3: warnings.warn("The Gtk3Agg backend is not known to work on Python 3.x.") -class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3, - backend_agg.FigureCanvasAgg): - def __init__(self, figure): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure) - self._bbox_queue = [] - - def _renderer_init(self): - pass - - def _render_figure(self, width, height): - backend_agg.FigureCanvasAgg.draw(self) - - def on_draw_event(self, widget, ctx): - """ GtkDrawable draw event, like expose_event in GTK 2.X - """ - allocation = self.get_allocation() - w, h = allocation.width, allocation.height - - if not len(self._bbox_queue): - if self._need_redraw: - self._render_figure(w, h) - bbox_queue = [transforms.Bbox([[0, 0], [w, h]])] - else: - return - else: - bbox_queue = self._bbox_queue - - for bbox in bbox_queue: - area = self.copy_from_bbox(bbox) - buf = np.fromstring(area.to_string_argb(), dtype='uint8') - - x = int(bbox.x0) - y = h - int(bbox.y1) - width = int(bbox.x1) - int(bbox.x0) - height = int(bbox.y1) - int(bbox.y0) - - image = cairo.ImageSurface.create_for_data( - buf, cairo.FORMAT_ARGB32, width, height) - ctx.set_source_surface(image, x, y) - ctx.paint() - - if len(self._bbox_queue): - self._bbox_queue = [] - - return False - - def blit(self, bbox=None): - # If bbox is None, blit the entire canvas to gtk. Otherwise - # blit only the area defined by the bbox. - if bbox is None: - bbox = self.figure.bbox - - allocation = self.get_allocation() - w, h = allocation.width, allocation.height - x = int(bbox.x0) - y = h - int(bbox.y1) - width = int(bbox.x1) - int(bbox.x0) - height = int(bbox.y1) - int(bbox.y0) - - self._bbox_queue.append(bbox) - self.queue_draw_area(x, y, width, height) - - def print_png(self, filename, *args, **kwargs): - # Do this so we can save the resolution of figure in the PNG file - agg = self.switch_backends(backend_agg.FigureCanvasAgg) - return agg.print_png(filename, *args, **kwargs) - - -class FigureManagerGTK3Agg(backend_gtk3.FigureManagerGTK3): - pass - +FigureCanvasGTK3Agg = backend_gtk3agg_gui.FigureCanvasGTK3AggGui +FigureManagerGTK3Agg = backend_gtk3.FigureManagerGTK3 def new_figure_manager(num, *args, **kwargs): """ diff --git a/lib/matplotlib/backends/backend_gtk3agg_gui.py b/lib/matplotlib/backends/backend_gtk3agg_gui.py new file mode 100644 index 000000000000..8cb070758362 --- /dev/null +++ b/lib/matplotlib/backends/backend_gtk3agg_gui.py @@ -0,0 +1,91 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import cairo +import numpy as np +import warnings + +from . import backend_agg +from . import backend_gtk3_gui +from matplotlib import transforms + +if six.PY3: + warnings.warn("The Gtk3Agg backend is not known to work on Python 3.x.") + + +class FigureCanvasGTK3AggGui(backend_gtk3_gui.FigureCanvasGTK3Gui, + backend_agg.FigureCanvasAgg): + def __init__(self, figure): + backend_gtk3_gui.FigureCanvasGTK3Gui.__init__(self, figure) + self._bbox_queue = [] + + def _renderer_init(self): + pass + + def _render_figure(self, width, height): + backend_agg.FigureCanvasAgg.draw(self) + + def on_draw_event(self, widget, ctx): + """ GtkDrawable draw event, like expose_event in GTK 2.X + """ + allocation = self.get_allocation() + w, h = allocation.width, allocation.height + + if not len(self._bbox_queue): + if self._need_redraw: + self._render_figure(w, h) + bbox_queue = [transforms.Bbox([[0, 0], [w, h]])] + else: + return + else: + bbox_queue = self._bbox_queue + + for bbox in bbox_queue: + area = self.copy_from_bbox(bbox) + buf = np.fromstring(area.to_string_argb(), dtype='uint8') + + x = int(bbox.x0) + y = h - int(bbox.y1) + width = int(bbox.x1) - int(bbox.x0) + height = int(bbox.y1) - int(bbox.y0) + + image = cairo.ImageSurface.create_for_data( + buf, cairo.FORMAT_ARGB32, width, height) + ctx.set_source_surface(image, x, y) + ctx.paint() + + if len(self._bbox_queue): + self._bbox_queue = [] + + return False + + def blit(self, bbox=None): + # If bbox is None, blit the entire canvas to gtk. Otherwise + # blit only the area defined by the bbox. + if bbox is None: + bbox = self.figure.bbox + + allocation = self.get_allocation() + w, h = allocation.width, allocation.height + x = int(bbox.x0) + y = h - int(bbox.y1) + width = int(bbox.x1) - int(bbox.x0) + height = int(bbox.y1) - int(bbox.y0) + + self._bbox_queue.append(bbox) + self.queue_draw_area(x, y, width, height) + + def print_png(self, filename, *args, **kwargs): + # Do this so we can save the resolution of figure in the PNG file + agg = self.switch_backends(backend_agg.FigureCanvasAgg) + return agg.print_png(filename, *args, **kwargs) + + +class FigureManagerGTK3AggGui(backend_gtk3_gui.FigureManagerGTK3Gui): + pass + + +FigureCanvas = FigureCanvasGTK3AggGui +FigureManager = FigureManagerGTK3AggGui diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index c53c20239b79..5411b72869db 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -3,45 +3,13 @@ import six +from . import backend_gtk3cairo_gui from . import backend_gtk3 -from . import backend_cairo from matplotlib.figure import Figure -class RendererGTK3Cairo(backend_cairo.RendererCairo): - def set_context(self, ctx): - self.gc.ctx = ctx - -class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3, - backend_cairo.FigureCanvasCairo): - def __init__(self, figure): - backend_gtk3.FigureCanvasGTK3.__init__(self, figure) - - def _renderer_init(self): - """use cairo renderer""" - self._renderer = RendererGTK3Cairo(self.figure.dpi) - - def _render_figure(self, width, height): - 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 - """ - # the _need_redraw flag doesnt work. it sometimes prevents - # the rendering and leaving the canvas blank - #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 - self._render_figure(w, h) - #self._need_redraw = False - - return False # finish event propagation? - - -class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3): - pass +FigureCanvasGTK3Cairo = backend_gtk3cairo_gui.FigureCanvasGTK3CairoGui +FigureManagerGTK3Cairo = backend_gtk3.FigureManagerGTK3 def new_figure_manager(num, *args, **kwargs): diff --git a/lib/matplotlib/backends/backend_gtk3cairo_gui.py b/lib/matplotlib/backends/backend_gtk3cairo_gui.py new file mode 100644 index 000000000000..003da542b032 --- /dev/null +++ b/lib/matplotlib/backends/backend_gtk3cairo_gui.py @@ -0,0 +1,45 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +from . import backend_gtk3_gui +from . import backend_cairo + + +class RendererGTK3Cairo(backend_cairo.RendererCairo): + def set_context(self, ctx): + self.gc.ctx = ctx + + +class FigureCanvasGTK3CairoGui(backend_gtk3_gui.FigureCanvasGTK3Gui, + backend_cairo.FigureCanvasCairo): + def __init__(self, figure): + backend_gtk3_gui.FigureCanvasGTK3Gui.__init__(self, figure) + + def _renderer_init(self): + """use cairo renderer""" + self._renderer = RendererGTK3Cairo(self.figure.dpi) + + def _render_figure(self, width, height): + 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 + """ + # the _need_redraw flag doesnt work. it sometimes prevents + # the rendering and leaving the canvas blank + #if self._need_redraw: + self._renderer.set_context(ctx) + allocation = self.get_allocation() + w, h = allocation.width, allocation.height + self._render_figure(w, h) + #self._need_redraw = False + + return False # finish event propagation? + +FigureManagerGTK3CairoGui = backend_gtk3_gui.FigureManagerGTK3Gui + +FigureCanvas = FigureCanvasGTK3CairoGui +FigureManager = FigureManagerGTK3CairoGui diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 5c88eb0c57ce..8bf3feec0c41 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -357,7 +357,8 @@ class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): Wrap everything up into a window for the pylab interface """ def __init__(self, canvas, num): - FigureManagerBase.__init__(self, canvas, num) + FigureManagerBase.__init__(self, canvas) + self.num = num title = "Figure %d" % num _macosx.FigureManager.__init__(self, canvas, title) if rcParams['toolbar']=='toolbar2': diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index b1d49758b22b..84cec0c8ffdf 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -441,7 +441,8 @@ class FigureManagerQT(FigureManagerBase): def __init__(self, canvas, num): if DEBUG: print('FigureManagerQT.%s' % fn_name()) - FigureManagerBase.__init__(self, canvas, num) + FigureManagerBase.__init__(self, canvas) + self.num = num self.canvas = canvas self.window = MainWindow() self.window.closing.connect(canvas.close_event) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 209a8520d044..1e9d0145307a 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -518,7 +518,8 @@ class FigureManagerTkAgg(FigureManagerBase): window : The tk.Window """ def __init__(self, canvas, num, window): - FigureManagerBase.__init__(self, canvas, num) + FigureManagerBase.__init__(self, canvas) + self.num = num self.window = window self.window.withdraw() self.set_window_title("Figure %d" % num) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index c7a50b420a4d..f7c135f7da7e 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -218,7 +218,8 @@ def stop_event_loop(self): class FigureManagerWebAgg(backend_bases.FigureManagerBase): def __init__(self, canvas, num): - backend_bases.FigureManagerBase.__init__(self, canvas, num) + backend_bases.FigureManagerBase.__init__(self, canvas) + self.num = num self.web_sockets = set() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index d99f04f07ea9..af119cf10c2e 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1414,7 +1414,8 @@ class FigureManagerWx(FigureManagerBase): """ def __init__(self, canvas, num, frame): DEBUG_MSG("__init__()", 1, self) - FigureManagerBase.__init__(self, canvas, num) + FigureManagerBase.__init__(self, canvas) + self.num = num self.frame = frame self.window = frame From e0bc1221566f3ed56b3f4828eb95dced397a731b Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 27 Nov 2013 16:55:50 -0500 Subject: [PATCH 2/3] missing backends that called figuremanager with num --- lib/matplotlib/backends/backend_agg.py | 3 ++- lib/matplotlib/backends/backend_cairo.py | 3 ++- lib/matplotlib/backends/backend_cocoaagg.py | 3 ++- lib/matplotlib/backends/backend_gdk.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b60227128c17..4b0e809aa0fd 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -425,7 +425,8 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasAgg(figure) - manager = FigureManagerBase(canvas, num) + manager = FigureManagerBase(canvas) + manager.num = num return manager diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index f210f6be6839..24f219e5194d 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -410,7 +410,8 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasCairo(figure) - manager = FigureManagerBase(canvas, num) + manager = FigureManagerBase(canvas) + manager.num = num return manager diff --git a/lib/matplotlib/backends/backend_cocoaagg.py b/lib/matplotlib/backends/backend_cocoaagg.py index 2a9fb6ba72b7..38ffcf48d44f 100644 --- a/lib/matplotlib/backends/backend_cocoaagg.py +++ b/lib/matplotlib/backends/backend_cocoaagg.py @@ -228,7 +228,8 @@ def startWithBundle_(self, bundle): class FigureManagerCocoaAgg(FigureManagerBase): def __init__(self, canvas, num): - FigureManagerBase.__init__(self, canvas, num) + FigureManagerBase.__init__(self, canvas) + self.num = num try: WMEnable('Matplotlib') diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py index fc705febeb59..b1c76a1a650c 100644 --- a/lib/matplotlib/backends/backend_gdk.py +++ b/lib/matplotlib/backends/backend_gdk.py @@ -434,7 +434,8 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasGDK(figure) - manager = FigureManagerBase(canvas, num) + manager = FigureManagerBase(canvas) + manager.num = num return manager From 55568cba2269dde03f9da154909eb4e1e04c0eb2 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Wed, 27 Nov 2013 18:01:40 -0500 Subject: [PATCH 3/3] coding standards --- lib/matplotlib/backends/backend_gtk3agg.py | 1 + lib/matplotlib/tests/test_coding_standards.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 21353ad5d7cf..9f11d979399b 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -16,6 +16,7 @@ FigureCanvasGTK3Agg = backend_gtk3agg_gui.FigureCanvasGTK3AggGui FigureManagerGTK3Agg = backend_gtk3.FigureManagerGTK3 + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance diff --git a/lib/matplotlib/tests/test_coding_standards.py b/lib/matplotlib/tests/test_coding_standards.py index 8dcc99a25add..de5f3a382781 100644 --- a/lib/matplotlib/tests/test_coding_standards.py +++ b/lib/matplotlib/tests/test_coding_standards.py @@ -105,8 +105,7 @@ '*/matplotlib/backends/backend_cocoaagg.py', '*/matplotlib/backends/backend_gdk.py', '*/matplotlib/backends/backend_gtk.py', - '*/matplotlib/backends/backend_gtk3.py', - '*/matplotlib/backends/backend_gtk3cairo.py', + '*/matplotlib/backends/backend_gtk3_gui.py', '*/matplotlib/backends/backend_gtkagg.py', '*/matplotlib/backends/backend_gtkcairo.py', '*/matplotlib/backends/backend_macosx.py',