[gtk/wip/ebassi/grid-layout] Add GtkGridLayout



commit 6e0f1800c84dc1c1790406c3355619117a0170e3
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Wed Apr 3 19:03:58 2019 +0100

    Add GtkGridLayout
    
    Layout manager for grid-like widgets.

 docs/reference/gtk/gtk4.types.in |    2 +
 gtk/gtk.h                        |    1 +
 gtk/gtkgridlayout.c              | 1870 ++++++++++++++++++++++++++++++++++++++
 gtk/gtkgridlayout.h              |  101 ++
 gtk/meson.build                  |    2 +
 5 files changed, 1976 insertions(+)
---
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 <gtk/gtkgesturezoom.h>
 #include <gtk/gtkglarea.h>
 #include <gtk/gtkgrid.h>
+#include <gtk/gtkgridlayout.h>
 #include <gtk/gtkheaderbar.h>
 #include <gtk/gtkicontheme.h>
 #include <gtk/gtkiconview.h>
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 <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 "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<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)
+{
+  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 <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <gtk/gtklayoutmanager.h>
+
+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',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]