forked from AuroraMiddleware/gtk
515b1f5292
if the loop for determining max width grows too big, print an error and abort assuming that a satisfactory value was reached. This will cause wrong layout and might cause widgets to overlap, but it will not infloop. It actually works around and doesn't really fix the primary cause of the following bugs, but good enough to close them: Fixes: #4252 Fixes: #4517
1088 lines
34 KiB
C
1088 lines
34 KiB
C
/* gtkboxlayout.c: Box layout manager
|
|
*
|
|
* Copyright 2019 GNOME Foundation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkboxlayout.h"
|
|
|
|
#include "gtkcsspositionvalueprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkorientable.h"
|
|
#include "gtkprivate.h"
|
|
#include "gtksizerequest.h"
|
|
#include "gtktypebuiltins.h"
|
|
#include "gtkwidgetprivate.h"
|
|
#include "gtkcssnodeprivate.h"
|
|
|
|
/**
|
|
* GtkBoxLayout:
|
|
*
|
|
* `GtkBoxLayout` is a layout manager that arranges children in a single
|
|
* row or column.
|
|
*
|
|
* Whether it is a row or column depends on the value of its
|
|
* [property@Gtk.Orientable:orientation] property. Within the other dimension
|
|
* all children all allocated the same size. The `GtkBoxLayout` will respect
|
|
* the [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign]
|
|
* properties of each child widget.
|
|
*
|
|
* If you want all children to be assigned the same size, you can use
|
|
* the [property@Gtk.BoxLayout:homogeneous] property.
|
|
*
|
|
* If you want to specify the amount of space placed between each child,
|
|
* you can use the [property@Gtk.BoxLayout:spacing] property.
|
|
*/
|
|
|
|
struct _GtkBoxLayout
|
|
{
|
|
GtkLayoutManager parent_instance;
|
|
|
|
gboolean homogeneous;
|
|
guint spacing;
|
|
GtkOrientation orientation;
|
|
GtkBaselinePosition baseline_position;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
|
|
|
|
enum {
|
|
PROP_HOMOGENEOUS = 1,
|
|
PROP_SPACING,
|
|
PROP_BASELINE_POSITION,
|
|
|
|
/* From GtkOrientable */
|
|
PROP_ORIENTATION,
|
|
|
|
N_PROPS = PROP_ORIENTATION
|
|
};
|
|
|
|
static GParamSpec *box_layout_props[N_PROPS];
|
|
|
|
static void
|
|
gtk_box_layout_set_orientation (GtkBoxLayout *self,
|
|
GtkOrientation orientation)
|
|
{
|
|
GtkLayoutManager *layout_manager = GTK_LAYOUT_MANAGER (self);
|
|
GtkWidget *widget;
|
|
|
|
if (self->orientation == orientation)
|
|
return;
|
|
|
|
self->orientation = orientation;
|
|
|
|
widget = gtk_layout_manager_get_widget (layout_manager);
|
|
if (widget != NULL && GTK_IS_ORIENTABLE (widget))
|
|
gtk_widget_update_orientation (widget, self->orientation);
|
|
|
|
gtk_layout_manager_layout_changed (layout_manager);
|
|
|
|
g_object_notify (G_OBJECT (self), "orientation");
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_set_property (GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkBoxLayout *self = GTK_BOX_LAYOUT (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_HOMOGENEOUS:
|
|
gtk_box_layout_set_homogeneous (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_SPACING:
|
|
gtk_box_layout_set_spacing (self, g_value_get_int (value));
|
|
break;
|
|
|
|
case PROP_BASELINE_POSITION:
|
|
gtk_box_layout_set_baseline_position (self, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_ORIENTATION:
|
|
gtk_box_layout_set_orientation (self, g_value_get_enum (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkBoxLayout *box_layout = GTK_BOX_LAYOUT (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_HOMOGENEOUS:
|
|
g_value_set_boolean (value, box_layout->homogeneous);
|
|
break;
|
|
|
|
case PROP_SPACING:
|
|
g_value_set_int (value, box_layout->spacing);
|
|
break;
|
|
|
|
case PROP_BASELINE_POSITION:
|
|
g_value_set_enum (value, box_layout->baseline_position);
|
|
break;
|
|
|
|
case PROP_ORIENTATION:
|
|
g_value_set_enum (value, box_layout->orientation);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
count_expand_children (GtkWidget *widget,
|
|
GtkOrientation orientation,
|
|
int *visible_children,
|
|
int *expand_children)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
*visible_children = *expand_children = 0;
|
|
|
|
for (child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
*visible_children += 1;
|
|
|
|
if (gtk_widget_compute_expand (child, orientation))
|
|
*expand_children += 1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
get_spacing (GtkBoxLayout *self,
|
|
GtkCssNode *node)
|
|
{
|
|
GtkCssStyle *style = gtk_css_node_get_style (node);
|
|
GtkCssValue *border_spacing;
|
|
int css_spacing;
|
|
|
|
border_spacing = style->size->border_spacing;
|
|
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
css_spacing = _gtk_css_position_value_get_x (border_spacing, 100);
|
|
else
|
|
css_spacing = _gtk_css_position_value_get_y (border_spacing, 100);
|
|
|
|
return css_spacing + self->spacing;
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_compute_size (GtkBoxLayout *self,
|
|
GtkWidget *widget,
|
|
int for_size,
|
|
int *minimum,
|
|
int *natural)
|
|
{
|
|
GtkWidget *child;
|
|
int n_visible_children = 0;
|
|
int required_min = 0, required_nat = 0;
|
|
int largest_min = 0, largest_nat = 0;
|
|
int spacing = get_spacing (self, gtk_widget_get_css_node (widget));
|
|
|
|
for (child = gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
int child_min = 0;
|
|
int child_nat = 0;
|
|
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
gtk_widget_measure (child, self->orientation,
|
|
for_size,
|
|
&child_min, &child_nat,
|
|
NULL, NULL);
|
|
|
|
largest_min = MAX (largest_min, child_min);
|
|
largest_nat = MAX (largest_nat, child_nat);
|
|
|
|
required_min += child_min;
|
|
required_nat += child_nat;
|
|
|
|
n_visible_children += 1;
|
|
}
|
|
|
|
if (n_visible_children > 0)
|
|
{
|
|
if (self->homogeneous)
|
|
{
|
|
required_min = largest_min * n_visible_children;
|
|
required_nat = largest_nat * n_visible_children;
|
|
}
|
|
|
|
required_min += (n_visible_children - 1) * spacing;
|
|
required_nat += (n_visible_children - 1) * spacing;
|
|
}
|
|
|
|
*minimum = required_min;
|
|
*natural = required_nat;
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
|
|
GtkWidget *widget,
|
|
int *minimum,
|
|
int *natural,
|
|
int *min_baseline,
|
|
int *nat_baseline)
|
|
{
|
|
GtkWidget *child;
|
|
int largest_min = 0, largest_nat = 0;
|
|
|
|
for (child = gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
int child_min = 0;
|
|
int child_nat = 0;
|
|
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
gtk_widget_measure (child,
|
|
OPPOSITE_ORIENTATION (self->orientation),
|
|
-1,
|
|
&child_min, &child_nat,
|
|
NULL, NULL);
|
|
|
|
largest_min = MAX (largest_min, child_min);
|
|
largest_nat = MAX (largest_nat, child_nat);
|
|
}
|
|
|
|
*minimum = largest_min;
|
|
*natural = largest_nat;
|
|
}
|
|
|
|
/* if widgets haven't reached their min opposite size at this
|
|
* huge value, things went massively wrong and we need to bail to not
|
|
* cause an infinite loop.
|
|
*/
|
|
#define MAX_ALLOWED_SIZE (1 << 20)
|
|
|
|
static int
|
|
distribute_remaining_size (GtkRequestedSize *sizes,
|
|
gsize n_sizes,
|
|
GtkOrientation orientation,
|
|
int available,
|
|
int min,
|
|
int max)
|
|
{
|
|
int total_size = 0;
|
|
gsize i;
|
|
|
|
if (n_sizes == 0)
|
|
return available;
|
|
|
|
for (i = 0; i < n_sizes; i++)
|
|
{
|
|
gtk_widget_measure (sizes[i].data,
|
|
orientation,
|
|
min,
|
|
&sizes[i].minimum_size, &sizes[i].natural_size,
|
|
NULL, NULL);
|
|
total_size += sizes[i].minimum_size;
|
|
}
|
|
|
|
if (total_size <= available)
|
|
return available - total_size;
|
|
|
|
/* total_size > available happens when we last ran for values too big,
|
|
* rerun for the correct value min == max in that case */
|
|
while (min < max || total_size > available)
|
|
{
|
|
int test;
|
|
|
|
if (min > MAX_ALLOWED_SIZE)
|
|
{
|
|
/* sanity check! */
|
|
for (i = 0; i < n_sizes; i++)
|
|
{
|
|
int check_min, check_nat;
|
|
gtk_widget_measure (sizes[i].data,
|
|
orientation,
|
|
MAX_ALLOWED_SIZE,
|
|
&sizes[i].minimum_size, &sizes[i].natural_size,
|
|
NULL, NULL);
|
|
gtk_widget_measure (sizes[i].data,
|
|
orientation,
|
|
-1,
|
|
&check_min, &check_nat,
|
|
NULL, NULL);
|
|
if (check_min < sizes[i].minimum_size)
|
|
{
|
|
g_critical ("%s %p reports a minimum %s of %u, but minimum %s for %s of %u is %u. Expect overlapping widgets.",
|
|
G_OBJECT_TYPE_NAME (sizes[i].data), sizes[i].data,
|
|
orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height",
|
|
check_min,
|
|
orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height",
|
|
orientation == GTK_ORIENTATION_HORIZONTAL ? "height" : "width",
|
|
MAX_ALLOWED_SIZE, sizes[i].minimum_size);
|
|
sizes[i].minimum_size = check_min;
|
|
sizes[i].natural_size = check_nat;
|
|
}
|
|
total_size += sizes[i].minimum_size;
|
|
}
|
|
return MAX (0, available - total_size);
|
|
}
|
|
|
|
if (max == MAX_ALLOWED_SIZE)
|
|
test = min * 2;
|
|
else
|
|
test = (min + max) / 2;
|
|
|
|
total_size = 0;
|
|
for (i = 0; i < n_sizes; i++)
|
|
{
|
|
gtk_widget_measure (sizes[i].data,
|
|
orientation,
|
|
test,
|
|
&sizes[i].minimum_size, &sizes[i].natural_size,
|
|
NULL, NULL);
|
|
total_size += sizes[i].minimum_size;
|
|
}
|
|
|
|
if (total_size > available)
|
|
min = test + 1;
|
|
else
|
|
max = test;
|
|
}
|
|
|
|
return available - total_size;
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self,
|
|
GtkWidget *widget,
|
|
int for_size,
|
|
int *minimum,
|
|
int *natural,
|
|
int *min_baseline,
|
|
int *nat_baseline)
|
|
{
|
|
GtkWidget *child;
|
|
int nvis_children;
|
|
int nexpand_children;
|
|
int computed_minimum = 0, computed_natural = 0;
|
|
int computed_minimum_above = 0, computed_natural_above = 0;
|
|
int computed_minimum_below = 0, computed_natural_below = 0;
|
|
int computed_minimum_baseline = -1, computed_natural_baseline = -1;
|
|
GtkRequestedSize *sizes;
|
|
int available, size_given_to_child, i;
|
|
int child_size, child_minimum, child_natural;
|
|
int child_minimum_baseline, child_natural_baseline;
|
|
int n_extra_widgets = 0;
|
|
int spacing;
|
|
gboolean have_baseline = FALSE;
|
|
|
|
count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children);
|
|
|
|
if (nvis_children <= 0)
|
|
return;
|
|
|
|
spacing = get_spacing (self, gtk_widget_get_css_node (widget));
|
|
sizes = g_newa (GtkRequestedSize, nvis_children);
|
|
g_assert ((nvis_children - 1) * spacing <= for_size);
|
|
available = for_size - (nvis_children - 1) * spacing;
|
|
|
|
if (self->homogeneous)
|
|
{
|
|
size_given_to_child = available / nvis_children;
|
|
n_extra_widgets = available % nvis_children;
|
|
|
|
for (child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
child_size = size_given_to_child;
|
|
if (n_extra_widgets)
|
|
{
|
|
child_size++;
|
|
n_extra_widgets--;
|
|
}
|
|
|
|
child_minimum_baseline = child_natural_baseline = -1;
|
|
/* Assign the child's position. */
|
|
gtk_widget_measure (child,
|
|
OPPOSITE_ORIENTATION (self->orientation),
|
|
child_size,
|
|
&child_minimum, &child_natural,
|
|
&child_minimum_baseline, &child_natural_baseline);
|
|
|
|
if (child_minimum_baseline >= 0)
|
|
{
|
|
have_baseline = TRUE;
|
|
computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
|
|
computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
|
|
computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
|
|
computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
|
|
}
|
|
else
|
|
{
|
|
computed_minimum = MAX (computed_minimum, child_minimum);
|
|
computed_natural = MAX (computed_natural, child_natural);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int min_size = 0, child_min_size;
|
|
int n_inconstant = 0;
|
|
|
|
/* Retrieve desired size for visible children */
|
|
for (i = 0, child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_CONSTANT_SIZE)
|
|
{
|
|
gtk_widget_measure (child,
|
|
self->orientation,
|
|
-1,
|
|
&sizes[i].minimum_size, &sizes[i].natural_size,
|
|
NULL, NULL);
|
|
sizes[i].data = child;
|
|
g_assert (available >= sizes[i].minimum_size);
|
|
available -= sizes[i].minimum_size;
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_measure (child,
|
|
OPPOSITE_ORIENTATION (self->orientation),
|
|
-1,
|
|
&child_min_size, NULL,
|
|
NULL, NULL);
|
|
min_size = MAX (min_size, child_min_size);
|
|
n_inconstant++;
|
|
sizes[nvis_children - n_inconstant].data = child;
|
|
}
|
|
}
|
|
|
|
available = distribute_remaining_size (sizes + nvis_children - n_inconstant,
|
|
n_inconstant,
|
|
self->orientation,
|
|
available,
|
|
min_size,
|
|
MAX_ALLOWED_SIZE);
|
|
|
|
/* Bring children up to size first */
|
|
available = gtk_distribute_natural_allocation (available, nvis_children, sizes);
|
|
|
|
/* Calculate space which hasn't distributed yet,
|
|
* and is available for expanding children.
|
|
*/
|
|
if (nexpand_children > 0)
|
|
{
|
|
size_given_to_child = available / nexpand_children;
|
|
n_extra_widgets = available % nexpand_children;
|
|
}
|
|
else
|
|
{
|
|
size_given_to_child = 0;
|
|
}
|
|
|
|
i = 0;
|
|
n_inconstant = 0;
|
|
for (child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
if (sizes[i].data == child)
|
|
{
|
|
child_size = sizes[i].minimum_size;
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
n_inconstant++;
|
|
g_assert (sizes[nvis_children - n_inconstant].data == child);
|
|
child_size = sizes[nvis_children - n_inconstant].minimum_size;
|
|
}
|
|
|
|
if (gtk_widget_compute_expand (child, self->orientation))
|
|
{
|
|
child_size += size_given_to_child;
|
|
|
|
if (n_extra_widgets > 0)
|
|
{
|
|
child_size++;
|
|
n_extra_widgets--;
|
|
}
|
|
}
|
|
|
|
child_minimum_baseline = child_natural_baseline = -1;
|
|
/* Assign the child's position. */
|
|
gtk_widget_measure (child,
|
|
OPPOSITE_ORIENTATION (self->orientation),
|
|
child_size,
|
|
&child_minimum, &child_natural,
|
|
&child_minimum_baseline, &child_natural_baseline);
|
|
|
|
if (child_minimum_baseline >= 0)
|
|
{
|
|
have_baseline = TRUE;
|
|
computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
|
|
computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
|
|
computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
|
|
computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
|
|
}
|
|
else
|
|
{
|
|
computed_minimum = MAX (computed_minimum, child_minimum);
|
|
computed_natural = MAX (computed_natural, child_natural);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (have_baseline)
|
|
{
|
|
computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
|
|
computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
|
|
switch (self->baseline_position)
|
|
{
|
|
case GTK_BASELINE_POSITION_TOP:
|
|
computed_minimum_baseline = computed_minimum_above;
|
|
computed_natural_baseline = computed_natural_above;
|
|
break;
|
|
case GTK_BASELINE_POSITION_CENTER:
|
|
computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
|
|
computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
|
|
break;
|
|
case GTK_BASELINE_POSITION_BOTTOM:
|
|
computed_minimum_baseline = computed_minimum - computed_minimum_below;
|
|
computed_natural_baseline = computed_natural - computed_natural_below;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
*minimum = computed_minimum;
|
|
*natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
|
|
*min_baseline = computed_minimum_baseline;
|
|
*nat_baseline = computed_natural_baseline;
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_measure (GtkLayoutManager *layout_manager,
|
|
GtkWidget *widget,
|
|
GtkOrientation orientation,
|
|
int for_size,
|
|
int *minimum,
|
|
int *natural,
|
|
int *min_baseline,
|
|
int *nat_baseline)
|
|
{
|
|
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
|
|
|
|
if (self->orientation != orientation)
|
|
{
|
|
if (for_size < 0)
|
|
{
|
|
gtk_box_layout_compute_opposite_size (self, widget,
|
|
minimum, natural,
|
|
min_baseline, nat_baseline);
|
|
}
|
|
else
|
|
{
|
|
gtk_box_layout_compute_opposite_size_for_size (self, widget, for_size,
|
|
minimum, natural,
|
|
min_baseline, nat_baseline);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gtk_box_layout_compute_size (self, widget, for_size,
|
|
minimum, natural);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_allocate (GtkLayoutManager *layout_manager,
|
|
GtkWidget *widget,
|
|
int width,
|
|
int height,
|
|
int baseline)
|
|
{
|
|
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
|
|
GtkWidget *child;
|
|
int nvis_children;
|
|
int nexpand_children;
|
|
GtkTextDirection direction;
|
|
GtkAllocation child_allocation;
|
|
GtkRequestedSize *sizes;
|
|
int child_minimum_baseline, child_natural_baseline;
|
|
int minimum_above, natural_above;
|
|
int minimum_below, natural_below;
|
|
gboolean have_baseline;
|
|
int extra_space;
|
|
int children_minimum_size = 0;
|
|
int size_given_to_child;
|
|
int n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */
|
|
int x = 0, y = 0, i;
|
|
int child_size;
|
|
int spacing;
|
|
|
|
count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children);
|
|
|
|
/* If there is no visible child, simply return. */
|
|
if (nvis_children <= 0)
|
|
return;
|
|
|
|
direction = _gtk_widget_get_direction (widget);
|
|
sizes = g_newa (GtkRequestedSize, nvis_children);
|
|
spacing = get_spacing (self, gtk_widget_get_css_node (widget));
|
|
|
|
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
extra_space = width - (nvis_children - 1) * spacing;
|
|
else
|
|
extra_space = height - (nvis_children - 1) * spacing;
|
|
|
|
have_baseline = FALSE;
|
|
minimum_above = natural_above = 0;
|
|
minimum_below = natural_below = 0;
|
|
|
|
/* Retrieve desired size for visible children. */
|
|
for (i = 0, child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
gtk_widget_measure (child,
|
|
self->orientation,
|
|
self->orientation == GTK_ORIENTATION_HORIZONTAL ? height : width,
|
|
&sizes[i].minimum_size, &sizes[i].natural_size,
|
|
NULL, NULL);
|
|
|
|
children_minimum_size += sizes[i].minimum_size;
|
|
|
|
sizes[i].data = child;
|
|
|
|
i++;
|
|
}
|
|
|
|
if (self->homogeneous)
|
|
{
|
|
/* We still need to run the above loop to populate the minimum sizes for
|
|
* children that aren't going to fill.
|
|
*/
|
|
|
|
size_given_to_child = extra_space / nvis_children;
|
|
n_extra_widgets = extra_space % nvis_children;
|
|
}
|
|
else
|
|
{
|
|
/* Bring children up to size first */
|
|
extra_space -= children_minimum_size;
|
|
extra_space = MAX (0, extra_space);
|
|
extra_space = gtk_distribute_natural_allocation (extra_space, nvis_children, sizes);
|
|
|
|
/* Calculate space which hasn't distributed yet,
|
|
* and is available for expanding children.
|
|
*/
|
|
if (nexpand_children > 0)
|
|
{
|
|
size_given_to_child = extra_space / nexpand_children;
|
|
n_extra_widgets = extra_space % nexpand_children;
|
|
}
|
|
else
|
|
{
|
|
size_given_to_child = 0;
|
|
}
|
|
}
|
|
|
|
/* Allocate child sizes. */
|
|
for (i = 0, child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
/* Assign the child's size. */
|
|
if (self->homogeneous)
|
|
{
|
|
child_size = size_given_to_child;
|
|
|
|
if (n_extra_widgets > 0)
|
|
{
|
|
child_size++;
|
|
n_extra_widgets--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
child_size = sizes[i].minimum_size;
|
|
|
|
if (gtk_widget_compute_expand (child, self->orientation))
|
|
{
|
|
child_size += size_given_to_child;
|
|
|
|
if (n_extra_widgets > 0)
|
|
{
|
|
child_size++;
|
|
n_extra_widgets--;
|
|
}
|
|
}
|
|
}
|
|
|
|
sizes[i].natural_size = child_size;
|
|
|
|
if (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
|
|
gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE)
|
|
{
|
|
int child_allocation_width;
|
|
int child_minimum_height, child_natural_height;
|
|
|
|
child_allocation_width = child_size;
|
|
|
|
child_minimum_baseline = -1;
|
|
child_natural_baseline = -1;
|
|
gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL,
|
|
child_allocation_width,
|
|
&child_minimum_height, &child_natural_height,
|
|
&child_minimum_baseline, &child_natural_baseline);
|
|
|
|
if (child_minimum_baseline >= 0)
|
|
{
|
|
have_baseline = TRUE;
|
|
minimum_below = MAX (minimum_below, child_minimum_height - child_minimum_baseline);
|
|
natural_below = MAX (natural_below, child_natural_height - child_natural_baseline);
|
|
minimum_above = MAX (minimum_above, child_minimum_baseline);
|
|
natural_above = MAX (natural_above, child_natural_baseline);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
if (self->orientation == GTK_ORIENTATION_VERTICAL)
|
|
baseline = -1;
|
|
|
|
/* we only calculate our own baseline if we don't get one passed from the parent
|
|
* and any of the child widgets explicitly request one */
|
|
if (baseline == -1 && have_baseline)
|
|
{
|
|
/* TODO: This is purely based on the minimum baseline, when things fit we should
|
|
use the natural one? */
|
|
|
|
switch (self->baseline_position)
|
|
{
|
|
case GTK_BASELINE_POSITION_TOP:
|
|
baseline = minimum_above;
|
|
break;
|
|
case GTK_BASELINE_POSITION_CENTER:
|
|
baseline = minimum_above + (height - (minimum_above + minimum_below)) / 2;
|
|
break;
|
|
case GTK_BASELINE_POSITION_BOTTOM:
|
|
baseline = height - minimum_below;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Allocate child positions. */
|
|
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
child_allocation.y = 0;
|
|
child_allocation.height = height;
|
|
x = 0;
|
|
}
|
|
else
|
|
{
|
|
child_allocation.x = 0;
|
|
child_allocation.width = width;
|
|
y = 0;
|
|
}
|
|
|
|
for (i = 0, child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!gtk_widget_should_layout (child))
|
|
continue;
|
|
|
|
child_size = sizes[i].natural_size;
|
|
|
|
/* Assign the child's position. */
|
|
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
child_allocation.width = child_size;
|
|
child_allocation.x = x;
|
|
|
|
x += child_size + spacing;
|
|
|
|
if (direction == GTK_TEXT_DIR_RTL)
|
|
child_allocation.x = width - child_allocation.x - child_allocation.width;
|
|
|
|
}
|
|
else /* (self->orientation == GTK_ORIENTATION_VERTICAL) */
|
|
{
|
|
child_allocation.height = child_size;
|
|
child_allocation.y = y;
|
|
|
|
y += child_size + spacing;
|
|
}
|
|
|
|
gtk_widget_size_allocate (child, &child_allocation, baseline);
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_class_init (GtkBoxLayoutClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
|
|
|
|
gobject_class->set_property = gtk_box_layout_set_property;
|
|
gobject_class->get_property = gtk_box_layout_get_property;
|
|
|
|
layout_manager_class->measure = gtk_box_layout_measure;
|
|
layout_manager_class->allocate = gtk_box_layout_allocate;
|
|
|
|
/**
|
|
* GtkBoxLayout:homogeneous: (attributes org.gtk.Property.get=gtk_box_layout_get_homogeneous org.gtk.Property.set=gtk_box_layout_set_homogeneous)
|
|
*
|
|
* Whether the box layout should distribute the available space
|
|
* equally among the children.
|
|
*/
|
|
box_layout_props[PROP_HOMOGENEOUS] =
|
|
g_param_spec_boolean ("homogeneous",
|
|
P_("Homogeneous"),
|
|
P_("Distribute space homogeneously"),
|
|
FALSE,
|
|
GTK_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkBoxLayout:spacing: (attributes org.gtk.Property.get=gtk_box_layout_get_spacing org.gtk.Property.set=gtk_box_layout_set_spacing)
|
|
*
|
|
* The space to put between the children.
|
|
*/
|
|
box_layout_props[PROP_SPACING] =
|
|
g_param_spec_int ("spacing",
|
|
P_("Spacing"),
|
|
P_("Spacing between widgets"),
|
|
0, G_MAXINT, 0,
|
|
GTK_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
/**
|
|
* GtkBoxLayout:baseline-position: (attributes org.gtk.Property.get=gtk_box_layout_get_baseline_position org.gtk.Property.set=gtk_box_layout_set_baseline_position)
|
|
*
|
|
* The position of the allocated baseline within the extra space
|
|
* allocated to each child.
|
|
*
|
|
* This property is only relevant for horizontal layouts containing
|
|
* at least one child with a baseline alignment.
|
|
*/
|
|
box_layout_props[PROP_BASELINE_POSITION] =
|
|
g_param_spec_enum ("baseline-position",
|
|
P_("Baseline position"),
|
|
P_("The position of the baseline aligned widgets if extra space is available"),
|
|
GTK_TYPE_BASELINE_POSITION,
|
|
GTK_BASELINE_POSITION_CENTER,
|
|
GTK_PARAM_READWRITE |
|
|
G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
g_object_class_install_properties (gobject_class, N_PROPS, box_layout_props);
|
|
g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
|
|
}
|
|
|
|
static void
|
|
gtk_box_layout_init (GtkBoxLayout *self)
|
|
{
|
|
self->homogeneous = FALSE;
|
|
self->spacing = 0;
|
|
self->orientation = GTK_ORIENTATION_HORIZONTAL;
|
|
self->baseline_position = GTK_BASELINE_POSITION_CENTER;
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_new:
|
|
* @orientation: the orientation for the new layout
|
|
*
|
|
* Creates a new `GtkBoxLayout`.
|
|
*
|
|
* Returns: a new box layout
|
|
*/
|
|
GtkLayoutManager *
|
|
gtk_box_layout_new (GtkOrientation orientation)
|
|
{
|
|
return g_object_new (GTK_TYPE_BOX_LAYOUT,
|
|
"orientation", orientation,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_set_homogeneous: (attributes org.gtk.Method.set_property=homogeneous)
|
|
* @box_layout: a `GtkBoxLayout`
|
|
* @homogeneous: %TRUE to set the box layout as homogeneous
|
|
*
|
|
* Sets whether the box layout will allocate the same
|
|
* size to all children.
|
|
*/
|
|
void
|
|
gtk_box_layout_set_homogeneous (GtkBoxLayout *box_layout,
|
|
gboolean homogeneous)
|
|
{
|
|
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
|
|
|
|
homogeneous = !!homogeneous;
|
|
if (box_layout->homogeneous == homogeneous)
|
|
return;
|
|
|
|
box_layout->homogeneous = homogeneous;
|
|
|
|
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
|
|
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_HOMOGENEOUS]);
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_get_homogeneous: (attributes org.gtk.Method.get_property=homogeneous)
|
|
* @box_layout: a `GtkBoxLayout`
|
|
*
|
|
* Returns whether the layout is set to be homogeneous.
|
|
*
|
|
* Return: %TRUE if the layout is homogeneous
|
|
*/
|
|
gboolean
|
|
gtk_box_layout_get_homogeneous (GtkBoxLayout *box_layout)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), FALSE);
|
|
|
|
return box_layout->homogeneous;
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_set_spacing: (attributes org.gtk.Method.set_property=spacing)
|
|
* @box_layout: a `GtkBoxLayout`
|
|
* @spacing: the spacing to apply between children
|
|
*
|
|
* Sets how much spacing to put between children.
|
|
*/
|
|
void
|
|
gtk_box_layout_set_spacing (GtkBoxLayout *box_layout,
|
|
guint spacing)
|
|
{
|
|
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
|
|
|
|
if (box_layout->spacing == spacing)
|
|
return;
|
|
|
|
box_layout->spacing = spacing;
|
|
|
|
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
|
|
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_SPACING]);
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_get_spacing: (attributes org.gtk.Method.get_property=spacing)
|
|
* @box_layout: a `GtkBoxLayout`
|
|
*
|
|
* Returns the space that @box_layout puts between children.
|
|
*
|
|
* Returns: the spacing of the layout
|
|
*/
|
|
guint
|
|
gtk_box_layout_get_spacing (GtkBoxLayout *box_layout)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), 0);
|
|
|
|
return box_layout->spacing;
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_set_baseline_position: (attributes org.gtk.Method.set_property=baseline-position)
|
|
* @box_layout: a `GtkBoxLayout`
|
|
* @position: a `GtkBaselinePosition`
|
|
*
|
|
* Sets the baseline position of a box layout.
|
|
*
|
|
* The baseline position affects only horizontal boxes with at least one
|
|
* baseline aligned child. If there is more vertical space available than
|
|
* requested, and the baseline is not allocated by the parent then the
|
|
* given @position is used to allocate the baseline within the extra
|
|
* space available.
|
|
*/
|
|
void
|
|
gtk_box_layout_set_baseline_position (GtkBoxLayout *box_layout,
|
|
GtkBaselinePosition position)
|
|
{
|
|
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
|
|
|
|
if (box_layout->baseline_position != position)
|
|
{
|
|
box_layout->baseline_position = position;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_BASELINE_POSITION]);
|
|
|
|
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gtk_box_layout_get_baseline_position: (attributes org.gtk.Method.get_property=baseline-position)
|
|
* @box_layout: a `GtkBoxLayout`
|
|
*
|
|
* Gets the value set by gtk_box_layout_set_baseline_position().
|
|
*
|
|
* Returns: the baseline position
|
|
*/
|
|
GtkBaselinePosition
|
|
gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), GTK_BASELINE_POSITION_CENTER);
|
|
|
|
return box_layout->baseline_position;
|
|
}
|