]> Pileus Git - ~andy/gtk/blob - gtk/gtksizerequest.c
Merge branch 'master' into treeview-refactor
[~andy/gtk] / gtk / gtksizerequest.c
1 /* gtksizerequest.c
2  * Copyright (C) 2007-2010 Openismus GmbH
3  *
4  * Authors:
5  *      Mathias Hasselmann <mathias@openismus.com>
6  *      Tristan Van Berkom <tristan.van.berkom@gmail.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <config.h>
25
26 #include "gtksizerequest.h"
27
28 #include "gtkdebug.h"
29 #include "gtkintl.h"
30 #include "gtkprivate.h"
31 #include "gtksizegroup-private.h"
32 #include "gtkwidgetprivate.h"
33
34 /* looks for a cached size request for this for_size. If not
35  * found, returns the oldest entry so it can be overwritten
36  *
37  * Note that this caching code was directly derived from
38  * the Clutter toolkit.
39  */
40 static gboolean
41 get_cached_size (SizeRequestCache  *cache,
42                  GtkSizeGroupMode   orientation,
43                  gint               for_size,
44                  SizeRequest      **result)
45 {
46   guint i, n_sizes;
47   SizeRequest  *cached_sizes;
48
49   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
50     {
51       cached_sizes = cache->widths;
52       n_sizes = cache->cached_widths;
53     }
54   else
55     {
56       cached_sizes = cache->heights;
57       n_sizes = cache->cached_widths;
58     }
59
60   /* Search for an already cached size */
61   for (i = 0; i < n_sizes; i++)
62     {
63       if (cached_sizes[i].for_size == for_size)
64         {
65           *result = &cached_sizes[i];
66           return TRUE;
67         }
68     }
69
70   /* If not found, pull a new size from the cache, the returned size cache
71    * will immediately be used to cache the new computed size so we go ahead
72    * and increment the last_cached_width/height right away */
73   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
74     {
75       if (cache->cached_widths < GTK_SIZE_REQUEST_CACHED_SIZES)
76         {
77           cache->cached_widths++;
78           cache->last_cached_width = cache->cached_widths - 1;
79         }
80       else
81         {
82           if (++cache->last_cached_width == GTK_SIZE_REQUEST_CACHED_SIZES)
83             cache->last_cached_width = 0;
84         }
85
86       *result = &cache->widths[cache->last_cached_width];
87     }
88   else /* GTK_SIZE_GROUP_VERTICAL */
89     {
90       if (cache->cached_heights < GTK_SIZE_REQUEST_CACHED_SIZES)
91         {
92           cache->cached_heights++;
93           cache->last_cached_height = cache->cached_heights - 1;
94         }
95       else
96         {
97           if (++cache->last_cached_height == GTK_SIZE_REQUEST_CACHED_SIZES)
98             cache->last_cached_height = 0;
99         }
100
101       *result = &cache->heights[cache->last_cached_height];
102     }
103
104   return FALSE;
105 }
106
107 static void
108 do_size_request (GtkWidget      *widget,
109                  GtkRequisition *requisition)
110 {
111   /* Now we dont bother caching the deprecated "size-request" returns,
112    * just unconditionally invoke here just in case we run into legacy stuff */
113   gtk_widget_ensure_style (widget);
114   g_signal_emit_by_name (widget, "size-request", requisition);
115 }
116
117 #ifndef G_DISABLE_CHECKS
118 static GQuark recursion_check_quark = 0;
119 #endif /* G_DISABLE_CHECKS */
120
121 static void
122 push_recursion_check (GtkWidget       *widget,
123                       GtkSizeGroupMode orientation,
124                       gint             for_size)
125 {
126 #ifndef G_DISABLE_CHECKS
127   const char *previous_method;
128   const char *method;
129
130   if (recursion_check_quark == 0)
131     recursion_check_quark = g_quark_from_static_string ("gtk-size-request-in-progress");
132
133   previous_method = g_object_get_qdata (G_OBJECT (widget), recursion_check_quark);
134
135   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
136     {
137       method = for_size < 0 ? "get_width" : "get_width_for_height";
138     }
139   else
140     {
141       method = for_size < 0 ? "get_height" : "get_height_for_width";
142     }
143
144   if (previous_method != NULL)
145     {
146       g_warning ("%s %p: widget tried to gtk_widget_%s inside "
147                  " GtkWidget     ::%s implementation. "
148                  "Should just invoke GTK_WIDGET_GET_CLASS(widget)->%s "
149                  "directly rather than using gtk_widget_%s",
150                  G_OBJECT_TYPE_NAME (widget), widget,
151                  method, previous_method,
152                  method, method);
153     }
154
155   g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, (char*) method);
156 #endif /* G_DISABLE_CHECKS */
157 }
158
159 static void
160 pop_recursion_check (GtkWidget       *widget,
161                      GtkSizeGroupMode orientation)
162 {
163 #ifndef G_DISABLE_CHECKS
164   g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, NULL);
165 #endif
166 }
167
168 static void
169 compute_size_for_orientation (GtkWidget         *widget,
170                               GtkSizeGroupMode   orientation,
171                               gint               for_size,
172                               gint              *minimum_size,
173                               gint              *natural_size)
174 {
175   SizeRequestCache *cache;
176   SizeRequest      *cached_size;
177   gboolean          found_in_cache = FALSE;
178   int adjusted_min, adjusted_natural;
179
180   g_return_if_fail (GTK_IS_WIDGET (widget));
181   g_return_if_fail (minimum_size != NULL || natural_size != NULL);
182
183   cache  = _gtk_widget_peek_request_cache (widget);
184
185   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
186     {
187       cached_size = &cache->widths[0];
188
189       if (!_gtk_widget_get_width_request_needed (widget))
190         found_in_cache = get_cached_size (cache, orientation, for_size, &cached_size);
191       else
192         {
193           memset (cache->widths, 0, GTK_SIZE_REQUEST_CACHED_SIZES * sizeof (SizeRequest));
194           cache->cached_widths = 1;
195           cache->last_cached_width = 0;
196         }
197     }
198   else
199     {
200       cached_size = &cache->heights[0];
201
202       if (!_gtk_widget_get_height_request_needed (widget))
203         found_in_cache = get_cached_size (cache, orientation, for_size, &cached_size);
204       else
205         {
206           memset (cache->heights, 0, GTK_SIZE_REQUEST_CACHED_SIZES * sizeof (SizeRequest));
207           cache->cached_heights = 1;
208           cache->last_cached_height = 0;
209         }
210     }
211
212   if (!found_in_cache)
213     {
214       GtkRequisition requisition = { 0, 0 };
215       gint min_size = 0, nat_size = 0;
216       gint requisition_size;
217
218       /* Unconditional size request runs but is often unhandled. */
219       do_size_request (widget, &requisition);
220
221       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
222         {
223           requisition_size = requisition.width;
224
225           if (for_size < 0)
226             {
227               push_recursion_check (widget, orientation, for_size);
228               GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_size, &nat_size);
229               pop_recursion_check (widget, orientation);
230             }
231           else
232             {
233               int ignored_position = 0;
234               int natural_height;
235
236               /* Pull the base natural height from the cache as it's needed to adjust 
237                * the proposed 'for_size' */
238               gtk_widget_get_preferred_height (widget, NULL, &natural_height);
239
240               /* convert for_size to unadjusted height (for_size is a proposed allocation) */
241               GTK_WIDGET_GET_CLASS (widget)->adjust_size_allocation (widget,
242                                                                      GTK_ORIENTATION_VERTICAL,
243                                                                      &natural_height,
244                                                                      &ignored_position,
245                                                                      &for_size);
246
247               push_recursion_check (widget, orientation, for_size);
248               GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget, for_size,
249                                                                               &min_size, &nat_size);
250               pop_recursion_check (widget, orientation);
251             }
252         }
253       else
254         {
255           requisition_size = requisition.height;
256
257           if (for_size < 0)
258             {
259               push_recursion_check (widget, orientation, for_size);
260               GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_size, &nat_size);
261               pop_recursion_check (widget, orientation);
262             }
263           else
264             {
265               int ignored_position = 0;
266               int natural_width;
267
268               /* Pull the base natural width from the cache as it's needed to adjust 
269                * the proposed 'for_size' */
270               gtk_widget_get_preferred_width (widget, NULL, &natural_width);
271
272               /* convert for_size to unadjusted width (for_size is a proposed allocation) */
273               GTK_WIDGET_GET_CLASS (widget)->adjust_size_allocation (widget,
274                                                                      GTK_ORIENTATION_HORIZONTAL,
275                                                                      &natural_width,
276                                                                      &ignored_position,
277                                                                      &for_size);
278
279               push_recursion_check (widget, orientation, for_size);
280               GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, for_size,
281                                                                               &min_size, &nat_size);
282               pop_recursion_check (widget, orientation);
283             }
284         }
285
286       if (min_size > nat_size)
287         {
288           g_warning ("%s %p reported min size %d and natural size %d; natural size must be >= min size",
289                      G_OBJECT_TYPE_NAME (widget), widget, min_size, nat_size);
290         }
291
292       /* Support for dangling "size-request" signal implementations on
293        * legacy widgets
294        */
295       min_size = MAX (min_size, requisition_size);
296       nat_size = MAX (nat_size, requisition_size);
297
298       cached_size->minimum_size = min_size;
299       cached_size->natural_size = nat_size;
300       cached_size->for_size     = for_size;
301
302       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
303           _gtk_widget_set_width_request_needed (widget, FALSE);
304       else
305           _gtk_widget_set_height_request_needed (widget, FALSE);
306
307       adjusted_min = cached_size->minimum_size;
308       adjusted_natural = cached_size->natural_size;
309       GTK_WIDGET_GET_CLASS (widget)->adjust_size_request (widget,
310                                                           orientation == GTK_SIZE_GROUP_HORIZONTAL ?
311                                                           GTK_ORIENTATION_HORIZONTAL :
312                                                           GTK_ORIENTATION_VERTICAL,
313                                                           &adjusted_min,
314                                                           &adjusted_natural);
315
316       if (adjusted_min < cached_size->minimum_size ||
317           adjusted_natural < cached_size->natural_size)
318         {
319           g_warning ("%s %p adjusted size %s min %d natural %d must not decrease below min %d natural %d",
320                      G_OBJECT_TYPE_NAME (widget), widget,
321                      orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal",
322                      adjusted_min, adjusted_natural,
323                      cached_size->minimum_size, cached_size->natural_size);
324           /* don't use the adjustment */
325         }
326       else if (adjusted_min > adjusted_natural)
327         {
328           g_warning ("%s %p adjusted size %s min %d natural %d original min %d natural %d has min greater than natural",
329                      G_OBJECT_TYPE_NAME (widget), widget,
330                      orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal",
331                      adjusted_min, adjusted_natural,
332                      cached_size->minimum_size, cached_size->natural_size);
333           /* don't use the adjustment */
334         }
335       else
336         {
337           /* adjustment looks good */
338           cached_size->minimum_size = adjusted_min;
339           cached_size->natural_size = adjusted_natural;
340         }
341
342       /* Update size-groups with our request and update our cached requests 
343        * with the size-group values in a single pass.
344        */
345       _gtk_size_group_bump_requisition (widget,
346                                         orientation,
347                                         &cached_size->minimum_size,
348                                         &cached_size->natural_size);
349     }
350
351   if (minimum_size)
352     *minimum_size = cached_size->minimum_size;
353
354   if (natural_size)
355     *natural_size = cached_size->natural_size;
356
357   g_assert (cached_size->minimum_size <= cached_size->natural_size);
358
359   GTK_NOTE (SIZE_REQUEST,
360             g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d (hit cache: %s)\n",
361                      widget, G_OBJECT_TYPE_NAME (widget),
362                      orientation == GTK_SIZE_GROUP_HORIZONTAL ?
363                      "width for height" : "height for width" ,
364                      for_size,
365                      cached_size->minimum_size,
366                      cached_size->natural_size,
367                      found_in_cache ? "yes" : "no"));
368
369 }
370
371 /**
372  * gtk_widget_get_request_mode:
373  * @widget: a #GtkWidget instance
374  *
375  * Gets whether the widget prefers a height-for-width layout
376  * or a width-for-height layout.
377  *
378  * <note><para>#GtkBin widgets generally propagate the preference of
379  * their child, container widgets need to request something either in
380  * context of their children or in context of their allocation
381  * capabilities.</para></note>
382  *
383  * Returns: The #GtkSizeRequestMode preferred by @widget.
384  *
385  * Since: 3.0
386  */
387 GtkSizeRequestMode
388 gtk_widget_get_request_mode (GtkWidget *widget)
389 {
390   GtkWidgetClass *klass;
391
392   g_return_val_if_fail (GTK_IS_WIDGET (widget), GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH);
393
394   klass = GTK_WIDGET_GET_CLASS (widget);
395   if (klass->get_request_mode)
396     return klass->get_request_mode (widget);
397
398   /* By default widgets are height-for-width. */
399   return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
400 }
401
402 /**
403  * gtk_widget_get_preferred_width:
404  * @widget: a #GtkWidget instance
405  * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL
406  * @natural_width: (out) (allow-none): location to store the natural width, or %NULL
407  *
408  * Retrieves a widget's initial minimum and natural width.
409  *
410  * <note><para>This call is specific to height-for-width
411  * requests.</para></note>
412  *
413  * The returned request will be modified by the
414  * GtkWidgetClass::adjust_size_request virtual method and by any
415  * #GtkSizeGroup that have been applied. That is, the returned request
416  * is the one that should be used for layout, not necessarily the one
417  * returned by the widget itself.
418  *
419  * Since: 3.0
420  */
421 void
422 gtk_widget_get_preferred_width (GtkWidget *widget,
423                                 gint      *minimum_width,
424                                 gint      *natural_width)
425 {
426   compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
427                                 -1, minimum_width, natural_width);
428 }
429
430
431 /**
432  * gtk_widget_get_preferred_height:
433  * @widget: a #GtkWidget instance
434  * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL
435  * @natural_height: (out) (allow-none): location to store the natural height, or %NULL
436  *
437  * Retrieves a widget's initial minimum and natural height.
438  *
439  * <note><para>This call is specific to width-for-height requests.</para></note>
440  *
441  * The returned request will be modified by the
442  * GtkWidgetClass::adjust_size_request virtual method and by any
443  * #GtkSizeGroup that have been applied. That is, the returned request
444  * is the one that should be used for layout, not necessarily the one
445  * returned by the widget itself.
446  *
447  * Since: 3.0
448  */
449 void
450 gtk_widget_get_preferred_height (GtkWidget *widget,
451                                  gint      *minimum_height,
452                                  gint      *natural_height)
453 {
454   compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
455                                 -1, minimum_height, natural_height);
456 }
457
458
459
460 /**
461  * gtk_widget_get_preferred_width_for_height:
462  * @widget: a #GtkWidget instance
463  * @height: the height which is available for allocation
464  * @minimum_width: (out) (allow-none): location for storing the minimum width, or %NULL
465  * @natural_width: (out) (allow-none): location for storing the natural width, or %NULL
466  *
467  * Retrieves a widget's minimum and natural width if it would be given
468  * the specified @height.
469  *
470  * The returned request will be modified by the
471  * GtkWidgetClass::adjust_size_request virtual method and by any
472  * #GtkSizeGroup that have been applied. That is, the returned request
473  * is the one that should be used for layout, not necessarily the one
474  * returned by the widget itself.
475  *
476  * Since: 3.0
477  */
478 void
479 gtk_widget_get_preferred_width_for_height (GtkWidget *widget,
480                                            gint       height,
481                                            gint      *minimum_width,
482                                            gint      *natural_width)
483 {
484   compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
485                                 height, minimum_width, natural_width);
486 }
487
488 /**
489  * gtk_widget_get_preferred_height_for_width:
490  * @widget: a #GtkWidget instance
491  * @width: the width which is available for allocation
492  * @minimum_height: (out) (allow-none): location for storing the minimum height, or %NULL
493  * @natural_height: (out) (allow-none): location for storing the natural height, or %NULL
494  *
495  * Retrieves a widget's minimum and natural height if it would be given
496  * the specified @width.
497  *
498  * The returned request will be modified by the
499  * GtkWidgetClass::adjust_size_request virtual method and by any
500  * #GtkSizeGroup that have been applied. That is, the returned request
501  * is the one that should be used for layout, not necessarily the one
502  * returned by the widget itself.
503  *
504  * Since: 3.0
505  */
506 void
507 gtk_widget_get_preferred_height_for_width (GtkWidget *widget,
508                                            gint       width,
509                                            gint      *minimum_height,
510                                            gint      *natural_height)
511 {
512   compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
513                                 width, minimum_height, natural_height);
514 }
515
516 /**
517  * gtk_widget_get_preferred_size:
518  * @widget: a #GtkWidget instance
519  * @minimum_size: (out) (allow-none): location for storing the minimum size, or %NULL
520  * @natural_size: (out) (allow-none): location for storing the natural size, or %NULL
521  *
522  * Retrieves the minimum and natural size of a widget taking
523  * into account the widget's preference for height-for-width management.
524  *
525  * This is used to retrieve a suitable size by container widgets which do
526  * not impose any restrictions on the child placement. It can be used
527  * to deduce toplevel window and menu sizes as well as child widgets in
528  * free form containers such as GtkLayout.
529  *
530  * <note><para>Handle with care, note that the natural height of a height-for-width
531  * widget will generally be a smaller size than the minimum height, since the required
532  * height for the natural width is generally smaller than the required height for
533  * the minimum width.</para></note>
534  *
535  * Since: 3.0
536  */
537 void
538 gtk_widget_get_preferred_size (GtkWidget      *widget,
539                                GtkRequisition *minimum_size,
540                                GtkRequisition *natural_size)
541 {
542   gint min_width, nat_width;
543   gint min_height, nat_height;
544
545   g_return_if_fail (GTK_IS_WIDGET (widget));
546
547   if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
548     {
549       gtk_widget_get_preferred_width (widget, &min_width, &nat_width);
550
551       if (minimum_size)
552         {
553           minimum_size->width = min_width;
554           gtk_widget_get_preferred_height_for_width (widget, min_width,
555                                                      &minimum_size->height, NULL);
556         }
557
558       if (natural_size)
559         {
560           natural_size->width = nat_width;
561           gtk_widget_get_preferred_height_for_width (widget, nat_width,
562                                                      NULL, &natural_size->height);
563         }
564     }
565   else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT */
566     {
567       gtk_widget_get_preferred_height (widget, &min_height, &nat_height);
568
569       if (minimum_size)
570         {
571           minimum_size->height = min_height;
572           gtk_widget_get_preferred_width_for_height (widget, min_height,
573                                                      &minimum_size->width, NULL);
574         }
575
576       if (natural_size)
577         {
578           natural_size->height = nat_height;
579           gtk_widget_get_preferred_width_for_height (widget, nat_height,
580                                                      NULL, &natural_size->width);
581         }
582     }
583 }
584
585
586 static gint
587 compare_gap (gconstpointer p1,
588              gconstpointer p2,
589              gpointer      data)
590 {
591   GtkRequestedSize *sizes = data;
592   const guint *c1 = p1;
593   const guint *c2 = p2;
594
595   const gint d1 = MAX (sizes[*c1].natural_size -
596                        sizes[*c1].minimum_size,
597                        0);
598   const gint d2 = MAX (sizes[*c2].natural_size -
599                        sizes[*c2].minimum_size,
600                        0);
601
602   gint delta = (d2 - d1);
603
604   if (0 == delta)
605     delta = (*c2 - *c1);
606
607   return delta;
608 }
609
610 /**
611  * gtk_distribute_natural_allocation: 
612  * @extra_space: Extra space to redistribute among children after subtracting
613  *               minimum sizes and any child padding from the overall allocation
614  * @n_requested_sizes: Number of requests to fit into the allocation
615  * @sizes: An array of structs with a client pointer and a minimum/natural size
616  *         in the orientation of the allocation.
617  *
618  * Distributes @extra_space to child @sizes by bringing up smaller
619  * children up to natural size first.
620  *
621  * The remaining space will be added to the @minimum_size member of the
622  * GtkRequestedSize struct. If all sizes reach their natural size then
623  * the remaining space is returned.
624  *
625  * Returns: The remainder of @extra_space after redistributing space
626  * to @sizes.
627  */
628 gint 
629 gtk_distribute_natural_allocation (gint              extra_space,
630                                    guint             n_requested_sizes,
631                                    GtkRequestedSize *sizes)
632 {
633   guint *spreading;
634   gint   i;
635
636   g_return_val_if_fail (extra_space >= 0, 0);
637
638   spreading = g_newa (guint, n_requested_sizes);
639
640   for (i = 0; i < n_requested_sizes; i++)
641     spreading[i] = i;
642
643   /* Distribute the container's extra space c_gap. We want to assign
644    * this space such that the sum of extra space assigned to children
645    * (c^i_gap) is equal to c_cap. The case that there's not enough
646    * space for all children to take their natural size needs some
647    * attention. The goals we want to achieve are:
648    *
649    *   a) Maximize number of children taking their natural size.
650    *   b) The allocated size of children should be a continuous
651    *   function of c_gap.  That is, increasing the container size by
652    *   one pixel should never make drastic changes in the distribution.
653    *   c) If child i takes its natural size and child j doesn't,
654    *   child j should have received at least as much gap as child i.
655    *
656    * The following code distributes the additional space by following
657    * these rules.
658    */
659   
660   /* Sort descending by gap and position. */
661   g_qsort_with_data (spreading,
662                      n_requested_sizes, sizeof (guint),
663                      compare_gap, sizes);
664   
665   /* Distribute available space.
666    * This master piece of a loop was conceived by Behdad Esfahbod.
667    */
668   for (i = n_requested_sizes - 1; extra_space > 0 && i >= 0; --i)
669     {
670       /* Divide remaining space by number of remaining children.
671        * Sort order and reducing remaining space by assigned space
672        * ensures that space is distributed equally.
673        */
674       gint glue = (extra_space + i) / (i + 1);
675       gint gap = sizes[(spreading[i])].natural_size
676         - sizes[(spreading[i])].minimum_size;
677       
678       gint extra = MIN (glue, gap);
679       
680       sizes[spreading[i]].minimum_size += extra;
681       
682       extra_space -= extra;
683     }
684
685   return extra_space;
686 }
687