/* gtkgridlayout.c: Layout manager for grid-like widgets
* Copyright 2019 GNOME Foundation
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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.1 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 .
*/
/**
* SECTION:gtkgridlayout
* @Short_description: Layout manager for grid-like widgets
* @Title: GtkGridLayout
* @See_also: #GtkBoxLayout
*
* GtkGridLayout is a layout manager which arranges child widgets in
* rows and columns, with arbitrary positions and horizontal/vertical
* spans.
*
* Children have an "attach point" defined by the horizontal and vertical
* index of the cell they occupy; children can span multiple rows or columns.
* The layout properties for setting the attach points and spans are set
* using the #GtkGridLayoutChild associated to each child widget.
*
* The behaviour of GtkGrid when several children occupy the same grid cell
* is undefined.
*
* GtkGridLayout can be used like a #GtkBoxLayout if all children are attached
* to the same row or column; however, if you only ever need a single row or
* column, you should consider using #GtkBoxLayout.
*/
#include "config.h"
#include "gtkgridlayout.h"
#include "gtkcontainerprivate.h"
#include "gtkcsspositionvalueprivate.h"
#include "gtkdebug.h"
#include "gtkintl.h"
#include "gtklayoutchild.h"
#include "gtkorientableprivate.h"
#include "gtkprivate.h"
#include "gtksizerequest.h"
#include "gtkstylecontextprivate.h"
#include "gtkwidgetprivate.h"
/* {{{ GtkGridLayoutChild */
typedef struct {
int pos;
int span;
} GridChildAttach;
struct _GtkGridLayoutChild
{
GtkLayoutChild parent_instance;
GridChildAttach attach[2];
};
#define CHILD_LEFT_ATTACH(child) ((child)->attach[GTK_ORIENTATION_HORIZONTAL].pos)
#define CHILD_COL_SPAN(child) ((child)->attach[GTK_ORIENTATION_HORIZONTAL].span)
#define CHILD_TOP_ATTACH(child) ((child)->attach[GTK_ORIENTATION_VERTICAL].pos)
#define CHILD_ROW_SPAN(child) ((child)->attach[GTK_ORIENTATION_VERTICAL].span)
enum {
PROP_CHILD_LEFT_ATTACH = 1,
PROP_CHILD_TOP_ATTACH,
PROP_CHILD_COLUMN_SPAN,
PROP_CHILD_ROW_SPAN,
N_CHILD_PROPERTIES
};
static GParamSpec *child_props[N_CHILD_PROPERTIES];
G_DEFINE_TYPE (GtkGridLayoutChild, gtk_grid_layout_child, GTK_TYPE_LAYOUT_CHILD)
static void
gtk_grid_layout_child_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkGridLayoutChild *self = GTK_GRID_LAYOUT_CHILD (gobject);
switch (prop_id)
{
case PROP_CHILD_LEFT_ATTACH:
gtk_grid_layout_child_set_left_attach (self, g_value_get_int (value));
break;
case PROP_CHILD_TOP_ATTACH:
gtk_grid_layout_child_set_top_attach (self, g_value_get_int (value));
break;
case PROP_CHILD_COLUMN_SPAN:
gtk_grid_layout_child_set_column_span (self, g_value_get_int (value));
break;
case PROP_CHILD_ROW_SPAN:
gtk_grid_layout_child_set_row_span (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gtk_grid_layout_child_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkGridLayoutChild *self = GTK_GRID_LAYOUT_CHILD (gobject);
switch (prop_id)
{
case PROP_CHILD_LEFT_ATTACH:
g_value_set_int (value, CHILD_LEFT_ATTACH (self));
break;
case PROP_CHILD_TOP_ATTACH:
g_value_set_int (value, CHILD_TOP_ATTACH (self));
break;
case PROP_CHILD_COLUMN_SPAN:
g_value_set_int (value, CHILD_COL_SPAN (self));
break;
case PROP_CHILD_ROW_SPAN:
g_value_set_int (value, CHILD_ROW_SPAN (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gtk_grid_layout_child_class_init (GtkGridLayoutChildClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gtk_grid_layout_child_set_property;
gobject_class->get_property = gtk_grid_layout_child_get_property;
/**
* GtkGridLayoutChild:left-attach:
*
* The column number to attach the left side of the child to.
*/
child_props[PROP_CHILD_LEFT_ATTACH] =
g_param_spec_int ("left-attach",
P_("Left attachment"),
P_("The column number to attach the left side of the child to"),
G_MININT, G_MAXINT, 0,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayoutChild:top-attach:
*
* The row number to attach the top side of the child to.
*/
child_props[PROP_CHILD_TOP_ATTACH] =
g_param_spec_int ("top-attach",
P_("Top attachment"),
P_("The row number to attach the top side of a child widget to"),
G_MININT, G_MAXINT, 0,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayoutChild:column-span:
*
* The number of columns the child spans to.
*/
child_props[PROP_CHILD_COLUMN_SPAN] =
g_param_spec_int ("column-span",
P_("Column span"),
P_("The number of columns that a child spans"),
1, G_MAXINT, 1,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayoutChild:row-span:
*
* The number of rows the child spans to.
*/
child_props[PROP_CHILD_ROW_SPAN] =
g_param_spec_int ("row-span",
P_("Row span"),
P_("The number of rows that a child spans"),
1, G_MAXINT, 1,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_CHILD_PROPERTIES, child_props);
}
static void
gtk_grid_layout_child_init (GtkGridLayoutChild *self)
{
}
/**
* gtk_grid_layout_child_set_top_attach:
* @child: a #GtkGridLayoutChild
* @attach: the attach point for @child
*
* Sets the row number to attach the top side of @child.
*/
void
gtk_grid_layout_child_set_top_attach (GtkGridLayoutChild *child,
int attach)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
if (CHILD_TOP_ATTACH (child) == attach)
return;
CHILD_TOP_ATTACH (child) = attach;
gtk_layout_manager_layout_changed (gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child)));
g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_CHILD_TOP_ATTACH]);
}
/**
* gtk_grid_layout_child_get_top_attach:
* @child: a #GtkGridLayoutChild
*
* Retrieves the row number to which @child attaches its top side.
*
* Returns: the row number
*/
int
gtk_grid_layout_child_get_top_attach (GtkGridLayoutChild *child)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 0);
return CHILD_TOP_ATTACH (child);
}
/**
* gtk_grid_layout_child_set_left_attach:
* @child: a #GtkGridLayoutChild
* @attach: the attach point for @child
*
* Sets the column number to attach the left side of @child.
*/
void
gtk_grid_layout_child_set_left_attach (GtkGridLayoutChild *child,
int attach)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
if (CHILD_LEFT_ATTACH (child) == attach)
return;
CHILD_LEFT_ATTACH (child) = attach;
gtk_layout_manager_layout_changed (gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child)));
g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_CHILD_LEFT_ATTACH]);
}
/**
* gtk_grid_layout_child_get_left_attach:
* @child: a #GtkGridLayoutChild
*
* Retrieves the column number to which @child attaches its left side.
*
* Returns: the column number
*/
int
gtk_grid_layout_child_get_left_attach (GtkGridLayoutChild *child)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 0);
return CHILD_LEFT_ATTACH (child);
}
/**
* gtk_grid_layout_child_set_column_span:
* @child: a #GtkGridLayoutChild
* @span: the span of @child
*
* Sets the number of columns @child spans to.
*/
void
gtk_grid_layout_child_set_column_span (GtkGridLayoutChild *child,
int span)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
if (CHILD_COL_SPAN (child) == span)
return;
CHILD_COL_SPAN (child) = span;
gtk_layout_manager_layout_changed (gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child)));
g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_CHILD_COLUMN_SPAN]);
}
/**
* gtk_grid_layout_child_get_column_span:
* @child: a #GtkGridLayoutChild
*
* Retrieves the number of columns that @child spans to.
*
* Returns: the number of columns
*/
int
gtk_grid_layout_child_get_column_span (GtkGridLayoutChild *child)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 1);
return CHILD_COL_SPAN (child);
}
/**
* gtk_grid_layout_child_set_row_span:
* @child: a #GtkGridLayoutChild
* @span: the span of @child
*
* Sets the number of rows @child spans to.
*/
void
gtk_grid_layout_child_set_row_span (GtkGridLayoutChild *child,
int span)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
if (CHILD_ROW_SPAN (child) == span)
return;
CHILD_ROW_SPAN (child) = span;
gtk_layout_manager_layout_changed (gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child)));
g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_CHILD_ROW_SPAN]);
}
/**
* gtk_grid_layout_child_get_row_span:
* @child: a #GtkGridLayoutChild
*
* Retrieves the number of rows that @child spans to.
*
* Returns: the number of row
*/
int
gtk_grid_layout_child_get_row_span (GtkGridLayoutChild *child)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 1);
return CHILD_ROW_SPAN (child);
}
/* }}} */
/* {{{ GtkGridLayout */
typedef struct {
int row;
GtkBaselinePosition baseline_position;
} GridRowProperties;
static const GridRowProperties grid_row_properties_default = {
0,
GTK_BASELINE_POSITION_CENTER
};
/* A GridLineData struct contains row/column specific parts
* of the grid.
*/
typedef struct {
gint16 spacing;
guint homogeneous : 1;
} GridLineData;
#define ROWS(layout) (&(layout)->linedata[GTK_ORIENTATION_HORIZONTAL])
#define COLUMNS(layout) (&(layout)->linedata[GTK_ORIENTATION_VERTICAL])
/* A GridLine struct represents a single row or column
* during size requests
*/
typedef struct {
int minimum;
int natural;
int minimum_above;
int minimum_below;
int natural_above;
int natural_below;
int position;
int allocation;
int allocated_baseline;
guint need_expand : 1;
guint expand : 1;
guint empty : 1;
} GridLine;
typedef struct {
GridLine *lines;
int min, max;
} GridLines;
typedef struct {
GtkGridLayout *layout;
GtkWidget *widget;
GridLines lines[2];
} GridRequest;
struct _GtkGridLayout
{
GtkLayoutManager parent_instance;
/* Array */
GArray *row_properties;
GtkOrientation orientation;
int baseline_row;
GridLineData linedata[2];
};
enum {
PROP_ROW_SPACING = 1,
PROP_COLUMN_SPACING,
PROP_ROW_HOMOGENEOUS,
PROP_COLUMN_HOMOGENEOUS,
PROP_BASELINE_ROW,
N_PROPERTIES
};
static GParamSpec *layout_props[N_PROPERTIES];
G_DEFINE_TYPE (GtkGridLayout, gtk_grid_layout, GTK_TYPE_LAYOUT_MANAGER)
static inline GtkGridLayoutChild *
get_grid_child (GtkGridLayout *self,
GtkWidget *child)
{
GtkLayoutManager *manager = GTK_LAYOUT_MANAGER (self);
return GTK_GRID_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (manager, child));
}
static int
get_spacing (GtkGridLayout *self,
GtkWidget *widget,
GtkOrientation orientation)
{
GtkCssValue *border_spacing;
gint css_spacing;
border_spacing = _gtk_style_context_peek_property (gtk_widget_get_style_context (widget),
GTK_CSS_PROPERTY_BORDER_SPACING);
if (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->linedata[orientation].spacing;
}
/* Calculates the min and max numbers for both orientations. */
static void
grid_request_count_lines (GridRequest *request)
{
GtkWidget *child;
int min[2];
int max[2];
min[0] = min[1] = G_MAXINT;
max[0] = max[1] = G_MININT;
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
GridChildAttach *attach = grid_child->attach;
min[0] = MIN (min[0], attach[0].pos);
max[0] = MAX (max[0], attach[0].pos + attach[0].span);
min[1] = MIN (min[1], attach[1].pos);
max[1] = MAX (max[1], attach[1].pos + attach[1].span);
}
request->lines[0].min = min[0];
request->lines[0].max = max[0];
request->lines[1].min = min[1];
request->lines[1].max = max[1];
}
/* Sets line sizes to 0 and marks lines as expand
* if they have a non-spanning expanding child.
*/
static void
grid_request_init (GridRequest *request,
GtkOrientation orientation)
{
GtkWidget *child;
GridLines *lines;
int i;
lines = &request->lines[orientation];
for (i = 0; i < lines->max - lines->min; i++)
{
lines->lines[i].minimum = 0;
lines->lines[i].natural = 0;
lines->lines[i].minimum_above = -1;
lines->lines[i].minimum_below = -1;
lines->lines[i].natural_above = -1;
lines->lines[i].natural_below = -1;
lines->lines[i].expand = FALSE;
lines->lines[i].empty = TRUE;
}
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
GridChildAttach *attach;
attach = &grid_child->attach[orientation];
if (attach->span == 1 && gtk_widget_compute_expand (child, orientation))
lines->lines[attach->pos - lines->min].expand = TRUE;
}
}
/* Sums allocations for lines spanned by child and their spacing.
*/
static gint
compute_allocation_for_child (GridRequest *request,
GtkGridLayoutChild *child,
GtkOrientation orientation)
{
GridLines *lines;
GridLine *line;
GridChildAttach *attach;
int size;
int i;
lines = &request->lines[orientation];
attach = &child->attach[orientation];
size = (attach->span - 1) * get_spacing (request->layout, request->widget, orientation);
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
size += line->allocation;
}
return size;
}
static void
compute_request_for_child (GridRequest *request,
GtkWidget *child,
GtkGridLayoutChild *grid_child,
GtkOrientation orientation,
gboolean contextual,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
if (minimum_baseline != NULL)
*minimum_baseline = -1;
if (natural_baseline != NULL)
*natural_baseline = -1;
if (contextual)
{
int size;
size = compute_allocation_for_child (request, grid_child, 1 - orientation);
gtk_widget_measure (child,
orientation,
size,
minimum, natural,
minimum_baseline, natural_baseline);
}
else
{
gtk_widget_measure (child,
orientation,
-1,
minimum, natural,
minimum_baseline, natural_baseline);
}
}
/* Sets requisition to max. of non-spanning children.
* If contextual is TRUE, requires allocations of
* lines in the opposite orientation to be set.
*/
static void
grid_request_non_spanning (GridRequest *request,
GtkOrientation orientation,
gboolean contextual)
{
GtkWidget *child;
GridLines *lines;
GridLine *line;
int i;
GtkBaselinePosition baseline_pos;
int minimum, minimum_baseline;
int natural, natural_baseline;
lines = &request->lines[orientation];
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
GridChildAttach *attach;
if (!gtk_widget_should_layout (child))
continue;
attach = &grid_child->attach[orientation];
if (attach->span != 1)
continue;
compute_request_for_child (request, child, grid_child, orientation, contextual, &minimum, &natural, &minimum_baseline, &natural_baseline);
line = &lines->lines[attach->pos - lines->min];
if (minimum_baseline != -1)
{
line->minimum_above = MAX (line->minimum_above, minimum_baseline);
line->minimum_below = MAX (line->minimum_below, minimum - minimum_baseline);
line->natural_above = MAX (line->natural_above, natural_baseline);
line->natural_below = MAX (line->natural_below, natural - natural_baseline);
}
else
{
line->minimum = MAX (line->minimum, minimum);
line->natural = MAX (line->natural, natural);
}
}
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->minimum_above != -1)
{
line->minimum = MAX (line->minimum, line->minimum_above + line->minimum_below);
line->natural = MAX (line->natural, line->natural_above + line->natural_below);
baseline_pos = gtk_grid_layout_get_row_baseline_position (request->layout, i + lines->min);
switch (baseline_pos)
{
case GTK_BASELINE_POSITION_TOP:
line->minimum_above += 0;
line->minimum_below += line->minimum - (line->minimum_above + line->minimum_below);
line->natural_above += 0;
line->natural_below += line->natural - (line->natural_above + line->natural_below);
break;
case GTK_BASELINE_POSITION_CENTER:
line->minimum_above += (line->minimum - (line->minimum_above + line->minimum_below))/2;
line->minimum_below += (line->minimum - (line->minimum_above + line->minimum_below))/2;
line->natural_above += (line->natural - (line->natural_above + line->natural_below))/2;
line->natural_below += (line->natural - (line->natural_above + line->natural_below))/2;
break;
case GTK_BASELINE_POSITION_BOTTOM:
line->minimum_above += line->minimum - (line->minimum_above + line->minimum_below);
line->minimum_below += 0;
line->natural_above += line->natural - (line->natural_above + line->natural_below);
line->natural_below += 0;
break;
default:
break;
}
}
}
}
/* Enforce homogeneous sizes */
static void
grid_request_homogeneous (GridRequest *request,
GtkOrientation orientation)
{
GtkGridLayout *self = request->layout;
GridLineData *linedata;
GridLines *lines;
gint minimum, natural;
gint i;
linedata = &self->linedata[orientation];
lines = &request->lines[orientation];
if (!linedata->homogeneous)
return;
minimum = 0;
natural = 0;
for (i = 0; i < lines->max - lines->min; i++)
{
minimum = MAX (minimum, lines->lines[i].minimum);
natural = MAX (natural, lines->lines[i].natural);
}
for (i = 0; i < lines->max - lines->min; i++)
{
lines->lines[i].minimum = minimum;
lines->lines[i].natural = natural;
/* TODO: Do we want to adjust the baseline here too?
* And if so, also in the homogeneous resize.
*/
}
}
/* Deals with spanning children.
* Requires expand fields of lines to be set for
* non-spanning children.
*/
static void
grid_request_spanning (GridRequest *request,
GtkOrientation orientation,
gboolean contextual)
{
GtkGridLayout *self = request->layout;
GtkWidget *child;
GridChildAttach *attach;
GridLineData *linedata;
GridLines *lines;
GridLine *line;
int minimum, natural;
int span_minimum, span_natural;
int span_expand;
gboolean force_expand;
int spacing;
int extra;
int expand;
int line_extra;
int i;
linedata = &self->linedata[orientation];
lines = &request->lines[orientation];
spacing = get_spacing (request->layout, request->widget, orientation);
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
if (!gtk_widget_should_layout (child))
continue;
attach = &grid_child->attach[orientation];
if (attach->span == 1)
continue;
/* We ignore baselines for spanning children */
compute_request_for_child (request, child, grid_child, orientation, contextual, &minimum, &natural, NULL, NULL);
span_minimum = (attach->span - 1) * spacing;
span_natural = (attach->span - 1) * spacing;
span_expand = 0;
force_expand = FALSE;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
span_minimum += line->minimum;
span_natural += line->natural;
if (line->expand)
span_expand += 1;
}
if (span_expand == 0)
{
span_expand = attach->span;
force_expand = TRUE;
}
/* If we need to request more space for this child to fill
* its requisition, then divide up the needed space amongst the
* lines it spans, favoring expandable lines if any.
*
* When doing homogeneous allocation though, try to keep the
* line allocations even, since we're going to force them to
* be the same anyway, and we don't want to introduce unnecessary
* extra space.
*/
if (span_minimum < minimum)
{
if (linedata->homogeneous)
{
int total, m;
total = minimum - (attach->span - 1) * spacing;
m = total / attach->span + (total % attach->span ? 1 : 0);
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
line->minimum = MAX (line->minimum, m);
}
}
else
{
extra = minimum - span_minimum;
expand = span_expand;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
if (force_expand || line->expand)
{
line_extra = extra / expand;
line->minimum += line_extra;
extra -= line_extra;
expand -= 1;
}
}
}
}
if (span_natural < natural)
{
if (linedata->homogeneous)
{
int total, n;
total = natural - (attach->span - 1) * spacing;
n = total / attach->span + (total % attach->span ? 1 : 0);
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
line->natural = MAX (line->natural, n);
}
}
else
{
extra = natural - span_natural;
expand = span_expand;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
if (force_expand || line->expand)
{
line_extra = extra / expand;
line->natural += line_extra;
extra -= line_extra;
expand -= 1;
}
}
}
}
}
}
/* Marks empty and expanding lines and counts them */
static void
grid_request_compute_expand (GridRequest *request,
GtkOrientation orientation,
int min,
int max,
int *nonempty_lines,
int *expand_lines)
{
GtkWidget *child;
GridChildAttach *attach;
int i;
GridLines *lines;
GridLine *line;
gboolean has_expand;
int expand;
int empty;
lines = &request->lines[orientation];
min = MAX (min, lines->min);
max = MIN (max, lines->max);
for (i = min - lines->min; i < max - lines->min; i++)
{
lines->lines[i].need_expand = FALSE;
lines->lines[i].expand = FALSE;
lines->lines[i].empty = TRUE;
}
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
if (!gtk_widget_should_layout (child))
continue;
attach = &grid_child->attach[orientation];
if (attach->span != 1)
continue;
if (attach->pos >= max || attach->pos < min)
continue;
line = &lines->lines[attach->pos - lines->min];
line->empty = FALSE;
if (gtk_widget_compute_expand (child, orientation))
line->expand = TRUE;
}
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
if (!gtk_widget_should_layout (child))
continue;
attach = &grid_child->attach[orientation];
if (attach->span == 1)
continue;
has_expand = FALSE;
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
if (line->expand)
has_expand = TRUE;
if (attach->pos + i >= max || attach->pos + 1 < min)
continue;
line->empty = FALSE;
}
if (!has_expand && gtk_widget_compute_expand (child, orientation))
{
for (i = 0; i < attach->span; i++)
{
if (attach->pos + i >= max || attach->pos + 1 < min)
continue;
line = &lines->lines[attach->pos - lines->min + i];
line->need_expand = TRUE;
}
}
}
empty = 0;
expand = 0;
for (i = min - lines->min; i < max - lines->min; i++)
{
line = &lines->lines[i];
if (line->need_expand)
line->expand = TRUE;
if (line->empty)
empty += 1;
if (line->expand)
expand += 1;
}
if (nonempty_lines)
*nonempty_lines = max - min - empty;
if (expand_lines)
*expand_lines = expand;
}
/* Sums the minimum and natural fields of lines and their spacing */
static void
grid_request_sum (GridRequest *request,
GtkOrientation orientation,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkGridLayout *self = request->layout;
GridLines *lines;
int i;
int min, nat;
int nonempty;
int spacing;
grid_request_compute_expand (request, orientation, G_MININT, G_MAXINT, &nonempty, NULL);
lines = &request->lines[orientation];
spacing = get_spacing (request->layout, request->widget, orientation);
min = 0;
nat = 0;
for (i = 0; i < lines->max - lines->min; i++)
{
if (orientation == GTK_ORIENTATION_VERTICAL &&
lines->min + i == self->baseline_row &&
lines->lines[i].minimum_above != -1)
{
if (minimum_baseline)
*minimum_baseline = min + lines->lines[i].minimum_above;
if (natural_baseline)
*natural_baseline = nat + lines->lines[i].natural_above;
}
min += lines->lines[i].minimum;
nat += lines->lines[i].natural;
if (!lines->lines[i].empty)
{
min += spacing;
nat += spacing;
}
}
/* Remove last spacing, if any was applied */
if (nonempty > 0)
{
min -= spacing;
nat -= spacing;
}
*minimum = min;
*natural = nat;
}
/* Computes minimum and natural fields of lines.
* When contextual is TRUE, requires allocation of
* lines in the opposite orientation to be set.
*/
static void
grid_request_run (GridRequest *request,
GtkOrientation orientation,
gboolean contextual)
{
grid_request_init (request, orientation);
grid_request_non_spanning (request, orientation, contextual);
grid_request_homogeneous (request, orientation);
grid_request_spanning (request, orientation, contextual);
grid_request_homogeneous (request, orientation);
}
static void
grid_distribute_non_homogeneous (GridLines *lines,
int nonempty,
int expand,
int size,
int min,
int max)
{
GtkRequestedSize *sizes;
GridLine *line;
int extra;
int rest;
int i, j;
if (nonempty == 0)
return;
sizes = g_newa (GtkRequestedSize, nonempty);
j = 0;
for (i = min - lines->min; i < max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
size -= line->minimum;
sizes[j].minimum_size = line->minimum;
sizes[j].natural_size = line->natural;
sizes[j].data = line;
j++;
}
size = gtk_distribute_natural_allocation (MAX (0, size), nonempty, sizes);
if (expand > 0)
{
extra = size / expand;
rest = size % expand;
}
else
{
extra = 0;
rest = 0;
}
j = 0;
for (i = min - lines->min; i < max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
g_assert (line == sizes[j].data);
line->allocation = sizes[j].minimum_size;
if (line->expand)
{
line->allocation += extra;
if (rest > 0)
{
line->allocation += 1;
rest -= 1;
}
}
j++;
}
}
/* Requires that the minimum and natural fields of lines
* have been set, computes the allocation field of lines
* by distributing total_size among lines.
*/
static void
grid_request_allocate (GridRequest *request,
GtkOrientation orientation,
int total_size)
{
GtkGridLayout *self = request->layout;
GridLineData *linedata;
GridLines *lines;
GridLine *line;
int nonempty1, nonempty2;
int expand1, expand2;
int i;
GtkBaselinePosition baseline_pos;
int baseline;
int extra, extra2;
int rest;
int size1, size2;
int split, split_pos;
int spacing;
linedata = &self->linedata[orientation];
lines = &request->lines[orientation];
spacing = get_spacing (request->layout, request->widget, orientation);
baseline = gtk_widget_get_allocated_baseline (request->widget);
if (orientation == GTK_ORIENTATION_VERTICAL && baseline != -1 &&
self->baseline_row >= lines->min && self->baseline_row < lines->max &&
lines->lines[self->baseline_row - lines->min].minimum_above != -1)
{
split = self->baseline_row;
split_pos = baseline - lines->lines[self->baseline_row - lines->min].minimum_above;
grid_request_compute_expand (request, orientation, lines->min, split, &nonempty1, &expand1);
grid_request_compute_expand (request, orientation, split, lines->max, &nonempty2, &expand2);
if (nonempty2 > 0)
{
size1 = split_pos - (nonempty1) * spacing;
size2 = (total_size - split_pos) - (nonempty2 - 1) * spacing;
}
else
{
size1 = total_size - (nonempty1 - 1) * spacing;
size2 = 0;
}
}
else
{
grid_request_compute_expand (request, orientation, lines->min, lines->max, &nonempty1, &expand1);
nonempty2 = expand2 = 0;
split = lines->max;
size1 = total_size - (nonempty1 - 1) * spacing;
size2 = 0;
}
if (nonempty1 == 0 && nonempty2 == 0)
return;
if (linedata->homogeneous)
{
if (nonempty1 > 0)
{
extra = size1 / nonempty1;
rest = size1 % nonempty1;
}
else
{
extra = 0;
rest = 0;
}
if (nonempty2 > 0)
{
extra2 = size2 / nonempty2;
if (extra2 < extra || nonempty1 == 0)
{
extra = extra2;
rest = size2 % nonempty2;
}
}
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
line->allocation = extra;
if (rest > 0)
{
line->allocation += 1;
rest -= 1;
}
}
}
else
{
grid_distribute_non_homogeneous (lines,
nonempty1,
expand1,
size1,
lines->min,
split);
grid_distribute_non_homogeneous (lines,
nonempty2,
expand2,
size2,
split,
lines->max);
}
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (line->empty)
continue;
if (line->minimum_above != -1)
{
/* Note: This is overridden in grid_request_position for the allocated baseline */
baseline_pos = gtk_grid_layout_get_row_baseline_position (request->layout, i + lines->min);
switch (baseline_pos)
{
case GTK_BASELINE_POSITION_TOP:
line->allocated_baseline = line->minimum_above;
break;
case GTK_BASELINE_POSITION_CENTER:
line->allocated_baseline = line->minimum_above +
(line->allocation - (line->minimum_above + line->minimum_below)) / 2;
break;
case GTK_BASELINE_POSITION_BOTTOM:
line->allocated_baseline = line->allocation - line->minimum_below;
break;
default:
break;
}
}
else
line->allocated_baseline = -1;
}
}
/* Computes the position fields from allocation and spacing */
static void
grid_request_position (GridRequest *request,
GtkOrientation orientation)
{
GtkGridLayout *self = request->layout;
GridLines *lines;
GridLine *line;
int position, old_position;
int allocated_baseline;
int spacing;
int i, j;
lines = &request->lines[orientation];
spacing = get_spacing (request->layout, request->widget, orientation);
allocated_baseline = gtk_widget_get_allocated_baseline (request->widget);
position = 0;
for (i = 0; i < lines->max - lines->min; i++)
{
line = &lines->lines[i];
if (orientation == GTK_ORIENTATION_VERTICAL &&
i + lines->min == self->baseline_row &&
allocated_baseline != -1 &&
lines->lines[i].minimum_above != -1)
{
old_position = position;
position = allocated_baseline - line->minimum_above;
/* Back-patch previous rows */
for (j = 0; j < i; j++)
{
if (!lines->lines[j].empty)
lines->lines[j].position += position - old_position;
}
}
if (!line->empty)
{
line->position = position;
position += line->allocation + spacing;
if (orientation == GTK_ORIENTATION_VERTICAL &&
i + lines->min == self->baseline_row &&
allocated_baseline != -1 &&
lines->lines[i].minimum_above != -1)
line->allocated_baseline = allocated_baseline - line->position;
}
}
}
static void
gtk_grid_layout_get_size (GtkGridLayout *self,
GtkWidget *widget,
GtkOrientation orientation,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GridRequest request;
GridLines *lines;
*minimum = 0;
*natural = 0;
if (minimum_baseline)
*minimum_baseline = -1;
if (natural_baseline)
*natural_baseline = -1;
if (gtk_widget_get_first_child (widget) == NULL)
return;
request.layout = self;
request.widget = widget;
grid_request_count_lines (&request);
lines = &request.lines[orientation];
lines->lines = g_newa (GridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (GridLine));
grid_request_run (&request, orientation, FALSE);
grid_request_sum (&request, orientation,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
gtk_grid_layout_get_size_for_size (GtkGridLayout *self,
GtkWidget *widget,
GtkOrientation orientation,
int size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GridRequest request;
GridLines *lines;
gint min_size, nat_size;
*minimum = 0;
*natural = 0;
if (minimum_baseline)
*minimum_baseline = -1;
if (natural_baseline)
*natural_baseline = -1;
if (gtk_widget_get_first_child (widget) == NULL)
return;
request.layout = self;
request.widget = widget;
grid_request_count_lines (&request);
lines = &request.lines[0];
lines->lines = g_newa (GridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (GridLine));
lines = &request.lines[1];
lines->lines = g_newa (GridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (GridLine));
grid_request_run (&request, 1 - orientation, FALSE);
grid_request_sum (&request, 1 - orientation, &min_size, &nat_size, NULL, NULL);
grid_request_allocate (&request, 1 - orientation, MAX (size, min_size));
grid_request_run (&request, orientation, TRUE);
grid_request_sum (&request, orientation,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
gtk_grid_layout_measure (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkGridLayout *self = GTK_GRID_LAYOUT (manager);
if ((orientation == GTK_ORIENTATION_HORIZONTAL &&
gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT) ||
(orientation == GTK_ORIENTATION_VERTICAL &&
gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH))
gtk_grid_layout_get_size_for_size (self, widget, orientation, for_size,
minimum, natural,
minimum_baseline, natural_baseline);
else
gtk_grid_layout_get_size (self, widget, orientation,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
allocate_child (GridRequest *request,
GtkOrientation orientation,
GtkWidget *child,
GtkGridLayoutChild *grid_child,
int *position,
int *size,
int *baseline)
{
GridLines *lines;
GridLine *line;
GridChildAttach *attach;
int i;
lines = &request->lines[orientation];
attach = &grid_child->attach[orientation];
*position = lines->lines[attach->pos - lines->min].position;
if (attach->span == 1 && gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE)
*baseline = lines->lines[attach->pos - lines->min].allocated_baseline;
else
*baseline = -1;
*size = (attach->span - 1) * get_spacing (request->layout, request->widget, orientation);
for (i = 0; i < attach->span; i++)
{
line = &lines->lines[attach->pos - lines->min + i];
*size += line->allocation;
}
}
static void
grid_request_allocate_children (GridRequest *request,
int grid_width,
int grid_height)
{
GtkWidget *child;
GtkAllocation child_allocation;
gint x, y, width, height, baseline, ignore;
for (child = gtk_widget_get_first_child (request->widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkGridLayoutChild *grid_child = get_grid_child (request->layout, child);
if (!gtk_widget_should_layout (child))
continue;
allocate_child (request, GTK_ORIENTATION_HORIZONTAL, child, grid_child, &x, &width, &ignore);
allocate_child (request, GTK_ORIENTATION_VERTICAL, child, grid_child, &y, &height, &baseline);
child_allocation.x = x;
child_allocation.y = y;
child_allocation.width = width;
child_allocation.height = height;
if (_gtk_widget_get_direction (request->widget) == GTK_TEXT_DIR_RTL)
child_allocation.x = grid_width - child_allocation.x - child_allocation.width;
gtk_widget_size_allocate (child, &child_allocation, baseline);
}
}
#define GET_SIZE(width, height, orientation) (orientation == GTK_ORIENTATION_HORIZONTAL ? width : height)
static void
gtk_grid_layout_allocate (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkGridLayout *self = GTK_GRID_LAYOUT (manager);
GridRequest request;
GridLines *lines;
GtkOrientation orientation;
if (gtk_widget_get_first_child (widget) == NULL)
return;
request.layout = self;
request.widget = widget;
grid_request_count_lines (&request);
lines = &request.lines[0];
lines->lines = g_newa (GridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (GridLine));
lines = &request.lines[1];
lines->lines = g_newa (GridLine, lines->max - lines->min);
memset (lines->lines, 0, (lines->max - lines->min) * sizeof (GridLine));
if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
orientation = GTK_ORIENTATION_HORIZONTAL;
else
orientation = GTK_ORIENTATION_VERTICAL;
grid_request_run (&request, OPPOSITE_ORIENTATION (orientation), FALSE);
grid_request_allocate (&request, OPPOSITE_ORIENTATION (orientation),
GET_SIZE (width, height, OPPOSITE_ORIENTATION (orientation)));
grid_request_run (&request, orientation, TRUE);
grid_request_allocate (&request, orientation, GET_SIZE (width, height, orientation));
grid_request_position (&request, 0);
grid_request_position (&request, 1);
grid_request_allocate_children (&request, width, height);
}
static void
gtk_grid_layout_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkGridLayout *self = GTK_GRID_LAYOUT (gobject);
switch (prop_id)
{
case PROP_ROW_SPACING:
gtk_grid_layout_set_row_spacing (self, g_value_get_int (value));
break;
case PROP_COLUMN_SPACING:
gtk_grid_layout_set_column_spacing (self, g_value_get_int (value));
break;
case PROP_ROW_HOMOGENEOUS:
gtk_grid_layout_set_row_homogeneous (self, g_value_get_boolean (value));
break;
case PROP_COLUMN_HOMOGENEOUS:
gtk_grid_layout_set_column_homogeneous (self, g_value_get_boolean (value));
break;
case PROP_BASELINE_ROW:
gtk_grid_layout_set_baseline_row (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gtk_grid_layout_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkGridLayout *self = GTK_GRID_LAYOUT (gobject);
switch (prop_id)
{
case PROP_ROW_SPACING:
g_value_set_int (value, COLUMNS (self)->spacing);
break;
case PROP_COLUMN_SPACING:
g_value_set_int (value, ROWS (self)->spacing);
break;
case PROP_ROW_HOMOGENEOUS:
g_value_set_boolean (value, COLUMNS (self)->homogeneous);
break;
case PROP_COLUMN_HOMOGENEOUS:
g_value_set_boolean (value, ROWS (self)->homogeneous);
break;
case PROP_BASELINE_ROW:
g_value_set_int (value, self->baseline_row);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gtk_grid_layout_finalize (GObject *gobject)
{
GtkGridLayout *self = GTK_GRID_LAYOUT (gobject);
g_clear_pointer (&self->row_properties, g_array_unref);
G_OBJECT_CLASS (gtk_grid_layout_parent_class)->finalize (gobject);
}
static void
gtk_grid_layout_class_init (GtkGridLayoutClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass);
layout_class->layout_child_type = GTK_TYPE_GRID_LAYOUT_CHILD;
layout_class->measure = gtk_grid_layout_measure;
layout_class->allocate = gtk_grid_layout_allocate;
gobject_class->set_property = gtk_grid_layout_set_property;
gobject_class->get_property = gtk_grid_layout_get_property;
gobject_class->finalize = gtk_grid_layout_finalize;
/**
* GtkGridLayout:row-spacing:
*
* The amount of space between to consecutive rows.
*/
layout_props[PROP_ROW_SPACING] =
g_param_spec_int ("row-spacing",
P_("Row spacing"),
P_("The amount of space between two consecutive rows"),
0, G_MAXINT16, 0,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayout:column-spacing:
*
* The amount of space between to consecutive columns.
*/
layout_props[PROP_COLUMN_SPACING] =
g_param_spec_int ("column-spacing",
P_("Column spacing"),
P_("The amount of space between two consecutive columns"),
0, G_MAXINT16, 0,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayout:row-homogeneous:
*
* Whether all the rows in the grid have the same height.
*/
layout_props[PROP_ROW_HOMOGENEOUS] =
g_param_spec_boolean ("row-homogeneous",
P_("Row Homogeneous"),
P_("If TRUE, the rows are all the same height"),
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayout:column-homogeneous:
*
* Whether all the columns in the grid have the same width.
*/
layout_props[PROP_COLUMN_HOMOGENEOUS] =
g_param_spec_boolean ("column-homogeneous",
P_("Column Homogeneous"),
P_("If TRUE, the columns are all the same width"),
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGridLayout:baseline-row:
*
* The row to align to the baseline, when #GtkWidget:valign is set
* to %GTK_ALIGN_BASELINE.
*/
layout_props[PROP_BASELINE_ROW] =
g_param_spec_int ("baseline-row",
P_("Baseline Row"),
P_("The row to align the to the baseline when valign is GTK_ALIGN_BASELINE"),
0, G_MAXINT, 0,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPERTIES, layout_props);
}
static void
gtk_grid_layout_init (GtkGridLayout *self)
{
}
/**
* gtk_grid_layout_new:
*
* Creates a new #GtkGridLayout.
*
* Returns: the newly created #GtkGridLayout
*/
GtkLayoutManager *
gtk_grid_layout_new (void)
{
return g_object_new (GTK_TYPE_GRID_LAYOUT, NULL);
}
/**
* gtk_grid_layout_set_row_homogeneous:
* @grid: a #GtkGridLayout
* @homogeneous: %TRUE to make rows homogeneous
*
* Sets whether all rows of @grid should have the same height.
*/
void
gtk_grid_layout_set_row_homogeneous (GtkGridLayout *grid,
gboolean homogeneous)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
/* Yes, homogeneous rows means all the columns have the same size */
if (COLUMNS (grid)->homogeneous == !!homogeneous)
return;
COLUMNS (grid)->homogeneous = !!homogeneous;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (grid));
g_object_notify_by_pspec (G_OBJECT (grid), layout_props[PROP_ROW_HOMOGENEOUS]);
}
/**
* gtk_grid_layout_get_row_homogeneous:
* @grid: a #GtkGridLayout
*
* Checks whether all rows of @grid should have the same height.
*
* Returns: %TRUE if the rows are homogeneous, and %FALSE otherwise
*/
gboolean
gtk_grid_layout_get_row_homogeneous (GtkGridLayout *grid)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), FALSE);
return COLUMNS (grid)->homogeneous;
}
/**
* gtk_grid_layout_set_row_spacing:
* @grid: a #GtkGridLayout
* @spacing: the amount of space between rows, in pixels
*
* Sets the amount of space to insert between consecutive rows.
*/
void
gtk_grid_layout_set_row_spacing (GtkGridLayout *grid,
guint spacing)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
g_return_if_fail (spacing <= G_MAXINT16);
if (COLUMNS (grid)->spacing == spacing)
return;
COLUMNS (grid)->spacing = spacing;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (grid));
g_object_notify_by_pspec (G_OBJECT (grid), layout_props[PROP_ROW_SPACING]);
}
/**
* gtk_grid_layout_get_row_spacing:
* @grid: a #GtkGridLayout
*
* Retrieves the spacing set with gtk_grid_layout_set_row_spacing().
*
* Returns: the spacing between consecutive rows
*/
guint
gtk_grid_layout_get_row_spacing (GtkGridLayout *grid)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), 0);
return COLUMNS (grid)->spacing;
}
/**
* gtk_grid_layout_set_column_homogeneous:
* @grid: a #GtkGridLayout
* @homogeneous: %TRUE to make columns homogeneous
*
* Sets whether all columns of @grid should have the same width.
*/
void
gtk_grid_layout_set_column_homogeneous (GtkGridLayout *grid,
gboolean homogeneous)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
/* Yes, homogeneous columns means all the rows have the same size */
if (ROWS (grid)->homogeneous == !!homogeneous)
return;
ROWS (grid)->homogeneous = !!homogeneous;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (grid));
g_object_notify_by_pspec (G_OBJECT (grid), layout_props[PROP_COLUMN_HOMOGENEOUS]);
}
/**
* gtk_grid_layout_get_column_homogeneous:
* @grid: a #GtkGridLayout
*
* Checks whether all columns of @grid should have the same width.
*
* Returns: %TRUE if the columns are homogeneous, and %FALSE otherwise
*/
gboolean
gtk_grid_layout_get_column_homogeneous (GtkGridLayout *grid)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), FALSE);
return ROWS (grid)->homogeneous;
}
/**
* gtk_grid_layout_set_column_spacing:
* @grid: a #GtkGridLayout
* @spacing: the amount of space between columns, in pixels
*
* Sets the amount of space to insert between consecutive columns.
*/
void
gtk_grid_layout_set_column_spacing (GtkGridLayout *grid,
guint spacing)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
g_return_if_fail (spacing <= G_MAXINT16);
if (ROWS (grid)->spacing == spacing)
return;
ROWS (grid)->spacing = spacing;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (grid));
g_object_notify_by_pspec (G_OBJECT (grid), layout_props[PROP_COLUMN_SPACING]);
}
/**
* gtk_grid_layout_get_column_spacing:
* @grid: a #GtkGridLayout
*
* Retrieves the spacing set with gtk_grid_layout_set_column_spacing().
*
* Returns: the spacing between consecutive columns
*/
guint
gtk_grid_layout_get_column_spacing (GtkGridLayout *grid)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), 0);
return ROWS (grid)->spacing;
}
static GridRowProperties *
find_row_properties (GtkGridLayout *self,
int row)
{
int i;
if (self->row_properties == NULL)
return NULL;
for (i = 0; i < self->row_properties->len; i++)
{
GridRowProperties *prop = &g_array_index (self->row_properties, GridRowProperties, i);
if (prop->row == row)
return prop;
}
return NULL;
}
static GridRowProperties *
get_row_properties_or_create (GtkGridLayout *self,
int row)
{
GridRowProperties *props;
props = find_row_properties (self, row);
if (props != NULL)
return props;
/* This is the only place where we create the row properties array;
* find_row_properties() is used by getters, so we should not create
* the array there.
*/
if (self->row_properties == NULL)
self->row_properties = g_array_new (FALSE, FALSE, sizeof (GridRowProperties));
g_array_append_vals (self->row_properties, &grid_row_properties_default, 1);
props = &g_array_index (self->row_properties, GridRowProperties, self->row_properties->len - 1);
props->row = row;
return props;
}
static const GridRowProperties *
get_row_properties_or_default (GtkGridLayout *self,
int row)
{
GridRowProperties *props;
props = find_row_properties (self, row);
if (props != NULL)
return props;
return &grid_row_properties_default;
}
/**
* gtk_grid_layout_set_row_baseline_position:
* @grid: a #GtkGridLayout
* @row: a row index
* @pos: a #GtkBaselinePosition
*
* Sets how the baseline should be positioned on @row of the
* grid, in case that row is assigned more space than is requested.
*/
void
gtk_grid_layout_set_row_baseline_position (GtkGridLayout *grid,
int row,
GtkBaselinePosition pos)
{
GridRowProperties *props;
g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
props = get_row_properties_or_create (grid, row);
if (props->baseline_position == pos)
return;
props->baseline_position = pos;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (grid));
}
/**
* gtk_grid_layout_get_row_baseline_position:
* @grid: a #GtkGridLayout
* @row: a row index
*
* Returns the baseline position of @row as set by
* gtk_grid_layout_set_row_baseline_position(), or the default value
* of %GTK_BASELINE_POSITION_CENTER.
*
* Returns: the baseline position of @row
*/
GtkBaselinePosition
gtk_grid_layout_get_row_baseline_position (GtkGridLayout *grid,
int row)
{
const GridRowProperties *props;
g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), GTK_BASELINE_POSITION_CENTER);
props = get_row_properties_or_default (grid, row);
return props->baseline_position;
}
/**
* gtk_grid_layout_set_baseline_row:
* @grid: a #GtkGridLayout
* @row: the row index
*
* Sets which row defines the global baseline for the entire grid.
*
* Each row in the grid can have its own local baseline, but only
* one of those is global, meaning it will be the baseline in the
* parent of the @grid.
*/
void
gtk_grid_layout_set_baseline_row (GtkGridLayout *grid,
int row)
{
g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
if (grid->baseline_row == row)
return;
grid->baseline_row = row;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (grid));
g_object_notify_by_pspec (G_OBJECT (grid), layout_props[PROP_BASELINE_ROW]);
}
/**
* gtk_grid_layout_get_baseline_row:
* @grid: a #GtkGridLayout
*
* Retrieves the row set with gtk_grid_layout_set_baseline_row().
*
* Returns: the global baseline row
*/
int
gtk_grid_layout_get_baseline_row (GtkGridLayout *grid)
{
g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), GTK_BASELINE_POSITION_CENTER);
return grid->baseline_row;
}
/* }}} */