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