]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkbbox.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkbbox.c
index c675ed73000d405f019743aeed35f4ec56f38a70..8385e2dd2e659a7489f56b40c8b34e579e56ab35 100644 (file)
@@ -12,9 +12,7 @@
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  */
 
 /*
 
 /**
  * SECTION:gtkbbox
- * @Short_description: Base class for GtkHButtonBox and GtkVButtonBox
+ * @Short_description: A container for arranging buttons
  * @Title: GtkButtonBox
- * @See_also: #GtkVButtonBox, #GtkHButtonBox
  *
- * The primary purpose of this class is to keep track of the various properties
- * of #GtkHButtonBox and #GtkVButtonBox widgets.
- *
- * gtk_button_box_get_child_size() retrieves the minimum width and height
- * for widgets in a given button box.
- *
- * The internal padding of buttons can be retrieved and changed per button box
- * using gtk_button_box_get_child_ipadding() and
- * gtk_button_box_set_child_ipadding() respectively.
- *
- * gtk_button_box_get_spacing() and gtk_button_box_set_spacing() retrieve and
- * change default number of pixels between buttons, respectively.
+ * A button box should be used to provide a consistent layout of buttons
+ * throughout your application. The layout/spacing can be altered by the
+ * programmer, or if desired, by the user to alter the 'feel' of a
+ * program to a small degree.
  *
  * 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
- * same size. Therefore it ignores the homogeneous property which it inherited
- * from GtkBox, and always behaves as if homogeneous was %TRUE.
+ * same size. GtkButtonBox gives all children the same size, but it does allow
+ * 'outliers' to keep their own larger size. To force all children to be
+ * strictly the same size without exceptions, you can set the
+ * #GtkButtonBox:homogeneous property to %TRUE.
+ *
+ * To excempt individual children from homogeneous sizing regardless of their
+ * 'outlier' status, you can set the #GtkButtonBox:non-homogeneous child
+ * property.
  */
 
 #include "config.h"
+
 #include "gtkbbox.h"
-#include "gtkhbbox.h"
-#include "gtkvbbox.h"
+
+#include "gtkboxprivate.h"
 #include "gtkorientable.h"
+#include "gtktypebuiltins.h"
 #include "gtkprivate.h"
+#include "gtksizerequest.h"
+
 #include "gtkintl.h"
 
 
+struct _GtkButtonBoxPrivate
+{
+  GtkButtonBoxStyle layout_style;
+};
+
 enum {
   PROP_0,
   PROP_LAYOUT_STYLE
@@ -68,36 +72,56 @@ enum {
 
 enum {
   CHILD_PROP_0,
-  CHILD_PROP_SECONDARY
+  CHILD_PROP_SECONDARY,
+  CHILD_PROP_NONHOMOGENEOUS
 };
 
+#define GTK_BOX_SECONDARY_CHILD "gtk-box-secondary-child"
+#define GTK_BOX_NON_HOMOGENEOUS "gtk-box-non-homogeneous"
+
 static void gtk_button_box_set_property       (GObject           *object,
-                                              guint              prop_id,
-                                              const GValue      *value,
-                                              GParamSpec        *pspec);
+                                               guint              prop_id,
+                                               const GValue      *value,
+                                               GParamSpec        *pspec);
 static void gtk_button_box_get_property       (GObject           *object,
-                                              guint              prop_id,
-                                              GValue            *value,
-                                              GParamSpec        *pspec);
-static void gtk_button_box_size_request       (GtkWidget         *widget,
-                                               GtkRequisition    *requisition);
+                                               guint              prop_id,
+                                               GValue            *value,
+                                               GParamSpec        *pspec);
+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);
+
 static void gtk_button_box_size_allocate      (GtkWidget         *widget,
                                                GtkAllocation     *allocation);
+static void gtk_button_box_remove             (GtkContainer      *container,
+                                               GtkWidget         *widget);
 static void gtk_button_box_set_child_property (GtkContainer      *container,
-                                              GtkWidget         *child,
-                                              guint              property_id,
-                                              const GValue      *value,
-                                              GParamSpec        *pspec);
+                                               GtkWidget         *child,
+                                               guint              property_id,
+                                               const GValue      *value,
+                                               GParamSpec        *pspec);
 static void gtk_button_box_get_child_property (GtkContainer      *container,
-                                              GtkWidget         *child,
-                                              guint              property_id,
-                                              GValue            *value,
-                                              GParamSpec        *pspec);
+                                               GtkWidget         *child,
+                                               guint              property_id,
+                                               GValue            *value,
+                                               GParamSpec        *pspec);
 
 #define DEFAULT_CHILD_MIN_WIDTH 85
 #define DEFAULT_CHILD_MIN_HEIGHT 27
 #define DEFAULT_CHILD_IPAD_X 4
 #define DEFAULT_CHILD_IPAD_Y 0
+#define DEFAULT_LAYOUT_STYLE GTK_BUTTONBOX_EDGE
 
 G_DEFINE_TYPE (GtkButtonBox, gtk_button_box, GTK_TYPE_BOX)
 
@@ -115,90 +139,108 @@ gtk_button_box_class_init (GtkButtonBoxClass *class)
   gobject_class->set_property = gtk_button_box_set_property;
   gobject_class->get_property = gtk_button_box_get_property;
 
-  widget_class->size_request = gtk_button_box_size_request;
+  widget_class->get_preferred_width = gtk_button_box_get_preferred_width;
+  widget_class->get_preferred_height = gtk_button_box_get_preferred_height;
+  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;
   widget_class->size_allocate = gtk_button_box_size_allocate;
 
+  container_class->remove = gtk_button_box_remove;
   container_class->set_child_property = gtk_button_box_set_child_property;
   container_class->get_child_property = gtk_button_box_get_child_property;
+  gtk_container_class_handle_border_width (container_class);
 
   /* FIXME we need to override the "spacing" property on GtkBox once
    * libgobject allows that.
    */
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("child-min-width",
-                                                            P_("Minimum child width"),
-                                                            P_("Minimum width of buttons inside the box"),
-                                                            0,
-                                                            G_MAXINT,
+                                           g_param_spec_int ("child-min-width",
+                                                             P_("Minimum child width"),
+                                                             P_("Minimum width of buttons inside the box"),
+                                                             0,
+                                                             G_MAXINT,
                                                              DEFAULT_CHILD_MIN_WIDTH,
-                                                            GTK_PARAM_READABLE));
+                                                             GTK_PARAM_READABLE));
 
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("child-min-height",
-                                                            P_("Minimum child height"),
-                                                            P_("Minimum height of buttons inside the box"),
-                                                            0,
-                                                            G_MAXINT,
+                                           g_param_spec_int ("child-min-height",
+                                                             P_("Minimum child height"),
+                                                             P_("Minimum height of buttons inside the box"),
+                                                             0,
+                                                             G_MAXINT,
                                                              DEFAULT_CHILD_MIN_HEIGHT,
-                                                            GTK_PARAM_READABLE));
+                                                             GTK_PARAM_READABLE));
 
   gtk_widget_class_install_style_property (widget_class,
-                                          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,
+                                           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,
                                                              DEFAULT_CHILD_IPAD_X,
-                                                            GTK_PARAM_READABLE));
+                                                             GTK_PARAM_READABLE));
 
   gtk_widget_class_install_style_property (widget_class,
-                                          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,
+                                           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,
                                                              DEFAULT_CHILD_IPAD_Y,
-                                                            GTK_PARAM_READABLE));
+                                                             GTK_PARAM_READABLE));
   g_object_class_install_property (gobject_class,
                                    PROP_LAYOUT_STYLE,
                                    g_param_spec_enum ("layout-style",
                                                       P_("Layout style"),
-                                                      P_("How to layout the buttons in the box. Possible values are default, spread, edge, start and end"),
-                                                     GTK_TYPE_BUTTON_BOX_STYLE,
-                                                     GTK_BUTTONBOX_DEFAULT_STYLE,
+                                                      P_("How to lay out the buttons in the box. Possible values are: spread, edge, start and end"),
+                                                      GTK_TYPE_BUTTON_BOX_STYLE,
+                                                      DEFAULT_LAYOUT_STYLE,
                                                       GTK_PARAM_READWRITE));
 
   gtk_container_class_install_child_property (container_class,
-                                             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));
+                                              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));
+
+  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));
+
+  g_type_class_add_private (class, sizeof (GtkButtonBoxPrivate));
 }
 
 static void
 gtk_button_box_init (GtkButtonBox *button_box)
 {
-  GTK_BOX (button_box)->spacing = 0;
-  button_box->child_min_width = GTK_BUTTONBOX_DEFAULT;
-  button_box->child_min_height = GTK_BUTTONBOX_DEFAULT;
-  button_box->child_ipad_x = GTK_BUTTONBOX_DEFAULT;
-  button_box->child_ipad_y = GTK_BUTTONBOX_DEFAULT;
-  button_box->layout_style = GTK_BUTTONBOX_DEFAULT_STYLE;
+  GtkButtonBoxPrivate *priv;
+
+  button_box->priv = G_TYPE_INSTANCE_GET_PRIVATE (button_box,
+                                                  GTK_TYPE_BUTTON_BOX,
+                                                  GtkButtonBoxPrivate);
+  priv = button_box->priv;
+
+  gtk_box_set_spacing (GTK_BOX (button_box), 0);
+  priv->layout_style = DEFAULT_LAYOUT_STYLE;
 }
 
 static void
-gtk_button_box_set_property (GObject         *object,
-                            guint            prop_id,
-                            const GValue    *value,
-                            GParamSpec      *pspec)
+gtk_button_box_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
 {
   switch (prop_id)
     {
     case PROP_LAYOUT_STYLE:
       gtk_button_box_set_layout (GTK_BUTTON_BOX (object),
-                                g_value_get_enum (value));
+                                 g_value_get_enum (value));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -207,15 +249,17 @@ gtk_button_box_set_property (GObject         *object,
 }
 
 static void
-gtk_button_box_get_property (GObject         *object,
-                            guint            prop_id,
-                            GValue          *value,
-                            GParamSpec      *pspec)
+gtk_button_box_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
 {
+  GtkButtonBoxPrivate *priv = GTK_BUTTON_BOX (object)->priv;
+
   switch (prop_id)
     {
     case PROP_LAYOUT_STYLE:
-      g_value_set_enum (value, GTK_BUTTON_BOX (object)->layout_style);
+      g_value_set_enum (value, priv->layout_style);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -224,17 +268,21 @@ gtk_button_box_get_property (GObject         *object,
 }
 
 static void
-gtk_button_box_set_child_property (GtkContainer    *container,
-                                  GtkWidget       *child,
-                                  guint            property_id,
-                                  const GValue    *value,
-                                  GParamSpec      *pspec)
+gtk_button_box_set_child_property (GtkContainer *container,
+                                   GtkWidget    *child,
+                                   guint         property_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
 {
   switch (property_id)
     {
     case CHILD_PROP_SECONDARY:
       gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (container), child,
-                                         g_value_get_boolean (value));
+                                          g_value_get_boolean (value));
+      break;
+    case CHILD_PROP_NONHOMOGENEOUS:
+      gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (container), child,
+                                                g_value_get_boolean (value));
       break;
     default:
       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
@@ -244,17 +292,22 @@ gtk_button_box_set_child_property (GtkContainer    *container,
 
 static void
 gtk_button_box_get_child_property (GtkContainer *container,
-                                  GtkWidget    *child,
-                                  guint         property_id,
-                                  GValue       *value,
-                                  GParamSpec   *pspec)
+                                   GtkWidget    *child,
+                                   guint         property_id,
+                                   GValue       *value,
+                                   GParamSpec   *pspec)
 {
   switch (property_id)
     {
     case CHILD_PROP_SECONDARY:
-      g_value_set_boolean (value, 
-                          gtk_button_box_get_child_secondary (GTK_BUTTON_BOX (container), 
-                                                              child));
+      g_value_set_boolean (value,
+                           gtk_button_box_get_child_secondary (GTK_BUTTON_BOX (container),
+                                                               child));
+      break;
+    case CHILD_PROP_NONHOMOGENEOUS:
+      g_value_set_boolean (value,
+                           gtk_button_box_get_child_non_homogeneous (GTK_BUTTON_BOX (container),
+                                                                     child));
       break;
     default:
       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
@@ -262,6 +315,19 @@ gtk_button_box_get_child_property (GtkContainer *container,
     }
 }
 
+static void
+gtk_button_box_remove (GtkContainer *container,
+                       GtkWidget    *widget)
+{
+  /* clear is_secondary and nonhomogeneous flag in case the widget
+   * is added to another container
+   */
+  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);
+
+  GTK_CONTAINER_CLASS (gtk_button_box_parent_class)->remove (container, widget);
+}
+
 /**
  * gtk_button_box_set_layout:
  * @widget: a #GtkButtonBox
@@ -270,16 +336,18 @@ gtk_button_box_get_child_property (GtkContainer *container,
  * Changes the way buttons are arranged in their container.
  */
 void
-gtk_button_box_set_layout (GtkButtonBox      *widget, 
+gtk_button_box_set_layout (GtkButtonBox      *widget,
                            GtkButtonBoxStyle  layout_style)
 {
+  GtkButtonBoxPrivate *priv;
+
   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
-  g_return_if_fail (layout_style >= GTK_BUTTONBOX_DEFAULT_STYLE &&
-                   layout_style <= GTK_BUTTONBOX_CENTER);
 
-  if (widget->layout_style != layout_style)
+  priv = widget->priv;
+
+  if (priv->layout_style != layout_style)
     {
-      widget->layout_style = layout_style;
+      priv->layout_style = layout_style;
       g_object_notify (G_OBJECT (widget), "layout-style");
       gtk_widget_queue_resize (GTK_WIDGET (widget));
     }
@@ -291,55 +359,39 @@ gtk_button_box_set_layout (GtkButtonBox      *widget,
  *
  * Retrieves the method being used to arrange the buttons in a button box.
  *
- * Returns: the method used to layout buttons in @widget.
+ * Returns: the method used to lay out buttons in @widget.
  */
-GtkButtonBoxStyle 
+GtkButtonBoxStyle
 gtk_button_box_get_layout (GtkButtonBox *widget)
 {
-  g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), GTK_BUTTONBOX_SPREAD);
-  
-  return widget->layout_style;
+  g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), DEFAULT_LAYOUT_STYLE);
+
+  return widget->priv->layout_style;
 }
 
 /**
  * gtk_button_box_get_child_secondary:
  * @widget: a #GtkButtonBox
- * @child: a child of @widget 
- * 
+ * @child: a child of @widget
+ *
  * Returns whether @child should appear in a secondary group of children.
  *
  * Return value: whether @child should appear in a secondary group of children.
  *
  * Since: 2.4
  **/
-gboolean 
+gboolean
 gtk_button_box_get_child_secondary (GtkButtonBox *widget,
-                                   GtkWidget    *child)
+                                    GtkWidget    *child)
 {
-  GList *list;
-  GtkBoxChild *child_info;
-
   g_return_val_if_fail (GTK_IS_BUTTON_BOX (widget), FALSE);
   g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
 
-  child_info = NULL;
-  list = GTK_BOX (widget)->children;
-  while (list)
-    {
-      child_info = list->data;
-      if (child_info->widget == child)
-       break;
-
-      list = list->next;
-    }
-
-  g_return_val_if_fail (list != NULL, FALSE);
-
-  return child_info->is_secondary;
+  return (g_object_get_data (G_OBJECT (child), GTK_BOX_SECONDARY_CHILD) != NULL);
 }
 
 /**
- * gtk_button_box_set_child_secondary
+ * gtk_button_box_set_child_secondary:
  * @widget: a #GtkButtonBox
  * @child: a child of @widget
  * @is_secondary: if %TRUE, the @child appears in a secondary group of the
@@ -358,167 +410,206 @@ gtk_button_box_get_child_secondary (GtkButtonBox *widget,
  * the other end of the button box from the main children. For the
  * other styles, they appear immediately next to the main children.
  **/
-void 
-gtk_button_box_set_child_secondary (GtkButtonBox *widget, 
-                                   GtkWidget    *child,
-                                   gboolean      is_secondary)
+void
+gtk_button_box_set_child_secondary (GtkButtonBox *widget,
+                                    GtkWidget    *child,
+                                    gboolean      is_secondary)
 {
-  GList *list;
-  
   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
   g_return_if_fail (GTK_IS_WIDGET (child));
-  g_return_if_fail (child->parent == GTK_WIDGET (widget));
-
-  list = GTK_BOX (widget)->children;
-  while (list)
-    {
-      GtkBoxChild *child_info = list->data;
-      if (child_info->widget == child)
-       {
-         child_info->is_secondary = is_secondary;
-         break;
-       }
-
-      list = list->next;
-    }
+  g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (widget));
 
+  g_object_set_data (G_OBJECT (child),
+                     GTK_BOX_SECONDARY_CHILD,
+                     is_secondary ? GINT_TO_POINTER (1) : NULL);
   gtk_widget_child_notify (child, "secondary");
 
-  if (gtk_widget_get_visible (GTK_WIDGET (widget))
-      && gtk_widget_get_visible (child))
+  if (gtk_widget_get_visible (GTK_WIDGET (widget)) &&
+      gtk_widget_get_visible (child))
     gtk_widget_queue_resize (child);
 }
 
-/* Ask children how much space they require and round up 
-   to match minimum size and internal padding.
-   Returns the size each single child should have. */
-void
-_gtk_button_box_child_requisition (GtkWidget *widget,
-                                   int       *nvis_children,
-                                  int       *nvis_secondaries,
-                                   int       *width,
-                                   int       *height)
+/* Ask children how much space they require and round up
+ * to match minimum size and internal padding.
+ * Returns the size each single child should have.
+ */
+static void
+gtk_button_box_child_requisition (GtkWidget  *widget,
+                                  gint       *nvis_children,
+                                  gint       *nvis_secondaries,
+                                  gint      **widths,
+                                  gint      **heights)
 {
   GtkButtonBox *bbox;
-  GtkBoxChild *child;
-  GList *children;
+  GList *children, *list;
   gint nchildren;
   gint nsecondaries;
   gint needed_width;
   gint needed_height;
+  gint avg_w, avg_h;
   GtkRequisition child_requisition;
   gint ipad_w;
   gint ipad_h;
-  gint width_default;
-  gint height_default;
-  gint ipad_x_default;
-  gint ipad_y_default;
-  
   gint child_min_width;
   gint child_min_height;
   gint ipad_x;
   gint ipad_y;
-  
+  gboolean homogeneous;
+  gint i;
+
   g_return_if_fail (GTK_IS_BUTTON_BOX (widget));
 
   bbox = GTK_BUTTON_BOX (widget);
 
+  homogeneous = gtk_box_get_homogeneous (GTK_BOX (widget));
+
   gtk_widget_style_get (widget,
-                        "child-min-width", &width_default,
-                        "child-min-height", &height_default,
-                        "child-internal-pad-x", &ipad_x_default,
-                        "child-internal-pad-y", &ipad_y_default, 
-                       NULL);
-  
-  child_min_width = bbox->child_min_width   != GTK_BUTTONBOX_DEFAULT
-         ? bbox->child_min_width : width_default;
-  child_min_height = bbox->child_min_height !=GTK_BUTTONBOX_DEFAULT
-         ? bbox->child_min_height : height_default;
-  ipad_x = bbox->child_ipad_x != GTK_BUTTONBOX_DEFAULT
-         ? bbox->child_ipad_x : ipad_x_default;
-  ipad_y = bbox->child_ipad_y != GTK_BUTTONBOX_DEFAULT
-         ? bbox->child_ipad_y : ipad_y_default;
+                        "child-min-width", &child_min_width,
+                        "child-min-height", &child_min_height,
+                        "child-internal-pad-x", &ipad_x,
+                        "child-internal-pad-y", &ipad_y,
+                        NULL);
 
   nchildren = 0;
   nsecondaries = 0;
-  children = GTK_BOX(bbox)->children;
+  list = children = _gtk_box_get_children (GTK_BOX (bbox));
   needed_width = child_min_width;
-  needed_height = child_min_height;  
+  needed_height = child_min_height;
   ipad_w = ipad_x * 2;
   ipad_h = ipad_y * 2;
-  
+
+  avg_w = avg_h = 0;
   while (children)
     {
+      GtkWidget *child;
+
       child = children->data;
       children = children->next;
 
-      if (gtk_widget_get_visible (child->widget))
-       {
-         nchildren += 1;
-         gtk_widget_size_request (child->widget, &child_requisition);
-
-         if (child_requisition.width + ipad_w > needed_width)
-           needed_width = child_requisition.width + ipad_w;
-         if (child_requisition.height + ipad_h > needed_height)
-           needed_height = child_requisition.height + ipad_h;
-         if (child->is_secondary)
-           nsecondaries++;
-       }
+      if (gtk_widget_get_visible (child))
+        {
+          nchildren += 1;
+          gtk_widget_get_preferred_size (child,
+                                         &child_requisition, NULL);
+          avg_w += child_requisition.width + ipad_w;
+          avg_h += child_requisition.height + ipad_h;
+        }
+    }
+  avg_w /= MAX (nchildren, 1);
+  avg_h /= MAX (nchildren, 1);
+
+  *widths = g_new (gint, nchildren);
+  *heights = g_new (gint, nchildren);
+
+  i = 0;
+  children = list;
+  while (children)
+    {
+      GtkWidget *child;
+      gboolean is_secondary;
+      gboolean non_homogeneous;
+
+      child = children->data;
+      children = children->next;
+
+      if (gtk_widget_get_visible (child))
+        {
+          is_secondary = gtk_button_box_get_child_secondary (bbox, child);
+          non_homogeneous = gtk_button_box_get_child_non_homogeneous (bbox, child);
+
+          if (is_secondary)
+            nsecondaries++;
+
+          gtk_widget_get_preferred_size (child, &child_requisition, NULL);
+
+          if (homogeneous ||
+              (!non_homogeneous && (child_requisition.width + ipad_w < avg_w * 1.5)))
+            {
+              (*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;
+            }
+
+          if (homogeneous ||
+              (!non_homogeneous && (child_requisition.height + ipad_h < avg_h * 1.5)))
+            {
+              (*heights)[i] = -1;
+              if (child_requisition.height + ipad_h > needed_height)
+                needed_height = child_requisition.height + ipad_h;
+            }
+          else
+            {
+              (*heights)[i] = child_requisition.height + ipad_h;
+            }
+
+          i++;
+        }
+    }
+
+  g_list_free (list);
+
+  for (i = 0; i < nchildren; i++)
+    {
+      if ((*widths)[i] == -1)
+        (*widths)[i] = needed_width;
+      if ((*heights)[i] == -1)
+        (*heights)[i] = needed_height;
     }
 
   if (nvis_children)
     *nvis_children = nchildren;
+
   if (nvis_secondaries)
     *nvis_secondaries = nsecondaries;
-  if (width)
-    *width = needed_width;
-  if (height)
-    *height = needed_height;
-}
-
-/* this is a kludge function to support the deprecated
- * gtk_[vh]button_box_set_layout_default() just in case anyone is still
- * using it (why?)
- */
-static GtkButtonBoxStyle
-gtk_button_box_kludge_get_layout_default (GtkButtonBox *widget)
-{
-  GtkOrientation orientation;
-
-  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
-
-  if (orientation == GTK_ORIENTATION_HORIZONTAL)
-    return _gtk_hbutton_box_get_layout_default ();
-  else
-    return _gtk_vbutton_box_get_layout_default ();
 }
 
 static void
 gtk_button_box_size_request (GtkWidget      *widget,
                              GtkRequisition *requisition)
 {
-  GtkBox *box;
+  GtkButtonBoxPrivate *priv;
   GtkButtonBox *bbox;
   gint nvis_children;
-  gint child_width;
-  gint child_height;
+  gint max_size;
+  gint total_size;
   gint spacing;
-  GtkButtonBoxStyle layout;
   GtkOrientation orientation;
+  gint *widths;
+  gint *heights;
+  gint i;
 
-  box = GTK_BOX (widget);
   bbox = GTK_BUTTON_BOX (widget);
+  priv = bbox->priv;
 
   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
-  spacing = box->spacing;
-  layout = bbox->layout_style != GTK_BUTTONBOX_DEFAULT_STYLE
-         ? bbox->layout_style : gtk_button_box_kludge_get_layout_default (GTK_BUTTON_BOX (widget));
+  spacing = gtk_box_get_spacing (GTK_BOX (widget));
 
-  _gtk_button_box_child_requisition (widget,
-                                     &nvis_children,
-                                    NULL,
-                                     &child_width,
-                                     &child_height);
+  gtk_button_box_child_requisition (widget,
+                                    &nvis_children,
+                                    NULL,
+                                    &widths, &heights);
+
+  max_size = 0;
+  total_size = 0;
+  for (i = 0; i < nvis_children; i++)
+    {
+      if (orientation == GTK_ORIENTATION_HORIZONTAL)
+        {
+          total_size += widths[i];
+          max_size = MAX (max_size, heights[i]);
+        }
+      else
+        {
+          total_size += heights[i];
+          max_size = MAX (max_size, widths[i]);
+        }
+    }
+  g_free (widths);
+  g_free (heights);
 
   if (nvis_children == 0)
     {
@@ -527,15 +618,13 @@ gtk_button_box_size_request (GtkWidget      *widget,
     }
   else
     {
-      switch (layout)
+      switch (priv->layout_style)
         {
           case GTK_BUTTONBOX_SPREAD:
             if (orientation == GTK_ORIENTATION_HORIZONTAL)
-              requisition->width =
-                      nvis_children*child_width + ((nvis_children+1)*spacing);
+              requisition->width = total_size + ((nvis_children + 1)*spacing);
             else
-              requisition->height =
-                      nvis_children*child_height + ((nvis_children+1)*spacing);
+              requisition->height = total_size + ((nvis_children + 1)*spacing);
 
             break;
           case GTK_BUTTONBOX_EDGE:
@@ -543,11 +632,9 @@ gtk_button_box_size_request (GtkWidget      *widget,
           case GTK_BUTTONBOX_END:
           case GTK_BUTTONBOX_CENTER:
             if (orientation == GTK_ORIENTATION_HORIZONTAL)
-              requisition->width =
-                      nvis_children*child_width + ((nvis_children-1)*spacing);
+              requisition->width = total_size + ((nvis_children - 1)*spacing);
             else
-              requisition->height =
-                      nvis_children*child_height + ((nvis_children-1)*spacing);
+              requisition->height = total_size + ((nvis_children - 1)*spacing);
 
             break;
           default:
@@ -556,79 +643,148 @@ gtk_button_box_size_request (GtkWidget      *widget,
         }
 
       if (orientation == GTK_ORIENTATION_HORIZONTAL)
-        requisition->height = child_height;
+        requisition->height = max_size;
       else
-        requisition->width = child_width;
+        requisition->width = max_size;
     }
+}
 
-  requisition->width += GTK_CONTAINER (box)->border_width * 2;
-  requisition->height += GTK_CONTAINER (box)->border_width * 2;
+static void
+gtk_button_box_get_preferred_width (GtkWidget *widget,
+                                    gint      *minimum,
+                                    gint      *natural)
+{
+  GtkRequisition requisition;
+
+  gtk_button_box_size_request (widget, &requisition);
+
+  *minimum = *natural = requisition.width;
+}
+
+static void
+gtk_button_box_get_preferred_height (GtkWidget *widget,
+                                     gint      *minimum,
+                                     gint      *natural)
+{
+  GtkRequisition requisition;
+
+  gtk_button_box_size_request (widget, &requisition);
+
+  *minimum = *natural = requisition.height;
+}
+
+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);
 }
 
 static void
 gtk_button_box_size_allocate (GtkWidget     *widget,
                               GtkAllocation *allocation)
 {
-  GtkBox *base_box;
-  GtkButtonBox *box;
-  GtkBoxChild *child;
-  GList *children;
+  GtkButtonBoxPrivate *priv;
+  GtkButtonBox *bbox;
+  GList *children, *list;
   GtkAllocation child_allocation;
   gint nvis_children;
+  gint n_primaries;
   gint n_secondaries;
-  gint child_width;
-  gint child_height;
   gint x = 0;
   gint y = 0;
   gint secondary_x = 0;
   gint secondary_y = 0;
   gint width = 0;
   gint height = 0;
-  gint childspace;
   gint childspacing = 0;
-  GtkButtonBoxStyle layout;
   gint spacing;
   GtkOrientation orientation;
+  gint ipad_x, ipad_y;
+  gint *widths;
+  gint *heights;
+  gint *sizes;
+  gint primary_size;
+  gint secondary_size;
+  gint total_size;
+  gint i;
+
+  bbox = GTK_BUTTON_BOX (widget);
+  priv = bbox->priv;
 
   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
-  base_box = GTK_BOX (widget);
-  box = GTK_BUTTON_BOX (widget);
-  spacing = base_box->spacing;
-  layout = box->layout_style != GTK_BUTTONBOX_DEFAULT_STYLE
-         ? box->layout_style : gtk_button_box_kludge_get_layout_default (GTK_BUTTON_BOX (widget));
-  _gtk_button_box_child_requisition (widget,
-                                     &nvis_children,
-                                     &n_secondaries,
-                                     &child_width,
-                                     &child_height);
-  widget->allocation = *allocation;
+  spacing = gtk_box_get_spacing (GTK_BOX (widget));
 
+  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,
+                                    &widths, &heights);
+
+  n_primaries = nvis_children - n_secondaries;
+  primary_size = 0;
+  secondary_size = 0;
   if (orientation == GTK_ORIENTATION_HORIZONTAL)
-    width = allocation->width - GTK_CONTAINER (box)->border_width*2;
+    sizes = widths;
   else
-    height = allocation->height - GTK_CONTAINER (box)->border_width*2;
+    sizes = heights;
 
-  switch (layout)
+  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;
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    width = allocation->width;
+  else
+    height = allocation->height;
+
+  switch (priv->layout_style)
     {
       case GTK_BUTTONBOX_SPREAD:
 
         if (orientation == GTK_ORIENTATION_HORIZONTAL)
           {
-            childspacing = (width - (nvis_children * child_width))
-                    / (nvis_children + 1);
-            x = allocation->x + GTK_CONTAINER (box)->border_width
-                    + childspacing;
-            secondary_x = x + ((nvis_children - n_secondaries)
-                            * (child_width + childspacing));
+            childspacing = (width - total_size) / (nvis_children + 1);
+            x = allocation->x + childspacing;
+            secondary_x = x + primary_size + n_primaries * childspacing;
           }
         else
           {
-            childspacing = (height - (nvis_children * child_height))
-                    / (nvis_children + 1);
-            y = allocation->y + GTK_CONTAINER (box)->border_width
-                    + childspacing;
-            secondary_y = y + ((nvis_children - n_secondaries)
-                            * (child_height + childspacing));
+            childspacing = (height - total_size) / (nvis_children + 1);
+            y = allocation->y + childspacing;
+            secondary_y = y + primary_size + n_primaries * childspacing;
           }
 
         break;
@@ -639,36 +795,44 @@ gtk_button_box_size_allocate (GtkWidget     *widget,
           {
             if (nvis_children >= 2)
               {
-                childspacing = (width - (nvis_children * child_width))
-                      / (nvis_children - 1);
-                x = allocation->x + GTK_CONTAINER (box)->border_width;
-                secondary_x = x + ((nvis_children - n_secondaries)
-                                   * (child_width + childspacing));
+                childspacing = (width - total_size) / (nvis_children - 1);
+                x = allocation->x;
+                secondary_x = x + primary_size + n_primaries * childspacing;
               }
-            else
+            else if (nvis_children == 1)
               {
-                /* one or zero children, just center */
+                /* one child, just center */
                 childspacing = width;
                 x = secondary_x = allocation->x
-                      + (allocation->width - child_width) / 2;
+                                  + (allocation->width - widths[0]) / 2;
+              }
+            else
+              {
+                /* zero children, meh */
+                childspacing = width;
+                x = secondary_x = allocation->x + allocation->width / 2;
               }
           }
         else
           {
             if (nvis_children >= 2)
               {
-                childspacing = (height - (nvis_children*child_height))
-                        / (nvis_children-1);
-                y = allocation->y + GTK_CONTAINER (box)->border_width;
-                secondary_y = y + ((nvis_children - n_secondaries)
-                                * (child_height + childspacing));
+                childspacing = (height - total_size) / (nvis_children - 1);
+                y = allocation->y;
+                secondary_y = y + primary_size + n_primaries * childspacing;
               }
-            else
+            else if (nvis_children == 1)
               {
-                /* one or zero children, just center */
+                /* one child, just center */
                 childspacing = height;
                 y = secondary_y = allocation->y
-                        + (allocation->height - child_height) / 2;
+                                     + (allocation->height - heights[0]) / 2;
+              }
+            else
+              {
+                /* zero children, meh */
+                childspacing = height;
+                y = secondary_y = allocation->y + allocation->height / 2;
               }
           }
 
@@ -679,20 +843,16 @@ gtk_button_box_size_allocate (GtkWidget     *widget,
         if (orientation == GTK_ORIENTATION_HORIZONTAL)
           {
             childspacing = spacing;
-            x = allocation->x + GTK_CONTAINER (box)->border_width;
+            x = allocation->x;
             secondary_x = allocation->x + allocation->width
-              - child_width * n_secondaries
-              - spacing * (n_secondaries - 1)
-              - GTK_CONTAINER (box)->border_width;
+              - secondary_size - spacing * (n_secondaries - 1);
           }
         else
           {
             childspacing = spacing;
-            y = allocation->y + GTK_CONTAINER (box)->border_width;
+            y = allocation->y;
             secondary_y = allocation->y + allocation->height
-              - child_height * n_secondaries
-              - spacing * (n_secondaries - 1)
-              - GTK_CONTAINER (box)->border_width;
+              - secondary_size - spacing * (n_secondaries - 1);
           }
 
         break;
@@ -703,19 +863,15 @@ gtk_button_box_size_allocate (GtkWidget     *widget,
           {
             childspacing = spacing;
             x = allocation->x + allocation->width
-              - child_width * (nvis_children - n_secondaries)
-              - spacing * (nvis_children - n_secondaries - 1)
-              - GTK_CONTAINER (box)->border_width;
-            secondary_x = allocation->x + GTK_CONTAINER (box)->border_width;
+              - primary_size - spacing * (n_primaries - 1);
+            secondary_x = allocation->x;
           }
         else
           {
             childspacing = spacing;
             y = allocation->y + allocation->height
-              - child_height * (nvis_children - n_secondaries)
-              - spacing * (nvis_children - n_secondaries - 1)
-              - GTK_CONTAINER (box)->border_width;
-            secondary_y = allocation->y + GTK_CONTAINER (box)->border_width;
+              - primary_size - spacing * (n_primaries - 1);
+            secondary_y = allocation->y;
           }
 
         break;
@@ -727,20 +883,18 @@ gtk_button_box_size_allocate (GtkWidget     *widget,
             childspacing = spacing;
             x = allocation->x +
               (allocation->width
-               - (child_width * (nvis_children - n_secondaries)
-               + spacing * (nvis_children - n_secondaries - 1))) / 2
-              + (n_secondaries * child_width + n_secondaries * spacing) / 2;
-            secondary_x = allocation->x + GTK_CONTAINER (box)->border_width;
+               - (primary_size + spacing * (n_primaries - 1))) / 2
+              + (secondary_size + n_secondaries * spacing) / 2;
+            secondary_x = allocation->x;
           }
         else
           {
             childspacing = spacing;
             y = allocation->y +
               (allocation->height
-               - (child_height * (nvis_children - n_secondaries)
-                  + spacing * (nvis_children - n_secondaries - 1))) / 2
-              + (n_secondaries * child_height + n_secondaries * spacing) / 2;
-            secondary_y = allocation->y + GTK_CONTAINER (box)->border_width;
+               - (primary_size + spacing * (n_primaries - 1))) / 2
+              + (secondary_size + n_secondaries * spacing) / 2;
+            secondary_y = allocation->y;
           }
 
         break;
@@ -750,67 +904,63 @@ gtk_button_box_size_allocate (GtkWidget     *widget,
         break;
     }
 
-    if (orientation == GTK_ORIENTATION_HORIZONTAL)
-      {
-        y = allocation->y + (allocation->height - child_height) / 2;
-        childspace = child_width + childspacing;
-      }
-    else
-      {
-        x = allocation->x + (allocation->width - child_width) / 2;
-        childspace = child_height + childspacing;
-      }
-
-  children = GTK_BOX (box)->children;
-
+  children = list;
+  i = 0;
   while (children)
     {
+      GtkWidget *child;
+
       child = children->data;
       children = children->next;
 
-      if (gtk_widget_get_visible (child->widget))
+      if (gtk_widget_get_visible (child))
         {
-          child_allocation.width = child_width;
-          child_allocation.height = child_height;
+          child_allocation.width = widths[i];
+          child_allocation.height = heights[i];
 
           if (orientation == GTK_ORIENTATION_HORIZONTAL)
             {
-              child_allocation.y = y;
+              child_allocation.y = allocation->y + (allocation->height - child_allocation.height) / 2;
 
-              if (child->is_secondary)
+              if (gtk_button_box_get_child_secondary (bbox, child))
                 {
                   child_allocation.x = secondary_x;
-                  secondary_x += childspace;
+                  secondary_x += child_allocation.width + childspacing;
                 }
               else
                 {
                   child_allocation.x = x;
-                  x += childspace;
+                  x += child_allocation.width + childspacing;
                 }
 
               if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
                   child_allocation.x = (allocation->x + allocation->width)
-                          - (child_allocation.x + child_width - allocation->x);
+                          - (child_allocation.x + child_allocation.width - allocation->x);
             }
           else
             {
-              child_allocation.x = x;
+              child_allocation.x = allocation->x + (allocation->width - child_allocation.width) / 2;
 
-              if (child->is_secondary)
+              if (gtk_button_box_get_child_secondary (bbox, child))
                 {
                   child_allocation.y = secondary_y;
-                  secondary_y += childspace;
+                  secondary_y += child_allocation.height + childspacing;
                 }
               else
                 {
                   child_allocation.y = y;
-                  y += childspace;
+                  y += child_allocation.height + childspacing;
                 }
             }
 
-          gtk_widget_size_allocate (child->widget, &child_allocation);
+          gtk_widget_size_allocate (child, &child_allocation);
+          i++;
         }
     }
+
+  g_list_free (list);
+  g_free (widths);
+  g_free (heights);
 }
 
 /**
@@ -830,3 +980,54 @@ gtk_button_box_new (GtkOrientation orientation)
                        "orientation", orientation,
                        NULL);
 }
+
+/**
+ * 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);
+}