diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index f8d9c90409..6a3e14e9ec 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -86,6 +86,8 @@ gtk_gesture_swipe_get_type gtk_gesture_zoom_get_type gtk_gl_area_get_type gtk_grid_get_type +gtk_grid_layout_child_get_type +gtk_grid_layout_get_type gtk_header_bar_get_type gtk_icon_theme_get_type gtk_icon_view_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index bb637b522a..e460b8dbb0 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -128,6 +128,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkgridlayout.c b/gtk/gtkgridlayout.c new file mode 100644 index 0000000000..9121b75345 --- /dev/null +++ b/gtk/gtkgridlayout.c @@ -0,0 +1,1870 @@ +/* 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; + + 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); + + 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); + + 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); + + 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) +{ +} + +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]); +} + +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); +} + +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]); +} + +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); +} + +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]); +} + +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); +} + +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]); +} + +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_get_visible (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 homogenous 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_get_visible (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_get_visible (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_get_visible (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_get_visible (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; + + 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); + + 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); + + 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); + + 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); + + 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_layou_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; +} + +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]); +} + +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; +} + +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]); +} + +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; +} + +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)); +} + +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; +} + +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]); +} + +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; +} +/* }}} */ diff --git a/gtk/gtkgridlayout.h b/gtk/gtkgridlayout.h new file mode 100644 index 0000000000..a0bd9e4717 --- /dev/null +++ b/gtk/gtkgridlayout.h @@ -0,0 +1,101 @@ +/* gtkgridlayout.h: 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 . + */ +#pragma once + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_GRID_LAYOUT (gtk_grid_layout_get_type ()) +#define GTK_TYPE_GRID_LAYOUT_CHILD (gtk_grid_layout_child_get_type ()) + +/** + * GtkGridLayout: + * + * Layout manager for grid-like widgets. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkGridLayout, gtk_grid_layout, GTK, GRID_LAYOUT, GtkLayoutManager) + +GDK_AVAILABLE_IN_ALL +GtkLayoutManager * gtk_grid_layout_new (void); + +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_set_row_homogeneous (GtkGridLayout *grid, + gboolean homogeneous); +GDK_AVAILABLE_IN_ALL +gboolean gtk_grid_layout_get_row_homogeneous (GtkGridLayout *grid); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_set_row_spacing (GtkGridLayout *grid, + guint spacing); +GDK_AVAILABLE_IN_ALL +guint gtk_grid_layout_get_row_spacing (GtkGridLayout *grid); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_set_column_homogeneous (GtkGridLayout *grid, + gboolean homogeneous); +GDK_AVAILABLE_IN_ALL +gboolean gtk_grid_layout_get_column_homogeneous (GtkGridLayout *grid); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_set_column_spacing (GtkGridLayout *grid, + guint spacing); +GDK_AVAILABLE_IN_ALL +guint gtk_grid_layout_get_column_spacing (GtkGridLayout *grid); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_set_row_baseline_position (GtkGridLayout *grid, + int row, + GtkBaselinePosition pos); +GDK_AVAILABLE_IN_ALL +GtkBaselinePosition gtk_grid_layout_get_row_baseline_position (GtkGridLayout *grid, + int row); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_set_baseline_row (GtkGridLayout *grid, + int row); +GDK_AVAILABLE_IN_ALL +int gtk_grid_layout_get_baseline_row (GtkGridLayout *grid); + +/** + * GtkGridLayoutChild: + * + * Layout properties for children of #GtkGridLayout. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkGridLayoutChild, gtk_grid_layout_child, GTK, GRID_LAYOUT_CHILD, GtkLayoutChild) + +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_child_set_top_attach (GtkGridLayoutChild *child, + int attach); +GDK_AVAILABLE_IN_ALL +int gtk_grid_layout_child_get_top_attach (GtkGridLayoutChild *child); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_child_set_left_attach (GtkGridLayoutChild *child, + int attach); +GDK_AVAILABLE_IN_ALL +int gtk_grid_layout_child_get_left_attach (GtkGridLayoutChild *child); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_child_set_column_span (GtkGridLayoutChild *child, + int span); +GDK_AVAILABLE_IN_ALL +int gtk_grid_layout_child_get_column_span (GtkGridLayoutChild *child); +GDK_AVAILABLE_IN_ALL +void gtk_grid_layout_child_set_row_span (GtkGridLayoutChild *child, + int span); +GDK_AVAILABLE_IN_ALL +int gtk_grid_layout_child_get_row_span (GtkGridLayoutChild *child); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 36615fdbd1..e38effb93c 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -248,6 +248,7 @@ gtk_public_sources = files([ 'gtkgesturezoom.c', 'gtkglarea.c', 'gtkgrid.c', + 'gtkgridlayout.c', 'gtkheaderbar.c', 'gtkicontheme.c', 'gtkiconview.c', @@ -505,6 +506,7 @@ gtk_public_headers = files([ 'gtkgesturezoom.h', 'gtkglarea.h', 'gtkgrid.h', + 'gtkgridlayout.h', 'gtkheaderbar.h', 'gtkicontheme.h', 'gtkiconview.h',