gtkheaderbar.c 74.8 KB
Newer Older
Matthias Clasen's avatar
Matthias Clasen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * Copyright (c) 2013 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "config.h"

#include "gtkheaderbar.h"
23
#include "gtkheaderbarprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
24 25 26 27
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
28
#include "gtkcssnodeprivate.h"
29
#include "gtkcsscustomgadgetprivate.h"
30
#include "gtkwindowprivate.h"
31 32
#include "gtkwidgetprivate.h"
#include "gtkcontainerprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
33 34 35 36 37 38 39 40
#include "a11y/gtkcontaineraccessible.h"

#include <string.h>

/**
 * SECTION:gtkheaderbar
 * @Short_description: A box with a centered child
 * @Title: GtkHeaderBar
41
 * @See_also: #GtkBox, #GtkActionBar
Matthias Clasen's avatar
Matthias Clasen committed
42
 *
43 44
 * GtkHeaderBar is similar to a horizontal #GtkBox. It allows children to 
 * be placed at the start or the end. In addition, it allows a title and
45 46 47 48 49 50 51 52 53
 * subtitle to be displayed. The title will be centered with respect to
 * the width of the box, even if the children at either side take up
 * different amounts of space. The height of the titlebar will be
 * set to provide sufficient space for the subtitle, even if none is
 * currently set. If a subtitle is not needed, the space reservation
 * can be turned off with gtk_header_bar_set_has_subtitle().
 *
 * GtkHeaderBar can add typical window frame controls, such as minimize,
 * maximize and close buttons, or the window icon.
54 55 56 57
 *
 * For these reasons, GtkHeaderBar is the natural choice for use as the custom
 * titlebar widget of a #GtkWindow (see gtk_window_set_titlebar()), as it gives
 * features typical of titlebars while allowing the addition of child widgets.
Matthias Clasen's avatar
Matthias Clasen committed
58 59 60
 */

#define DEFAULT_SPACING 6
61
#define MIN_TITLE_CHARS 5
Matthias Clasen's avatar
Matthias Clasen committed
62 63 64 65

struct _GtkHeaderBarPrivate
{
  gchar *title;
66 67 68 69 70
  gchar *subtitle;
  GtkWidget *title_label;
  GtkWidget *subtitle_label;
  GtkWidget *label_box;
  GtkWidget *label_sizing_box;
71
  GtkWidget *subtitle_sizing_label;
Matthias Clasen's avatar
Matthias Clasen committed
72 73
  GtkWidget *custom_title;
  gint spacing;
74
  gboolean has_subtitle;
Matthias Clasen's avatar
Matthias Clasen committed
75 76

  GList *children;
77 78

  gboolean shows_wm_decorations;
79 80
  gchar *decoration_layout;
  gboolean decoration_layout_set;
81 82 83 84

  GtkWidget *titlebar_start_box;
  GtkWidget *titlebar_end_box;

85 86 87
  GtkWidget *titlebar_start_separator;
  GtkWidget *titlebar_end_separator;

88
  GtkWidget *titlebar_icon;
89 90

  GtkCssGadget *gadget;
Matthias Clasen's avatar
Matthias Clasen committed
91 92 93 94 95 96 97 98 99 100 101 102
};

typedef struct _Child Child;
struct _Child
{
  GtkWidget *widget;
  GtkPackType pack_type;
};

enum {
  PROP_0,
  PROP_TITLE,
103
  PROP_SUBTITLE,
104
  PROP_HAS_SUBTITLE,
Matthias Clasen's avatar
Matthias Clasen committed
105 106
  PROP_CUSTOM_TITLE,
  PROP_SPACING,
107
  PROP_SHOW_CLOSE_BUTTON,
108
  PROP_DECORATION_LAYOUT,
109 110
  PROP_DECORATION_LAYOUT_SET,
  LAST_PROP
Matthias Clasen's avatar
Matthias Clasen committed
111 112 113 114 115 116 117 118
};

enum {
  CHILD_PROP_0,
  CHILD_PROP_PACK_TYPE,
  CHILD_PROP_POSITION
};

119 120
static GParamSpec *header_bar_props[LAST_PROP] = { NULL, };

121
static void gtk_header_bar_buildable_init (GtkBuildableIface *iface);
Matthias Clasen's avatar
Matthias Clasen committed
122 123

G_DEFINE_TYPE_WITH_CODE (GtkHeaderBar, gtk_header_bar, GTK_TYPE_CONTAINER,
124
                         G_ADD_PRIVATE (GtkHeaderBar)
Matthias Clasen's avatar
Matthias Clasen committed
125
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
126
                                                gtk_header_bar_buildable_init));
Matthias Clasen's avatar
Matthias Clasen committed
127 128

static void
129
init_sizing_box (GtkHeaderBar *bar)
Matthias Clasen's avatar
Matthias Clasen committed
130
{
131
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
132
  GtkWidget *w;
133
  GtkStyleContext *context;
134 135 136 137 138 139

  /* We use this box to always request size for the two labels (title
   * and subtitle) as if they were always visible, but then allocate
   * the real label box with its actual size, to keep it center-aligned
   * in case we have only the title.
   */
140 141 142
  w = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_widget_show (w);
  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 149 150 151
  gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0);
  gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
  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 158 159 160
  gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0);
  gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
  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
}
Matthias Clasen's avatar
Matthias Clasen committed
164

165 166 167 168 169
static GtkWidget *
create_title_box (const char *title,
                  const char *subtitle,
                  GtkWidget **ret_title_label,
                  GtkWidget **ret_subtitle_label)
170 171 172 173
{
  GtkWidget *label_box;
  GtkWidget *title_label;
  GtkWidget *subtitle_label;
174
  GtkStyleContext *context;
175 176 177 178 179 180

  label_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_widget_set_valign (label_box, GTK_ALIGN_CENTER);
  gtk_widget_show (label_box);

  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 184 185 186 187
  gtk_label_set_line_wrap (GTK_LABEL (title_label), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (title_label), TRUE);
  gtk_label_set_ellipsize (GTK_LABEL (title_label), PANGO_ELLIPSIZE_END);
  gtk_box_pack_start (GTK_BOX (label_box), title_label, FALSE, FALSE, 0);
  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 194 195 196 197
  gtk_label_set_line_wrap (GTK_LABEL (subtitle_label), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (subtitle_label), TRUE);
  gtk_label_set_ellipsize (GTK_LABEL (subtitle_label), PANGO_ELLIPSIZE_END);
  gtk_box_pack_start (GTK_BOX (label_box), subtitle_label, FALSE, FALSE, 0);
  gtk_widget_set_no_show_all (subtitle_label, TRUE);
198
  gtk_widget_set_visible (subtitle_label, subtitle && subtitle[0]);
199 200 201 202 203 204 205 206 207

  if (ret_title_label)
    *ret_title_label = title_label;
  if (ret_subtitle_label)
    *ret_subtitle_label = subtitle_label;

  return label_box;
}

208
gboolean
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 217 218

  if (priv->titlebar_icon == NULL)
    return FALSE;

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 228 229 230 231 232
      cairo_surface_t *surface;

      surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, gtk_widget_get_window (priv->titlebar_icon));

      gtk_image_set_from_surface (GTK_IMAGE (priv->titlebar_icon), surface);
      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 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
static void
_gtk_header_bar_update_separator_visibility (GtkHeaderBar *bar)
{
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
  gboolean have_visible_at_start = FALSE;
  gboolean have_visible_at_end = FALSE;
  GList *l;
  
  for (l = priv->children; l != NULL; l = l->next)
    {
      Child *child = l->data;

      if (gtk_widget_get_visible (child->widget))
        {
          if (child->pack_type == GTK_PACK_START)
            have_visible_at_start = TRUE;
          else
            have_visible_at_end = TRUE;
        }
    }

  if (priv->titlebar_start_separator != NULL)
    gtk_widget_set_visible (priv->titlebar_start_separator, have_visible_at_start);

  if (priv->titlebar_end_separator != NULL)
    gtk_widget_set_visible (priv->titlebar_end_separator, have_visible_at_end);
}

270 271
void
_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 276 277 278 279 280 281
  GtkWindow *window;
  GtkTextDirection direction;
  gchar *layout_desc;
  gchar **tokens, **t;
  gint i, j;
  GMenuModel *menu;
  gboolean shown_by_shell;
282
  gboolean is_sovereign_window;
283

284 285
  toplevel = gtk_widget_get_toplevel (widget);
  if (!gtk_widget_is_toplevel (toplevel))
286 287
    return;

288 289
  if (priv->titlebar_start_box)
    {
290
      gtk_widget_unparent (priv->titlebar_start_box);
291
      priv->titlebar_start_box = NULL;
292
      priv->titlebar_start_separator = NULL;
293 294 295
    }
  if (priv->titlebar_end_box)
    {
296
      gtk_widget_unparent (priv->titlebar_end_box);
297
      priv->titlebar_end_box = NULL;
298
      priv->titlebar_end_separator = NULL;
299
    }
300

301 302
  priv->titlebar_icon = NULL;

Matthias Clasen's avatar
Matthias Clasen committed
303 304 305
  if (!priv->shows_wm_decorations)
    return;

306 307 308 309 310 311
  direction = gtk_widget_get_direction (widget);

  g_object_get (gtk_widget_get_settings (widget),
                "gtk-shell-shows-app-menu", &shown_by_shell,
                "gtk-decoration-layout", &layout_desc,
                NULL);
312

313 314 315 316 317 318
  if (priv->decoration_layout_set)
    {
      g_free (layout_desc);
      layout_desc = g_strdup (priv->decoration_layout);
    }

319
  window = GTK_WINDOW (toplevel);
320

321 322 323 324 325
  if (!shown_by_shell && gtk_window_get_application (window))
    menu = gtk_application_get_app_menu (gtk_window_get_application (window));
  else
    menu = NULL;

326 327 328
  is_sovereign_window = (!gtk_window_get_modal (window) &&
                          gtk_window_get_transient_for (window) == NULL &&
                          gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL);
329

330 331
  tokens = g_strsplit (layout_desc, ":", 2);
  if (tokens)
332
    {
333
      for (i = 0; i < 2; i++)
334
        {
335 336 337 338 339
          GtkWidget *box;
          GtkWidget *separator;
          int n_children = 0;

          if (tokens[i] == NULL)
340
            break;
341 342 343 344

          t = g_strsplit (tokens[i], ",", -1);

          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 349 350 351 352 353 354 355
          box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, priv->spacing);

          for (j = 0; t[j]; j++)
            {
              GtkWidget *button = NULL;
              GtkWidget *image = NULL;
              AtkObject *accessible;

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 365
                  gtk_widget_set_size_request (button, 20, 20);
                  gtk_widget_show (button);
366

367
                  if (!_gtk_header_bar_update_window_icon (bar, window))
368 369 370 371 372
                    {
                      gtk_widget_destroy (button);
                      priv->titlebar_icon = NULL;
                      button = NULL;
                    }
373
                }
374 375
              else if (strcmp (t[j], "menu") == 0 &&
                       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 386 387
                  gtk_container_add (GTK_CONTAINER (button), image);
                  gtk_widget_set_can_focus (button, FALSE);
                  gtk_widget_show_all (button);
388

389 390 391
                  accessible = gtk_widget_get_accessible (button);
                  if (GTK_IS_ACCESSIBLE (accessible))
                    atk_object_set_name (accessible, _("Application menu"));
392

393 394
                  priv->titlebar_icon = image;
                  if (!_gtk_header_bar_update_window_icon (bar, window))
395 396
                    gtk_image_set_from_icon_name (GTK_IMAGE (priv->titlebar_icon),
                                                  "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 406 407 408 409 410 411
                  image = gtk_image_new_from_icon_name ("window-minimize-symbolic", GTK_ICON_SIZE_MENU);
                  g_object_set (image, "use-fallback", TRUE, NULL);
                  gtk_container_add (GTK_CONTAINER (button), image);
                  gtk_widget_set_can_focus (button, FALSE);
                  gtk_widget_show_all (button);
                  g_signal_connect_swapped (button, "clicked",
                                            G_CALLBACK (gtk_window_iconify), window);
412

413 414 415 416 417 418
                  accessible = gtk_widget_get_accessible (button);
                  if (GTK_IS_ACCESSIBLE (accessible))
                    atk_object_set_name (accessible, _("Minimize"));
                }
              else if (strcmp (t[j], "maximize") == 0 &&
                       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 425

                  icon_name = maximized ? "window-restore-symbolic" : "window-maximize-symbolic";
                  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 430 431 432 433 434 435
                  image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
                  g_object_set (image, "use-fallback", TRUE, NULL);
                  gtk_container_add (GTK_CONTAINER (button), image);
                  gtk_widget_set_can_focus (button, FALSE);
                  gtk_widget_show_all (button);
                  g_signal_connect_swapped (button, "clicked",
                                            G_CALLBACK (_gtk_window_toggle_maximized), window);
436

437 438 439 440 441
                  accessible = gtk_widget_get_accessible (button);
                  if (GTK_IS_ACCESSIBLE (accessible))
                    atk_object_set_name (accessible, maximized ? _("Restore") : _("Maximize"));
                }
              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 447
                  image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU);
                  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 450 451 452 453 454
                  g_object_set (image, "use-fallback", TRUE, NULL);
                  gtk_container_add (GTK_CONTAINER (button), image);
                  gtk_widget_set_can_focus (button, FALSE);
                  gtk_widget_show_all (button);
                  g_signal_connect_swapped (button, "clicked",
                                            G_CALLBACK (gtk_window_close), window);
455

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
                  accessible = gtk_widget_get_accessible (button);
                  if (GTK_IS_ACCESSIBLE (accessible))
                    atk_object_set_name (accessible, _("Close"));
                }

              if (button)
                {
                  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
                  n_children ++;
                }
            }
          g_strfreev (t);

          if (n_children == 0)
            {
471 472 473 474
              g_object_ref_sink (box);
              g_object_unref (box);
              g_object_ref_sink (separator);
              g_object_unref (separator);
475 476 477
              continue;
            }

478
          gtk_box_pack_start (GTK_BOX (box), separator, FALSE, FALSE, 0);
479
          if (i == 1)
480 481
            gtk_box_reorder_child (GTK_BOX (box), separator, 0);

482 483
          if ((direction == GTK_TEXT_DIR_LTR && i == 0) ||
              (direction == GTK_TEXT_DIR_RTL && i == 1))
484 485 486 487 488 489
            {
              gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_LEFT);
              gtk_css_node_insert_after (gtk_widget_get_css_node (GTK_WIDGET (bar)),
                                         gtk_widget_get_css_node (box),
                                         NULL);
            }
490
          else
491 492 493 494 495 496
            {
              gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_RIGHT);
              gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (bar)),
                                          gtk_widget_get_css_node (box),
                                          NULL);
            }
497

498 499 500
          gtk_widget_show (box);
          gtk_widget_set_parent (box, GTK_WIDGET (bar));

501
          if (i == 0)
502 503 504 505
            {
              priv->titlebar_start_box = box;
              priv->titlebar_start_separator = separator;
            }
506
          else
507 508 509 510
            {
              priv->titlebar_end_box = box;
              priv->titlebar_end_separator = separator;
            }
511
        }
512
      g_strfreev (tokens);
513
    }
514
  g_free (layout_desc);
515 516

  _gtk_header_bar_update_separator_visibility (bar);
517
}
518

519
gboolean
520
_gtk_header_bar_shows_app_menu (GtkHeaderBar *bar)
521 522
{
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
523 524 525 526 527 528 529 530 531 532 533 534 535
  GtkWindow *window;
  gchar *layout_desc;
  gboolean ret;

  window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (bar)));
  gtk_widget_style_get (GTK_WIDGET (window),
                        "decoration-button-layout", &layout_desc,
                        NULL);

  ret = priv->shows_wm_decorations &&
        (layout_desc && strstr (layout_desc, "menu"));

  g_free (layout_desc);
536

537
  return ret;
538 539
}

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
/* As an intended side effect, this function allows @child
 * to be the title/label box */
static void
gtk_header_bar_reorder_css_node (GtkHeaderBar *bar,
                                 GtkPackType   pack_type,
                                 GtkWidget    *widget)
{
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
  GtkWidget *previous_widget;
  GList *l;
  
  if (pack_type == GTK_PACK_START)
    previous_widget = priv->titlebar_start_box;
  else
    previous_widget = priv->titlebar_end_box;

  for (l = priv->children; l; l = l->next)
    {
      Child *iter = l->data;

      if (iter->widget == widget)
        break;

      if (iter->pack_type == pack_type)
        previous_widget = iter->widget;
    }

  if ((pack_type == GTK_PACK_START)
      ^ (gtk_widget_get_direction (GTK_WIDGET (bar)) == GTK_TEXT_DIR_LTR))
    gtk_css_node_insert_after (gtk_widget_get_css_node (GTK_WIDGET (bar)),
                               gtk_widget_get_css_node (widget),
                               previous_widget ? gtk_widget_get_css_node (previous_widget) : NULL);
  else
    gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (bar)),
                                gtk_widget_get_css_node (widget),
                                previous_widget ? gtk_widget_get_css_node (previous_widget) : NULL);
}

578 579 580
static void
construct_label_box (GtkHeaderBar *bar)
{
581
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
582

583 584
  g_assert (priv->label_box == NULL);

585 586 587 588
  priv->label_box = create_title_box (priv->title,
                                      priv->subtitle,
                                      &priv->title_label,
                                      &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
}

Matthias Clasen's avatar
Matthias Clasen committed
593 594 595
static gint
count_visible_children (GtkHeaderBar *bar)
{
596
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
597 598 599 600 601
  GList *l;
  Child *child;
  gint n;

  n = 0;
602
  for (l = priv->children; l; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
    {
      child = l->data;
      if (gtk_widget_get_visible (child->widget))
        n++;
    }

  return n;
}

static gboolean
add_child_size (GtkWidget      *child,
                GtkOrientation  orientation,
                gint           *minimum,
                gint           *natural)
{
  gint child_minimum, child_natural;

  if (!gtk_widget_get_visible (child))
    return FALSE;

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    gtk_widget_get_preferred_width (child, &child_minimum, &child_natural);
  else
    gtk_widget_get_preferred_height (child, &child_minimum, &child_natural);

  if (GTK_ORIENTATION_HORIZONTAL == orientation)
    {
      *minimum += child_minimum;
      *natural += child_natural;
    }
  else
    {
      *minimum = MAX (*minimum, child_minimum);
      *natural = MAX (*natural, child_natural);
    }

  return TRUE;
}

static void
gtk_header_bar_get_size (GtkWidget      *widget,
                         GtkOrientation  orientation,
                         gint           *minimum_size,
                         gint           *natural_size)
{
  GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
649
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
650 651 652
  GList *l;
  gint nvis_children;
  gint minimum, natural;
653
  gint center_min, center_nat;
Matthias Clasen's avatar
Matthias Clasen committed
654 655 656 657 658 659 660 661 662 663 664 665

  minimum = natural = 0;
  nvis_children = 0;

  for (l = priv->children; l; l = l->next)
    {
      Child *child = l->data;

      if (add_child_size (child->widget, orientation, &minimum, &natural))
        nvis_children += 1;
    }

666
  center_min = center_nat = 0;
667
  if (priv->label_box != NULL)
668
    {
669 670 671 672 673 674
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
        add_child_size (priv->label_box, orientation, &center_min, &center_nat);
      else
        add_child_size (priv->label_sizing_box, orientation, &center_min, &center_nat);

      if (_gtk_widget_get_visible (priv->label_sizing_box))
675 676
        nvis_children += 1;
    }
Matthias Clasen's avatar
Matthias Clasen committed
677

678
  if (priv->custom_title != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
679
    {
680
      if (add_child_size (priv->custom_title, orientation, &center_min, &center_nat))
Matthias Clasen's avatar
Matthias Clasen committed
681 682 683
        nvis_children += 1;
    }

684
  if (priv->titlebar_start_box != NULL)
685
    {
686
      if (add_child_size (priv->titlebar_start_box, orientation, &minimum, &natural))
687 688 689
        nvis_children += 1;
    }

690
  if (priv->titlebar_end_box != NULL)
691
    {
692
      if (add_child_size (priv->titlebar_end_box, orientation, &minimum, &natural))
693 694 695
        nvis_children += 1;
    }

696
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
Matthias Clasen's avatar
Matthias Clasen committed
697
    {
698 699
      minimum += center_min;
      natural += center_nat;
Matthias Clasen's avatar
Matthias Clasen committed
700
    }
701
  else
Matthias Clasen's avatar
Matthias Clasen committed
702
    {
703 704
      minimum = MAX (minimum, center_min);
      natural = MAX (natural, center_nat);
Matthias Clasen's avatar
Matthias Clasen committed
705
    }
706 707

  if (nvis_children > 0 && orientation == GTK_ORIENTATION_HORIZONTAL)
Matthias Clasen's avatar
Matthias Clasen committed
708
    {
709 710
      minimum += nvis_children * priv->spacing;
      natural += nvis_children * priv->spacing;
Matthias Clasen's avatar
Matthias Clasen committed
711 712
    }

713 714
  *minimum_size = minimum;
  *natural_size = natural;
Matthias Clasen's avatar
Matthias Clasen committed
715 716 717 718 719 720 721 722 723
}

static void
gtk_header_bar_compute_size_for_orientation (GtkWidget *widget,
                                             gint       avail_size,
                                             gint      *minimum_size,
                                             gint      *natural_size)
{
  GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
724
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
  GList *children;
  gint required_size = 0;
  gint required_natural = 0;
  gint child_size;
  gint child_natural;
  gint nvis_children;

  nvis_children = 0;

  for (children = priv->children; children != NULL; children = children->next)
    {
      Child *child = children->data;

      if (gtk_widget_get_visible (child->widget))
        {
          gtk_widget_get_preferred_width_for_height (child->widget,
                                                     avail_size, &child_size, &child_natural);

          required_size += child_size;
          required_natural += child_natural;

          nvis_children += 1;
        }
    }

750
  if (priv->label_box != NULL)
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
    {
      gtk_widget_get_preferred_width (priv->label_sizing_box,
                                      &child_size, &child_natural);
      required_size += child_size;
      required_natural += child_natural;
    }

  if (priv->custom_title != NULL &&
      gtk_widget_get_visible (priv->custom_title))
    {
      gtk_widget_get_preferred_width (priv->custom_title,
                                      &child_size, &child_natural);
      required_size += child_size;
      required_natural += child_natural;
    }
Matthias Clasen's avatar
Matthias Clasen committed
766

767
  if (priv->titlebar_start_box != NULL)
768
    {
769
      gtk_widget_get_preferred_width (priv->titlebar_start_box,
770 771 772
                                      &child_size, &child_natural);
      required_size += child_size;
      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 780 781
                                      &child_size, &child_natural);
      required_size += child_size;
      required_natural += child_natural;
782
      nvis_children += 1;
783 784
    }

Matthias Clasen's avatar
Matthias Clasen committed
785 786 787 788 789 790
  if (nvis_children > 0)
    {
      required_size += nvis_children * priv->spacing;
      required_natural += nvis_children * priv->spacing;
    }

791 792
  *minimum_size = required_size;
  *natural_size = required_natural;
Matthias Clasen's avatar
Matthias Clasen committed
793 794 795 796 797 798 799 800 801
}

static void
gtk_header_bar_compute_size_for_opposing_orientation (GtkWidget *widget,
                                                      gint       avail_size,
                                                      gint      *minimum_size,
                                                      gint      *natural_size)
{
  GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
802
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
803 804 805 806 807 808 809
  Child *child;
  GList *children;
  gint nvis_children;
  gint computed_minimum = 0;
  gint computed_natural = 0;
  GtkRequestedSize *sizes;
  GtkPackType packing;
810
  gint size = 0;
Matthias Clasen's avatar
Matthias Clasen committed
811 812 813 814
  gint i;
  gint child_size;
  gint child_minimum;
  gint child_natural;
815
  gint center_min, center_nat;
Matthias Clasen's avatar
Matthias Clasen committed
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841

  nvis_children = count_visible_children (bar);

  if (nvis_children <= 0)
    return;

  sizes = g_newa (GtkRequestedSize, nvis_children);

  /* Retrieve desired size for visible children */
  for (i = 0, children = priv->children; children; children = children->next)
    {
      child = children->data;

      if (gtk_widget_get_visible (child->widget))
        {
          gtk_widget_get_preferred_width (child->widget,
                                          &sizes[i].minimum_size,
                                          &sizes[i].natural_size);

          size -= sizes[i].minimum_size;
          sizes[i].data = child;
          i += 1;
        }
    }

  /* Bring children up to size first */
842
  size = gtk_distribute_natural_allocation (MAX (0, avail_size), nvis_children, sizes);
Matthias Clasen's avatar
Matthias Clasen committed
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875

  /* Allocate child positions. */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing)
    {
      for (i = 0, children = priv->children; children; children = children->next)
        {
          child = children->data;

          /* If widget is not visible, skip it. */
          if (!gtk_widget_get_visible (child->widget))
            continue;

          /* If widget is packed differently skip it, but still increment i,
           * since widget is visible and will be handled in next loop
           * iteration.
           */
          if (child->pack_type != packing)
            {
              i++;
              continue;
            }

          child_size = sizes[i].minimum_size;

          gtk_widget_get_preferred_height_for_width (child->widget,
                                                     child_size, &child_minimum, &child_natural);

          computed_minimum = MAX (computed_minimum, child_minimum);
          computed_natural = MAX (computed_natural, child_natural);
        }
      i += 1;
    }

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 884 885 886
    }

  if (priv->custom_title != NULL &&
      gtk_widget_get_visible (priv->custom_title))
    {
      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 894 895 896 897
                                       &child_minimum, &child_natural);
      computed_minimum = MAX (computed_minimum, child_minimum);
      computed_natural = MAX (computed_natural, child_natural);
    }

898
  if (priv->titlebar_end_box != NULL)
899
    {
900
      gtk_widget_get_preferred_height (priv->titlebar_end_box,
901 902 903 904 905
                                       &child_minimum, &child_natural);
      computed_minimum = MAX (computed_minimum, child_minimum);
      computed_natural = MAX (computed_natural, child_natural);
    }

906 907
  *minimum_size = computed_minimum;
  *natural_size = computed_natural;
Matthias Clasen's avatar
Matthias Clasen committed
908 909
}

910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
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)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);

  if (for_size < 0)
    gtk_header_bar_get_size (widget, orientation, minimum, natural);
  else if (orientation == GTK_ORIENTATION_HORIZONTAL)
    gtk_header_bar_compute_size_for_orientation (widget, for_size, minimum, natural);
  else
    gtk_header_bar_compute_size_for_opposing_orientation (widget, for_size, minimum, natural);
}

Matthias Clasen's avatar
Matthias Clasen committed
930 931
static void
gtk_header_bar_get_preferred_width (GtkWidget *widget,
932 933
                                    gint      *minimum,
                                    gint      *natural)
Matthias Clasen's avatar
Matthias Clasen committed
934
{
935 936 937 938 939 940 941
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));

  gtk_css_gadget_get_preferred_size (priv->gadget,
                                     GTK_ORIENTATION_HORIZONTAL,
                                     -1,
                                     minimum, natural,
                                     NULL, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
942 943 944 945
}

static void
gtk_header_bar_get_preferred_height (GtkWidget *widget,
946 947
                                     gint      *minimum,
                                     gint      *natural)
Matthias Clasen's avatar
Matthias Clasen committed
948
{
949 950 951 952 953 954 955
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));

  gtk_css_gadget_get_preferred_size (priv->gadget,
                                     GTK_ORIENTATION_VERTICAL,
                                     -1,
                                     minimum, natural,
                                     NULL, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
956 957 958 959 960
}

static void
gtk_header_bar_get_preferred_width_for_height (GtkWidget *widget,
                                               gint       height,
961 962
                                               gint      *minimum,
                                               gint      *natural)
Matthias Clasen's avatar
Matthias Clasen committed
963
{
964 965 966 967 968 969 970
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));

  gtk_css_gadget_get_preferred_size (priv->gadget,
                                     GTK_ORIENTATION_HORIZONTAL,
                                     height,
                                     minimum, natural,
                                     NULL, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
971 972 973 974 975
}

static void
gtk_header_bar_get_preferred_height_for_width (GtkWidget *widget,
                                               gint       width,
976 977
                                               gint      *minimum,
                                               gint      *natural)
Matthias Clasen's avatar
Matthias Clasen committed
978
{
979 980 981 982 983 984 985
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));

  gtk_css_gadget_get_preferred_size (priv->gadget,
                                     GTK_ORIENTATION_VERTICAL,
                                     width,
                                     minimum, natural,
                                     NULL, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
986 987 988 989 990 991
}

static void
gtk_header_bar_size_allocate (GtkWidget     *widget,
                              GtkAllocation *allocation)
{
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (GTK_HEADER_BAR (widget));
  GtkAllocation clip;

  gtk_widget_set_allocation (widget, allocation);

  gtk_css_gadget_allocate (priv->gadget, allocation, gtk_widget_get_allocated_baseline (widget), &clip);

  gtk_widget_set_clip (widget, &clip);
}

static void
gtk_header_bar_allocate_contents (GtkCssGadget        *gadget,
                                  const GtkAllocation *allocation,
                                  int                  baseline,
                                  GtkAllocation       *out_clip,
                                  gpointer             unused)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1010
  GtkWidget *title_widget;
Matthias Clasen's avatar
Matthias Clasen committed
1011
  GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
1012
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
1013 1014 1015 1016 1017
  GtkRequestedSize *sizes;
  gint width, height;
  gint nvis_children;
  gint title_minimum_size;
  gint title_natural_size;
1018
  gboolean title_expands = FALSE;
1019
  gint start_width, end_width;
1020 1021 1022
  gint uniform_expand_bonus[2] = { 0 };
  gint leftover_expand_bonus[2] = { 0 };
  gint nexpand_children[2] = { 0 };
Matthias Clasen's avatar
Matthias Clasen committed
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
  gint side[2];
  GList *l;
  gint i;
  Child *child;
  GtkPackType packing;
  GtkAllocation child_allocation;
  gint x;
  gint child_size;
  GtkTextDirection direction;

  direction = gtk_widget_get_direction (widget);
  nvis_children = count_visible_children (bar);
  sizes = g_newa (GtkRequestedSize, nvis_children);

1037 1038
  width = allocation->width - nvis_children * priv->spacing;
  height = allocation->height;
Matthias Clasen's avatar
Matthias Clasen committed
1039 1040 1041 1042 1043 1044 1045 1046

  i = 0;
  for (l = priv->children; l; l = l->next)
    {
      child = l->data;
      if (!gtk_widget_get_visible (child->widget))
        continue;

1047 1048 1049
      if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL))
        nexpand_children[child->pack_type]++;

Matthias Clasen's avatar
Matthias Clasen committed
1050 1051 1052 1053 1054 1055 1056 1057
      gtk_widget_get_preferred_width_for_height (child->widget,
                                                 height,
                                                 &sizes[i].minimum_size,
                                                 &sizes[i].natural_size);
      width -= sizes[i].minimum_size;
      i++;
    }

1058 1059 1060
  title_minimum_size = 0;
  title_natural_size = 0;

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 1066 1067 1068 1069
    title_widget = priv->label_box;
  else
    title_widget = NULL;

  if (title_widget)
1070 1071 1072 1073 1074 1075
    {
      gtk_widget_get_preferred_width_for_height (title_widget,
                                                 height,
                                                 &title_minimum_size,
                                                 &title_natural_size);
      width -= title_natural_size;
Matthias Clasen's avatar
Matthias Clasen committed
1076

1077 1078
      title_expands = gtk_widget_compute_expand (title_widget, GTK_ORIENTATION_HORIZONTAL);
    }
1079

1080 1081
  start_width = 0;
  if (priv->titlebar_start_box != NULL)
1082 1083
    {
      gint min, nat;
1084
      gtk_widget_get_preferred_width_for_height (priv->titlebar_start_box,
1085 1086
                                                 height,
                                                 &min, &nat);
1087
      start_width = nat + priv->spacing;
1088
    }
1089
  width -= start_width;
1090

1091 1092
  end_width = 0;
  if (priv->titlebar_end_box != NULL)
1093 1094
    {
      gint min, nat;
1095
      gtk_widget_get_preferred_width_for_height (priv->titlebar_end_box,
1096 1097
                                                 height,
                                                 &min, &nat);
1098
      end_width = nat + priv->spacing;
1099
    }
1100
  width -= end_width;
1101

Matthias Clasen's avatar
Matthias Clasen committed
1102 1103
  width = gtk_distribute_natural_allocation (MAX (0, width), nvis_children, sizes);

1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
  /* compute the nominal size of the children filling up each side of
   * the title in titlebar
   */
  side[0] = start_width;
  side[1] = end_width;
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
    {
      i = 0;
      for (l = priv->children; l != NULL; l = l->next)
        {
          child = l->data;
          if (!gtk_widget_get_visible (child->widget))
            continue;

          if (child->pack_type == packing)
            side[packing] += sizes[i].minimum_size + priv->spacing;

          i++;
        }
    }

1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
  /* figure out how much space is left on each side of the title,
   * and earkmark that space for the expanded children.
   *
   * If the title itself is expanded, then it gets half the spoils
   * from each side.
   */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
    {
      gint side_free_space;

      side_free_space = allocation->width / 2 - title_natural_size / 2 - side[packing];

      if (side_free_space > 0 && nexpand_children[packing] > 0)
        {
          width -= side_free_space;

          if (title_expands)
            side_free_space -= side_free_space / 2;

          side[packing] += side_free_space;
          uniform_expand_bonus[packing] = side_free_space / nexpand_children[packing];
          leftover_expand_bonus[packing] = side_free_space % nexpand_children[packing];
        }
    }

1150
  /* allocate the children on both sides of the title */
Matthias Clasen's avatar
Matthias Clasen committed
1151 1152
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
    {
1153
      child_allocation.y = allocation->y;
Matthias Clasen's avatar
Matthias Clasen committed
1154 1155
      child_allocation.height = height;
      if (packing == GTK_PACK_START)
1156
        x = allocation->x + start_width;
Matthias Clasen's avatar
Matthias Clasen committed
1157
      else
1158
        x = allocation->x + allocation->width - end_width;
Matthias Clasen's avatar
Matthias Clasen committed
1159

1160 1161
      i = 0;
      for (l = priv->children; l != NULL; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1162 1163 1164 1165 1166 1167 1168 1169 1170 1171
        {
          child = l->data;
          if (!gtk_widget_get_visible (child->widget))
            continue;

          if (child->pack_type != packing)
            goto next;

          child_size = sizes[i].minimum_size;

1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
          /* if this child is expanded, give it extra space from the reserves */
          if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL))
            {
              gint expand_bonus;

              expand_bonus = uniform_expand_bonus[packing];

              if (leftover_expand_bonus[packing] > 0)
                {
                  expand_bonus++;
                  leftover_expand_bonus[packing]--;
                }

              child_size += expand_bonus;
            }

Matthias Clasen's avatar
Matthias Clasen committed
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
          child_allocation.width = child_size;

          if (packing == GTK_PACK_START)
            {
              child_allocation.x = x;
              x += child_size;
              x += priv->spacing;
            }
          else
            {
              x -= child_size;
              child_allocation.x = x;
              x -= priv->spacing;
            }

          if (direction == GTK_TEXT_DIR_RTL)
            child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;

          gtk_widget_size_allocate (child->widget, &child_allocation);

        next:
1209
          i++;
Matthias Clasen's avatar
Matthias Clasen committed
1210 1211 1212
        }
    }

1213 1214 1215 1216 1217
  /* We don't enforce css borders on the center widget, to make
   * title/subtitle combinations fit without growing the header
   */
  child_allocation.y = allocation->y;
  child_allocation.height = allocation->height;
Matthias Clasen's avatar
Matthias Clasen committed
1218

1219
  child_size = MIN (allocation->width - side[0] - side[1], title_natural_size);
Matthias Clasen's avatar
Matthias Clasen committed
1220 1221 1222 1223

  child_allocation.x = allocation->x + (allocation->width - child_size) / 2;
  child_allocation.width = child_size;

1224 1225 1226 1227 1228 1229 1230 1231 1232
  /* if the title widget is expanded, then grow it by all the available
   * free space, and recenter it
   */
  if (title_expands && width > 0)
    {
      child_allocation.width += width;
      child_allocation.x -= width / 2;
    }

Matthias Clasen's avatar
Matthias Clasen committed
1233 1234 1235 1236 1237 1238 1239 1240
  if (allocation->x + side[0] > child_allocation.x)
    child_allocation.x = allocation->x + side[0];
  else if (allocation->x + allocation->width - side[1] < child_allocation.x + child_allocation.width)
    child_allocation.x = allocation->x + allocation->width - side[1] - child_allocation.width;

  if (direction == GTK_TEXT_DIR_RTL)
    child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;

1241 1242
  if (title_widget != NULL)
    gtk_widget_size_allocate (title_widget, &child_allocation);
1243

1244
  child_allocation.y = allocation->y;
1245 1246
  child_allocation.height = height;

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);
Matthias Clasen's avatar
Matthias Clasen committed
1270 1271 1272 1273 1274
}

/**
 * gtk_header_bar_set_title:
 * @bar: a #GtkHeaderBar
1275
 * @title: (allow-none): a title, or %NULL
Matthias Clasen's avatar
Matthias Clasen committed
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286
 *
 * Sets the title of the #GtkHeaderBar. The title should help a user
 * identify the current view. A good title should not include the
 * application name.
 *
 * Since: 3.10
 */
void
gtk_header_bar_set_title (GtkHeaderBar *bar,
                          const gchar  *title)
{
1287
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1288
  gchar *new_title;
Matthias Clasen's avatar
Matthias Clasen committed
1289 1290 1291 1292 1293 1294 1295

  g_return_if_fail (GTK_IS_HEADER_BAR (bar));

  new_title = g_strdup (title);
  g_free (priv->title);
  priv->title = new_title;

1296
  if (priv->title_label != NULL)
1297
    {
1298
      gtk_label_set_label (GTK_LABEL (priv->title_label), priv->title);
1299 1300
      gtk_widget_queue_resize (GTK_WIDGET (bar));
    }
Matthias Clasen's avatar
Matthias Clasen committed
1301

1302
  g_object_notify_by_pspec (G_OBJECT (bar), header_bar_props[PROP_TITLE]);
Matthias Clasen's avatar
Matthias Clasen committed
1303 1304 1305 1306 1307 1308 1309 1310
}

/**
 * gtk_header_bar_get_title:
 * @bar: a #GtkHeaderBar
 *
 * Retrieves the title of the header. See gtk_header_bar_set_title().
 *
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
Matthias Clasen's avatar
Matthias Clasen committed
1313 1314 1315 1316 1317 1318 1319
 *    and must not be modified or freed.
 *
 * Since: 3.10
 */
const gchar *
gtk_header_bar_get_title (GtkHeaderBar *bar)
{
1320 1321
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);

Matthias Clasen's avatar
Matthias Clasen committed
1322 1323
  g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);

1324
  return priv->title;
Matthias Clasen's avatar
Matthias Clasen committed
1325 1326
}

1327 1328 1329
/**
 * gtk_header_bar_set_subtitle:
 * @bar: a #GtkHeaderBar
1330
 * @subtitle: (allow-none): a subtitle, or %NULL
1331 1332 1333 1334
 *
 * Sets the subtitle of the #GtkHeaderBar. The title should give a user
 * an additional detail to help him identify the current view.
 *
1335 1336 1337
 * Note that GtkHeaderBar by default reserves room for the subtitle,
 * even if none is currently set. If this is not desired, set the
 * #GtkHeaderBar:has-subtitle property to %FALSE.
1338
 *
1339 1340 1341 1342 1343 1344
 * Since: 3.10
 */
void
gtk_header_bar_set_subtitle (GtkHeaderBar *bar,
                             const gchar  *subtitle)
{
1345
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356
  gchar *new_subtitle;

  g_return_if_fail (GTK_IS_HEADER_BAR (bar));

  new_subtitle = g_strdup (subtitle);
  g_free (priv->subtitle);
  priv->subtitle = new_subtitle;

  if (priv->subtitle_label != NULL)
    {
      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 1359 1360
      gtk_widget_queue_resize (GTK_WIDGET (bar));
    }

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 1368 1369 1370 1371
}

/**
 * gtk_header_bar_get_subtitle:
 * @bar: a #GtkHeaderBar
 *
 * Retrieves the subtitle of the header. See gtk_header_bar_set_subtitle().
 *
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 1375 1376 1377 1378 1379 1380
 *    and must not be modified or freed.
 *
 * Since: 3.10
 */
const gchar *
gtk_header_bar_get_subtitle (GtkHeaderBar *bar)
{
1381 1382
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);

1383 1384
  g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);

1385
  return priv->subtitle;
1386 1387
}

Matthias Clasen's avatar
Matthias Clasen committed
1388 1389 1390 1391 1392
/**
 * gtk_header_bar_set_custom_title:
 * @bar: a #GtkHeaderBar
 * @title_widget: (allow-none): a custom widget to use for a title
 *
1393 1394 1395 1396 1397
 * Sets a custom title for the #GtkHeaderBar.
 *
 * The title should help a user identify the current view. This
 * supersedes any title set by gtk_header_bar_set_title() or
 * gtk_header_bar_set_subtitle(). To achieve the same style as
1398
 * the builtin title and subtitle, use the “title” and “subtitle”
1399 1400 1401 1402
 * style classes.
 *
 * You should set the custom title to %NULL, for the header title
 * label to be visible again.
Matthias Clasen's avatar
Matthias Clasen committed
1403 1404 1405 1406 1407 1408 1409
 *
 * Since: 3.10
 */
void
gtk_header_bar_set_custom_title (GtkHeaderBar *bar,
                                 GtkWidget    *title_widget)
{
1410
  GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar);
Matthias Clasen's avatar
Matthias Clasen committed
1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427

  g_return_if_fail (GTK_IS_HEADER_BAR (bar));
  if (title_widget)
    g_return_if_fail (GTK_IS_WIDGET (title_widget));

  /* No need to do anything if the custom widget stays the same */
  if (priv->custom_title == title_widget)
    return;

  if (priv->custom_title)
    {
      GtkWidget *custom = priv->custom_title;

      priv->custom_title = NULL;
      gtk_widget_unparent (custom);
    }

1428
  if (title_widget != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
1429
    {
1430
      priv->custom_title = title_widget;
Matthias Clasen's avatar
Matthias Clasen committed
1431

1432
      gtk_header_bar_reorder_css_node (bar, GTK_PACK_START, priv->custom_title);
Matthias Clasen's avatar
Matthias Clasen committed
1433 1434
      gtk_widget_set_parent (priv->custom_title, GTK_WIDGET (bar));
      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