diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index 0c7f7e131c..519879eed2 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -203,6 +203,7 @@ Layout Containers + diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index f0ba03b67e..2ebc6b1101 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -6350,3 +6350,34 @@ GTK_APPLICATION_GET_CLASS gtk_application_get_type GtkApplicationPrivate + +
+gtkgrid +GtkGrid +GtkGrid +gtk_grid_new +gtk_grid_attach +gtk_grid_attach_next_to +gtk_grid_set_row_homogeneous +gtk_grid_get_row_homogeneous +gtk_grid_set_row_spacing +gtk_grid_get_row_spacing +gtk_grid_set_column_homogeneous +gtk_grid_get_column_homogeneous +gtk_grid_set_column_spacing +gtk_grid_get_column_spacing + + +GtkGrid +GtkGridClass +GTK_TYPE_GRID +GTK_GRID +GTK_GRID_CLASS +GTK_IS_GRID +GTK_IS_GRID_CLASS +GTK_GRID_GET_CLASS + + +GtkGridPrivate +gtk_grid_get_type +
diff --git a/docs/reference/gtk/gtk3.types b/docs/reference/gtk/gtk3.types index 5cd9d9b84f..5c02fbe40b 100644 --- a/docs/reference/gtk/gtk3.types +++ b/docs/reference/gtk/gtk3.types @@ -61,6 +61,7 @@ gtk_font_button_get_type gtk_font_selection_dialog_get_type gtk_font_selection_get_type gtk_frame_get_type +gtk_grid_get_type gtk_handle_box_get_type gtk_hbox_get_type gtk_hbutton_box_get_type diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 5d6892bdc0..0774710ff2 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -209,6 +209,7 @@ gtk_public_h_sources = \ gtkfontbutton.h \ gtkfontsel.h \ gtkframe.h \ + gtkgrid.h \ gtkhandlebox.h \ gtkhbbox.h \ gtkhbox.h \ @@ -472,6 +473,7 @@ gtk_base_c_sources = \ gtkfontbutton.c \ gtkfontsel.c \ gtkframe.c \ + gtkgrid.c \ gtkhandlebox.c \ gtkhbbox.c \ gtkhbox.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index ea2e20714b..6e99849a1c 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -93,6 +93,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index a2aeaf4e17..795cd3cc1b 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -4528,3 +4528,20 @@ gtk_info_bar_set_message_type gtk_info_bar_get_message_type #endif #endif + +#if IN_HEADER(__GTK_GRID_H__) +#if IN_FILE(__GTK_GRID_c__) +gtk_grid_get_type G_GNUC_CONST +gtk_grid_new +gtk_grid_attach +gtk_grid_attach_next_to +gtk_grid_set_row_homogeneous +gtk_grid_get_row_homogeneous +gtk_grid_set_row_spacing +gtk_grid_get_row_spacing +gtk_grid_set_column_homogeneous +gtk_grid_get_column_homogeneous +gtk_grid_set_column_spacing +gtk_grid_get_column_spacing +#endif +#endif diff --git a/gtk/gtkgrid.c b/gtk/gtkgrid.c new file mode 100644 index 0000000000..60d55b80d9 --- /dev/null +++ b/gtk/gtkgrid.c @@ -0,0 +1,1676 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2010 Red Hat, Inc. + * Author: Matthias Clasen + * + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "gtkgrid.h" + +#include "gtkorientable.h" +#include "gtksizerequest.h" +#include "gtkprivate.h" +#include "gtkintl.h" + + +/** + * SECTION:gtkgrid + * @Short_description: Pack widgets in a rows and columns + * @Title: GtkGrid + * @See_also: #GtkTable, #GtkHBox, #GtkVBox + * + * GtkGrid is a container which arranges its child widgets in + * rows and columns. It is a very similar to #GtkTable and #GtkBox, + * but it consistently uses #GtkWidget's margin and expand properties + * instead of custom child properties, and it fully supports + * height-for-width geometry management. + * + * Children are added using gtk_grid_attach(). They can span multiple + * rows or columns. It is also possible to add a child next to an + * existing child, using gtk_grid_attach_next_to(). + * + * GtkGrid can be used like a #GtkBox by just using gtk_container_add(), + * which will place children next to each other in the direction determined + * by the #GtkGrid::orientation property. + */ + +typedef struct _GtkGridChild GtkGridChild; +typedef struct _GtkGridChildAttach GtkGridChildAttach; +typedef struct _GtkGridLine GtkGridLine; +typedef struct _GtkGridLines GtkGridLines; +typedef struct _GtkGridLineData GtkGridLineData; +typedef struct _GtkGridRequest GtkGridRequest; + +struct _GtkGridChildAttach +{ + gint pos; + gint span; +}; + +struct _GtkGridChild +{ + GtkWidget *widget; + GtkGridChildAttach attach[2]; +}; + +#define CHILD_LEFT(child) ((child)->attach[GTK_ORIENTATION_HORIZONTAL].pos) +#define CHILD_WIDTH(child) ((child)->attach[GTK_ORIENTATION_HORIZONTAL].span) +#define CHILD_TOP(child) ((child)->attach[GTK_ORIENTATION_VERTICAL].pos) +#define CHILD_HEIGHT(child) ((child)->attach[GTK_ORIENTATION_VERTICAL].span) + +/* A GtkGridLineData struct contains row/column specific parts + * of the grid. + */ +struct _GtkGridLineData +{ + gint16 spacing; + guint homogeneous : 1; +}; + +struct _GtkGridPrivate +{ + GList *children; + + GtkOrientation orientation; + + GtkGridLineData linedata[2]; +}; + +#define ROWS(priv) (&(priv)->linedata[GTK_ORIENTATION_HORIZONTAL]) +#define COLUMNS(priv) (&(priv)->linedata[GTK_ORIENTATION_VERTICAL]) + +/* A GtkGridLine struct represents a single row or column + * during size requests + */ +struct _GtkGridLine +{ + gint minimum; + gint natural; + gint position; + gint allocation; + + guint need_expand : 1; + guint expand : 1; + guint empty : 1; +}; + +struct _GtkGridLines +{ + GtkGridLine *lines; + gint min, max; +}; + +struct _GtkGridRequest +{ + GtkGrid *grid; + GtkGridLines lines[2]; +}; + + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_ROW_SPACING, + PROP_COLUMN_SPACING, + PROP_ROW_HOMOGENEOUS, + PROP_COLUMN_HOMOGENEOUS +}; + +enum +{ + CHILD_PROP_0, + CHILD_PROP_LEFT_ATTACH, + CHILD_PROP_TOP_ATTACH, + CHILD_PROP_WIDTH, + CHILD_PROP_HEIGHT +}; + +G_DEFINE_TYPE_WITH_CODE (GtkGrid, gtk_grid, GTK_TYPE_CONTAINER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) + + +static void +gtk_grid_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkGrid *grid = GTK_GRID (object); + GtkGridPrivate *priv = grid->priv; + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + + case PROP_ROW_SPACING: + g_value_set_int (value, ROWS (priv)->spacing); + break; + + case PROP_COLUMN_SPACING: + g_value_set_int (value, COLUMNS (priv)->spacing); + break; + + case PROP_ROW_HOMOGENEOUS: + g_value_set_boolean (value, ROWS (priv)->homogeneous); + break; + + case PROP_COLUMN_HOMOGENEOUS: + g_value_set_boolean (value, COLUMNS (priv)->homogeneous); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_grid_set_orientation (GtkGrid *grid, + GtkOrientation orientation) +{ + GtkGridPrivate *priv = grid->priv; + GList *list; + GtkGridChild *child; + gint left, top, width, height; + + if (priv->orientation != orientation) + { + priv->orientation = orientation; + + g_object_notify (G_OBJECT (grid), "orientation"); + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + left = CHILD_LEFT (child); + top = CHILD_TOP (child); + width = CHILD_WIDTH (child); + height = CHILD_HEIGHT (child); + + if (orientation == GTK_ORIENTATION_VERTICAL) + { + CHILD_LEFT (child) = - (top + height); + CHILD_TOP (child) = left; + CHILD_WIDTH (child) = height; + CHILD_HEIGHT (child) = width; + } + else + { + CHILD_LEFT (child) = top; + CHILD_TOP (child) = - (left + width); + CHILD_WIDTH (child) = height; + CHILD_HEIGHT (child) = width; + } + } + + gtk_widget_queue_resize (GTK_WIDGET (grid)); + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + gtk_widget_child_notify (child->widget, "left-attach"); + gtk_widget_child_notify (child->widget, "top-attach"); + gtk_widget_child_notify (child->widget, "width"); + gtk_widget_child_notify (child->widget, "height"); + + } + } +} + +static void +gtk_grid_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkGrid *grid = GTK_GRID (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + gtk_grid_set_orientation (grid, g_value_get_enum (value)); + break; + + case PROP_ROW_SPACING: + gtk_grid_set_row_spacing (grid, g_value_get_int (value)); + break; + + case PROP_COLUMN_SPACING: + gtk_grid_set_column_spacing (grid, g_value_get_int (value)); + break; + + case PROP_ROW_HOMOGENEOUS: + gtk_grid_set_row_homogeneous (grid, g_value_get_boolean (value)); + break; + + case PROP_COLUMN_HOMOGENEOUS: + gtk_grid_set_column_homogeneous (grid, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GtkGridChild * +find_grid_child (GtkGrid *grid, + GtkWidget *widget) +{ + GtkGridPrivate *priv = grid->priv; + GtkGridChild *child; + GList *list; + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + if (child->widget == widget) + return child; + } + + return NULL; +} + +static void +gtk_grid_get_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkGrid *grid = GTK_GRID (container); + GtkGridChild *grid_child; + + grid_child = find_grid_child (grid, child); + + if (grid_child == NULL) + { + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + return; + } + + switch (property_id) + { + case CHILD_PROP_LEFT_ATTACH: + g_value_set_int (value, CHILD_LEFT (grid_child)); + break; + + case CHILD_PROP_TOP_ATTACH: + g_value_set_int (value, CHILD_TOP (grid_child)); + break; + + case CHILD_PROP_WIDTH: + g_value_set_int (value, CHILD_WIDTH (grid_child)); + break; + + case CHILD_PROP_HEIGHT: + g_value_set_int (value, CHILD_HEIGHT (grid_child)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static void +gtk_grid_set_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkGrid *grid = GTK_GRID (container); + GtkGridChild *grid_child; + + grid_child = find_grid_child (grid, child); + + if (grid_child == NULL) + { + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + return; + } + + switch (property_id) + { + case CHILD_PROP_LEFT_ATTACH: + CHILD_LEFT (grid_child) = g_value_get_int (value); + break; + + case CHILD_PROP_TOP_ATTACH: + CHILD_TOP (grid_child) = g_value_get_int (value); + break; + + case CHILD_PROP_WIDTH: + CHILD_WIDTH (grid_child) = g_value_get_int (value); + break; + + case CHILD_PROP_HEIGHT: + CHILD_HEIGHT (grid_child) = g_value_get_int (value); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } + + if (gtk_widget_get_visible (child) && + gtk_widget_get_visible (GTK_WIDGET (grid))) + gtk_widget_queue_resize (child); +} + +static void +gtk_grid_init (GtkGrid *grid) +{ + GtkGridPrivate *priv; + + grid->priv = G_TYPE_INSTANCE_GET_PRIVATE (grid, GTK_TYPE_GRID, GtkGridPrivate); + priv = grid->priv; + + gtk_widget_set_has_window (GTK_WIDGET (grid), FALSE); + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (grid), FALSE); + + priv->children = NULL; + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + + priv->linedata[0].spacing = 0; + priv->linedata[1].spacing = 0; + + priv->linedata[0].homogeneous = FALSE; + priv->linedata[1].homogeneous = FALSE; +} + +static void grid_attach (GtkGrid *grid, + GtkWidget *child, + gint left, + gint top, + gint width, + gint height); + +static void +gtk_grid_add (GtkContainer *container, + GtkWidget *child) +{ + GtkGrid *grid = GTK_GRID (container); + GtkGridPrivate *priv = grid->priv; + GtkGridChild *grid_child; + GtkGridChildAttach *attach; + GtkGridChildAttach *opposite; + GList *list; + gint pos; + + pos = 0; + for (list = priv->children; list; list = list->next) + { + grid_child = list->data; + + attach = &grid_child->attach[priv->orientation]; + opposite = &grid_child->attach[1 - priv->orientation]; + + if (opposite->pos <= 0 && opposite->pos + opposite->span > 0) + pos = MAX (pos, attach->pos + attach->span); + } + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + grid_attach (grid, child, pos, 0, 1, 1); + else + grid_attach (grid, child, 0, pos, 1, 1); +} + +static void +gtk_grid_remove (GtkContainer *container, + GtkWidget *child) +{ + GtkGrid *grid = GTK_GRID (container); + GtkGridPrivate *priv = grid->priv; + GtkGridChild *grid_child; + GList *list; + + for (list = priv->children; list; list = list->next) + { + grid_child = list->data; + + if (grid_child->widget == child) + { + gboolean was_visible = gtk_widget_get_visible (child); + + gtk_widget_unparent (child); + + priv->children = g_list_remove (priv->children, grid_child); + + g_slice_free (GtkGridChild, grid_child); + + if (was_visible && gtk_widget_get_visible (GTK_WIDGET (grid))) + gtk_widget_queue_resize (GTK_WIDGET (grid)); + + break; + } + } +} + +static void +gtk_grid_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkGrid *grid = GTK_GRID (container); + GtkGridPrivate *priv = grid->priv; + GtkGridChild *child; + GList *list; + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + (* callback) (child->widget, callback_data); + } +} + +static GType +gtk_grid_child_type (GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +static GtkSizeRequestMode +gtk_grid_get_request_mode (GtkWidget *widget) +{ + GtkGridPrivate *priv = GTK_GRID (widget)->priv; + + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; + else + return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT; +} + +/* Calculates the min and max numbers for both orientations. + */ +static void +gtk_grid_request_count_lines (GtkGridRequest *request) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridChild *child; + GtkGridChildAttach *attach; + GList *list; + gint min[2]; + gint max[2]; + + min[0] = min[1] = G_MAXINT; + max[0] = max[1] = G_MININT; + + for (list = priv->children; list; list = list->next) + { + child = list->data; + attach = 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 +gtk_grid_request_init (GtkGridRequest *request, + GtkOrientation orientation) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridChild *child; + GtkGridChildAttach *attach; + GtkGridLines *lines; + GList *list; + gint 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].expand = FALSE; + } + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + attach = &child->attach[orientation]; + if (attach->span == 1 && gtk_widget_compute_expand (child->widget, 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 (GtkGridRequest *request, + GtkGridChild *child, + GtkOrientation orientation) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridLineData *linedata; + GtkGridLines *lines; + GtkGridLine *line; + GtkGridChildAttach *attach; + gint size; + gint i; + + linedata = &priv->linedata[orientation]; + lines = &request->lines[orientation]; + attach = &child->attach[orientation]; + + size = (attach->span - 1) * linedata->spacing; + 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 (GtkGridRequest *request, + GtkGridChild *child, + GtkOrientation orientation, + gboolean contextual, + gint *minimum, + gint *natural) +{ + if (contextual) + { + gint size; + + size = compute_allocation_for_child (request, child, 1 - orientation); + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width_for_height (child->widget, + size, + minimum, natural); + else + gtk_widget_get_preferred_height_for_width (child->widget, + size, + minimum, natural); + } + else + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width (child->widget, minimum, natural); + else + gtk_widget_get_preferred_height (child->widget, minimum, natural); + } +} + +/* 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 +gtk_grid_request_non_spanning (GtkGridRequest *request, + GtkOrientation orientation, + gboolean contextual) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridChild *child; + GtkGridChildAttach *attach; + GtkGridLines *lines; + GtkGridLine *line; + GList *list; + gint minimum; + gint natural; + + lines = &request->lines[orientation]; + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + attach = &child->attach[orientation]; + if (attach->span != 1) + continue; + + compute_request_for_child (request, child, orientation, contextual, &minimum, &natural); + + line = &lines->lines[attach->pos - lines->min]; + line->minimum = MAX (line->minimum, minimum); + line->natural = MAX (line->natural, natural); + } +} + +/* Enforce homogeneous sizes. + */ +static void +gtk_grid_request_homogeneous (GtkGridRequest *request, + GtkOrientation orientation) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridLineData *linedata; + GtkGridLines *lines; + gint minimum, natural; + gint i; + + linedata = &priv->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; + } +} + +/* Deals with spanning children. + * Requires expand fields of lines to be set for + * non-spanning children. + */ +static void +gtk_grid_request_spanning (GtkGridRequest *request, + GtkOrientation orientation, + gboolean contextual) +{ + GtkGridPrivate *priv = request->grid->priv; + GList *list; + GtkGridChild *child; + GtkGridChildAttach *attach; + GtkGridLineData *linedata; + GtkGridLines *lines; + GtkGridLine *line; + gint minimum; + gint natural; + gint span_minimum; + gint span_natural; + gint span_expand; + gboolean force_expand; + gint extra; + gint expand; + gint line_extra; + gint i; + + linedata = &priv->linedata[orientation]; + lines = &request->lines[orientation]; + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + attach = &child->attach[orientation]; + if (attach->span == 1) + continue; + + compute_request_for_child (request, child, orientation, contextual, &minimum, &natural); + + span_minimum = (attach->span - 1) * linedata->spacing; + span_natural = (attach->span - 1) * linedata->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. + */ + if (span_minimum < minimum) + { + 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) + { + 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 +gtk_grid_request_compute_expand (GtkGridRequest *request, + GtkOrientation orientation, + gint *nonempty_lines, + gint *expand_lines) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridChild *child; + GtkGridChildAttach *attach; + GList *list; + gint i; + GtkGridLines *lines; + GtkGridLine *line; + gboolean has_expand; + gint expand; + gint empty; + + lines = &request->lines[orientation]; + + for (i = 0; i < lines->max - lines->min; i++) + { + lines->lines[i].need_expand = FALSE; + lines->lines[i].expand = FALSE; + lines->lines[i].empty = TRUE; + } + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + attach = &child->attach[orientation]; + if (attach->span != 1) + continue; + + line = &lines->lines[attach->pos - lines->min]; + line->empty = FALSE; + if (gtk_widget_compute_expand (child->widget, orientation)) + line->expand = TRUE; + } + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + attach = &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]; + line->empty = FALSE; + if (line->expand) + has_expand = TRUE; + } + + if (!has_expand && gtk_widget_compute_expand (child->widget, orientation)) + { + for (i = 0; i < attach->span; i++) + { + line = &lines->lines[attach->pos - lines->min + i]; + line->need_expand = TRUE; + } + } + } + + empty = 0; + expand = 0; + for (i = 0; i < lines->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 = lines->max - lines->min - empty; + + if (expand_lines) + *expand_lines = expand; +} + +/* Sums the minimum and natural fields of lines and their spacing. + */ +static void +gtk_grid_request_sum (GtkGridRequest *request, + GtkOrientation orientation, + gint *minimum, + gint *natural) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridLineData *linedata; + GtkGridLines *lines; + gint i; + gint min, nat; + gint nonempty; + + gtk_grid_request_compute_expand (request, orientation, &nonempty, NULL); + + linedata = &priv->linedata[orientation]; + lines = &request->lines[orientation]; + + min = (nonempty - 1) * linedata->spacing; + nat = (nonempty - 1) * linedata->spacing; + + for (i = 0; i < lines->max - lines->min; i++) + { + min += lines->lines[i].minimum; + nat += lines->lines[i].natural; + } + + if (minimum) + *minimum = min; + + if (natural) + *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 +gtk_grid_request_run (GtkGridRequest *request, + GtkOrientation orientation, + gboolean contextual) +{ + gtk_grid_request_init (request, orientation); + gtk_grid_request_non_spanning (request, orientation, contextual); + gtk_grid_request_homogeneous (request, orientation); + gtk_grid_request_spanning (request, orientation, contextual); + gtk_grid_request_homogeneous (request, orientation); +} + +/* 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 +gtk_grid_request_allocate (GtkGridRequest *request, + GtkOrientation orientation, + gint total_size) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridLineData *linedata; + GtkGridLines *lines; + GtkGridLine *line; + gint nonempty; + gint expand; + gint i, j; + GtkRequestedSize *sizes; + gint extra; + gint rest; + gint size; + + gtk_grid_request_compute_expand (request, orientation, &nonempty, &expand); + + linedata = &priv->linedata[orientation]; + lines = &request->lines[orientation]; + + size = total_size - (nonempty - 1) * linedata->spacing; + + if (linedata->homogeneous) + { + extra = size / nonempty; + rest = size % nonempty; + + 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 + { + sizes = g_newa (GtkRequestedSize, nonempty); + + j = 0; + for (i = 0; i < lines->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 = 0; i < lines->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++; + } + } +} + +/* Computes the position fields from allocation and spacing. + */ +static void +gtk_grid_request_position (GtkGridRequest *request, + GtkOrientation orientation) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridLineData *linedata; + GtkGridLines *lines; + GtkGridLine *line; + gint position; + gint i; + + linedata = &priv->linedata[orientation]; + lines = &request->lines[orientation]; + + position = 0; + for (i = 0; i < lines->max - lines->min; i++) + { + line = &lines->lines[i]; + if (!line->empty) + { + line->position = position; + position += line->allocation + linedata->spacing; + } + } +} + +static void +gtk_grid_get_size (GtkGrid *grid, + GtkOrientation orientation, + gint *minimum, + gint *natural) +{ + GtkGridRequest request; + GtkGridLines *lines; + + request.grid = grid; + gtk_grid_request_count_lines (&request); + lines = &request.lines[orientation]; + lines->lines = g_newa (GtkGridLine, lines->max - lines->min); + + gtk_grid_request_run (&request, orientation, FALSE); + gtk_grid_request_sum (&request, orientation, minimum, natural); +} + +static void +gtk_grid_get_size_for_size (GtkGrid *grid, + GtkOrientation orientation, + gint size, + gint *minimum, + gint *natural) +{ + GtkGridRequest request; + GtkGridLines *lines; + gint min_size; + + request.grid = grid; + gtk_grid_request_count_lines (&request); + lines = &request.lines[0]; + lines->lines = g_newa (GtkGridLine, lines->max - lines->min); + lines = &request.lines[1]; + lines->lines = g_newa (GtkGridLine, lines->max - lines->min); + + gtk_grid_request_run (&request, 1 - orientation, FALSE); + gtk_grid_request_sum (&request, 1 - orientation, &min_size, NULL); + gtk_grid_request_allocate (&request, 1 - orientation, MAX (size, min_size)); + + gtk_grid_request_run (&request, orientation, TRUE); + gtk_grid_request_sum (&request, orientation, minimum, natural); +} + +static void +gtk_grid_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GtkGrid *grid = GTK_GRID (widget); + + if (gtk_grid_get_request_mode (widget) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT) + + gtk_grid_get_size_for_size (grid, GTK_ORIENTATION_HORIZONTAL, 0, minimum, natural); + else + gtk_grid_get_size (grid, GTK_ORIENTATION_HORIZONTAL, minimum, natural); +} + +static void +gtk_grid_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GtkGrid *grid = GTK_GRID (widget); + + if (gtk_grid_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + gtk_grid_get_size_for_size (grid, GTK_ORIENTATION_VERTICAL, 0, minimum, natural); + else + gtk_grid_get_size (grid, GTK_ORIENTATION_VERTICAL, minimum, natural); +} + +static void +gtk_grid_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum, + gint *natural) +{ + GtkGrid *grid = GTK_GRID (widget); + + if (gtk_grid_get_request_mode (widget) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT) + gtk_grid_get_size_for_size (grid, GTK_ORIENTATION_HORIZONTAL, height, minimum, natural); + else + gtk_grid_get_size (grid, GTK_ORIENTATION_HORIZONTAL, minimum, natural); +} + +static void +gtk_grid_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum, + gint *natural) +{ + GtkGrid *grid = GTK_GRID (widget); + + if (gtk_grid_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + gtk_grid_get_size_for_size (grid, GTK_ORIENTATION_VERTICAL, width, minimum, natural); + else + gtk_grid_get_size (grid, GTK_ORIENTATION_VERTICAL, minimum, natural); +} + +static void +allocate_child (GtkGridRequest *request, + GtkOrientation orientation, + GtkGridChild *child, + gint *position, + gint *size) +{ + GtkGridPrivate *priv = request->grid->priv; + GtkGridLineData *linedata; + GtkGridLines *lines; + GtkGridLine *line; + GtkGridChildAttach *attach; + gint i; + + linedata = &priv->linedata[orientation]; + lines = &request->lines[orientation]; + attach = &child->attach[orientation]; + + *position = lines->lines[attach->pos - lines->min].position; + + *size = (attach->span - 1) * linedata->spacing; + for (i = 0; i < attach->span; i++) + { + line = &lines->lines[attach->pos - lines->min + i]; + *size += line->allocation; + } +} + +static void +gtk_grid_request_allocate_children (GtkGridRequest *request) +{ + GtkGridPrivate *priv = request->grid->priv; + GList *list; + GtkGridChild *child; + GtkAllocation allocation; + GtkAllocation child_allocation; + gint x, y, width, height; + + gtk_widget_get_allocation (GTK_WIDGET (request->grid), &allocation); + + for (list = priv->children; list; list = list->next) + { + child = list->data; + + if (!gtk_widget_get_visible (child->widget)) + continue; + + allocate_child (request, GTK_ORIENTATION_HORIZONTAL, child, &x, &width); + allocate_child (request, GTK_ORIENTATION_VERTICAL, child, &y, &height); + + child_allocation.x = allocation.x + x; + child_allocation.y = allocation.y + y; + child_allocation.width = MAX (1, width); + child_allocation.height = MAX (1, height); + + gtk_widget_size_allocate (child->widget, &child_allocation); + } +} + +#define GET_SIZE(allocation, orientation) (orientation == GTK_ORIENTATION_HORIZONTAL ? allocation->width : allocation->height) + +static void +gtk_grid_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkGrid *grid = GTK_GRID (widget); + GtkGridPrivate *priv = grid->priv; + GtkGridRequest request; + GtkGridLines *lines; + + request.grid = grid; + gtk_grid_request_count_lines (&request); + lines = &request.lines[0]; + lines->lines = g_newa (GtkGridLine, lines->max - lines->min); + lines = &request.lines[1]; + lines->lines = g_newa (GtkGridLine, lines->max - lines->min); + + gtk_widget_set_allocation (widget, allocation); + + gtk_grid_request_run (&request, 1 - priv->orientation, FALSE); + gtk_grid_request_allocate (&request, 1 - priv->orientation, GET_SIZE (allocation, 1 - priv->orientation)); + gtk_grid_request_run (&request, priv->orientation, TRUE); + gtk_grid_request_allocate (&request, priv->orientation, GET_SIZE (allocation, priv->orientation)); + + gtk_grid_request_position (&request, 0); + gtk_grid_request_position (&request, 1); + + gtk_grid_request_allocate_children (&request); +} + +static void +gtk_grid_class_init (GtkGridClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); + + object_class->get_property = gtk_grid_get_property; + object_class->set_property = gtk_grid_set_property; + + widget_class->size_allocate = gtk_grid_size_allocate; + widget_class->get_preferred_width = gtk_grid_get_preferred_width; + widget_class->get_preferred_height = gtk_grid_get_preferred_height; + widget_class->get_request_mode = gtk_grid_get_request_mode; + widget_class->get_preferred_width_for_height = gtk_grid_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = gtk_grid_get_preferred_height_for_width; + + container_class->add = gtk_grid_add; + container_class->remove = gtk_grid_remove; + container_class->forall = gtk_grid_forall; + container_class->child_type = gtk_grid_child_type; + container_class->set_child_property = gtk_grid_set_child_property; + container_class->get_child_property = gtk_grid_get_child_property; + gtk_container_class_handle_border_width (container_class); + + g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); + + g_object_class_install_property (object_class, 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_object_class_install_property (object_class, 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_object_class_install_property (object_class, 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_object_class_install_property (object_class, 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)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_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)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_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)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_WIDTH, + g_param_spec_int ("width", + P_("Width"), + P_("The number of columns that a child spans"), + 1, G_MAXINT, 1, + GTK_PARAM_READWRITE)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_HEIGHT, + g_param_spec_int ("height", + P_("Height"), + P_("The number of rows that a child spans"), + 1, G_MAXINT, 1, + GTK_PARAM_READWRITE)); + + g_type_class_add_private (class, sizeof (GtkGridPrivate)); +} + +/** + * gtk_grid_new: + * + * Creates a new grid widget. + * + * Returns: the new #GtkGrid + */ +GtkWidget * +gtk_grid_new (void) +{ + return g_object_new (GTK_TYPE_GRID, NULL); +} + +static void +grid_attach (GtkGrid *grid, + GtkWidget *widget, + gint left, + gint top, + gint width, + gint height) +{ + GtkGridPrivate *priv = grid->priv; + GtkGridChild *child; + + child = g_slice_new (GtkGridChild); + child->widget = widget; + CHILD_LEFT (child) = left; + CHILD_TOP (child) = top; + CHILD_WIDTH (child) = width; + CHILD_HEIGHT (child) = height; + + priv->children = g_list_prepend (priv->children, child); + + gtk_widget_set_parent (widget, GTK_WIDGET (grid)); +} + +/** + * gtk_grid_attach: + * @grid: a #GtkGrid + * @child: the widget to add + * @left: the column number to attach the left side of @child to + * @top: the row number to attach the top side of @child to + * @width: the number of columns that @child will span + * @height: the number of rows that @child will span + * + * Adds a widget to the grid. + * + * The position of @child is determined by @left and @top. The + * number of 'cells' that @child will occupy is determined by + * @width and @height. + */ +void +gtk_grid_attach (GtkGrid *grid, + GtkWidget *child, + gint left, + gint top, + gint width, + gint height) +{ + g_return_if_fail (GTK_IS_GRID (grid)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (gtk_widget_get_parent (child) == NULL); + g_return_if_fail (width > 0); + g_return_if_fail (height > 0); + + grid_attach (grid, child, left, top, width, height); +} + +/** + * gtk_grid_attach_next_to: + * @grid: a #GtkGrid + * @child: the widget to add + * @sibling: the child of @grid that @child will be placed next to + * @side: the side of @sibling that @child is positioned next to + * @width: the number of columns that @child will span + * @height: the number of rows that @child will span + * + * Adds a widget to the grid. + * + * The widget is placed next to @sibling, on the side determined by + * @side. + */ +void +gtk_grid_attach_next_to (GtkGrid *grid, + GtkWidget *child, + GtkWidget *sibling, + GtkPositionType side, + gint width, + gint height) +{ + GtkGridChild *grid_sibling; + gint left, top; + + g_return_if_fail (GTK_IS_GRID (grid)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (gtk_widget_get_parent (child) == NULL); + g_return_if_fail (gtk_widget_get_parent (sibling) == (GtkWidget*)grid); + g_return_if_fail (width > 0); + g_return_if_fail (height > 0); + + grid_sibling = find_grid_child (grid, sibling); + + switch (side) + { + case GTK_POS_LEFT: + left = CHILD_LEFT (grid_sibling) - width; + top = CHILD_TOP (grid_sibling); + break; + case GTK_POS_RIGHT: + left = CHILD_LEFT (grid_sibling) + CHILD_WIDTH (grid_sibling); + top = CHILD_TOP (grid_sibling); + break; + case GTK_POS_TOP: + left = CHILD_LEFT (grid_sibling); + top = CHILD_TOP (grid_sibling) - height; + break; + case GTK_POS_BOTTOM: + left = CHILD_LEFT (grid_sibling); + top = CHILD_TOP (grid_sibling) + CHILD_HEIGHT (grid_sibling); + break; + default: + g_assert_not_reached (); + } + + grid_attach (grid, child, left, top, width, height); +} + +/** + * gtk_grid_set_row_homogeneous: + * @grid: a #GtkGrid + * @homogeneous: %TRUE to make rows homogeneous + * + * Sets whether all rows of @grid will have the same height. + */ +void +gtk_grid_set_row_homogeneous (GtkGrid *grid, + gboolean homogeneous) +{ + GtkGridPrivate *priv; + g_return_if_fail (GTK_IS_GRID (grid)); + + priv = grid->priv; + + if (ROWS (priv)->homogeneous != homogeneous) + { + ROWS (priv)->homogeneous = homogeneous; + + if (gtk_widget_get_visible (GTK_WIDGET (grid))) + gtk_widget_queue_resize (GTK_WIDGET (grid)); + + g_object_notify (G_OBJECT (grid), "row-homogeneous"); + } +} + +/** + * gtk_grid_get_row_homogeneous: + * @grid: a #GtkGrid + * + * Returns whether all rows of @grid have the same height. + * + * Returns: whether all rows of @grid have the same height. + */ +gboolean +gtk_grid_get_row_homogeneous (GtkGrid *grid) +{ + GtkGridPrivate *priv; + g_return_val_if_fail (GTK_IS_GRID (grid), FALSE); + + priv = grid->priv; + + return ROWS (priv)->homogeneous; +} + +/** + * gtk_grid_set_column_homogeneous: + * @grid: a #GtkGrid + * @homogeneous: %TRUE to make columns homogeneous + * + * Sets whether all columns of @grid will have the same width. + */ +void +gtk_grid_set_column_homogeneous (GtkGrid *grid, + gboolean homogeneous) +{ + GtkGridPrivate *priv; + g_return_if_fail (GTK_IS_GRID (grid)); + + priv = grid->priv; + + if (COLUMNS (priv)->homogeneous != homogeneous) + { + COLUMNS (priv)->homogeneous = homogeneous; + + if (gtk_widget_get_visible (GTK_WIDGET (grid))) + gtk_widget_queue_resize (GTK_WIDGET (grid)); + + g_object_notify (G_OBJECT (grid), "column-homogeneous"); + } +} + +/** + * gtk_grid_get_column_homogeneous: + * @grid: a #GtkGrid + * + * Returns whether all columns of @grid have the same width. + * + * Returns: whether all columns of @grid have the same width. + */ +gboolean +gtk_grid_get_column_homogeneous (GtkGrid *grid) +{ + GtkGridPrivate *priv; + g_return_val_if_fail (GTK_IS_GRID (grid), FALSE); + + priv = grid->priv; + + return COLUMNS (priv)->homogeneous; +} + +/** + * gtk_grid_set_row_spacing: + * @grid: a #GtkGrid + * @spacing: the amount of space to insert between rows + * + * Sets the amount of space between rows of @grid. + */ +void +gtk_grid_set_row_spacing (GtkGrid *grid, + guint spacing) +{ + GtkGridPrivate *priv; + g_return_if_fail (GTK_IS_GRID (grid)); + g_return_if_fail (spacing <= G_MAXINT16); + + priv = grid->priv; + + if (ROWS (priv)->spacing != spacing) + { + ROWS (priv)->spacing = spacing; + + if (gtk_widget_get_visible (GTK_WIDGET (grid))) + gtk_widget_queue_resize (GTK_WIDGET (grid)); + + g_object_notify (G_OBJECT (grid), "row-spacing"); + } +} + +/** + * gtk_grid_get_row_spacing: + * @grid: a #GtkGrid + * + * Returns the amount of space between the rows of @grid. + * + * Returns: the row spacing of @grid + */ +guint +gtk_grid_get_row_spacing (GtkGrid *grid) +{ + GtkGridPrivate *priv; + g_return_val_if_fail (GTK_IS_GRID (grid), 0); + + priv = grid->priv; + + return ROWS (priv)->spacing; +} + +/** + * gtk_grid_set_column_spacing: + * @grid: a #GtkGrid + * @spacing: the amount of space to insert between columns + * + * Sets the amount of space between columns of @grid. + */ +void +gtk_grid_set_column_spacing (GtkGrid *grid, + guint spacing) +{ + GtkGridPrivate *priv; + g_return_if_fail (GTK_IS_GRID (grid)); + g_return_if_fail (spacing <= G_MAXINT16); + + priv = grid->priv; + + if (COLUMNS (priv)->spacing != spacing) + { + COLUMNS (priv)->spacing = spacing; + + if (gtk_widget_get_visible (GTK_WIDGET (grid))) + gtk_widget_queue_resize (GTK_WIDGET (grid)); + + g_object_notify (G_OBJECT (grid), "column-spacing"); + } +} + +/** + * gtk_grid_get_column_spacing: + * @grid: a #GtkGrid + * + * Returns the amount of space between the columns of @grid. + * + * Returns: the column spacing of @grid + */ +guint +gtk_grid_get_column_spacing (GtkGrid *grid) +{ + GtkGridPrivate *priv; + + g_return_val_if_fail (GTK_IS_GRID (grid), 0); + + priv = grid->priv; + + return COLUMNS (priv)->spacing; +} diff --git a/gtk/gtkgrid.h b/gtk/gtkgrid.h new file mode 100644 index 0000000000..590d9cddeb --- /dev/null +++ b/gtk/gtkgrid.h @@ -0,0 +1,98 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2010 Red Hat, Inc. + * Author: Matthias Clasen + * + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GTK_GRID_H__ +#define __GTK_GRID_H__ + + +#include + + +G_BEGIN_DECLS + +#define GTK_TYPE_GRID (gtk_grid_get_type ()) +#define GTK_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_GRID, GtkGrid)) +#define GTK_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_GRID, GtkGridClass)) +#define GTK_IS_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_GRID)) +#define GTK_IS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_GRID)) +#define GTK_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_GRID, GtkGridClass)) + + +typedef struct _GtkGrid GtkGrid; +typedef struct _GtkGridPrivate GtkGridPrivate; +typedef struct _GtkGridClass GtkGridClass; + +struct _GtkGrid +{ + /* */ + GtkContainer container; + + GtkGridPrivate *priv; +}; + +struct _GtkGridClass +{ + GtkContainerClass parent_class; + + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); + void (*_gtk_reserved7) (void); + void (*_gtk_reserved8) (void); +}; + +GType gtk_grid_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_grid_new (void); +void gtk_grid_attach (GtkGrid *grid, + GtkWidget *child, + gint left, + gint top, + gint width, + gint height); +void gtk_grid_attach_next_to (GtkGrid *grid, + GtkWidget *widget, + GtkWidget *sibling, + GtkPositionType side, + gint width, + gint height); +void gtk_grid_set_row_homogeneous (GtkGrid *grid, + gboolean homogeneous); +gboolean gtk_grid_get_row_homogeneous (GtkGrid *grid); +void gtk_grid_set_row_spacing (GtkGrid *grid, + guint spacing); +guint gtk_grid_get_row_spacing (GtkGrid *grid); +void gtk_grid_set_column_homogeneous (GtkGrid *grid, + gboolean homogeneous); +gboolean gtk_grid_get_column_homogeneous (GtkGrid *grid); +void gtk_grid_set_column_spacing (GtkGrid *grid, + guint spacing); +guint gtk_grid_get_column_spacing (GtkGrid *grid); + + +G_END_DECLS + +#endif /* __GTK_GRID_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 455bf4bf54..6132dc8743 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -48,6 +48,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testfilechooserbutton \ testframe \ testgeometry \ + testgrid \ testgtk \ testheightforwidth \ testiconview \ @@ -137,6 +138,7 @@ testfilechooser_DEPENDENCIES = $(TEST_DEPS) testfilechooserbutton_DEPENDENCIES = $(TEST_DEPS) testframe_DEPENDENCIES = $(TEST_DEPS) testgeometry_DEPENDENCIES = $(TEST_DEPS) +testgrid_DEPENDENCIES = $(TEST_DEPS) testgtk_DEPENDENCIES = $(TEST_DEPS) testinput_DEPENDENCIES = $(TEST_DEPS) testimage_DEPENDENCIES = $(TEST_DEPS) @@ -202,6 +204,7 @@ testfilechooser_LDADD = $(LDADDS) testfilechooserbutton_LDADD = $(LDADDS) testframe_LDADD = $(LDADDS) testgeometry_LDADD = $(LDADDS) +testgrid_LDADD = $(LDADDS) testgtk_LDADD = $(LDADDS) testheightforwidth_LDADD = $(LDADDS) testicontheme_LDADD = $(LDADDS) @@ -266,6 +269,9 @@ testfilechooserbutton_SOURCES = \ prop-editor.c \ testfilechooserbutton.c +testgrid_SOURCES = \ + testgrid.c + testgtk_SOURCES = \ prop-editor.c \ testgtk.c diff --git a/tests/testgrid.c b/tests/testgrid.c new file mode 100644 index 0000000000..76d1a8cb52 --- /dev/null +++ b/tests/testgrid.c @@ -0,0 +1,264 @@ +#include + +static GtkWidget * +oriented_test_widget (const gchar *label, const gchar *color, gdouble angle) +{ + GtkWidget *box; + GtkWidget *widget; + GdkColor c; + + widget = gtk_label_new (label); + gtk_label_set_angle (GTK_LABEL (widget), angle); + box = gtk_event_box_new (); + gdk_color_parse (color, &c); + gtk_widget_modify_bg (box, GTK_STATE_NORMAL, &c); + gtk_container_add (GTK_CONTAINER (box), widget); + + return box; +} + +static GtkWidget * +test_widget (const gchar *label, const gchar *color) +{ + return oriented_test_widget (label, color, 0.0); +} + +static GtkOrientation o; + +static gboolean +toggle_orientation (GtkWidget *window, GdkEventButton *event, GtkGrid *grid) +{ + o = 1 - o; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), o); + + return FALSE; +} + +static void +simple_grid (void) +{ + GtkWidget *window; + GtkWidget *grid; + GtkWidget *child; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Orientation"); + grid = gtk_grid_new (); + gtk_container_add (GTK_CONTAINER (window), grid); + g_signal_connect (window, "button-press-event", G_CALLBACK (toggle_orientation), grid); + + gtk_grid_set_column_spacing (GTK_GRID (grid), 5); + gtk_grid_set_row_spacing (GTK_GRID (grid), 5); + gtk_container_add (GTK_CONTAINER (grid), test_widget ("1", "red")); + gtk_container_add (GTK_CONTAINER (grid), test_widget ("2", "green")); + gtk_container_add (GTK_CONTAINER (grid), test_widget ("3", "blue")); + child = test_widget ("4", "green"); + gtk_grid_attach (GTK_GRID (grid), child, 0, 1, 1, 1); + gtk_widget_set_vexpand (child, TRUE); + gtk_grid_attach_next_to (GTK_GRID (grid), test_widget ("5", "blue"), child, GTK_POS_RIGHT, 2, 1); + child = test_widget ("6", "yellow"); + gtk_grid_attach (GTK_GRID (grid), child, -1, 0, 1, 2); + gtk_widget_set_hexpand (child, TRUE); + + gtk_widget_show_all (window); +} + +static void +text_grid (void) +{ + GtkWidget *window; + GtkWidget *grid; + GtkWidget *paned1; + GtkWidget *box; + GtkWidget *label; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Height-for-Width"); + paned1 = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_container_add (GTK_CONTAINER (window), paned1); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0); + gtk_paned_pack1 (GTK_PANED (paned1), box, TRUE, FALSE); + gtk_paned_pack2 (GTK_PANED (paned1), gtk_label_new ("Space"), TRUE, FALSE); + + grid = gtk_grid_new (); + gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL); + gtk_container_add (GTK_CONTAINER (box), gtk_label_new ("Above")); + gtk_container_add (GTK_CONTAINER (box), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + gtk_container_add (GTK_CONTAINER (box), grid); + gtk_container_add (GTK_CONTAINER (box), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + gtk_container_add (GTK_CONTAINER (box), gtk_label_new ("Below")); + + label = gtk_label_new ("Some text that may wrap if it has to"); + gtk_label_set_width_chars (GTK_LABEL (label), 10); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + + gtk_grid_attach (GTK_GRID (grid), test_widget ("1", "red"), 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), test_widget ("2", "blue"), 0, 1, 1, 1); + + label = gtk_label_new ("Some text that may wrap if it has to"); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_width_chars (GTK_LABEL (label), 10); + gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1); + + gtk_widget_show_all (window); +} + +static void +box_comparison (void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *box; + GtkWidget *label; + GtkWidget *grid; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Grid vs. Box"); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 5); + gtk_container_add (GTK_CONTAINER (window), vbox); + + gtk_container_add (GTK_CONTAINER (vbox), gtk_label_new ("Above")); + gtk_container_add (GTK_CONTAINER (vbox), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0, FALSE); + gtk_container_add (GTK_CONTAINER (vbox), box); + + gtk_box_pack_start (GTK_BOX (box), test_widget ("1", "white"), FALSE, FALSE, 0); + + label = gtk_label_new ("Some ellipsizing text"); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_width_chars (GTK_LABEL (label), 10); + gtk_box_pack_start (GTK_BOX (box), label, TRUE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (box), test_widget ("2", "green"), FALSE, FALSE, 0); + + label = gtk_label_new ("Some text that may wrap if needed"); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_width_chars (GTK_LABEL (label), 10); + gtk_box_pack_start (GTK_BOX (box), label, TRUE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (box), test_widget ("3", "red"), FALSE, FALSE, 0); + + grid = gtk_grid_new (); + gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL); + gtk_container_add (GTK_CONTAINER (vbox), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + gtk_container_add (GTK_CONTAINER (vbox), grid); + + gtk_grid_attach (GTK_GRID (grid), test_widget ("1", "white"), 0, 0, 1, 1); + + label = gtk_label_new ("Some ellipsizing text"); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_width_chars (GTK_LABEL (label), 10); + gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1); + gtk_widget_set_hexpand (label, TRUE); + + gtk_grid_attach (GTK_GRID (grid), test_widget ("2", "green"), 2, 0, 1, 1); + + label = gtk_label_new ("Some text that may wrap if needed"); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_width_chars (GTK_LABEL (label), 10); + gtk_grid_attach (GTK_GRID (grid), label, 3, 0, 1, 1); + gtk_widget_set_hexpand (label, TRUE); + + gtk_grid_attach (GTK_GRID (grid), test_widget ("3", "red"), 4, 0, 1, 1); + + gtk_container_add (GTK_CONTAINER (vbox), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + gtk_container_add (GTK_CONTAINER (vbox), gtk_label_new ("Below")); + + gtk_widget_show_all (window); +} + +static void +empty_line (void) +{ + GtkWidget *window; + GtkWidget *grid; + GtkWidget *child; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Empty row"); + grid = gtk_grid_new (); + gtk_container_add (GTK_CONTAINER (window), grid); + + gtk_grid_set_row_spacing (GTK_GRID (grid), 10); + gtk_grid_set_column_spacing (GTK_GRID (grid), 10); + + child = test_widget ("(0, 0)", "red"); + gtk_grid_attach (GTK_GRID (grid), child, 0, 0, 1, 1); + gtk_widget_set_hexpand (child, TRUE); + gtk_widget_set_vexpand (child, TRUE); + + gtk_grid_attach (GTK_GRID (grid), test_widget ("(0, 1)", "blue"), 0, 1, 1, 1); + + gtk_grid_attach (GTK_GRID (grid), test_widget ("(10, 0)", "green"), 10, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), test_widget ("(10, 1)", "magenta"), 10, 1, 1, 1); + + gtk_widget_show_all (window); +} + +static void +scrolling (void) +{ + GtkWidget *window; + GtkWidget *sw; + GtkWidget *viewport; + GtkWidget *grid; + GtkWidget *child; + gint i; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Scrolling"); + sw = gtk_scrolled_window_new (NULL, NULL); + viewport = gtk_viewport_new (NULL, NULL); + grid = gtk_grid_new (); + + gtk_container_add (GTK_CONTAINER (window), sw); + gtk_container_add (GTK_CONTAINER (sw), viewport); + gtk_container_add (GTK_CONTAINER (viewport), grid); + + child = oriented_test_widget ("#800080", "#800080", -45.0); + gtk_grid_attach (GTK_GRID (grid), child, 0, 0, 1, 1); + gtk_widget_set_hexpand (child, TRUE); + gtk_widget_set_vexpand (child, TRUE); + + for (i = 1; i < 16; i++) + { + gchar *color; + color = g_strdup_printf ("#%02x00%02x", 128 + 8*i, 128 - 8*i); + child = test_widget (color, color); + gtk_grid_attach (GTK_GRID (grid), child, 0, i, i + 1, 1); + gtk_widget_set_hexpand (child, TRUE); + g_free (color); + } + + for (i = 1; i < 16; i++) + { + gchar *color; + color = g_strdup_printf ("#%02x00%02x", 128 - 8*i, 128 + 8*i); + child = oriented_test_widget (color, color, -90.0); + gtk_grid_attach (GTK_GRID (grid), child, i, 0, 1, i); + gtk_widget_set_vexpand (child, TRUE); + g_free (color); + } + + gtk_widget_show_all (window); +} + +int +main (int argc, char *argv[]) +{ + gtk_init (NULL, NULL); + + simple_grid (); + text_grid (); + box_comparison (); + empty_line (); + scrolling (); + + gtk_main (); + + return 0; +}