8000 Merge pull request #22079 from anntzer/gtkman · matplotlib/matplotlib@4359e3a · GitHub
[go: up one dir, main page]

Skip to content

Commit 4359e3a

Browse files
authored
Merge pull request #22079 from anntzer/gtkman
Share FigureManager class between gtk3 and gtk4.
2 parents f23a8de + bb3c039 commit 4359e3a

File tree

3 files changed

+147
-234
lines changed

3 files changed

+147
-234
lines changed

lib/matplotlib/backends/_backend_gtk.py

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
"""
44

55
import logging
6+
import sys
67

78
import matplotlib as mpl
89
from matplotlib import _api, backend_tools, cbook
9-
from matplotlib.backend_bases import _Backend, NavigationToolbar2, TimerBase
10+
from matplotlib._pylab_helpers import Gcf
11+
from matplotlib.backend_bases import (
12+
_Backend, FigureManagerBase, NavigationToolbar2, TimerBase)
1013
from matplotlib.backend_tools import Cursors
1114

1215
# The GTK3/GTK4 backends will have already called `gi.require_version` to set
1316
# the desired GTK.
14-
from gi.repository import Gio, GLib, Gtk
17+
from gi.repository import Gdk, Gio, GLib, Gtk
1518

1619

1720
_log = logging.getLogger(__name__)
@@ -109,6 +112,134 @@ def _on_timer(self):
109112
return False
110113

111114

115+
class _FigureManagerGTK(FigureManagerBase):
116+
"""
117+
Attributes
118+
----------
119+
canvas : `FigureCanvas`
120+
The FigureCanvas instance
121+
num : int or str
122+
The Figure number
123+
toolbar : Gtk.Toolbar or Gtk.Box
124+
The toolbar
125+
vbox : Gtk.VBox
126+
The Gtk.VBox containing the canvas and toolbar
127+
window : Gtk.Window
128+
The Gtk.Window
129+
"""
130+
131+
def __init__(self, canvas, num):
132+
self._gtk_ver = gtk_ver = Gtk.get_major_version()
133+
134+
app = _create_application()
135+
self.window = Gtk.Window()
136+
app.add_window(self.window)
137+
super().__init__(canvas, num)
138+
139+
if gtk_ver == 3:
140+
self.window.set_wmclass("matplotlib", "Matplotlib")
141+
icon_ext = "png" if sys.platform == "win32" else "svg"
142+
self.window.set_icon_from_file(
143+
str(cbook._get_data_path(f"images/matplotlib.{icon_ext}")))
144+
145+
self.vbox = Gtk.Box()
146+
self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
147+
148+
if gtk_ver == 3:
149+
self.window.add(self.vbox)
150+
self.vbox.show()
151+
self.canvas.show()
152+
self.vbox.pack_start(self.canvas, True, True, 0)
153+
elif gtk_ver == 4:
154+
self.window.set_child(self.vbox)
155+
self.vbox.prepend(self.canvas)
156+
157+
# calculate size for window
158+
w, h = self.canvas.get_width_height()
159+
160+
if self.toolbar is not None:
161+
if gtk_ver == 3:
162+
self.toolbar.show()
163+
self.vbox.pack_end(self.toolbar, False, False, 0)
164+
elif gtk_ver == 4:
165+
sw = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
166+
sw.set_child(self.toolbar)
167+
self.vbox.append(sw)
168+
min_size, nat_size = self.toolbar.get_preferred_size()
169+
h += nat_size.height
170+
171+
self.window.set_default_size(w, h)
172+
173+
self._destroying = False
174+
self.window.connect("destroy", lambda *args: Gcf.destroy(self))
175+
self.window.connect({3: "delete_event", 4: "close-request"}[gtk_ver],
176+
lambda *args: Gcf.destroy(self))
177+
if mpl.is_interactive():
178+
self.window.show()
179+
self.canvas.draw_idle()
180+
181+
self.canvas.grab_focus()
182+
183+
def destroy(self, *args):
184+
if self._destroying:
185+
# Otherwise, this can be called twice when the user presses 'q',
186+
# which calls Gcf.destroy(self), then this destroy(), then triggers
187+
# Gcf.destroy(self) once again via
188+
# `connect("destroy", lambda *args: Gcf.destroy(self))`.
189+
return
190+
self._destroying = True
191+
self.window.destroy()
192+
self.canvas.destroy()
193+
194+
def show(self):
195+
# show the figure window
196+
self.window.show()
197+
self.canvas.draw()
198+
if mpl.rcParams["figure.raise_window"]:
199+
meth_name = {3: "get_window", 4: "get_surface"}[self._gtk_ver]
200+
if getattr(self.window, meth_name)():
201+
self.window.present()
202+
else:
203+
# If this is called by a callback early during init,
204+
# self.window (a GtkWindow) may not have an associated
205+
# low-level GdkWindow (on GTK3) or GdkSurface (on GTK4) yet,
206+
# and present() would crash.
207+
_api.warn_external("Cannot raise window yet to be setup")
208+
209+
def full_screen_toggle(self):
210+
is_fullscreen = {
211+
3: lambda w: (w.get_window().get_state()
212+
& Gdk.WindowState.FULLSCREEN),
213+
4: lambda w: w.is_fullscreen(),
214+
}[self._gtk_ver]
215+
if is_fullscreen(self.window):
216+
self.window.unfullscreen()
217+
else:
218+
self.window.fullscreen()
219+
220+
def get_window_title(self):
221+
return self.window.get_title()
222+
223+
def set_window_title(self, title):
224+
self.window.set_title(title)
225+
226+
def resize(self, width, height):
227+
width = int(width / self.canvas.device_pixel_ratio)
228+
height = int(height / self.canvas.device_pixel_ratio)
229+
if self.toolbar:
230+
toolbar_size = self.toolbar.size_request()
231+
height += toolbar_size.height
232+
canvas_size = self.canvas.get_allocation()
233+
if self._gtk_ver >= 4 or canvas_size.width == canvas_size.height == 1:
234+
# A canvas size of (1, 1) cannot exist in most cases, because
235+
# window decorations would prevent such a small window. This call
236+
# must be before the window has been mapped and widgets have been
237+
# sized, so just change the window's starting size.
238+
self.window.set_default_size(width, height)
239+
else:
240+
self.window.resize(width, height)
241+
242+
112243
class _NavigationToolbar2GTK(NavigationToolbar2):
113244
# Must be implemented in GTK3/GTK4 backends:
114245
# * __init__

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 7 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
import matplotlib as mpl
88
from matplotlib import _api, backend_tools, cbook
9-
from matplotlib._pylab_helpers import Gcf
10-
from matplotlib.backend_bases import (
11-
FigureCanvasBase, FigureManagerBase, ToolContainerBase)
9+
from matplotlib.backend_bases import FigureCanvasBase, ToolContainerBase
1210
from matplotlib.backend_tools import Cursors
1311
from matplotlib.figure import Figure
1412

@@ -29,7 +27,7 @@
2927
from gi.repository import Gio, GLib, GObject, Gtk, Gdk
3028
from . import _backend_gtk
3129
from ._backend_gtk import (
32-
backend_version, _BackendGTK, _NavigationToolbar2GTK,
30+
backend_version, _BackendGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
3331
TimerGTK as TimerGTK3,
3432
)
3533

@@ -293,130 +291,6 @@ def flush_events(self):
293291
context.iteration(True)
294292

295293

296-
class FigureManagerGTK3(FigureManagerBase):
297-
"""
298-
Attributes
299-
----------
300-
canvas : `FigureCanvas`
301-
The FigureCanvas instance
302-
num : int or str
303-
The Figure number
304-
toolbar : Gtk.Toolbar
305-
The toolbar
306-
vbox : Gtk.VBox
307-
The Gtk.VBox containing the canvas and toolbar
308-
window : Gtk.Window
309-
The Gtk.Window
310-
"""
311-
312-
def __init__(self, canvas, num):
313-
app = _backend_gtk._create_application()
314-
self.window = Gtk.Window()
315-
app.add_window(self.window)
316-
super().__init__(canvas, num)
317-
318-
self.window.set_wmclass("matplotlib", "Matplotlib")
319-
icon_ext = "png" if sys.platform == "win32" else "svg"
320-
self.window.set_icon_from_file(
321-
str(cbook._get_data_path(f"images/matplotlib.{icon_ext}")))
322-
323-
self.vbox = Gtk.Box()
324-
self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
325-
self.window.add(self.vbox)
326-
self.vbox.show()
327-
328-
self.canvas.show()
329-
330-
self.vbox.pack_start(self.canvas, True, True, 0)
331-
# calculate size for window
332-
w, h = self.canvas.get_width_height()
333-
334-
if self.toolbar is not None:
335-
self.toolbar.show()
336-
self.vbox.pack_end(self.toolbar, False, False, 0)
337-
min_size, nat_size = self.toolbar.get_preferred_size()
338-
h += nat_size.height
339-
340-
self.window.set_default_size(w, h)
341-
342-
self._destroying = False
343-
self.window.connect("destroy", lambda *args: Gcf.destroy(self))
344-
self.window.connect("delete_event", lambda *args: Gcf.destroy(self))
345-
if mpl.is_interactive():
346-
self.window.show()
347-
self.canvas.draw_idle()
348-
349-
self.canvas.grab_focus()
350-
351-
def destroy(self, *args):
352-
if self._destroying:
353-
# Otherwise, this can be called twice when the user presses 'q',
354-
# which calls Gcf.destroy(self), then this destroy(), then triggers
355-
# Gcf.destroy(self) once again via
356-
# `connect("destroy", lambda *args: Gcf.destroy(self))`.
357-
return
358-
self._destroying = True
359-
self.vbox.destroy()
360-
self.window.destroy()
361-
self.canvas.destroy()
362-
if self.toolbar:
363-
self.toolbar.destroy()
364-
365-
def show(self):
366-
# show the figure window
367-
self.window.show()
368-
self.canvas.draw()
369-
if mpl.rcParams['figure.raise_window']:
370-
if self.window.get_window():
371-
self.window.present()
372-
else:
373-
# If this is called by a callback early during init,
374-
# self.window (a GtkWindow) may not have an associated
375-
# low-level GdkWindow (self.window.get_window()) yet, and
376-
# present() would crash.
377-
_api.warn_external("Cannot raise window yet to be setup")
378-
379-
def full_screen_toggle(self):
380-
if self.window.get_window().get_state() & Gdk.WindowState.FULLSCREEN:
381-
self.window.unfullscreen()
382-
else:
383-
self.window.fullscreen()
384-
385-
def _get_toolbar(self):
386-
# must be inited after the window, drawingArea and figure
387-
# attrs are set
388-
if mpl.rcParams['toolbar'] == 'toolbar2':
389-
toolbar = NavigationToolbar2GTK3(self.canvas)
390-
elif mpl.rcParams['toolbar'] == 'toolmanager':
391-
toolbar = ToolbarGTK3(self.toolmanager)
392-
else:
393-
toolbar = None
394-
return toolbar
395-
396-
def get_window_title(self):
397-
return self.window.get_title()
398-
399-
def set_window_title(self, title):
400-
self.window.set_title(title)
401-
402-
def resize(self, width, height):
403-
"""Set the canvas size in pixels."""
404-
width = int(width / self.canvas.device_pixel_ratio)
405-
height = int(height / self.canvas.device_pixel_ratio)
406-
if self.toolbar:
407-
toolbar_size = self.toolbar.size_request()
408-
height += toolbar_size.height
409-
canvas_size = self.canvas.get_allocation()
410-
if canvas_size.width == canvas_size.height == 1:
411-
# A canvas size of (1, 1) cannot exist in most cases, because
412-
# window decorations would prevent such a small window. This call
413-
# must be before the window has been mapped and widgets have been
414-
# sized, so just change the window's starting size.
415-
self.window.set_default_size(width, height)
416-
else:
417-
self.window.resize(width, height)
418-
419-
420294
class NavigationToolbar2GTK3(_NavigationToolbar2GTK, Gtk.Toolbar):
421295
@_api.delete_parameter("3.6", "window")
422296
def __init__(self, canvas, window=None):
@@ -730,8 +604,11 @@ def error_msg_gtk(msg, parent=None):
730604
FigureCanvasGTK3, _backend_gtk.ConfigureSubplotsGTK)
731605
backend_tools._register_tool_class(
732606
FigureCanvasGTK3, _backend_gtk.RubberbandGTK)
733-
FigureManagerGTK3._toolbar2_class = NavigationToolbar2GTK3
734-
FigureManagerGTK3._toolmanager_toolbar_class = ToolbarGTK3
607+
608+
609+
class FigureManagerGTK3(_FigureManagerGTK):
610+
_toolbar2_class = NavigationToolbar2GTK3
611+
_toolmanager_toolbar_class = ToolbarGTK3
735612

736613

737614
@_BackendGTK.export

0 commit comments

Comments
 (0)
0