gtkbbox.c 39.8 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17 18

/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
22
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 24
 */

25 26
/**
 * SECTION:gtkbbox
27
 * @Short_description: A container for arranging buttons
28 29
 * @Title: GtkButtonBox
 *
30 31
 * A button box should be used to provide a consistent layout of buttons
 * throughout your application. The layout/spacing can be altered by the
32
 * programmer, or if desired, by the user to alter the “feel” of a
33
 * program to a small degree.
34 35 36 37 38 39
 *
 * gtk_button_box_get_layout() and gtk_button_box_set_layout() retrieve and
 * alter the method used to spread the buttons in a button box across the
 * container, respectively.
 *
 * The main purpose of GtkButtonBox is to make sure the children have all the
40
 * same size. GtkButtonBox gives all children the same size, but it does allow
41
 * 'outliers' to keep their own larger size.
42 43
 *
 * To excempt individual children from homogeneous sizing regardless of their
44
 * 'outlier' status, you can set the non-homogeneous child
45
 * property.
46 47
 */

48
#include "config.h"
49

Elliot Lee's avatar
Elliot Lee committed
50
#include "gtkbbox.h"
51

52
#include "gtkboxprivate.h"
53
#include "gtkorientable.h"
54
#include "gtktypebuiltins.h"
55
#include "gtkprivate.h"
56
#include "gtksizerequest.h"
57
#include "gtkwidgetprivate.h"
58

Havoc Pennington's avatar
Havoc Pennington committed
59
#include "gtkintl.h"
60

Elliot Lee's avatar
Elliot Lee committed
61

62
struct _GtkButtonBoxPrivate
63 64 65 66
{
  GtkButtonBoxStyle layout_style;
};

67 68
enum {
  PROP_0,
69
  PROP_LAYOUT_STYLE
70
};
Elliot Lee's avatar
Elliot Lee committed
71

72 73
enum {
  CHILD_PROP_0,
74 75
  CHILD_PROP_SECONDARY,
  CHILD_PROP_NONHOMOGENEOUS
76 77
};

78
#define GTK_BOX_SECONDARY_CHILD "gtk-box-secondary-child"
79
#define GTK_BOX_NON_HOMOGENEOUS "gtk-box-non-homogeneous"
80

81
static void gtk_button_box_set_property       (GObject           *object,
82 83 84
                                               guint              prop_id,
                                               const GValue      *value,
                                               GParamSpec        *pspec);
85
static void gtk_button_box_get_property       (GObject           *object,
86 87 88
                                               guint              prop_id,
                                               GValue            *value,
                                               GParamSpec        *pspec);
89 90 91 92 93 94 95 96 97 98 99 100 101 102
static void gtk_button_box_get_preferred_width            (GtkWidget *widget,
                                                           gint      *minimum,
                                                           gint      *natural);
static void gtk_button_box_get_preferred_height           (GtkWidget *widget,
                                                           gint      *minimum,
                                                           gint      *natural);
static void gtk_button_box_get_preferred_width_for_height (GtkWidget *widget,
                                                           gint       height,
                                                           gint      *minimum,
                                                           gint      *natural);
static void gtk_button_box_get_preferred_height_for_width (GtkWidget *widget,
                                                           gint       width,
                                                           gint      *minimum,
                                                           gint      *natural);
103 104 105 106 107 108
static void gtk_button_box_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
									gint       width,
									gint      *minimum,
									gint      *natural,
									gint      *minimum_baseline,
									gint      *natural_baseline);
109

110 111
static void gtk_button_box_size_allocate      (GtkWidget         *widget,
                                               GtkAllocation     *allocation);
112 113
static void gtk_button_box_remove             (GtkContainer      *container,
                                               GtkWidget         *widget);
114
static void gtk_button_box_set_child_property (GtkContainer      *container,
115 116 117 118
                                               GtkWidget         *child,
                                               guint              property_id,
                                               const GValue      *value,
                                               GParamSpec        *pspec);
119
static void gtk_button_box_get_child_property (GtkContainer      *container,
120 121 122 123
                                               GtkWidget         *child,
                                               guint              property_id,
                                               GValue            *value,
                                               GParamSpec        *pspec);
Elliot Lee's avatar
Elliot Lee committed
124

Havoc Pennington's avatar
Havoc Pennington committed
125 126
#define DEFAULT_CHILD_MIN_WIDTH 85
#define DEFAULT_CHILD_MIN_HEIGHT 27
127
#define DEFAULT_CHILD_IPAD_X 4
Havoc Pennington's avatar
Havoc Pennington committed
128
#define DEFAULT_CHILD_IPAD_Y 0
129
#define DEFAULT_LAYOUT_STYLE GTK_BUTTONBOX_EDGE
Elliot Lee's avatar
Elliot Lee committed
130

131
G_DEFINE_TYPE_WITH_PRIVATE (GtkButtonBox, gtk_button_box, GTK_TYPE_BOX)
Elliot Lee's avatar
Elliot Lee committed
132

133 134 135 136 137 138 139
static void
gtk_button_box_add (GtkContainer *container,
                    GtkWidget    *widget)
{
  gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
}

Elliot Lee's avatar
Elliot Lee committed
140 141 142 143
static void
gtk_button_box_class_init (GtkButtonBoxClass *class)
{
  GtkWidgetClass *widget_class;
144
  GObjectClass *gobject_class;
145
  GtkContainerClass *container_class;
Elliot Lee's avatar
Elliot Lee committed
146

147
  gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
148
  widget_class = (GtkWidgetClass*) class;
149
  container_class = (GtkContainerClass*) class;
150

151 152
  gobject_class->set_property = gtk_button_box_set_property;
  gobject_class->get_property = gtk_button_box_get_property;
153

154 155
  widget_class->get_preferred_width = gtk_button_box_get_preferred_width;
  widget_class->get_preferred_height = gtk_button_box_get_preferred_height;
156 157
  widget_class->get_preferred_width_for_height = gtk_button_box_get_preferred_width_for_height;
  widget_class->get_preferred_height_for_width = gtk_button_box_get_preferred_height_for_width;
158
  widget_class->get_preferred_height_and_baseline_for_width = gtk_button_box_get_preferred_height_and_baseline_for_width;
159 160
  widget_class->size_allocate = gtk_button_box_size_allocate;

161
  container_class->remove = gtk_button_box_remove;
162
  container_class->add = gtk_button_box_add;
163 164
  container_class->set_child_property = gtk_button_box_set_child_property;
  container_class->get_child_property = gtk_button_box_get_child_property;
165
  gtk_container_class_handle_border_width (container_class);
166

167 168 169
  /* FIXME we need to override the "spacing" property on GtkBox once
   * libgobject allows that.
   */
Havoc Pennington's avatar
Havoc Pennington committed
170
  gtk_widget_class_install_style_property (widget_class,
171 172 173 174 175
                                           g_param_spec_int ("child-min-width",
                                                             P_("Minimum child width"),
                                                             P_("Minimum width of buttons inside the box"),
                                                             0,
                                                             G_MAXINT,
Havoc Pennington's avatar
Havoc Pennington committed
176
                                                             DEFAULT_CHILD_MIN_WIDTH,
177
                                                             GTK_PARAM_READABLE));
Havoc Pennington's avatar
Havoc Pennington committed
178 179

  gtk_widget_class_install_style_property (widget_class,
180 181 182 183 184
                                           g_param_spec_int ("child-min-height",
                                                             P_("Minimum child height"),
                                                             P_("Minimum height of buttons inside the box"),
                                                             0,
                                                             G_MAXINT,
Havoc Pennington's avatar
Havoc Pennington committed
185
                                                             DEFAULT_CHILD_MIN_HEIGHT,
186
                                                             GTK_PARAM_READABLE));
Havoc Pennington's avatar
Havoc Pennington committed
187 188

  gtk_widget_class_install_style_property (widget_class,
189 190 191 192 193
                                           g_param_spec_int ("child-internal-pad-x",
                                                             P_("Child internal width padding"),
                                                             P_("Amount to increase child's size on either side"),
                                                             0,
                                                             G_MAXINT,
Havoc Pennington's avatar
Havoc Pennington committed
194
                                                             DEFAULT_CHILD_IPAD_X,
195
                                                             GTK_PARAM_READABLE));
Havoc Pennington's avatar
Havoc Pennington committed
196 197

  gtk_widget_class_install_style_property (widget_class,
198 199 200 201 202
                                           g_param_spec_int ("child-internal-pad-y",
                                                             P_("Child internal height padding"),
                                                             P_("Amount to increase child's size on the top and bottom"),
                                                             0,
                                                             G_MAXINT,
Havoc Pennington's avatar
Havoc Pennington committed
203
                                                             DEFAULT_CHILD_IPAD_Y,
204
                                                             GTK_PARAM_READABLE));
205 206
  g_object_class_install_property (gobject_class,
                                   PROP_LAYOUT_STYLE,
207
                                   g_param_spec_enum ("layout-style",
208
                                                      P_("Layout style"),
209
                                                      P_("How to lay out the buttons in the box. Possible values are: spread, edge, start and end"),
210 211
                                                      GTK_TYPE_BUTTON_BOX_STYLE,
                                                      DEFAULT_LAYOUT_STYLE,
212
                                                      GTK_PARAM_READWRITE));
213 214

  gtk_container_class_install_child_property (container_class,
215 216 217 218 219 220
                                              CHILD_PROP_SECONDARY,
                                              g_param_spec_boolean ("secondary", 
                                                                    P_("Secondary"),
                                                                    P_("If TRUE, the child appears in a secondary group of children, suitable for, e.g., help buttons"),
                                                                    FALSE,
                                                                    GTK_PARAM_READWRITE));
221

222 223 224 225 226 227 228
  gtk_container_class_install_child_property (container_class,
                                              CHILD_PROP_NONHOMOGENEOUS,
                                              g_param_spec_boolean ("non-homogeneous",
                                                                    P_("Non-Homogeneous"),
                                                                    P_("If TRUE, the child will not be subject to homogeneous sizing"),
                                                                    FALSE,
                                                                    GTK_PARAM_READWRITE));
Elliot Lee's avatar
Elliot Lee committed
229 230 231 232 233
}

static void
gtk_button_box_init (GtkButtonBox *button_box)
{
234 235
  button_box->priv = gtk_button_box_get_instance_private (button_box);
  button_box->priv->layout_style = DEFAULT_LAYOUT_STYLE;
236 237

  gtk_box_set_spacing (GTK_BOX (button_box), 0);
Elliot Lee's avatar
Elliot Lee committed
238 239
}

240
static void
241 242 243 244
gtk_button_box_set_property (GObject      *object,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec)
245
{
246
  switch (prop_id)
247 248 249
    {
    case PROP_LAYOUT_STYLE:
      gtk_button_box_set_layout (GTK_BUTTON_BOX (object),
250
                                 g_value_get_enum (value));
251 252 253 254 255 256 257 258
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
259 260 261 262
gtk_button_box_get_property (GObject    *object,
                             guint       prop_id,
                             GValue     *value,
                             GParamSpec *pspec)
263
{
264
  GtkButtonBoxPrivate *priv = GTK_BUTTON_BOX (object)->priv;
265

266 267 268
  switch (prop_id)
    {
    case PROP_LAYOUT_STYLE:
269
      g_value_set_enum (value, priv->layout_style);
270 271 272 273 274 275 276
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

277
static void
278 279 280 281 282
gtk_button_box_set_child_property (GtkContainer *container,
                                   GtkWidget    *child,
                                   guint         property_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
283 284 285 286 287
{
  switch (property_id)
    {
    case CHILD_PROP_SECONDARY:
      gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (container), child,
288
                                          g_value_get_boolean (value));
289
      break;
290 291 292 293
    case CHILD_PROP_NONHOMOGENEOUS:
      gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (container), child,
                                                g_value_get_boolean (value));
      break;
294 295 296 297 298 299 300 301
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static void
gtk_button_box_get_child_property (GtkContainer *container,
302 303 304 305
                                   GtkWidget    *child,
                                   guint         property_id,
                                   GValue       *value,
                                   GParamSpec   *pspec)
306 307 308 309
{
  switch (property_id)
    {
    case CHILD_PROP_SECONDARY:
310 311 312
      g_value_set_boolean (value,
                           gtk_button_box_get_child_secondary (GTK_BUTTON_BOX (container),
                                                               child));
313
      break;
314 315 316 317 318
    case CHILD_PROP_NONHOMOGENEOUS:
      g_value_set_boolean (value,
                           gtk_button_box_get_child_non_homogeneous (GTK_BUTTON_BOX (container),
                                                                     child));
      break;
319 320 321 322 323
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}
324

325 326 327 328
static void
gtk_button_box_remove (GtkContainer *container,
                       GtkWidget    *widget)
{
329
  /* clear is_secondary and nonhomogeneous flag in case the widget
330 331
   * is added to another container
   */
332 333
  gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (container), widget, FALSE);
  gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (container), widget, FALSE);
334 335 336 337

  GTK_CONTAINER_CLASS (gtk_button_box_parent_class)->remove (container, widget);
}

338 339 340 341 342 343 344
/**
 * gtk_button_box_set_layout:
 * @widget: a #GtkButtonBox
 * @layout_style: the new layout style
 *
 * Changes the way buttons are arranged in their container.
 */
345
void
346
gtk_button_box_set_layout (GtkButtonBox      *widget,
347
                           GtkButtonBoxStyle  layout_style)
Elliot Lee's avatar
Elliot Lee committed
348
{
349
  GtkButtonBoxPrivate *priv;
350

351
  g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
352

353 354 355
  priv = widget->priv;

  if (priv->layout_style != layout_style)
356
    {
357
      priv->layout_style = layout_style;
358

359
      if (priv->layout_style == GTK_BUTTONBOX_EXPAND)
360 361 362 363
        {
          gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (widget)), "linked");
          gtk_box_set_homogeneous (GTK_BOX (widget), TRUE);
        }
364
      else
365 366 367 368 369
        {
          gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (widget)), "linked");
          gtk_box_set_homogeneous (GTK_BOX (widget), FALSE);
        }

370
      g_object_notify (G_OBJECT (widget), "layout-style");
371 372
      gtk_widget_queue_resize (GTK_WIDGET (widget));
    }
Elliot Lee's avatar
Elliot Lee committed
373 374
}

375 376 377 378 379 380
/**
 * gtk_button_box_get_layout:
 * @widget: a #GtkButtonBox
 *
 * Retrieves the method being used to arrange the buttons in a button box.
 *
381
 * Returns: the method used to lay out buttons in @widget.
382
 */
383
GtkButtonBoxStyle
384
gtk_button_box_get_layout (GtkButtonBox *widget)
Elliot Lee's avatar
Elliot Lee committed
385
{
386 387
  g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), DEFAULT_LAYOUT_STYLE);

388
  return widget->priv->layout_style;
Elliot Lee's avatar
Elliot Lee committed
389 390
}

Matthias Clasen's avatar
Matthias Clasen committed
391 392 393
/**
 * gtk_button_box_get_child_secondary:
 * @widget: a #GtkButtonBox
394 395
 * @child: a child of @widget
 *
Matthias Clasen's avatar
Matthias Clasen committed
396 397
 * Returns whether @child should appear in a secondary group of children.
 *
398
 * Returns: whether @child should appear in a secondary group of children.
Matthias Clasen's avatar
Matthias Clasen committed
399 400 401
 *
 * Since: 2.4
 **/
402
gboolean
Matthias Clasen's avatar
Matthias Clasen committed
403
gtk_button_box_get_child_secondary (GtkButtonBox *widget,
404
                                    GtkWidget    *child)
Matthias Clasen's avatar
Matthias Clasen committed
405
{
406 407
  g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), FALSE);
  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
408

409
  return (g_object_get_data (G_OBJECT (child), GTK_BOX_SECONDARY_CHILD) != NULL);
Matthias Clasen's avatar
Matthias Clasen committed
410 411
}

412
/**
413
 * gtk_button_box_set_child_secondary:
414 415 416 417 418 419 420 421 422 423
 * @widget: a #GtkButtonBox
 * @child: a child of @widget
 * @is_secondary: if %TRUE, the @child appears in a secondary group of the
 *                button box.
 *
 * Sets whether @child should appear in a secondary group of children.
 * A typical use of a secondary child is the help button in a dialog.
 *
 * This group appears after the other children if the style
 * is %GTK_BUTTONBOX_START, %GTK_BUTTONBOX_SPREAD or
424
 * %GTK_BUTTONBOX_EDGE, and before the other children if the style
425
 * is %GTK_BUTTONBOX_END. For horizontal button boxes, the definition
Matthias Clasen's avatar
Matthias Clasen committed
426 427 428
 * of before/after depends on direction of the widget (see
 * gtk_widget_set_direction()). If the style is %GTK_BUTTONBOX_START
 * or %GTK_BUTTONBOX_END, then the secondary children are aligned at
429 430 431
 * the other end of the button box from the main children. For the
 * other styles, they appear immediately next to the main children.
 **/
432 433 434 435
void
gtk_button_box_set_child_secondary (GtkButtonBox *widget,
                                    GtkWidget    *child,
                                    gboolean      is_secondary)
436
{
437 438
  GtkButtonBox *bbox;

439 440
  g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
  g_return_if_fail (GTK_IS_WIDGET (child));
441
  g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (widget));
Elliot Lee's avatar
Elliot Lee committed
442

443 444
  bbox = GTK_BUTTON_BOX (widget);

445 446 447
  g_object_set_data (G_OBJECT (child),
                     GTK_BOX_SECONDARY_CHILD,
                     is_secondary ? GINT_TO_POINTER (1) : NULL);
448 449
  gtk_widget_child_notify (child, "secondary");

450 451
  if (bbox->priv->layout_style == GTK_BUTTONBOX_EXPAND)
    {
452
      gtk_box_reorder_child (GTK_BOX (bbox), child, is_secondary ? 0 : -1);
453 454
    }

455 456
  if (gtk_widget_get_visible (GTK_WIDGET (widget)) &&
      gtk_widget_get_visible (child))
457 458
    gtk_widget_queue_resize (child);
}
Elliot Lee's avatar
Elliot Lee committed
459

460
/* Ask children how much space they require and round up
461 462 463
 * to match minimum size and internal padding.
 * Returns the size each single child should have.
 */
464
static void
465 466 467 468
gtk_button_box_child_requisition (GtkWidget  *widget,
                                  gint       *nvis_children,
                                  gint       *nvis_secondaries,
                                  gint      **widths,
469 470 471 472
                                  gint      **heights,
                                  gint      **baselines,
				  gint       *baseline,
				  gint       *baseline_height)
Elliot Lee's avatar
Elliot Lee committed
473 474
{
  GtkButtonBox *bbox;
475
  GList *children, *list;
Elliot Lee's avatar
Elliot Lee committed
476
  gint nchildren;
477
  gint nsecondaries;
Elliot Lee's avatar
Elliot Lee committed
478 479
  gint needed_width;
  gint needed_height;
480
  gint needed_above, needed_below;
481
  gint avg_w, avg_h;
Elliot Lee's avatar
Elliot Lee committed
482 483 484 485 486 487 488
  GtkRequisition child_requisition;
  gint ipad_w;
  gint ipad_h;
  gint child_min_width;
  gint child_min_height;
  gint ipad_x;
  gint ipad_y;
489 490
  gboolean homogeneous;
  gint i;
491 492 493
  gint max_above, max_below, child_baseline;
  GtkOrientation orientation;
  gboolean have_baseline;
494

Elliot Lee's avatar
Elliot Lee committed
495 496 497 498
  g_return_if_fail (GTK_IS_BUTTON_BOX (widget));

  bbox = GTK_BUTTON_BOX (widget);

499
  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
500 501
  homogeneous = gtk_box_get_homogeneous (GTK_BOX (widget));

Havoc Pennington's avatar
Havoc Pennington committed
502
  gtk_widget_style_get (widget,
503 504 505 506
                        "child-min-width", &child_min_width,
                        "child-min-height", &child_min_height,
                        "child-internal-pad-x", &ipad_x,
                        "child-internal-pad-y", &ipad_y,
507 508
                        NULL);

Elliot Lee's avatar
Elliot Lee committed
509
  nchildren = 0;
510
  nsecondaries = 0;
511
  list = children = _gtk_box_get_children (GTK_BOX (bbox));
Elliot Lee's avatar
Elliot Lee committed
512
  needed_width = child_min_width;
513
  needed_height = child_min_height;
514 515
  needed_above = 0;
  needed_below = 0;
Elliot Lee's avatar
Elliot Lee committed
516 517
  ipad_w = ipad_x * 2;
  ipad_h = ipad_y * 2;
518

519 520
  have_baseline = FALSE;
  max_above = max_below = 0;
521
  avg_w = avg_h = 0;
522
  for (children = list; children != NULL; children = children->next)
Elliot Lee's avatar
Elliot Lee committed
523
    {
524 525
      GtkWidget *child;

Elliot Lee's avatar
Elliot Lee committed
526 527
      child = children->data;

528
      if (gtk_widget_get_visible (child))
529 530
        {
          nchildren += 1;
531 532
          _gtk_widget_get_preferred_size_and_baseline (child,
                                                       &child_requisition, NULL, &child_baseline, NULL);
533 534 535 536 537 538 539 540
	  if (orientation == GTK_ORIENTATION_HORIZONTAL &&
	      gtk_widget_get_valign_with_baseline (child) == GTK_ALIGN_BASELINE &&
	      child_baseline != -1)
	    {
	      have_baseline = TRUE;
	      max_above = MAX (max_above, child_baseline + ipad_y);
	      max_below = MAX (max_below , child_requisition.height + ipad_h - (child_baseline + ipad_y));
	    }
541 542 543 544
          avg_w += child_requisition.width + ipad_w;
          avg_h += child_requisition.height + ipad_h;
        }
    }
545 546
  avg_w /= MAX (nchildren, 1);
  avg_h /= MAX (nchildren, 1);
547

548 549 550 551 552
  if (baseline)
    *baseline = have_baseline ? max_above : -1;
  if (baseline_height)
    *baseline_height = max_above + max_below;

553 554
  *widths = g_new (gint, nchildren);
  *heights = g_new (gint, nchildren);
555
  *baselines = g_new (gint, nchildren);
556 557 558 559 560 561 562

  i = 0;
  children = list;
  while (children)
    {
      GtkWidget *child;
      gboolean is_secondary;
563
      gboolean non_homogeneous;
564

565 566 567 568 569 570
      child = children->data;
      children = children->next;

      if (gtk_widget_get_visible (child))
        {
          is_secondary = gtk_button_box_get_child_secondary (bbox, child);
571 572
          non_homogeneous = gtk_button_box_get_child_non_homogeneous (bbox, child);

573 574
          if (is_secondary)
            nsecondaries++;
575

576 577
          _gtk_widget_get_preferred_size_and_baseline (child,
                                                       &child_requisition, NULL, &child_baseline, NULL);
578

579 580
          if (homogeneous ||
              (!non_homogeneous && (child_requisition.width + ipad_w < avg_w * 1.5)))
581 582 583 584 585 586 587 588 589 590
            {
              (*widths)[i] = -1;
              if (child_requisition.width + ipad_w > needed_width)
                needed_width = child_requisition.width + ipad_w;
            }
          else
            {
              (*widths)[i] = child_requisition.width + ipad_w;
            }

591 592
	  (*baselines)[i] = -1;

593 594
          if (homogeneous ||
              (!non_homogeneous && (child_requisition.height + ipad_h < avg_h * 1.5)))
595 596
            {
              (*heights)[i] = -1;
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613

	      if (orientation == GTK_ORIENTATION_HORIZONTAL &&
		  gtk_widget_get_valign_with_baseline (child) == GTK_ALIGN_BASELINE &&
		  child_baseline != -1)
		{
		  (*baselines)[i] = child_baseline + ipad_y;

		  if (child_baseline + ipad_y > needed_above)
		    needed_above = child_baseline + ipad_y;
		  if (child_requisition.height - child_baseline + ipad_y > needed_below)
		    needed_below = child_requisition.height - child_baseline + ipad_y;
		}
	      else
		{
		  if (child_requisition.height + ipad_h > needed_height)
		    needed_height = child_requisition.height + ipad_h;
		}
614 615 616 617
            }
          else
            {
              (*heights)[i] = child_requisition.height + ipad_h;
618 619 620 621 622

	      if (orientation == GTK_ORIENTATION_HORIZONTAL &&
		  gtk_widget_get_valign_with_baseline (child) == GTK_ALIGN_BASELINE &&
		  child_baseline != -1)
		(*baselines)[i] = child_baseline + ipad_y;
623 624 625
            }

          i++;
626
        }
Elliot Lee's avatar
Elliot Lee committed
627
    }
628

629 630
  g_list_free (list);

631 632
  needed_height = MAX (needed_height, needed_above + needed_below);

633 634 635 636 637
  for (i = 0; i < nchildren; i++)
    {
      if ((*widths)[i] == -1)
        (*widths)[i] = needed_width;
      if ((*heights)[i] == -1)
638 639 640 641 642
	{
	  (*heights)[i] = needed_height;
	  if ((*baselines)[i] != -1)
	    (*baselines)[i] = needed_above;
	}
643 644
    }

645 646
  if (nvis_children)
    *nvis_children = nchildren;
647

648 649
  if (nvis_secondaries)
    *nvis_secondaries = nsecondaries;
Elliot Lee's avatar
Elliot Lee committed
650
}
651

652 653
static void
gtk_button_box_size_request (GtkWidget      *widget,
654 655
                             GtkRequisition *requisition,
			     gint           *baseline)
656
{
657
  GtkButtonBoxPrivate *priv;
658 659
  GtkButtonBox *bbox;
  gint nvis_children;
660
  gint max_size, max_above, max_below;
661
  gint total_size;
662 663
  gint spacing;
  GtkOrientation orientation;
664 665
  gint *widths;
  gint *heights;
666
  gint *baselines;
667
  gint i;
668

669 670 671
  if (baseline)
    *baseline = -1;

672
  bbox = GTK_BUTTON_BOX (widget);
673
  priv = bbox->priv;
674 675

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
676
  spacing = gtk_box_get_spacing (GTK_BOX (widget));
677

678 679 680
  gtk_button_box_child_requisition (widget,
                                    &nvis_children,
                                    NULL,
681
                                    &widths, &heights, &baselines, baseline, NULL);
682

683
  max_size = max_above = max_below = 0;
684 685 686 687 688 689
  total_size = 0;
  for (i = 0; i < nvis_children; i++)
    {
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
        {
          total_size += widths[i];
690 691 692 693 694 695 696
	  if (baselines[i] == -1)
	    max_size = MAX (max_size, heights[i]);
	  else
	    {
	      max_above = MAX (max_above, baselines[i]);
	      max_below = MAX (max_below, heights[i] - baselines[i]);
	    }
697 698 699 700 701 702 703 704 705
        }
      else
        {
          total_size += heights[i];
          max_size = MAX (max_size, widths[i]);
        }
    }
  g_free (widths);
  g_free (heights);
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
  g_free (baselines);

  max_size = MAX (max_size, max_above + max_below);

  switch (gtk_box_get_baseline_position (GTK_BOX (widget)))
    {
    case GTK_BASELINE_POSITION_TOP:
      break;
    case GTK_BASELINE_POSITION_CENTER:
      if (baseline != NULL && *baseline != -1)
	*baseline += (max_size - (max_above + max_below)) / 2;
      break;
    case GTK_BASELINE_POSITION_BOTTOM:
      if (baseline != NULL && *baseline != -1)
	*baseline += max_size - (max_above + max_below);
      break;
    }
723 724 725 726 727 728 729 730

  if (nvis_children == 0)
    {
      requisition->width = 0;
      requisition->height = 0;
    }
  else
    {
731
      switch (priv->layout_style)
732 733 734
        {
          case GTK_BUTTONBOX_SPREAD:
            if (orientation == GTK_ORIENTATION_HORIZONTAL)
735
              requisition->width = total_size + ((nvis_children + 1)*spacing);
736
            else
737
              requisition->height = total_size + ((nvis_children + 1)*spacing);
738 739 740 741 742 743

            break;
          case GTK_BUTTONBOX_EDGE:
          case GTK_BUTTONBOX_START:
          case GTK_BUTTONBOX_END:
          case GTK_BUTTONBOX_CENTER:
744
          case GTK_BUTTONBOX_EXPAND:
745
            if (orientation == GTK_ORIENTATION_HORIZONTAL)
746
              requisition->width = total_size + ((nvis_children - 1)*spacing);
747
            else
748
              requisition->height = total_size + ((nvis_children - 1)*spacing);
749 750 751 752 753 754 755

            break;
          default:
            g_assert_not_reached ();
            break;
        }

756
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
757
        requisition->height = max_size;
758
      else
759
        requisition->width = max_size;
760 761 762
    }
}

763 764 765 766 767 768 769
static void
gtk_button_box_get_preferred_width (GtkWidget *widget,
                                    gint      *minimum,
                                    gint      *natural)
{
  GtkRequisition requisition;

770
  gtk_button_box_size_request (widget, &requisition, NULL);
771 772 773 774 775 776 777 778 779

  *minimum = *natural = requisition.width;
}

static void
gtk_button_box_get_preferred_height (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
{
780 781 782
  gtk_button_box_get_preferred_height_and_baseline_for_width (widget, -1,
							      minimum, natural,
							      NULL, NULL);
783 784
}

785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
static void
gtk_button_box_get_preferred_width_for_height (GtkWidget *widget,
                                               gint       height,
                                               gint      *minimum,
                                               gint      *natural)
{
  gtk_button_box_get_preferred_width (widget, minimum, natural);
}

static void
gtk_button_box_get_preferred_height_for_width (GtkWidget *widget,
                                               gint       width,
                                               gint      *minimum,
                                               gint      *natural)
{
  gtk_button_box_get_preferred_height (widget, minimum, natural);
}

803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
static void
gtk_button_box_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
							    gint       width,
							    gint      *minimum,
							    gint      *natural,
							    gint      *minimum_baseline,
							    gint      *natural_baseline)
{
  GtkRequisition requisition;
  gint baseline;

  gtk_button_box_size_request (widget, &requisition, &baseline);

  *minimum = *natural = requisition.height;
  if (minimum_baseline)
    *minimum_baseline = baseline;
  if (natural_baseline)
    *natural_baseline = baseline;
}

823 824 825 826
static void
gtk_button_box_size_allocate (GtkWidget     *widget,
                              GtkAllocation *allocation)
{
827
  GtkButtonBoxPrivate *priv;
828
  GtkButtonBox *bbox;
829
  GList *children, *list;
830 831
  GtkAllocation child_allocation;
  gint nvis_children;
832
  gint n_primaries;
833 834 835 836 837
  gint n_secondaries;
  gint x = 0;
  gint y = 0;
  gint secondary_x = 0;
  gint secondary_y = 0;
838 839
  gint width = 0;
  gint height = 0;
840 841 842
  gint childspacing = 0;
  gint spacing;
  GtkOrientation orientation;
843 844 845
  gint ipad_x, ipad_y;
  gint *widths;
  gint *heights;
846
  gint *baselines;
847 848 849 850
  gint *sizes;
  gint primary_size;
  gint secondary_size;
  gint total_size;
851 852
  gint baseline, baseline_height;
  gint child_baseline, allocated_baseline;
853
  gint i;
854

855 856
  bbox = GTK_BUTTON_BOX (widget);
  priv = bbox->priv;
857

858 859 860 861 862 863 864
  if (priv->layout_style == GTK_BUTTONBOX_EXPAND)
    {
      GTK_WIDGET_CLASS (gtk_button_box_parent_class)->size_allocate (widget, allocation);
      return;
    }


865
  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
866
  spacing = gtk_box_get_spacing (GTK_BOX (widget));
867

868 869 870 871 872 873 874
  gtk_widget_style_get (widget,
                        "child-internal-pad-x", &ipad_x,
                        "child-internal-pad-y", &ipad_y,
                        NULL);
  gtk_button_box_child_requisition (widget,
                                    &nvis_children,
                                    &n_secondaries,
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
                                    &widths, &heights, &baselines, &baseline, &baseline_height);

  allocated_baseline = gtk_widget_get_allocated_baseline (widget);
  if (allocated_baseline != -1)
    baseline = allocated_baseline;
  else if (baseline != -1)
    {
      /* TODO: modify baseline based on baseline_pos && allocated_baseline*/
      switch (gtk_box_get_baseline_position (GTK_BOX (widget)))
	{
	case GTK_BASELINE_POSITION_TOP:
	  baseline = baseline;
	  break;
	case GTK_BASELINE_POSITION_CENTER:
	  baseline = baseline + (allocation->height - baseline_height) / 2;
	  break;
	case GTK_BASELINE_POSITION_BOTTOM:
	  baseline = allocation->height - (baseline_height - baseline);
	  break;
	}
    }
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924

  n_primaries = nvis_children - n_secondaries;
  primary_size = 0;
  secondary_size = 0;
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    sizes = widths;
  else
    sizes = heights;

  i = 0;
  list = children = _gtk_box_get_children (GTK_BOX (widget));
  while (children)
    {
      GtkWidget *child;

      child = children->data;
      children = children->next;

      if (gtk_widget_get_visible (child))
        {
          if (gtk_button_box_get_child_secondary (bbox, child))
            secondary_size += sizes[i];
          else
            primary_size += sizes[i];
          i++;
        }
    }
  total_size = primary_size + secondary_size;

925
  gtk_widget_set_allocation (widget, allocation);
926 927

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
928
    width = allocation->width;
929
  else
930
    height = allocation->height;
931

932
  switch (priv->layout_style)
933 934 935 936 937
    {
      case GTK_BUTTONBOX_SPREAD:

        if (orientation == GTK_ORIENTATION_HORIZONTAL)
          {
938
            childspacing = (width - total_size) / (nvis_children + 1);
939
            x = allocation->x + childspacing;
940
            secondary_x = x + primary_size + n_primaries * childspacing;
941 942 943
          }
        else
          {
944
            childspacing = (height - total_size) / (nvis_children + 1);
945
            y = allocation->y + childspacing;
946
            secondary_y = y + primary_size + n_primaries * childspacing;
947 948 949 950 951 952 953 954 955
          }

        break;

      case GTK_BUTTONBOX_EDGE:

        if (orientation == GTK_ORIENTATION_HORIZONTAL)
          {
            if (nvis_children >= 2)
956
              {
957
                childspacing = (width - total_size) / (nvis_children - 1);
958
                x = allocation->x;
959
                secondary_x = x + primary_size + n_primaries * childspacing;
960
              }
961
            else if (nvis_children == 1)
962
              {
963
                /* one child, just center */
964 965
                childspacing = width;
                x = secondary_x = allocation->x
966
                                  + (allocation->width - widths[0]) / 2;
967
              }
968 969 970 971 972 973
            else
              {
                /* zero children, meh */
                childspacing = width;
                x = secondary_x = allocation->x + allocation->width / 2;
              }
974 975 976 977 978
          }
        else
          {
            if (nvis_children >= 2)
              {
979
                childspacing = (height - total_size) / (nvis_children - 1);
980
                y = allocation->y;
981
                secondary_y = y + primary_size + n_primaries * childspacing;
982
              }
983
            else if (nvis_children == 1)
984
              {
985
                /* one child, just center */
986 987
                childspacing = height;
                y = secondary_y = allocation->y
988 989 990 991 992 993 994
                                     + (allocation->height - heights[0]) / 2;
              }
            else
              {
                /* zero children, meh */
                childspacing = height;
                y = secondary_y = allocation->y + allocation->height / 2;
995 996 997 998 999 1000 1001 1002 1003 1004
              }
          }

        break;

      case GTK_BUTTONBOX_START:

        if (orientation == GTK_ORIENTATION_HORIZONTAL)
          {
            childspacing = spacing;
1005
            x = allocation->x;
1006
            secondary_x = allocation->x + allocation->width
1007
              - secondary_size - spacing * (n_secondaries - 1);
1008 1009 1010 1011
          }
        else
          {
            childspacing = spacing;
1012
            y = allocation->y;
1013
            secondary_y = allocation->y + allocation->height
1014
              - secondary_size - spacing * (n_secondaries - 1);
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
          }

        break;

      case GTK_BUTTONBOX_END:

        if (orientation == GTK_ORIENTATION_HORIZONTAL)
          {
            childspacing = spacing;
            x = allocation->x + allocation->width
1025 1026
              - primary_size - spacing * (n_primaries - 1);
            secondary_x = allocation->x;
1027 1028 1029 1030 1031
          }
        else
          {
            childspacing = spacing;
            y = allocation->y + allocation->height
1032 1033
              - primary_size - spacing * (n_primaries - 1);
            secondary_y = allocation->y;
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
          }

        break;

      case GTK_BUTTONBOX_CENTER:

        if (orientation == GTK_ORIENTATION_HORIZONTAL)
          {
            childspacing = spacing;
            x = allocation->x +
              (allocation->width
1045 1046
               - (primary_size + spacing * (n_primaries - 1))) / 2
              + (secondary_size + n_secondaries * spacing) / 2;
1047
            secondary_x = allocation->x;
1048 1049 1050 1051 1052 1053
          }
        else
          {
            childspacing = spacing;
            y = allocation->y +
              (allocation->height
1054 1055
               - (primary_size + spacing * (n_primaries - 1))) / 2
              + (secondary_size + n_secondaries * spacing) / 2;
1056
            secondary_y = allocation->y;
1057 1058 1059 1060 1061
          }

        break;

      default:
1062
        g_assert_not_reached ();
1063 1064 1065
        break;
    }

1066 1067
  children = list;
  i = 0;
1068 1069
  while (children)
    {
1070 1071
      GtkWidget *child;

1072 1073 1074
      child = children->data;
      children = children->next;

1075
      if (gtk_widget_get_visible (child))
1076
        {
1077 1078
          child_allocation.width = widths[i];
          child_allocation.height = heights[i];
1079
	  child_baseline = -1;
1080 1081 1082

          if (orientation == GTK_ORIENTATION_HORIZONTAL)
            {
1083 1084 1085 1086 1087 1088 1089
	      if (baselines[i] != -1)
		{
		  child_allocation.y = allocation->y + baseline - baselines[i];
		  child_baseline = baselines[i];
		}
	      else
		child_allocation.y = allocation->y + (allocation->height - child_allocation.height) / 2;
1090

1091
              if (gtk_button_box_get_child_secondary (bbox, child))
1092 1093
                {
                  child_allocation.x = secondary_x;
1094
                  secondary_x += child_allocation.width + childspacing;
1095 1096 1097 1098
                }
              else
                {
                  child_allocation.x = x;
1099
                  x += child_allocation.width + childspacing;
1100 1101 1102 1103
                }

              if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
                  child_allocation.x = (allocation->x + allocation->width)
1104
                          - (child_allocation.x + child_allocation.width - allocation->x);
1105 1106 1107
            }
          else
            {
1108
              child_allocation.x = allocation->x + (allocation->width - child_allocation.width) / 2;
1109

1110
              if (gtk_button_box_get_child_secondary (bbox, child))
1111 1112
                {
                  child_allocation.y = secondary_y;
1113
                  secondary_y += child_allocation.height + childspacing;
1114 1115 1116 1117
                }
              else
                {
                  child_allocation.y = y;
1118
                  y += child_allocation.height + childspacing;
1119 1120 1121
                }
            }

1122
          gtk_widget_size_allocate_with_baseline (child, &child_allocation, child_baseline);
1123
          i++;
1124 1125
        }
    }
1126 1127

  g_list_free (list);
1128 1129
  g_free (widths);
  g_free (heights);
1130
  g_free (baselines);
1131 1132
}

1133 1134 1135 1136 1137 1138
/**
 * gtk_button_box_new:
 * @orientation: the box' orientation.
 *
 * Creates a new #GtkButtonBox.
 *
1139
 * Returns: a new #GtkButtonBox.
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
 *
 * Since: 3.0
 */
GtkWidget *
gtk_button_box_new (GtkOrientation orientation)
{
  return g_object_new (GTK_TYPE_BUTTON_BOX,
                       "orientation", orientation,
                       NULL);
}
1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200

/**
 * gtk_button_box_get_child_non_homogeneous:
 * @widget: a #GtkButtonBox
 * @child: a child of @widget
 *
 * Returns whether the child is exempted from homogenous
 * sizing.
 *
 * Returns: %TRUE if the child is not subject to homogenous sizing
 *
 * Since: 3.2
 */
gboolean
gtk_button_box_get_child_non_homogeneous (GtkButtonBox *widget,
                                          GtkWidget    *child)
{
  g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), FALSE);
  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);

  return (g_object_get_data (G_OBJECT (child), GTK_BOX_NON_HOMOGENEOUS) != NULL);
}

/**
 * gtk_button_box_set_child_non_homogeneous:
 * @widget: a #GtkButtonBox
 * @child: a child of @widget
 * @non_homogeneous: the new value
 *
 * Sets whether the child is exempted from homogeous sizing.
 *
 * Since: 3.2
 */
void
gtk_button_box_set_child_non_homogeneous (GtkButtonBox *widget,
                                          GtkWidget    *child,
                                          gboolean      non_homogeneous)
{
  g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
  g_return_if_fail (GTK_IS_WIDGET (child));
  g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (widget));

  g_object_set_data (G_OBJECT (child),
                     GTK_BOX_NON_HOMOGENEOUS,
                     non_homogeneous ? GINT_TO_POINTER (1) : NULL);
  gtk_widget_child_notify (child, "non-homogeneous");

  if (gtk_widget_get_visible (GTK_WIDGET (widget)) &&
      gtk_widget_get_visible (child))
    gtk_widget_queue_resize (child);
}