1 /*
2  * Copyright (c) 2013 Red Hat, Inc.
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 
20 #include "config.h"
21 
22 #include "gtkheaderbar.h"
23 #include "gtkheaderbarprivate.h"
24 #include "gtkintl.h"
25 #include "gtkprivate.h"
26 #include "gtktypebuiltins.h"
27 #include "gtkwidgetprivate.h"
28 #include "gtkcssnodeprivate.h"
29 #include "gtkcsscustomgadgetprivate.h"
30 #include "gtkwindowprivate.h"
31 #include "gtkwidgetprivate.h"
32 #include "gtkcontainerprivate.h"
33 #include "a11y/gtkheaderbaraccessible.h"
34 
35 #include <string.h>
36 
37 /**
38  * SECTION:gtkheaderbar
39  * @Short_description: A box with a centered child
40  * @Title: GtkHeaderBar
41  * @See_also: #GtkBox, #GtkActionBar
42  *
43  * GtkHeaderBar is similar to a horizontal #GtkBox. It allows children to
44  * be placed at the start or the end. In addition, it allows a title and
45  * subtitle to be displayed. The title will be centered with respect to
46  * the width of the box, even if the children at either side take up
47  * different amounts of space. The height of the titlebar will be
48  * set to provide sufficient space for the subtitle, even if none is
49  * currently set. If a subtitle is not needed, the space reservation
50  * can be turned off with gtk_header_bar_set_has_subtitle().
51  *
52  * GtkHeaderBar can add typical window frame controls, such as minimize,
53  * maximize and close buttons, or the window icon.
54  *
55  * For these reasons, GtkHeaderBar is the natural choice for use as the custom
56  * titlebar widget of a #GtkWindow (see gtk_window_set_titlebar()), as it gives
57  * features typical of titlebars while allowing the addition of child widgets.
58  */
59 
60 #define DEFAULT_SPACING 6
61 #define MIN_TITLE_CHARS 5
62 
63 struct _GtkHeaderBarPrivate
64 {
65   gchar *title;
66   gchar *subtitle;
67   GtkWidget *title_label;
68   GtkWidget *subtitle_label;
69   GtkWidget *label_box;
70   GtkWidget *label_sizing_box;
71   GtkWidget *subtitle_sizing_label;
72   GtkWidget *custom_title;
73   gint spacing;
74   gboolean has_subtitle;
75 
76   GList *children;
77 
78   gboolean shows_wm_decorations;
79   gchar *decoration_layout;
80   gboolean decoration_layout_set;
81 
82   GtkWidget *titlebar_start_box;
83   GtkWidget *titlebar_end_box;
84 
85   GtkWidget *titlebar_start_separator;
86   GtkWidget *titlebar_end_separator;
87 
88   GtkWidget *titlebar_icon;
89 
90   GtkCssGadget *gadget;
91 };
92 
93 typedef struct _Child Child;
94 struct _Child
95 {
96   GtkWidget *widget;
97   GtkPackType pack_type;
98 };
99 
100 enum {
101   PROP_0,
102   PROP_TITLE,
103   PROP_SUBTITLE,
104   PROP_HAS_SUBTITLE,
105   PROP_CUSTOM_TITLE,
106   PROP_SPACING,
107   PROP_SHOW_CLOSE_BUTTON,
108   PROP_DECORATION_LAYOUT,
109   PROP_DECORATION_LAYOUT_SET,
110   LAST_PROP
111 };
112 
113 enum {
114   CHILD_PROP_0,
115   CHILD_PROP_PACK_TYPE,
116   CHILD_PROP_POSITION
117 };
118 
119 static GParamSpec *header_bar_props[LAST_PROP] = { NULL, };
120 
121 static void gtk_header_bar_buildable_init (GtkBuildableIface *iface);
122 
123 G_DEFINE_TYPE_WITH_CODE (GtkHeaderBar, gtk_header_bar, GTK_TYPE_CONTAINER,
124                          G_ADD_PRIVATE (GtkHeaderBar)
125                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
126                                                 gtk_header_bar_buildable_init));
127 
128 static void
init_sizing_box(GtkHeaderBar * bar)129 init_sizing_box (GtkHeaderBar *bar)
130 {
131   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
132   GtkWidget *w;
133   GtkStyleContext *context;
134 
135   /* We use this box to always request size for the two labels (title
136    * and subtitle) as if they were always visible, but then allocate
137    * the real label box with its actual size, to keep it center-aligned
138    * in case we have only the title.
139    */
140   w = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
141   gtk_widget_show (w);
142   priv->label_sizing_box = g_object_ref_sink (w);
143 
144   w = gtk_label_new (NULL);
145   gtk_widget_show (w);
146   context = gtk_widget_get_style_context (w);
147   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TITLE);
148   gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0);
149   gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
150   gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
151   gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END);
152   gtk_label_set_width_chars (GTK_LABEL (w), MIN_TITLE_CHARS);
153 
154   w = gtk_label_new (NULL);
155   context = gtk_widget_get_style_context (w);
156   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUBTITLE);
157   gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0);
158   gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
159   gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
160   gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END);
161   gtk_widget_set_visible (w, priv->has_subtitle || (priv->subtitle && priv->subtitle[0]));
162   priv->subtitle_sizing_label = w;
163 }
164 
165 static GtkWidget *
create_title_box(const char * title,const char * subtitle,GtkWidget ** ret_title_label,GtkWidget ** ret_subtitle_label)166 create_title_box (const char *title,
167                   const char *subtitle,
168                   GtkWidget **ret_title_label,
169                   GtkWidget **ret_subtitle_label)
170 {
171   GtkWidget *label_box;
172   GtkWidget *title_label;
173   GtkWidget *subtitle_label;
174   GtkStyleContext *context;
175 
176   label_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
177   gtk_widget_set_valign (label_box, GTK_ALIGN_CENTER);
178   gtk_widget_show (label_box);
179 
180   title_label = gtk_label_new (title);
181   context = gtk_widget_get_style_context (title_label);
182   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TITLE);
183   gtk_label_set_line_wrap (GTK_LABEL (title_label), FALSE);
184   gtk_label_set_single_line_mode (GTK_LABEL (title_label), TRUE);
185   gtk_label_set_ellipsize (GTK_LABEL (title_label), PANGO_ELLIPSIZE_END);
186   gtk_box_pack_start (GTK_BOX (label_box), title_label, FALSE, FALSE, 0);
187   gtk_widget_show (title_label);
188   gtk_label_set_width_chars (GTK_LABEL (title_label), MIN_TITLE_CHARS);
189 
190   subtitle_label = gtk_label_new (subtitle);
191   context = gtk_widget_get_style_context (subtitle_label);
192   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUBTITLE);
193   gtk_label_set_line_wrap (GTK_LABEL (subtitle_label), FALSE);
194   gtk_label_set_single_line_mode (GTK_LABEL (subtitle_label), TRUE);
195   gtk_label_set_ellipsize (GTK_LABEL (subtitle_label), PANGO_ELLIPSIZE_END);
196   gtk_box_pack_start (GTK_BOX (label_box), subtitle_label, FALSE, FALSE, 0);
197   gtk_widget_set_no_show_all (subtitle_label, TRUE);
198   gtk_widget_set_visible (subtitle_label, subtitle && subtitle[0]);
199 
200   if (ret_title_label)
201     *ret_title_label = title_label;
202   if (ret_subtitle_label)
203     *ret_subtitle_label = subtitle_label;
204 
205   return label_box;
206 }
207 
208 gboolean
_gtk_header_bar_update_window_icon(GtkHeaderBar * bar,GtkWindow * window)209 _gtk_header_bar_update_window_icon (GtkHeaderBar *bar,
210                                     GtkWindow    *window)
211 {
212   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
213   GdkPixbuf *pixbuf;
214   gint scale;
215 
216   if (priv->titlebar_icon == NULL)
217     return FALSE;
218 
219   scale = gtk_widget_get_scale_factor (priv->titlebar_icon);
220   if (GTK_IS_BUTTON (gtk_widget_get_parent (priv->titlebar_icon)))
221     pixbuf = gtk_window_get_icon_for_size (window, scale * 16);
222   else
223     pixbuf = gtk_window_get_icon_for_size (window, scale * 20);
224 
225   if (pixbuf)
226     {
227       cairo_surface_t *surface;
228 
229       surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, gtk_widget_get_window (priv->titlebar_icon));
230 
231       gtk_image_set_from_surface (GTK_IMAGE (priv->titlebar_icon), surface);
232       cairo_surface_destroy (surface);
233       g_object_unref (pixbuf);
234       gtk_widget_show (priv->titlebar_icon);
235 
236       return TRUE;
237     }
238 
239   return FALSE;
240 }
241 
242 static void
_gtk_header_bar_update_separator_visibility(GtkHeaderBar * bar)243 _gtk_header_bar_update_separator_visibility (GtkHeaderBar *bar)
244 {
245   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
246   gboolean have_visible_at_start = FALSE;
247   gboolean have_visible_at_end = FALSE;
248   GList *l;
249 
250   for (l = priv->children; l != NULL; l = l->next)
251     {
252       Child *child = l->data;
253 
254       if (gtk_widget_get_visible (child->widget))
255         {
256           if (child->pack_type == GTK_PACK_START)
257             have_visible_at_start = TRUE;
258           else
259             have_visible_at_end = TRUE;
260         }
261     }
262 
263   if (priv->titlebar_start_separator != NULL)
264     gtk_widget_set_visible (priv->titlebar_start_separator, have_visible_at_start);
265 
266   if (priv->titlebar_end_separator != NULL)
267     gtk_widget_set_visible (priv->titlebar_end_separator, have_visible_at_end);
268 }
269 
270 void
_gtk_header_bar_update_window_buttons(GtkHeaderBar * bar)271 _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar)
272 {
273   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
274   GtkWidget *widget = GTK_WIDGET (bar), *toplevel;
275   GtkWindow *window;
276   GtkTextDirection direction;
277   gchar *layout_desc;
278   gchar **tokens, **t;
279   gint i, j;
280   GMenuModel *menu;
281   gboolean shown_by_shell;
282   gboolean is_sovereign_window;
283 
284   toplevel = gtk_widget_get_toplevel (widget);
285   if (!gtk_widget_is_toplevel (toplevel))
286     return;
287 
288   if (priv->titlebar_start_box)
289     {
290       gtk_widget_unparent (priv->titlebar_start_box);
291       priv->titlebar_start_box = NULL;
292       priv->titlebar_start_separator = NULL;
293     }
294   if (priv->titlebar_end_box)
295     {
296       gtk_widget_unparent (priv->titlebar_end_box);
297       priv->titlebar_end_box = NULL;
298       priv->titlebar_end_separator = NULL;
299     }
300 
301   priv->titlebar_icon = NULL;
302 
303   if (!priv->shows_wm_decorations)
304     return;
305 
306   direction = gtk_widget_get_direction (widget);
307 
308   g_object_get (gtk_widget_get_settings (widget),
309                 "gtk-shell-shows-app-menu", &shown_by_shell,
310                 "gtk-decoration-layout", &layout_desc,
311                 NULL);
312 
313   if (priv->decoration_layout_set)
314     {
315       g_free (layout_desc);
316       layout_desc = g_strdup (priv->decoration_layout);
317     }
318 
319   window = GTK_WINDOW (toplevel);
320 
321   if (!shown_by_shell && gtk_window_get_application (window))
322     menu = gtk_application_get_app_menu (gtk_window_get_application (window));
323   else
324     menu = NULL;
325 
326   is_sovereign_window = (!gtk_window_get_modal (window) &&
327                           gtk_window_get_transient_for (window) == NULL &&
328                           gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL);
329 
330   tokens = g_strsplit (layout_desc, ":", 2);
331   if (tokens)
332     {
333       for (i = 0; i < 2; i++)
334         {
335           GtkWidget *box;
336           GtkWidget *separator;
337           int n_children = 0;
338 
339           if (tokens[i] == NULL)
340             break;
341 
342           t = g_strsplit (tokens[i], ",", -1);
343 
344           separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
345           gtk_widget_set_no_show_all (separator, TRUE);
346           gtk_style_context_add_class (gtk_widget_get_style_context (separator), "titlebutton");
347 
348           box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, priv->spacing);
349 
350           for (j = 0; t[j]; j++)
351             {
352               GtkWidget *button = NULL;
353               GtkWidget *image = NULL;
354               AtkObject *accessible;
355 
356               if (strcmp (t[j], "icon") == 0 &&
357                   is_sovereign_window)
358                 {
359                   button = gtk_image_new ();
360                   gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
361                   priv->titlebar_icon = button;
362                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
363                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "icon");
364                   gtk_widget_set_size_request (button, 20, 20);
365                   gtk_widget_show (button);
366 
367                   if (!_gtk_header_bar_update_window_icon (bar, window))
368                     {
369                       gtk_widget_destroy (button);
370                       priv->titlebar_icon = NULL;
371                       button = NULL;
372                     }
373                 }
374               else if (strcmp (t[j], "menu") == 0 &&
375                        menu != NULL &&
376                        is_sovereign_window)
377                 {
378                   button = gtk_menu_button_new ();
379                   gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
380                   gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), menu);
381                   gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (button), TRUE);
382                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
383                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "appmenu");
384                   image = gtk_image_new ();
385                   gtk_container_add (GTK_CONTAINER (button), image);
386                   gtk_widget_set_can_focus (button, FALSE);
387                   gtk_widget_show_all (button);
388 
389                   accessible = gtk_widget_get_accessible (button);
390                   if (GTK_IS_ACCESSIBLE (accessible))
391                     atk_object_set_name (accessible, _("Application menu"));
392 
393                   priv->titlebar_icon = image;
394                   if (!_gtk_header_bar_update_window_icon (bar, window))
395                     gtk_image_set_from_icon_name (GTK_IMAGE (priv->titlebar_icon),
396                                                   "application-x-executable-symbolic", GTK_ICON_SIZE_MENU);
397                 }
398               else if (strcmp (t[j], "minimize") == 0 &&
399                        is_sovereign_window)
400                 {
401                   button = gtk_button_new ();
402                   gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
403                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
404                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "minimize");
405                   image = gtk_image_new_from_icon_name ("window-minimize-symbolic", GTK_ICON_SIZE_MENU);
406                   g_object_set (image, "use-fallback", TRUE, NULL);
407                   gtk_container_add (GTK_CONTAINER (button), image);
408                   gtk_widget_set_can_focus (button, FALSE);
409                   gtk_widget_show_all (button);
410                   g_signal_connect_swapped (button, "clicked",
411                                             G_CALLBACK (gtk_window_iconify), window);
412 
413                   accessible = gtk_widget_get_accessible (button);
414                   if (GTK_IS_ACCESSIBLE (accessible))
415                     atk_object_set_name (accessible, _("Minimize"));
416                 }
417               else if (strcmp (t[j], "maximize") == 0 &&
418                        gtk_window_get_resizable (window) &&
419                        is_sovereign_window)
420                 {
421                   const gchar *icon_name;
422                   gboolean maximized = gtk_window_is_maximized (window);
423 
424                   icon_name = maximized ? "window-restore-symbolic" : "window-maximize-symbolic";
425                   button = gtk_button_new ();
426                   gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
427                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
428                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "maximize");
429                   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
430                   g_object_set (image, "use-fallback", TRUE, NULL);
431                   gtk_container_add (GTK_CONTAINER (button), image);
432                   gtk_widget_set_can_focus (button, FALSE);
433                   gtk_widget_show_all (button);
434                   g_signal_connect_swapped (button, "clicked",
435                                             G_CALLBACK (_gtk_window_toggle_maximized), window);
436 
437                   accessible = gtk_widget_get_accessible (button);
438                   if (GTK_IS_ACCESSIBLE (accessible))
439                     atk_object_set_name (accessible, maximized ? _("Restore") : _("Maximize"));
440                 }
441               else if (strcmp (t[j], "close") == 0 &&
442                        gtk_window_get_deletable (window))
443                 {
444                   button = gtk_button_new ();
445                   gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
446                   image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU);
447                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
448                   gtk_style_context_add_class (gtk_widget_get_style_context (button), "close");
449                   g_object_set (image, "use-fallback", TRUE, NULL);
450                   gtk_container_add (GTK_CONTAINER (button), image);
451                   gtk_widget_set_can_focus (button, FALSE);
452                   gtk_widget_show_all (button);
453                   g_signal_connect_swapped (button, "clicked",
454                                             G_CALLBACK (gtk_window_close), window);
455 
456                   accessible = gtk_widget_get_accessible (button);
457                   if (GTK_IS_ACCESSIBLE (accessible))
458                     atk_object_set_name (accessible, _("Close"));
459                 }
460 
461               if (button)
462                 {
463                   gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
464                   n_children ++;
465                 }
466             }
467           g_strfreev (t);
468 
469           if (n_children == 0)
470             {
471               g_object_ref_sink (box);
472               g_object_unref (box);
473               g_object_ref_sink (separator);
474               g_object_unref (separator);
475               continue;
476             }
477 
478           gtk_box_pack_start (GTK_BOX (box), separator, FALSE, FALSE, 0);
479           if (i == 1)
480             gtk_box_reorder_child (GTK_BOX (box), separator, 0);
481 
482           if ((direction == GTK_TEXT_DIR_LTR && i == 0) ||
483               (direction == GTK_TEXT_DIR_RTL && i == 1))
484             {
485               gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_LEFT);
486               gtk_css_node_insert_after (gtk_widget_get_css_node (GTK_WIDGET (bar)),
487                                          gtk_widget_get_css_node (box),
488                                          NULL);
489             }
490           else
491             {
492               gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_RIGHT);
493               gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (bar)),
494                                           gtk_widget_get_css_node (box),
495                                           NULL);
496             }
497 
498           gtk_widget_show (box);
499           gtk_widget_set_parent (box, GTK_WIDGET (bar));
500 
501           if (i == 0)
502             {
503               priv->titlebar_start_box = box;
504               priv->titlebar_start_separator = separator;
505             }
506           else
507             {
508               priv->titlebar_end_box = box;
509               priv->titlebar_end_separator = separator;
510             }
511         }
512       g_strfreev (tokens);
513     }
514   g_free (layout_desc);
515 
516   _gtk_header_bar_update_separator_visibility (bar);
517 }
518 
519 gboolean
_gtk_header_bar_shows_app_menu(GtkHeaderBar * bar)520 _gtk_header_bar_shows_app_menu (GtkHeaderBar *bar)
521 {
522   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
523   GtkWindow *window;
524   gchar *layout_desc;
525   gboolean ret;
526 
527   window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (bar)));
528   gtk_widget_style_get (GTK_WIDGET (window),
529                         "decoration-button-layout", &layout_desc,
530                         NULL);
531 
532   ret = priv->shows_wm_decorations &&
533         (layout_desc && strstr (layout_desc, "menu"));
534 
535   g_free (layout_desc);
536 
537   return ret;
538 }
539 
540 /* As an intended side effect, this function allows @child
541  * to be the title/label box */
542 static void
gtk_header_bar_reorder_css_node(GtkHeaderBar * bar,GtkPackType pack_type,GtkWidget * widget)543 gtk_header_bar_reorder_css_node (GtkHeaderBar *bar,
544                                  GtkPackType   pack_type,
545                                  GtkWidget    *widget)
546 {
547   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
548   GtkWidget *previous_widget;
549   GList *l;
550 
551   if (pack_type == GTK_PACK_START)
552     previous_widget = priv->titlebar_start_box;
553   else
554     previous_widget = priv->titlebar_end_box;
555 
556   for (l = priv->children; l; l = l->next)
557     {
558       Child *iter = l->data;
559 
560       if (iter->widget == widget)
561         break;
562 
563       if (iter->pack_type == pack_type)
564         previous_widget = iter->widget;
565     }
566 
567   if ((pack_type == GTK_PACK_START)
568       ^ (gtk_widget_get_direction (GTK_WIDGET (bar)) == GTK_TEXT_DIR_LTR))
569     gtk_css_node_insert_after (gtk_widget_get_css_node (GTK_WIDGET (bar)),
570                                gtk_widget_get_css_node (widget),
571                                previous_widget ? gtk_widget_get_css_node (previous_widget) : NULL);
572   else
573     gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (bar)),
574                                 gtk_widget_get_css_node (widget),
575                                 previous_widget ? gtk_widget_get_css_node (previous_widget) : NULL);
576 }
577 
578 static void
construct_label_box(GtkHeaderBar * bar)579 construct_label_box (GtkHeaderBar *bar)
580 {
581   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
582 
583   g_assert (priv->label_box == NULL);
584 
585   priv->label_box = create_title_box (priv->title,
586                                       priv->subtitle,
587                                       &priv->title_label,
588                                       &priv->subtitle_label);
589   gtk_header_bar_reorder_css_node (bar, GTK_PACK_START, priv->label_box);
590   gtk_widget_set_parent (priv->label_box, GTK_WIDGET (bar));
591 }
592 
593 static gint
count_visible_children(GtkHeaderBar * bar)594 count_visible_children (GtkHeaderBar *bar)
595 {
596   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
597   GList *l;
598   Child *child;
599   gint n;
600 
601   n = 0;
602   for (l = priv->children; l; l = l->next)
603     {
604       child = l->data;
605       if (gtk_widget_get_visible (child->widget))
606         n++;
607     }
608 
609   return n;
610 }
611 
612 static gboolean
add_child_size(GtkWidget * child,GtkOrientation orientation,gint * minimum,gint * natural)613 add_child_size (GtkWidget      *child,
614                 GtkOrientation  orientation,
615                 gint           *minimum,
616                 gint           *natural)
617 {
618   gint child_minimum, child_natural;
619 
620   if (!gtk_widget_get_visible (child))
621     return FALSE;
622 
623   if (orientation == GTK_ORIENTATION_HORIZONTAL)
624     gtk_widget_get_preferred_width (child, &child_minimum, &child_natural);
625   else
626     gtk_widget_get_preferred_height (child, &child_minimum, &child_natural);
627 
628   if (GTK_ORIENTATION_HORIZONTAL == orientation)
629     {
630       *minimum += child_minimum;
631       *natural += child_natural;
632     }
633   else
634     {
635       *minimum = MAX (*minimum, child_minimum);
636       *natural = MAX (*natural, child_natural);
637     }
638 
639   return TRUE;
640 }
641 
642 static void
gtk_header_bar_get_size(GtkWidget * widget,GtkOrientation orientation,gint * minimum_size,gint * natural_size)643 gtk_header_bar_get_size (GtkWidget      *widget,
644                          GtkOrientation  orientation,
645                          gint           *minimum_size,
646                          gint           *natural_size)
647 {
648   GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
649   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
650   GList *l;
651   gint nvis_children;
652   gint minimum, natural;
653   gint center_min, center_nat;
654 
655   minimum = natural = 0;
656   nvis_children = 0;
657 
658   for (l = priv->children; l; l = l->next)
659     {
660       Child *child = l->data;
661 
662       if (add_child_size (child->widget, orientation, &minimum, &natural))
663         nvis_children += 1;
664     }
665 
666   center_min = center_nat = 0;
667   if (priv->label_box != NULL)
668     {
669       if (orientation == GTK_ORIENTATION_HORIZONTAL)
670         add_child_size (priv->label_box, orientation, &center_min, &center_nat);
671       else
672         add_child_size (priv->label_sizing_box, orientation, &center_min, &center_nat);
673 
674       if (_gtk_widget_get_visible (priv->label_sizing_box))
675         nvis_children += 1;
676     }
677 
678   if (priv->custom_title != NULL)
679     {
680       if (add_child_size (priv->custom_title, orientation, &center_min, &center_nat))
681         nvis_children += 1;
682     }
683 
684   if (priv->titlebar_start_box != NULL)
685     {
686       if (add_child_size (priv->titlebar_start_box, orientation, &minimum, &natural))
687         nvis_children += 1;
688     }
689 
690   if (priv->titlebar_end_box != NULL)
691     {
692       if (add_child_size (priv->titlebar_end_box, orientation, &minimum, &natural))
693         nvis_children += 1;
694     }
695 
696   if (orientation == GTK_ORIENTATION_HORIZONTAL)
697     {
698       minimum += center_min;
699       natural += center_nat;
700     }
701   else
702     {
703       minimum = MAX (minimum, center_min);
704       natural = MAX (natural, center_nat);
705     }
706 
707   if (nvis_children > 0 && orientation == GTK_ORIENTATION_HORIZONTAL)
708     {
709       minimum += nvis_children * priv->spacing;
710       natural += nvis_children * priv->spacing;
711     }
712 
713   *minimum_size = minimum;
714   *natural_size = natural;
715 }
716 
717 static void
gtk_header_bar_compute_size_for_orientation(GtkWidget * widget,gint avail_size,gint * minimum_size,gint * natural_size)718 gtk_header_bar_compute_size_for_orientation (GtkWidget *widget,
719                                              gint       avail_size,
720                                              gint      *minimum_size,
721                                              gint      *natural_size)
722 {
723   GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
724   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
725   GList *children;
726   gint required_size = 0;
727   gint required_natural = 0;
728   gint child_size;
729   gint child_natural;
730   gint nvis_children;
731 
732   nvis_children = 0;
733 
734   for (children = priv->children; children != NULL; children = children->next)
735     {
736       Child *child = children->data;
737 
738       if (gtk_widget_get_visible (child->widget))
739         {
740           gtk_widget_get_preferred_width_for_height (child->widget,
741                                                      avail_size, &child_size, &child_natural);
742 
743           required_size += child_size;
744           required_natural += child_natural;
745 
746           nvis_children += 1;
747         }
748     }
749 
750   if (priv->label_box != NULL)
751     {
752       gtk_widget_get_preferred_width (priv->label_sizing_box,
753                                       &child_size, &child_natural);
754       required_size += child_size;
755       required_natural += child_natural;
756     }
757 
758   if (priv->custom_title != NULL &&
759       gtk_widget_get_visible (priv->custom_title))
760     {
761       gtk_widget_get_preferred_width (priv->custom_title,
762                                       &child_size, &child_natural);
763       required_size += child_size;
764       required_natural += child_natural;
765     }
766 
767   if (priv->titlebar_start_box != NULL)
768     {
769       gtk_widget_get_preferred_width (priv->titlebar_start_box,
770                                       &child_size, &child_natural);
771       required_size += child_size;
772       required_natural += child_natural;
773       nvis_children += 1;
774     }
775 
776   if (priv->titlebar_end_box != NULL)
777     {
778       gtk_widget_get_preferred_width (priv->titlebar_end_box,
779                                       &child_size, &child_natural);
780       required_size += child_size;
781       required_natural += child_natural;
782       nvis_children += 1;
783     }
784 
785   if (nvis_children > 0)
786     {
787       required_size += nvis_children * priv->spacing;
788       required_natural += nvis_children * priv->spacing;
789     }
790 
791   *minimum_size = required_size;
792   *natural_size = required_natural;
793 }
794 
795 static void
gtk_header_bar_compute_size_for_opposing_orientation(GtkWidget * widget,gint avail_size,gint * minimum_size,gint * natural_size)796 gtk_header_bar_compute_size_for_opposing_orientation (GtkWidget *widget,
797                                                       gint       avail_size,
798                                                       gint      *minimum_size,
799                                                       gint      *natural_size)
800 {
801   GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
802   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
803   Child *child;
804   GList *children;
805   gint nvis_children;
806   gint computed_minimum = 0;
807   gint computed_natural = 0;
808   GtkRequestedSize *sizes;
809   GtkPackType packing;
810   gint size = 0;
811   gint i;
812   gint child_size;
813   gint child_minimum;
814   gint child_natural;
815   gint center_min, center_nat;
816 
817   nvis_children = count_visible_children (bar);
818 
819   if (nvis_children <= 0)
820     return;
821 
822   sizes = g_newa (GtkRequestedSize, nvis_children);
823 
824   /* Retrieve desired size for visible children */
825   for (i = 0, children = priv->children; children; children = children->next)
826     {
827       child = children->data;
828 
829       if (gtk_widget_get_visible (child->widget))
830         {
831           gtk_widget_get_preferred_width (child->widget,
832                                           &sizes[i].minimum_size,
833                                           &sizes[i].natural_size);
834 
835           size -= sizes[i].minimum_size;
836           sizes[i].data = child;
837           i += 1;
838         }
839     }
840 
841   /* Bring children up to size first */
842   size = gtk_distribute_natural_allocation (MAX (0, avail_size), nvis_children, sizes);
843 
844   /* Allocate child positions. */
845   for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing)
846     {
847       for (i = 0, children = priv->children; children; children = children->next)
848         {
849           child = children->data;
850 
851           /* If widget is not visible, skip it. */
852           if (!gtk_widget_get_visible (child->widget))
853             continue;
854 
855           /* If widget is packed differently skip it, but still increment i,
856            * since widget is visible and will be handled in next loop
857            * iteration.
858            */
859           if (child->pack_type != packing)
860             {
861               i++;
862               continue;
863             }
864 
865           child_size = sizes[i].minimum_size;
866 
867           gtk_widget_get_preferred_height_for_width (child->widget,
868                                                      child_size, &child_minimum, &child_natural);
869 
870           computed_minimum = MAX (computed_minimum, child_minimum);
871           computed_natural = MAX (computed_natural, child_natural);
872         }
873       i += 1;
874     }
875 
876   center_min = center_nat = 0;
877   if (priv->label_box != NULL)
878     {
879       gtk_widget_get_preferred_height (priv->label_sizing_box,
880                                        &center_min, &center_nat);
881     }
882 
883   if (priv->custom_title != NULL &&
884       gtk_widget_get_visible (priv->custom_title))
885     {
886       gtk_widget_get_preferred_height (priv->custom_title,
887                                        &center_min, &center_nat);
888     }
889 
890   if (priv->titlebar_start_box != NULL)
891     {
892       gtk_widget_get_preferred_height (priv->titlebar_start_box,
893                                        &child_minimum, &child_natural);
894       computed_minimum = MAX (computed_minimum, child_minimum);
895       computed_natural = MAX (computed_natural, child_natural);
896     }
897 
898   if (priv->titlebar_end_box != NULL)
899     {
900       gtk_widget_get_preferred_height (priv->titlebar_end_box,
901                                        &child_minimum, &child_natural);
902       computed_minimum = MAX (computed_minimum, child_minimum);
903       computed_natural = MAX (computed_natural, child_natural);
904     }
905 
906   *minimum_size = computed_minimum;
907   *natural_size = computed_natural;
908 }
909 
910 static void
gtk_header_bar_get_content_size(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer unused)911 gtk_header_bar_get_content_size (GtkCssGadget   *gadget,
912                                  GtkOrientation  orientation,
913                                  gint            for_size,
914                                  gint           *minimum,
915                                  gint           *natural,
916                                  gint           *minimum_baseline,
917                                  gint           *natural_baseline,
918                                  gpointer        unused)
919 {
920   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
921 
922   if (for_size < 0)
923     gtk_header_bar_get_size (widget, orientation, minimum, natural);
924   else if (orientation == GTK_ORIENTATION_HORIZONTAL)
925     gtk_header_bar_compute_size_for_orientation (widget, for_size, minimum, natural);
926   else
927     gtk_header_bar_compute_size_for_opposing_orientation (widget, for_size, minimum, natural);
928 }
929 
930 static void
gtk_header_bar_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)931 gtk_header_bar_get_preferred_width (GtkWidget *widget,
932                                     gint      *minimum,
933                                     gint      *natural)
934 {
935   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
936 
937   gtk_css_gadget_get_preferred_size (priv->gadget,
938                                      GTK_ORIENTATION_HORIZONTAL,
939                                      -1,
940                                      minimum, natural,
941                                      NULL, NULL);
942 }
943 
944 static void
gtk_header_bar_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)945 gtk_header_bar_get_preferred_height (GtkWidget *widget,
946                                      gint      *minimum,
947                                      gint      *natural)
948 {
949   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
950 
951   gtk_css_gadget_get_preferred_size (priv->gadget,
952                                      GTK_ORIENTATION_VERTICAL,
953                                      -1,
954                                      minimum, natural,
955                                      NULL, NULL);
956 }
957 
958 static void
gtk_header_bar_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum,gint * natural)959 gtk_header_bar_get_preferred_width_for_height (GtkWidget *widget,
960                                                gint       height,
961                                                gint      *minimum,
962                                                gint      *natural)
963 {
964   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
965 
966   gtk_css_gadget_get_preferred_size (priv->gadget,
967                                      GTK_ORIENTATION_HORIZONTAL,
968                                      height,
969                                      minimum, natural,
970                                      NULL, NULL);
971 }
972 
973 static void
gtk_header_bar_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural)974 gtk_header_bar_get_preferred_height_for_width (GtkWidget *widget,
975                                                gint       width,
976                                                gint      *minimum,
977                                                gint      *natural)
978 {
979   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
980 
981   gtk_css_gadget_get_preferred_size (priv->gadget,
982                                      GTK_ORIENTATION_VERTICAL,
983                                      width,
984                                      minimum, natural,
985                                      NULL, NULL);
986 }
987 
988 static void
gtk_header_bar_size_allocate(GtkWidget * widget,GtkAllocation * allocation)989 gtk_header_bar_size_allocate (GtkWidget     *widget,
990                               GtkAllocation *allocation)
991 {
992   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
993   GtkAllocation clip;
994 
995   gtk_widget_set_allocation (widget, allocation);
996 
997   gtk_css_gadget_allocate (priv->gadget, allocation, gtk_widget_get_allocated_baseline (widget), &clip);
998 
999   gtk_widget_set_clip (widget, &clip);
1000 }
1001 
1002 static void
gtk_header_bar_allocate_contents(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer unused)1003 gtk_header_bar_allocate_contents (GtkCssGadget        *gadget,
1004                                   const GtkAllocation *allocation,
1005                                   int                  baseline,
1006                                   GtkAllocation       *out_clip,
1007                                   gpointer             unused)
1008 {
1009   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1010   GtkWidget *title_widget;
1011   GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
1012   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1013   GtkRequestedSize *sizes;
1014   gint width, height;
1015   gint nvis_children;
1016   gint title_minimum_size;
1017   gint title_natural_size;
1018   gboolean title_expands = FALSE;
1019   gint start_width, end_width;
1020   gint uniform_expand_bonus[2] = { 0 };
1021   gint leftover_expand_bonus[2] = { 0 };
1022   gint nexpand_children[2] = { 0 };
1023   gint side[2];
1024   GList *l;
1025   gint i;
1026   Child *child;
1027   GtkPackType packing;
1028   GtkAllocation child_allocation;
1029   gint x;
1030   gint child_size;
1031   GtkTextDirection direction;
1032 
1033   direction = gtk_widget_get_direction (widget);
1034   nvis_children = count_visible_children (bar);
1035   sizes = g_newa (GtkRequestedSize, nvis_children);
1036 
1037   width = allocation->width - nvis_children * priv->spacing;
1038   height = allocation->height;
1039 
1040   i = 0;
1041   for (l = priv->children; l; l = l->next)
1042     {
1043       child = l->data;
1044       if (!gtk_widget_get_visible (child->widget))
1045         continue;
1046 
1047       if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL))
1048         nexpand_children[child->pack_type]++;
1049 
1050       gtk_widget_get_preferred_width_for_height (child->widget,
1051                                                  height,
1052                                                  &sizes[i].minimum_size,
1053                                                  &sizes[i].natural_size);
1054       width -= sizes[i].minimum_size;
1055       i++;
1056     }
1057 
1058   title_minimum_size = 0;
1059   title_natural_size = 0;
1060 
1061   if (priv->custom_title != NULL &&
1062       gtk_widget_get_visible (priv->custom_title))
1063     title_widget = priv->custom_title;
1064   else if (priv->label_box != NULL)
1065     title_widget = priv->label_box;
1066   else
1067     title_widget = NULL;
1068 
1069   if (title_widget)
1070     {
1071       gtk_widget_get_preferred_width_for_height (title_widget,
1072                                                  height,
1073                                                  &title_minimum_size,
1074                                                  &title_natural_size);
1075       width -= title_natural_size;
1076 
1077       title_expands = gtk_widget_compute_expand (title_widget, GTK_ORIENTATION_HORIZONTAL);
1078     }
1079 
1080   start_width = 0;
1081   if (priv->titlebar_start_box != NULL)
1082     {
1083       gint min, nat;
1084       gtk_widget_get_preferred_width_for_height (priv->titlebar_start_box,
1085                                                  height,
1086                                                  &min, &nat);
1087       start_width = nat + priv->spacing;
1088     }
1089   width -= start_width;
1090 
1091   end_width = 0;
1092   if (priv->titlebar_end_box != NULL)
1093     {
1094       gint min, nat;
1095       gtk_widget_get_preferred_width_for_height (priv->titlebar_end_box,
1096                                                  height,
1097                                                  &min, &nat);
1098       end_width = nat + priv->spacing;
1099     }
1100   width -= end_width;
1101 
1102   width = gtk_distribute_natural_allocation (MAX (0, width), nvis_children, sizes);
1103 
1104   /* compute the nominal size of the children filling up each side of
1105    * the title in titlebar
1106    */
1107   side[0] = start_width;
1108   side[1] = end_width;
1109   for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
1110     {
1111       i = 0;
1112       for (l = priv->children; l != NULL; l = l->next)
1113         {
1114           child = l->data;
1115           if (!gtk_widget_get_visible (child->widget))
1116             continue;
1117 
1118           if (child->pack_type == packing)
1119             side[packing] += sizes[i].minimum_size + priv->spacing;
1120 
1121           i++;
1122         }
1123     }
1124 
1125   /* figure out how much space is left on each side of the title,
1126    * and earkmark that space for the expanded children.
1127    *
1128    * If the title itself is expanded, then it gets half the spoils
1129    * from each side.
1130    */
1131   for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
1132     {
1133       gint side_free_space;
1134 
1135       side_free_space = allocation->width / 2 - title_natural_size / 2 - side[packing];
1136 
1137       if (side_free_space > 0 && nexpand_children[packing] > 0)
1138         {
1139           width -= side_free_space;
1140 
1141           if (title_expands)
1142             side_free_space -= side_free_space / 2;
1143 
1144           side[packing] += side_free_space;
1145           uniform_expand_bonus[packing] = side_free_space / nexpand_children[packing];
1146           leftover_expand_bonus[packing] = side_free_space % nexpand_children[packing];
1147         }
1148     }
1149 
1150   /* allocate the children on both sides of the title */
1151   for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
1152     {
1153       child_allocation.y = allocation->y;
1154       child_allocation.height = height;
1155       if (packing == GTK_PACK_START)
1156         x = allocation->x + start_width;
1157       else
1158         x = allocation->x + allocation->width - end_width;
1159 
1160       i = 0;
1161       for (l = priv->children; l != NULL; l = l->next)
1162         {
1163           child = l->data;
1164           if (!gtk_widget_get_visible (child->widget))
1165             continue;
1166 
1167           if (child->pack_type != packing)
1168             goto next;
1169 
1170           child_size = sizes[i].minimum_size;
1171 
1172           /* if this child is expanded, give it extra space from the reserves */
1173           if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL))
1174             {
1175               gint expand_bonus;
1176 
1177               expand_bonus = uniform_expand_bonus[packing];
1178 
1179               if (leftover_expand_bonus[packing] > 0)
1180                 {
1181                   expand_bonus++;
1182                   leftover_expand_bonus[packing]--;
1183                 }
1184 
1185               child_size += expand_bonus;
1186             }
1187 
1188           child_allocation.width = child_size;
1189 
1190           if (packing == GTK_PACK_START)
1191             {
1192               child_allocation.x = x;
1193               x += child_size;
1194               x += priv->spacing;
1195             }
1196           else
1197             {
1198               x -= child_size;
1199               child_allocation.x = x;
1200               x -= priv->spacing;
1201             }
1202 
1203           if (direction == GTK_TEXT_DIR_RTL)
1204             child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
1205 
1206           gtk_widget_size_allocate (child->widget, &child_allocation);
1207 
1208         next:
1209           i++;
1210         }
1211     }
1212 
1213   /* We don't enforce css borders on the center widget, to make
1214    * title/subtitle combinations fit without growing the header
1215    */
1216   child_allocation.y = allocation->y;
1217   child_allocation.height = allocation->height;
1218 
1219   child_size = MIN (allocation->width - side[0] - side[1], title_natural_size);
1220 
1221   child_allocation.x = allocation->x + (allocation->width - child_size) / 2;
1222   child_allocation.width = child_size;
1223 
1224   /* if the title widget is expanded, then grow it by all the available
1225    * free space, and recenter it
1226    */
1227   if (title_expands && width > 0)
1228     {
1229       child_allocation.width += width;
1230       child_allocation.x -= width / 2;
1231     }
1232 
1233   if (allocation->x + side[0] > child_allocation.x)
1234     child_allocation.x = allocation->x + side[0];
1235   else if (allocation->x + allocation->width - side[1] < child_allocation.x + child_allocation.width)
1236     child_allocation.x = allocation->x + allocation->width - side[1] - child_allocation.width;
1237 
1238   if (direction == GTK_TEXT_DIR_RTL)
1239     child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
1240 
1241   if (title_widget != NULL)
1242     gtk_widget_size_allocate (title_widget, &child_allocation);
1243 
1244   child_allocation.y = allocation->y;
1245   child_allocation.height = height;
1246 
1247   if (priv->titlebar_start_box)
1248     {
1249       gboolean left = (direction == GTK_TEXT_DIR_LTR);
1250       if (left)
1251         child_allocation.x = allocation->x;
1252       else
1253         child_allocation.x = allocation->x + allocation->width - start_width + priv->spacing;
1254       child_allocation.width = start_width - priv->spacing;
1255       gtk_widget_size_allocate (priv->titlebar_start_box, &child_allocation);
1256     }
1257 
1258   if (priv->titlebar_end_box)
1259     {
1260       gboolean left = (direction != GTK_TEXT_DIR_LTR);
1261       if (left)
1262         child_allocation.x = allocation->x;
1263       else
1264         child_allocation.x = allocation->x + allocation->width - end_width + priv->spacing;
1265       child_allocation.width = end_width - priv->spacing;
1266       gtk_widget_size_allocate (priv->titlebar_end_box, &child_allocation);
1267     }
1268 
1269   gtk_container_get_children_clip (GTK_CONTAINER (widget), out_clip);
1270 }
1271 
1272 /**
1273  * gtk_header_bar_set_title:
1274  * @bar: a #GtkHeaderBar
1275  * @title: (allow-none): a title, or %NULL
1276  *
1277  * Sets the title of the #GtkHeaderBar. The title should help a user
1278  * identify the current view. A good title should not include the
1279  * application name.
1280  *
1281  * Since: 3.10
1282  */
1283 void
gtk_header_bar_set_title(GtkHeaderBar * bar,const gchar * title)1284 gtk_header_bar_set_title (GtkHeaderBar *bar,
1285                           const gchar  *title)
1286 {
1287   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1288   gchar *new_title;
1289 
1290   g_return_if_fail (GTK_IS_HEADER_BAR (bar));
1291 
1292   new_title = g_strdup (title);
1293   g_free (priv->title);
1294   priv->title = new_title;
1295 
1296   if (priv->title_label != NULL)
1297     {
1298       gtk_label_set_label (GTK_LABEL (priv->title_label), priv->title);
1299       gtk_widget_queue_resize (GTK_WIDGET (bar));
1300     }
1301 
1302   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_TITLE]);
1303 }
1304 
1305 /**
1306  * gtk_header_bar_get_title:
1307  * @bar: a #GtkHeaderBar
1308  *
1309  * Retrieves the title of the header. See gtk_header_bar_set_title().
1310  *
1311  * Returns: (nullable): the title of the header, or %NULL if none has
1312  *    been set explicitly. The returned string is owned by the widget
1313  *    and must not be modified or freed.
1314  *
1315  * Since: 3.10
1316  */
1317 const gchar *
gtk_header_bar_get_title(GtkHeaderBar * bar)1318 gtk_header_bar_get_title (GtkHeaderBar *bar)
1319 {
1320   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1321 
1322   g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);
1323 
1324   return priv->title;
1325 }
1326 
1327 /**
1328  * gtk_header_bar_set_subtitle:
1329  * @bar: a #GtkHeaderBar
1330  * @subtitle: (allow-none): a subtitle, or %NULL
1331  *
1332  * Sets the subtitle of the #GtkHeaderBar. The title should give a user
1333  * an additional detail to help him identify the current view.
1334  *
1335  * Note that GtkHeaderBar by default reserves room for the subtitle,
1336  * even if none is currently set. If this is not desired, set the
1337  * #GtkHeaderBar:has-subtitle property to %FALSE.
1338  *
1339  * Since: 3.10
1340  */
1341 void
gtk_header_bar_set_subtitle(GtkHeaderBar * bar,const gchar * subtitle)1342 gtk_header_bar_set_subtitle (GtkHeaderBar *bar,
1343                              const gchar  *subtitle)
1344 {
1345   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1346   gchar *new_subtitle;
1347 
1348   g_return_if_fail (GTK_IS_HEADER_BAR (bar));
1349 
1350   new_subtitle = g_strdup (subtitle);
1351   g_free (priv->subtitle);
1352   priv->subtitle = new_subtitle;
1353 
1354   if (priv->subtitle_label != NULL)
1355     {
1356       gtk_label_set_label (GTK_LABEL (priv->subtitle_label), priv->subtitle);
1357       gtk_widget_set_visible (priv->subtitle_label, priv->subtitle && priv->subtitle[0]);
1358       gtk_widget_queue_resize (GTK_WIDGET (bar));
1359     }
1360 
1361   gtk_widget_set_visible (priv->subtitle_sizing_label, priv->has_subtitle || (priv->subtitle && priv->subtitle[0]));
1362 
1363   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_SUBTITLE]);
1364 }
1365 
1366 /**
1367  * gtk_header_bar_get_subtitle:
1368  * @bar: a #GtkHeaderBar
1369  *
1370  * Retrieves the subtitle of the header. See gtk_header_bar_set_subtitle().
1371  *
1372  * Returns: (nullable): the subtitle of the header, or %NULL if none has
1373  *    been set explicitly. The returned string is owned by the widget
1374  *    and must not be modified or freed.
1375  *
1376  * Since: 3.10
1377  */
1378 const gchar *
gtk_header_bar_get_subtitle(GtkHeaderBar * bar)1379 gtk_header_bar_get_subtitle (GtkHeaderBar *bar)
1380 {
1381   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1382 
1383   g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);
1384 
1385   return priv->subtitle;
1386 }
1387 
1388 /**
1389  * gtk_header_bar_set_custom_title:
1390  * @bar: a #GtkHeaderBar
1391  * @title_widget: (allow-none): a custom widget to use for a title
1392  *
1393  * Sets a custom title for the #GtkHeaderBar.
1394  *
1395  * The title should help a user identify the current view. This
1396  * supersedes any title set by gtk_header_bar_set_title() or
1397  * gtk_header_bar_set_subtitle(). To achieve the same style as
1398  * the builtin title and subtitle, use the “title” and “subtitle”
1399  * style classes.
1400  *
1401  * You should set the custom title to %NULL, for the header title
1402  * label to be visible again.
1403  *
1404  * Since: 3.10
1405  */
1406 void
gtk_header_bar_set_custom_title(GtkHeaderBar * bar,GtkWidget * title_widget)1407 gtk_header_bar_set_custom_title (GtkHeaderBar *bar,
1408                                  GtkWidget    *title_widget)
1409 {
1410   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1411 
1412   g_return_if_fail (GTK_IS_HEADER_BAR (bar));
1413   if (title_widget)
1414     g_return_if_fail (GTK_IS_WIDGET (title_widget));
1415 
1416   /* No need to do anything if the custom widget stays the same */
1417   if (priv->custom_title == title_widget)
1418     return;
1419 
1420   if (priv->custom_title)
1421     {
1422       GtkWidget *custom = priv->custom_title;
1423 
1424       priv->custom_title = NULL;
1425       gtk_widget_unparent (custom);
1426     }
1427 
1428   if (title_widget != NULL)
1429     {
1430       priv->custom_title = title_widget;
1431 
1432       gtk_header_bar_reorder_css_node (bar, GTK_PACK_START, priv->custom_title);
1433       gtk_widget_set_parent (priv->custom_title, GTK_WIDGET (bar));
1434       gtk_widget_set_valign (priv->custom_title, GTK_ALIGN_CENTER);
1435 
1436       if (priv->label_box != NULL)
1437         {
1438           GtkWidget *label_box = priv->label_box;
1439 
1440           priv->label_box = NULL;
1441           priv->title_label = NULL;
1442           priv->subtitle_label = NULL;
1443           gtk_widget_unparent (label_box);
1444         }
1445 
1446     }
1447   else
1448     {
1449       if (priv->label_box == NULL)
1450         construct_label_box (bar);
1451     }
1452 
1453   gtk_widget_queue_resize (GTK_WIDGET (bar));
1454 
1455   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_CUSTOM_TITLE]);
1456 }
1457 
1458 /**
1459  * gtk_header_bar_get_custom_title:
1460  * @bar: a #GtkHeaderBar
1461  *
1462  * Retrieves the custom title widget of the header. See
1463  * gtk_header_bar_set_custom_title().
1464  *
1465  * Returns: (nullable) (transfer none): the custom title widget
1466  *    of the header, or %NULL if none has been set explicitly.
1467  *
1468  * Since: 3.10
1469  */
1470 GtkWidget *
gtk_header_bar_get_custom_title(GtkHeaderBar * bar)1471 gtk_header_bar_get_custom_title (GtkHeaderBar *bar)
1472 {
1473   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1474 
1475   g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);
1476 
1477   return priv->custom_title;
1478 }
1479 
1480 static void
gtk_header_bar_destroy(GtkWidget * widget)1481 gtk_header_bar_destroy (GtkWidget *widget)
1482 {
1483   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
1484 
1485   if (priv->label_sizing_box)
1486     {
1487       gtk_widget_destroy (priv->label_sizing_box);
1488       g_clear_object (&priv->label_sizing_box);
1489     }
1490 
1491   if (priv->custom_title)
1492     {
1493       gtk_widget_unparent (priv->custom_title);
1494       priv->custom_title = NULL;
1495     }
1496 
1497   if (priv->label_box)
1498     {
1499       gtk_widget_unparent (priv->label_box);
1500       priv->label_box = NULL;
1501     }
1502 
1503   if (priv->titlebar_start_box)
1504     {
1505       gtk_widget_unparent (priv->titlebar_start_box);
1506       priv->titlebar_start_box = NULL;
1507       priv->titlebar_start_separator = NULL;
1508     }
1509 
1510   if (priv->titlebar_end_box)
1511     {
1512       gtk_widget_unparent (priv->titlebar_end_box);
1513       priv->titlebar_end_box = NULL;
1514       priv->titlebar_end_separator = NULL;
1515     }
1516 
1517   GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->destroy (widget);
1518 }
1519 
1520 static void
gtk_header_bar_finalize(GObject * object)1521 gtk_header_bar_finalize (GObject *object)
1522 {
1523   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (object));
1524 
1525   g_free (priv->title);
1526   g_free (priv->subtitle);
1527   g_free (priv->decoration_layout);
1528 
1529   g_clear_object (&priv->gadget);
1530 
1531   G_OBJECT_CLASS (gtk_header_bar_parent_class)->finalize (object);
1532 }
1533 
1534 static void
gtk_header_bar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1535 gtk_header_bar_get_property (GObject    *object,
1536                              guint       prop_id,
1537                              GValue     *value,
1538                              GParamSpec *pspec)
1539 {
1540   GtkHeaderBar *bar = GTK_HEADER_BAR (object);
1541   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1542 
1543   switch (prop_id)
1544     {
1545     case PROP_TITLE:
1546       g_value_set_string (value, priv->title);
1547       break;
1548 
1549     case PROP_SUBTITLE:
1550       g_value_set_string (value, priv->subtitle);
1551       break;
1552 
1553     case PROP_CUSTOM_TITLE:
1554       g_value_set_object (value, priv->custom_title);
1555       break;
1556 
1557     case PROP_SPACING:
1558       g_value_set_int (value, priv->spacing);
1559       break;
1560 
1561     case PROP_SHOW_CLOSE_BUTTON:
1562       g_value_set_boolean (value, gtk_header_bar_get_show_close_button (bar));
1563       break;
1564 
1565     case PROP_HAS_SUBTITLE:
1566       g_value_set_boolean (value, gtk_header_bar_get_has_subtitle (bar));
1567       break;
1568 
1569     case PROP_DECORATION_LAYOUT:
1570       g_value_set_string (value, gtk_header_bar_get_decoration_layout (bar));
1571       break;
1572 
1573     case PROP_DECORATION_LAYOUT_SET:
1574       g_value_set_boolean (value, priv->decoration_layout_set);
1575       break;
1576 
1577     default:
1578       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1579       break;
1580     }
1581 }
1582 
1583 static void
gtk_header_bar_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1584 gtk_header_bar_set_property (GObject      *object,
1585                              guint         prop_id,
1586                              const GValue *value,
1587                              GParamSpec   *pspec)
1588 {
1589   GtkHeaderBar *bar = GTK_HEADER_BAR (object);
1590   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1591 
1592   switch (prop_id)
1593     {
1594     case PROP_TITLE:
1595       gtk_header_bar_set_title (bar, g_value_get_string (value));
1596       break;
1597 
1598     case PROP_SUBTITLE:
1599       gtk_header_bar_set_subtitle (bar, g_value_get_string (value));
1600       break;
1601 
1602     case PROP_CUSTOM_TITLE:
1603       gtk_header_bar_set_custom_title (bar, g_value_get_object (value));
1604       break;
1605 
1606     case PROP_SPACING:
1607       if (priv->spacing != g_value_get_int (value))
1608         {
1609           priv->spacing = g_value_get_int (value);
1610           gtk_widget_queue_resize (GTK_WIDGET (bar));
1611           g_object_notify_by_pspec (object, pspec);
1612         }
1613       break;
1614 
1615     case PROP_SHOW_CLOSE_BUTTON:
1616       gtk_header_bar_set_show_close_button (bar, g_value_get_boolean (value));
1617       break;
1618 
1619     case PROP_HAS_SUBTITLE:
1620       gtk_header_bar_set_has_subtitle (bar, g_value_get_boolean (value));
1621       break;
1622 
1623     case PROP_DECORATION_LAYOUT:
1624       gtk_header_bar_set_decoration_layout (bar, g_value_get_string (value));
1625       break;
1626 
1627     case PROP_DECORATION_LAYOUT_SET:
1628       priv->decoration_layout_set = g_value_get_boolean (value);
1629       break;
1630 
1631     default:
1632       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1633       break;
1634     }
1635 }
1636 
1637 static void
notify_child_cb(GObject * child,GParamSpec * pspec,GtkHeaderBar * bar)1638 notify_child_cb (GObject      *child,
1639                  GParamSpec   *pspec,
1640                  GtkHeaderBar *bar)
1641 {
1642   _gtk_header_bar_update_separator_visibility (bar);
1643 }
1644 
1645 static void
gtk_header_bar_pack(GtkHeaderBar * bar,GtkWidget * widget,GtkPackType pack_type)1646 gtk_header_bar_pack (GtkHeaderBar *bar,
1647                      GtkWidget    *widget,
1648                      GtkPackType   pack_type)
1649 {
1650   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1651   Child *child;
1652 
1653   g_return_if_fail (gtk_widget_get_parent (widget) == NULL);
1654 
1655   child = g_new (Child, 1);
1656   child->widget = widget;
1657   child->pack_type = pack_type;
1658 
1659   priv->children = g_list_append (priv->children, child);
1660 
1661   gtk_widget_freeze_child_notify (widget);
1662   gtk_header_bar_reorder_css_node (bar, GTK_PACK_START, widget);
1663   gtk_widget_set_parent (widget, GTK_WIDGET (bar));
1664   g_signal_connect (widget, "notify::visible", G_CALLBACK (notify_child_cb), bar);
1665   gtk_widget_child_notify (widget, "pack-type");
1666   gtk_widget_child_notify (widget, "position");
1667   gtk_widget_thaw_child_notify (widget);
1668 
1669   _gtk_header_bar_update_separator_visibility (bar);
1670 }
1671 
1672 static void
gtk_header_bar_add(GtkContainer * container,GtkWidget * child)1673 gtk_header_bar_add (GtkContainer *container,
1674                     GtkWidget    *child)
1675 {
1676   gtk_header_bar_pack (GTK_HEADER_BAR (container), child, GTK_PACK_START);
1677 }
1678 
1679 static GList *
find_child_link(GtkHeaderBar * bar,GtkWidget * widget,gint * position)1680 find_child_link (GtkHeaderBar *bar,
1681                  GtkWidget    *widget,
1682                  gint         *position)
1683 {
1684   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1685   GList *l;
1686   Child *child;
1687   gint i;
1688 
1689   for (l = priv->children, i = 0; l; l = l->next, i++)
1690     {
1691       child = l->data;
1692       if (child->widget == widget)
1693         {
1694           if (position)
1695             *position = i;
1696           return l;
1697         }
1698     }
1699 
1700   return NULL;
1701 }
1702 
1703 static void
gtk_header_bar_remove(GtkContainer * container,GtkWidget * widget)1704 gtk_header_bar_remove (GtkContainer *container,
1705                        GtkWidget    *widget)
1706 {
1707   GtkHeaderBar *bar = GTK_HEADER_BAR (container);
1708   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1709   GList *l;
1710   Child *child;
1711 
1712   l = find_child_link (bar, widget, NULL);
1713   if (l)
1714     {
1715       child = l->data;
1716       g_signal_handlers_disconnect_by_func (widget, notify_child_cb, bar);
1717       gtk_widget_unparent (child->widget);
1718       priv->children = g_list_delete_link (priv->children, l);
1719       g_free (child);
1720       gtk_widget_queue_resize (GTK_WIDGET (container));
1721       _gtk_header_bar_update_separator_visibility (bar);
1722     }
1723 }
1724 
1725 static void
gtk_header_bar_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)1726 gtk_header_bar_forall (GtkContainer *container,
1727                        gboolean      include_internals,
1728                        GtkCallback   callback,
1729                        gpointer      callback_data)
1730 {
1731   GtkHeaderBar *bar = GTK_HEADER_BAR (container);
1732   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1733   Child *child;
1734   GList *children;
1735 
1736   if (include_internals && priv->titlebar_start_box != NULL)
1737     (* callback) (priv->titlebar_start_box, callback_data);
1738 
1739   children = priv->children;
1740   while (children)
1741     {
1742       child = children->data;
1743       children = children->next;
1744       if (child->pack_type == GTK_PACK_START)
1745         (* callback) (child->widget, callback_data);
1746     }
1747 
1748   if (priv->custom_title != NULL)
1749     (* callback) (priv->custom_title, callback_data);
1750 
1751   if (include_internals && priv->label_box != NULL)
1752     (* callback) (priv->label_box, callback_data);
1753 
1754   children = priv->children;
1755   while (children)
1756     {
1757       child = children->data;
1758       children = children->next;
1759       if (child->pack_type == GTK_PACK_END)
1760         (* callback) (child->widget, callback_data);
1761     }
1762 
1763   if (include_internals && priv->titlebar_end_box != NULL)
1764     (* callback) (priv->titlebar_end_box, callback_data);
1765 }
1766 
1767 static void
gtk_header_bar_reorder_child(GtkHeaderBar * bar,GtkWidget * widget,gint position)1768 gtk_header_bar_reorder_child (GtkHeaderBar *bar,
1769                               GtkWidget    *widget,
1770                               gint          position)
1771 {
1772   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1773   GList *l;
1774   gint old_position;
1775   Child *child;
1776 
1777   l = find_child_link (bar, widget, &old_position);
1778 
1779   if (l == NULL)
1780     return;
1781 
1782   if (old_position == position)
1783     return;
1784 
1785   child = l->data;
1786   priv->children = g_list_delete_link (priv->children, l);
1787 
1788   if (position < 0)
1789     l = NULL;
1790   else
1791     l = g_list_nth (priv->children, position);
1792 
1793   priv->children = g_list_insert_before (priv->children, l, child);
1794   gtk_header_bar_reorder_css_node (bar, child->pack_type, widget);
1795   gtk_widget_child_notify (widget, "position");
1796   gtk_widget_queue_resize (widget);
1797 }
1798 
1799 static GType
gtk_header_bar_child_type(GtkContainer * container)1800 gtk_header_bar_child_type (GtkContainer *container)
1801 {
1802   return GTK_TYPE_WIDGET;
1803 }
1804 
1805 static void
gtk_header_bar_get_child_property(GtkContainer * container,GtkWidget * widget,guint property_id,GValue * value,GParamSpec * pspec)1806 gtk_header_bar_get_child_property (GtkContainer *container,
1807                                    GtkWidget    *widget,
1808                                    guint         property_id,
1809                                    GValue       *value,
1810                                    GParamSpec   *pspec)
1811 {
1812   GtkHeaderBar *bar = GTK_HEADER_BAR (container);
1813   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1814   GList *l;
1815   Child *child;
1816 
1817   l = find_child_link (bar, widget, NULL);
1818   if (l == NULL)
1819     {
1820       g_param_value_set_default (pspec, value);
1821       return;
1822     }
1823 
1824   child = l->data;
1825 
1826   switch (property_id)
1827     {
1828     case CHILD_PROP_PACK_TYPE:
1829       g_value_set_enum (value, child->pack_type);
1830       break;
1831 
1832     case CHILD_PROP_POSITION:
1833       g_value_set_int (value, g_list_position (priv->children, l));
1834       break;
1835 
1836     default:
1837       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
1838       break;
1839     }
1840 }
1841 
1842 static void
gtk_header_bar_set_child_property(GtkContainer * container,GtkWidget * widget,guint property_id,const GValue * value,GParamSpec * pspec)1843 gtk_header_bar_set_child_property (GtkContainer *container,
1844                                    GtkWidget    *widget,
1845                                    guint         property_id,
1846                                    const GValue *value,
1847                                    GParamSpec   *pspec)
1848 {
1849   GtkHeaderBar *bar = GTK_HEADER_BAR (container);
1850   GList *l;
1851   Child *child;
1852 
1853   l = find_child_link (bar, widget, NULL);
1854   if (l == NULL)
1855     return;
1856 
1857   child = l->data;
1858 
1859   switch (property_id)
1860     {
1861     case CHILD_PROP_PACK_TYPE:
1862       child->pack_type = g_value_get_enum (value);
1863       _gtk_header_bar_update_separator_visibility (bar);
1864       gtk_widget_queue_resize (widget);
1865       break;
1866 
1867     case CHILD_PROP_POSITION:
1868       gtk_header_bar_reorder_child (bar, widget, g_value_get_int (value));
1869       break;
1870 
1871     default:
1872       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
1873       break;
1874     }
1875 }
1876 
1877 static gint
gtk_header_bar_draw(GtkWidget * widget,cairo_t * cr)1878 gtk_header_bar_draw (GtkWidget *widget,
1879                      cairo_t   *cr)
1880 {
1881   GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
1882 
1883   gtk_css_gadget_draw (priv->gadget, cr);
1884 
1885   return FALSE;
1886 }
1887 
1888 static gboolean
gtk_header_bar_render_contents(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer unused)1889 gtk_header_bar_render_contents (GtkCssGadget *gadget,
1890                                 cairo_t      *cr,
1891                                 int           x,
1892                                 int           y,
1893                                 int           width,
1894                                 int           height,
1895                                 gpointer      unused)
1896 {
1897   GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1898 
1899   GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->draw (widget, cr);
1900 
1901   return FALSE;
1902 }
1903 
1904 static void
gtk_header_bar_realize(GtkWidget * widget)1905 gtk_header_bar_realize (GtkWidget *widget)
1906 {
1907   GtkSettings *settings;
1908 
1909   GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->realize (widget);
1910 
1911   settings = gtk_widget_get_settings (widget);
1912   g_signal_connect_swapped (settings, "notify::gtk-shell-shows-app-menu",
1913                             G_CALLBACK (_gtk_header_bar_update_window_buttons), widget);
1914   g_signal_connect_swapped (settings, "notify::gtk-decoration-layout",
1915                             G_CALLBACK (_gtk_header_bar_update_window_buttons), widget);
1916   _gtk_header_bar_update_window_buttons (GTK_HEADER_BAR (widget));
1917 }
1918 
1919 static void
gtk_header_bar_unrealize(GtkWidget * widget)1920 gtk_header_bar_unrealize (GtkWidget *widget)
1921 {
1922   GtkSettings *settings;
1923 
1924   settings = gtk_widget_get_settings (widget);
1925 
1926   g_signal_handlers_disconnect_by_func (settings, _gtk_header_bar_update_window_buttons, widget);
1927 
1928   GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->unrealize (widget);
1929 }
1930 
1931 static gboolean
window_state_changed(GtkWidget * window,GdkEventWindowState * event,gpointer data)1932 window_state_changed (GtkWidget           *window,
1933                       GdkEventWindowState *event,
1934                       gpointer             data)
1935 {
1936   GtkHeaderBar *bar = GTK_HEADER_BAR (data);
1937 
1938   if (event->changed_mask & (GDK_WINDOW_STATE_FULLSCREEN |
1939                              GDK_WINDOW_STATE_MAXIMIZED |
1940                              GDK_WINDOW_STATE_TILED |
1941                              GDK_WINDOW_STATE_TOP_TILED |
1942                              GDK_WINDOW_STATE_RIGHT_TILED |
1943                              GDK_WINDOW_STATE_BOTTOM_TILED |
1944                              GDK_WINDOW_STATE_LEFT_TILED))
1945     _gtk_header_bar_update_window_buttons (bar);
1946 
1947   return FALSE;
1948 }
1949 
1950 static void
gtk_header_bar_hierarchy_changed(GtkWidget * widget,GtkWidget * previous_toplevel)1951 gtk_header_bar_hierarchy_changed (GtkWidget *widget,
1952                                   GtkWidget *previous_toplevel)
1953 {
1954   GtkWidget *toplevel;
1955   GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
1956 
1957   toplevel = gtk_widget_get_toplevel (widget);
1958 
1959   if (previous_toplevel)
1960     g_signal_handlers_disconnect_by_func (previous_toplevel,
1961                                           window_state_changed, widget);
1962 
1963   if (toplevel)
1964     g_signal_connect_after (toplevel, "window-state-event",
1965                             G_CALLBACK (window_state_changed), widget);
1966 
1967   _gtk_header_bar_update_window_buttons (bar);
1968 }
1969 
1970 static void
gtk_header_bar_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)1971 gtk_header_bar_direction_changed (GtkWidget        *widget,
1972                                   GtkTextDirection  previous_direction)
1973 {
1974   GTK_WIDGET_CLASS (gtk_header_bar_parent_class)->direction_changed (widget, previous_direction);
1975 
1976   gtk_css_node_reverse_children (gtk_widget_get_css_node (widget));
1977 }
1978 
1979 static void
gtk_header_bar_class_init(GtkHeaderBarClass * class)1980 gtk_header_bar_class_init (GtkHeaderBarClass *class)
1981 {
1982   GObjectClass *object_class = G_OBJECT_CLASS (class);
1983   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
1984   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
1985 
1986   object_class->finalize = gtk_header_bar_finalize;
1987   object_class->get_property = gtk_header_bar_get_property;
1988   object_class->set_property = gtk_header_bar_set_property;
1989 
1990   widget_class->destroy = gtk_header_bar_destroy;
1991   widget_class->size_allocate = gtk_header_bar_size_allocate;
1992   widget_class->get_preferred_width = gtk_header_bar_get_preferred_width;
1993   widget_class->get_preferred_height = gtk_header_bar_get_preferred_height;
1994   widget_class->get_preferred_height_for_width = gtk_header_bar_get_preferred_height_for_width;
1995   widget_class->get_preferred_width_for_height = gtk_header_bar_get_preferred_width_for_height;
1996   widget_class->draw = gtk_header_bar_draw;
1997   widget_class->realize = gtk_header_bar_realize;
1998   widget_class->unrealize = gtk_header_bar_unrealize;
1999   widget_class->hierarchy_changed = gtk_header_bar_hierarchy_changed;
2000   widget_class->direction_changed = gtk_header_bar_direction_changed;
2001 
2002   container_class->add = gtk_header_bar_add;
2003   container_class->remove = gtk_header_bar_remove;
2004   container_class->forall = gtk_header_bar_forall;
2005   container_class->child_type = gtk_header_bar_child_type;
2006   container_class->set_child_property = gtk_header_bar_set_child_property;
2007   container_class->get_child_property = gtk_header_bar_get_child_property;
2008   gtk_container_class_handle_border_width (container_class);
2009 
2010   gtk_container_class_install_child_property (container_class,
2011                                               CHILD_PROP_PACK_TYPE,
2012                                               g_param_spec_enum ("pack-type",
2013                                                                  P_("Pack type"),
2014                                                                  P_("A GtkPackType indicating whether the child is packed with reference to the start or end of the parent"),
2015                                                                  GTK_TYPE_PACK_TYPE, GTK_PACK_START,
2016                                                                  GTK_PARAM_READWRITE));
2017   gtk_container_class_install_child_property (container_class,
2018                                               CHILD_PROP_POSITION,
2019                                               g_param_spec_int ("position",
2020                                                                 P_("Position"),
2021                                                                 P_("The index of the child in the parent"),
2022                                                                 -1, G_MAXINT, 0,
2023                                                                 GTK_PARAM_READWRITE));
2024 
2025   header_bar_props[PROP_TITLE] =
2026       g_param_spec_string ("title",
2027                            P_("Title"),
2028                            P_("The title to display"),
2029                            NULL,
2030                            G_PARAM_READWRITE);
2031 
2032   header_bar_props[PROP_SUBTITLE] =
2033       g_param_spec_string ("subtitle",
2034                            P_("Subtitle"),
2035                            P_("The subtitle to display"),
2036                            NULL,
2037                            G_PARAM_READWRITE);
2038 
2039   header_bar_props[PROP_CUSTOM_TITLE] =
2040       g_param_spec_object ("custom-title",
2041                            P_("Custom Title"),
2042                            P_("Custom title widget to display"),
2043                            GTK_TYPE_WIDGET,
2044                            G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);
2045 
2046   header_bar_props[PROP_SPACING] =
2047       g_param_spec_int ("spacing",
2048                         P_("Spacing"),
2049                         P_("The amount of space between children"),
2050                         0, G_MAXINT,
2051                         DEFAULT_SPACING,
2052                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
2053 
2054   /**
2055    * GtkHeaderBar:show-close-button:
2056    *
2057    * Whether to show window decorations.
2058    *
2059    * Which buttons are actually shown and where is determined
2060    * by the #GtkHeaderBar:decoration-layout property, and by
2061    * the state of the window (e.g. a close button will not be
2062    * shown if the window can't be closed).
2063    */
2064   header_bar_props[PROP_SHOW_CLOSE_BUTTON] =
2065       g_param_spec_boolean ("show-close-button",
2066                             P_("Show decorations"),
2067                             P_("Whether to show window decorations"),
2068                             FALSE,
2069                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
2070 
2071   /**
2072    * GtkHeaderBar:decoration-layout:
2073    *
2074    * The decoration layout for buttons. If this property is
2075    * not set, the #GtkSettings:gtk-decoration-layout setting
2076    * is used.
2077    *
2078    * See gtk_header_bar_set_decoration_layout() for information
2079    * about the format of this string.
2080    *
2081    * Since: 3.12
2082    */
2083   header_bar_props[PROP_DECORATION_LAYOUT] =
2084       g_param_spec_string ("decoration-layout",
2085                            P_("Decoration Layout"),
2086                            P_("The layout for window decorations"),
2087                            NULL,
2088                            GTK_PARAM_READWRITE);
2089 
2090   /**
2091    * GtkHeaderBar:decoration-layout-set:
2092    *
2093    * Set to %TRUE if #GtkHeaderBar:decoration-layout is set.
2094    *
2095    * Since: 3.12
2096    */
2097   header_bar_props[PROP_DECORATION_LAYOUT_SET] =
2098       g_param_spec_boolean ("decoration-layout-set",
2099                             P_("Decoration Layout Set"),
2100                             P_("Whether the decoration-layout property has been set"),
2101                             FALSE,
2102                             GTK_PARAM_READWRITE);
2103 
2104   /**
2105    * GtkHeaderBar:has-subtitle:
2106    *
2107    * If %TRUE, reserve space for a subtitle, even if none
2108    * is currently set.
2109    *
2110    * Since: 3.12
2111    */
2112   header_bar_props[PROP_HAS_SUBTITLE] =
2113       g_param_spec_boolean ("has-subtitle",
2114                             P_("Has Subtitle"),
2115                             P_("Whether to reserve space for a subtitle"),
2116                             TRUE,
2117                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
2118 
2119   g_object_class_install_properties (object_class, LAST_PROP, header_bar_props);
2120 
2121   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_HEADER_BAR_ACCESSIBLE);
2122   gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL);
2123   gtk_widget_class_set_css_name (widget_class, "headerbar");
2124 }
2125 
2126 static void
gtk_header_bar_init(GtkHeaderBar * bar)2127 gtk_header_bar_init (GtkHeaderBar *bar)
2128 {
2129   GtkHeaderBarPrivate *priv;
2130   GtkCssNode *widget_node;
2131 
2132   priv = gtk_header_bar_get_instance_private (bar);
2133 
2134   gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE);
2135 
2136   priv->title = NULL;
2137   priv->subtitle = NULL;
2138   priv->custom_title = NULL;
2139   priv->children = NULL;
2140   priv->spacing = DEFAULT_SPACING;
2141   priv->has_subtitle = TRUE;
2142   priv->decoration_layout = NULL;
2143   priv->decoration_layout_set = FALSE;
2144 
2145   init_sizing_box (bar);
2146   construct_label_box (bar);
2147 
2148   widget_node = gtk_widget_get_css_node (GTK_WIDGET (bar));
2149   priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
2150                                                      GTK_WIDGET (bar),
2151                                                      gtk_header_bar_get_content_size,
2152                                                      gtk_header_bar_allocate_contents,
2153                                                      gtk_header_bar_render_contents,
2154                                                      NULL,
2155                                                      NULL);
2156 
2157 }
2158 
2159 static void
gtk_header_bar_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * type)2160 gtk_header_bar_buildable_add_child (GtkBuildable *buildable,
2161                                     GtkBuilder   *builder,
2162                                     GObject      *child,
2163                                     const gchar  *type)
2164 {
2165   if (type && strcmp (type, "title") == 0)
2166     gtk_header_bar_set_custom_title (GTK_HEADER_BAR (buildable), GTK_WIDGET (child));
2167   else if (!type)
2168     gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
2169   else
2170     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GTK_HEADER_BAR (buildable), type);
2171 }
2172 
2173 static void
gtk_header_bar_buildable_init(GtkBuildableIface * iface)2174 gtk_header_bar_buildable_init (GtkBuildableIface *iface)
2175 {
2176   iface->add_child = gtk_header_bar_buildable_add_child;
2177 }
2178 
2179 /**
2180  * gtk_header_bar_pack_start:
2181  * @bar: A #GtkHeaderBar
2182  * @child: the #GtkWidget to be added to @bar
2183  *
2184  * Adds @child to @bar, packed with reference to the
2185  * start of the @bar.
2186  *
2187  * Since: 3.10
2188  */
2189 void
gtk_header_bar_pack_start(GtkHeaderBar * bar,GtkWidget * child)2190 gtk_header_bar_pack_start (GtkHeaderBar *bar,
2191                            GtkWidget    *child)
2192 {
2193   gtk_header_bar_pack (bar, child, GTK_PACK_START);
2194 }
2195 
2196 /**
2197  * gtk_header_bar_pack_end:
2198  * @bar: A #GtkHeaderBar
2199  * @child: the #GtkWidget to be added to @bar
2200  *
2201  * Adds @child to @bar, packed with reference to the
2202  * end of the @bar.
2203  *
2204  * Since: 3.10
2205  */
2206 void
gtk_header_bar_pack_end(GtkHeaderBar * bar,GtkWidget * child)2207 gtk_header_bar_pack_end (GtkHeaderBar *bar,
2208                          GtkWidget    *child)
2209 {
2210   gtk_header_bar_pack (bar, child, GTK_PACK_END);
2211 }
2212 
2213 /**
2214  * gtk_header_bar_new:
2215  *
2216  * Creates a new #GtkHeaderBar widget.
2217  *
2218  * Returns: a new #GtkHeaderBar
2219  *
2220  * Since: 3.10
2221  */
2222 GtkWidget *
gtk_header_bar_new(void)2223 gtk_header_bar_new (void)
2224 {
2225   return GTK_WIDGET (g_object_new (GTK_TYPE_HEADER_BAR, NULL));
2226 }
2227 
2228 /**
2229  * gtk_header_bar_get_show_close_button:
2230  * @bar: a #GtkHeaderBar
2231  *
2232  * Returns whether this header bar shows the standard window
2233  * decorations.
2234  *
2235  * Returns: %TRUE if the decorations are shown
2236  *
2237  * Since: 3.10
2238  */
2239 gboolean
gtk_header_bar_get_show_close_button(GtkHeaderBar * bar)2240 gtk_header_bar_get_show_close_button (GtkHeaderBar *bar)
2241 {
2242   GtkHeaderBarPrivate *priv;
2243 
2244   g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), FALSE);
2245 
2246   priv = gtk_header_bar_get_instance_private (bar);
2247 
2248   return priv->shows_wm_decorations;
2249 }
2250 
2251 /**
2252  * gtk_header_bar_set_show_close_button:
2253  * @bar: a #GtkHeaderBar
2254  * @setting: %TRUE to show standard window decorations
2255  *
2256  * Sets whether this header bar shows the standard window decorations,
2257  * including close, maximize, and minimize.
2258  *
2259  * Since: 3.10
2260  */
2261 void
gtk_header_bar_set_show_close_button(GtkHeaderBar * bar,gboolean setting)2262 gtk_header_bar_set_show_close_button (GtkHeaderBar *bar,
2263                                       gboolean      setting)
2264 {
2265   GtkHeaderBarPrivate *priv;
2266 
2267   g_return_if_fail (GTK_IS_HEADER_BAR (bar));
2268 
2269   priv = gtk_header_bar_get_instance_private (bar);
2270 
2271   setting = setting != FALSE;
2272 
2273   if (priv->shows_wm_decorations == setting)
2274     return;
2275 
2276   priv->shows_wm_decorations = setting;
2277   _gtk_header_bar_update_window_buttons (bar);
2278   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_SHOW_CLOSE_BUTTON]);
2279 }
2280 
2281 /**
2282  * gtk_header_bar_set_has_subtitle:
2283  * @bar: a #GtkHeaderBar
2284  * @setting: %TRUE to reserve space for a subtitle
2285  *
2286  * Sets whether the header bar should reserve space
2287  * for a subtitle, even if none is currently set.
2288  *
2289  * Since: 3.12
2290  */
2291 void
gtk_header_bar_set_has_subtitle(GtkHeaderBar * bar,gboolean setting)2292 gtk_header_bar_set_has_subtitle (GtkHeaderBar *bar,
2293                                  gboolean      setting)
2294 {
2295   GtkHeaderBarPrivate *priv;
2296 
2297   g_return_if_fail (GTK_IS_HEADER_BAR (bar));
2298 
2299   priv = gtk_header_bar_get_instance_private (bar);
2300 
2301   setting = setting != FALSE;
2302 
2303   if (priv->has_subtitle == setting)
2304     return;
2305 
2306   priv->has_subtitle = setting;
2307   gtk_widget_set_visible (priv->subtitle_sizing_label, setting || (priv->subtitle && priv->subtitle[0]));
2308 
2309   gtk_widget_queue_resize (GTK_WIDGET (bar));
2310 
2311   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_HAS_SUBTITLE]);
2312 }
2313 
2314 /**
2315  * gtk_header_bar_get_has_subtitle:
2316  * @bar: a #GtkHeaderBar
2317  *
2318  * Retrieves whether the header bar reserves space for
2319  * a subtitle, regardless if one is currently set or not.
2320  *
2321  * Returns: %TRUE if the header bar reserves space
2322  *     for a subtitle
2323  *
2324  * Since: 3.12
2325  */
2326 gboolean
gtk_header_bar_get_has_subtitle(GtkHeaderBar * bar)2327 gtk_header_bar_get_has_subtitle (GtkHeaderBar *bar)
2328 {
2329   GtkHeaderBarPrivate *priv;
2330 
2331   g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), FALSE);
2332 
2333   priv = gtk_header_bar_get_instance_private (bar);
2334 
2335   return priv->has_subtitle;
2336 }
2337 
2338 /**
2339  * gtk_header_bar_set_decoration_layout:
2340  * @bar: a #GtkHeaderBar
2341  * @layout: (allow-none): a decoration layout, or %NULL to
2342  *     unset the layout
2343  *
2344  * Sets the decoration layout for this header bar, overriding
2345  * the #GtkSettings:gtk-decoration-layout setting.
2346  *
2347  * There can be valid reasons for overriding the setting, such
2348  * as a header bar design that does not allow for buttons to take
2349  * room on the right, or only offers room for a single close button.
2350  * Split header bars are another example for overriding the
2351  * setting.
2352  *
2353  * The format of the string is button names, separated by commas.
2354  * A colon separates the buttons that should appear on the left
2355  * from those on the right. Recognized button names are minimize,
2356  * maximize, close, icon (the window icon) and menu (a menu button
2357  * for the fallback app menu).
2358  *
2359  * For example, “menu:minimize,maximize,close” specifies a menu
2360  * on the left, and minimize, maximize and close buttons on the right.
2361  *
2362  * Since: 3.12
2363  */
2364 void
gtk_header_bar_set_decoration_layout(GtkHeaderBar * bar,const gchar * layout)2365 gtk_header_bar_set_decoration_layout (GtkHeaderBar *bar,
2366                                       const gchar  *layout)
2367 {
2368   GtkHeaderBarPrivate *priv;
2369 
2370   g_return_if_fail (GTK_IS_HEADER_BAR (bar));
2371 
2372   priv = gtk_header_bar_get_instance_private (bar);
2373 
2374   g_free (priv->decoration_layout);
2375   priv->decoration_layout = g_strdup (layout);
2376   priv->decoration_layout_set = (layout != NULL);
2377 
2378   _gtk_header_bar_update_window_buttons (bar);
2379 
2380   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_DECORATION_LAYOUT]);
2381   g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_DECORATION_LAYOUT_SET]);
2382 }
2383 
2384 /**
2385  * gtk_header_bar_get_decoration_layout:
2386  * @bar: a #GtkHeaderBar
2387  *
2388  * Gets the decoration layout set with
2389  * gtk_header_bar_set_decoration_layout().
2390  *
2391  * Returns: the decoration layout
2392  *
2393  * Since: 3.12
2394  */
2395 const gchar *
gtk_header_bar_get_decoration_layout(GtkHeaderBar * bar)2396 gtk_header_bar_get_decoration_layout (GtkHeaderBar *bar)
2397 {
2398   GtkHeaderBarPrivate *priv;
2399 
2400   g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);
2401 
2402   priv = gtk_header_bar_get_instance_private (bar);
2403 
2404   return priv->decoration_layout;
2405 }
2406