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