10
10
from matplotlib ._pylab_helpers import Gcf
11
11
from matplotlib .backend_bases import (
12
12
_Backend , FigureCanvasBase , FigureManagerBase , NavigationToolbar2 ,
13
- TimerBase )
13
+ TimerBase , WindowBase , ExpandableBase )
14
14
from matplotlib .backend_tools import Cursors
15
15
16
16
import gi
@@ -118,6 +118,109 @@ class _FigureCanvasGTK(FigureCanvasBase):
118
118
_timer_cls = TimerGTK
119
119
120
120
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
+
121
224
class _FigureManagerGTK (FigureManagerBase ):
122
225
"""
123
226
Attributes
@@ -135,51 +238,22 @@ class _FigureManagerGTK(FigureManagerBase):
135
238
"""
136
239
137
240
def __init__ (self , canvas , num ):
138
- self ._gtk_ver = gtk_ver = Gtk .get_major_version ()
139
-
140
241
app = _create_application ()
141
- self .window = Gtk . Window ( )
242
+ self .window = self . _window_class ( 'Matplotlib Figure Manager' )
142
243
app .add_window (self .window )
143
244
super ().__init__ (canvas , num )
144
245
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' )
164
247
w , h = self .canvas .get_width_height ()
165
248
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?
176
251
177
252
self .window .set_default_size (w , h )
178
253
179
254
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
+
183
257
if mpl .is_interactive ():
184
258
self .window .show ()
185
259
self .canvas .draw_idle ()
@@ -220,24 +294,9 @@ def show(self):
220
294
# show the figure window
221
295
self .window .show ()
222
296
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" )
233
297
234
298
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 ():
241
300
self .window .unfullscreen ()
242
301
else :
243
302
self .window .fullscreen ()
@@ -255,7 +314,7 @@ def resize(self, width, height):
255
314
min_size , nat_size = self .toolbar .get_preferred_size ()
256
315
height += nat_size .height
257
316
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 :
259
318
# A canvas size of (1, 1) cannot exist in most cases, because
260
319
# window decorations would prevent such a small window. This call
261
320
# must be before the window has been mapped and widgets have been
0 commit comments