]> Pileus Git - ~andy/gtk/blob - gtk/gtkbbox.c
69bab4131bb4e375fd7d149cf237c7d1689ebfd6
[~andy/gtk] / gtk / gtkbbox.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
25  */
26
27 /**
28  * SECTION:gtkbbox
29  * @Short_description: A container for arranging buttons
30  * @Title: GtkButtonBox
31  *
32  * A button box should be used to provide a consistent layout of buttons
33  * throughout your application. The layout/spacing can be altered by the
34  * programmer, or if desired, by the user to alter the 'feel' of a
35  * program to a small degree.
36  *
37  * gtk_button_box_get_layout() and gtk_button_box_set_layout() retrieve and
38  * alter the method used to spread the buttons in a button box across the
39  * container, respectively.
40  *
41  * The main purpose of GtkButtonBox is to make sure the children have all the
42  * same size. GtkButtonBox gives all children the same size, but it does allow
43  * 'outliers' to keep their own larger size. To force all children to be
44  * strictly the same size without exceptions, you can set the
45  * #GtkButtonBox:homogeneous property to %TRUE.
46  *
47  * To excempt individual children from homogeneous sizing regardless of their
48  * 'outlier' status, you can set the #GtkButtonBox:non-homogeneous child
49  * property.
50  */
51
52 #include "config.h"
53
54 #include "gtkbbox.h"
55
56 #include "gtkboxprivate.h"
57 #include "gtkorientable.h"
58 #include "gtktypebuiltins.h"
59 #include "gtkprivate.h"
60 #include "gtksizerequest.h"
61
62 #include "gtkintl.h"
63
64
65 struct _GtkButtonBoxPrivate
66 {
67   GtkButtonBoxStyle layout_style;
68 };
69
70 enum {
71   PROP_0,
72   PROP_LAYOUT_STYLE
73 };
74
75 enum {
76   CHILD_PROP_0,
77   CHILD_PROP_SECONDARY,
78   CHILD_PROP_NONHOMOGENEOUS
79 };
80
81 #define GTK_BOX_SECONDARY_CHILD "gtk-box-secondary-child"
82 #define GTK_BOX_NON_HOMOGENEOUS "gtk-box-non-homogeneous"
83
84 static void gtk_button_box_set_property       (GObject           *object,
85                                                guint              prop_id,
86                                                const GValue      *value,
87                                                GParamSpec        *pspec);
88 static void gtk_button_box_get_property       (GObject           *object,
89                                                guint              prop_id,
90                                                GValue            *value,
91                                                GParamSpec        *pspec);
92 static void gtk_button_box_get_preferred_width            (GtkWidget *widget,
93                                                            gint      *minimum,
94                                                            gint      *natural);
95 static void gtk_button_box_get_preferred_height           (GtkWidget *widget,
96                                                            gint      *minimum,
97                                                            gint      *natural);
98 static void gtk_button_box_get_preferred_width_for_height (GtkWidget *widget,
99                                                            gint       height,
100                                                            gint      *minimum,
101                                                            gint      *natural);
102 static void gtk_button_box_get_preferred_height_for_width (GtkWidget *widget,
103                                                            gint       width,
104                                                            gint      *minimum,
105                                                            gint      *natural);
106
107 static void gtk_button_box_size_allocate      (GtkWidget         *widget,
108                                                GtkAllocation     *allocation);
109 static void gtk_button_box_remove             (GtkContainer      *container,
110                                                GtkWidget         *widget);
111 static void gtk_button_box_set_child_property (GtkContainer      *container,
112                                                GtkWidget         *child,
113                                                guint              property_id,
114                                                const GValue      *value,
115                                                GParamSpec        *pspec);
116 static void gtk_button_box_get_child_property (GtkContainer      *container,
117                                                GtkWidget         *child,
118                                                guint              property_id,
119                                                GValue            *value,
120                                                GParamSpec        *pspec);
121
122 #define DEFAULT_CHILD_MIN_WIDTH 85
123 #define DEFAULT_CHILD_MIN_HEIGHT 27
124 #define DEFAULT_CHILD_IPAD_X 4
125 #define DEFAULT_CHILD_IPAD_Y 0
126 #define DEFAULT_LAYOUT_STYLE GTK_BUTTONBOX_EDGE
127
128 G_DEFINE_TYPE (GtkButtonBox, gtk_button_box, GTK_TYPE_BOX)
129
130 static void
131 gtk_button_box_class_init (GtkButtonBoxClass *class)
132 {
133   GtkWidgetClass *widget_class;
134   GObjectClass *gobject_class;
135   GtkContainerClass *container_class;
136
137   gobject_class = G_OBJECT_CLASS (class);
138   widget_class = (GtkWidgetClass*) class;
139   container_class = (GtkContainerClass*) class;
140
141   gobject_class->set_property = gtk_button_box_set_property;
142   gobject_class->get_property = gtk_button_box_get_property;
143
144   widget_class->get_preferred_width = gtk_button_box_get_preferred_width;
145   widget_class->get_preferred_height = gtk_button_box_get_preferred_height;
146   widget_class->get_preferred_width_for_height = gtk_button_box_get_preferred_width_for_height;
147   widget_class->get_preferred_height_for_width = gtk_button_box_get_preferred_height_for_width;
148   widget_class->size_allocate = gtk_button_box_size_allocate;
149
150   container_class->remove = gtk_button_box_remove;
151   container_class->set_child_property = gtk_button_box_set_child_property;
152   container_class->get_child_property = gtk_button_box_get_child_property;
153   gtk_container_class_handle_border_width (container_class);
154
155   /* FIXME we need to override the "spacing" property on GtkBox once
156    * libgobject allows that.
157    */
158   gtk_widget_class_install_style_property (widget_class,
159                                            g_param_spec_int ("child-min-width",
160                                                              P_("Minimum child width"),
161                                                              P_("Minimum width of buttons inside the box"),
162                                                              0,
163                                                              G_MAXINT,
164                                                              DEFAULT_CHILD_MIN_WIDTH,
165                                                              GTK_PARAM_READABLE));
166
167   gtk_widget_class_install_style_property (widget_class,
168                                            g_param_spec_int ("child-min-height",
169                                                              P_("Minimum child height"),
170                                                              P_("Minimum height of buttons inside the box"),
171                                                              0,
172                                                              G_MAXINT,
173                                                              DEFAULT_CHILD_MIN_HEIGHT,
174                                                              GTK_PARAM_READABLE));
175
176   gtk_widget_class_install_style_property (widget_class,
177                                            g_param_spec_int ("child-internal-pad-x",
178                                                              P_("Child internal width padding"),
179                                                              P_("Amount to increase child's size on either side"),
180                                                              0,
181                                                              G_MAXINT,
182                                                              DEFAULT_CHILD_IPAD_X,
183                                                              GTK_PARAM_READABLE));
184
185   gtk_widget_class_install_style_property (widget_class,
186                                            g_param_spec_int ("child-internal-pad-y",
187                                                              P_("Child internal height padding"),
188                                                              P_("Amount to increase child's size on the top and bottom"),
189                                                              0,
190                                                              G_MAXINT,
191                                                              DEFAULT_CHILD_IPAD_Y,
192                                                              GTK_PARAM_READABLE));
193   g_object_class_install_property (gobject_class,
194                                    PROP_LAYOUT_STYLE,
195                                    g_param_spec_enum ("layout-style",
196                                                       P_("Layout style"),
197                                                       P_("How to lay out the buttons in the box. Possible values are: spread, edge, start and end"),
198                                                       GTK_TYPE_BUTTON_BOX_STYLE,
199                                                       DEFAULT_LAYOUT_STYLE,
200                                                       GTK_PARAM_READWRITE));
201
202   gtk_container_class_install_child_property (container_class,
203                                               CHILD_PROP_SECONDARY,
204                                               g_param_spec_boolean ("secondary", 
205                                                                     P_("Secondary"),
206                                                                     P_("If TRUE, the child appears in a secondary group of children, suitable for, e.g., help buttons"),
207                                                                     FALSE,
208                                                                     GTK_PARAM_READWRITE));
209
210   gtk_container_class_install_child_property (container_class,
211                                               CHILD_PROP_NONHOMOGENEOUS,
212                                               g_param_spec_boolean ("non-homogeneous",
213                                                                     P_("Non-Homogeneous"),
214                                                                     P_("If TRUE, the child will not be subject to homogeneous sizing"),
215                                                                     FALSE,
216                                                                     GTK_PARAM_READWRITE));
217
218   g_type_class_add_private (class, sizeof (GtkButtonBoxPrivate));
219 }
220
221 static void
222 gtk_button_box_init (GtkButtonBox *button_box)
223 {
224   GtkButtonBoxPrivate *priv;
225
226   button_box->priv = G_TYPE_INSTANCE_GET_PRIVATE (button_box,
227                                                   GTK_TYPE_BUTTON_BOX,
228                                                   GtkButtonBoxPrivate);
229   priv = button_box->priv;
230
231   gtk_box_set_spacing (GTK_BOX (button_box), 0);
232   priv->layout_style = DEFAULT_LAYOUT_STYLE;
233 }
234
235 static void
236 gtk_button_box_set_property (GObject      *object,
237                              guint         prop_id,
238                              const GValue *value,
239                              GParamSpec   *pspec)
240 {
241   switch (prop_id)
242     {
243     case PROP_LAYOUT_STYLE:
244       gtk_button_box_set_layout (GTK_BUTTON_BOX (object),
245                                  g_value_get_enum (value));
246       break;
247     default:
248       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
249       break;
250     }
251 }
252
253 static void
254 gtk_button_box_get_property (GObject    *object,
255                              guint       prop_id,
256                              GValue     *value,
257                              GParamSpec *pspec)
258 {
259   GtkButtonBoxPrivate *priv = GTK_BUTTON_BOX (object)->priv;
260
261   switch (prop_id)
262     {
263     case PROP_LAYOUT_STYLE:
264       g_value_set_enum (value, priv->layout_style);
265       break;
266     default:
267       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
268       break;
269     }
270 }
271
272 static void
273 gtk_button_box_set_child_property (GtkContainer *container,
274                                    GtkWidget    *child,
275                                    guint         property_id,
276                                    const GValue *value,
277                                    GParamSpec   *pspec)
278 {
279   switch (property_id)
280     {
281     case CHILD_PROP_SECONDARY:
282       gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (container), child,
283                                           g_value_get_boolean (value));
284       break;
285     case CHILD_PROP_NONHOMOGENEOUS:
286       gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (container), child,
287                                                 g_value_get_boolean (value));
288       break;
289     default:
290       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
291       break;
292     }
293 }
294
295 static void
296 gtk_button_box_get_child_property (GtkContainer *container,
297                                    GtkWidget    *child,
298                                    guint         property_id,
299                                    GValue       *value,
300                                    GParamSpec   *pspec)
301 {
302   switch (property_id)
303     {
304     case CHILD_PROP_SECONDARY:
305       g_value_set_boolean (value,
306                            gtk_button_box_get_child_secondary (GTK_BUTTON_BOX (container),
307                                                                child));
308       break;
309     case CHILD_PROP_NONHOMOGENEOUS:
310       g_value_set_boolean (value,
311                            gtk_button_box_get_child_non_homogeneous (GTK_BUTTON_BOX (container),
312                                                                      child));
313       break;
314     default:
315       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
316       break;
317     }
318 }
319
320 static void
321 gtk_button_box_remove (GtkContainer *container,
322                        GtkWidget    *widget)
323 {
324   /* clear is_secondary and nonhomogeneous flag in case the widget
325    * is added to another container
326    */
327   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (container), widget, FALSE);
328   gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (container), widget, FALSE);
329
330   GTK_CONTAINER_CLASS (gtk_button_box_parent_class)->remove (container, widget);
331 }
332
333 /**
334  * gtk_button_box_set_layout:
335  * @widget: a #GtkButtonBox
336  * @layout_style: the new layout style
337  *
338  * Changes the way buttons are arranged in their container.
339  */
340 void
341 gtk_button_box_set_layout (GtkButtonBox      *widget,
342                            GtkButtonBoxStyle  layout_style)
343 {
344   GtkButtonBoxPrivate *priv;
345
346   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
347
348   priv = widget->priv;
349
350   if (priv->layout_style != layout_style)
351     {
352       priv->layout_style = layout_style;
353       g_object_notify (G_OBJECT (widget), "layout-style");
354       gtk_widget_queue_resize (GTK_WIDGET (widget));
355     }
356 }
357
358 /**
359  * gtk_button_box_get_layout:
360  * @widget: a #GtkButtonBox
361  *
362  * Retrieves the method being used to arrange the buttons in a button box.
363  *
364  * Returns: the method used to lay out buttons in @widget.
365  */
366 GtkButtonBoxStyle
367 gtk_button_box_get_layout (GtkButtonBox *widget)
368 {
369   g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), DEFAULT_LAYOUT_STYLE);
370
371   return widget->priv->layout_style;
372 }
373
374 /**
375  * gtk_button_box_get_child_secondary:
376  * @widget: a #GtkButtonBox
377  * @child: a child of @widget
378  *
379  * Returns whether @child should appear in a secondary group of children.
380  *
381  * Return value: whether @child should appear in a secondary group of children.
382  *
383  * Since: 2.4
384  **/
385 gboolean
386 gtk_button_box_get_child_secondary (GtkButtonBox *widget,
387                                     GtkWidget    *child)
388 {
389   g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), FALSE);
390   g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
391
392   return (g_object_get_data (G_OBJECT (child), GTK_BOX_SECONDARY_CHILD) != NULL);
393 }
394
395 /**
396  * gtk_button_box_set_child_secondary
397  * @widget: a #GtkButtonBox
398  * @child: a child of @widget
399  * @is_secondary: if %TRUE, the @child appears in a secondary group of the
400  *                button box.
401  *
402  * Sets whether @child should appear in a secondary group of children.
403  * A typical use of a secondary child is the help button in a dialog.
404  *
405  * This group appears after the other children if the style
406  * is %GTK_BUTTONBOX_START, %GTK_BUTTONBOX_SPREAD or
407  * %GTK_BUTTONBOX_EDGE, and before the other children if the style
408  * is %GTK_BUTTONBOX_END. For horizontal button boxes, the definition
409  * of before/after depends on direction of the widget (see
410  * gtk_widget_set_direction()). If the style is %GTK_BUTTONBOX_START
411  * or %GTK_BUTTONBOX_END, then the secondary children are aligned at
412  * the other end of the button box from the main children. For the
413  * other styles, they appear immediately next to the main children.
414  **/
415 void
416 gtk_button_box_set_child_secondary (GtkButtonBox *widget,
417                                     GtkWidget    *child,
418                                     gboolean      is_secondary)
419 {
420   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
421   g_return_if_fail (GTK_IS_WIDGET (child));
422   g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (widget));
423
424   g_object_set_data (G_OBJECT (child),
425                      GTK_BOX_SECONDARY_CHILD,
426                      is_secondary ? GINT_TO_POINTER (1) : NULL);
427   gtk_widget_child_notify (child, "secondary");
428
429   if (gtk_widget_get_visible (GTK_WIDGET (widget)) &&
430       gtk_widget_get_visible (child))
431     gtk_widget_queue_resize (child);
432 }
433
434 /* Ask children how much space they require and round up
435  * to match minimum size and internal padding.
436  * Returns the size each single child should have.
437  */
438 static void
439 gtk_button_box_child_requisition (GtkWidget  *widget,
440                                   gint       *nvis_children,
441                                   gint       *nvis_secondaries,
442                                   gint      **widths,
443                                   gint      **heights)
444 {
445   GtkButtonBox *bbox;
446   GList *children, *list;
447   gint nchildren;
448   gint nsecondaries;
449   gint needed_width;
450   gint needed_height;
451   gint avg_w, avg_h;
452   GtkRequisition child_requisition;
453   gint ipad_w;
454   gint ipad_h;
455   gint child_min_width;
456   gint child_min_height;
457   gint ipad_x;
458   gint ipad_y;
459   gboolean homogeneous;
460   gint i;
461
462   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
463
464   bbox = GTK_BUTTON_BOX (widget);
465
466   homogeneous = gtk_box_get_homogeneous (GTK_BOX (widget));
467
468   gtk_widget_style_get (widget,
469                         "child-min-width", &child_min_width,
470                         "child-min-height", &child_min_height,
471                         "child-internal-pad-x", &ipad_x,
472                         "child-internal-pad-y", &ipad_y,
473                         NULL);
474
475   nchildren = 0;
476   nsecondaries = 0;
477   list = children = _gtk_box_get_children (GTK_BOX (bbox));
478   needed_width = child_min_width;
479   needed_height = child_min_height;
480   ipad_w = ipad_x * 2;
481   ipad_h = ipad_y * 2;
482
483   avg_w = avg_h = 0;
484   while (children)
485     {
486       GtkWidget *child;
487
488       child = children->data;
489       children = children->next;
490
491       if (gtk_widget_get_visible (child))
492         {
493           nchildren += 1;
494           gtk_widget_get_preferred_size (child,
495                                          &child_requisition, NULL);
496           avg_w += child_requisition.width + ipad_w;
497           avg_h += child_requisition.height + ipad_h;
498         }
499     }
500   avg_w /= MAX (nchildren, 1);
501   avg_h /= MAX (nchildren, 1);
502
503   *widths = g_new (gint, nchildren);
504   *heights = g_new (gint, nchildren);
505
506   i = 0;
507   children = list;
508   while (children)
509     {
510       GtkWidget *child;
511       gboolean is_secondary;
512       gboolean non_homogeneous;
513
514       child = children->data;
515       children = children->next;
516
517       if (gtk_widget_get_visible (child))
518         {
519           is_secondary = gtk_button_box_get_child_secondary (bbox, child);
520           non_homogeneous = gtk_button_box_get_child_non_homogeneous (bbox, child);
521
522           if (is_secondary)
523             nsecondaries++;
524
525           gtk_widget_get_preferred_size (child, &child_requisition, NULL);
526
527           if (homogeneous ||
528               (!non_homogeneous && (child_requisition.width + ipad_w < avg_w * 1.5)))
529             {
530               (*widths)[i] = -1;
531               if (child_requisition.width + ipad_w > needed_width)
532                 needed_width = child_requisition.width + ipad_w;
533             }
534           else
535             {
536               (*widths)[i] = child_requisition.width + ipad_w;
537             }
538
539           if (homogeneous ||
540               (!non_homogeneous && (child_requisition.height + ipad_h < avg_h * 1.5)))
541             {
542               (*heights)[i] = -1;
543               if (child_requisition.height + ipad_h > needed_height)
544                 needed_height = child_requisition.height + ipad_h;
545             }
546           else
547             {
548               (*heights)[i] = child_requisition.height + ipad_h;
549             }
550
551           i++;
552         }
553     }
554
555   g_list_free (list);
556
557   for (i = 0; i < nchildren; i++)
558     {
559       if ((*widths)[i] == -1)
560         (*widths)[i] = needed_width;
561       if ((*heights)[i] == -1)
562         (*heights)[i] = needed_height;
563     }
564
565   if (nvis_children)
566     *nvis_children = nchildren;
567
568   if (nvis_secondaries)
569     *nvis_secondaries = nsecondaries;
570 }
571
572 static void
573 gtk_button_box_size_request (GtkWidget      *widget,
574                              GtkRequisition *requisition)
575 {
576   GtkButtonBoxPrivate *priv;
577   GtkButtonBox *bbox;
578   gint nvis_children;
579   gint max_size;
580   gint total_size;
581   gint spacing;
582   GtkOrientation orientation;
583   gint *widths;
584   gint *heights;
585   gint i;
586
587   bbox = GTK_BUTTON_BOX (widget);
588   priv = bbox->priv;
589
590   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
591   spacing = gtk_box_get_spacing (GTK_BOX (widget));
592
593   gtk_button_box_child_requisition (widget,
594                                     &nvis_children,
595                                     NULL,
596                                     &widths, &heights);
597
598   max_size = 0;
599   total_size = 0;
600   for (i = 0; i < nvis_children; i++)
601     {
602       if (orientation == GTK_ORIENTATION_HORIZONTAL)
603         {
604           total_size += widths[i];
605           max_size = MAX (max_size, heights[i]);
606         }
607       else
608         {
609           total_size += heights[i];
610           max_size = MAX (max_size, widths[i]);
611         }
612     }
613   g_free (widths);
614   g_free (heights);
615
616   if (nvis_children == 0)
617     {
618       requisition->width = 0;
619       requisition->height = 0;
620     }
621   else
622     {
623       switch (priv->layout_style)
624         {
625           case GTK_BUTTONBOX_SPREAD:
626             if (orientation == GTK_ORIENTATION_HORIZONTAL)
627               requisition->width = total_size + ((nvis_children + 1)*spacing);
628             else
629               requisition->height = total_size + ((nvis_children + 1)*spacing);
630
631             break;
632           case GTK_BUTTONBOX_EDGE:
633           case GTK_BUTTONBOX_START:
634           case GTK_BUTTONBOX_END:
635           case GTK_BUTTONBOX_CENTER:
636             if (orientation == GTK_ORIENTATION_HORIZONTAL)
637               requisition->width = total_size + ((nvis_children - 1)*spacing);
638             else
639               requisition->height = total_size + ((nvis_children - 1)*spacing);
640
641             break;
642           default:
643             g_assert_not_reached ();
644             break;
645         }
646
647       if (orientation == GTK_ORIENTATION_HORIZONTAL)
648         requisition->height = max_size;
649       else
650         requisition->width = max_size;
651     }
652 }
653
654 static void
655 gtk_button_box_get_preferred_width (GtkWidget *widget,
656                                     gint      *minimum,
657                                     gint      *natural)
658 {
659   GtkRequisition requisition;
660
661   gtk_button_box_size_request (widget, &requisition);
662
663   *minimum = *natural = requisition.width;
664 }
665
666 static void
667 gtk_button_box_get_preferred_height (GtkWidget *widget,
668                                      gint      *minimum,
669                                      gint      *natural)
670 {
671   GtkRequisition requisition;
672
673   gtk_button_box_size_request (widget, &requisition);
674
675   *minimum = *natural = requisition.height;
676 }
677
678 static void
679 gtk_button_box_get_preferred_width_for_height (GtkWidget *widget,
680                                                gint       height,
681                                                gint      *minimum,
682                                                gint      *natural)
683 {
684   gtk_button_box_get_preferred_width (widget, minimum, natural);
685 }
686
687 static void
688 gtk_button_box_get_preferred_height_for_width (GtkWidget *widget,
689                                                gint       width,
690                                                gint      *minimum,
691                                                gint      *natural)
692 {
693   gtk_button_box_get_preferred_height (widget, minimum, natural);
694 }
695
696 static void
697 gtk_button_box_size_allocate (GtkWidget     *widget,
698                               GtkAllocation *allocation)
699 {
700   GtkButtonBoxPrivate *priv;
701   GtkButtonBox *bbox;
702   GList *children, *list;
703   GtkAllocation child_allocation;
704   gint nvis_children;
705   gint n_primaries;
706   gint n_secondaries;
707   gint x = 0;
708   gint y = 0;
709   gint secondary_x = 0;
710   gint secondary_y = 0;
711   gint width = 0;
712   gint height = 0;
713   gint childspacing = 0;
714   gint spacing;
715   GtkOrientation orientation;
716   gint ipad_x, ipad_y;
717   gint *widths;
718   gint *heights;
719   gint *sizes;
720   gint primary_size;
721   gint secondary_size;
722   gint total_size;
723   gint i;
724
725   bbox = GTK_BUTTON_BOX (widget);
726   priv = bbox->priv;
727
728   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
729   spacing = gtk_box_get_spacing (GTK_BOX (widget));
730
731   gtk_widget_style_get (widget,
732                         "child-internal-pad-x", &ipad_x,
733                         "child-internal-pad-y", &ipad_y,
734                         NULL);
735   gtk_button_box_child_requisition (widget,
736                                     &nvis_children,
737                                     &n_secondaries,
738                                     &widths, &heights);
739
740   n_primaries = nvis_children - n_secondaries;
741   primary_size = 0;
742   secondary_size = 0;
743   if (orientation == GTK_ORIENTATION_HORIZONTAL)
744     sizes = widths;
745   else
746     sizes = heights;
747
748   i = 0;
749   list = children = _gtk_box_get_children (GTK_BOX (widget));
750   while (children)
751     {
752       GtkWidget *child;
753
754       child = children->data;
755       children = children->next;
756
757       if (gtk_widget_get_visible (child))
758         {
759           if (gtk_button_box_get_child_secondary (bbox, child))
760             secondary_size += sizes[i];
761           else
762             primary_size += sizes[i];
763           i++;
764         }
765     }
766   total_size = primary_size + secondary_size;
767
768   gtk_widget_set_allocation (widget, allocation);
769
770   if (orientation == GTK_ORIENTATION_HORIZONTAL)
771     width = allocation->width;
772   else
773     height = allocation->height;
774
775   switch (priv->layout_style)
776     {
777       case GTK_BUTTONBOX_SPREAD:
778
779         if (orientation == GTK_ORIENTATION_HORIZONTAL)
780           {
781             childspacing = (width - total_size) / (nvis_children + 1);
782             x = allocation->x + childspacing;
783             secondary_x = x + primary_size + n_primaries * childspacing;
784           }
785         else
786           {
787             childspacing = (height - total_size) / (nvis_children + 1);
788             y = allocation->y + childspacing;
789             secondary_y = y + primary_size + n_primaries * childspacing;
790           }
791
792         break;
793
794       case GTK_BUTTONBOX_EDGE:
795
796         if (orientation == GTK_ORIENTATION_HORIZONTAL)
797           {
798             if (nvis_children >= 2)
799               {
800                 childspacing = (width - total_size) / (nvis_children - 1);
801                 x = allocation->x;
802                 secondary_x = x + primary_size + n_primaries * childspacing;
803               }
804             else if (nvis_children == 1)
805               {
806                 /* one child, just center */
807                 childspacing = width;
808                 x = secondary_x = allocation->x
809                                   + (allocation->width - widths[0]) / 2;
810               }
811             else
812               {
813                 /* zero children, meh */
814                 childspacing = width;
815                 x = secondary_x = allocation->x + allocation->width / 2;
816               }
817           }
818         else
819           {
820             if (nvis_children >= 2)
821               {
822                 childspacing = (height - total_size) / (nvis_children - 1);
823                 y = allocation->y;
824                 secondary_y = y + primary_size + n_primaries * childspacing;
825               }
826             else if (nvis_children == 1)
827               {
828                 /* one child, just center */
829                 childspacing = height;
830                 y = secondary_y = allocation->y
831                                      + (allocation->height - heights[0]) / 2;
832               }
833             else
834               {
835                 /* zero children, meh */
836                 childspacing = height;
837                 y = secondary_y = allocation->y + allocation->height / 2;
838               }
839           }
840
841         break;
842
843       case GTK_BUTTONBOX_START:
844
845         if (orientation == GTK_ORIENTATION_HORIZONTAL)
846           {
847             childspacing = spacing;
848             x = allocation->x;
849             secondary_x = allocation->x + allocation->width
850               - secondary_size - spacing * (n_secondaries - 1);
851           }
852         else
853           {
854             childspacing = spacing;
855             y = allocation->y;
856             secondary_y = allocation->y + allocation->height
857               - secondary_size - spacing * (n_secondaries - 1);
858           }
859
860         break;
861
862       case GTK_BUTTONBOX_END:
863
864         if (orientation == GTK_ORIENTATION_HORIZONTAL)
865           {
866             childspacing = spacing;
867             x = allocation->x + allocation->width
868               - primary_size - spacing * (n_primaries - 1);
869             secondary_x = allocation->x;
870           }
871         else
872           {
873             childspacing = spacing;
874             y = allocation->y + allocation->height
875               - primary_size - spacing * (n_primaries - 1);
876             secondary_y = allocation->y;
877           }
878
879         break;
880
881       case GTK_BUTTONBOX_CENTER:
882
883         if (orientation == GTK_ORIENTATION_HORIZONTAL)
884           {
885             childspacing = spacing;
886             x = allocation->x +
887               (allocation->width
888                - (primary_size + spacing * (n_primaries - 1))) / 2
889               + (secondary_size + n_secondaries * spacing) / 2;
890             secondary_x = allocation->x;
891           }
892         else
893           {
894             childspacing = spacing;
895             y = allocation->y +
896               (allocation->height
897                - (primary_size + spacing * (n_primaries - 1))) / 2
898               + (secondary_size + n_secondaries * spacing) / 2;
899             secondary_y = allocation->y;
900           }
901
902         break;
903
904       default:
905         g_assert_not_reached ();
906         break;
907     }
908
909   children = list;
910   i = 0;
911   while (children)
912     {
913       GtkWidget *child;
914
915       child = children->data;
916       children = children->next;
917
918       if (gtk_widget_get_visible (child))
919         {
920           child_allocation.width = widths[i];
921           child_allocation.height = heights[i];
922
923           if (orientation == GTK_ORIENTATION_HORIZONTAL)
924             {
925               child_allocation.y = allocation->y + (allocation->height - child_allocation.height) / 2;
926
927               if (gtk_button_box_get_child_secondary (bbox, child))
928                 {
929                   child_allocation.x = secondary_x;
930                   secondary_x += child_allocation.width + childspacing;
931                 }
932               else
933                 {
934                   child_allocation.x = x;
935                   x += child_allocation.width + childspacing;
936                 }
937
938               if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
939                   child_allocation.x = (allocation->x + allocation->width)
940                           - (child_allocation.x + child_allocation.width - allocation->x);
941             }
942           else
943             {
944               child_allocation.x = allocation->x + (allocation->width - child_allocation.width) / 2;
945
946               if (gtk_button_box_get_child_secondary (bbox, child))
947                 {
948                   child_allocation.y = secondary_y;
949                   secondary_y += child_allocation.height + childspacing;
950                 }
951               else
952                 {
953                   child_allocation.y = y;
954                   y += child_allocation.height + childspacing;
955                 }
956             }
957
958           gtk_widget_size_allocate (child, &child_allocation);
959           i++;
960         }
961     }
962
963   g_list_free (list);
964   g_free (widths);
965   g_free (heights);
966 }
967
968 /**
969  * gtk_button_box_new:
970  * @orientation: the box' orientation.
971  *
972  * Creates a new #GtkButtonBox.
973  *
974  * Return value: a new #GtkButtonBox.
975  *
976  * Since: 3.0
977  */
978 GtkWidget *
979 gtk_button_box_new (GtkOrientation orientation)
980 {
981   return g_object_new (GTK_TYPE_BUTTON_BOX,
982                        "orientation", orientation,
983                        NULL);
984 }
985
986 /**
987  * gtk_button_box_get_child_non_homogeneous:
988  * @widget: a #GtkButtonBox
989  * @child: a child of @widget
990  *
991  * Returns whether the child is exempted from homogenous
992  * sizing.
993  *
994  * Returns: %TRUE if the child is not subject to homogenous sizing
995  *
996  * Since: 3.2
997  */
998 gboolean
999 gtk_button_box_get_child_non_homogeneous (GtkButtonBox *widget,
1000                                           GtkWidget    *child)
1001 {
1002   g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), FALSE);
1003   g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
1004
1005   return (g_object_get_data (G_OBJECT (child), GTK_BOX_NON_HOMOGENEOUS) != NULL);
1006 }
1007
1008 /**
1009  * gtk_button_box_set_child_non_homogeneous:
1010  * @widget: a #GtkButtonBox
1011  * @child: a child of @widget
1012  * @non_homogeneous: the new value
1013  *
1014  * Sets whether the child is exempted from homogeous sizing.
1015  *
1016  * Since: 3.2
1017  */
1018 void
1019 gtk_button_box_set_child_non_homogeneous (GtkButtonBox *widget,
1020                                           GtkWidget    *child,
1021                                           gboolean      non_homogeneous)
1022 {
1023   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
1024   g_return_if_fail (GTK_IS_WIDGET (child));
1025   g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (widget));
1026
1027   g_object_set_data (G_OBJECT (child),
1028                      GTK_BOX_NON_HOMOGENEOUS,
1029                      non_homogeneous ? GINT_TO_POINTER (1) : NULL);
1030   gtk_widget_child_notify (child, "non-homogeneous");
1031
1032   if (gtk_widget_get_visible (GTK_WIDGET (widget)) &&
1033       gtk_widget_get_visible (child))
1034     gtk_widget_queue_resize (child);
1035 }