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