[gtk/wip/ebassi/grid-layout] Add GtkGridLayout
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/ebassi/grid-layout] Add GtkGridLayout
- Date: Wed, 3 Apr 2019 18:05:01 +0000 (UTC)
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]