]> Pileus Git - ~andy/gtk/blob - gtk/gtksizegroup.c
ca6cc5d38c7206dda5f9dbe7877b3cbf7dbf758f
[~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 "gtkcontainer.h"
23 #include "gtkintl.h"
24 #include "gtkprivate.h"
25 #include "gtksizegroup.h"
26 #include "gtkalias.h"
27
28 enum {
29   PROP_0,
30   PROP_MODE
31 };
32
33 static void gtk_size_group_set_property (GObject      *object,
34                                          guint         prop_id,
35                                          const GValue *value,
36                                          GParamSpec   *pspec);
37 static void gtk_size_group_get_property (GObject      *object,
38                                          guint         prop_id,
39                                          GValue       *value,
40                                          GParamSpec   *pspec);
41
42 static void add_group_to_closure  (GtkSizeGroup      *group,
43                                    GtkSizeGroupMode   mode,
44                                    GSList           **groups,
45                                    GSList           **widgets);
46 static void add_widget_to_closure (GtkWidget         *widget,
47                                    GtkSizeGroupMode   mode,
48                                    GSList           **groups,
49                                    GSList           **widgets);
50
51 static GQuark size_groups_quark;
52 static const gchar size_groups_tag[] = "gtk-size-groups";
53
54 static GSList *
55 get_size_groups (GtkWidget *widget)
56 {
57   if (!size_groups_quark)
58     size_groups_quark = g_quark_from_string (size_groups_tag);
59   
60   return g_object_get_qdata (G_OBJECT (widget), size_groups_quark);
61 }
62
63 static void
64 set_size_groups (GtkWidget *widget,
65                  GSList    *groups)
66 {
67   if (!size_groups_quark)
68     size_groups_quark = g_quark_from_string (size_groups_tag);
69
70   g_object_set_qdata (G_OBJECT (widget), size_groups_quark, groups);
71 }
72
73 static void
74 add_group_to_closure (GtkSizeGroup    *group,
75                       GtkSizeGroupMode mode,
76                       GSList         **groups,
77                       GSList         **widgets)
78 {
79   GSList *tmp_widgets;
80   
81   *groups = g_slist_prepend (*groups, group);
82
83   tmp_widgets = group->widgets;
84   while (tmp_widgets)
85     {
86       GtkWidget *tmp_widget = tmp_widgets->data;
87       
88       if (!g_slist_find (*widgets, tmp_widget))
89         add_widget_to_closure (tmp_widget, mode, groups, widgets);
90       
91       tmp_widgets = tmp_widgets->next;
92     }
93 }
94
95 static void
96 add_widget_to_closure (GtkWidget       *widget,
97                        GtkSizeGroupMode mode,
98                        GSList         **groups,
99                        GSList         **widgets)
100 {
101   GSList *tmp_groups;
102
103   *widgets = g_slist_prepend (*widgets, widget);
104
105   tmp_groups = get_size_groups (widget);
106   while (tmp_groups)
107     {
108       GtkSizeGroup *tmp_group = tmp_groups->data;
109       
110       if ((tmp_group->mode == GTK_SIZE_GROUP_BOTH || tmp_group->mode == mode) &&
111           !g_slist_find (*groups, tmp_group))
112         add_group_to_closure (tmp_group, mode, groups, widgets);
113
114       tmp_groups = tmp_groups->next;
115     }
116 }
117
118 static void
119 real_queue_resize (GtkWidget *widget)
120 {
121   GTK_PRIVATE_SET_FLAG (widget, GTK_ALLOC_NEEDED);
122   GTK_PRIVATE_SET_FLAG (widget, GTK_REQUEST_NEEDED);
123   
124   if (widget->parent)
125     _gtk_container_queue_resize (GTK_CONTAINER (widget->parent));
126   else if (GTK_WIDGET_TOPLEVEL (widget) && GTK_IS_CONTAINER (widget))
127     _gtk_container_queue_resize (GTK_CONTAINER (widget));
128 }
129
130 static void
131 reset_group_sizes (GSList *groups)
132 {
133   GSList *tmp_list = groups;
134   while (tmp_list)
135     {
136       GtkSizeGroup *tmp_group = tmp_list->data;
137
138       tmp_group->have_width = FALSE;
139       tmp_group->have_height = FALSE;
140       
141       tmp_list = tmp_list->next;
142     }
143 }
144
145 static void
146 queue_resize_on_widget (GtkWidget *widget,
147                         gboolean   check_siblings)
148 {
149   GtkWidget *parent = widget;
150   GSList *tmp_list;
151
152   while (parent)
153     {
154       GSList *widget_groups;
155       GSList *groups;
156       GSList *widgets;
157       
158       if (widget == parent && !check_siblings)
159         {
160           real_queue_resize (widget);
161           parent = parent->parent;
162           continue;
163         }
164       
165       widget_groups = get_size_groups (parent);
166       if (!widget_groups)
167         {
168           if (widget == parent)
169             real_queue_resize (widget);
170
171           parent = parent->parent;
172           continue;
173         }
174
175       groups = NULL;
176       widgets = NULL;
177           
178       add_widget_to_closure (parent, GTK_SIZE_GROUP_HORIZONTAL, &groups, &widgets);
179       reset_group_sizes (groups);
180               
181       tmp_list = widgets;
182       while (tmp_list)
183         {
184           if (tmp_list->data == parent)
185             {
186               if (widget == parent)
187                 real_queue_resize (parent);
188             }
189           else
190             queue_resize_on_widget (tmp_list->data, FALSE);
191
192           tmp_list = tmp_list->next;
193         }
194       
195       g_slist_free (widgets);
196       g_slist_free (groups);
197               
198       groups = NULL;
199       widgets = NULL;
200               
201       add_widget_to_closure (parent, GTK_SIZE_GROUP_VERTICAL, &groups, &widgets);
202       reset_group_sizes (groups);
203               
204       tmp_list = widgets;
205       while (tmp_list)
206         {
207           if (tmp_list->data == parent)
208             {
209               if (widget == parent)
210                 real_queue_resize (parent);
211             }
212           else
213             queue_resize_on_widget (tmp_list->data, FALSE);
214
215           tmp_list = tmp_list->next;
216         }
217       
218       g_slist_free (widgets);
219       g_slist_free (groups);
220       
221       parent = parent->parent;
222     }
223 }
224
225 static void
226 queue_resize_on_group (GtkSizeGroup *size_group)
227 {
228   if (size_group->widgets)
229     queue_resize_on_widget (size_group->widgets->data, TRUE);
230 }
231
232 static void
233 gtk_size_group_class_init (GtkSizeGroupClass *klass)
234 {
235   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
236
237   gobject_class->set_property = gtk_size_group_set_property;
238   gobject_class->get_property = gtk_size_group_get_property;
239   
240   g_object_class_install_property (gobject_class,
241                                    PROP_MODE,
242                                    g_param_spec_enum ("mode",
243                                                       P_("Mode"),
244                                                       P_("The directions in which the size group affects the requested sizes"
245                                                         " of its component widgets"),
246                                                       GTK_TYPE_SIZE_GROUP_MODE,
247                                                       GTK_SIZE_GROUP_HORIZONTAL,
248                                                       G_PARAM_READWRITE));
249 }
250
251 static void
252 gtk_size_group_init (GtkSizeGroup *size_group)
253 {
254   size_group->widgets = NULL;
255   size_group->mode = GTK_SIZE_GROUP_HORIZONTAL;
256   size_group->have_width = 0;
257   size_group->have_height = 0;
258 }
259
260 GType
261 gtk_size_group_get_type (void)
262 {
263   static GType size_group_type = 0;
264
265   if (!size_group_type)
266     {
267       static const GTypeInfo size_group_info =
268       {
269         sizeof (GtkSizeGroupClass),
270         NULL,           /* base_init */
271         NULL,           /* base_finalize */
272         (GClassInitFunc) gtk_size_group_class_init,
273         NULL,           /* class_finalize */
274         NULL,           /* class_data */
275         sizeof (GtkSizeGroup),
276         16,             /* n_preallocs */
277         (GInstanceInitFunc) gtk_size_group_init,
278       };
279
280       size_group_type = g_type_register_static (G_TYPE_OBJECT, "GtkSizeGroup",
281                                                 &size_group_info, 0);
282     }
283
284   return size_group_type;
285 }
286
287 static void
288 gtk_size_group_set_property (GObject      *object,
289                              guint         prop_id,
290                              const GValue *value,
291                              GParamSpec   *pspec)
292 {
293   GtkSizeGroup *size_group = GTK_SIZE_GROUP (object);
294
295   switch (prop_id)
296     {
297     case PROP_MODE:
298       gtk_size_group_set_mode (size_group, g_value_get_enum (value));
299       break;
300     default:
301       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
302       break;
303     }
304 }
305
306 static void
307 gtk_size_group_get_property (GObject      *object,
308                              guint         prop_id,
309                              GValue       *value,
310                              GParamSpec   *pspec)
311 {
312   GtkSizeGroup *size_group = GTK_SIZE_GROUP (object);
313
314   switch (prop_id)
315     {
316     case PROP_MODE:
317       g_value_set_enum (value, size_group->mode);
318       break;
319     default:
320       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321       break;
322     }
323 }
324
325 /**
326  * gtk_size_group_new:
327  * @mode: the mode for the new size group.
328  * 
329  * Create a new #GtkSizeGroup.
330  
331  * Return value: a newly created #GtkSizeGroup
332  **/
333 GtkSizeGroup *
334 gtk_size_group_new (GtkSizeGroupMode  mode)
335 {
336   GtkSizeGroup *size_group = g_object_new (GTK_TYPE_SIZE_GROUP, NULL);
337
338   size_group->mode = mode;
339
340   return size_group;
341 }
342
343 /**
344  * gtk_size_group_set_mode:
345  * @size_group: a #GtkSizeGroup
346  * @mode: the mode to set for the size group.
347  * 
348  * Sets the #GtkSizeGroupMode of the size group. The mode of the size
349  * group determines whether the widgets in the size group should
350  * all have the same horizontal requisition (%GTK_SIZE_GROUP_MODE_HORIZONTAL)
351  * all have the same vertical requisition (%GTK_SIZE_GROUP_MODE_VERTICAL),
352  * or should all have the same requisition in both directions
353  * (%GTK_SIZE_GROUP_MODE_BOTH).
354  **/
355 void
356 gtk_size_group_set_mode (GtkSizeGroup     *size_group,
357                          GtkSizeGroupMode  mode)
358 {
359   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
360
361   if (size_group->mode != mode)
362     {
363       if (size_group->mode != GTK_SIZE_GROUP_NONE)
364         queue_resize_on_group (size_group);
365       size_group->mode = mode;
366       if (size_group->mode != GTK_SIZE_GROUP_NONE)
367         queue_resize_on_group (size_group);
368
369       g_object_notify (G_OBJECT (size_group), "mode");
370     }
371 }
372
373 /**
374  * gtk_size_group_get_mode:
375  * @size_group: a #GtkSizeGroup
376  * 
377  * Gets the current mode of the size group. See gtk_size_group_set_mode().
378  * 
379  * Return value: the current mode of the size group.
380  **/
381 GtkSizeGroupMode
382 gtk_size_group_get_mode (GtkSizeGroup *size_group)
383 {
384   g_return_val_if_fail (GTK_IS_SIZE_GROUP (size_group), GTK_SIZE_GROUP_BOTH);
385
386   return size_group->mode;
387 }
388
389 static void
390 gtk_size_group_widget_destroyed (GtkWidget    *widget,
391                                  GtkSizeGroup *size_group)
392 {
393   gtk_size_group_remove_widget (size_group, widget);
394 }
395
396 /**
397  * gtk_size_group_add_widget:
398  * @size_group: a #GtkSizeGroup
399  * @widget: the #GtkWidget to add
400  * 
401  * Adds a widget to a #GtkSizeGroup. In the future, the requisition
402  * of the widget will be determined as the maximum of its requisition
403  * and the requisition of the other widgets in the size group.
404  * Whether this applies horizontally, vertically, or in both directions
405  * depends on the mode of the size group. See gtk_size_group_set_mode().
406  **/
407 void
408 gtk_size_group_add_widget (GtkSizeGroup     *size_group,
409                            GtkWidget        *widget)
410 {
411   GSList *groups;
412   
413   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
414   g_return_if_fail (GTK_IS_WIDGET (widget));
415   
416   groups = get_size_groups (widget);
417
418   if (!g_slist_find (groups, size_group))
419     {
420       groups = g_slist_prepend (groups, size_group);
421       set_size_groups (widget, groups);
422
423       size_group->widgets = g_slist_prepend (size_group->widgets, widget);
424
425       g_signal_connect (widget, "destroy",
426                         G_CALLBACK (gtk_size_group_widget_destroyed),
427                         size_group);
428
429       g_object_ref (size_group);
430     }
431   
432   queue_resize_on_group (size_group);
433 }
434
435 /**
436  * gtk_size_group_remove_widget:
437  * @size_group: a #GtkSizeGrup
438  * @widget: the #GtkWidget to remove
439  * 
440  * Removes a widget from a #GtkSizeGroup.
441  **/
442 void
443 gtk_size_group_remove_widget (GtkSizeGroup     *size_group,
444                               GtkWidget        *widget)
445 {
446   GSList *groups;
447   
448   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
449   g_return_if_fail (GTK_IS_WIDGET (widget));
450   g_return_if_fail (g_slist_find (size_group->widgets, widget));
451
452   g_signal_handlers_disconnect_by_func (widget,
453                                         gtk_size_group_widget_destroyed,
454                                         size_group);
455   
456   groups = get_size_groups (widget);
457   groups = g_slist_remove (groups, size_group);
458   set_size_groups (widget, groups);
459
460   size_group->widgets = g_slist_remove (size_group->widgets, widget);
461   queue_resize_on_group (size_group);
462   gtk_widget_queue_resize (widget);
463
464   g_object_unref (size_group);
465 }
466
467 static gint
468 get_base_dimension (GtkWidget        *widget,
469                     GtkSizeGroupMode  mode)
470 {
471   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
472
473   if (mode == GTK_SIZE_GROUP_HORIZONTAL)
474     {
475       if (aux_info && aux_info->width > 0)
476         return aux_info->width;
477       else
478         return widget->requisition.width;
479     }
480   else
481     {
482       if (aux_info && aux_info->height > 0)
483         return aux_info->height;
484       else
485         return widget->requisition.height;
486     }
487 }
488
489 static void
490 do_size_request (GtkWidget *widget)
491 {
492   if (GTK_WIDGET_REQUEST_NEEDED (widget))
493     {
494       gtk_widget_ensure_style (widget);      
495       GTK_PRIVATE_UNSET_FLAG (widget, GTK_REQUEST_NEEDED);
496       g_signal_emit_by_name (widget,
497                              "size_request",
498                              &widget->requisition);
499     }
500 }
501
502 static gint
503 compute_base_dimension (GtkWidget        *widget,
504                         GtkSizeGroupMode  mode)
505 {
506   do_size_request (widget);
507
508   return get_base_dimension (widget, mode);
509 }
510
511 static gint
512 compute_dimension (GtkWidget        *widget,
513                    GtkSizeGroupMode  mode)
514 {
515   GSList *widgets = NULL;
516   GSList *groups = NULL;
517   GSList *tmp_list;
518   gint result = 0;
519
520   add_widget_to_closure (widget, mode, &groups, &widgets);
521
522   g_slist_foreach (widgets, (GFunc)g_object_ref, NULL);
523   
524   if (!groups)
525     {
526       result = compute_base_dimension (widget, mode);
527     }
528   else
529     {
530       GtkSizeGroup *group = groups->data;
531
532       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
533         result = group->requisition.width;
534       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
535         result = group->requisition.height;
536       else
537         {
538           tmp_list = widgets;
539           while (tmp_list)
540             {
541               GtkWidget *tmp_widget = tmp_list->data;
542
543               gint dimension = compute_base_dimension (tmp_widget, mode);
544
545               if (dimension > result)
546                 result = dimension;
547               
548               tmp_list = tmp_list->next;
549             }
550
551           tmp_list = groups;
552           while (tmp_list)
553             {
554               GtkSizeGroup *tmp_group = tmp_list->data;
555
556               if (mode == GTK_SIZE_GROUP_HORIZONTAL)
557                 {
558                   tmp_group->have_width = TRUE;
559                   tmp_group->requisition.width = result;
560                 }
561               else
562                 {
563                   tmp_group->have_height = TRUE;
564                   tmp_group->requisition.height = result;
565                 }
566               
567               tmp_list = tmp_list->next;
568             }
569         }
570     }
571
572   g_slist_foreach (widgets, (GFunc)g_object_unref, NULL);
573   
574   g_slist_free (widgets);
575   g_slist_free (groups);
576
577   return result;
578 }
579
580 static gint
581 get_dimension (GtkWidget        *widget,
582                GtkSizeGroupMode  mode)
583 {
584   GSList *widgets = NULL;
585   GSList *groups = NULL;
586   gint result = 0;
587
588   add_widget_to_closure (widget, mode, &groups, &widgets);
589
590   if (!groups)
591     {
592       result = get_base_dimension (widget, mode);
593     }
594   else
595     {
596       GtkSizeGroup *group = groups->data;
597
598       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
599         result = group->requisition.width;
600       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
601         result = group->requisition.height;
602     }
603
604   g_slist_free (widgets);
605   g_slist_free (groups);
606
607   return result;
608 }
609
610 static void
611 get_fast_child_requisition (GtkWidget      *widget,
612                             GtkRequisition *requisition)
613 {
614   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
615   
616   *requisition = widget->requisition;
617   
618   if (aux_info)
619     {
620       if (aux_info->width > 0)
621         requisition->width = aux_info->width;
622       if (aux_info && aux_info->height > 0)
623         requisition->height = aux_info->height;
624     }
625 }
626
627 /**
628  * _gtk_size_group_get_child_requisition:
629  * @widget: a #GtkWidget
630  * @requisition: location to store computed requisition.
631  * 
632  * Retrieve the "child requisition" of the widget, taking account grouping
633  * of the widget's requisition with other widgets.
634  **/
635 void
636 _gtk_size_group_get_child_requisition (GtkWidget      *widget,
637                                        GtkRequisition *requisition)
638 {
639   if (requisition)
640     {
641       if (get_size_groups (widget))
642         {
643           requisition->width = get_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL);
644           requisition->height = get_dimension (widget, GTK_SIZE_GROUP_VERTICAL);
645
646           /* Only do the full computation if we actually have size groups */
647         }
648       else
649         get_fast_child_requisition (widget, requisition);
650     }
651 }
652
653 /**
654  * _gtk_size_group_compute_requisition:
655  * @widget: a #GtkWidget
656  * @requisition: location to store computed requisition.
657  * 
658  * Compute the requisition of a widget taking into account grouping of
659  * the widget's requisition with other widgets.
660  **/
661 void
662 _gtk_size_group_compute_requisition (GtkWidget      *widget,
663                                      GtkRequisition *requisition)
664 {
665   gint width;
666   gint height;
667
668   if (get_size_groups (widget))
669     {
670       /* Only do the full computation if we actually have size groups */
671       
672       width = compute_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL);
673       height = compute_dimension (widget, GTK_SIZE_GROUP_VERTICAL);
674
675       if (requisition)
676         {
677           requisition->width = width;
678           requisition->height = height;
679         }
680     }
681   else
682     {
683       do_size_request (widget);
684       
685       if (requisition)
686         get_fast_child_requisition (widget, requisition);
687     }
688 }
689
690 /**
691  * _gtk_size_group_queue_resize:
692  * @widget: a #GtkWidget
693  * 
694  * Queue a resize on a widget, and on all other widgets grouped with this widget.
695  **/
696 void
697 _gtk_size_group_queue_resize (GtkWidget *widget)
698 {
699   queue_resize_on_widget (widget, TRUE);
700 }
701
702 #define __GTK_SIZE_GROUP_C__
703 #include "gtkaliasdef.c"