gtksizerequest.c 31.3 KB
Newer Older
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 9 10 11 12 13 14 15 16 17 18
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Javier Jardón's avatar
Javier Jardón committed
19
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 21 22
 */

#include <config.h>
Emmanuele Bassi's avatar
Emmanuele Bassi committed
23

24
#include "gtksizerequest.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
25

26
#include "gtkdebug.h"
27
#include "gtkintl.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
28 29
#include "gtkprivate.h"
#include "gtksizegroup-private.h"
30
#include "gtksizerequestcacheprivate.h"
Emmanuele Bassi's avatar
Emmanuele Bassi committed
31
#include "gtkwidgetprivate.h"
32
#include "deprecated/gtkstyle.h"
33

34

35
#ifdef G_ENABLE_CONSISTENCY_CHECKS
36 37 38 39
static GQuark recursion_check_quark = 0;

static void
push_recursion_check (GtkWidget       *widget,
40
                      GtkOrientation   orientation,
41 42 43 44 45 46 47 48 49 50
                      gint             for_size)
{
  const char *previous_method;
  const char *method;

  if (recursion_check_quark == 0)
    recursion_check_quark = g_quark_from_static_string ("gtk-size-request-in-progress");

  previous_method = g_object_get_qdata (G_OBJECT (widget), recursion_check_quark);

51
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    {
      method = for_size < 0 ? "get_width" : "get_width_for_height";
    }
  else
    {
      method = for_size < 0 ? "get_height" : "get_height_for_width";
    }

  if (previous_method != NULL)
    {
      g_warning ("%s %p: widget tried to gtk_widget_%s inside "
                 " GtkWidget     ::%s implementation. "
                 "Should just invoke GTK_WIDGET_GET_CLASS(widget)->%s "
                 "directly rather than using gtk_widget_%s",
                 G_OBJECT_TYPE_NAME (widget), widget,
                 method, previous_method,
                 method, method);
    }

  g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, (char*) method);
}

static void
pop_recursion_check (GtkWidget       *widget,
76
                     GtkOrientation   orientation)
77 78 79
{
  g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, NULL);
}
80 81 82 83
#else
#define push_recursion_check(widget, orientation, for_size)
#define pop_recursion_check(widget, orientation)
#endif /* G_ENABLE_CONSISTENCY_CHECKS */
84

85
static const char *
86 87
get_vfunc_name (GtkOrientation orientation,
                gint           for_size)
88
{
89
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
90 91 92 93 94
    return for_size < 0 ? "get_preferred_width" : "get_preferred_width_for_height";
  else
    return for_size < 0 ? "get_preferred_height" : "get_preferred_height_for_width";
}

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
static gboolean
widget_class_has_baseline_support (GtkWidgetClass *widget_class)
{
  GtkWidgetClass *parent_class;

  if (widget_class->get_preferred_height_and_baseline_for_width == NULL)
    return FALSE;

  /* This is kinda hacky, but for backwards compatibility reasons we have to handle the case
     where a class previously did not support get_preferred_height_and_baseline_for_width,
     but then gained support for it, and a subclass of it overrides the previous non-baseline
     methods. If this happens we need to call the overridden (non-baseline supporting) versions
     on the subclass, rather than the inherited but not overriddent new get_preferred_height_and_baseline_for_width.
  */

  /* Loop over all parent classes that inherit the same get_preferred_height_and_baseline_for_width */
  parent_class = g_type_class_peek_parent (widget_class);
  while (parent_class != NULL &&
	 parent_class->get_preferred_height_and_baseline_for_width == widget_class->get_preferred_height_and_baseline_for_width)
    {
      if (parent_class->get_preferred_height != widget_class->get_preferred_height ||
	  parent_class->get_preferred_height_for_width != widget_class->get_preferred_height_for_width)
	return FALSE;

	parent_class = g_type_class_peek_parent (parent_class);
    }

  return TRUE;
}

gboolean
_gtk_widget_has_baseline_support (GtkWidget *widget)
{
  GtkWidgetClass *widget_class;

  widget_class = GTK_WIDGET_GET_CLASS (widget);

  return widget_class_has_baseline_support (widget_class);
}

135
static void
136
gtk_widget_query_size_for_orientation (GtkWidget        *widget,
137
                                       GtkOrientation    orientation,
138 139
                                       gint              for_size,
                                       gint             *minimum_size,
140 141 142
                                       gint             *natural_size,
                                       gint             *minimum_baseline,
                                       gint             *natural_baseline)
143
{
144
  SizeRequestCache *cache;
145
  GtkWidgetClass *widget_class;
146 147
  gint min_size = 0;
  gint nat_size = 0;
148 149
  gint min_baseline = -1;
  gint nat_baseline = -1;
150
  gboolean found_in_cache;
151

152 153 154
  if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_CONSTANT_SIZE)
    for_size = -1;

155 156 157 158 159
  cache = _gtk_widget_peek_request_cache (widget);
  found_in_cache = _gtk_size_request_cache_lookup (cache,
                                                   orientation,
                                                   for_size,
                                                   &min_size,
160 161 162 163 164
                                                   &nat_size,
						   &min_baseline,
						   &nat_baseline);

  widget_class = GTK_WIDGET_GET_CLASS (widget);
165
  
166 167
  if (!found_in_cache)
    {
168
      gint adjusted_min, adjusted_natural, adjusted_for_size = for_size;
169

170
      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
171
      gtk_widget_ensure_style (widget);
172
      G_GNUC_END_IGNORE_DEPRECATIONS;
173

174
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
175
        {
176
          if (for_size < 0)
177
            {
178
	      push_recursion_check (widget, orientation, for_size);
179
              widget_class->get_preferred_width (widget, &min_size, &nat_size);
180
	      pop_recursion_check (widget, orientation);
181
            }
182
          else
183
            {
184 185 186
              gint ignored_position = 0;
              gint minimum_height;
              gint natural_height;
187

188
	      /* Pull the base natural height from the cache as it's needed to adjust
189
	       * the proposed 'for_size' */
190
	      gtk_widget_get_preferred_height (widget, &minimum_height, &natural_height);
191 192

              /* convert for_size to unadjusted height (for_size is a proposed allocation) */
193 194 195 196 197 198
              widget_class->adjust_size_allocation (widget,
						    GTK_ORIENTATION_VERTICAL,
						    &minimum_height,
						    &natural_height,
						    &ignored_position,
						    &adjusted_for_size);
199 200

	      push_recursion_check (widget, orientation, for_size);
201 202 203
              widget_class->get_preferred_width_for_height (widget,
							    MAX (adjusted_for_size, minimum_height),
							    &min_size, &nat_size);
204
	      pop_recursion_check (widget, orientation);
205
            }
206
        }
207
      else
208
        {
209
          if (for_size < 0)
210
            {
211
	      push_recursion_check (widget, orientation, for_size);
212 213 214 215 216 217
	      if (widget_class_has_baseline_support (widget_class))
		widget_class->get_preferred_height_and_baseline_for_width (widget, -1,
									   &min_size, &nat_size,
									   &min_baseline, &nat_baseline);
	      else
		widget_class->get_preferred_height (widget, &min_size, &nat_size);
218
	      pop_recursion_check (widget, orientation);
219
            }
220
          else
221
            {
222 223 224
              gint ignored_position = 0;
              gint minimum_width;
              gint natural_width;
225

226
	      /* Pull the base natural width from the cache as it's needed to adjust
227
	       * the proposed 'for_size' */
228
	      gtk_widget_get_preferred_width (widget, &minimum_width, &natural_width);
229 230

              /* convert for_size to unadjusted width (for_size is a proposed allocation) */
231 232 233 234 235 236
              widget_class->adjust_size_allocation (widget,
						    GTK_ORIENTATION_HORIZONTAL,
						    &minimum_width,
						    &natural_width,
						    &ignored_position,
						    &adjusted_for_size);
237 238

	      push_recursion_check (widget, orientation, for_size);
239 240 241 242 243 244 245
	      if (widget_class_has_baseline_support (widget_class))
		widget_class->get_preferred_height_and_baseline_for_width (widget, MAX (adjusted_for_size, minimum_width),
									   &min_size, &nat_size,
									   &min_baseline, &nat_baseline);
	      else
		widget_class->get_preferred_height_for_width (widget, MAX (adjusted_for_size, minimum_width),
							      &min_size, &nat_size);
246
	      pop_recursion_check (widget, orientation);
247
            }
248
        }
249

250 251
      if (min_size > nat_size)
        {
252 253
          g_warning ("%s %p reported min size %d and natural size %d in %s(); natural size must be >= min size",
                     G_OBJECT_TYPE_NAME (widget), widget, min_size, nat_size, get_vfunc_name (orientation, for_size));
254 255
        }

256 257
      adjusted_min     = min_size;
      adjusted_natural = nat_size;
258 259 260 261
      widget_class->adjust_size_request (widget,
					 orientation,
					 &adjusted_min,
					 &adjusted_natural);
262

263 264
      if (adjusted_min < min_size ||
          adjusted_natural < nat_size)
265 266
        {
          g_warning ("%s %p adjusted size %s min %d natural %d must not decrease below min %d natural %d",
267
                     G_OBJECT_TYPE_NAME (widget), widget,
268
                     orientation == GTK_ORIENTATION_VERTICAL ? "vertical" : "horizontal",
269
                     adjusted_min, adjusted_natural,
270
                     min_size, nat_size);
271 272 273 274 275
          /* don't use the adjustment */
        }
      else if (adjusted_min > adjusted_natural)
        {
          g_warning ("%s %p adjusted size %s min %d natural %d original min %d natural %d has min greater than natural",
276
                     G_OBJECT_TYPE_NAME (widget), widget,
277
                     orientation == GTK_ORIENTATION_VERTICAL ? "vertical" : "horizontal",
278
                     adjusted_min, adjusted_natural,
279
                     min_size, nat_size);
280 281 282 283 284
          /* don't use the adjustment */
        }
      else
        {
          /* adjustment looks good */
285 286
          min_size = adjusted_min;
          nat_size = adjusted_natural;
287 288
        }

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
      if (min_baseline != -1 || nat_baseline != -1)
	{
	  if (orientation == GTK_ORIENTATION_HORIZONTAL)
	    {
	      g_warning ("%s %p reported a horizontal baseline",
			 G_OBJECT_TYPE_NAME (widget), widget);
	      min_baseline = -1;
	      nat_baseline = -1;
	    }
	  else if (min_baseline == -1 || nat_baseline == -1)
	    {
	      g_warning ("%s %p reported baseline for only one of min/natural (min: %d, natural: %d)",
			 G_OBJECT_TYPE_NAME (widget), widget,
			 min_baseline, nat_baseline);
	      min_baseline = -1;
	      nat_baseline = -1;
	    }
	  else if (gtk_widget_get_valign_with_baseline (widget) != GTK_ALIGN_BASELINE)
	    {
	      /* Ignore requested baseline for non-aligned widgets */
	      min_baseline = -1;
	      nat_baseline = -1;
	    }
	  else
	    widget_class->adjust_baseline_request (widget,
						   &min_baseline,
						   &nat_baseline);
	}

318
      _gtk_size_request_cache_commit (cache,
319 320 321
                                      orientation,
                                      for_size,
                                      min_size,
322 323 324
                                      nat_size,
				      min_baseline,
				      nat_baseline);
325
    }
326 327

  if (minimum_size)
328
    *minimum_size = min_size;
329

330
  if (natural_size)
331
    *natural_size = nat_size;
332

333 334 335 336 337 338
  if (minimum_baseline)
    *minimum_baseline = min_baseline;

  if (natural_baseline)
    *natural_baseline = nat_baseline;

339
  g_assert (min_size <= nat_size);
340

341
  GTK_NOTE (SIZE_REQUEST,
342
            g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d",
343
                     widget, G_OBJECT_TYPE_NAME (widget),
344
                     orientation == GTK_ORIENTATION_HORIZONTAL ?
345
                     "width for height" : "height for width" ,
346 347 348 349 350 351 352
                     for_size, min_size, nat_size);
	    if (min_baseline != -1 || nat_baseline != -1)
	      g_print (", baseline %d/%d",
		       min_baseline, nat_baseline);
	    g_print (" (hit cache: %s)\n",
		     found_in_cache ? "yes" : "no")
	    );
353 354 355 356 357 358 359
}

/* This is the main function that checks for a cached size and
 * possibly queries the widget class to compute the size if it's
 * not cached. If the for_size here is -1, then get_preferred_width()
 * or get_preferred_height() will be used.
 */
360 361 362 363 364 365 366 367
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)
368 369 370 371 372 373
{
  GHashTable *widgets;
  GHashTableIter iter;
  gpointer key;
  gint    min_result = 0, nat_result = 0;

374
  if (!_gtk_widget_get_visible (widget) && !_gtk_widget_is_toplevel (widget))
375 376 377 378 379
    {
      if (minimum)
        *minimum = 0;
      if (natural)
        *natural = 0;
380 381 382 383
      if (minimum_baseline)
        *minimum_baseline = -1;
      if (natural_baseline)
        *natural_baseline = -1;
384 385 386
      return;
    }

387 388
  if (G_LIKELY (!_gtk_widget_get_sizegroups (widget)))
    {
389 390
      gtk_widget_query_size_for_orientation (widget, orientation, for_size, minimum, natural,
					     minimum_baseline, natural_baseline);
391 392
      return;
    }
393

394
  widgets = _gtk_size_group_get_widget_peers (widget, orientation);
395 396 397 398 399 400 401 402 403

  g_hash_table_foreach (widgets, (GHFunc) g_object_ref, NULL);
  
  g_hash_table_iter_init (&iter, widgets);
  while (g_hash_table_iter_next (&iter, &key, NULL))
    {
      GtkWidget *tmp_widget = key;
      gint min_dimension, nat_dimension;

404
      gtk_widget_query_size_for_orientation (tmp_widget, orientation, for_size, &min_dimension, &nat_dimension, NULL, NULL);
405 406 407 408 409 410 411 412 413

      min_result = MAX (min_result, min_dimension);
      nat_result = MAX (nat_result, nat_dimension);
    }

  g_hash_table_foreach (widgets, (GHFunc) g_object_unref, NULL);

  g_hash_table_destroy (widgets);

414 415 416 417 418 419 420
  /* Baselines make no sense with sizegroups really */
  if (minimum_baseline)
    *minimum_baseline = -1;

  if (natural_baseline)
    *natural_baseline = -1;

421 422 423 424 425
  if (minimum)
    *minimum = min_result;

  if (natural)
    *natural = nat_result;
426 427
}

428
/**
429
 * gtk_widget_get_request_mode:
430
 * @widget: a #GtkWidget instance
431 432
 *
 * Gets whether the widget prefers a height-for-width layout
433 434
 * or a width-for-height layout.
 *
435
 * #GtkBin widgets generally propagate the preference of
436 437
 * their child, container widgets need to request something either in
 * context of their children or in context of their allocation
438
 * capabilities.
439
 *
440
 * Returns: The #GtkSizeRequestMode preferred by @widget.
441 442 443
 *
 * Since: 3.0
 */
444
GtkSizeRequestMode
445
gtk_widget_get_request_mode (GtkWidget *widget)
446
{
447 448
  SizeRequestCache *cache;

449
  g_return_val_if_fail (GTK_IS_WIDGET (widget), GTK_SIZE_REQUEST_CONSTANT_SIZE);
450

451 452 453 454 455 456 457 458 459
  cache = _gtk_widget_peek_request_cache (widget);

  if (!cache->request_mode_valid)
    {
      cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget);
      cache->request_mode_valid = TRUE;
    }

  return cache->request_mode;
460 461
}

462
/**
463 464
 * gtk_widget_get_preferred_width:
 * @widget: a #GtkWidget instance
465 466
 * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL
 * @natural_width: (out) (allow-none): location to store the natural width, or %NULL
467
 *
468
 * Retrieves a widget’s initial minimum and natural width.
469
 *
470
 * This call is specific to height-for-width requests.
471
 *
472 473
 * The returned request will be modified by the
 * GtkWidgetClass::adjust_size_request virtual method and by any
474
 * #GtkSizeGroups that have been applied. That is, the returned request
475 476 477
 * is the one that should be used for layout, not necessarily the one
 * returned by the widget itself.
 *
478 479 480
 * Since: 3.0
 */
void
481 482 483
gtk_widget_get_preferred_width (GtkWidget *widget,
                                gint      *minimum_width,
                                gint      *natural_width)
484
{
485 486 487
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (minimum_width != NULL || natural_width != NULL);

488 489 490 491 492 493
  gtk_widget_compute_size_for_orientation (widget,
                                           GTK_ORIENTATION_HORIZONTAL,
                                           -1,
                                           minimum_width,
                                           natural_width,
                                           NULL, NULL);
494 495 496 497
}


/**
498 499
 * gtk_widget_get_preferred_height:
 * @widget: a #GtkWidget instance
500 501
 * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL
 * @natural_height: (out) (allow-none): location to store the natural height, or %NULL
502
 *
503
 * Retrieves a widget’s initial minimum and natural height.
504
 *
505
 * This call is specific to width-for-height requests.
506
 *
507 508
 * The returned request will be modified by the
 * GtkWidgetClass::adjust_size_request virtual method and by any
509
 * #GtkSizeGroups that have been applied. That is, the returned request
510 511 512
 * is the one that should be used for layout, not necessarily the one
 * returned by the widget itself.
 *
513 514 515
 * Since: 3.0
 */
void
516 517 518
gtk_widget_get_preferred_height (GtkWidget *widget,
                                 gint      *minimum_height,
                                 gint      *natural_height)
519
{
520 521 522
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (minimum_height != NULL || natural_height != NULL);

523 524 525 526 527 528
  gtk_widget_compute_size_for_orientation (widget,
                                           GTK_ORIENTATION_VERTICAL,
                                           -1,
                                           minimum_height,
                                           natural_height,
                                           NULL, NULL);
529 530
}

531 532


533
/**
534 535
 * gtk_widget_get_preferred_width_for_height:
 * @widget: a #GtkWidget instance
536
 * @height: the height which is available for allocation
537 538
 * @minimum_width: (out) (allow-none): location for storing the minimum width, or %NULL
 * @natural_width: (out) (allow-none): location for storing the natural width, or %NULL
539
 *
540
 * Retrieves a widget’s minimum and natural width if it would be given
541
 * the specified @height.
542
 *
543 544
 * The returned request will be modified by the
 * GtkWidgetClass::adjust_size_request virtual method and by any
545
 * #GtkSizeGroups that have been applied. That is, the returned request
546 547 548
 * is the one that should be used for layout, not necessarily the one
 * returned by the widget itself.
 *
549
 * Since: 3.0
550 551
 */
void
552 553 554 555
gtk_widget_get_preferred_width_for_height (GtkWidget *widget,
                                           gint       height,
                                           gint      *minimum_width,
                                           gint      *natural_width)
556
{
557 558 559 560
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (minimum_width != NULL || natural_width != NULL);
  g_return_if_fail (height >= 0);

561 562 563 564 565 566
  gtk_widget_compute_size_for_orientation (widget,
                                           GTK_ORIENTATION_HORIZONTAL,
                                           height,
                                           minimum_width,
                                           natural_width,
                                           NULL, NULL);
567 568 569
}

/**
570 571
 * gtk_widget_get_preferred_height_for_width:
 * @widget: a #GtkWidget instance
572
 * @width: the width which is available for allocation
573 574
 * @minimum_height: (out) (allow-none): location for storing the minimum height, or %NULL
 * @natural_height: (out) (allow-none): location for storing the natural height, or %NULL
575
 *
576
 * Retrieves a widget’s minimum and natural height if it would be given
577
 * the specified @width.
578
 *
579 580
 * The returned request will be modified by the
 * GtkWidgetClass::adjust_size_request virtual method and by any
581
 * #GtkSizeGroups that have been applied. That is, the returned request
582 583 584
 * is the one that should be used for layout, not necessarily the one
 * returned by the widget itself.
 *
585
 * Since: 3.0
586 587
 */
void
588 589 590 591
gtk_widget_get_preferred_height_for_width (GtkWidget *widget,
                                           gint       width,
                                           gint      *minimum_height,
                                           gint      *natural_height)
592
{
593 594 595 596
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (minimum_height != NULL || natural_height != NULL);
  g_return_if_fail (width >= 0);

597 598 599 600 601 602
  gtk_widget_compute_size_for_orientation (widget,
                                           GTK_ORIENTATION_VERTICAL,
                                           width,
                                           minimum_height,
                                           natural_height,
                                           NULL, NULL);
603 604
}

605
/**
606 607 608 609 610 611 612 613
 * gtk_widget_get_preferred_height_and_baseline_for_width:
 * @widget: a #GtkWidget instance
 * @width: the width which is available for allocation, or -1 if none
 * @minimum_height: (out) (allow-none): location for storing the minimum height, or %NULL
 * @natural_height: (out) (allow-none): location for storing the natural height, or %NULL
 * @minimum_baseline: (out) (allow-none): location for storing the baseline for the minimum height, or %NULL
 * @natural_baseline: (out) (allow-none): location for storing the baseline for the natural height, or %NULL
 *
614
 * Retrieves a widget’s minimum and natural height and the corresponding baselines if it would be given
615 616 617 618 619
 * the specified @width, or the default height if @width is -1. The baselines may be -1 which means
 * that no baseline is requested for this widget.
 *
 * The returned request will be modified by the
 * GtkWidgetClass::adjust_size_request and GtkWidgetClass::adjust_baseline_request virtual methods
620
 * and by any #GtkSizeGroups that have been applied. That is, the returned request
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
 * is the one that should be used for layout, not necessarily the one
 * returned by the widget itself.
 *
 * Since: 3.10
 */
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)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (minimum_height != NULL || natural_height != NULL);
  g_return_if_fail (width >= -1);

638 639 640 641 642 643 644
  gtk_widget_compute_size_for_orientation (widget,
                                           GTK_ORIENTATION_VERTICAL,
                                           width,
                                           minimum_height,
                                           natural_height,
                                           minimum_baseline,
                                           natural_baseline);
645 646
}

647 648
/*
 * _gtk_widget_get_preferred_size_and_baseline:
649
 * @widget: a #GtkWidget instance
650 651
 * @minimum_size: (out) (allow-none): location for storing the minimum size, or %NULL
 * @natural_size: (out) (allow-none): location for storing the natural size, or %NULL
652
 *
653
 * Retrieves the minimum and natural size  and the corresponding baselines of a widget, taking
654
 * into account the widget’s preference for height-for-width management. The baselines may
655
 * be -1 which means that no baseline is requested for this widget.
656
 *
657
 * This is used to retrieve a suitable size by container widgets which do
658 659
 * not impose any restrictions on the child placement. It can be used
 * to deduce toplevel window and menu sizes as well as child widgets in
660
 * free-form containers such as GtkLayout.
661
 *
662
 * Handle with care. Note that the natural height of a height-for-width
663 664
 * widget will generally be a smaller size than the minimum height, since the required
 * height for the natural width is generally smaller than the required height for
665
 * the minimum width.
666 667
 */
void
668 669 670 671 672
_gtk_widget_get_preferred_size_and_baseline (GtkWidget      *widget,
                                             GtkRequisition *minimum_size,
                                             GtkRequisition *natural_size,
                                             gint           *minimum_baseline,
                                             gint           *natural_baseline)
673 674 675 676
{
  gint min_width, nat_width;
  gint min_height, nat_height;

677
  g_return_if_fail (GTK_IS_WIDGET (widget));
678

679
  if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
680
    {
681
      gtk_widget_get_preferred_width (widget, &min_width, &nat_width);
682 683 684 685

      if (minimum_size)
	{
	  minimum_size->width = min_width;
686 687
	  gtk_widget_get_preferred_height_and_baseline_for_width (widget, min_width,
								  &minimum_size->height, NULL, minimum_baseline, NULL);
688 689 690 691 692
	}

      if (natural_size)
	{
	  natural_size->width = nat_width;
693 694
	  gtk_widget_get_preferred_height_and_baseline_for_width (widget, nat_width,
								  NULL, &natural_size->height, NULL, natural_baseline);
695
	}
696
    }
697
  else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT or CONSTANT_SIZE */
698
    {
699
      gtk_widget_get_preferred_height_and_baseline_for_width (widget, -1, &min_height, &nat_height, minimum_baseline, natural_baseline);
700 701 702 703

      if (minimum_size)
	{
	  minimum_size->height = min_height;
704 705
	  gtk_widget_get_preferred_width_for_height (widget, min_height,
                                                     &minimum_size->width, NULL);
706 707 708 709 710
	}

      if (natural_size)
	{
	  natural_size->height = nat_height;
711 712
	  gtk_widget_get_preferred_width_for_height (widget, nat_height,
                                                     NULL, &natural_size->width);
713
	}
714 715
    }
}
716

717 718 719 720 721 722 723
/**
 * gtk_widget_get_preferred_size:
 * @widget: a #GtkWidget instance
 * @minimum_size: (out) (allow-none): location for storing the minimum size, or %NULL
 * @natural_size: (out) (allow-none): location for storing the natural size, or %NULL
 *
 * Retrieves the minimum and natural size of a widget, taking
724
 * into account the widget’s preference for height-for-width management.
725 726 727 728 729 730
 *
 * This is used to retrieve a suitable size by container widgets which do
 * not impose any restrictions on the child placement. It can be used
 * to deduce toplevel window and menu sizes as well as child widgets in
 * free-form containers such as GtkLayout.
 *
731
 * Handle with care. Note that the natural height of a height-for-width
732 733
 * widget will generally be a smaller size than the minimum height, since the required
 * height for the natural width is generally smaller than the required height for
734
 * the minimum width.
735
 *
736
 * Use gtk_widget_get_preferred_height_and_baseline_for_width() if you want to support
737 738 739 740 741 742 743 744 745
 * baseline alignment.
 *
 * Since: 3.0
 */
void
gtk_widget_get_preferred_size (GtkWidget      *widget,
                               GtkRequisition *minimum_size,
                               GtkRequisition *natural_size)
{
746 747
  _gtk_widget_get_preferred_size_and_baseline (widget, minimum_size, natural_size,
                                               NULL, NULL);
748
}
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774

static gint
compare_gap (gconstpointer p1,
	     gconstpointer p2,
	     gpointer      data)
{
  GtkRequestedSize *sizes = data;
  const guint *c1 = p1;
  const guint *c2 = p2;

  const gint d1 = MAX (sizes[*c1].natural_size -
                       sizes[*c1].minimum_size,
                       0);
  const gint d2 = MAX (sizes[*c2].natural_size -
                       sizes[*c2].minimum_size,
                       0);

  gint delta = (d2 - d1);

  if (0 == delta)
    delta = (*c2 - *c1);

  return delta;
}

/**
775
 * gtk_distribute_natural_allocation:
776 777 778 779 780 781
 * @extra_space: Extra space to redistribute among children after subtracting
 *               minimum sizes and any child padding from the overall allocation
 * @n_requested_sizes: Number of requests to fit into the allocation
 * @sizes: An array of structs with a client pointer and a minimum/natural size
 *         in the orientation of the allocation.
 *
782
 * Distributes @extra_space to child @sizes by bringing smaller
783 784 785 786 787 788 789 790 791
 * children up to natural size first.
 *
 * The remaining space will be added to the @minimum_size member of the
 * GtkRequestedSize struct. If all sizes reach their natural size then
 * the remaining space is returned.
 *
 * Returns: The remainder of @extra_space after redistributing space
 * to @sizes.
 */
792
gint
793 794 795 796
gtk_distribute_natural_allocation (gint              extra_space,
				   guint             n_requested_sizes,
				   GtkRequestedSize *sizes)
{
797
  guint *spreading;
798 799
  gint   i;

800 801
  g_return_val_if_fail (extra_space >= 0, 0);

802 803
  spreading = g_newa (guint, n_requested_sizes);

804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
  for (i = 0; i < n_requested_sizes; i++)
    spreading[i] = i;

  /* Distribute the container's extra space c_gap. We want to assign
   * this space such that the sum of extra space assigned to children
   * (c^i_gap) is equal to c_cap. The case that there's not enough
   * space for all children to take their natural size needs some
   * attention. The goals we want to achieve are:
   *
   *   a) Maximize number of children taking their natural size.
   *   b) The allocated size of children should be a continuous
   *   function of c_gap.  That is, increasing the container size by
   *   one pixel should never make drastic changes in the distribution.
   *   c) If child i takes its natural size and child j doesn't,
   *   child j should have received at least as much gap as child i.
   *
   * The following code distributes the additional space by following
   * these rules.
   */
823

824 825 826 827
  /* Sort descending by gap and position. */
  g_qsort_with_data (spreading,
		     n_requested_sizes, sizeof (guint),
		     compare_gap, sizes);
828

829 830 831 832 833 834 835 836 837 838 839 840
  /* Distribute available space.
   * This master piece of a loop was conceived by Behdad Esfahbod.
   */
  for (i = n_requested_sizes - 1; extra_space > 0 && i >= 0; --i)
    {
      /* Divide remaining space by number of remaining children.
       * Sort order and reducing remaining space by assigned space
       * ensures that space is distributed equally.
       */
      gint glue = (extra_space + i) / (i + 1);
      gint gap = sizes[(spreading[i])].natural_size
	- sizes[(spreading[i])].minimum_size;
841

842
      gint extra = MIN (glue, gap);
843

844
      sizes[spreading[i]].minimum_size += extra;
845

846 847 848 849 850
      extra_space -= extra;
    }

  return extra_space;
}
851 852 853 854 855 856

void
_gtk_widget_get_preferred_size_for_size (GtkWidget      *widget,
                                         GtkOrientation  orientation,
                                         gint            size,
                                         gint           *minimum,
857 858 859
                                         gint           *natural,
                                         gint           *minimum_baseline,
                                         gint           *natural_baseline)
860 861 862 863 864
{
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (size >= -1);

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
865 866 867 868 869 870 871 872 873 874 875
    {
      if (size < 0)
        gtk_widget_get_preferred_width (widget, minimum, natural);
      else
        gtk_widget_get_preferred_width_for_height (widget, size, minimum, natural);

      if (minimum_baseline)
        *minimum_baseline = -1;
      if (natural_baseline)
        *natural_baseline = -1;
    }
876
  else
877 878 879 880 881 882 883 884
    {
      gtk_widget_get_preferred_height_and_baseline_for_width (widget,
                                                              size,
                                                              minimum,
                                                              natural,
                                                              minimum_baseline,
                                                              natural_baseline);
    }
885 886
}