Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b86ebb1

Browse files
committed
Refactoring the WindowGTK classes as part 1 of the larger MEP27 refactor, splitting up previous PRs into smaller easier to review chunks.
1 parent a833d99 commit b86ebb1

File tree

5 files changed

+343
-57
lines changed

5 files changed

+343
-57
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1686,7 +1686,59 @@ def save_args_and_handle_sigint(*args):
16861686
old_sigint_handler(*handler_args)
16871687

16881688

1689-
class FigureCanvasBase:
1689+
class ExpandableBase(object):
1690+
"""
1691+
Base class for GUI elements that can expand to fill the area given to them
1692+
by the encapsulating container (e.g. the main window).
1693+
At the moment this class does not do anything apart from mark such classes,
1694+
but this may well change at a later date, PRs welcome.
1695+
"""
1696+
pass
1697+
1698+
class FlowBase(object):
1699+
"""
1700+
Base mixin class for all GUI elements that can flow, aka laid out in
1701+
different directions.
1702+
The MPL window class deals with the manipulation of this mixin, so users
1703+
don't actually need to interact with this class.
1704+
Classes the implement this class must override the _update_flow method.
1705+
"""
1706+
flow_types = ['horizontal', 'vertical']
1707+
1708+
def __init__(self, flow='horizontal', flow_locked=False, **kwargs):
1709+
super(FlowBase, self).__init__(**kwargs)
1710+
self.flow_locked = flow_locked
1711+
self.flow = flow
1712+
1713+
@property
1714+
def flow(self):
1715+
"""
1716+
The direction of flow, one of the strings in `flow_type`.
1717+
"""
1718+
return FlowBase.flow_types[self._flow]
1719+
1720+
@flow.setter
1721+
def flow(self, flow):
1722+
if self.flow_locked:
1723+
return
1724+
1725+
try:
1726+
self._flow = FlowBase.flow_types.index(flow)
1727+
except ValueError:
1728+
raise ValueError('Flow (%s), not in list %s' % (flow, FlowBase.flow_types))
1729+
1730+
self._update_flow()
1731+
1732+
def _update_flow(self):
1733+
"""
1734+
Classes that extend FlowBase must override this method.
1735+
You can use the internal property self._flow whereby
1736+
flow_types[self._flow] gives the current flow.
1737+
"""
1738+
raise NotImplementedError
1739+
1740+
1741+
class FigureCanvasBase(ExpandableBase):
16901742
"""
16911743
The canvas the figure renders into.
16921744
@@ -2581,6 +2633,120 @@ class NonGuiException(Exception):
25812633
pass
25822634

25832635

2636+
class WindowEvent(object):
2637+
def __init__(self, name, window):
2638+
self.name = name
2639+
self.window = window
2640+
2641+
2642+
class WindowBase(cbook.EventEmitter):
2643+
"""The base class to show a window on screen.
2644+
2645+
Parameters
2646+
----------
2647+
title : str
2648+
The title of the window.
2649+
"""
2650+
2651+
def __init__(self, title, **kwargs):
2652+
super(WindowBase, self).__init__(**kwargs)
2653+
2654+
def show(self):
2655+
"""
2656+
For GUI backends, show the figure window and redraw.
2657+
For non-GUI backends, raise an exception to be caught
2658+
by :meth:`~matplotlib.figure.Figure.show`, for an
2659+
optional warning.
2660+
"""
2661+
raise NonGuiException()
2662+
2663+
def destroy(self):
2664+
"""Destroys the window"""
2665+
pass
2666+
2667+
def set_fullscreen(self, fullscreen):
2668+
"""Whether to show the window fullscreen or not, GUI only.
2669+
2670+
Parameters
2671+
----------
2672+
fullscreen : bool
2673+
True for yes, False for no.
2674+
"""
2675+
pass
2676+
2677+
def set_default_size(self, width, height):
2678+
"""Sets the default size of the window, defaults to a simple resize.
2679+
2680+
Parameters
2681+
----------
2682+
width : int
2683+
The default width (in pixels) of the window.
2684+
height : int
2685+
The default height (in pixels) of the window.
2686+
"""
2687+
self.resize(width, height)
2688+
2689+
def resize(self, width, height):
2690+
""""For gui backends, resizes the window.
2691+
2692+
Parameters
2693+
----------
2694+
width : int
2695+
The new width (in pixels) for the window.
2696+
height : int
2697+
The new height (in pixels) for the window.
2698+
"""
2699+
pass
2700+
2701+
def get_window_title(self):
2702+
"""
2703+
Get the title text of the window containing the figure.
2704+
Return None for non-GUI backends (e.g., a PS backend).
2705+
2706+
Returns
2707+
-------
2708+
str : The window's title.
2709+
"""
2710+
return 'image'
2711+
2712+
def set_window_title(self, title):
2713+
"""
2714+
Set the title text of the window containing the figure. Note that
2715+
this has no effect for non-GUI backends (e.g., a PS backend).
2716+
2717+
Parameters
2718+
----------
2719+
title : str
2720+
The title of the window.
2721+
"""
2722+
pass
2723+
2724+
def add_element(self, element, place):
2725+
""" Adds a gui widget to the window.
2726+
This has no effect for non-GUI backends and properties only apply
2727+
to those backends that support them, or have a suitable workaround.
2728+
2729+
Parameters
2730+
----------
2731+
element : A gui element.
2732+
The element to add to the window
2733+
place : string
2734+
The location to place the element, either compass points north,
2735+
east, south, west, or center.
2736+
"""
2737+
pass
2738+
2739+
def destroy_event(self, *args):
2740+
"""Fires this event when the window wants to destroy itself.
2741+
2742+
Note this method should hook up to the backend's internal window's
2743+
close event.
2744+
"""
2745+
s = 'window_destroy_event'
2746+
event = WindowEvent(s, self)
2747+
self._callbacks.process(s, event)
2748+
2749+
25842750
class FigureManagerBase:
25852751
"""
25862752
A backend-independent abstraction of a figure container and controller.

lib/matplotlib/backends/_backend_gtk.py

Lines changed: 112 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from matplotlib._pylab_helpers import Gcf
1111
from matplotlib.backend_bases import (
1212
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
13-
TimerBase)
13+
TimerBase, WindowBase, ExpandableBase)
1414
from matplotlib.backend_tools import Cursors
1515

1616
import gi
@@ -118,6 +118,109 @@ class _FigureCanvasGTK(FigureCanvasBase):
118118
_timer_cls = TimerGTK
119119

120120

121+
_flow = [Gtk.Orientation.HORIZONTAL, Gtk.Orientation.VERTICAL]
122+
123+
124+
class _WindowGTK(WindowBase, Gtk.Window):
125+
# Must be implemented in GTK3/GTK4 backends:
126+
# * _add_element - to add an widget to a container
127+
# * _setup_signals
128+
# * _get_self - a method to ensure that we have been fully initialised
129+
130+
def __init__(self, title, **kwargs):
131+
super().__init__(title=title, **kwargs)
132+
133+
self.set_window_title(title)
134+
135+
self._layout = {}
136+
self._setup_box('_outer', Gtk.Orientation.VERTICAL, False, None)
137+
self._setup_box('north', Gtk.Orientation.VERTICAL, False, '_outer')
138+
self._setup_box('_middle', Gtk.Orientation.HORIZONTAL, True, '_outer')
139+
self._setup_box('south', Gtk.Orientation.VERTICAL, False, '_outer')
140+
141+
self._setup_box('west', Gtk.Orientation.HORIZONTAL, False, '_middle')
142+
self._setup_box('center', Gtk.Orientation.VERTICAL, True, '_middle')
143+
self._setup_box('east', Gtk.Orientation.HORIZONTAL, False, '_middle')
144+
145+
self.set_child(self._layout['_outer'])
146+
147+
self._setup_signals()
148+
149+
def _setup_box(self, name, orientation, grow, parent):
150+
self._layout[name] = Gtk.Box(orientation=orientation)
151+
if parent:
152+
self._add_element(self._layout[parent], self._layout[name], True, grow)
153+
self._layout[name].show()
154+
155+
def add_element(self, element, place):
156+
element.show()
157+
158+
# Get the flow of the element (the opposite of the container)
159+
flow_index = not _flow.index(self._layout[place].get_orientation())
160+
flow = _flow[flow_index]
161+
separator = Gtk.Separator(orientation=flow)
162+
separator.show()
163+
164+
try:
165+
element.flow = element.flow_types[flow_index]
166+
except AttributeError:
167+
pass
168+
169+
# Determine if this element should fill all the space given to it
170+
expand = isinstance(element, ExpandableBase)
171+
172+
if place in ['north', 'west', 'center']:
173+
to_start = True
174+
elif place in ['south', 'east']:
175+
to_start = False
176+
else:
177+
raise KeyError('Unknown value for place, %s' % place)
178+
179+
self._add_element(self._layout[place], element, to_start, expand)
180+
self._add_element(self._layout[place], separator, to_start, False)
181+
182+
h = 0
183+
for e in [element, separator]:
184+
min_size, nat_size = e.get_preferred_size()
185+
h += nat_size.height
186+
187+
return h
188+
189+
def set_default_size(self, width, height):
190+
Gtk.Window.set_default_size(self, width, height)
191+
192+
def show(self):
193+
# show the window
194+
Gtk.Window.show(self)
195+
if mpl.rcParams["figure.raise_window"]:
196+
if self._get_self():
197+
self.present()
198+
else:
199+
# If this is called by a callback early during init,
200+
# self.window (a GtkWindow) may not have an associated
201+
# low-level GdkWindow (on GTK3) or GdkSurface (on GTK4) yet,
202+
# and present() would crash.
203+
_api.warn_external("Cannot raise window yet to be setup")
204+
205+
def destroy(self):
206+
Gtk.Window.destroy(self)
207+
208+
def set_fullscreen(self, fullscreen):
209+
if fullscreen:
210+
self.fullscreen()
211+
else:
212+
self.unfullscreen()
213+
214+
def get_window_title(self):
215+
return self.get_title()
216+
217+
def set_window_title(self, title):
218+
self.set_title(title)
219+
220+
def resize(self, width, height):
221+
Gtk.Window.resize(self, width, height)
222+
223+
121224
class _FigureManagerGTK(FigureManagerBase):
122225
"""
123226
Attributes
@@ -135,51 +238,22 @@ class _FigureManagerGTK(FigureManagerBase):
135238
"""
136239

137240
def __init__(self, canvas, num):
138-
self._gtk_ver = gtk_ver = Gtk.get_major_version()
139-
140241
app = _create_application()
141-
self.window = Gtk.Window()
242+
self.window = self._window_class('Matplotlib Figure Manager')
142243
app.add_window(self.window)
143244
super().__init__(canvas, num)
144245

145-
if gtk_ver == 3:
146-
self.window.set_wmclass("matplotlib", "Matplotlib")
147-
icon_ext = "png" if sys.platform == "win32" else "svg"
148-
self.window.set_icon_from_file(
149-
str(cbook._get_data_path(f"images/matplotlib.{icon_ext}")))
150-
151-
self.vbox = Gtk.Box()
152-
self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
153-
154-
if gtk_ver == 3:
155-
self.window.add(self.vbox)
156-
self.vbox.show()
157-
self.canvas.show()
158-
self.vbox.pack_start(self.canvas, True, True, 0)
159-
elif gtk_ver == 4:
160-
self.window.set_child(self.vbox)
161-
self.vbox.prepend(self.canvas)
162-
163-
# calculate size for window
246+
self.window.add_element(self.canvas, 'center')
164247
w, h = self.canvas.get_width_height()
165248

166-
if self.toolbar is not None:
167-
if gtk_ver == 3:
168-
self.toolbar.show()
169-
self.vbox.pack_end(self.toolbar, False, False, 0)
170-
elif gtk_ver == 4:
171-
sw = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
172-
sw.set_child(self.toolbar)
173-
self.vbox.append(sw)
174-
min_size, nat_size = self.toolbar.get_preferred_size()
175-
h += nat_size.height
249+
if self.toolbar:
250+
h += self.window.add_element(self.toolbar, 'south') # put in ScrolledWindow in GTK4?
176251

177252
self.window.set_default_size(w, h)
178253

179254
self._destroying = False
180-
self.window.connect("destroy", lambda *args: Gcf.destroy(self))
181-
self.window.connect({3: "delete_event", 4: "close-request"}[gtk_ver],
182-
lambda *args: Gcf.destroy(self))
255+
self.window.mpl_connect('window_destroy_event', lambda *args: Gcf.destroy(self))
256+
183257
if mpl.is_interactive():
184258
self.window.show()
185259
self.canvas.draw_idle()
@@ -220,24 +294,9 @@ def show(self):
220294
# show the figure window
221295
self.window.show()
222296
self.canvas.draw()
223-
if mpl.rcParams["figure.raise_window"]:
224-
meth_name = {3: "get_window", 4: "get_surface"}[self._gtk_ver]
225-
if getattr(self.window, meth_name)():
226-
self.window.present()
227-
else:
228-
# If this is called by a callback early during init,
229-
# self.window (a GtkWindow) may not have an associated
230-
# low-level GdkWindow (on GTK3) or GdkSurface (on GTK4) yet,
231-
# and present() would crash.
232-
_api.warn_external("Cannot raise window yet to be setup")
233297

234298
def full_screen_toggle(self):
235-
is_fullscreen = {
236-
3: lambda w: (w.get_window().get_state()
237-
& Gdk.WindowState.FULLSCREEN),
238-
4: lambda w: w.is_fullscreen(),
239-
}[self._gtk_ver]
240-
if is_fullscreen(self.window):
299+
if self.window.is_fullscreen():
241300
self.window.unfullscreen()
242301
else:
243302
self.window.fullscreen()
@@ -255,7 +314,7 @@ def resize(self, width, height):
255314
min_size, nat_size = self.toolbar.get_preferred_size()
256315
height += nat_size.height
257316
canvas_size = self.canvas.get_allocation()
258-
if self._gtk_ver >= 4 or canvas_size.width == canvas_size.height == 1:
317+
if canvas_size.width == canvas_size.height == 1:
259318
# A canvas size of (1, 1) cannot exist in most cases, because
260319
# window decorations would prevent such a small window. This call
261320
# must be before the window has been mapped and widgets have been

0 commit comments

Comments
 (0)