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