]> Pileus Git - ~andy/gtk/blob - gtk/gtksizegroup.c
4a5737f6060242a586fd6457227ea03cb950b852
[~andy/gtk] / gtk / gtksizegroup.c
1 /* GTK - The GIMP Toolkit
2  * gtksizegroup.c: 
3  * Copyright (C) 2001 Red Hat Software
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include "config.h"
22 #include <string.h>
23
24 #include "gtkbuildable.h"
25 #include "gtkcontainer.h"
26 #include "gtkintl.h"
27 #include "gtktypebuiltins.h"
28 #include "gtkprivate.h"
29 #include "gtksizegroup-private.h"
30 #include "gtkwidgetprivate.h"
31 #include "gtkcontainerprivate.h"
32
33
34 /**
35  * SECTION:gtksizegroup
36  * @Short_description: Grouping widgets so they request the same size
37  * @Title: GtkSizeGroup
38  *
39  * #GtkSizeGroup provides a mechanism for grouping a number of widgets
40  * together so they all request the same amount of space.  This is
41  * typically useful when you want a column of widgets to have the same
42  * size, but you can't use a #GtkGrid widget.
43  *
44  * In detail, the size requested for each widget in a #GtkSizeGroup is
45  * the maximum of the sizes that would have been requested for each
46  * widget in the size group if they were not in the size group. The mode
47  * of the size group (see gtk_size_group_set_mode()) determines whether
48  * this applies to the horizontal size, the vertical size, or both sizes.
49  *
50  * Note that size groups only affect the amount of space requested, not
51  * the size that the widgets finally receive. If you want the widgets in
52  * a #GtkSizeGroup to actually be the same size, you need to pack them in
53  * such a way that they get the size they request and not more. For
54  * example, if you are packing your widgets into a table, you would not
55  * include the %GTK_FILL flag.
56  *
57  * #GtkSizeGroup objects are referenced by each widget in the size group,
58  * so once you have added all widgets to a #GtkSizeGroup, you can drop
59  * the initial reference to the size group with g_object_unref(). If the
60  * widgets in the size group are subsequently destroyed, then they will
61  * be removed from the size group and drop their references on the size
62  * group; when all widgets have been removed, the size group will be
63  * freed.
64  *
65  * Widgets can be part of multiple size groups; GTK+ will compute the
66  * horizontal size of a widget from the horizontal requisition of all
67  * widgets that can be reached from the widget by a chain of size groups
68  * of type %GTK_SIZE_GROUP_HORIZONTAL or %GTK_SIZE_GROUP_BOTH, and the
69  * vertical size from the vertical requisition of all widgets that can be
70  * reached from the widget by a chain of size groups of type
71  * %GTK_SIZE_GROUP_VERTICAL or %GTK_SIZE_GROUP_BOTH.
72  *
73  * Note that only non-contextual sizes of every widget are ever consulted
74  * by size groups (since size groups have no knowledge of what size a widget
75  * will be allocated in one dimension, it cannot derive how much height
76  * a widget will receive for a given width). When grouping widgets that
77  * trade height for width in mode %GTK_SIZE_GROUP_VERTICAL or %GTK_SIZE_GROUP_BOTH:
78  * the height for the minimum width will be the requested height for all
79  * widgets in the group. The same is of course true when horizontally grouping
80  * width for height widgets.
81  *
82  * Widgets that trade height-for-width should set a reasonably large minimum width
83  * by way of #GtkLabel:width-chars for instance. Widgets with static sizes as well
84  * as widgets that grow (such as ellipsizing text) need no such considerations.
85  *
86  * <refsect2 id="GtkSizeGroup-BUILDER-UI">
87  * <title>GtkSizeGroup as GtkBuildable</title>
88  * <para>
89  * Size groups can be specified in a UI definition by placing an
90  * &lt;object&gt; element with <literal>class="GtkSizeGroup"</literal>
91  * somewhere in the UI definition. The widgets that belong to the
92  * size group are specified by a &lt;widgets&gt; element that may
93  * contain multiple &lt;widget&gt; elements, one for each member
94  * of the size group. The name attribute gives the id of the widget.
95  *
96  * <example>
97  * <title>A UI definition fragment with GtkSizeGroup</title>
98  * <programlisting><![CDATA[
99  * <object class="GtkSizeGroup">
100  *   <property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property>
101  *   <widgets>
102  *     <widget name="radio1"/>
103  *     <widget name="radio2"/>
104  *   </widgets>
105  * </object>
106  * ]]></programlisting>
107  * </example>
108  * </para>
109  * </refsect2>
110  */
111
112
113 struct _GtkSizeGroupPrivate
114 {
115   GtkRequisition  minimum_req;
116   GtkRequisition  natural_req;
117
118   GSList         *widgets;
119
120   guint8          mode;
121
122   guint           have_width    : 1;
123   guint           have_height   : 1;
124   guint           ignore_hidden : 1;
125   guint           visited       : 1;
126 };
127
128 enum {
129   PROP_0,
130   PROP_MODE,
131   PROP_IGNORE_HIDDEN
132 };
133
134 static void gtk_size_group_set_property (GObject      *object,
135                                          guint         prop_id,
136                                          const GValue *value,
137                                          GParamSpec   *pspec);
138 static void gtk_size_group_get_property (GObject      *object,
139                                          guint         prop_id,
140                                          GValue       *value,
141                                          GParamSpec   *pspec);
142
143 static void add_group_to_closure  (GtkSizeGroup      *group,
144                                    GtkSizeGroupMode   mode,
145                                    GSList           **groups,
146                                    GSList           **widgets);
147 static void add_widget_to_closure (GtkWidget         *widget,
148                                    GtkSizeGroupMode   mode,
149                                    GSList           **groups,
150                                    GSList           **widgets);
151
152 /* GtkBuildable */
153 static void gtk_size_group_buildable_init (GtkBuildableIface *iface);
154 static gboolean gtk_size_group_buildable_custom_tag_start (GtkBuildable  *buildable,
155                                                            GtkBuilder    *builder,
156                                                            GObject       *child,
157                                                            const gchar   *tagname,
158                                                            GMarkupParser *parser,
159                                                            gpointer      *data);
160 static void gtk_size_group_buildable_custom_finished (GtkBuildable  *buildable,
161                                                       GtkBuilder    *builder,
162                                                       GObject       *child,
163                                                       const gchar   *tagname,
164                                                       gpointer       user_data);
165
166 static void
167 mark_group_unvisited (GtkSizeGroup *group)
168 {
169   group->priv->visited = FALSE;
170 }
171
172 static void
173 mark_widget_unvisited (GtkWidget *widget)
174 {
175   _gtk_widget_set_sizegroup_visited (widget, FALSE);
176 }
177
178 static void
179 add_group_to_closure (GtkSizeGroup    *group,
180                       GtkSizeGroupMode mode,
181                       GSList         **groups,
182                       GSList         **widgets)
183 {
184   GtkSizeGroupPrivate *priv = group->priv;
185   GSList *tmp_widgets;
186   
187   *groups = g_slist_prepend (*groups, group);
188   priv->visited = TRUE;
189
190   tmp_widgets = priv->widgets;
191   while (tmp_widgets)
192     {
193       GtkWidget *tmp_widget = tmp_widgets->data;
194       
195       if (!_gtk_widget_get_sizegroup_visited (tmp_widget))
196         add_widget_to_closure (tmp_widget, mode, groups, widgets);
197       
198       tmp_widgets = tmp_widgets->next;
199     }
200 }
201
202 static void
203 add_widget_to_closure (GtkWidget       *widget,
204                        GtkSizeGroupMode mode,
205                        GSList         **groups,
206                        GSList         **widgets)
207 {
208   GSList *tmp_groups;
209
210   *widgets = g_slist_prepend (*widgets, widget);
211   _gtk_widget_set_sizegroup_visited (widget, TRUE);
212
213   tmp_groups = _gtk_widget_get_sizegroups (widget);
214   while (tmp_groups)
215     {
216       GtkSizeGroup        *tmp_group = tmp_groups->data;
217       GtkSizeGroupPrivate *tmp_priv  = tmp_group->priv;
218
219       if ((tmp_priv->mode == GTK_SIZE_GROUP_BOTH || tmp_priv->mode == mode) &&
220           !tmp_group->priv->visited)
221         add_group_to_closure (tmp_group, mode, groups, widgets);
222
223       tmp_groups = tmp_groups->next;
224     }
225 }
226
227 static void
228 real_queue_resize (GtkWidget          *widget,
229                    GtkQueueResizeFlags flags)
230 {
231   GtkWidget *container;
232
233   _gtk_widget_set_alloc_needed (widget, TRUE);
234   _gtk_widget_set_width_request_needed (widget, TRUE);
235   _gtk_widget_set_height_request_needed (widget, TRUE);
236
237   container = gtk_widget_get_parent (widget);
238   if (!container &&
239       gtk_widget_is_toplevel (widget) && GTK_IS_CONTAINER (widget))
240     container = widget;
241
242   if (container)
243     {
244       if (flags & GTK_QUEUE_RESIZE_INVALIDATE_ONLY)
245         _gtk_container_resize_invalidate (GTK_CONTAINER (container));
246       else
247         _gtk_container_queue_resize (GTK_CONTAINER (container));
248     }
249 }
250
251 static void
252 reset_group_sizes (GSList *groups)
253 {
254   GSList *tmp_list = groups;
255   while (tmp_list)
256     {
257       GtkSizeGroup *tmp_group = tmp_list->data;
258       GtkSizeGroupPrivate *tmp_priv = tmp_group->priv;
259
260       tmp_priv->have_width = FALSE;
261       tmp_priv->have_height = FALSE;
262
263       tmp_list = tmp_list->next;
264     }
265 }
266
267 static void
268 queue_resize_on_widget (GtkWidget          *widget,
269                         gboolean            check_siblings,
270                         GtkQueueResizeFlags flags)
271 {
272   GtkWidget *parent = widget;
273   GSList *tmp_list;
274
275   while (parent)
276     {
277       GSList *widget_groups;
278       GSList *groups;
279       GSList *widgets;
280       
281       if (widget == parent && !check_siblings)
282         {
283           real_queue_resize (widget, flags);
284           parent = gtk_widget_get_parent (parent);
285           continue;
286         }
287       
288       widget_groups = _gtk_widget_get_sizegroups (parent);
289       if (!widget_groups)
290         {
291           if (widget == parent)
292             real_queue_resize (widget, flags);
293
294           parent = gtk_widget_get_parent (parent);
295           continue;
296         }
297
298       groups = NULL;
299       widgets = NULL;
300           
301       add_widget_to_closure (parent, GTK_SIZE_GROUP_HORIZONTAL, &groups, &widgets);
302       g_slist_foreach (widgets, (GFunc)mark_widget_unvisited, NULL);
303       g_slist_foreach (groups, (GFunc)mark_group_unvisited, NULL);
304
305       reset_group_sizes (groups);
306               
307       tmp_list = widgets;
308       while (tmp_list)
309         {
310           if (tmp_list->data == parent)
311             {
312               if (widget == parent)
313                 real_queue_resize (parent, flags);
314             }
315           else if (tmp_list->data == widget)
316             {
317               g_warning ("A container and its child are part of this SizeGroup");
318             }
319           else
320             queue_resize_on_widget (tmp_list->data, FALSE, flags);
321
322           tmp_list = tmp_list->next;
323         }
324       
325       g_slist_free (widgets);
326       g_slist_free (groups);
327               
328       groups = NULL;
329       widgets = NULL;
330               
331       add_widget_to_closure (parent, GTK_SIZE_GROUP_VERTICAL, &groups, &widgets);
332       g_slist_foreach (widgets, (GFunc)mark_widget_unvisited, NULL);
333       g_slist_foreach (groups, (GFunc)mark_group_unvisited, NULL);
334
335       reset_group_sizes (groups);
336               
337       tmp_list = widgets;
338       while (tmp_list)
339         {
340           if (tmp_list->data == parent)
341             {
342               if (widget == parent)
343                 real_queue_resize (parent, flags);
344             }
345           else if (tmp_list->data == widget)
346             {
347               g_warning ("A container and its child are part of this SizeGroup");
348             }
349           else
350             queue_resize_on_widget (tmp_list->data, FALSE, flags);
351
352           tmp_list = tmp_list->next;
353         }
354       
355       g_slist_free (widgets);
356       g_slist_free (groups);
357
358       parent = gtk_widget_get_parent (parent);
359     }
360 }
361
362 static void
363 queue_resize_on_group (GtkSizeGroup       *size_group)
364 {
365   GtkSizeGroupPrivate *priv = size_group->priv;
366
367   if (priv->widgets)
368     queue_resize_on_widget (priv->widgets->data, TRUE, 0);
369 }
370
371 static void
372 gtk_size_group_class_init (GtkSizeGroupClass *klass)
373 {
374   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
375
376   gobject_class->set_property = gtk_size_group_set_property;
377   gobject_class->get_property = gtk_size_group_get_property;
378   
379   g_object_class_install_property (gobject_class,
380                                    PROP_MODE,
381                                    g_param_spec_enum ("mode",
382                                                       P_("Mode"),
383                                                       P_("The directions in which the size group affects the requested sizes"
384                                                         " of its component widgets"),
385                                                       GTK_TYPE_SIZE_GROUP_MODE,
386                                                       GTK_SIZE_GROUP_HORIZONTAL,                                                      GTK_PARAM_READWRITE));
387
388   /**
389    * GtkSizeGroup:ignore-hidden:
390    *
391    * If %TRUE, unmapped widgets are ignored when determining 
392    * the size of the group.
393    *
394    * Since: 2.8
395    */
396   g_object_class_install_property (gobject_class,
397                                    PROP_IGNORE_HIDDEN,
398                                    g_param_spec_boolean ("ignore-hidden",
399                                                          P_("Ignore hidden"),
400                                                          P_("If TRUE, unmapped widgets are ignored "
401                                                             "when determining the size of the group"),
402                                                          FALSE,
403                                                          GTK_PARAM_READWRITE));
404
405   g_type_class_add_private (klass, sizeof (GtkSizeGroupPrivate));
406 }
407
408 static void
409 gtk_size_group_init (GtkSizeGroup *size_group)
410 {
411   GtkSizeGroupPrivate *priv;
412
413   size_group->priv = G_TYPE_INSTANCE_GET_PRIVATE (size_group,
414                                                   GTK_TYPE_SIZE_GROUP,
415                                                   GtkSizeGroupPrivate);
416   priv = size_group->priv;
417
418   priv->widgets = NULL;
419   priv->mode = GTK_SIZE_GROUP_HORIZONTAL;
420   priv->have_width = 0;
421   priv->have_height = 0;
422   priv->ignore_hidden = 0;
423   priv->visited  = FALSE;
424 }
425
426 static void
427 gtk_size_group_buildable_init (GtkBuildableIface *iface)
428 {
429   iface->custom_tag_start = gtk_size_group_buildable_custom_tag_start;
430   iface->custom_finished = gtk_size_group_buildable_custom_finished;
431 }
432
433 G_DEFINE_TYPE_WITH_CODE (GtkSizeGroup, gtk_size_group, G_TYPE_OBJECT,
434                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
435                                                 gtk_size_group_buildable_init))
436
437 static void
438 gtk_size_group_set_property (GObject      *object,
439                              guint         prop_id,
440                              const GValue *value,
441                              GParamSpec   *pspec)
442 {
443   GtkSizeGroup *size_group = GTK_SIZE_GROUP (object);
444
445   switch (prop_id)
446     {
447     case PROP_MODE:
448       gtk_size_group_set_mode (size_group, g_value_get_enum (value));
449       break;
450     case PROP_IGNORE_HIDDEN:
451       gtk_size_group_set_ignore_hidden (size_group, g_value_get_boolean (value));
452       break;
453     default:
454       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
455       break;
456     }
457 }
458
459 static void
460 gtk_size_group_get_property (GObject      *object,
461                              guint         prop_id,
462                              GValue       *value,
463                              GParamSpec   *pspec)
464 {
465   GtkSizeGroup *size_group = GTK_SIZE_GROUP (object);
466   GtkSizeGroupPrivate *priv = size_group->priv;
467
468   switch (prop_id)
469     {
470     case PROP_MODE:
471       g_value_set_enum (value, priv->mode);
472       break;
473     case PROP_IGNORE_HIDDEN:
474       g_value_set_boolean (value, priv->ignore_hidden);
475       break;
476     default:
477       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
478       break;
479     }
480 }
481
482 /**
483  * gtk_size_group_new:
484  * @mode: the mode for the new size group.
485  * 
486  * Create a new #GtkSizeGroup.
487  
488  * Return value: a newly created #GtkSizeGroup
489  **/
490 GtkSizeGroup *
491 gtk_size_group_new (GtkSizeGroupMode mode)
492 {
493   GtkSizeGroup *size_group = g_object_new (GTK_TYPE_SIZE_GROUP, NULL);
494   GtkSizeGroupPrivate *priv = size_group->priv;
495
496   priv->mode = mode;
497
498   return size_group;
499 }
500
501 /**
502  * gtk_size_group_set_mode:
503  * @size_group: a #GtkSizeGroup
504  * @mode: the mode to set for the size group.
505  * 
506  * Sets the #GtkSizeGroupMode of the size group. The mode of the size
507  * group determines whether the widgets in the size group should
508  * all have the same horizontal requisition (%GTK_SIZE_GROUP_HORIZONTAL)
509  * all have the same vertical requisition (%GTK_SIZE_GROUP_VERTICAL),
510  * or should all have the same requisition in both directions
511  * (%GTK_SIZE_GROUP_BOTH).
512  **/
513 void
514 gtk_size_group_set_mode (GtkSizeGroup     *size_group,
515                          GtkSizeGroupMode  mode)
516 {
517   GtkSizeGroupPrivate *priv;
518
519   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
520
521   priv = size_group->priv;
522
523   if (priv->mode != mode)
524     {
525       if (priv->mode != GTK_SIZE_GROUP_NONE)
526         queue_resize_on_group (size_group);
527       priv->mode = mode;
528       if (priv->mode != GTK_SIZE_GROUP_NONE)
529         queue_resize_on_group (size_group);
530
531       g_object_notify (G_OBJECT (size_group), "mode");
532     }
533 }
534
535 /**
536  * gtk_size_group_get_mode:
537  * @size_group: a #GtkSizeGroup
538  * 
539  * Gets the current mode of the size group. See gtk_size_group_set_mode().
540  * 
541  * Return value: the current mode of the size group.
542  **/
543 GtkSizeGroupMode
544 gtk_size_group_get_mode (GtkSizeGroup *size_group)
545 {
546   g_return_val_if_fail (GTK_IS_SIZE_GROUP (size_group), GTK_SIZE_GROUP_BOTH);
547
548   return size_group->priv->mode;
549 }
550
551 /**
552  * gtk_size_group_set_ignore_hidden:
553  * @size_group: a #GtkSizeGroup
554  * @ignore_hidden: whether unmapped widgets should be ignored
555  *   when calculating the size
556  * 
557  * Sets whether unmapped widgets should be ignored when
558  * calculating the size.
559  *
560  * Since: 2.8 
561  */
562 void
563 gtk_size_group_set_ignore_hidden (GtkSizeGroup *size_group,
564                                   gboolean      ignore_hidden)
565 {
566   GtkSizeGroupPrivate *priv;
567
568   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
569
570   priv = size_group->priv;
571
572   ignore_hidden = ignore_hidden != FALSE;
573
574   if (priv->ignore_hidden != ignore_hidden)
575     {
576       priv->ignore_hidden = ignore_hidden;
577
578       g_object_notify (G_OBJECT (size_group), "ignore-hidden");
579     }
580 }
581
582 /**
583  * gtk_size_group_get_ignore_hidden:
584  * @size_group: a #GtkSizeGroup
585  *
586  * Returns if invisible widgets are ignored when calculating the size.
587  *
588  * Returns: %TRUE if invisible widgets are ignored.
589  *
590  * Since: 2.8
591  */
592 gboolean
593 gtk_size_group_get_ignore_hidden (GtkSizeGroup *size_group)
594 {
595   g_return_val_if_fail (GTK_IS_SIZE_GROUP (size_group), FALSE);
596
597   return size_group->priv->ignore_hidden;
598 }
599
600 static void
601 gtk_size_group_widget_destroyed (GtkWidget    *widget,
602                                  GtkSizeGroup *size_group)
603 {
604   gtk_size_group_remove_widget (size_group, widget);
605 }
606
607 /**
608  * gtk_size_group_add_widget:
609  * @size_group: a #GtkSizeGroup
610  * @widget: the #GtkWidget to add
611  * 
612  * Adds a widget to a #GtkSizeGroup. In the future, the requisition
613  * of the widget will be determined as the maximum of its requisition
614  * and the requisition of the other widgets in the size group.
615  * Whether this applies horizontally, vertically, or in both directions
616  * depends on the mode of the size group. See gtk_size_group_set_mode().
617  *
618  * When the widget is destroyed or no longer referenced elsewhere, it will 
619  * be removed from the size group.
620  */
621 void
622 gtk_size_group_add_widget (GtkSizeGroup     *size_group,
623                            GtkWidget        *widget)
624 {
625   GtkSizeGroupPrivate *priv;
626   GSList *groups;
627   
628   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
629   g_return_if_fail (GTK_IS_WIDGET (widget));
630
631   priv = size_group->priv;
632
633   groups = _gtk_widget_get_sizegroups (widget);
634
635   if (!g_slist_find (groups, size_group))
636     {
637       _gtk_widget_add_sizegroup (widget, size_group);
638
639       priv->widgets = g_slist_prepend (priv->widgets, widget);
640
641       g_signal_connect (widget, "destroy",
642                         G_CALLBACK (gtk_size_group_widget_destroyed),
643                         size_group);
644
645       g_object_ref (size_group);
646     }
647   
648   queue_resize_on_group (size_group);
649 }
650
651 /**
652  * gtk_size_group_remove_widget:
653  * @size_group: a #GtkSizeGroup
654  * @widget: the #GtkWidget to remove
655  * 
656  * Removes a widget from a #GtkSizeGroup.
657  **/
658 void
659 gtk_size_group_remove_widget (GtkSizeGroup *size_group,
660                               GtkWidget    *widget)
661 {
662   GtkSizeGroupPrivate *priv;
663   
664   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
665   g_return_if_fail (GTK_IS_WIDGET (widget));
666
667   priv = size_group->priv;
668
669   g_return_if_fail (g_slist_find (priv->widgets, widget));
670
671   g_signal_handlers_disconnect_by_func (widget,
672                                         gtk_size_group_widget_destroyed,
673                                         size_group);
674   
675   _gtk_widget_remove_sizegroup (widget, size_group);
676
677   priv->widgets = g_slist_remove (priv->widgets, widget);
678   queue_resize_on_group (size_group);
679   gtk_widget_queue_resize (widget);
680
681   g_object_unref (size_group);
682 }
683
684 /**
685  * gtk_size_group_get_widgets:
686  * @size_group: a #GtkSizeGroup
687  * 
688  * Returns the list of widgets associated with @size_group.
689  *
690  * Return value:  (element-type GtkWidget) (transfer none): a #GSList of
691  *   widgets. The list is owned by GTK+ and should not be modified.
692  *
693  * Since: 2.10
694  **/
695 GSList *
696 gtk_size_group_get_widgets (GtkSizeGroup *size_group)
697 {
698   return size_group->priv->widgets;
699 }
700
701 static void
702 compute_dimension (GtkWidget        *widget,
703                    GtkSizeGroupMode  mode,
704                    gint             *minimum, /* in-out */
705                    gint             *natural) /* in-out */
706 {
707   GSList *widgets = NULL;
708   GSList *groups = NULL;
709   GSList *tmp_list;
710   gint    min_result = 0, nat_result = 0;
711
712   add_widget_to_closure (widget, mode, &groups, &widgets);
713   g_slist_foreach (widgets, (GFunc)mark_widget_unvisited, NULL);
714   g_slist_foreach (groups, (GFunc)mark_group_unvisited, NULL);
715
716   g_slist_foreach (widgets, (GFunc)g_object_ref, NULL);
717   
718   if (!groups)
719     {
720       min_result = *minimum;
721       nat_result = *natural;
722     }
723   else
724     {
725       GtkSizeGroup *group = groups->data;
726       GtkSizeGroupPrivate *priv = group->priv;
727
728       if (mode == GTK_SIZE_GROUP_HORIZONTAL && priv->have_width)
729         {
730           min_result = priv->minimum_req.width;
731           nat_result = priv->natural_req.width;
732         }
733       else if (mode == GTK_SIZE_GROUP_VERTICAL && priv->have_height)
734         {
735           min_result = priv->minimum_req.height;
736           nat_result = priv->natural_req.height;
737         }
738       else
739         {
740           tmp_list = widgets;
741           while (tmp_list)
742             {
743               GtkWidget *tmp_widget = tmp_list->data;
744               gint min_dimension, nat_dimension;
745
746               if (tmp_widget == widget)
747                 {
748                   min_dimension = *minimum;
749                   nat_dimension = *natural;
750                 }
751               else
752                 {
753                   if (mode == GTK_SIZE_GROUP_HORIZONTAL)
754                     gtk_widget_get_preferred_width (tmp_widget, &min_dimension, &nat_dimension);
755                   else
756                     gtk_widget_get_preferred_height (tmp_widget, &min_dimension, &nat_dimension);
757                 }
758
759               if (gtk_widget_get_mapped (tmp_widget) || !priv->ignore_hidden)
760                 {
761                   min_result = MAX (min_result, min_dimension);
762                   nat_result = MAX (nat_result, nat_dimension);
763                 }
764
765               tmp_list = tmp_list->next;
766             }
767
768           tmp_list = groups;
769           while (tmp_list)
770             {
771               GtkSizeGroup *tmp_group = tmp_list->data;
772               GtkSizeGroupPrivate *tmp_priv = tmp_group->priv;
773
774               if (mode == GTK_SIZE_GROUP_HORIZONTAL)
775                 {
776                   tmp_priv->have_width = TRUE;
777                   tmp_priv->minimum_req.width = min_result;
778                   tmp_priv->natural_req.width = nat_result;
779                 }
780               else
781                 {
782                   tmp_priv->have_height = TRUE;
783                   tmp_priv->minimum_req.height = min_result;
784                   tmp_priv->natural_req.height = nat_result;
785                 }
786               
787               tmp_list = tmp_list->next;
788             }
789         }
790     }
791
792   g_slist_foreach (widgets, (GFunc)g_object_unref, NULL);
793
794   g_slist_free (widgets);
795   g_slist_free (groups);
796
797   *minimum = min_result;
798   *natural = nat_result;
799 }
800
801 /**
802  * _gtk_size_group_bump_requisition:
803  * @widget: a #GtkWidget
804  * @mode: either %GTK_SIZE_GROUP_HORIZONTAL or %GTK_SIZE_GROUP_VERTICAL, depending
805  *        on the dimension in which to bump the size.
806  * @minimum: a pointer to the widget's minimum size
807  * @natural: a pointer to the widget's natural size
808  *
809  * Refreshes the sizegroup while returning the groups requested
810  * value in the dimension @mode.
811  *
812  * This function is used both to update sizegroup minimum and natural size 
813  * information and widget minimum and natural sizes in multiple passes from 
814  * the size request apis.
815  */
816 void
817 _gtk_size_group_bump_requisition (GtkWidget        *widget,
818                                   GtkSizeGroupMode  mode,
819                                   gint             *minimum,
820                                   gint             *natural)
821 {
822   if (!_gtk_widget_get_sizegroup_bumping (widget))
823     {
824       /* Avoid recursion here */
825       _gtk_widget_set_sizegroup_bumping (widget, TRUE);
826
827       if (_gtk_widget_get_sizegroups (widget))
828         compute_dimension (widget, mode, minimum, natural);
829
830       _gtk_widget_set_sizegroup_bumping (widget, FALSE);
831     }
832 }
833
834
835
836 /**
837  * _gtk_size_group_queue_resize:
838  * @widget: a #GtkWidget
839  * 
840  * Queue a resize on a widget, and on all other widgets grouped with this widget.
841  **/
842 void
843 _gtk_size_group_queue_resize (GtkWidget           *widget,
844                               GtkQueueResizeFlags  flags)
845 {
846   queue_resize_on_widget (widget, TRUE, flags);
847 }
848
849 typedef struct {
850   GObject *object;
851   GSList *items;
852 } GSListSubParserData;
853
854 static void
855 size_group_start_element (GMarkupParseContext *context,
856                           const gchar         *element_name,
857                           const gchar        **names,
858                           const gchar        **values,
859                           gpointer            user_data,
860                           GError            **error)
861 {
862   guint i;
863   GSListSubParserData *data = (GSListSubParserData*)user_data;
864
865   if (strcmp (element_name, "widget") == 0)
866     for (i = 0; names[i]; i++)
867       if (strcmp (names[i], "name") == 0)
868         data->items = g_slist_prepend (data->items, g_strdup (values[i]));
869   else if (strcmp (element_name, "widgets") == 0)
870     return;
871   else
872     g_warning ("Unsupported type tag for GtkSizeGroup: %s\n",
873                element_name);
874
875 }
876
877 static const GMarkupParser size_group_parser =
878   {
879     size_group_start_element
880   };
881
882 static gboolean
883 gtk_size_group_buildable_custom_tag_start (GtkBuildable  *buildable,
884                                            GtkBuilder    *builder,
885                                            GObject       *child,
886                                            const gchar   *tagname,
887                                            GMarkupParser *parser,
888                                            gpointer      *data)
889 {
890   GSListSubParserData *parser_data;
891
892   if (child)
893     return FALSE;
894
895   if (strcmp (tagname, "widgets") == 0)
896     {
897       parser_data = g_slice_new0 (GSListSubParserData);
898       parser_data->items = NULL;
899       parser_data->object = G_OBJECT (buildable);
900
901       *parser = size_group_parser;
902       *data = parser_data;
903       return TRUE;
904     }
905
906   return FALSE;
907 }
908
909 static void
910 gtk_size_group_buildable_custom_finished (GtkBuildable  *buildable,
911                                           GtkBuilder    *builder,
912                                           GObject       *child,
913                                           const gchar   *tagname,
914                                           gpointer       user_data)
915 {
916   GSList *l;
917   GSListSubParserData *data;
918   GObject *object;
919
920   if (strcmp (tagname, "widgets"))
921     return;
922   
923   data = (GSListSubParserData*)user_data;
924   data->items = g_slist_reverse (data->items);
925
926   for (l = data->items; l; l = l->next)
927     {
928       object = gtk_builder_get_object (builder, l->data);
929       if (!object)
930         {
931           g_warning ("Unknown object %s specified in sizegroup %s",
932                      (const gchar*)l->data,
933                      gtk_buildable_get_name (GTK_BUILDABLE (data->object)));
934           continue;
935         }
936       gtk_size_group_add_widget (GTK_SIZE_GROUP (data->object),
937                                  GTK_WIDGET (object));
938       g_free (l->data);
939     }
940   g_slist_free (data->items);
941   g_slice_free (GSListSubParserData, data);
942 }