1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000 |
- import functools
- import logging
- import os
- from pathlib import Path
- import sys
- import matplotlib
- from matplotlib import backend_tools, cbook, rcParams
- from matplotlib._pylab_helpers import Gcf
- from matplotlib.backend_bases import (
- _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
- StatusbarBase, TimerBase, ToolContainerBase, cursors)
- from matplotlib.backend_managers import ToolManager
- from matplotlib.figure import Figure
- from matplotlib.widgets import SubplotTool
- try:
- import gi
- except ImportError:
- raise ImportError("The GTK3 backends require PyGObject")
- try:
- # :raises ValueError: If module/version is already loaded, already
- # required, or unavailable.
- gi.require_version("Gtk", "3.0")
- except ValueError as e:
- # in this case we want to re-raise as ImportError so the
- # auto-backend selection logic correctly skips.
- raise ImportError from e
- from gi.repository import GLib, GObject, Gtk, Gdk
- _log = logging.getLogger(__name__)
- backend_version = "%s.%s.%s" % (
- Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version())
- try:
- 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),
- cursors.WAIT: Gdk.Cursor.new(Gdk.CursorType.WATCH),
- }
- except TypeError as exc:
- # Happens when running headless. Convert to ImportError to cooperate with
- # backend switching.
- raise ImportError(exc)
- class TimerGTK3(TimerBase):
- """
- Subclass of `.TimerBase` using GTK3 for timer events.
- Attributes
- ----------
- interval : int
- The time between timer events in milliseconds. Default is 1000 ms.
- single_shot : bool
- Boolean flag indicating whether this timer should operate as single
- shot (run once and then stop). Defaults to False.
- callbacks : list
- 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 = GLib.timeout_add(self._interval, self._on_timer)
- def _timer_stop(self):
- if self._timer is not None:
- GLib.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 self.callbacks and not self._single:
- return True
- else:
- self._timer = None
- return False
- class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase):
- required_interactive_framework = "gtk3"
- 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
- | Gdk.EventMask.SCROLL_MASK)
- def __init__(self, figure):
- FigureCanvasBase.__init__(self, figure)
- GObject.GObject.__init__(self)
- self._idle_draw_id = 0
- 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.connect('size_allocate', self.size_allocate)
- self.set_events(self.__class__.event_mask)
- self.set_double_buffered(True)
- self.set_can_focus(True)
- self._renderer_init()
- def destroy(self):
- #Gtk.DrawingArea.destroy(self)
- self.close_event()
- if self._idle_draw_id != 0:
- GLib.source_remove(self._idle_draw_id)
- def scroll_event(self, widget, event):
- x = event.x
- # flipy so y=0 is bottom of canvas
- y = self.get_allocation().height - event.y
- step = 1 if event.direction == Gdk.ScrollDirection.UP else -1
- FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
- return False # finish event propagation?
- def button_press_event(self, widget, event):
- 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):
- 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):
- key = self._get_key(event)
- FigureCanvasBase.key_press_event(self, key, guiEvent=event)
- return True # stop event propagation
- def key_release_event(self, widget, event):
- key = self._get_key(event)
- FigureCanvasBase.key_release_event(self, key, guiEvent=event)
- return True # stop event propagation
- def motion_notify_event(self, widget, event):
- if event.is_hint:
- t, x, y, state = event.window.get_pointer()
- else:
- x, y = event.x, event.y
- # 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):
- x = event.x
- # flipy so y=0 is bottom of canvas
- y = self.get_allocation().height - event.y
- FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y))
- def size_allocate(self, widget, allocation):
- dpival = self.figure.dpi
- winch = allocation.width / dpival
- hinch = allocation.height / dpival
- self.figure.set_size_inches(winch, hinch, forward=False)
- FigureCanvasBase.resize_event(self)
- self.draw_idle()
- 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 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, forward=False)
- return False # finish event propagation?
- def on_draw_event(self, widget, ctx):
- # to be overwritten by GTK3Agg or GTK3Cairo
- pass
- def draw(self):
- # docstring inherited
- if self.is_drawable():
- self.queue_draw()
- def draw_idle(self):
- # docstring inherited
- if self._idle_draw_id != 0:
- return
- def idle_draw(*args):
- try:
- self.draw()
- finally:
- self._idle_draw_id = 0
- return False
- self._idle_draw_id = GLib.idle_add(idle_draw)
- def new_timer(self, *args, **kwargs):
- # docstring inherited
- return TimerGTK3(*args, **kwargs)
- def flush_events(self):
- # docstring inherited
- Gdk.threads_enter()
- while Gtk.events_pending():
- Gtk.main_iteration()
- Gdk.flush()
- Gdk.threads_leave()
- class FigureManagerGTK3(FigureManagerBase):
- """
- Attributes
- ----------
- canvas : `FigureCanvas`
- The FigureCanvas instance
- num : int or str
- The Figure number
- toolbar : Gtk.Toolbar
- The Gtk.Toolbar
- vbox : Gtk.VBox
- The Gtk.VBox containing the canvas and toolbar
- window : Gtk.Window
- The Gtk.Window
- """
- def __init__(self, canvas, num):
- FigureManagerBase.__init__(self, canvas, num)
- self.window = Gtk.Window()
- self.window.set_wmclass("matplotlib", "Matplotlib")
- self.set_window_title("Figure %d" % num)
- try:
- self.window.set_icon_from_file(window_icon)
- except Exception:
- # 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
- _log.info('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)
- # calculate size for window
- w = int(self.canvas.figure.bbox.width)
- h = int(self.canvas.figure.bbox.height)
- self.toolmanager = self._get_toolmanager()
- self.toolbar = self._get_toolbar()
- self.statusbar = None
- def add_widget(child, expand, fill, padding):
- child.show()
- self.vbox.pack_end(child, False, False, 0)
- size_request = child.size_request()
- return size_request.height
- if self.toolmanager:
- backend_tools.add_tools_to_manager(self.toolmanager)
- if self.toolbar:
- backend_tools.add_tools_to_container(self.toolbar)
- self.statusbar = StatusbarGTK3(self.toolmanager)
- h += add_widget(self.statusbar, False, False, 0)
- h += add_widget(Gtk.HSeparator(), False, False, 0)
- if self.toolbar is not None:
- self.toolbar.show()
- h += add_widget(self.toolbar, False, False, 0)
- 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()
- self.canvas.draw_idle()
- self.canvas.grab_focus()
- def destroy(self, *args):
- self.vbox.destroy()
- self.window.destroy()
- self.canvas.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 show(self):
- # show the figure window
- self.window.show()
- self.window.present()
- 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):
- # must be inited after the window, drawingArea and figure
- # attrs are set
- if rcParams['toolbar'] == 'toolbar2':
- toolbar = NavigationToolbar2GTK3(self.canvas, self.window)
- elif rcParams['toolbar'] == 'toolmanager':
- toolbar = ToolbarGTK3(self.toolmanager)
- else:
- toolbar = None
- return toolbar
- def _get_toolmanager(self):
- # must be initialised after toolbar has been set
- if rcParams['toolbar'] == 'toolmanager':
- toolmanager = ToolManager(self.canvas.figure)
- else:
- toolmanager = None
- return toolmanager
- 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])
- Gtk.main_iteration()
- 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)
- self._gtk_ids = {}
- for text, tooltip_text, image_file, callback in self.toolitems:
- if text is None:
- self.insert(Gtk.SeparatorToolItem(), -1)
- continue
- image = Gtk.Image()
- image.set_from_file(
- str(cbook._get_data_path('images', image_file + '.png')))
- self._gtk_ids[text] = 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()
- @cbook.deprecated("3.1")
- def get_filechooser(self):
- fc = FileChooserDialog(
- title='Save the figure',
- parent=self.win,
- path=os.path.expanduser(rcParams['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):
- dialog = Gtk.FileChooserDialog(
- title="Save the figure",
- parent=self.canvas.get_toplevel(),
- action=Gtk.FileChooserAction.SAVE,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
- )
- for name, fmts \
- in self.canvas.get_supported_filetypes_grouped().items():
- ff = Gtk.FileFilter()
- ff.set_name(name)
- for fmt in fmts:
- ff.add_pattern("*." + fmt)
- dialog.add_filter(ff)
- if self.canvas.get_default_filetype() in fmts:
- dialog.set_filter(ff)
- @functools.partial(dialog.connect, "notify::filter")
- def on_notify_filter(*args):
- name = dialog.get_filter().get_name()
- fmt = self.canvas.get_supported_filetypes_grouped()[name][0]
- dialog.set_current_name(
- str(Path(dialog.get_current_name()).with_suffix("." + fmt)))
- dialog.set_current_folder(rcParams["savefig.directory"])
- dialog.set_current_name(self.canvas.get_default_filename())
- dialog.set_do_overwrite_confirmation(True)
- response = dialog.run()
- fname = dialog.get_filename()
- ff = dialog.get_filter() # Doesn't autoadjust to filename :/
- fmt = self.canvas.get_supported_filetypes_grouped()[ff.get_name()][0]
- dialog.destroy()
- if response == Gtk.ResponseType.CANCEL:
- return
- # Save dir for next time, unless empty str (which means use cwd).
- if rcParams['savefig.directory']:
- rcParams['savefig.directory'] = os.path.dirname(fname)
- try:
- self.canvas.figure.savefig(fname, format=fmt)
- except Exception as e:
- error_msg_gtk(str(e), parent=self)
- def configure_subplots(self, button):
- toolfig = Figure(figsize=(6, 3))
- canvas = type(self.canvas)(toolfig)
- toolfig.subplots_adjust(top=0.9)
- # Need to keep a reference to the tool.
- _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 Exception:
- # 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 set_history_buttons(self):
- can_backward = self._nav_stack._pos > 0
- can_forward = self._nav_stack._pos < len(self._nav_stack._elements) - 1
- if 'Back' in self._gtk_ids:
- self._gtk_ids['Back'].set_sensitive(can_backward)
- if 'Forward' in self._gtk_ids:
- self._gtk_ids['Forward'].set_sensitive(can_forward)
- @cbook.deprecated("3.1")
- 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().__init__(title, parent, action, buttons)
- self.set_default_response(Gtk.ResponseType.OK)
- self.set_do_overwrite_confirmation(True)
- if not path:
- path = os.getcwd()
- # 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()
- 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
- sorted_filetypes = sorted(filetypes.items())
- default = 0
- for i, (ext, name) in enumerate(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 = 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):
- if self.run() == int(Gtk.ResponseType.OK):
- return self.get_filename(), self.ext
- else:
- return None, self.ext
- class ToolbarGTK3(ToolContainerBase, Gtk.Box):
- _icon_extension = '.png'
- def __init__(self, toolmanager):
- ToolContainerBase.__init__(self, toolmanager)
- Gtk.Box.__init__(self)
- self.set_property("orientation", Gtk.Orientation.VERTICAL)
- self._toolarea = Gtk.Box()
- self._toolarea.set_property('orientation', Gtk.Orientation.HORIZONTAL)
- self.pack_start(self._toolarea, False, False, 0)
- self._toolarea.show_all()
- self._groups = {}
- self._toolitems = {}
- def add_toolitem(self, name, group, position, image_file, description,
- toggle):
- if toggle:
- tbutton = Gtk.ToggleToolButton()
- else:
- tbutton = Gtk.ToolButton()
- tbutton.set_label(name)
- if image_file is not None:
- image = Gtk.Image()
- image.set_from_file(image_file)
- tbutton.set_icon_widget(image)
- if position is None:
- position = -1
- self._add_button(tbutton, group, position)
- signal = tbutton.connect('clicked', self._call_tool, name)
- tbutton.set_tooltip_text(description)
- tbutton.show_all()
- self._toolitems.setdefault(name, [])
- self._toolitems[name].append((tbutton, signal))
- def _add_button(self, button, group, position):
- if group not in self._groups:
- if self._groups:
- self._add_separator()
- toolbar = Gtk.Toolbar()
- toolbar.set_style(Gtk.ToolbarStyle.ICONS)
- self._toolarea.pack_start(toolbar, False, False, 0)
- toolbar.show_all()
- self._groups[group] = toolbar
- self._groups[group].insert(button, position)
- def _call_tool(self, btn, name):
- self.trigger_tool(name)
- def toggle_toolitem(self, name, toggled):
- if name not in self._toolitems:
- return
- for toolitem, signal in self._toolitems[name]:
- toolitem.handler_block(signal)
- toolitem.set_active(toggled)
- toolitem.handler_unblock(signal)
- def remove_toolitem(self, name):
- if name not in self._toolitems:
- self.toolmanager.message_event('%s Not in toolbar' % name, self)
- return
- for group in self._groups:
- for toolitem, _signal in self._toolitems[name]:
- if toolitem in self._groups[group]:
- self._groups[group].remove(toolitem)
- del self._toolitems[name]
- def _add_separator(self):
- sep = Gtk.Separator()
- sep.set_property("orientation", Gtk.Orientation.VERTICAL)
- self._toolarea.pack_start(sep, False, True, 0)
- sep.show_all()
- class StatusbarGTK3(StatusbarBase, Gtk.Statusbar):
- def __init__(self, *args, **kwargs):
- StatusbarBase.__init__(self, *args, **kwargs)
- Gtk.Statusbar.__init__(self)
- self._context = self.get_context_id('message')
- def set_message(self, s):
- self.pop(self._context)
- self.push(self._context, s)
- class RubberbandGTK3(backend_tools.RubberbandBase):
- def draw_rubberband(self, x0, y0, x1, y1):
- NavigationToolbar2GTK3.draw_rubberband(
- self._make_classic_style_pseudo_toolbar(), None, x0, y0, x1, y1)
- class SaveFigureGTK3(backend_tools.SaveFigureBase):
- @cbook.deprecated("3.1")
- def get_filechooser(self):
- fc = FileChooserDialog(
- title='Save the figure',
- parent=self.figure.canvas.manager.window,
- path=os.path.expanduser(rcParams['savefig.directory']),
- filetypes=self.figure.canvas.get_supported_filetypes(),
- default_filetype=self.figure.canvas.get_default_filetype())
- fc.set_current_name(self.figure.canvas.get_default_filename())
- return fc
- def trigger(self, *args, **kwargs):
- class PseudoToolbar:
- canvas = self.figure.canvas
- return NavigationToolbar2GTK3.save_figure(PseudoToolbar())
- class SetCursorGTK3(backend_tools.SetCursorBase):
- def set_cursor(self, cursor):
- NavigationToolbar2GTK3.set_cursor(
- self._make_classic_style_pseudo_toolbar(), cursor)
- class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window):
- @cbook.deprecated("3.2")
- @property
- def window(self):
- if not hasattr(self, "_window"):
- self._window = None
- return self._window
- @window.setter
- @cbook.deprecated("3.2")
- def window(self, window):
- self._window = window
- @cbook.deprecated("3.2")
- def init_window(self):
- if self.window:
- return
- self.window = Gtk.Window(title="Subplot Configuration Tool")
- try:
- self.window.window.set_icon_from_file(window_icon)
- except Exception:
- # we presumably already logged a message on the
- # failure of the main plot, don't keep reporting
- pass
- 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)
- toolfig = Figure(figsize=(6, 3))
- canvas = self.figure.canvas.__class__(toolfig)
- toolfig.subplots_adjust(top=0.9)
- SubplotTool(self.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()
- @cbook.deprecated("3.2")
- def destroy(self, *args):
- self.window.destroy()
- self.window = None
- def _get_canvas(self, fig):
- return self.canvas.__class__(fig)
- def trigger(self, *args):
- NavigationToolbar2GTK3.configure_subplots(
- self._make_classic_style_pseudo_toolbar(), None)
- class HelpGTK3(backend_tools.ToolHelpBase):
- def _normalize_shortcut(self, key):
- """
- Convert Matplotlib key presses to GTK+ accelerator identifiers.
- Related to `FigureCanvasGTK3._get_key`.
- """
- special = {
- 'backspace': 'BackSpace',
- 'pagedown': 'Page_Down',
- 'pageup': 'Page_Up',
- 'scroll_lock': 'Scroll_Lock',
- }
- parts = key.split('+')
- mods = ['<' + mod + '>' for mod in parts[:-1]]
- key = parts[-1]
- if key in special:
- key = special[key]
- elif len(key) > 1:
- key = key.capitalize()
- elif key.isupper():
- mods += ['<shift>']
- return ''.join(mods) + key
- def _show_shortcuts_window(self):
- section = Gtk.ShortcutsSection()
- for name, tool in sorted(self.toolmanager.tools.items()):
- if not tool.description:
- continue
- # Putting everything in a separate group allows GTK to
- # automatically split them into separate columns/pages, which is
- # useful because we have lots of shortcuts, some with many keys
- # that are very wide.
- group = Gtk.ShortcutsGroup()
- section.add(group)
- # A hack to remove the title since we have no group naming.
- group.forall(lambda widget, data: widget.set_visible(False), None)
- shortcut = Gtk.ShortcutsShortcut(
- accelerator=' '.join(
- self._normalize_shortcut(key)
- for key in self.toolmanager.get_tool_keymap(name)
- # Will never be sent:
- if 'cmd+' not in key),
- title=tool.name,
- subtitle=tool.description)
- group.add(shortcut)
- window = Gtk.ShortcutsWindow(
- title='Help',
- modal=True,
- transient_for=self._figure.canvas.get_toplevel())
- section.show() # Must be done explicitly before add!
- window.add(section)
- window.show_all()
- def _show_shortcuts_dialog(self):
- dialog = Gtk.MessageDialog(
- self._figure.canvas.get_toplevel(),
- 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, self._get_help_text(),
- title="Help")
- dialog.run()
- dialog.destroy()
- def trigger(self, *args):
- if Gtk.check_version(3, 20, 0) is None:
- self._show_shortcuts_window()
- else:
- self._show_shortcuts_dialog()
- class ToolCopyToClipboardGTK3(backend_tools.ToolCopyToClipboardBase):
- def trigger(self, *args, **kwargs):
- clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
- window = self.canvas.get_window()
- x, y, width, height = window.get_geometry()
- pb = Gdk.pixbuf_get_from_window(window, x, y, width, height)
- clipboard.set_image(pb)
- # Define the file to use as the GTk icon
- if sys.platform == 'win32':
- icon_filename = 'matplotlib.png'
- else:
- icon_filename = 'matplotlib.svg'
- window_icon = str(cbook._get_data_path('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 isinstance(msg, str):
- 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()
- backend_tools.ToolSaveFigure = SaveFigureGTK3
- backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3
- backend_tools.ToolSetCursor = SetCursorGTK3
- backend_tools.ToolRubberband = RubberbandGTK3
- backend_tools.ToolHelp = HelpGTK3
- backend_tools.ToolCopyToClipboard = ToolCopyToClipboardGTK3
- Toolbar = ToolbarGTK3
- @_Backend.export
- class _BackendGTK3(_Backend):
- FigureCanvas = FigureCanvasGTK3
- FigureManager = FigureManagerGTK3
- @staticmethod
- def trigger_manager_draw(manager):
- manager.canvas.draw_idle()
- @staticmethod
- def mainloop():
- if Gtk.main_level() == 0:
- Gtk.main()
|