]> Pileus Git - ~andy/gtk/blob - gtk/gtksizegroup.c
48b9d7989ad48197abdc4a0278dfd82a6ae387c3
[~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 #include "gtkcontainer.h"
24 #include "gtkintl.h"
25 #include "gtkprivate.h"
26 #include "gtksizegroup.h"
27 #include "gtkbuildable.h"
28 #include "gtkextendedlayout.h"
29 #include "gtkalias.h"
30
31 #define GTK_SIZE_GROUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_SIZE_GROUP, GtkSizeGroupPrivate))
32
33 typedef struct _GtkSizeGroupPrivate GtkSizeGroupPrivate;
34
35 struct _GtkSizeGroupPrivate
36 {
37   GtkRequisition natural_size;
38 };
39
40 enum {
41   PROP_0,
42   PROP_MODE,
43   PROP_IGNORE_HIDDEN
44 };
45
46 static void gtk_size_group_set_property (GObject      *object,
47                                          guint         prop_id,
48                                          const GValue *value,
49                                          GParamSpec   *pspec);
50 static void gtk_size_group_get_property (GObject      *object,
51                                          guint         prop_id,
52                                          GValue       *value,
53                                          GParamSpec   *pspec);
54
55 static void add_group_to_closure  (GtkSizeGroup      *group,
56                                    GtkSizeGroupMode   mode,
57                                    GSList           **groups,
58                                    GSList           **widgets);
59 static void add_widget_to_closure (GtkWidget         *widget,
60                                    GtkSizeGroupMode   mode,
61                                    GSList           **groups,
62                                    GSList           **widgets);
63
64 /* GtkBuildable */
65 static void gtk_size_group_buildable_init (GtkBuildableIface *iface);
66 static gboolean gtk_size_group_buildable_custom_tag_start (GtkBuildable  *buildable,
67                                                            GtkBuilder    *builder,
68                                                            GObject       *child,
69                                                            const gchar   *tagname,
70                                                            GMarkupParser *parser,
71                                                            gpointer      *data);
72 static void gtk_size_group_buildable_custom_finished (GtkBuildable  *buildable,
73                                                       GtkBuilder    *builder,
74                                                       GObject       *child,
75                                                       const gchar   *tagname,
76                                                       gpointer       user_data);
77
78 static GQuark size_groups_quark;
79 static const gchar size_groups_tag[] = "gtk-size-groups";
80
81 static GQuark visited_quark;
82 static const gchar visited_tag[] = "gtk-size-group-visited";
83
84 static GSList *
85 get_size_groups (GtkWidget *widget)
86 {
87   return g_object_get_qdata (G_OBJECT (widget), size_groups_quark);
88 }
89
90 static void
91 set_size_groups (GtkWidget *widget,
92                  GSList    *groups)
93 {
94   g_object_set_qdata (G_OBJECT (widget), size_groups_quark, groups);
95 }
96
97 static void
98 mark_visited (gpointer object)
99 {
100   g_object_set_qdata (object, visited_quark, "visited");
101 }
102
103 static void
104 mark_unvisited (gpointer object)
105 {
106   g_object_set_qdata (object, visited_quark, NULL);
107 }
108
109 static gboolean
110 is_visited (gpointer object)
111 {
112   return g_object_get_qdata (object, visited_quark) != NULL;
113 }
114
115 static void
116 add_group_to_closure (GtkSizeGroup    *group,
117                       GtkSizeGroupMode mode,
118                       GSList         **groups,
119                       GSList         **widgets)
120 {
121   GSList *tmp_widgets;
122   
123   *groups = g_slist_prepend (*groups, group);
124   mark_visited (group);
125
126   tmp_widgets = group->widgets;
127   while (tmp_widgets)
128     {
129       GtkWidget *tmp_widget = tmp_widgets->data;
130       
131       if (!is_visited (tmp_widget))
132         add_widget_to_closure (tmp_widget, mode, groups, widgets);
133       
134       tmp_widgets = tmp_widgets->next;
135     }
136 }
137
138 static void
139 add_widget_to_closure (GtkWidget       *widget,
140                        GtkSizeGroupMode mode,
141                        GSList         **groups,
142                        GSList         **widgets)
143 {
144   GSList *tmp_groups;
145
146   *widgets = g_slist_prepend (*widgets, widget);
147   mark_visited (widget);
148
149   tmp_groups = get_size_groups (widget);
150   while (tmp_groups)
151     {
152       GtkSizeGroup *tmp_group = tmp_groups->data;
153       
154       if ((tmp_group->mode == GTK_SIZE_GROUP_BOTH || tmp_group->mode == mode) &&
155           !is_visited (tmp_group))
156         add_group_to_closure (tmp_group, mode, groups, widgets);
157
158       tmp_groups = tmp_groups->next;
159     }
160 }
161
162 static void
163 real_queue_resize (GtkWidget *widget)
164 {
165   GTK_PRIVATE_SET_FLAG (widget, GTK_ALLOC_NEEDED);
166   GTK_PRIVATE_SET_FLAG (widget, GTK_REQUEST_NEEDED);
167   
168   if (widget->parent)
169     _gtk_container_queue_resize (GTK_CONTAINER (widget->parent));
170   else if (GTK_WIDGET_TOPLEVEL (widget) && GTK_IS_CONTAINER (widget))
171     _gtk_container_queue_resize (GTK_CONTAINER (widget));
172 }
173
174 static void
175 reset_group_sizes (GSList *groups)
176 {
177   GSList *tmp_list = groups;
178   while (tmp_list)
179     {
180       GtkSizeGroup *tmp_group = tmp_list->data;
181
182       tmp_group->have_width = FALSE;
183       tmp_group->have_height = FALSE;
184       
185       tmp_list = tmp_list->next;
186     }
187 }
188
189 static void
190 queue_resize_on_widget (GtkWidget *widget,
191                         gboolean   check_siblings)
192 {
193   GtkWidget *parent = widget;
194   GSList *tmp_list;
195
196   while (parent)
197     {
198       GSList *widget_groups;
199       GSList *groups;
200       GSList *widgets;
201       
202       if (widget == parent && !check_siblings)
203         {
204           real_queue_resize (widget);
205           parent = parent->parent;
206           continue;
207         }
208       
209       widget_groups = get_size_groups (parent);
210       if (!widget_groups)
211         {
212           if (widget == parent)
213             real_queue_resize (widget);
214
215           parent = parent->parent;
216           continue;
217         }
218
219       groups = NULL;
220       widgets = NULL;
221           
222       add_widget_to_closure (parent, GTK_SIZE_GROUP_HORIZONTAL, &groups, &widgets);
223       g_slist_foreach (widgets, (GFunc)mark_unvisited, NULL);
224       g_slist_foreach (groups, (GFunc)mark_unvisited, NULL);
225
226       reset_group_sizes (groups);
227               
228       tmp_list = widgets;
229       while (tmp_list)
230         {
231           if (tmp_list->data == parent)
232             {
233               if (widget == parent)
234                 real_queue_resize (parent);
235             }
236           else if (tmp_list->data == widget)
237             {
238               g_warning ("A container and its child are part of this SizeGroup");
239             }
240           else
241             queue_resize_on_widget (tmp_list->data, FALSE);
242
243           tmp_list = tmp_list->next;
244         }
245       
246       g_slist_free (widgets);
247       g_slist_free (groups);
248               
249       groups = NULL;
250       widgets = NULL;
251               
252       add_widget_to_closure (parent, GTK_SIZE_GROUP_VERTICAL, &groups, &widgets);
253       g_slist_foreach (widgets, (GFunc)mark_unvisited, NULL);
254       g_slist_foreach (groups, (GFunc)mark_unvisited, NULL);
255
256       reset_group_sizes (groups);
257               
258       tmp_list = widgets;
259       while (tmp_list)
260         {
261           if (tmp_list->data == parent)
262             {
263               if (widget == parent)
264                 real_queue_resize (parent);
265             }
266           else if (tmp_list->data == widget)
267             {
268               g_warning ("A container and its child are part of this SizeGroup");
269             }
270           else
271             queue_resize_on_widget (tmp_list->data, FALSE);
272
273           tmp_list = tmp_list->next;
274         }
275       
276       g_slist_free (widgets);
277       g_slist_free (groups);
278       
279       parent = parent->parent;
280     }
281 }
282
283 static void
284 queue_resize_on_group (GtkSizeGroup *size_group)
285 {
286   if (size_group->widgets)
287     queue_resize_on_widget (size_group->widgets->data, TRUE);
288 }
289
290 static void
291 initialize_size_group_quarks (void)
292 {
293   if (!size_groups_quark)
294     {
295       size_groups_quark = g_quark_from_static_string (size_groups_tag);
296       visited_quark = g_quark_from_static_string (visited_tag);
297     }
298 }
299
300 static void
301 gtk_size_group_class_init (GtkSizeGroupClass *klass)
302 {
303   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
304
305   gobject_class->set_property = gtk_size_group_set_property;
306   gobject_class->get_property = gtk_size_group_get_property;
307   
308   g_object_class_install_property (gobject_class,
309                                    PROP_MODE,
310                                    g_param_spec_enum ("mode",
311                                                       P_("Mode"),
312                                                       P_("The directions in which the size group affects the requested sizes"
313                                                         " of its component widgets"),
314                                                       GTK_TYPE_SIZE_GROUP_MODE,
315                                                       GTK_SIZE_GROUP_HORIZONTAL,                                                      GTK_PARAM_READWRITE));
316
317   /**
318    * GtkSizeGroup:ignore-hidden:
319    *
320    * If %TRUE, unmapped widgets are ignored when determining 
321    * the size of the group.
322    *
323    * Since: 2.8
324    */
325   g_object_class_install_property (gobject_class,
326                                    PROP_IGNORE_HIDDEN,
327                                    g_param_spec_boolean ("ignore-hidden",
328                                                          P_("Ignore hidden"),
329                                                          P_("If TRUE, unmapped widgets are ignored "
330                                                             "when determining the size of the group"),
331                                                          FALSE,
332                                                          GTK_PARAM_READWRITE));
333   
334   initialize_size_group_quarks ();
335   g_type_class_add_private (klass, sizeof (GtkSizeGroupPrivate));
336 }
337
338 static void
339 gtk_size_group_init (GtkSizeGroup *size_group)
340 {
341   size_group->widgets = NULL;
342   size_group->mode = GTK_SIZE_GROUP_HORIZONTAL;
343   size_group->have_width = 0;
344   size_group->have_height = 0;
345   size_group->ignore_hidden = 0;
346 }
347
348 static void
349 gtk_size_group_buildable_init (GtkBuildableIface *iface)
350 {
351   iface->custom_tag_start = gtk_size_group_buildable_custom_tag_start;
352   iface->custom_finished = gtk_size_group_buildable_custom_finished;
353 }
354
355 G_DEFINE_TYPE_WITH_CODE (GtkSizeGroup, gtk_size_group, G_TYPE_OBJECT,
356                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
357                                                 gtk_size_group_buildable_init))
358
359 static void
360 gtk_size_group_set_property (GObject      *object,
361                              guint         prop_id,
362                              const GValue *value,
363                              GParamSpec   *pspec)
364 {
365   GtkSizeGroup *size_group = GTK_SIZE_GROUP (object);
366
367   switch (prop_id)
368     {
369     case PROP_MODE:
370       gtk_size_group_set_mode (size_group, g_value_get_enum (value));
371       break;
372     case PROP_IGNORE_HIDDEN:
373       gtk_size_group_set_ignore_hidden (size_group, g_value_get_boolean (value));
374       break;
375     default:
376       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
377       break;
378     }
379 }
380
381 static void
382 gtk_size_group_get_property (GObject      *object,
383                              guint         prop_id,
384                              GValue       *value,
385                              GParamSpec   *pspec)
386 {
387   GtkSizeGroup *size_group = GTK_SIZE_GROUP (object);
388
389   switch (prop_id)
390     {
391     case PROP_MODE:
392       g_value_set_enum (value, size_group->mode);
393       break;
394     case PROP_IGNORE_HIDDEN:
395       g_value_set_boolean (value, size_group->ignore_hidden);
396       break;
397     default:
398       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
399       break;
400     }
401 }
402
403 /**
404  * gtk_size_group_new:
405  * @mode: the mode for the new size group.
406  * 
407  * Create a new #GtkSizeGroup.
408  
409  * Return value: a newly created #GtkSizeGroup
410  **/
411 GtkSizeGroup *
412 gtk_size_group_new (GtkSizeGroupMode mode)
413 {
414   GtkSizeGroup *size_group = g_object_new (GTK_TYPE_SIZE_GROUP, NULL);
415
416   size_group->mode = mode;
417
418   return size_group;
419 }
420
421 /**
422  * gtk_size_group_set_mode:
423  * @size_group: a #GtkSizeGroup
424  * @mode: the mode to set for the size group.
425  * 
426  * Sets the #GtkSizeGroupMode of the size group. The mode of the size
427  * group determines whether the widgets in the size group should
428  * all have the same horizontal requisition (%GTK_SIZE_GROUP_MODE_HORIZONTAL)
429  * all have the same vertical requisition (%GTK_SIZE_GROUP_MODE_VERTICAL),
430  * or should all have the same requisition in both directions
431  * (%GTK_SIZE_GROUP_MODE_BOTH).
432  **/
433 void
434 gtk_size_group_set_mode (GtkSizeGroup     *size_group,
435                          GtkSizeGroupMode  mode)
436 {
437   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
438
439   if (size_group->mode != mode)
440     {
441       if (size_group->mode != GTK_SIZE_GROUP_NONE)
442         queue_resize_on_group (size_group);
443       size_group->mode = mode;
444       if (size_group->mode != GTK_SIZE_GROUP_NONE)
445         queue_resize_on_group (size_group);
446
447       g_object_notify (G_OBJECT (size_group), "mode");
448     }
449 }
450
451 /**
452  * gtk_size_group_get_mode:
453  * @size_group: a #GtkSizeGroup
454  * 
455  * Gets the current mode of the size group. See gtk_size_group_set_mode().
456  * 
457  * Return value: the current mode of the size group.
458  **/
459 GtkSizeGroupMode
460 gtk_size_group_get_mode (GtkSizeGroup *size_group)
461 {
462   g_return_val_if_fail (GTK_IS_SIZE_GROUP (size_group), GTK_SIZE_GROUP_BOTH);
463
464   return size_group->mode;
465 }
466
467 /**
468  * gtk_size_group_set_ignore_hidden:
469  * @size_group: a #GtkSizeGroup
470  * @ignore_hidden: whether unmapped widgets should be ignored
471  *   when calculating the size
472  * 
473  * Sets whether unmapped widgets should be ignored when
474  * calculating the size.
475  *
476  * Since: 2.8 
477  */
478 void
479 gtk_size_group_set_ignore_hidden (GtkSizeGroup *size_group,
480                                   gboolean      ignore_hidden)
481 {
482   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
483   
484   ignore_hidden = ignore_hidden != FALSE;
485
486   if (size_group->ignore_hidden != ignore_hidden)
487     {
488       size_group->ignore_hidden = ignore_hidden;
489
490       g_object_notify (G_OBJECT (size_group), "ignore-hidden");
491     }
492 }
493
494 /**
495  * gtk_size_group_get_ignore_hidden:
496  * @size_group: a #GtkSizeGroup
497  *
498  * Returns if invisible widgets are ignored when calculating the size.
499  *
500  * Returns: %TRUE if invisible widgets are ignored.
501  *
502  * Since: 2.8
503  */
504 gboolean
505 gtk_size_group_get_ignore_hidden (GtkSizeGroup *size_group)
506 {
507   g_return_val_if_fail (GTK_IS_SIZE_GROUP (size_group), FALSE);
508
509   return size_group->ignore_hidden;
510 }
511
512 static void
513 gtk_size_group_widget_destroyed (GtkWidget    *widget,
514                                  GtkSizeGroup *size_group)
515 {
516   gtk_size_group_remove_widget (size_group, widget);
517 }
518
519 /**
520  * gtk_size_group_add_widget:
521  * @size_group: a #GtkSizeGroup
522  * @widget: the #GtkWidget to add
523  * 
524  * Adds a widget to a #GtkSizeGroup. In the future, the requisition
525  * of the widget will be determined as the maximum of its requisition
526  * and the requisition of the other widgets in the size group.
527  * Whether this applies horizontally, vertically, or in both directions
528  * depends on the mode of the size group. See gtk_size_group_set_mode().
529  *
530  * When the widget is destroyed or no longer referenced elsewhere, it will 
531  * be removed from the size group.
532  */
533 void
534 gtk_size_group_add_widget (GtkSizeGroup     *size_group,
535                            GtkWidget        *widget)
536 {
537   GSList *groups;
538   
539   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
540   g_return_if_fail (GTK_IS_WIDGET (widget));
541   
542   groups = get_size_groups (widget);
543
544   if (!g_slist_find (groups, size_group))
545     {
546       groups = g_slist_prepend (groups, size_group);
547       set_size_groups (widget, groups);
548
549       size_group->widgets = g_slist_prepend (size_group->widgets, widget);
550
551       g_signal_connect (widget, "destroy",
552                         G_CALLBACK (gtk_size_group_widget_destroyed),
553                         size_group);
554
555       g_object_ref (size_group);
556     }
557   
558   queue_resize_on_group (size_group);
559 }
560
561 /**
562  * gtk_size_group_remove_widget:
563  * @size_group: a #GtkSizeGrup
564  * @widget: the #GtkWidget to remove
565  * 
566  * Removes a widget from a #GtkSizeGroup.
567  **/
568 void
569 gtk_size_group_remove_widget (GtkSizeGroup *size_group,
570                               GtkWidget    *widget)
571 {
572   GSList *groups;
573   
574   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
575   g_return_if_fail (GTK_IS_WIDGET (widget));
576   g_return_if_fail (g_slist_find (size_group->widgets, widget));
577
578   g_signal_handlers_disconnect_by_func (widget,
579                                         gtk_size_group_widget_destroyed,
580                                         size_group);
581   
582   groups = get_size_groups (widget);
583   groups = g_slist_remove (groups, size_group);
584   set_size_groups (widget, groups);
585
586   size_group->widgets = g_slist_remove (size_group->widgets, widget);
587   queue_resize_on_group (size_group);
588   gtk_widget_queue_resize (widget);
589
590   g_object_unref (size_group);
591 }
592
593 /**
594  * gtk_size_group_get_widgets:
595  * @size_group: a #GtkSizeGrup
596  * 
597  * Returns the list of widgets associated with @size_group.
598  *
599  * Return value:  (element-type GtkWidget) (transfer none): a #GSList of
600  *   widgets. The list is owned by GTK+ and should not be modified.
601  *
602  * Since: 2.10
603  **/
604 GSList *
605 gtk_size_group_get_widgets (GtkSizeGroup *size_group)
606 {
607   return size_group->widgets;
608 }
609
610 static void
611 get_base_dimensions (GtkWidget        *widget,
612                      GtkSizeGroupMode  mode,
613                      gint             *minimum_size,
614                      gint             *natural_size)
615 {
616   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
617
618   if (mode == GTK_SIZE_GROUP_HORIZONTAL)
619     {
620       if (minimum_size)
621         {
622           if (aux_info && aux_info->width > 0)
623             *minimum_size = aux_info->width;
624           else
625             *minimum_size = widget->requisition.width;
626         }
627
628       if (natural_size)
629         {
630           if (aux_info)
631             *natural_size = aux_info->natural_size.width;
632           else
633             *natural_size = widget->requisition.width;
634         }
635     }
636   else
637     {
638       if (minimum_size)
639         {
640           if (aux_info && aux_info->height > 0)
641             *minimum_size = aux_info->height;
642           else
643             *minimum_size = widget->requisition.height;
644         }
645
646       if (natural_size)
647         {
648           if (aux_info)
649             *natural_size = aux_info->natural_size.height;
650           else
651             *natural_size = widget->requisition.height;
652         }
653     }
654 }
655
656 static void
657 do_size_request (GtkWidget *widget)
658 {
659   if (GTK_WIDGET_REQUEST_NEEDED (widget))
660     {
661       GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, TRUE);
662
663       gtk_widget_ensure_style (widget);      
664       GTK_PRIVATE_UNSET_FLAG (widget, GTK_REQUEST_NEEDED);
665
666       gtk_extended_layout_get_desired_size (GTK_EXTENDED_LAYOUT (widget),
667                                             &widget->requisition,
668                                             &aux_info->natural_size);
669
670       g_assert (widget->requisition.width <= aux_info->natural_size.width);
671       g_assert (widget->requisition.height <= aux_info->natural_size.height);
672     }
673 }
674
675 static void
676 compute_base_dimensions (GtkWidget        *widget,
677                          GtkSizeGroupMode  mode,
678                          gint             *minimum_size,
679                          gint             *natural_size)
680 {
681   do_size_request (widget);
682   get_base_dimensions (widget, mode, minimum_size, natural_size);
683 }
684
685 static gint
686 compute_dimension (GtkWidget        *widget,
687                    GtkSizeGroupMode  mode,
688                    gint             *minimum_size,
689                    gint             *natural_size)
690 {
691   GSList *widgets = NULL;
692   GSList *groups = NULL;
693   GSList *tmp_list;
694   gint result = 0;
695
696   add_widget_to_closure (widget, mode, &groups, &widgets);
697
698   g_slist_foreach (widgets, (GFunc)mark_unvisited, NULL);
699   g_slist_foreach (groups, (GFunc)mark_unvisited, NULL);
700   
701   g_slist_foreach (widgets, (GFunc)g_object_ref, NULL);
702   
703   if (!groups)
704     {
705       compute_base_dimensions (widget, mode, minimum_size, natural_size);
706     }
707   else
708     {
709       GtkSizeGroup *group = groups->data;
710       GtkSizeGroupPrivate *priv = GTK_SIZE_GROUP_GET_PRIVATE (group);
711
712       gint result_minimum_size = 0;
713       gint result_natural_size = 0;
714
715       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
716         {
717           result_minimum_size = group->requisition.width;
718           result_natural_size = priv->natural_size.width;
719         }
720       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
721         {
722           result_minimum_size = group->requisition.height;
723           result_natural_size = priv->natural_size.height;
724         }
725       else
726         {
727           tmp_list = widgets;
728           while (tmp_list)
729             {
730               GtkWidget *tmp_widget = tmp_list->data;
731
732               gint tmp_widget_minimum_size;
733               gint tmp_widget_natural_size;
734
735               compute_base_dimensions (tmp_widget, mode,
736                                        &tmp_widget_minimum_size,
737                                        &tmp_widget_natural_size);
738
739               if (GTK_WIDGET_MAPPED (tmp_widget) || !group->ignore_hidden)
740                 {
741                   if (result_minimum_size < tmp_widget_minimum_size)
742                     result_minimum_size = tmp_widget_minimum_size;
743                   if (result_natural_size < tmp_widget_natural_size)
744                     result_natural_size = tmp_widget_natural_size;
745                 }
746
747               tmp_list = tmp_list->next;
748             }
749
750           tmp_list = groups;
751           while (tmp_list)
752             {
753               GtkSizeGroup *tmp_group = tmp_list->data;
754               GtkSizeGroupPrivate *tmp_priv = GTK_SIZE_GROUP_GET_PRIVATE (tmp_group);
755
756               if (mode == GTK_SIZE_GROUP_HORIZONTAL)
757                 {
758                   tmp_group->have_width = TRUE;
759                   tmp_group->requisition.width = result_minimum_size;
760                   tmp_priv->natural_size.width = result_natural_size;
761                 }
762               else
763                 {
764                   tmp_group->have_height = TRUE;
765                   tmp_group->requisition.height = result_minimum_size;
766                   tmp_priv->natural_size.height = result_natural_size;
767                 }
768               
769               tmp_list = tmp_list->next;
770             }
771         }
772
773       if (minimum_size)
774         *minimum_size = result_minimum_size;
775       if (natural_size)
776         *natural_size = result_natural_size;
777     }
778
779   g_slist_foreach (widgets, (GFunc)g_object_unref, NULL);
780
781   g_slist_free (widgets);
782   g_slist_free (groups);
783 }
784
785 static void
786 get_dimensions (GtkWidget        *widget,
787                 GtkSizeGroupMode  mode,
788                 gint             *minimum_size,
789                 gint             *natural_size)
790 {
791   GSList *widgets = NULL;
792   GSList *groups = NULL;
793
794   add_widget_to_closure (widget, mode, &groups, &widgets);
795
796   g_slist_foreach (widgets, (GFunc)mark_unvisited, NULL);
797   g_slist_foreach (groups, (GFunc)mark_unvisited, NULL);  
798
799   if (!groups)
800     {
801       get_base_dimensions (widget, mode, minimum_size, natural_size);
802     }
803   else
804     {
805       GtkSizeGroup *group = groups->data;
806       GtkSizeGroupPrivate *priv = GTK_SIZE_GROUP_GET_PRIVATE (group);
807
808       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
809         {
810           if (minimum_size)
811             *minimum_size = group->requisition.width;
812           if (natural_size)
813             *natural_size = priv->natural_size.width;
814         }
815       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
816         {
817           if (minimum_size)
818             *minimum_size = group->requisition.height;
819           if (natural_size)
820             *natural_size = priv->natural_size.height;
821         }
822     }
823
824   g_slist_free (widgets);
825   g_slist_free (groups);
826 }
827
828 static void
829 get_fast_child_requisition (GtkWidget      *widget,
830                             GtkRequisition *requisition)
831 {
832   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
833   
834   *requisition = widget->requisition;
835   
836   if (aux_info)
837     {
838       if (aux_info->width > 0)
839         requisition->width = aux_info->width;
840       if (aux_info->height > 0)
841         requisition->height = aux_info->height;
842     }
843 }
844
845 static void
846 get_fast_natural_size (GtkWidget      *widget,
847                        GtkRequisition *requisition)
848 {
849   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
850
851   if (aux_info)
852     *requisition = aux_info->natural_size;
853   else
854     *requisition = widget->requisition;
855 }
856
857 /**
858  * _gtk_size_group_get_child_requisition:
859  * @widget: a #GtkWidget
860  * @requisition: location to store computed requisition.
861  * 
862  * Retrieve the "child requisition" of the widget, taking account grouping
863  * of the widget's requisition with other widgets.
864  **/
865 void
866 _gtk_size_group_get_child_requisition (GtkWidget      *widget,
867                                        GtkRequisition *requisition)
868 {
869   initialize_size_group_quarks ();
870
871   if (requisition)
872     {
873       if (get_size_groups (widget))
874         {
875           get_dimensions (widget, GTK_SIZE_GROUP_HORIZONTAL, &requisition->width, NULL);
876           get_dimensions (widget, GTK_SIZE_GROUP_VERTICAL, &requisition->height, NULL);
877
878           /* Only do the full computation if we actually have size groups */
879         }
880       else
881         get_fast_child_requisition (widget, requisition);
882     }
883 }
884
885 /**
886  * _gtk_size_group_compute_desired_size:
887  * @widget: a #GtkWidget
888  * @minimum_size: location to store computed minimum size
889  * @natural_size: location to store computed natural size
890  * 
891  * Compute the desired size of a widget taking into account grouping of
892  * the widget's requisition with other widgets.
893  **/
894 void
895 _gtk_size_group_compute_desired_size (GtkWidget      *widget,
896                                       GtkRequisition *minimum_size,
897                                       GtkRequisition *natural_size)
898 {
899   initialize_size_group_quarks ();
900
901   if (get_size_groups (widget))
902     {
903       /* Only do the full computation if we actually have size groups */
904
905       compute_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL,
906                          minimum_size ? &minimum_size->width : NULL,
907                          natural_size ? &natural_size->width : NULL);
908       compute_dimension (widget, GTK_SIZE_GROUP_VERTICAL,
909                          minimum_size ? &minimum_size->height : NULL,
910                          natural_size ? &natural_size->height : NULL);
911     }
912   else
913     {
914       do_size_request (widget);
915
916       if (minimum_size)
917         get_fast_child_requisition (widget, minimum_size);
918       if (natural_size)
919         get_fast_natural_size (widget, natural_size);
920     }
921 }
922
923 /**
924  * _gtk_size_group_queue_resize:
925  * @widget: a #GtkWidget
926  * 
927  * Queue a resize on a widget, and on all other widgets grouped with this widget.
928  **/
929 void
930 _gtk_size_group_queue_resize (GtkWidget *widget)
931 {
932   initialize_size_group_quarks ();
933
934   queue_resize_on_widget (widget, TRUE);
935 }
936
937 typedef struct {
938   GObject *object;
939   GSList *items;
940 } GSListSubParserData;
941
942 static void
943 size_group_start_element (GMarkupParseContext *context,
944                           const gchar         *element_name,
945                           const gchar        **names,
946                           const gchar        **values,
947                           gpointer            user_data,
948                           GError            **error)
949 {
950   guint i;
951   GSListSubParserData *data = (GSListSubParserData*)user_data;
952
953   if (strcmp (element_name, "widget") == 0)
954     for (i = 0; names[i]; i++)
955       if (strcmp (names[i], "name") == 0)
956         data->items = g_slist_prepend (data->items, g_strdup (values[i]));
957   else if (strcmp (element_name, "widgets") == 0)
958     return;
959   else
960     g_warning ("Unsupported type tag for GtkSizeGroup: %s\n",
961                element_name);
962
963 }
964
965 static const GMarkupParser size_group_parser =
966   {
967     size_group_start_element
968   };
969
970 static gboolean
971 gtk_size_group_buildable_custom_tag_start (GtkBuildable  *buildable,
972                                            GtkBuilder    *builder,
973                                            GObject       *child,
974                                            const gchar   *tagname,
975                                            GMarkupParser *parser,
976                                            gpointer      *data)
977 {
978   GSListSubParserData *parser_data;
979
980   if (child)
981     return FALSE;
982
983   if (strcmp (tagname, "widgets") == 0)
984     {
985       parser_data = g_slice_new0 (GSListSubParserData);
986       parser_data->items = NULL;
987       parser_data->object = G_OBJECT (buildable);
988
989       *parser = size_group_parser;
990       *data = parser_data;
991       return TRUE;
992     }
993
994   return FALSE;
995 }
996
997 static void
998 gtk_size_group_buildable_custom_finished (GtkBuildable  *buildable,
999                                           GtkBuilder    *builder,
1000                                           GObject       *child,
1001                                           const gchar   *tagname,
1002                                           gpointer       user_data)
1003 {
1004   GSList *l;
1005   GSListSubParserData *data;
1006   GObject *object;
1007
1008   if (strcmp (tagname, "widgets"))
1009     return;
1010   
1011   data = (GSListSubParserData*)user_data;
1012   data->items = g_slist_reverse (data->items);
1013
1014   for (l = data->items; l; l = l->next)
1015     {
1016       object = gtk_builder_get_object (builder, l->data);
1017       if (!object)
1018         {
1019           g_warning ("Unknown object %s specified in sizegroup %s",
1020                      (const gchar*)l->data,
1021                      gtk_buildable_get_name (GTK_BUILDABLE (data->object)));
1022           continue;
1023         }
1024       gtk_size_group_add_widget (GTK_SIZE_GROUP (data->object),
1025                                  GTK_WIDGET (object));
1026       g_free (l->data);
1027     }
1028   g_slist_free (data->items);
1029   g_slice_free (GSListSubParserData, data);
1030 }
1031
1032
1033 #define __GTK_SIZE_GROUP_C__
1034 #include "gtkaliasdef.c"