new ip
This commit is contained in:
@@ -0,0 +1,617 @@
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import matplotlib as mpl
|
||||
from matplotlib import _api, backend_tools, cbook
|
||||
from matplotlib.backend_bases import (
|
||||
ToolContainerBase, CloseEvent, KeyEvent, LocationEvent, MouseEvent,
|
||||
ResizeEvent)
|
||||
|
||||
try:
|
||||
import gi
|
||||
except ImportError as err:
|
||||
raise ImportError("The GTK3 backends require PyGObject") from err
|
||||
|
||||
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(e) from e
|
||||
|
||||
from gi.repository import Gio, GLib, GObject, Gtk, Gdk
|
||||
from . import _backend_gtk
|
||||
from ._backend_gtk import ( # noqa: F401 # pylint: disable=W0611
|
||||
_BackendGTK, _FigureCanvasGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
|
||||
TimerGTK as TimerGTK3,
|
||||
)
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@_api.caching_module_getattr # module-level deprecations
|
||||
class __getattr__:
|
||||
icon_filename = _api.deprecated("3.6", obj_type="")(property(
|
||||
lambda self:
|
||||
"matplotlib.png" if sys.platform == "win32" else "matplotlib.svg"))
|
||||
window_icon = _api.deprecated("3.6", obj_type="")(property(
|
||||
lambda self:
|
||||
str(cbook._get_data_path("images", __getattr__("icon_filename")))))
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def _mpl_to_gtk_cursor(mpl_cursor):
|
||||
return Gdk.Cursor.new_from_name(
|
||||
Gdk.Display.get_default(),
|
||||
_backend_gtk.mpl_to_gtk_cursor_name(mpl_cursor))
|
||||
|
||||
|
||||
class FigureCanvasGTK3(_FigureCanvasGTK, Gtk.DrawingArea):
|
||||
required_interactive_framework = "gtk3"
|
||||
manager_class = _api.classproperty(lambda cls: FigureManagerGTK3)
|
||||
# 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.SCROLL_MASK)
|
||||
|
||||
def __init__(self, figure=None):
|
||||
super().__init__(figure=figure)
|
||||
|
||||
self._idle_draw_id = 0
|
||||
self._rubberband_rect = 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('screen-changed', self._update_device_pixel_ratio)
|
||||
self.connect('notify::scale-factor', self._update_device_pixel_ratio)
|
||||
self.connect('draw', self.on_draw_event)
|
||||
self.connect('draw', self._post_draw)
|
||||
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('enter_notify_event', self.enter_notify_event)
|
||||
self.connect('leave_notify_event', self.leave_notify_event)
|
||||
self.connect('size_allocate', self.size_allocate)
|
||||
|
||||
self.set_events(self.__class__.event_mask)
|
||||
|
||||
self.set_can_focus(True)
|
||||
|
||||
css = Gtk.CssProvider()
|
||||
css.load_from_data(b".matplotlib-canvas { background-color: white; }")
|
||||
style_ctx = self.get_style_context()
|
||||
style_ctx.add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
style_ctx.add_class("matplotlib-canvas")
|
||||
|
||||
def destroy(self):
|
||||
CloseEvent("close_event", self)._process()
|
||||
|
||||
def set_cursor(self, cursor):
|
||||
# docstring inherited
|
||||
window = self.get_property("window")
|
||||
if window is not None:
|
||||
window.set_cursor(_mpl_to_gtk_cursor(cursor))
|
||||
context = GLib.MainContext.default()
|
||||
context.iteration(True)
|
||||
|
||||
def _mpl_coords(self, event=None):
|
||||
"""
|
||||
Convert the position of a GTK event, or of the current cursor position
|
||||
if *event* is None, to Matplotlib coordinates.
|
||||
|
||||
GTK use logical pixels, but the figure is scaled to physical pixels for
|
||||
rendering. Transform to physical pixels so that all of the down-stream
|
||||
transforms work as expected.
|
||||
|
||||
Also, the origin is different and needs to be corrected.
|
||||
"""
|
||||
if event is None:
|
||||
window = self.get_window()
|
||||
t, x, y, state = window.get_device_position(
|
||||
window.get_display().get_device_manager().get_client_pointer())
|
||||
else:
|
||||
x, y = event.x, event.y
|
||||
x = x * self.device_pixel_ratio
|
||||
# flip y so y=0 is bottom of canvas
|
||||
y = self.figure.bbox.height - y * self.device_pixel_ratio
|
||||
return x, y
|
||||
|
||||
def scroll_event(self, widget, event):
|
||||
step = 1 if event.direction == Gdk.ScrollDirection.UP else -1
|
||||
MouseEvent("scroll_event", self,
|
||||
*self._mpl_coords(event), step=step,
|
||||
modifiers=self._mpl_modifiers(event.state),
|
||||
guiEvent=event)._process()
|
||||
return False # finish event propagation?
|
||||
|
||||
def button_press_event(self, widget, event):
|
||||
MouseEvent("button_press_event", self,
|
||||
*self._mpl_coords(event), event.button,
|
||||
modifiers=self._mpl_modifiers(event.state),
|
||||
guiEvent=event)._process()
|
||||
return False # finish event propagation?
|
||||
|
||||
def button_release_event(self, widget, event):
|
||||
MouseEvent("button_release_event", self,
|
||||
*self._mpl_coords(event), event.button,
|
||||
modifiers=self._mpl_modifiers(event.state),
|
||||
guiEvent=event)._process()
|
||||
return False # finish event propagation?
|
||||
|
||||
def key_press_event(self, widget, event):
|
||||
KeyEvent("key_press_event", self,
|
||||
self._get_key(event), *self._mpl_coords(),
|
||||
guiEvent=event)._process()
|
||||
return True # stop event propagation
|
||||
|
||||
def key_release_event(self, widget, event):
|
||||
KeyEvent("key_release_event", self,
|
||||
self._get_key(event), *self._mpl_coords(),
|
||||
guiEvent=event)._process()
|
||||
return True # stop event propagation
|
||||
|
||||
def motion_notify_event(self, widget, event):
|
||||
MouseEvent("motion_notify_event", self, *self._mpl_coords(event),
|
||||
modifiers=self._mpl_modifiers(event.state),
|
||||
guiEvent=event)._process()
|
||||
return False # finish event propagation?
|
||||
|
||||
def enter_notify_event(self, widget, event):
|
||||
gtk_mods = Gdk.Keymap.get_for_display(
|
||||
self.get_display()).get_modifier_state()
|
||||
LocationEvent("figure_enter_event", self, *self._mpl_coords(event),
|
||||
modifiers=self._mpl_modifiers(gtk_mods),
|
||||
guiEvent=event)._process()
|
||||
|
||||
def leave_notify_event(self, widget, event):
|
||||
gtk_mods = Gdk.Keymap.get_for_display(
|
||||
self.get_display()).get_modifier_state()
|
||||
LocationEvent("figure_leave_event", self, *self._mpl_coords(event),
|
||||
modifiers=self._mpl_modifiers(gtk_mods),
|
||||
guiEvent=event)._process()
|
||||
|
||||
def size_allocate(self, widget, allocation):
|
||||
dpival = self.figure.dpi
|
||||
winch = allocation.width * self.device_pixel_ratio / dpival
|
||||
hinch = allocation.height * self.device_pixel_ratio / dpival
|
||||
self.figure.set_size_inches(winch, hinch, forward=False)
|
||||
ResizeEvent("resize_event", self)._process()
|
||||
self.draw_idle()
|
||||
|
||||
@staticmethod
|
||||
def _mpl_modifiers(event_state, *, exclude=None):
|
||||
modifiers = [
|
||||
("ctrl", Gdk.ModifierType.CONTROL_MASK, "control"),
|
||||
("alt", Gdk.ModifierType.MOD1_MASK, "alt"),
|
||||
("shift", Gdk.ModifierType.SHIFT_MASK, "shift"),
|
||||
("super", Gdk.ModifierType.MOD4_MASK, "super"),
|
||||
]
|
||||
return [name for name, mask, key in modifiers
|
||||
if exclude != key and event_state & mask]
|
||||
|
||||
def _get_key(self, event):
|
||||
unikey = chr(Gdk.keyval_to_unicode(event.keyval))
|
||||
key = cbook._unikey_or_keysym_to_mplkey(
|
||||
unikey, Gdk.keyval_name(event.keyval))
|
||||
mods = self._mpl_modifiers(event.state, exclude=key)
|
||||
if "shift" in mods and unikey.isprintable():
|
||||
mods.remove("shift")
|
||||
return "+".join([*mods, key])
|
||||
|
||||
def _update_device_pixel_ratio(self, *args, **kwargs):
|
||||
# We need to be careful in cases with mixed resolution displays if
|
||||
# device_pixel_ratio changes.
|
||||
if self._set_device_pixel_ratio(self.get_scale_factor()):
|
||||
# The easiest way to resize the canvas is to emit a resize event
|
||||
# since we implement all the logic for resizing the canvas for that
|
||||
# event.
|
||||
self.queue_resize()
|
||||
self.queue_draw()
|
||||
|
||||
def configure_event(self, widget, event):
|
||||
if widget.get_property("window") is None:
|
||||
return
|
||||
w = event.width * self.device_pixel_ratio
|
||||
h = event.height * self.device_pixel_ratio
|
||||
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 _draw_rubberband(self, rect):
|
||||
self._rubberband_rect = rect
|
||||
# TODO: Only update the rubberband area.
|
||||
self.queue_draw()
|
||||
|
||||
def _post_draw(self, widget, ctx):
|
||||
if self._rubberband_rect is None:
|
||||
return
|
||||
|
||||
x0, y0, w, h = (dim / self.device_pixel_ratio
|
||||
for dim in self._rubberband_rect)
|
||||
x1 = x0 + w
|
||||
y1 = y0 + h
|
||||
|
||||
# Draw the lines from x0, y0 towards x1, y1 so that the
|
||||
# dashes don't "jump" when moving the zoom box.
|
||||
ctx.move_to(x0, y0)
|
||||
ctx.line_to(x0, y1)
|
||||
ctx.move_to(x0, y0)
|
||||
ctx.line_to(x1, y0)
|
||||
ctx.move_to(x0, y1)
|
||||
ctx.line_to(x1, y1)
|
||||
ctx.move_to(x1, y0)
|
||||
ctx.line_to(x1, y1)
|
||||
|
||||
ctx.set_antialias(1)
|
||||
ctx.set_line_width(1)
|
||||
ctx.set_dash((3, 3), 0)
|
||||
ctx.set_source_rgb(0, 0, 0)
|
||||
ctx.stroke_preserve()
|
||||
|
||||
ctx.set_dash((3, 3), 3)
|
||||
ctx.set_source_rgb(1, 1, 1)
|
||||
ctx.stroke()
|
||||
|
||||
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 flush_events(self):
|
||||
# docstring inherited
|
||||
context = GLib.MainContext.default()
|
||||
while context.pending():
|
||||
context.iteration(True)
|
||||
|
||||
|
||||
class NavigationToolbar2GTK3(_NavigationToolbar2GTK, Gtk.Toolbar):
|
||||
@_api.delete_parameter("3.6", "window")
|
||||
def __init__(self, canvas, window=None):
|
||||
self._win = window
|
||||
GObject.GObject.__init__(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.new_from_gicon(
|
||||
Gio.Icon.new_for_string(
|
||||
str(cbook._get_data_path('images',
|
||||
f'{image_file}-symbolic.svg'))),
|
||||
Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self._gtk_ids[text] = button = (
|
||||
Gtk.ToggleToolButton() if callback in ['zoom', 'pan'] else
|
||||
Gtk.ToolButton())
|
||||
button.set_label(text)
|
||||
button.set_icon_widget(image)
|
||||
# Save the handler id, so that we can block it as needed.
|
||||
button._signal_handler = button.connect(
|
||||
'clicked', getattr(self, callback))
|
||||
button.set_tooltip_text(tooltip_text)
|
||||
self.insert(button, -1)
|
||||
|
||||
# This filler item ensures the toolbar is always at least two text
|
||||
# lines high. Otherwise the canvas gets redrawn as the mouse hovers
|
||||
# over images because those use two-line messages which resize the
|
||||
# toolbar.
|
||||
toolitem = Gtk.ToolItem()
|
||||
self.insert(toolitem, -1)
|
||||
label = Gtk.Label()
|
||||
label.set_markup(
|
||||
'<small>\N{NO-BREAK SPACE}\n\N{NO-BREAK SPACE}</small>')
|
||||
toolitem.set_expand(True) # Push real message to the right.
|
||||
toolitem.add(label)
|
||||
|
||||
toolitem = Gtk.ToolItem()
|
||||
self.insert(toolitem, -1)
|
||||
self.message = Gtk.Label()
|
||||
self.message.set_justify(Gtk.Justification.RIGHT)
|
||||
toolitem.add(self.message)
|
||||
|
||||
self.show_all()
|
||||
|
||||
_NavigationToolbar2GTK.__init__(self, canvas)
|
||||
|
||||
win = _api.deprecated("3.6")(property(lambda self: self._win))
|
||||
|
||||
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(f'*.{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(f'.{fmt}')))
|
||||
|
||||
dialog.set_current_folder(mpl.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.OK:
|
||||
return
|
||||
# Save dir for next time, unless empty str (which means use cwd).
|
||||
if mpl.rcParams['savefig.directory']:
|
||||
mpl.rcParams['savefig.directory'] = os.path.dirname(fname)
|
||||
try:
|
||||
self.canvas.figure.savefig(fname, format=fmt)
|
||||
except Exception as e:
|
||||
dialog = Gtk.MessageDialog(
|
||||
parent=self.canvas.get_toplevel(), message_format=str(e),
|
||||
type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK)
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
|
||||
class ToolbarGTK3(ToolContainerBase, Gtk.Box):
|
||||
_icon_extension = '-symbolic.svg'
|
||||
|
||||
def __init__(self, toolmanager):
|
||||
ToolContainerBase.__init__(self, toolmanager)
|
||||
Gtk.Box.__init__(self)
|
||||
self.set_property('orientation', Gtk.Orientation.HORIZONTAL)
|
||||
self._message = Gtk.Label()
|
||||
self._message.set_justify(Gtk.Justification.RIGHT)
|
||||
self.pack_end(self._message, False, False, 0)
|
||||
self.show_all()
|
||||
self._groups = {}
|
||||
self._toolitems = {}
|
||||
|
||||
def add_toolitem(self, name, group, position, image_file, description,
|
||||
toggle):
|
||||
if toggle:
|
||||
button = Gtk.ToggleToolButton()
|
||||
else:
|
||||
button = Gtk.ToolButton()
|
||||
button.set_label(name)
|
||||
|
||||
if image_file is not None:
|
||||
image = Gtk.Image.new_from_gicon(
|
||||
Gio.Icon.new_for_string(image_file),
|
||||
Gtk.IconSize.LARGE_TOOLBAR)
|
||||
button.set_icon_widget(image)
|
||||
|
||||
if position is None:
|
||||
position = -1
|
||||
|
||||
self._add_button(button, group, position)
|
||||
signal = button.connect('clicked', self._call_tool, name)
|
||||
button.set_tooltip_text(description)
|
||||
button.show_all()
|
||||
self._toolitems.setdefault(name, [])
|
||||
self._toolitems[name].append((button, 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.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(f'{name} not in toolbar', 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.pack_start(sep, False, True, 0)
|
||||
sep.show_all()
|
||||
|
||||
def set_message(self, s):
|
||||
self._message.set_label(s)
|
||||
|
||||
|
||||
@backend_tools._register_tool_class(FigureCanvasGTK3)
|
||||
class SaveFigureGTK3(backend_tools.SaveFigureBase):
|
||||
def trigger(self, *args, **kwargs):
|
||||
NavigationToolbar2GTK3.save_figure(
|
||||
self._make_classic_style_pseudo_toolbar())
|
||||
|
||||
|
||||
@backend_tools._register_tool_class(FigureCanvasGTK3)
|
||||
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 _is_valid_shortcut(self, key):
|
||||
"""
|
||||
Check for a valid shortcut to be displayed.
|
||||
|
||||
- GTK will never send 'cmd+' (see `FigureCanvasGTK3._get_key`).
|
||||
- The shortcut window only shows keyboard shortcuts, not mouse buttons.
|
||||
"""
|
||||
return 'cmd+' not in key and not key.startswith('MouseButton.')
|
||||
|
||||
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)
|
||||
if self._is_valid_shortcut(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()
|
||||
|
||||
|
||||
@backend_tools._register_tool_class(FigureCanvasGTK3)
|
||||
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)
|
||||
|
||||
|
||||
@_api.deprecated("3.6")
|
||||
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()
|
||||
|
||||
|
||||
Toolbar = ToolbarGTK3
|
||||
backend_tools._register_tool_class(
|
||||
FigureCanvasGTK3, _backend_gtk.ConfigureSubplotsGTK)
|
||||
backend_tools._register_tool_class(
|
||||
FigureCanvasGTK3, _backend_gtk.RubberbandGTK)
|
||||
|
||||
|
||||
class FigureManagerGTK3(_FigureManagerGTK):
|
||||
_toolbar2_class = NavigationToolbar2GTK3
|
||||
_toolmanager_toolbar_class = ToolbarGTK3
|
||||
|
||||
|
||||
@_BackendGTK.export
|
||||
class _BackendGTK3(_BackendGTK):
|
||||
FigureCanvas = FigureCanvasGTK3
|
||||
FigureManager = FigureManagerGTK3
|
||||
Reference in New Issue
Block a user