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