1 | /* |
2 | * Copyright (c) 2020 Alexander Mikhaylenko <alexm@gnome.org> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU Lesser General Public License as published by |
6 | * the Free Software Foundation; either version 2 of the License, or (at your |
7 | * option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, but |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
11 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
12 | * License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public License |
15 | * along with this program; if not, write to the Free Software Foundation, |
16 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
17 | */ |
18 | |
19 | #include "config.h" |
20 | |
21 | #include "gtkwindowhandle.h" |
22 | |
23 | #include "gtkactionmuxerprivate.h" |
24 | #include "gtkbinlayout.h" |
25 | #include "gtkbox.h" |
26 | #include "gtkbuildable.h" |
27 | #include "gtkdragsourceprivate.h" |
28 | #include "gtkgestureclick.h" |
29 | #include "gtkgesturedrag.h" |
30 | #include "gtkgestureprivate.h" |
31 | #include "gtkintl.h" |
32 | #include "gtkmodelbuttonprivate.h" |
33 | #include "gtknative.h" |
34 | #include "gtkpopovermenuprivate.h" |
35 | #include "gtkprivate.h" |
36 | #include "gtkseparator.h" |
37 | #include "gtkwidgetprivate.h" |
38 | |
39 | |
40 | /** |
41 | * GtkWindowHandle: |
42 | * |
43 | * `GtkWindowHandle` is a titlebar area widget. |
44 | * |
45 | * When added into a window, it can be dragged to move the window, and handles |
46 | * right click, double click and middle click as expected of a titlebar. |
47 | * |
48 | * # CSS nodes |
49 | * |
50 | * `GtkWindowHandle` has a single CSS node with the name `windowhandle`. |
51 | * |
52 | * # Accessibility |
53 | * |
54 | * `GtkWindowHandle` uses the %GTK_ACCESSIBLE_ROLE_GROUP role. |
55 | */ |
56 | |
57 | struct _GtkWindowHandle { |
58 | GtkWidget parent_instance; |
59 | |
60 | GtkGesture *click_gesture; |
61 | GtkGesture *drag_gesture; |
62 | |
63 | GtkWidget *child; |
64 | GtkWidget *fallback_menu; |
65 | }; |
66 | |
67 | enum { |
68 | PROP_0, |
69 | PROP_CHILD, |
70 | LAST_PROP |
71 | }; |
72 | |
73 | static GParamSpec *props[LAST_PROP] = { NULL, }; |
74 | |
75 | static void gtk_window_handle_buildable_iface_init (GtkBuildableIface *iface); |
76 | |
77 | G_DEFINE_TYPE_WITH_CODE (GtkWindowHandle, gtk_window_handle, GTK_TYPE_WIDGET, |
78 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_window_handle_buildable_iface_init)) |
79 | |
80 | static void |
81 | lower_window (GtkWindowHandle *self) |
82 | { |
83 | GdkSurface *surface = |
84 | gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (self))); |
85 | |
86 | gdk_toplevel_lower (toplevel: GDK_TOPLEVEL (ptr: surface)); |
87 | } |
88 | |
89 | static GtkWindow * |
90 | get_window (GtkWindowHandle *self) |
91 | { |
92 | GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self)); |
93 | |
94 | if (GTK_IS_WINDOW (root)) |
95 | return GTK_WINDOW (root); |
96 | |
97 | return NULL; |
98 | } |
99 | |
100 | static void |
101 | restore_window_clicked (GtkModelButton *button, |
102 | GtkWindowHandle *self) |
103 | { |
104 | GtkWindow *window = get_window (self); |
105 | |
106 | if (!window) |
107 | return; |
108 | |
109 | if (gtk_window_is_maximized (window)) |
110 | gtk_window_unmaximize (window); |
111 | } |
112 | |
113 | static void |
114 | minimize_window_clicked (GtkModelButton *button, |
115 | GtkWindowHandle *self) |
116 | { |
117 | GtkWindow *window = get_window (self); |
118 | |
119 | if (!window) |
120 | return; |
121 | |
122 | /* Turns out, we can't minimize a maximized window */ |
123 | if (gtk_window_is_maximized (window)) |
124 | gtk_window_unmaximize (window); |
125 | |
126 | gtk_window_minimize (window); |
127 | } |
128 | |
129 | static void |
130 | maximize_window_clicked (GtkModelButton *button, |
131 | GtkWindowHandle *self) |
132 | { |
133 | GtkWindow *window = get_window (self); |
134 | |
135 | if (window) |
136 | gtk_window_maximize (window); |
137 | } |
138 | |
139 | static void |
140 | close_window_clicked (GtkModelButton *button, |
141 | GtkWindowHandle *self) |
142 | { |
143 | GtkWindow *window = get_window (self); |
144 | |
145 | if (window) |
146 | gtk_window_close (window); |
147 | } |
148 | |
149 | static void |
150 | (GtkPopover *popover, |
151 | GtkWindowHandle *self) |
152 | { |
153 | g_clear_pointer (&self->fallback_menu, gtk_widget_unparent); |
154 | } |
155 | |
156 | static void |
157 | (GtkWindowHandle *self, |
158 | GdkEvent *event) |
159 | { |
160 | GdkRectangle rect = { 0, 0, 1, 1 }; |
161 | GdkDevice *device; |
162 | GdkSeat *seat; |
163 | GtkWidget *box, *; |
164 | GtkWindow *window; |
165 | gboolean maximized, resizable, deletable; |
166 | |
167 | g_clear_pointer (&self->fallback_menu, gtk_widget_unparent); |
168 | |
169 | window = get_window (self); |
170 | |
171 | if (window) |
172 | { |
173 | maximized = gtk_window_is_maximized (window); |
174 | resizable = gtk_window_get_resizable (window); |
175 | deletable = gtk_window_get_deletable (window); |
176 | } |
177 | else |
178 | { |
179 | maximized = FALSE; |
180 | resizable = FALSE; |
181 | deletable = FALSE; |
182 | } |
183 | |
184 | self->fallback_menu = gtk_popover_menu_new (); |
185 | gtk_widget_set_parent (widget: self->fallback_menu, GTK_WIDGET (self)); |
186 | |
187 | gtk_popover_set_has_arrow (GTK_POPOVER (self->fallback_menu), FALSE); |
188 | gtk_widget_set_halign (widget: self->fallback_menu, align: GTK_ALIGN_START); |
189 | |
190 | |
191 | device = gdk_event_get_device (event); |
192 | seat = gdk_event_get_seat (event); |
193 | |
194 | if (device == gdk_seat_get_keyboard (seat)) |
195 | device = gdk_seat_get_pointer (seat); |
196 | |
197 | if (device) |
198 | { |
199 | GtkNative *native; |
200 | GdkSurface *surface; |
201 | double px, py; |
202 | double nx, ny; |
203 | |
204 | native = gtk_widget_get_native (GTK_WIDGET (self)); |
205 | surface = gtk_native_get_surface (self: native); |
206 | gdk_surface_get_device_position (surface, device, x: &px, y: &py, NULL); |
207 | gtk_native_get_surface_transform (self: native, x: &nx, y: &ny); |
208 | |
209 | gtk_widget_translate_coordinates (GTK_WIDGET (gtk_widget_get_native (GTK_WIDGET (self))), |
210 | GTK_WIDGET (self), |
211 | src_x: px - nx, src_y: py - ny, |
212 | dest_x: &px, dest_y: &py); |
213 | rect.x = px; |
214 | rect.y = py; |
215 | } |
216 | |
217 | gtk_popover_set_pointing_to (GTK_POPOVER (self->fallback_menu), rect: &rect); |
218 | |
219 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
220 | gtk_popover_menu_add_submenu (GTK_POPOVER_MENU (self->fallback_menu), submenu: box, name: "main" ); |
221 | |
222 | menuitem = gtk_model_button_new (); |
223 | g_object_set (object: menuitem, first_property_name: "text" , _("Restore" ), NULL); |
224 | gtk_widget_set_sensitive (widget: menuitem, sensitive: maximized && resizable); |
225 | g_signal_connect (G_OBJECT (menuitem), "clicked" , |
226 | G_CALLBACK (restore_window_clicked), self); |
227 | gtk_box_append (GTK_BOX (box), child: menuitem); |
228 | |
229 | menuitem = gtk_model_button_new (); |
230 | g_object_set (object: menuitem, first_property_name: "text" , _("Minimize" ), NULL); |
231 | g_signal_connect (G_OBJECT (menuitem), "clicked" , |
232 | G_CALLBACK (minimize_window_clicked), self); |
233 | gtk_box_append (GTK_BOX (box), child: menuitem); |
234 | |
235 | menuitem = gtk_model_button_new (); |
236 | g_object_set (object: menuitem, first_property_name: "text" , _("Maximize" ), NULL); |
237 | gtk_widget_set_sensitive (widget: menuitem, sensitive: resizable && !maximized); |
238 | g_signal_connect (G_OBJECT (menuitem), "clicked" , |
239 | G_CALLBACK (maximize_window_clicked), self); |
240 | gtk_box_append (GTK_BOX (box), child: menuitem); |
241 | |
242 | menuitem = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL); |
243 | gtk_box_append (GTK_BOX (box), child: menuitem); |
244 | |
245 | menuitem = gtk_model_button_new (); |
246 | g_object_set (object: menuitem, first_property_name: "text" , _("Close" ), NULL); |
247 | gtk_widget_set_sensitive (widget: menuitem, sensitive: deletable); |
248 | g_signal_connect (G_OBJECT (menuitem), "clicked" , |
249 | G_CALLBACK (close_window_clicked), self); |
250 | gtk_box_append (GTK_BOX (box), child: menuitem); |
251 | |
252 | g_signal_connect (self->fallback_menu, "closed" , |
253 | G_CALLBACK (popup_menu_closed), self); |
254 | gtk_popover_popup (GTK_POPOVER (self->fallback_menu)); |
255 | } |
256 | |
257 | static void |
258 | (GtkWindowHandle *self, |
259 | GdkEvent *event) |
260 | { |
261 | GdkSurface *surface = |
262 | gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (self))); |
263 | |
264 | if (!gdk_toplevel_show_window_menu (toplevel: GDK_TOPLEVEL (ptr: surface), event)) |
265 | do_popup_fallback (self, event); |
266 | } |
267 | |
268 | static gboolean |
269 | perform_titlebar_action_fallback (GtkWindowHandle *self, |
270 | GdkEvent *event, |
271 | GdkTitlebarGesture gesture) |
272 | { |
273 | GtkSettings *settings; |
274 | char *action = NULL; |
275 | gboolean retval = TRUE; |
276 | |
277 | settings = gtk_widget_get_settings (GTK_WIDGET (self)); |
278 | switch (gesture) |
279 | { |
280 | case GDK_TITLEBAR_GESTURE_DOUBLE_CLICK: |
281 | g_object_get (object: settings, first_property_name: "gtk-titlebar-double-click" , &action, NULL); |
282 | break; |
283 | case GDK_TITLEBAR_GESTURE_MIDDLE_CLICK: |
284 | g_object_get (object: settings, first_property_name: "gtk-titlebar-middle-click" , &action, NULL); |
285 | break; |
286 | case GDK_TITLEBAR_GESTURE_RIGHT_CLICK: |
287 | g_object_get (object: settings, first_property_name: "gtk-titlebar-right-click" , &action, NULL); |
288 | break; |
289 | default: |
290 | break; |
291 | } |
292 | |
293 | if (action == NULL) |
294 | retval = FALSE; |
295 | else if (g_str_equal (v1: action, v2: "none" )) |
296 | retval = FALSE; |
297 | /* treat all maximization variants the same */ |
298 | else if (g_str_has_prefix (str: action, prefix: "toggle-maximize" )) |
299 | gtk_widget_activate_action (GTK_WIDGET (self), |
300 | name: "window.toggle-maximized" , |
301 | NULL); |
302 | else if (g_str_equal (v1: action, v2: "lower" )) |
303 | lower_window (self); |
304 | else if (g_str_equal (v1: action, v2: "minimize" )) |
305 | gtk_widget_activate_action (GTK_WIDGET (self), |
306 | name: "window.minimize" , |
307 | NULL); |
308 | else if (g_str_equal (v1: action, v2: "menu" )) |
309 | do_popup (self, event); |
310 | else |
311 | { |
312 | g_warning ("Unsupported titlebar action %s" , action); |
313 | retval = FALSE; |
314 | } |
315 | |
316 | g_free (mem: action); |
317 | |
318 | return retval; |
319 | } |
320 | |
321 | static gboolean |
322 | perform_titlebar_action (GtkWindowHandle *self, |
323 | GdkEvent *event, |
324 | guint button, |
325 | int n_press) |
326 | { |
327 | GdkSurface *surface = |
328 | gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (self))); |
329 | GdkTitlebarGesture gesture; |
330 | |
331 | switch (button) |
332 | { |
333 | case GDK_BUTTON_PRIMARY: |
334 | if (n_press == 2) |
335 | gesture = GDK_TITLEBAR_GESTURE_DOUBLE_CLICK; |
336 | else |
337 | return FALSE; |
338 | break; |
339 | case GDK_BUTTON_MIDDLE: |
340 | gesture = GDK_TITLEBAR_GESTURE_MIDDLE_CLICK; |
341 | break; |
342 | case GDK_BUTTON_SECONDARY: |
343 | gesture = GDK_TITLEBAR_GESTURE_RIGHT_CLICK; |
344 | break; |
345 | default: |
346 | return FALSE; |
347 | } |
348 | |
349 | if (gdk_toplevel_titlebar_gesture (toplevel: GDK_TOPLEVEL (ptr: surface), gesture)) |
350 | return TRUE; |
351 | |
352 | return perform_titlebar_action_fallback (self, event, gesture); |
353 | } |
354 | |
355 | static void |
356 | click_gesture_pressed_cb (GtkGestureClick *gesture, |
357 | int n_press, |
358 | double x, |
359 | double y, |
360 | GtkWindowHandle *self) |
361 | { |
362 | GtkWidget *widget; |
363 | GdkEventSequence *sequence; |
364 | GdkEvent *event; |
365 | guint button; |
366 | |
367 | widget = GTK_WIDGET (self); |
368 | sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
369 | button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); |
370 | event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); |
371 | |
372 | if (!event) |
373 | return; |
374 | |
375 | if (n_press > 1) |
376 | gtk_gesture_set_state (gesture: self->drag_gesture, state: GTK_EVENT_SEQUENCE_DENIED); |
377 | |
378 | if (gdk_display_device_is_grabbed (display: gtk_widget_get_display (widget), |
379 | device: gtk_gesture_get_device (GTK_GESTURE (gesture)))) |
380 | { |
381 | gtk_gesture_set_state (gesture: self->drag_gesture, state: GTK_EVENT_SEQUENCE_DENIED); |
382 | return; |
383 | } |
384 | |
385 | switch (button) |
386 | { |
387 | case GDK_BUTTON_PRIMARY: |
388 | if (n_press == 2) |
389 | { |
390 | perform_titlebar_action (self, event, button, n_press); |
391 | gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), |
392 | sequence, state: GTK_EVENT_SEQUENCE_CLAIMED); |
393 | } |
394 | break; |
395 | |
396 | case GDK_BUTTON_SECONDARY: |
397 | if (perform_titlebar_action (self, event, button, n_press)) |
398 | gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), |
399 | sequence, state: GTK_EVENT_SEQUENCE_CLAIMED); |
400 | |
401 | gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture)); |
402 | gtk_event_controller_reset (GTK_EVENT_CONTROLLER (self->drag_gesture)); |
403 | break; |
404 | |
405 | case GDK_BUTTON_MIDDLE: |
406 | if (perform_titlebar_action (self, event, button, n_press)) |
407 | gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), |
408 | sequence, state: GTK_EVENT_SEQUENCE_CLAIMED); |
409 | break; |
410 | |
411 | default: |
412 | return; |
413 | } |
414 | } |
415 | |
416 | static void |
417 | drag_gesture_update_cb (GtkGestureDrag *gesture, |
418 | double offset_x, |
419 | double offset_y, |
420 | GtkWindowHandle *self) |
421 | { |
422 | if (gtk_drag_check_threshold_double (GTK_WIDGET (self), start_x: 0, start_y: 0, current_x: offset_x, current_y: offset_y)) |
423 | { |
424 | double start_x, start_y; |
425 | double native_x, native_y; |
426 | double window_x, window_y; |
427 | GtkNative *native; |
428 | GdkSurface *surface; |
429 | |
430 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
431 | |
432 | gtk_gesture_drag_get_start_point (gesture, x: &start_x, y: &start_y); |
433 | |
434 | native = gtk_widget_get_native (GTK_WIDGET (self)); |
435 | |
436 | gtk_widget_translate_coordinates (GTK_WIDGET (self), |
437 | GTK_WIDGET (native), |
438 | src_x: start_x, src_y: start_y, |
439 | dest_x: &window_x, dest_y: &window_y); |
440 | |
441 | gtk_native_get_surface_transform (self: native, x: &native_x, y: &native_y); |
442 | window_x += native_x; |
443 | window_y += native_y; |
444 | |
445 | surface = gtk_native_get_surface (self: native); |
446 | if (GDK_IS_TOPLEVEL (ptr: surface)) |
447 | gdk_toplevel_begin_move (toplevel: GDK_TOPLEVEL (ptr: surface), |
448 | device: gtk_gesture_get_device (GTK_GESTURE (gesture)), |
449 | button: gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)), |
450 | x: window_x, y: window_y, |
451 | timestamp: gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture))); |
452 | |
453 | gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture)); |
454 | gtk_event_controller_reset (GTK_EVENT_CONTROLLER (self->click_gesture)); |
455 | } |
456 | } |
457 | |
458 | static void |
459 | gtk_window_handle_unrealize (GtkWidget *widget) |
460 | { |
461 | GtkWindowHandle *self = GTK_WINDOW_HANDLE (ptr: widget); |
462 | |
463 | g_clear_pointer (&self->fallback_menu, gtk_widget_unparent); |
464 | |
465 | GTK_WIDGET_CLASS (gtk_window_handle_parent_class)->unrealize (widget); |
466 | } |
467 | |
468 | static void |
469 | gtk_window_handle_dispose (GObject *object) |
470 | { |
471 | GtkWindowHandle *self = GTK_WINDOW_HANDLE (ptr: object); |
472 | |
473 | g_clear_pointer (&self->child, gtk_widget_unparent); |
474 | |
475 | G_OBJECT_CLASS (gtk_window_handle_parent_class)->dispose (object); |
476 | } |
477 | |
478 | static void |
479 | gtk_window_handle_get_property (GObject *object, |
480 | guint prop_id, |
481 | GValue *value, |
482 | GParamSpec *pspec) |
483 | { |
484 | GtkWindowHandle *self = GTK_WINDOW_HANDLE (ptr: object); |
485 | |
486 | switch (prop_id) |
487 | { |
488 | case PROP_CHILD: |
489 | g_value_set_object (value, v_object: gtk_window_handle_get_child (self)); |
490 | break; |
491 | |
492 | default: |
493 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
494 | break; |
495 | } |
496 | } |
497 | |
498 | static void |
499 | gtk_window_handle_set_property (GObject *object, |
500 | guint prop_id, |
501 | const GValue *value, |
502 | GParamSpec *pspec) |
503 | { |
504 | GtkWindowHandle *self = GTK_WINDOW_HANDLE (ptr: object); |
505 | |
506 | switch (prop_id) |
507 | { |
508 | case PROP_CHILD: |
509 | gtk_window_handle_set_child (self, child: g_value_get_object (value)); |
510 | break; |
511 | |
512 | default: |
513 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
514 | break; |
515 | } |
516 | } |
517 | |
518 | static void |
519 | gtk_window_handle_class_init (GtkWindowHandleClass *klass) |
520 | { |
521 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
522 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
523 | |
524 | object_class->dispose = gtk_window_handle_dispose; |
525 | object_class->get_property = gtk_window_handle_get_property; |
526 | object_class->set_property = gtk_window_handle_set_property; |
527 | |
528 | widget_class->unrealize = gtk_window_handle_unrealize; |
529 | |
530 | /** |
531 | * GtkWindowHandle:child: (attributes org.gtk.Property.get=gtk_window_handle_get_child org.gtk.Property.set=gtk_window_handle_set_child) |
532 | * |
533 | * The child widget. |
534 | */ |
535 | props[PROP_CHILD] = |
536 | g_param_spec_object (name: "child" , |
537 | P_("Child" ), |
538 | P_("The child widget" ), |
539 | GTK_TYPE_WIDGET, |
540 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
541 | |
542 | g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props); |
543 | |
544 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
545 | gtk_widget_class_set_css_name (widget_class, I_("windowhandle" )); |
546 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_GROUP); |
547 | } |
548 | |
549 | static void |
550 | gtk_window_handle_init (GtkWindowHandle *self) |
551 | { |
552 | self->click_gesture = gtk_gesture_click_new (); |
553 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->click_gesture), button: 0); |
554 | g_signal_connect (self->click_gesture, "pressed" , |
555 | G_CALLBACK (click_gesture_pressed_cb), self); |
556 | gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->click_gesture)); |
557 | |
558 | self->drag_gesture = gtk_gesture_drag_new (); |
559 | g_signal_connect (self->drag_gesture, "drag-update" , |
560 | G_CALLBACK (drag_gesture_update_cb), self); |
561 | gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->drag_gesture)); |
562 | } |
563 | |
564 | static GtkBuildableIface *parent_buildable_iface; |
565 | |
566 | static void |
567 | gtk_window_handle_buildable_add_child (GtkBuildable *buildable, |
568 | GtkBuilder *builder, |
569 | GObject *child, |
570 | const char *type) |
571 | { |
572 | if (GTK_IS_WIDGET (child)) |
573 | gtk_window_handle_set_child (self: GTK_WINDOW_HANDLE (ptr: buildable), GTK_WIDGET (child)); |
574 | else |
575 | parent_buildable_iface->add_child (buildable, builder, child, type); |
576 | } |
577 | |
578 | static void |
579 | gtk_window_handle_buildable_iface_init (GtkBuildableIface *iface) |
580 | { |
581 | parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface); |
582 | |
583 | iface->add_child = gtk_window_handle_buildable_add_child; |
584 | } |
585 | |
586 | /** |
587 | * gtk_window_handle_new: |
588 | * |
589 | * Creates a new `GtkWindowHandle`. |
590 | * |
591 | * Returns: a new `GtkWindowHandle`. |
592 | */ |
593 | GtkWidget * |
594 | gtk_window_handle_new (void) |
595 | { |
596 | return g_object_new (GTK_TYPE_WINDOW_HANDLE, NULL); |
597 | } |
598 | |
599 | /** |
600 | * gtk_window_handle_get_child: (attributes org.gtk.Method.get_property=child) |
601 | * @self: a `GtkWindowHandle` |
602 | * |
603 | * Gets the child widget of @self. |
604 | * |
605 | * Returns: (nullable) (transfer none): the child widget of @self |
606 | */ |
607 | GtkWidget * |
608 | gtk_window_handle_get_child (GtkWindowHandle *self) |
609 | { |
610 | g_return_val_if_fail (GTK_IS_WINDOW_HANDLE (self), NULL); |
611 | |
612 | return self->child; |
613 | } |
614 | |
615 | /** |
616 | * gtk_window_handle_set_child: (attributes org.gtk.Method.set_property=child) |
617 | * @self: a `GtkWindowHandle` |
618 | * @child: (nullable): the child widget |
619 | * |
620 | * Sets the child widget of @self. |
621 | */ |
622 | void |
623 | gtk_window_handle_set_child (GtkWindowHandle *self, |
624 | GtkWidget *child) |
625 | { |
626 | g_return_if_fail (GTK_IS_WINDOW_HANDLE (self)); |
627 | g_return_if_fail (child == NULL || GTK_IS_WIDGET (child)); |
628 | |
629 | if (self->child == child) |
630 | return; |
631 | |
632 | g_clear_pointer (&self->child, gtk_widget_unparent); |
633 | |
634 | self->child = child; |
635 | |
636 | if (child) |
637 | gtk_widget_set_parent (widget: child, GTK_WIDGET (self)); |
638 | |
639 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_CHILD]); |
640 | } |
641 | |