gtk2/gtk/gtkgridlayout.c
Emmanuele Bassi 6e52ef96a4 Move orientable style classes into GtkWidget
It feels slightly wrong to have GtkOrientable operate on widgets, but at
least what happens when an orientable widget changes orientation should
be part of GtkWidget.

This will allow to add more state changes without accessing widget state
from the outside of gtkwidget.c.
2020-06-05 20:35:09 +01:00

2049 lines
58 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
/**
* 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 "gtkcsspositionvalueprivate.h"
#include "gtkdebug.h"
#include "gtkintl.h"
#include "gtklayoutchild.h"
#include "gtkorientable.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)
{
CHILD_ROW_SPAN (self) = 1;
CHILD_COL_SPAN (self) = 1;
}
/**
* 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<GridRowProperties> */
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)
{
GtkCssNode *node = gtk_widget_get_css_node (widget);
GtkCssStyle *style = gtk_css_node_get_style (node);
GtkCssValue *border_spacing;
int css_spacing;
border_spacing = style->size->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;
}
/* }}} */