]> Pileus Git - ~andy/gtk/blob - gtk/gtksizerequest.c
Split off gtkprivate.h
[~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       *request,
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 (request), 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 (request), request,
151                  method, previous_method,
152                  method, method);
153     }
154
155   g_object_set_qdata (G_OBJECT (request), recursion_check_quark, (char*) method);
156 #endif /* G_DISABLE_CHECKS */
157 }
158
159 static void
160 pop_recursion_check (GtkWidget       *request,
161                      GtkSizeGroupMode orientation)
162 {
163 #ifndef G_DISABLE_CHECKS
164   g_object_set_qdata (G_OBJECT (request), recursion_check_quark, NULL);
165 #endif
166 }
167
168 static void
169 compute_size_for_orientation (GtkWidget         *request,
170                               GtkSizeGroupMode   orientation,
171                               gint               for_size,
172                               gint              *minimum_size,
173                               gint              *natural_size)
174 {
175   SizeRequestCache *cache;
176   SizeRequest      *cached_size;
177   GtkWidget        *widget;
178   gboolean          found_in_cache = FALSE;
179   int adjusted_min, adjusted_natural;
180
181   g_return_if_fail (GTK_IS_WIDGET (request));
182   g_return_if_fail (minimum_size != NULL || natural_size != NULL);
183
184   widget = GTK_WIDGET (request);
185   cache  = _gtk_widget_peek_request_cache (widget);
186
187   if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
188     {
189       cached_size = &cache->widths[0];
190
191       if (!_gtk_widget_get_width_request_needed (widget))
192         found_in_cache = get_cached_size (cache, orientation, for_size, &cached_size);
193       else
194         {
195           memset (cache->widths, 0, GTK_SIZE_REQUEST_CACHED_SIZES * sizeof (SizeRequest));
196           cache->cached_widths = 1;
197           cache->last_cached_width = 0;
198         }
199     }
200   else
201     {
202       cached_size = &cache->heights[0];
203
204       if (!_gtk_widget_get_height_request_needed (widget))
205         found_in_cache = get_cached_size (cache, orientation, for_size, &cached_size);
206       else
207         {
208           memset (cache->heights, 0, GTK_SIZE_REQUEST_CACHED_SIZES * sizeof (SizeRequest));
209           cache->cached_heights = 1;
210           cache->last_cached_height = 0;
211         }
212     }
213
214   if (!found_in_cache)
215     {
216       GtkRequisition requisition = { 0, 0 };
217       gint min_size = 0, nat_size = 0;
218       gint requisition_size;
219
220       /* Unconditional size request runs but is often unhandled. */
221       do_size_request (widget, &requisition);
222
223       push_recursion_check (request, orientation, for_size);
224       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
225         {
226           requisition_size = requisition.width;
227
228           if (for_size < 0)
229             GTK_WIDGET_GET_CLASS (request)->get_preferred_width (request, &min_size, &nat_size);
230           else
231             GTK_WIDGET_GET_CLASS (request)->get_preferred_width_for_height (request, for_size, 
232                                                                                   &min_size, &nat_size);
233         }
234       else
235         {
236           requisition_size = requisition.height;
237
238           if (for_size < 0)
239             GTK_WIDGET_GET_CLASS (request)->get_preferred_height (request, &min_size, &nat_size);
240           else
241             GTK_WIDGET_GET_CLASS (request)->get_preferred_height_for_width (request, for_size, 
242                                                                                   &min_size, &nat_size);
243         }
244       pop_recursion_check (request, orientation);
245
246       if (min_size > nat_size)
247         {
248           g_warning ("%s %p reported min size %d and natural size %d; natural size must be >= min size",
249                      G_OBJECT_TYPE_NAME (request), request, min_size, nat_size);
250         }
251
252       /* Support for dangling "size-request" signal implementations on
253        * legacy widgets
254        */
255       min_size = MAX (min_size, requisition_size);
256       nat_size = MAX (nat_size, requisition_size);
257
258       cached_size->minimum_size = min_size;
259       cached_size->natural_size = nat_size;
260       cached_size->for_size     = for_size;
261
262       if (orientation == GTK_SIZE_GROUP_HORIZONTAL)
263           _gtk_widget_set_width_request_needed (widget, FALSE);
264       else
265           _gtk_widget_set_height_request_needed (widget, FALSE);
266
267       adjusted_min = cached_size->minimum_size;
268       adjusted_natural = cached_size->natural_size;
269       GTK_WIDGET_GET_CLASS (request)->adjust_size_request (GTK_WIDGET (request),
270                                                            orientation == GTK_SIZE_GROUP_HORIZONTAL ?
271                                                            GTK_ORIENTATION_HORIZONTAL :
272                                                            GTK_ORIENTATION_VERTICAL,
273                                                            cached_size->for_size,
274                                                            &adjusted_min,
275                                                            &adjusted_natural);
276
277       if (adjusted_min < cached_size->minimum_size ||
278           adjusted_natural < cached_size->natural_size)
279         {
280           g_warning ("%s %p adjusted size %s min %d natural %d must not decrease below min %d natural %d",
281                      G_OBJECT_TYPE_NAME (request), request,
282                      orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal",
283                      adjusted_min, adjusted_natural,
284                      cached_size->minimum_size, cached_size->natural_size);
285           /* don't use the adjustment */
286         }
287       else if (adjusted_min > adjusted_natural)
288         {
289           g_warning ("%s %p adjusted size %s min %d natural %d original min %d natural %d has min greater than natural",
290                      G_OBJECT_TYPE_NAME (request), request,
291                      orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal",
292                      adjusted_min, adjusted_natural,
293                      cached_size->minimum_size, cached_size->natural_size);
294           /* don't use the adjustment */
295         }
296       else
297         {
298           /* adjustment looks good */
299           cached_size->minimum_size = adjusted_min;
300           cached_size->natural_size = adjusted_natural;
301         }
302
303       /* Update size-groups with our request and update our cached requests 
304        * with the size-group values in a single pass.
305        */
306       _gtk_size_group_bump_requisition (GTK_WIDGET (request),
307                                         orientation, 
308                                         &cached_size->minimum_size,
309                                         &cached_size->natural_size);
310     }
311
312   if (minimum_size)
313     *minimum_size = cached_size->minimum_size;
314
315   if (natural_size)
316     *natural_size = cached_size->natural_size;
317
318   g_assert (cached_size->minimum_size <= cached_size->natural_size);
319
320   GTK_NOTE (SIZE_REQUEST,
321             g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d (hit cache: %s)\n",
322                      request, G_OBJECT_TYPE_NAME (request),
323                      orientation == GTK_SIZE_GROUP_HORIZONTAL ?
324                      "width for height" : "height for width" ,
325                      for_size,
326                      cached_size->minimum_size,
327                      cached_size->natural_size,
328                      found_in_cache ? "yes" : "no"));
329
330 }
331
332 /**
333  * gtk_widget_get_request_mode:
334  * @widget: a #GtkWidget instance
335  *
336  * Gets whether the widget prefers a height-for-width layout
337  * or a width-for-height layout.
338  *
339  * <note><para>#GtkBin widgets generally propagate the preference of
340  * their child, container widgets need to request something either in
341  * context of their children or in context of their allocation
342  * capabilities.</para></note>
343  *
344  * Returns: The #GtkSizeRequestMode preferred by @widget.
345  *
346  * Since: 3.0
347  */
348 GtkSizeRequestMode
349 gtk_widget_get_request_mode (GtkWidget *widget)
350 {
351   GtkWidgetClass *klass;
352
353   g_return_val_if_fail (GTK_IS_WIDGET (widget), GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH);
354
355   klass = GTK_WIDGET_GET_CLASS (widget);
356   if (klass->get_request_mode)
357     return klass->get_request_mode (widget);
358
359   /* By default widgets are height-for-width. */
360   return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
361 }
362
363 /**
364  * gtk_widget_get_preferred_width:
365  * @widget: a #GtkWidget instance
366  * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL
367  * @natural_width: (out) (allow-none): location to store the natural width, or %NULL
368  *
369  * Retrieves a widget's initial minimum and natural width.
370  *
371  * <note><para>This call is specific to height-for-width
372  * requests.</para></note>
373  *
374  * The returned request will be modified by the
375  * GtkWidgetClass::adjust_size_request virtual method and by any
376  * #GtkSizeGroup that have been applied. That is, the returned request
377  * is the one that should be used for layout, not necessarily the one
378  * returned by the widget itself.
379  *
380  * Since: 3.0
381  */
382 void
383 gtk_widget_get_preferred_width (GtkWidget *widget,
384                                 gint      *minimum_width,
385                                 gint      *natural_width)
386 {
387   compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
388                                 -1, minimum_width, natural_width);
389 }
390
391
392 /**
393  * gtk_widget_get_preferred_height:
394  * @widget: a #GtkWidget instance
395  * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL
396  * @natural_height: (out) (allow-none): location to store the natural height, or %NULL
397  *
398  * Retrieves a widget's initial minimum and natural height.
399  *
400  * <note><para>This call is specific to width-for-height requests.</para></note>
401  *
402  * The returned request will be modified by the
403  * GtkWidgetClass::adjust_size_request virtual method and by any
404  * #GtkSizeGroup that have been applied. That is, the returned request
405  * is the one that should be used for layout, not necessarily the one
406  * returned by the widget itself.
407  *
408  * Since: 3.0
409  */
410 void
411 gtk_widget_get_preferred_height (GtkWidget *widget,
412                                  gint      *minimum_height,
413                                  gint      *natural_height)
414 {
415   compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
416                                 -1, minimum_height, natural_height);
417 }
418
419
420
421 /**
422  * gtk_widget_get_preferred_width_for_height:
423  * @widget: a #GtkWidget instance
424  * @height: the height which is available for allocation
425  * @minimum_width: (out) (allow-none): location for storing the minimum width, or %NULL
426  * @natural_width: (out) (allow-none): location for storing the natural width, or %NULL
427  *
428  * Retrieves a widget's minimum and natural width if it would be given
429  * the specified @height.
430  *
431  * The returned request will be modified by the
432  * GtkWidgetClass::adjust_size_request virtual method and by any
433  * #GtkSizeGroup that have been applied. That is, the returned request
434  * is the one that should be used for layout, not necessarily the one
435  * returned by the widget itself.
436  *
437  * Since: 3.0
438  */
439 void
440 gtk_widget_get_preferred_width_for_height (GtkWidget *widget,
441                                            gint       height,
442                                            gint      *minimum_width,
443                                            gint      *natural_width)
444 {
445   compute_size_for_orientation (widget, GTK_SIZE_GROUP_HORIZONTAL,
446                                 height, minimum_width, natural_width);
447 }
448
449 /**
450  * gtk_widget_get_preferred_height_for_width:
451  * @widget: a #GtkWidget instance
452  * @width: the width which is available for allocation
453  * @minimum_height: (out) (allow-none): location for storing the minimum height, or %NULL
454  * @natural_height: (out) (allow-none): location for storing the natural height, or %NULL
455  *
456  * Retrieves a widget's minimum and natural height if it would be given
457  * the specified @width.
458  *
459  * The returned request will be modified by the
460  * GtkWidgetClass::adjust_size_request virtual method and by any
461  * #GtkSizeGroup that have been applied. That is, the returned request
462  * is the one that should be used for layout, not necessarily the one
463  * returned by the widget itself.
464  *
465  * Since: 3.0
466  */
467 void
468 gtk_widget_get_preferred_height_for_width (GtkWidget *widget,
469                                            gint       width,
470                                            gint      *minimum_height,
471                                            gint      *natural_height)
472 {
473   compute_size_for_orientation (widget, GTK_SIZE_GROUP_VERTICAL,
474                                 width, minimum_height, natural_height);
475 }
476
477 /**
478  * gtk_widget_get_preferred_size:
479  * @widget: a #GtkWidget instance
480  * @minimum_size: (out) (allow-none): location for storing the minimum size, or %NULL
481  * @natural_size: (out) (allow-none): location for storing the natural size, or %NULL
482  *
483  * Retrieves the minimum and natural size of a widget taking
484  * into account the widget's preference for height-for-width management.
485  *
486  * This is used to retrieve a suitable size by container widgets which do
487  * not impose any restrictions on the child placement. It can be used
488  * to deduce toplevel window and menu sizes as well as child widgets in
489  * free form containers such as GtkLayout.
490  *
491  * <note><para>Handle with care, note that the natural height of a height-for-width
492  * widget will generally be a smaller size than the minimum height, since the required
493  * height for the natural width is generally smaller than the required height for
494  * the minimum width.</para></note>
495  *
496  * Since: 3.0
497  */
498 void
499 gtk_widget_get_preferred_size (GtkWidget      *widget,
500                                GtkRequisition *minimum_size,
501                                GtkRequisition *natural_size)
502 {
503   gint min_width, nat_width;
504   gint min_height, nat_height;
505
506   g_return_if_fail (GTK_IS_WIDGET (widget));
507
508   if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
509     {
510       gtk_widget_get_preferred_width (widget, &min_width, &nat_width);
511
512       if (minimum_size)
513         {
514           minimum_size->width = min_width;
515           gtk_widget_get_preferred_height_for_width (widget, min_width,
516                                                      &minimum_size->height, NULL);
517         }
518
519       if (natural_size)
520         {
521           natural_size->width = nat_width;
522           gtk_widget_get_preferred_height_for_width (widget, nat_width,
523                                                      NULL, &natural_size->height);
524         }
525     }
526   else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT */
527     {
528       gtk_widget_get_preferred_height (widget, &min_height, &nat_height);
529
530       if (minimum_size)
531         {
532           minimum_size->height = min_height;
533           gtk_widget_get_preferred_width_for_height (widget, min_height,
534                                                      &minimum_size->width, NULL);
535         }
536
537       if (natural_size)
538         {
539           natural_size->height = nat_height;
540           gtk_widget_get_preferred_width_for_height (widget, nat_height,
541                                                      NULL, &natural_size->width);
542         }
543     }
544 }
545
546
547 static gint
548 compare_gap (gconstpointer p1,
549              gconstpointer p2,
550              gpointer      data)
551 {
552   GtkRequestedSize *sizes = data;
553   const guint *c1 = p1;
554   const guint *c2 = p2;
555
556   const gint d1 = MAX (sizes[*c1].natural_size -
557                        sizes[*c1].minimum_size,
558                        0);
559   const gint d2 = MAX (sizes[*c2].natural_size -
560                        sizes[*c2].minimum_size,
561                        0);
562
563   gint delta = (d2 - d1);
564
565   if (0 == delta)
566     delta = (*c2 - *c1);
567
568   return delta;
569 }
570
571 /**
572  * gtk_distribute_natural_allocation: 
573  * @extra_space: Extra space to redistribute among children after subtracting
574  *               minimum sizes and any child padding from the overall allocation
575  * @n_requested_sizes: Number of requests to fit into the allocation
576  * @sizes: An array of structs with a client pointer and a minimum/natural size
577  *         in the orientation of the allocation.
578  *
579  * Distributes @extra_space to child @sizes by bringing up smaller
580  * children up to natural size first.
581  *
582  * The remaining space will be added to the @minimum_size member of the
583  * GtkRequestedSize struct. If all sizes reach their natural size then
584  * the remaining space is returned.
585  *
586  * Returns: The remainder of @extra_space after redistributing space
587  * to @sizes.
588  */
589 gint 
590 gtk_distribute_natural_allocation (gint              extra_space,
591                                    guint             n_requested_sizes,
592                                    GtkRequestedSize *sizes)
593 {
594   guint *spreading;
595   gint   i;
596
597   g_return_val_if_fail (extra_space >= 0, 0);
598
599   spreading = g_newa (guint, n_requested_sizes);
600
601   for (i = 0; i < n_requested_sizes; i++)
602     spreading[i] = i;
603
604   /* Distribute the container's extra space c_gap. We want to assign
605    * this space such that the sum of extra space assigned to children
606    * (c^i_gap) is equal to c_cap. The case that there's not enough
607    * space for all children to take their natural size needs some
608    * attention. The goals we want to achieve are:
609    *
610    *   a) Maximize number of children taking their natural size.
611    *   b) The allocated size of children should be a continuous
612    *   function of c_gap.  That is, increasing the container size by
613    *   one pixel should never make drastic changes in the distribution.
614    *   c) If child i takes its natural size and child j doesn't,
615    *   child j should have received at least as much gap as child i.
616    *
617    * The following code distributes the additional space by following
618    * these rules.
619    */
620   
621   /* Sort descending by gap and position. */
622   g_qsort_with_data (spreading,
623                      n_requested_sizes, sizeof (guint),
624                      compare_gap, sizes);
625   
626   /* Distribute available space.
627    * This master piece of a loop was conceived by Behdad Esfahbod.
628    */
629   for (i = n_requested_sizes - 1; extra_space > 0 && i >= 0; --i)
630     {
631       /* Divide remaining space by number of remaining children.
632        * Sort order and reducing remaining space by assigned space
633        * ensures that space is distributed equally.
634        */
635       gint glue = (extra_space + i) / (i + 1);
636       gint gap = sizes[(spreading[i])].natural_size
637         - sizes[(spreading[i])].minimum_size;
638       
639       gint extra = MIN (glue, gap);
640       
641       sizes[spreading[i]].minimum_size += extra;
642       
643       extra_space -= extra;
644     }
645
646   return extra_space;
647 }
648