]> Pileus Git - ~andy/gtk/blob - gtk/gtksizegroup.c
Don't add attributes with empty ranges. (fixes #101564 and #80637)
[~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 "gtksizegroup.h"
25
26 enum {
27   PROP_0,
28   PROP_MODE
29 };
30
31 static void gtk_size_group_set_property (GObject      *object,
32                                          guint         prop_id,
33                                          const GValue *value,
34                                          GParamSpec   *pspec);
35 static void gtk_size_group_get_property (GObject      *object,
36                                          guint         prop_id,
37                                          GValue       *value,
38                                          GParamSpec   *pspec);
39
40 static void add_group_to_closure  (GtkSizeGroup      *group,
41                                    GtkSizeGroupMode   mode,
42                                    GSList           **groups,
43                                    GSList           **widgets);
44 static void add_widget_to_closure (GtkWidget         *widget,
45                                    GtkSizeGroupMode   mode,
46                                    GSList           **groups,
47                                    GSList           **widgets);
48
49 static GQuark size_groups_quark;
50 static const gchar size_groups_tag[] = "gtk-size-groups";
51
52 static GSList *
53 get_size_groups (GtkWidget *widget)
54 {
55   if (!size_groups_quark)
56     size_groups_quark = g_quark_from_string (size_groups_tag);
57   
58   return g_object_get_qdata (G_OBJECT (widget), size_groups_quark);
59 }
60
61 static void
62 set_size_groups (GtkWidget *widget,
63                  GSList    *groups)
64 {
65   if (!size_groups_quark)
66     size_groups_quark = g_quark_from_string (size_groups_tag);
67
68   g_object_set_qdata (G_OBJECT (widget), size_groups_quark, groups);
69 }
70
71 static void
72 add_group_to_closure (GtkSizeGroup    *group,
73                       GtkSizeGroupMode mode,
74                       GSList         **groups,
75                       GSList         **widgets)
76 {
77   GSList *tmp_widgets;
78   
79   *groups = g_slist_prepend (*groups, group);
80
81   tmp_widgets = group->widgets;
82   while (tmp_widgets)
83     {
84       GtkWidget *tmp_widget = tmp_widgets->data;
85       
86       if (!g_slist_find (*widgets, tmp_widget))
87         add_widget_to_closure (tmp_widget, mode, groups, widgets);
88       
89       tmp_widgets = tmp_widgets->next;
90     }
91 }
92
93 static void
94 add_widget_to_closure (GtkWidget       *widget,
95                        GtkSizeGroupMode mode,
96                        GSList         **groups,
97                        GSList         **widgets)
98 {
99   GSList *tmp_groups;
100
101   *widgets = g_slist_prepend (*widgets, widget);
102
103   tmp_groups = get_size_groups (widget);
104   while (tmp_groups)
105     {
106       GtkSizeGroup *tmp_group = tmp_groups->data;
107       
108       if ((tmp_group->mode == GTK_SIZE_GROUP_BOTH || tmp_group->mode == mode) &&
109           !g_slist_find (*groups, tmp_group))
110         add_group_to_closure (tmp_group, mode, groups, widgets);
111
112       tmp_groups = tmp_groups->next;
113     }
114 }
115
116 static void
117 real_queue_resize (GtkWidget *widget)
118 {
119   GTK_PRIVATE_SET_FLAG (widget, GTK_ALLOC_NEEDED);
120   GTK_PRIVATE_SET_FLAG (widget, GTK_REQUEST_NEEDED);
121   
122   if (widget->parent)
123     _gtk_container_queue_resize (GTK_CONTAINER (widget->parent));
124   else if (GTK_WIDGET_TOPLEVEL (widget) && GTK_IS_CONTAINER (widget))
125     _gtk_container_queue_resize (GTK_CONTAINER (widget));
126 }
127
128 static void
129 reset_group_sizes (GSList *groups)
130 {
131   GSList *tmp_list = groups;
132   while (tmp_list)
133     {
134       GtkSizeGroup *tmp_group = tmp_list->data;
135
136       tmp_group->have_width = FALSE;
137       tmp_group->have_height = FALSE;
138       
139       tmp_list = tmp_list->next;
140     }
141 }
142
143 static void
144 queue_resize_on_widget (GtkWidget *widget,
145                         gboolean   check_siblings)
146 {
147   GtkWidget *parent = widget;
148   GSList *tmp_list;
149
150   while (parent)
151     {
152       GSList *widget_groups;
153       GSList *groups;
154       GSList *widgets;
155       
156       if (widget == parent && !check_siblings)
157         {
158           real_queue_resize (widget);
159           parent = parent->parent;
160           continue;
161         }
162       
163       widget_groups = get_size_groups (parent);
164       if (!widget_groups)
165         {
166           if (widget == parent)
167             real_queue_resize (widget);
168
169           parent = parent->parent;
170           continue;
171         }
172
173       groups = NULL;
174       widgets = NULL;
175           
176       add_widget_to_closure (parent, GTK_SIZE_GROUP_HORIZONTAL, &groups, &widgets);
177       reset_group_sizes (groups);
178               
179       tmp_list = widgets;
180       while (tmp_list)
181         {
182           if (tmp_list->data == parent)
183             {
184               if (widget == parent)
185                 real_queue_resize (parent);
186             }
187           else
188             queue_resize_on_widget (tmp_list->data, FALSE);
189
190           tmp_list = tmp_list->next;
191         }
192       
193       g_slist_free (widgets);
194       g_slist_free (groups);
195               
196       groups = NULL;
197       widgets = NULL;
198               
199       add_widget_to_closure (parent, GTK_SIZE_GROUP_VERTICAL, &groups, &widgets);
200       reset_group_sizes (groups);
201               
202       tmp_list = widgets;
203       while (tmp_list)
204         {
205           if (tmp_list->data == parent)
206             {
207               if (widget == parent)
208                 real_queue_resize (parent);
209             }
210           else
211             queue_resize_on_widget (tmp_list->data, FALSE);
212
213           tmp_list = tmp_list->next;
214         }
215       
216       g_slist_free (widgets);
217       g_slist_free (groups);
218       
219       parent = parent->parent;
220     }
221 }
222
223 static void
224 queue_resize_on_group (GtkSizeGroup *size_group)
225 {
226   if (size_group->widgets)
227     queue_resize_on_widget (size_group->widgets->data, TRUE);
228 }
229
230 static void
231 gtk_size_group_class_init (GtkSizeGroupClass *klass)
232 {
233   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
234
235   gobject_class->set_property = gtk_size_group_set_property;
236   gobject_class->get_property = gtk_size_group_get_property;
237   
238   g_object_class_install_property (gobject_class,
239                                    PROP_MODE,
240                                    g_param_spec_enum ("mode",
241                                                       _("Mode"),
242                                                       _("The directions in which the size group effects the requested sizes"
243                                                         " of its component widgets"),
244                                                       GTK_TYPE_SIZE_GROUP_MODE,
245                                                       GTK_SIZE_GROUP_HORIZONTAL,
246                                                       G_PARAM_READWRITE));
247 }
248
249 static void
250 gtk_size_group_init (GtkSizeGroup *size_group)
251 {
252   size_group->widgets = NULL;
253   size_group->mode = GTK_SIZE_GROUP_HORIZONTAL;
254   size_group->have_width = 0;
255   size_group->have_height = 0;
256 }
257
258 GType
259 gtk_size_group_get_type (void)
260 {
261   static GType size_group_type = 0;
262
263   if (!size_group_type)
264     {
265       static const GTypeInfo size_group_info =
266       {
267         sizeof (GtkSizeGroupClass),
268         NULL,           /* base_init */
269         NULL,           /* base_finalize */
270         (GClassInitFunc) gtk_size_group_class_init,
271         NULL,           /* class_finalize */
272         NULL,           /* class_data */
273         sizeof (GtkSizeGroup),
274         16,             /* n_preallocs */
275         (GInstanceInitFunc) gtk_size_group_init,
276       };
277
278       size_group_type = g_type_register_static (G_TYPE_OBJECT, "GtkSizeGroup",
279                                                 &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       g_signal_connect (widget, "destroy",
424                         G_CALLBACK (gtk_size_group_widget_destroyed),
425                         size_group);
426
427       g_object_ref (size_group);
428     }
429   
430   queue_resize_on_group (size_group);
431 }
432
433 /**
434  * gtk_size_group_remove_widget:
435  * @size_group: a #GtkSizeGrup
436  * @widget: the #GtkWidget to remove
437  * 
438  * Removes a widget from a #GtkSizeGroup.
439  **/
440 void
441 gtk_size_group_remove_widget (GtkSizeGroup     *size_group,
442                               GtkWidget        *widget)
443 {
444   GSList *groups;
445   
446   g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
447   g_return_if_fail (GTK_IS_WIDGET (widget));
448   g_return_if_fail (g_slist_find (size_group->widgets, widget));
449
450   g_signal_handlers_disconnect_by_func (widget,
451                                         gtk_size_group_widget_destroyed,
452                                         size_group);
453   
454   groups = get_size_groups (widget);
455   groups = g_slist_remove (groups, size_group);
456   set_size_groups (widget, groups);
457
458   size_group->widgets = g_slist_remove (size_group->widgets, widget);
459   queue_resize_on_group (size_group);
460   gtk_widget_queue_resize (widget);
461
462   g_object_unref (size_group);
463 }
464
465 static gint
466 get_base_dimension (GtkWidget        *widget,
467                     GtkSizeGroupMode  mode)
468 {
469   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
470
471   if (mode == GTK_SIZE_GROUP_HORIZONTAL)
472     {
473       if (aux_info && aux_info->width > 0)
474         return aux_info->width;
475       else
476         return widget->requisition.width;
477     }
478   else
479     {
480       if (aux_info && aux_info->height > 0)
481         return aux_info->height;
482       else
483         return widget->requisition.height;
484     }
485 }
486
487 static void
488 do_size_request (GtkWidget *widget)
489 {
490   if (GTK_WIDGET_REQUEST_NEEDED (widget))
491     {
492       gtk_widget_ensure_style (widget);
493       g_signal_emit_by_name (widget,
494                              "size_request",
495                              &widget->requisition);
496       
497       GTK_PRIVATE_UNSET_FLAG (widget, GTK_REQUEST_NEEDED);
498     }
499 }
500
501 static gint
502 compute_base_dimension (GtkWidget        *widget,
503                         GtkSizeGroupMode  mode)
504 {
505   do_size_request (widget);
506
507   return get_base_dimension (widget, mode);
508 }
509
510 static gint
511 compute_dimension (GtkWidget        *widget,
512                    GtkSizeGroupMode  mode)
513 {
514   GSList *widgets = NULL;
515   GSList *groups = NULL;
516   GSList *tmp_list;
517   gint result = 0;
518
519   add_widget_to_closure (widget, mode, &groups, &widgets);
520
521   g_slist_foreach (widgets, (GFunc)g_object_ref, NULL);
522   
523   if (!groups)
524     {
525       result = compute_base_dimension (widget, mode);
526     }
527   else
528     {
529       GtkSizeGroup *group = groups->data;
530
531       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
532         result = group->requisition.width;
533       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
534         result = group->requisition.height;
535       else
536         {
537           tmp_list = widgets;
538           while (tmp_list)
539             {
540               GtkWidget *tmp_widget = tmp_list->data;
541
542               gint dimension = compute_base_dimension (tmp_widget, mode);
543
544               if (dimension > result)
545                 result = dimension;
546               
547               tmp_list = tmp_list->next;
548             }
549
550           tmp_list = groups;
551           while (tmp_list)
552             {
553               GtkSizeGroup *tmp_group = tmp_list->data;
554
555               if (mode == GTK_SIZE_GROUP_HORIZONTAL)
556                 {
557                   tmp_group->have_width = TRUE;
558                   tmp_group->requisition.width = result;
559                 }
560               else
561                 {
562                   tmp_group->have_height = TRUE;
563                   tmp_group->requisition.height = result;
564                 }
565               
566               tmp_list = tmp_list->next;
567             }
568         }
569     }
570
571   g_slist_foreach (widgets, (GFunc)g_object_unref, NULL);
572   
573   g_slist_free (widgets);
574   g_slist_free (groups);
575
576   return result;
577 }
578
579 static gint
580 get_dimension (GtkWidget        *widget,
581                GtkSizeGroupMode  mode)
582 {
583   GSList *widgets = NULL;
584   GSList *groups = NULL;
585   gint result = 0;
586
587   add_widget_to_closure (widget, mode, &groups, &widgets);
588
589   if (!groups)
590     {
591       result = get_base_dimension (widget, mode);
592     }
593   else
594     {
595       GtkSizeGroup *group = groups->data;
596
597       if (mode == GTK_SIZE_GROUP_HORIZONTAL && group->have_width)
598         result = group->requisition.width;
599       else if (mode == GTK_SIZE_GROUP_VERTICAL && group->have_height)
600         result = group->requisition.height;
601     }
602
603   g_slist_free (widgets);
604   g_slist_free (groups);
605
606   return result;
607 }
608
609 static void
610 get_fast_child_requisition (GtkWidget      *widget,
611                             GtkRequisition *requisition)
612 {
613   GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
614   
615   *requisition = widget->requisition;
616   
617   if (aux_info)
618     {
619       if (aux_info->width > 0)
620         requisition->width = aux_info->width;
621       if (aux_info && aux_info->height > 0)
622         requisition->height = aux_info->height;
623     }
624 }
625
626 /**
627  * _gtk_size_group_get_child_requisition:
628  * @widget: a #GtkWidget
629  * @requisition: location to store computed requisition.
630  * 
631  * Retrieve the "child requisition" of the widget, taking account grouping
632  * of the widget's requisition with other widgets.
633  **/
634 void
635 _gtk_size_group_get_child_requisition (GtkWidget      *widget,
636                                        GtkRequisition *requisition)
637 {
638   if (requisition)
639     {
640       if (get_size_groups (widget))
641         {
642           requisition->width = get_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL);
643           requisition->height = get_dimension (widget, GTK_SIZE_GROUP_VERTICAL);
644
645           /* Only do the full computation if we actually have size groups */
646         }
647       else
648         get_fast_child_requisition (widget, requisition);
649     }
650 }
651
652 /**
653  * _gtk_size_group_compute_requisition:
654  * @widget: a #GtkWidget
655  * @requisition: location to store computed requisition.
656  * 
657  * Compute the requisition of a widget taking into account grouping of
658  * the widget's requisition with other widgets.
659  **/
660 void
661 _gtk_size_group_compute_requisition (GtkWidget      *widget,
662                                      GtkRequisition *requisition)
663 {
664   gint width;
665   gint height;
666
667   if (get_size_groups (widget))
668     {
669       /* Only do the full computation if we actually have size groups */
670       
671       width = compute_dimension (widget, GTK_SIZE_GROUP_HORIZONTAL);
672       height = compute_dimension (widget, GTK_SIZE_GROUP_VERTICAL);
673
674       if (requisition)
675         {
676           requisition->width = width;
677           requisition->height = height;
678         }
679     }
680   else
681     {
682       do_size_request (widget);
683       
684       if (requisition)
685         get_fast_child_requisition (widget, requisition);
686     }
687 }
688
689 /**
690  * _gtk_size_group_queue_resize:
691  * @widget: a #GtkWidget
692  * 
693  * Queue a resize on a widget, and on all other widgets grouped with this widget.
694  **/
695 void
696 _gtk_size_group_queue_resize (GtkWidget *widget)
697 {
698   queue_resize_on_widget (widget, TRUE);
699 }