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 "gtkcssnodeprivate.h" |
33 | #include "gtkcssnumbervalueprivate.h" |
34 | #include "gtklayoutmanagerprivate.h" |
35 | |
36 | |
37 | #ifdef G_ENABLE_CONSISTENCY_CHECKS |
38 | static GQuark recursion_check_quark = 0; |
39 | |
40 | static void |
41 | push_recursion_check (GtkWidget *widget, |
42 | GtkOrientation orientation) |
43 | { |
44 | gboolean in_measure = FALSE; |
45 | |
46 | if (recursion_check_quark == 0) |
47 | recursion_check_quark = g_quark_from_static_string ("gtk-size-request-in-progress" ); |
48 | |
49 | in_measure = GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (widget), recursion_check_quark)); |
50 | |
51 | if (in_measure) |
52 | { |
53 | g_warning ("%s %p: widget tried to gtk_widget_measure inside " |
54 | " GtkWidget::measure implementation. " |
55 | "Should just invoke GTK_WIDGET_GET_CLASS(widget)->measure " |
56 | "directly rather than using gtk_widget_measure" , |
57 | G_OBJECT_TYPE_NAME (widget), widget); |
58 | } |
59 | |
60 | g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, GINT_TO_POINTER(TRUE)); |
61 | } |
62 | |
63 | static void |
64 | pop_recursion_check (GtkWidget *widget, |
65 | GtkOrientation orientation) |
66 | { |
67 | g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, NULL); |
68 | } |
69 | #else |
70 | #define push_recursion_check(widget, orientation) |
71 | #define pop_recursion_check(widget, orientation) |
72 | #endif /* G_ENABLE_CONSISTENCY_CHECKS */ |
73 | |
74 | static GtkSizeRequestMode |
75 | fetch_request_mode (GtkWidget *widget) |
76 | { |
77 | GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget); |
78 | |
79 | if (layout_manager != NULL) |
80 | return gtk_layout_manager_get_request_mode (manager: layout_manager); |
81 | else |
82 | return GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget); |
83 | } |
84 | |
85 | static int |
86 | get_number (GtkCssValue *value) |
87 | { |
88 | double d = _gtk_css_number_value_get (number: value, one_hundred_percent: 100); |
89 | |
90 | if (d < 1) |
91 | return ceil (x: d); |
92 | else |
93 | return floor (x: d); |
94 | } |
95 | |
96 | /* Special-case min-width|height to round upwards, to avoid underalloc by 1px */ |
97 | static int |
98 | get_number_ceil (GtkCssValue *value) |
99 | { |
100 | return ceil (x: _gtk_css_number_value_get (number: value, one_hundred_percent: 100)); |
101 | } |
102 | |
103 | static void |
104 | get_box_margin (GtkCssStyle *style, |
105 | GtkBorder *margin) |
106 | { |
107 | margin->top = get_number (value: style->size->margin_top); |
108 | margin->left = get_number (value: style->size->margin_left); |
109 | margin->bottom = get_number (value: style->size->margin_bottom); |
110 | margin->right = get_number (value: style->size->margin_right); |
111 | } |
112 | |
113 | static void |
114 | get_box_border (GtkCssStyle *style, |
115 | GtkBorder *border) |
116 | { |
117 | border->top = get_number (value: style->border->border_top_width); |
118 | border->left = get_number (value: style->border->border_left_width); |
119 | border->bottom = get_number (value: style->border->border_bottom_width); |
120 | border->right = get_number (value: style->border->border_right_width); |
121 | } |
122 | |
123 | static void |
124 | get_box_padding (GtkCssStyle *style, |
125 | GtkBorder *border) |
126 | { |
127 | border->top = get_number (value: style->size->padding_top); |
128 | border->left = get_number (value: style->size->padding_left); |
129 | border->bottom = get_number (value: style->size->padding_bottom); |
130 | border->right = get_number (value: style->size->padding_right); |
131 | } |
132 | |
133 | static void |
134 | gtk_widget_query_size_for_orientation (GtkWidget *widget, |
135 | GtkOrientation orientation, |
136 | int for_size, |
137 | int *minimum, |
138 | int *natural, |
139 | int *minimum_baseline, |
140 | int *natural_baseline) |
141 | { |
142 | SizeRequestCache *cache; |
143 | int min_size = 0; |
144 | int nat_size = 0; |
145 | int min_baseline = -1; |
146 | int nat_baseline = -1; |
147 | gboolean found_in_cache; |
148 | |
149 | gtk_widget_ensure_resize (widget); |
150 | |
151 | /* We check the request mode first, to determine whether the widget even does |
152 | * any wfh/hfw handling. If it doesn't, we reset for_size to -1 and ensure |
153 | * that we only cache one size for the widget (i.e. a lot more cache hits). */ |
154 | cache = _gtk_widget_peek_request_cache (widget); |
155 | if (G_UNLIKELY (!cache->request_mode_valid)) |
156 | { |
157 | cache->request_mode = fetch_request_mode (widget); |
158 | cache->request_mode_valid = TRUE; |
159 | } |
160 | |
161 | if (cache->request_mode == GTK_SIZE_REQUEST_CONSTANT_SIZE) |
162 | for_size = -1; |
163 | |
164 | found_in_cache = _gtk_size_request_cache_lookup (cache, |
165 | orientation, |
166 | for_size, |
167 | minimum: &min_size, |
168 | natural: &nat_size, |
169 | minimum_baseline: &min_baseline, |
170 | natural_baseline: &nat_baseline); |
171 | |
172 | if (!found_in_cache) |
173 | { |
174 | GtkWidgetClass *widget_class; |
175 | GtkCssStyle *style; |
176 | GtkBorder margin, border, padding; |
177 | int adjusted_min, adjusted_natural; |
178 | int reported_min_size = 0; |
179 | int reported_nat_size = 0; |
180 | int css_min_size; |
181 | int css_min_for_size; |
182 | int ; |
183 | int ; |
184 | int widget_margins_for_size; |
185 | |
186 | style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget)); |
187 | get_box_margin (style, margin: &margin); |
188 | get_box_border (style, border: &border); |
189 | get_box_padding (style, border: &padding); |
190 | |
191 | widget_class = GTK_WIDGET_GET_CLASS (widget); |
192 | |
193 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
194 | { |
195 | css_extra_size = margin.left + margin.right + border.left + border.right + padding.left + padding.right; |
196 | css_extra_for_size = margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom; |
197 | css_min_size = get_number_ceil (value: style->size->min_width); |
198 | css_min_for_size = get_number_ceil (value: style->size->min_height); |
199 | widget_margins_for_size = widget->priv->margin.top + widget->priv->margin.bottom; |
200 | } |
201 | else |
202 | { |
203 | css_extra_size = margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom; |
204 | css_extra_for_size = margin.left + margin.right + border.left + border.right + padding.left + padding.right; |
205 | css_min_size = get_number_ceil (value: style->size->min_height); |
206 | css_min_for_size = get_number_ceil (value: style->size->min_width); |
207 | widget_margins_for_size = widget->priv->margin.left + widget->priv->margin.right; |
208 | } |
209 | |
210 | GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget); |
211 | |
212 | if (layout_manager != NULL) |
213 | { |
214 | if (for_size < 0) |
215 | { |
216 | push_recursion_check (widget, orientation); |
217 | gtk_layout_manager_measure (manager: layout_manager, widget, |
218 | orientation, for_size: -1, |
219 | minimum: &reported_min_size, natural: &reported_nat_size, |
220 | minimum_baseline: &min_baseline, natural_baseline: &nat_baseline); |
221 | pop_recursion_check (widget, orientation); |
222 | } |
223 | else |
224 | { |
225 | int adjusted_for_size; |
226 | int minimum_for_size = 0; |
227 | int natural_for_size = 0; |
228 | |
229 | /* Pull the minimum for_size from the cache as it's needed to adjust |
230 | * the proposed 'for_size' */ |
231 | gtk_layout_manager_measure (manager: layout_manager, widget, |
232 | OPPOSITE_ORIENTATION (orientation), for_size: -1, |
233 | minimum: &minimum_for_size, natural: &natural_for_size, |
234 | NULL, NULL); |
235 | |
236 | if (minimum_for_size < css_min_for_size) |
237 | minimum_for_size = css_min_for_size; |
238 | |
239 | if (for_size < minimum_for_size) |
240 | for_size = minimum_for_size; |
241 | |
242 | adjusted_for_size = for_size - widget_margins_for_size; |
243 | adjusted_for_size -= css_extra_for_size; |
244 | if (adjusted_for_size < 0) |
245 | adjusted_for_size = minimum_for_size; |
246 | |
247 | push_recursion_check (widget, orientation); |
248 | gtk_layout_manager_measure (manager: layout_manager, widget, |
249 | orientation, |
250 | for_size: adjusted_for_size, |
251 | minimum: &reported_min_size, natural: &reported_nat_size, |
252 | minimum_baseline: &min_baseline, natural_baseline: &nat_baseline); |
253 | pop_recursion_check (widget, orientation); |
254 | } |
255 | } |
256 | else |
257 | { |
258 | if (for_size < 0) |
259 | { |
260 | push_recursion_check (widget, orientation); |
261 | widget_class->measure (widget, orientation, -1, |
262 | &reported_min_size, &reported_nat_size, |
263 | &min_baseline, &nat_baseline); |
264 | pop_recursion_check (widget, orientation); |
265 | } |
266 | else |
267 | { |
268 | int adjusted_for_size; |
269 | int minimum_for_size = 0; |
270 | int natural_for_size = 0; |
271 | |
272 | /* Pull the minimum for_size from the cache as it's needed to adjust |
273 | * the proposed 'for_size' */ |
274 | gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), for_size: -1, |
275 | minimum: &minimum_for_size, natural: &natural_for_size, NULL, NULL); |
276 | |
277 | if (minimum_for_size < css_min_for_size) |
278 | minimum_for_size = css_min_for_size; |
279 | |
280 | if (for_size < minimum_for_size) |
281 | for_size = minimum_for_size; |
282 | |
283 | adjusted_for_size = for_size - widget_margins_for_size; |
284 | adjusted_for_size -= css_extra_for_size; |
285 | if (adjusted_for_size < 0) |
286 | adjusted_for_size = minimum_for_size; |
287 | |
288 | push_recursion_check (widget, orientation); |
289 | widget_class->measure (widget, |
290 | orientation, |
291 | adjusted_for_size, |
292 | &reported_min_size, &reported_nat_size, |
293 | &min_baseline, &nat_baseline); |
294 | pop_recursion_check (widget, orientation); |
295 | } |
296 | } |
297 | |
298 | min_size = MAX (0, MAX (reported_min_size, css_min_size)) + css_extra_size; |
299 | nat_size = MAX (0, MAX (reported_nat_size, css_min_size)) + css_extra_size; |
300 | |
301 | if (G_UNLIKELY (min_size > nat_size)) |
302 | { |
303 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
304 | { |
305 | g_warning ("%s %p (%s) reported min width %d and natural width %d in measure() with for_size=%d; natural size must be >= min size" , |
306 | G_OBJECT_TYPE_NAME (widget), widget, |
307 | g_quark_to_string (gtk_css_node_get_name (gtk_widget_get_css_node (widget))), |
308 | min_size, nat_size, for_size); |
309 | } |
310 | else |
311 | { |
312 | g_warning ("%s %p (%s) reported min height %d and natural height %d in measure() with for_size=%d; natural size must be >= min size" , |
313 | G_OBJECT_TYPE_NAME (widget), widget, |
314 | g_quark_to_string (gtk_css_node_get_name (gtk_widget_get_css_node (widget))), |
315 | min_size, nat_size, for_size); |
316 | |
317 | } |
318 | |
319 | nat_size = min_size; |
320 | } |
321 | else if (G_UNLIKELY (min_size < 0)) |
322 | { |
323 | g_warning ("%s %p (%s) reported min %s %d, but sizes must be >= 0" , |
324 | G_OBJECT_TYPE_NAME (widget), widget, |
325 | g_quark_to_string (gtk_css_node_get_name (gtk_widget_get_css_node (widget))), |
326 | orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height" , |
327 | min_size); |
328 | min_size = 0; |
329 | nat_size = MAX (0, min_size); |
330 | } |
331 | |
332 | adjusted_min = min_size; |
333 | adjusted_natural = nat_size; |
334 | gtk_widget_adjust_size_request (widget, orientation, |
335 | minimum_size: &adjusted_min, natural_size: &adjusted_natural); |
336 | |
337 | if (adjusted_min < min_size || |
338 | adjusted_natural < nat_size) |
339 | { |
340 | g_warning ("%s %p adjusted size %s min %d natural %d must not decrease below min %d natural %d" , |
341 | G_OBJECT_TYPE_NAME (widget), widget, |
342 | orientation == GTK_ORIENTATION_VERTICAL ? "vertical" : "horizontal" , |
343 | adjusted_min, adjusted_natural, |
344 | min_size, nat_size); |
345 | /* don't use the adjustment */ |
346 | } |
347 | else if (adjusted_min > adjusted_natural) |
348 | { |
349 | g_warning ("%s %p adjusted size %s min %d natural %d original min %d natural %d has min greater than natural" , |
350 | G_OBJECT_TYPE_NAME (widget), widget, |
351 | orientation == GTK_ORIENTATION_VERTICAL ? "vertical" : "horizontal" , |
352 | adjusted_min, adjusted_natural, |
353 | min_size, nat_size); |
354 | /* don't use the adjustment */ |
355 | } |
356 | else |
357 | { |
358 | /* adjustment looks good */ |
359 | min_size = adjusted_min; |
360 | nat_size = adjusted_natural; |
361 | } |
362 | |
363 | if (min_baseline != -1 || nat_baseline != -1) |
364 | { |
365 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
366 | { |
367 | g_warning ("%s %p reported a horizontal baseline" , |
368 | G_OBJECT_TYPE_NAME (widget), widget); |
369 | min_baseline = -1; |
370 | nat_baseline = -1; |
371 | } |
372 | else if (min_baseline == -1 || nat_baseline == -1) |
373 | { |
374 | g_warning ("%s %p reported baseline for only one of min/natural (min: %d, natural: %d)" , |
375 | G_OBJECT_TYPE_NAME (widget), widget, |
376 | min_baseline, nat_baseline); |
377 | min_baseline = -1; |
378 | nat_baseline = -1; |
379 | } |
380 | else if (min_baseline > reported_min_size || |
381 | nat_baseline > reported_nat_size || |
382 | min_baseline < 0 || |
383 | nat_baseline < 0) |
384 | { |
385 | g_warning ("%s %p reported baselines of minimum %d and natural %d, but sizes of " |
386 | "minimum %d and natural %d. Baselines must be inside the widget size." , |
387 | G_OBJECT_TYPE_NAME (widget), widget, min_baseline, nat_baseline, |
388 | reported_min_size, reported_nat_size); |
389 | min_baseline = -1; |
390 | nat_baseline = -1; |
391 | } |
392 | else |
393 | { |
394 | if (css_min_size > reported_min_size) |
395 | { |
396 | min_baseline += (css_min_size - reported_min_size) / 2; |
397 | nat_baseline += (css_min_size - reported_min_size) / 2; |
398 | } |
399 | |
400 | min_baseline += margin.top + border.top + padding.top; |
401 | nat_baseline += margin.top + border.top + padding.top; |
402 | |
403 | gtk_widget_adjust_baseline_request (widget, minimum_baseline: &min_baseline, natural_baseline: &nat_baseline); |
404 | } |
405 | } |
406 | |
407 | _gtk_size_request_cache_commit (cache, |
408 | orientation, |
409 | for_size, |
410 | minimum_size: min_size, |
411 | natural_size: nat_size, |
412 | minimum_baseline: min_baseline, |
413 | natural_baseline: nat_baseline); |
414 | } |
415 | |
416 | if (minimum) |
417 | *minimum = min_size; |
418 | |
419 | if (natural) |
420 | *natural = nat_size; |
421 | |
422 | if (minimum_baseline) |
423 | *minimum_baseline = min_baseline; |
424 | |
425 | if (natural_baseline) |
426 | *natural_baseline = nat_baseline; |
427 | |
428 | g_assert (min_size <= nat_size); |
429 | |
430 | GTK_DISPLAY_NOTE (_gtk_widget_get_display (widget), SIZE_REQUEST, { |
431 | GString *s; |
432 | |
433 | s = g_string_new ("" ); |
434 | g_string_append_printf (s, "[%p] %s\t%s: %d is minimum %d and natural: %d" , |
435 | widget, G_OBJECT_TYPE_NAME (widget), |
436 | orientation == GTK_ORIENTATION_HORIZONTAL |
437 | ? "width for height" |
438 | : "height for width" , |
439 | for_size, min_size, nat_size); |
440 | if (min_baseline != -1 || nat_baseline != -1) |
441 | { |
442 | g_string_append_printf (s, ", baseline %d/%d" , |
443 | min_baseline, nat_baseline); |
444 | } |
445 | g_string_append_printf (s, " (hit cache: %s)\n" , |
446 | found_in_cache ? "yes" : "no" ); |
447 | g_printerr ("%s" , s->str); |
448 | g_string_free (s, TRUE); |
449 | }); |
450 | } |
451 | |
452 | /** |
453 | * gtk_widget_measure: |
454 | * @widget: A `GtkWidget` instance |
455 | * @orientation: the orientation to measure |
456 | * @for_size: Size for the opposite of @orientation, i.e. |
457 | * if @orientation is %GTK_ORIENTATION_HORIZONTAL, this is |
458 | * the height the widget should be measured with. The %GTK_ORIENTATION_VERTICAL |
459 | * case is analogous. This way, both height-for-width and width-for-height |
460 | * requests can be implemented. If no size is known, -1 can be passed. |
461 | * @minimum: (out) (optional): location to store the minimum size |
462 | * @natural: (out) (optional): location to store the natural size |
463 | * @minimum_baseline: (out) (optional): location to store the baseline |
464 | * position for the minimum size, or -1 to report no baseline |
465 | * @natural_baseline: (out) (optional): location to store the baseline |
466 | * position for the natural size, or -1 to report no baseline |
467 | * |
468 | * Measures @widget in the orientation @orientation and for the given @for_size. |
469 | * |
470 | * As an example, if @orientation is %GTK_ORIENTATION_HORIZONTAL and @for_size |
471 | * is 300, this functions will compute the minimum and natural width of @widget |
472 | * if it is allocated at a height of 300 pixels. |
473 | * |
474 | * See [GtkWidget’s geometry management section](class.Widget.html#height-for-width-geometry-management) for |
475 | * a more details on implementing `GtkWidgetClass.measure()`. |
476 | */ |
477 | void |
478 | gtk_widget_measure (GtkWidget *widget, |
479 | GtkOrientation orientation, |
480 | int for_size, |
481 | int *minimum, |
482 | int *natural, |
483 | int *minimum_baseline, |
484 | int *natural_baseline) |
485 | { |
486 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
487 | g_return_if_fail (for_size >= -1); |
488 | g_return_if_fail (orientation == GTK_ORIENTATION_HORIZONTAL || |
489 | orientation == GTK_ORIENTATION_VERTICAL); |
490 | |
491 | if (for_size >= 0) |
492 | { |
493 | int min_opposite_size; |
494 | gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), for_size: -1, minimum: &min_opposite_size, NULL, NULL, NULL); |
495 | if (for_size < min_opposite_size) |
496 | for_size = min_opposite_size; |
497 | } |
498 | |
499 | /* This is the main function that checks for a cached size and |
500 | * possibly queries the widget class to compute the size if it's |
501 | * not cached. |
502 | */ |
503 | if (!_gtk_widget_get_visible (widget) && !GTK_IS_ROOT (ptr: widget)) |
504 | { |
505 | if (minimum) |
506 | *minimum = 0; |
507 | if (natural) |
508 | *natural = 0; |
509 | if (minimum_baseline) |
510 | *minimum_baseline = -1; |
511 | if (natural_baseline) |
512 | *natural_baseline = -1; |
513 | return; |
514 | } |
515 | |
516 | if (G_LIKELY (!_gtk_widget_get_sizegroups (widget))) |
517 | { |
518 | gtk_widget_query_size_for_orientation (widget, orientation, for_size, minimum, natural, |
519 | minimum_baseline, natural_baseline); |
520 | } |
521 | else |
522 | { |
523 | GHashTable *widgets; |
524 | GHashTableIter iter; |
525 | gpointer key; |
526 | int min_result = 0, nat_result = 0; |
527 | |
528 | widgets = _gtk_size_group_get_widget_peers (for_widget: widget, orientation); |
529 | |
530 | g_hash_table_iter_init (iter: &iter, hash_table: widgets); |
531 | while (g_hash_table_iter_next (iter: &iter, key: &key, NULL)) |
532 | { |
533 | GtkWidget *tmp_widget = key; |
534 | int min_dimension, nat_dimension; |
535 | |
536 | gtk_widget_query_size_for_orientation (widget: tmp_widget, orientation, for_size, |
537 | minimum: &min_dimension, natural: &nat_dimension, NULL, NULL); |
538 | |
539 | min_result = MAX (min_result, min_dimension); |
540 | nat_result = MAX (nat_result, nat_dimension); |
541 | } |
542 | |
543 | g_hash_table_destroy (hash_table: widgets); |
544 | |
545 | /* Baselines make no sense with sizegroups really */ |
546 | if (minimum_baseline) |
547 | *minimum_baseline = -1; |
548 | |
549 | if (natural_baseline) |
550 | *natural_baseline = -1; |
551 | |
552 | if (minimum) |
553 | *minimum = min_result; |
554 | |
555 | if (natural) |
556 | *natural = nat_result; |
557 | } |
558 | } |
559 | |
560 | /** |
561 | * gtk_widget_get_request_mode: |
562 | * @widget: a `GtkWidget` instance |
563 | * |
564 | * Gets whether the widget prefers a height-for-width layout |
565 | * or a width-for-height layout. |
566 | * |
567 | * Single-child widgets generally propagate the preference of |
568 | * their child, more complex widgets need to request something |
569 | * either in context of their children or in context of their |
570 | * allocation capabilities. |
571 | * |
572 | * Returns: The `GtkSizeRequestMode` preferred by @widget. |
573 | */ |
574 | GtkSizeRequestMode |
575 | gtk_widget_get_request_mode (GtkWidget *widget) |
576 | { |
577 | SizeRequestCache *cache; |
578 | |
579 | cache = _gtk_widget_peek_request_cache (widget); |
580 | |
581 | if (G_UNLIKELY (!cache->request_mode_valid)) |
582 | { |
583 | cache->request_mode = fetch_request_mode (widget); |
584 | cache->request_mode_valid = TRUE; |
585 | } |
586 | |
587 | return cache->request_mode; |
588 | } |
589 | |
590 | /** |
591 | * gtk_widget_get_preferred_size: |
592 | * @widget: a `GtkWidget` instance |
593 | * @minimum_size: (out) (optional): location for storing the minimum size |
594 | * @natural_size: (out) (optional): location for storing the natural size |
595 | * |
596 | * Retrieves the minimum and natural size of a widget, taking |
597 | * into account the widget’s preference for height-for-width management. |
598 | * |
599 | * This is used to retrieve a suitable size by container widgets which do |
600 | * not impose any restrictions on the child placement. It can be used |
601 | * to deduce toplevel window and menu sizes as well as child widgets in |
602 | * free-form containers such as `GtkFixed`. |
603 | * |
604 | * Handle with care. Note that the natural height of a height-for-width |
605 | * widget will generally be a smaller size than the minimum height, since |
606 | * the required height for the natural width is generally smaller than the |
607 | * required height for the minimum width. |
608 | * |
609 | * Use [id@gtk_widget_measure] if you want to support baseline alignment. |
610 | */ |
611 | void |
612 | gtk_widget_get_preferred_size (GtkWidget *widget, |
613 | GtkRequisition *minimum_size, |
614 | GtkRequisition *natural_size) |
615 | { |
616 | int min_width, nat_width; |
617 | int min_height, nat_height; |
618 | |
619 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
620 | |
621 | if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) |
622 | { |
623 | gtk_widget_measure (widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
624 | minimum: &min_width, natural: &nat_width, NULL, NULL); |
625 | |
626 | if (minimum_size) |
627 | { |
628 | minimum_size->width = min_width; |
629 | gtk_widget_measure (widget, |
630 | orientation: GTK_ORIENTATION_VERTICAL, for_size: min_width, |
631 | minimum: &minimum_size->height, NULL, NULL, NULL); |
632 | } |
633 | |
634 | if (natural_size) |
635 | { |
636 | natural_size->width = nat_width; |
637 | gtk_widget_measure (widget, |
638 | orientation: GTK_ORIENTATION_VERTICAL, for_size: nat_width, |
639 | NULL, natural: &natural_size->height, NULL, NULL); |
640 | } |
641 | } |
642 | else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT or CONSTANT_SIZE */ |
643 | { |
644 | gtk_widget_measure (widget, orientation: GTK_ORIENTATION_VERTICAL, |
645 | for_size: -1, minimum: &min_height, natural: &nat_height, NULL, NULL); |
646 | |
647 | if (minimum_size) |
648 | { |
649 | minimum_size->height = min_height; |
650 | gtk_widget_measure (widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: min_height, |
651 | minimum: &minimum_size->width, NULL, NULL, NULL); |
652 | } |
653 | |
654 | if (natural_size) |
655 | { |
656 | natural_size->height = nat_height; |
657 | gtk_widget_measure (widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: nat_height, |
658 | NULL, natural: &natural_size->width, NULL, NULL); |
659 | } |
660 | } |
661 | } |
662 | |
663 | static int |
664 | compare_gap (gconstpointer p1, |
665 | gconstpointer p2, |
666 | gpointer data) |
667 | { |
668 | GtkRequestedSize *sizes = data; |
669 | const guint *c1 = p1; |
670 | const guint *c2 = p2; |
671 | |
672 | const int d1 = MAX (sizes[*c1].natural_size - |
673 | sizes[*c1].minimum_size, |
674 | 0); |
675 | const int d2 = MAX (sizes[*c2].natural_size - |
676 | sizes[*c2].minimum_size, |
677 | 0); |
678 | |
679 | int delta = (d2 - d1); |
680 | |
681 | if (0 == delta) |
682 | delta = (*c2 - *c1); |
683 | |
684 | return delta; |
685 | } |
686 | |
687 | /** |
688 | * gtk_distribute_natural_allocation: |
689 | * @extra_space: Extra space to redistribute among children after subtracting |
690 | * minimum sizes and any child padding from the overall allocation |
691 | * @n_requested_sizes: Number of requests to fit into the allocation |
692 | * @sizes: (array length=n_requested_sizes): An array of structs with a client pointer and a minimum/natural size |
693 | * in the orientation of the allocation. |
694 | * |
695 | * Distributes @extra_space to child @sizes by bringing smaller |
696 | * children up to natural size first. |
697 | * |
698 | * The remaining space will be added to the @minimum_size member of the |
699 | * `GtkRequestedSize` struct. If all sizes reach their natural size then |
700 | * the remaining space is returned. |
701 | * |
702 | * Returns: The remainder of @extra_space after redistributing space |
703 | * to @sizes. |
704 | */ |
705 | int |
706 | gtk_distribute_natural_allocation (int , |
707 | guint n_requested_sizes, |
708 | GtkRequestedSize *sizes) |
709 | { |
710 | guint *spreading; |
711 | int i; |
712 | |
713 | g_return_val_if_fail (extra_space >= 0, 0); |
714 | |
715 | if (n_requested_sizes == 0) |
716 | return extra_space; |
717 | |
718 | spreading = g_newa (guint, n_requested_sizes); |
719 | |
720 | for (i = 0; i < n_requested_sizes; i++) |
721 | spreading[i] = i; |
722 | |
723 | /* Distribute the container's extra space c_gap. We want to assign |
724 | * this space such that the sum of extra space assigned to children |
725 | * (c^i_gap) is equal to c_cap. The case that there's not enough |
726 | * space for all children to take their natural size needs some |
727 | * attention. The goals we want to achieve are: |
728 | * |
729 | * a) Maximize number of children taking their natural size. |
730 | * b) The allocated size of children should be a continuous |
731 | * function of c_gap. That is, increasing the container size by |
732 | * one pixel should never make drastic changes in the distribution. |
733 | * c) If child i takes its natural size and child j doesn't, |
734 | * child j should have received at least as much gap as child i. |
735 | * |
736 | * The following code distributes the additional space by following |
737 | * these rules. |
738 | */ |
739 | |
740 | /* Sort descending by gap and position. */ |
741 | g_qsort_with_data (pbase: spreading, |
742 | total_elems: n_requested_sizes, size: sizeof (guint), |
743 | compare_func: compare_gap, user_data: sizes); |
744 | |
745 | /* Distribute available space. |
746 | * This masterpiece of a loop was conceived by Behdad Esfahbod. |
747 | */ |
748 | for (i = n_requested_sizes - 1; extra_space > 0 && i >= 0; --i) |
749 | { |
750 | /* Divide remaining space by number of remaining children. |
751 | * Sort order and reducing remaining space by assigned space |
752 | * ensures that space is distributed equally. |
753 | */ |
754 | int glue = (extra_space + i) / (i + 1); |
755 | int gap = sizes[(spreading[i])].natural_size |
756 | - sizes[(spreading[i])].minimum_size; |
757 | |
758 | int = MIN (glue, gap); |
759 | |
760 | sizes[spreading[i]].minimum_size += extra; |
761 | |
762 | extra_space -= extra; |
763 | } |
764 | |
765 | return extra_space; |
766 | } |
767 | |