]> Pileus Git - ~andy/gtk/blob - gtk/gtksizegroup.c
Replace homegrown "nbsp", "hash" and "percent" entities by standard ISO
[~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 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       if (size_group->mode != GTK_SIZE_GROUP_NONE)
362         queue_resize_on_group (size_group);
363       size_group->mode = mode;
364       if (size_group->mode != GTK_SIZE_GROUP_NONE)
365         queue_resize_on_group (size_group);
366
367       g_object_notify (G_OBJECT (size_group), "mode");
368     }
369 }
370
371 /**
372  * gtk_size_group_get_mode:
373  * @size_group: a #GtkSizeGroup
374  * 
375  * Gets the current mode of the size group. See gtk_size_group_set_mode().
376  * 
377  * Return value: the current mode of the size group.
378  **/
379 GtkSizeGroupMode
380 gtk_size_group_get_mode (GtkSizeGroup *size_group)
381 {
382   g_return_val_if_fail (GTK_IS_SIZE_GROUP (size_group), GTK_SIZE_GROUP_BOTH);
383
384   return size_group->mode;
385 }
386
387 static void
388 gtk_size_group_widget_destroyed (GtkWidget    *widget,
389                                  GtkSizeGroup *size_group)
390 {
391   gtk_size_group_remove_widget (size_group, widget);
392 }
393
394 /**
395  * gtk_size_group_add_widget:
396  * @size_group: a #GtkSizeGroup
397  * @widget: the #GtkWidget to add
398  * 
399  * Adds a widget to a #GtkSizeGroup. In the future, the requisition
400  * of the widget will be determined as the maximum of its requisition
401  * and the requisition of the other widgets in the size group.
402  * Whether this applies horizontally, vertically, or in both directions
403  * depends on the mode of the size group. See gtk_size_group_set_mode().
404  **/
405 void
406 gtk_size_group_add_widget (GtkSizeGroup     *size_group,
407                            GtkWidget        *widget)
408 {
409   GSList *groups;
410   
411   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
412   g_return_if_fail (GTK_IS_WIDGET (widget));
413   
414   groups = get_size_groups (widget);
415
416   if (!g_slist_find (groups, widget))
417     {
418       groups = g_slist_prepend (groups, size_group);
419       set_size_groups (widget, groups);
420
421       size_group->widgets = g_slist_prepend (size_group->widgets, widget);
422
423       gtk_signal_connect (GTK_OBJECT (widget), "destroy",
424                           GTK_SIGNAL_FUNC (gtk_size_group_widget_destroyed), size_group);
425
426       g_object_ref (G_OBJECT (size_group));
427     }
428   
429   queue_resize_on_group (size_group);
430 }
431
432 /**
433  * gtk_size_group_remove_widget:
434  * @size_group: a #GtkSizeGrup
435  * @widget: the #GtkWidget to remove
436  * 
437  * Removes a widget from a #GtkSizeGroup.
438  **/
439 void
440 gtk_size_group_remove_widget (GtkSizeGroup     *size_group,
441                               GtkWidget        *widget)
442 {
443   GSList *groups;
444   
445   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
446   g_return_if_fail (GTK_IS_WIDGET (widget));
447   g_return_if_fail (g_slist_find (size_group->widgets, widget));
448
449   gtk_signal_disconnect_by_func (GTK_OBJECT (widget), 
450                                  GTK_SIGNAL_FUNC (gtk_size_group_widget_destroyed), size_group);
451   
452   groups = get_size_groups (widget);
453   groups = g_slist_remove (groups, size_group);
454   set_size_groups (widget, groups);
455
456   size_group->widgets = g_slist_remove (size_group->widgets, widget);
457   queue_resize_on_group (size_group);
458   gtk_widget_queue_resize (widget);
459
460   g_object_unref (G_OBJECT (size_group));
461 }
462
463 static gint
464 get_base_dimension (GtkWidget        *widget,
465                     GtkSizeGroupMode  mode)
466 {
467   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
468
469   if (mode == GTK_SIZE_GROUP_HORIZONTAL)
470     {
471       if (aux_info && aux_info->width > 0)
472         return aux_info->width;
473       else
474         return widget->requisition.width;
475     }
476   else
477     {
478       if (aux_info && aux_info->height > 0)
479         return aux_info->height;
480       else
481         return widget->requisition.height;
482     }
483 }
484
485 static void
486 do_size_request (GtkWidget *widget)
487 {
488   if (GTK_WIDGET_REQUEST_NEEDED (widget))
489     {
490       gtk_widget_ensure_style (widget);
491       gtk_signal_emit_by_name (GTK_OBJECT (widget), "size_request", &widget->requisition);
492       
493       GTK_PRIVATE_UNSET_FLAG (widget, GTK_REQUEST_NEEDED);
494     }
495 }
496
497 static gint
498 compute_base_dimension (GtkWidget        *widget,
499                         GtkSizeGroupMode  mode)
500 {
501   do_size_request (widget);
502
503   return get_base_dimension (widget, mode);
504 }
505
506 static gint
507 compute_dimension (GtkWidget        *widget,
508                    GtkSizeGroupMode  mode)
509 {
510   GSList *widgets = NULL;
511   GSList *groups = NULL;
512   GSList *tmp_list;
513   gint result = 0;
514
515   add_widget_to_closure (widget, mode, &groups, &widgets);
516
517   g_slist_foreach (widgets, (GFunc)g_object_ref, NULL);
518   
519   if (!groups)
520     {
521       result = compute_base_dimension (widget, mode);
522     }
523   else
524     {
525       GtkSizeGroup *group = groups->data;
526
527       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
528         result = group->requisition.width;
529       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
530         result = group->requisition.height;
531       else
532         {
533           tmp_list = widgets;
534           while (tmp_list)
535             {
536               GtkWidget *tmp_widget = tmp_list->data;
537
538               gint dimension = compute_base_dimension (tmp_widget, mode);
539
540               if (dimension > result)
541                 result = dimension;
542               
543               tmp_list = tmp_list->next;
544             }
545
546           tmp_list = groups;
547           while (tmp_list)
548             {
549               GtkSizeGroup *tmp_group = tmp_list->data;
550
551               if (mode == GTK_SIZE_GROUP_HORIZONTAL)
552                 {
553                   tmp_group->have_width = TRUE;
554                   tmp_group->requisition.width = result;
555                 }
556               else
557                 {
558                   tmp_group->have_height = TRUE;
559                   tmp_group->requisition.height = result;
560                 }
561               
562               tmp_list = tmp_list->next;
563             }
564         }
565     }
566
567   g_slist_foreach (widgets, (GFunc)g_object_unref, NULL);
568   
569   g_slist_free (widgets);
570   g_slist_free (groups);
571
572   return result;
573 }
574
575 static gint
576 get_dimension (GtkWidget        *widget,
577                GtkSizeGroupMode  mode)
578 {
579   GSList *widgets = NULL;
580   GSList *groups = NULL;
581   gint result = 0;
582
583   add_widget_to_closure (widget, mode, &groups, &widgets);
584
585   if (!groups)
586     {
587       result = get_base_dimension (widget, mode);
588     }
589   else
590     {
591       GtkSizeGroup *group = groups->data;
592
593       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
594         result = group->requisition.width;
595       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
596         result = group->requisition.height;
597     }
598
599   g_slist_free (widgets);
600   g_slist_free (groups);
601
602   return result;
603 }
604
605 static void
606 get_fast_child_requisition (GtkWidget      *widget,
607                             GtkRequisition *requisition)
608 {
609   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
610   
611   *requisition = widget->requisition;
612   
613   if (aux_info)
614     {
615       if (aux_info->width > 0)
616         requisition->width = aux_info->width;
617       if (aux_info && aux_info->height > 0)
618         requisition->height = aux_info->height;
619     }
620 }
621
622 /**
623  * _gtk_size_group_get_child_requisition:
624  * @widget: a #GtkWidget
625  * @requisition: location to store computed requisition.
626  * 
627  * Retrieve the "child requisition" of the widget, taking account grouping
628  * of the widget's requisition with other widgets.
629  **/
630 void
631 _gtk_size_group_get_child_requisition (GtkWidget      *widget,
632                                        GtkRequisition *requisition)
633 {
634   if (requisition)
635     {
636       if (get_size_groups (widget))
637         {
638           requisition->width = get_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL);
639           requisition->height = get_dimension (widget, GTK_SIZE_GROUP_VERTICAL);
640
641           /* Only do the full computation if we actually have size groups */
642         }
643       else
644         get_fast_child_requisition (widget, requisition);
645     }
646 }
647
648 /**
649  * _gtk_size_group_compute_requisition:
650  * @widget: a #GtkWidget
651  * @requisition: location to store computed requisition.
652  * 
653  * Compute the requisition of a widget taking into account grouping of
654  * the widget's requisition with other widgets.
655  **/
656 void
657 _gtk_size_group_compute_requisition (GtkWidget      *widget,
658                                      GtkRequisition *requisition)
659 {
660   gint width;
661   gint height;
662
663   if (get_size_groups (widget))
664     {
665       /* Only do the full computation if we actually have size groups */
666       
667       width = compute_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL);
668       height = compute_dimension (widget, GTK_SIZE_GROUP_VERTICAL);
669
670       if (requisition)
671         {
672           requisition->width = width;
673           requisition->height = height;
674         }
675     }
676   else
677     {
678       do_size_request (widget);
679       
680       if (requisition)
681         get_fast_child_requisition (widget, requisition);
682     }
683 }
684
685 /**
686  * _gtk_size_group_queue_resize:
687  * @widget: a #GtkWidget
688  * 
689  * Queue a resize on a widget, and on all other widgets grouped with this widget.
690  **/
691 void
692 _gtk_size_group_queue_resize (GtkWidget *widget)
693 {
694   queue_resize_on_widget (widget, TRUE);
695 }