]> Pileus Git - ~andy/gtk/blob - gtk/gtksizerequest.c
13c7b34f7bb2c296b1563ca9b4ee15872294ab5c
[~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 #include "deprecated/gtkstyle.h"
34
35
36 #ifndef G_DISABLE_CHECKS
37 static GQuark recursion_check_quark = 0;
38 #endif /* G_DISABLE_CHECKS */
39
40 static void
41 push_recursion_check (GtkWidget       *widget,
42                       GtkSizeGroupMode orientation,
43                       gint             for_size)
44 {
45 #ifndef G_DISABLE_CHECKS
46   const char *previous_method;
47   const char *method;
48
49   if (recursion_check_quark == 0)
50     recursion_check_quark = g_quark_from_static_string ("gtk-size-request-in-progress");
51
52   previous_method = g_object_get_qdata (G_OBJECT (widget), recursion_check_quark);
53
54   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
55     {
56       method = for_size < 0 ? "get_width" : "get_width_for_height";
57     }
58   else
59     {
60       method = for_size < 0 ? "get_height" : "get_height_for_width";
61     }
62
63   if (previous_method != NULL)
64     {
65       g_warning ("%s %p: widget tried to gtk_widget_%s inside "
66                  " GtkWidget     ::%s implementation. "
67                  "Should just invoke GTK_WIDGET_GET_CLASS(widget)->%s "
68                  "directly rather than using gtk_widget_%s",
69                  G_OBJECT_TYPE_NAME (widget), widget,
70                  method, previous_method,
71                  method, method);
72     }
73
74   g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, (char*) method);
75 #endif /* G_DISABLE_CHECKS */
76 }
77
78 static void
79 pop_recursion_check (GtkWidget       *widget,
80                      GtkSizeGroupMode orientation)
81 {
82 #ifndef G_DISABLE_CHECKS
83   g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, NULL);
84 #endif
85 }
86
87
88 static void
89 clear_cache (SizeRequestCache   *cache,
90              GtkSizeGroupMode    orientation)
91 {
92   SizeRequest **sizes;
93   gint          i;
94
95   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
96     {
97       sizes = cache->widths;
98
99       cache->widths            = NULL;
100       cache->cached_widths     = 0;
101       cache->last_cached_width = 0;
102       cache->cached_base_width = FALSE;
103     }
104   else
105     {
106       sizes = cache->heights;
107
108       cache->heights            = NULL;
109       cache->cached_heights     = 0;
110       cache->last_cached_height = 0; 
111       cache->cached_base_height = FALSE;
112    }
113
114   if (sizes)
115     {
116       for (i = 0; i < GTK_SIZE_REQUEST_CACHED_SIZES && sizes[i] != NULL; i++)
117         g_slice_free (SizeRequest, sizes[i]);
118       
119       g_slice_free1 (sizeof (SizeRequest *) * GTK_SIZE_REQUEST_CACHED_SIZES, sizes);
120     }
121 }
122
123 void
124 _gtk_widget_free_cached_sizes (GtkWidget *widget)
125 {
126   SizeRequestCache   *cache;
127
128   cache = _gtk_widget_peek_request_cache (widget);
129
130   clear_cache (cache, GTK_SIZE_GROUP_HORIZONTAL);
131   clear_cache (cache, GTK_SIZE_GROUP_VERTICAL);
132 }
133
134 /* This function checks if 'request_needed' flag is present
135  * and resets the cache state if a request is needed for
136  * a given orientation.
137  */
138 static SizeRequestCache *
139 init_cache (GtkWidget        *widget)
140 {
141   SizeRequestCache *cache;
142
143   cache = _gtk_widget_peek_request_cache (widget);
144
145   if (_gtk_widget_get_width_request_needed (widget))
146     clear_cache (cache, GTK_SIZE_GROUP_HORIZONTAL);
147   
148   if (_gtk_widget_get_height_request_needed (widget))
149     clear_cache (cache, GTK_SIZE_GROUP_VERTICAL);
150
151   return cache;
152 }
153
154 /* looks for a cached size request for this for_size. If not
155  * found, returns the oldest entry so it can be overwritten
156  *
157  * Note that this caching code was originally derived from
158  * the Clutter toolkit but has evolved for other GTK+ requirements.
159  */
160 static gboolean
161 get_cached_size (GtkWidget         *widget,
162                  GtkSizeGroupMode   orientation,
163                  gint               for_size,
164                  CachedSize       **result)
165 {
166   SizeRequestCache  *cache;
167   SizeRequest      **cached_sizes;
168   guint              i, n_sizes;
169
170   cache = init_cache (widget);
171
172   if (for_size < 0)
173     {
174       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
175         {
176           *result = &cache->cached_width;
177           return cache->cached_base_width;
178         }
179       else
180         {
181           *result = &cache->cached_height;
182           return cache->cached_base_height;
183         }
184     }
185
186   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
187     {
188       cached_sizes = cache->widths;
189       n_sizes      = cache->cached_widths;
190     }
191   else
192     {
193       cached_sizes = cache->heights;
194       n_sizes      = cache->cached_heights;
195     }
196
197   /* Search for an already cached size */
198   for (i = 0; i < n_sizes; i++)
199     {
200       if (cached_sizes[i]->lower_for_size <= for_size &&
201           cached_sizes[i]->upper_for_size >= for_size)
202         {
203           *result = &cached_sizes[i]->cached_size;
204           return TRUE;
205         }
206     }
207
208   return FALSE;
209 }
210
211 static void
212 commit_cached_size (GtkWidget         *widget,
213                     GtkSizeGroupMode   orientation,
214                     gint               for_size,
215                     gint               minimum_size,
216                     gint               natural_size)
217 {
218   SizeRequestCache  *cache;
219   SizeRequest      **cached_sizes;
220   guint              i, n_sizes;
221
222   cache = _gtk_widget_peek_request_cache (widget);
223
224   /* First handle caching of the base requests */
225   if (for_size < 0)
226     {
227       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
228         {
229           cache->cached_width.minimum_size = minimum_size;
230           cache->cached_width.natural_size = natural_size;
231           cache->cached_base_width = TRUE;
232         }
233       else
234         {
235           cache->cached_height.minimum_size = minimum_size;
236           cache->cached_height.natural_size = natural_size;
237           cache->cached_base_height = TRUE;
238         }
239       return;
240     }
241
242   /* Check if the minimum_size and natural_size is already
243    * in the cache and if this result can be used to extend
244    * that cache entry 
245    */
246   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
247     {
248       cached_sizes = cache->widths;
249       n_sizes = cache->cached_widths;
250     }
251   else
252     {
253       cached_sizes = cache->heights;
254       n_sizes = cache->cached_heights;
255     }
256
257   for (i = 0; i < n_sizes; i++)
258     {
259       if (cached_sizes[i]->cached_size.minimum_size == minimum_size &&
260           cached_sizes[i]->cached_size.natural_size == natural_size)
261         {
262           cached_sizes[i]->lower_for_size = MIN (cached_sizes[i]->lower_for_size, for_size);
263           cached_sizes[i]->upper_for_size = MAX (cached_sizes[i]->upper_for_size, for_size);
264           return;
265         }
266     }
267
268   /* If not found, pull a new size from the cache, the returned size cache
269    * will immediately be used to cache the new computed size so we go ahead
270    * and increment the last_cached_width/height right away */
271   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
272     {
273       if (cache->cached_widths < GTK_SIZE_REQUEST_CACHED_SIZES)
274         {
275           cache->cached_widths++;
276           cache->last_cached_width = cache->cached_widths - 1;
277         }
278       else
279         {
280           if (++cache->last_cached_width == GTK_SIZE_REQUEST_CACHED_SIZES)
281             cache->last_cached_width = 0;
282         }
283
284       if (!cache->widths)
285         cache->widths = g_slice_alloc0 (sizeof (SizeRequest *) * GTK_SIZE_REQUEST_CACHED_SIZES);
286
287       if (!cache->widths[cache->last_cached_width])
288         cache->widths[cache->last_cached_width] = g_slice_new (SizeRequest);
289
290       cache->widths[cache->last_cached_width]->lower_for_size = for_size;
291       cache->widths[cache->last_cached_width]->upper_for_size = for_size;
292       cache->widths[cache->last_cached_width]->cached_size.minimum_size = minimum_size;
293       cache->widths[cache->last_cached_width]->cached_size.natural_size = natural_size;
294     }
295   else /* GTK_SIZE_GROUP_VERTICAL */
296     {
297       if (cache->cached_heights < GTK_SIZE_REQUEST_CACHED_SIZES)
298         {
299           cache->cached_heights++;
300           cache->last_cached_height = cache->cached_heights - 1;
301         }
302       else
303         {
304           if (++cache->last_cached_height == GTK_SIZE_REQUEST_CACHED_SIZES)
305             cache->last_cached_height = 0;
306         }
307
308       if (!cache->heights)
309         cache->heights = g_slice_alloc0 (sizeof (SizeRequest *) * GTK_SIZE_REQUEST_CACHED_SIZES);
310
311       if (!cache->heights[cache->last_cached_height])
312         cache->heights[cache->last_cached_height] = g_slice_new (SizeRequest);
313
314       cache->heights[cache->last_cached_height]->lower_for_size = for_size;
315       cache->heights[cache->last_cached_height]->upper_for_size = for_size;
316       cache->heights[cache->last_cached_height]->cached_size.minimum_size = minimum_size;
317       cache->heights[cache->last_cached_height]->cached_size.natural_size = natural_size;
318     }
319 }
320
321 /* This is the main function that checks for a cached size and
322  * possibly queries the widget class to compute the size if it's
323  * not cached. If the for_size here is -1, then get_preferred_width()
324  * or get_preferred_height() will be used.
325  */
326 static void
327 compute_size_for_orientation (GtkWidget         *widget,
328                               GtkSizeGroupMode   orientation,
329                               gint               for_size,
330                               gint              *minimum_size,
331                               gint              *natural_size)
332 {
333   CachedSize       *cached_size;
334   gboolean          found_in_cache = FALSE;
335   gint              min_size = 0;
336   gint              nat_size = 0;
337
338   found_in_cache = get_cached_size (widget, orientation, for_size, &cached_size);
339
340   if (!found_in_cache)
341     {
342       gint adjusted_min, adjusted_natural, adjusted_for_size = for_size;
343
344       gtk_widget_ensure_style (widget);
345
346       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
347         {
348           if (for_size < 0)
349             {
350               push_recursion_check (widget, orientation, for_size);
351               GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_size, &nat_size);
352               pop_recursion_check (widget, orientation);
353             }
354           else
355             {
356               gint ignored_position = 0;
357               gint minimum_height;
358               gint natural_height;
359
360               /* Pull the base natural height from the cache as it's needed to adjust
361                * the proposed 'for_size' */
362               gtk_widget_get_preferred_height (widget, &minimum_height, &natural_height);
363
364               /* convert for_size to unadjusted height (for_size is a proposed allocation) */
365               GTK_WIDGET_GET_CLASS (widget)->adjust_size_allocation (widget,
366                                                                      GTK_ORIENTATION_VERTICAL,
367                                                                      &minimum_height,
368                                                                      &natural_height,
369                                                                      &ignored_position,
370                                                                      &adjusted_for_size);
371
372               push_recursion_check (widget, orientation, for_size);
373               GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget, 
374                                                                              MAX (adjusted_for_size, minimum_height),
375                                                                              &min_size, &nat_size);
376               pop_recursion_check (widget, orientation);
377             }
378         }
379       else
380         {
381           if (for_size < 0)
382             {
383               push_recursion_check (widget, orientation, for_size);
384               GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_size, &nat_size);
385               pop_recursion_check (widget, orientation);
386             }
387           else
388             {
389               gint ignored_position = 0;
390               gint minimum_width;
391               gint natural_width;
392
393               /* Pull the base natural width from the cache as it's needed to adjust
394                * the proposed 'for_size' */
395               gtk_widget_get_preferred_width (widget, &minimum_width, &natural_width);
396
397               /* convert for_size to unadjusted width (for_size is a proposed allocation) */
398               GTK_WIDGET_GET_CLASS (widget)->adjust_size_allocation (widget,
399                                                                      GTK_ORIENTATION_HORIZONTAL,
400                                                                      &minimum_width,
401                                                                      &natural_width,
402                                                                      &ignored_position,
403                                                                      &adjusted_for_size);
404
405               push_recursion_check (widget, orientation, for_size);
406               GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, 
407                                                                              MAX (adjusted_for_size, minimum_width),
408                                                                              &min_size, &nat_size);
409               pop_recursion_check (widget, orientation);
410             }
411         }
412
413       if (min_size > nat_size)
414         {
415           g_warning ("%s %p reported min size %d and natural size %d; natural size must be >= min size",
416                      G_OBJECT_TYPE_NAME (widget), widget, min_size, nat_size);
417         }
418
419       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
420           _gtk_widget_set_width_request_needed (widget, FALSE);
421       else
422           _gtk_widget_set_height_request_needed (widget, FALSE);
423
424       adjusted_min     = min_size;
425       adjusted_natural = nat_size;
426       GTK_WIDGET_GET_CLASS (widget)->adjust_size_request (widget,
427                                                           orientation == GTK_SIZE_GROUP_HORIZONTAL ?
428                                                           GTK_ORIENTATION_HORIZONTAL :
429                                                           GTK_ORIENTATION_VERTICAL,
430                                                           &adjusted_min,
431                                                           &adjusted_natural);
432
433       if (adjusted_min < min_size ||
434           adjusted_natural < nat_size)
435         {
436           g_warning ("%s %p adjusted size %s min %d natural %d must not decrease below min %d natural %d",
437                      G_OBJECT_TYPE_NAME (widget), widget,
438                      orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal",
439                      adjusted_min, adjusted_natural,
440                      min_size, nat_size);
441           /* don't use the adjustment */
442         }
443       else if (adjusted_min > adjusted_natural)
444         {
445           g_warning ("%s %p adjusted size %s min %d natural %d original min %d natural %d has min greater than natural",
446                      G_OBJECT_TYPE_NAME (widget), widget,
447                      orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal",
448                      adjusted_min, adjusted_natural,
449                      min_size, nat_size);
450           /* don't use the adjustment */
451         }
452       else
453         {
454           /* adjustment looks good */
455           min_size = adjusted_min;
456           nat_size = adjusted_natural;
457         }
458
459       /* Update size-groups with our request and update our cached requests
460        * with the size-group values in a single pass.
461        */
462       _gtk_size_group_bump_requisition (widget,
463                                         orientation,
464                                         &min_size,
465                                         &nat_size);
466
467       commit_cached_size (widget, orientation, for_size, min_size, nat_size);
468     }
469   else
470     {
471       min_size = cached_size->minimum_size;
472       nat_size = cached_size->natural_size;
473     }
474
475   if (minimum_size)
476     *minimum_size = min_size;
477
478   if (natural_size)
479     *natural_size = nat_size;
480
481   g_assert (min_size <= nat_size);
482
483   GTK_NOTE (SIZE_REQUEST,
484             g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d (hit cache: %s)\n",
485                      widget, G_OBJECT_TYPE_NAME (widget),
486                      orientation == GTK_SIZE_GROUP_HORIZONTAL ?
487                      "width for height" : "height for width" ,
488                      for_size, min_size, nat_size,
489                      found_in_cache ? "yes" : "no"));
490
491 }
492
493 /**
494  * gtk_widget_get_request_mode:
495  * @widget: a #GtkWidget instance
496  *
497  * Gets whether the widget prefers a height-for-width layout
498  * or a width-for-height layout.
499  *
500  * <note><para>#GtkBin widgets generally propagate the preference of
501  * their child, container widgets need to request something either in
502  * context of their children or in context of their allocation
503  * capabilities.</para></note>
504  *
505  * Returns: The #GtkSizeRequestMode preferred by @widget.
506  *
507  * Since: 3.0
508  */
509 GtkSizeRequestMode
510 gtk_widget_get_request_mode (GtkWidget *widget)
511 {
512   g_return_val_if_fail (GTK_IS_WIDGET (widget), GTK_SIZE_REQUEST_CONSTANT_SIZE);
513
514   return GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget);
515 }
516
517 /**
518  * gtk_widget_get_preferred_width:
519  * @widget: a #GtkWidget instance
520  * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL
521  * @natural_width: (out) (allow-none): location to store the natural width, or %NULL
522  *
523  * Retrieves a widget's initial minimum and natural width.
524  *
525  * <note><para>This call is specific to height-for-width
526  * requests.</para></note>
527  *
528  * The returned request will be modified by the
529  * GtkWidgetClass::adjust_size_request virtual method and by any
530  * #GtkSizeGroup<!-- -->s that have been applied. That is, the returned request
531  * is the one that should be used for layout, not necessarily the one
532  * returned by the widget itself.
533  *
534  * Since: 3.0
535  */
536 void
537 gtk_widget_get_preferred_width (GtkWidget *widget,
538                                 gint      *minimum_width,
539                                 gint      *natural_width)
540 {
541   g_return_if_fail (GTK_IS_WIDGET (widget));
542   g_return_if_fail (minimum_width != NULL || natural_width != NULL);
543
544   compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
545                                 -1, minimum_width, natural_width);
546 }
547
548
549 /**
550  * gtk_widget_get_preferred_height:
551  * @widget: a #GtkWidget instance
552  * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL
553  * @natural_height: (out) (allow-none): location to store the natural height, or %NULL
554  *
555  * Retrieves a widget's initial minimum and natural height.
556  *
557  * <note><para>This call is specific to width-for-height requests.</para></note>
558  *
559  * The returned request will be modified by the
560  * GtkWidgetClass::adjust_size_request virtual method and by any
561  * #GtkSizeGroup<!-- -->s that have been applied. That is, the returned request
562  * is the one that should be used for layout, not necessarily the one
563  * returned by the widget itself.
564  *
565  * Since: 3.0
566  */
567 void
568 gtk_widget_get_preferred_height (GtkWidget *widget,
569                                  gint      *minimum_height,
570                                  gint      *natural_height)
571 {
572   g_return_if_fail (GTK_IS_WIDGET (widget));
573   g_return_if_fail (minimum_height != NULL || natural_height != NULL);
574
575   compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
576                                 -1, minimum_height, natural_height);
577 }
578
579
580
581 /**
582  * gtk_widget_get_preferred_width_for_height:
583  * @widget: a #GtkWidget instance
584  * @height: the height which is available for allocation
585  * @minimum_width: (out) (allow-none): location for storing the minimum width, or %NULL
586  * @natural_width: (out) (allow-none): location for storing the natural width, or %NULL
587  *
588  * Retrieves a widget's minimum and natural width if it would be given
589  * the specified @height.
590  *
591  * The returned request will be modified by the
592  * GtkWidgetClass::adjust_size_request virtual method and by any
593  * #GtkSizeGroup<!-- -->s that have been applied. That is, the returned request
594  * is the one that should be used for layout, not necessarily the one
595  * returned by the widget itself.
596  *
597  * Since: 3.0
598  */
599 void
600 gtk_widget_get_preferred_width_for_height (GtkWidget *widget,
601                                            gint       height,
602                                            gint      *minimum_width,
603                                            gint      *natural_width)
604 {
605   g_return_if_fail (GTK_IS_WIDGET (widget));
606   g_return_if_fail (minimum_width != NULL || natural_width != NULL);
607   g_return_if_fail (height >= 0);
608
609   if (GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget) == GTK_SIZE_REQUEST_CONSTANT_SIZE)
610     compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
611                                   -1, minimum_width, natural_width);
612   else
613     compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
614                                   height, minimum_width, natural_width);
615 }
616
617 /**
618  * gtk_widget_get_preferred_height_for_width:
619  * @widget: a #GtkWidget instance
620  * @width: the width which is available for allocation
621  * @minimum_height: (out) (allow-none): location for storing the minimum height, or %NULL
622  * @natural_height: (out) (allow-none): location for storing the natural height, or %NULL
623  *
624  * Retrieves a widget's minimum and natural height if it would be given
625  * the specified @width.
626  *
627  * The returned request will be modified by the
628  * GtkWidgetClass::adjust_size_request virtual method and by any
629  * #GtkSizeGroup<!-- -->s that have been applied. That is, the returned request
630  * is the one that should be used for layout, not necessarily the one
631  * returned by the widget itself.
632  *
633  * Since: 3.0
634  */
635 void
636 gtk_widget_get_preferred_height_for_width (GtkWidget *widget,
637                                            gint       width,
638                                            gint      *minimum_height,
639                                            gint      *natural_height)
640 {
641   g_return_if_fail (GTK_IS_WIDGET (widget));
642   g_return_if_fail (minimum_height != NULL || natural_height != NULL);
643   g_return_if_fail (width >= 0);
644
645   if (GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget) == GTK_SIZE_REQUEST_CONSTANT_SIZE)
646     compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
647                                   -1, minimum_height, natural_height);
648   else
649     compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
650                                   width, minimum_height, natural_height);
651 }
652
653 /**
654  * gtk_widget_get_preferred_size:
655  * @widget: a #GtkWidget instance
656  * @minimum_size: (out) (allow-none): location for storing the minimum size, or %NULL
657  * @natural_size: (out) (allow-none): location for storing the natural size, or %NULL
658  *
659  * Retrieves the minimum and natural size of a widget, taking
660  * into account the widget's preference for height-for-width management.
661  *
662  * This is used to retrieve a suitable size by container widgets which do
663  * not impose any restrictions on the child placement. It can be used
664  * to deduce toplevel window and menu sizes as well as child widgets in
665  * free-form containers such as GtkLayout.
666  *
667  * <note><para>Handle with care. Note that the natural height of a height-for-width
668  * widget will generally be a smaller size than the minimum height, since the required
669  * height for the natural width is generally smaller than the required height for
670  * the minimum width.</para></note>
671  *
672  * Since: 3.0
673  */
674 void
675 gtk_widget_get_preferred_size (GtkWidget      *widget,
676                                GtkRequisition *minimum_size,
677                                GtkRequisition *natural_size)
678 {
679   gint min_width, nat_width;
680   gint min_height, nat_height;
681
682   g_return_if_fail (GTK_IS_WIDGET (widget));
683
684   if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
685     {
686       gtk_widget_get_preferred_width (widget, &min_width, &nat_width);
687
688       if (minimum_size)
689         {
690           minimum_size->width = min_width;
691           gtk_widget_get_preferred_height_for_width (widget, min_width,
692                                                      &minimum_size->height, NULL);
693         }
694
695       if (natural_size)
696         {
697           natural_size->width = nat_width;
698           gtk_widget_get_preferred_height_for_width (widget, nat_width,
699                                                      NULL, &natural_size->height);
700         }
701     }
702   else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT or CONSTANT_SIZE */
703     {
704       gtk_widget_get_preferred_height (widget, &min_height, &nat_height);
705
706       if (minimum_size)
707         {
708           minimum_size->height = min_height;
709           gtk_widget_get_preferred_width_for_height (widget, min_height,
710                                                      &minimum_size->width, NULL);
711         }
712
713       if (natural_size)
714         {
715           natural_size->height = nat_height;
716           gtk_widget_get_preferred_width_for_height (widget, nat_height,
717                                                      NULL, &natural_size->width);
718         }
719     }
720 }
721
722
723 static gint
724 compare_gap (gconstpointer p1,
725              gconstpointer p2,
726              gpointer      data)
727 {
728   GtkRequestedSize *sizes = data;
729   const guint *c1 = p1;
730   const guint *c2 = p2;
731
732   const gint d1 = MAX (sizes[*c1].natural_size -
733                        sizes[*c1].minimum_size,
734                        0);
735   const gint d2 = MAX (sizes[*c2].natural_size -
736                        sizes[*c2].minimum_size,
737                        0);
738
739   gint delta = (d2 - d1);
740
741   if (0 == delta)
742     delta = (*c2 - *c1);
743
744   return delta;
745 }
746
747 /**
748  * gtk_distribute_natural_allocation:
749  * @extra_space: Extra space to redistribute among children after subtracting
750  *               minimum sizes and any child padding from the overall allocation
751  * @n_requested_sizes: Number of requests to fit into the allocation
752  * @sizes: An array of structs with a client pointer and a minimum/natural size
753  *         in the orientation of the allocation.
754  *
755  * Distributes @extra_space to child @sizes by bringing smaller
756  * children up to natural size first.
757  *
758  * The remaining space will be added to the @minimum_size member of the
759  * GtkRequestedSize struct. If all sizes reach their natural size then
760  * the remaining space is returned.
761  *
762  * Returns: The remainder of @extra_space after redistributing space
763  * to @sizes.
764  */
765 gint
766 gtk_distribute_natural_allocation (gint              extra_space,
767                                    guint             n_requested_sizes,
768                                    GtkRequestedSize *sizes)
769 {
770   guint *spreading;
771   gint   i;
772
773   g_return_val_if_fail (extra_space >= 0, 0);
774
775   spreading = g_newa (guint, n_requested_sizes);
776
777   for (i = 0; i < n_requested_sizes; i++)
778     spreading[i] = i;
779
780   /* Distribute the container's extra space c_gap. We want to assign
781    * this space such that the sum of extra space assigned to children
782    * (c^i_gap) is equal to c_cap. The case that there's not enough
783    * space for all children to take their natural size needs some
784    * attention. The goals we want to achieve are:
785    *
786    *   a) Maximize number of children taking their natural size.
787    *   b) The allocated size of children should be a continuous
788    *   function of c_gap.  That is, increasing the container size by
789    *   one pixel should never make drastic changes in the distribution.
790    *   c) If child i takes its natural size and child j doesn't,
791    *   child j should have received at least as much gap as child i.
792    *
793    * The following code distributes the additional space by following
794    * these rules.
795    */
796
797   /* Sort descending by gap and position. */
798   g_qsort_with_data (spreading,
799                      n_requested_sizes, sizeof (guint),
800                      compare_gap, sizes);
801
802   /* Distribute available space.
803    * This master piece of a loop was conceived by Behdad Esfahbod.
804    */
805   for (i = n_requested_sizes - 1; extra_space > 0 && i >= 0; --i)
806     {
807       /* Divide remaining space by number of remaining children.
808        * Sort order and reducing remaining space by assigned space
809        * ensures that space is distributed equally.
810        */
811       gint glue = (extra_space + i) / (i + 1);
812       gint gap = sizes[(spreading[i])].natural_size
813         - sizes[(spreading[i])].minimum_size;
814
815       gint extra = MIN (glue, gap);
816
817       sizes[spreading[i]].minimum_size += extra;
818
819       extra_space -= extra;
820     }
821
822   return extra_space;
823 }