473 lines
14 KiB
C
473 lines
14 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Copyright 2013 Red Hat, Inc.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:nmt-newt-grid
|
|
* @short_description: Grid container
|
|
*
|
|
* #NmtNewtGrid is the most general-purpose container widget in NmtNewt.
|
|
*
|
|
* An #NmtNewtGrid consists of a number of rows and columns. There is
|
|
* no pre-established maximum row or columns. Rather, rows and columns
|
|
* exist if and only if there are widgets in them.
|
|
*
|
|
* The width of each column is the width of the widest widget in that
|
|
* column, and the height of each row is the height of the tallest
|
|
* widget in that row. Empty rows and empty columns take up no space,
|
|
* so a grid with a single widget at 0,0 would look exactly the same
|
|
* if the widget was at 5,10 instead.
|
|
*
|
|
* If a widget's cell ends up being larger than the widget's requested
|
|
* size, then by default the widget will be centered in its cell.
|
|
* However, this can be modified by changing its #NmtNewtGridFlags.
|
|
* FIXME: the FILL/ANCHOR flags can be implemented in #NmtNewtWidget
|
|
* and so should move there. Less clear about the EXPAND flags, which
|
|
* must be implemented by the container...
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "nmt-newt-grid.h"
|
|
|
|
G_DEFINE_TYPE (NmtNewtGrid, nmt_newt_grid, NMT_TYPE_NEWT_CONTAINER)
|
|
|
|
#define NMT_NEWT_GRID_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NMT_TYPE_NEWT_GRID, NmtNewtGridPrivate))
|
|
|
|
typedef struct {
|
|
NmtNewtWidget *widget;
|
|
int x, y;
|
|
NmtNewtGridFlags flags;
|
|
int req_height, req_width;
|
|
} NmtNewtGridChild;
|
|
|
|
typedef struct {
|
|
GArray *children;
|
|
int max_x, max_y;
|
|
int *row_heights, *col_widths;
|
|
gboolean *expand_rows, *expand_cols;
|
|
int n_expand_rows, n_expand_cols;
|
|
int req_height, req_width;
|
|
} NmtNewtGridPrivate;
|
|
|
|
/**
|
|
* nmt_newt_grid_new:
|
|
*
|
|
* Creates a new #NmtNewtGrid
|
|
*
|
|
* Returns: a new #NmtNewtGrid
|
|
*/
|
|
NmtNewtWidget *
|
|
nmt_newt_grid_new (void)
|
|
{
|
|
return g_object_new (NMT_TYPE_NEWT_GRID, NULL);
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_init (NmtNewtGrid *grid)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
|
|
priv->children = g_array_new (FALSE, FALSE, sizeof (NmtNewtGridChild));
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_finalize (GObject *object)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (object);
|
|
|
|
g_array_unref (priv->children);
|
|
g_clear_pointer (&priv->row_heights, g_free);
|
|
g_clear_pointer (&priv->col_widths, g_free);
|
|
g_clear_pointer (&priv->expand_rows, g_free);
|
|
g_clear_pointer (&priv->expand_cols, g_free);
|
|
|
|
G_OBJECT_CLASS (nmt_newt_grid_parent_class)->finalize (object);
|
|
}
|
|
|
|
static int
|
|
child_sort_func (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
NmtNewtGridChild *child_a = (NmtNewtGridChild *)a;
|
|
NmtNewtGridChild *child_b = (NmtNewtGridChild *)b;
|
|
|
|
if (child_a->y != child_b->y)
|
|
return child_a->y - child_b->y;
|
|
else
|
|
return child_a->x - child_b->x;
|
|
}
|
|
|
|
static newtComponent *
|
|
nmt_newt_grid_get_components (NmtNewtWidget *widget)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (widget);
|
|
NmtNewtGridChild *children;
|
|
GPtrArray *cos;
|
|
newtComponent *child_cos;
|
|
int i, c;
|
|
|
|
g_array_sort (priv->children, child_sort_func);
|
|
children = (NmtNewtGridChild *)priv->children->data;
|
|
|
|
cos = g_ptr_array_new ();
|
|
|
|
for (i = 0; i < priv->children->len; i++) {
|
|
if (!nmt_newt_widget_get_visible (children[i].widget))
|
|
continue;
|
|
|
|
child_cos = nmt_newt_widget_get_components (children[i].widget);
|
|
for (c = 0; child_cos[c]; c++)
|
|
g_ptr_array_add (cos, child_cos[c]);
|
|
g_free (child_cos);
|
|
}
|
|
g_ptr_array_add (cos, NULL);
|
|
|
|
return (newtComponent *) g_ptr_array_free (cos, FALSE);
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_size_request (NmtNewtWidget *widget,
|
|
int *width,
|
|
int *height)
|
|
{
|
|
NmtNewtGrid *grid = NMT_NEWT_GRID (widget);
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
NmtNewtGridChild *children = (NmtNewtGridChild *)priv->children->data;
|
|
int row, col, i;
|
|
|
|
g_free (priv->row_heights);
|
|
g_free (priv->col_widths);
|
|
g_free (priv->expand_rows);
|
|
g_free (priv->expand_cols);
|
|
|
|
priv->row_heights = g_new0 (int, priv->max_y + 1);
|
|
priv->col_widths = g_new0 (int, priv->max_x + 1);
|
|
priv->expand_rows = g_new0 (gboolean, priv->max_y + 1);
|
|
priv->expand_cols = g_new0 (gboolean, priv->max_x + 1);
|
|
priv->n_expand_rows = priv->n_expand_cols = 0;
|
|
|
|
for (row = 0; row < priv->max_y + 1; row++) {
|
|
for (col = 0; col < priv->max_x + 1; col++) {
|
|
for (i = 0; i < priv->children->len; i++) {
|
|
if (children[i].x != col || children[i].y != row)
|
|
continue;
|
|
if (!nmt_newt_widget_get_visible (children[i].widget))
|
|
continue;
|
|
|
|
nmt_newt_widget_size_request (children[i].widget,
|
|
&children[i].req_width,
|
|
&children[i].req_height);
|
|
if (children[i].req_height > priv->row_heights[row])
|
|
priv->row_heights[row] = children[i].req_height;
|
|
if (children[i].req_width > priv->col_widths[col])
|
|
priv->col_widths[col] = children[i].req_width;
|
|
|
|
if ( (children[i].flags & NMT_NEWT_GRID_EXPAND_X)
|
|
&& !priv->expand_cols[children[i].x]) {
|
|
priv->expand_cols[children[i].x] = TRUE;
|
|
priv->n_expand_cols++;
|
|
}
|
|
if ( (children[i].flags & NMT_NEWT_GRID_EXPAND_Y)
|
|
&& !priv->expand_rows[children[i].y]) {
|
|
priv->expand_rows[children[i].y] = TRUE;
|
|
priv->n_expand_rows++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
priv->req_height = priv->req_width = 0;
|
|
for (row = 0; row < priv->max_y + 1; row++)
|
|
priv->req_height += priv->row_heights[row];
|
|
for (col = 0; col < priv->max_x + 1; col++)
|
|
priv->req_width += priv->col_widths[col];
|
|
|
|
*height = priv->req_height;
|
|
*width = priv->req_width;
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_size_allocate (NmtNewtWidget *widget,
|
|
int x,
|
|
int y,
|
|
int width,
|
|
int height)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (widget);
|
|
NmtNewtGridChild *children = (NmtNewtGridChild *)priv->children->data, *child;
|
|
int i, row, col;
|
|
int child_x, child_y, child_width, child_height;
|
|
int extra, extra_all, extra_some;
|
|
|
|
extra = width - priv->req_width;
|
|
if (extra > 0 && priv->n_expand_cols) {
|
|
extra_all = extra / priv->n_expand_cols;
|
|
extra_some = extra % priv->n_expand_cols;
|
|
|
|
for (col = 0; col < priv->max_x + 1; col++) {
|
|
if (!priv->expand_cols[col])
|
|
continue;
|
|
priv->col_widths[col] += extra_all;
|
|
if (extra_some) {
|
|
priv->col_widths[col]++;
|
|
extra_some--;
|
|
}
|
|
}
|
|
}
|
|
|
|
extra = height - priv->req_height;
|
|
if (extra > 0 && priv->n_expand_rows) {
|
|
extra_all = extra / priv->n_expand_rows;
|
|
extra_some = extra % priv->n_expand_rows;
|
|
|
|
for (row = 0; row < priv->max_y + 1; row++) {
|
|
if (!priv->expand_rows[row])
|
|
continue;
|
|
priv->row_heights[row] += extra_all;
|
|
if (extra_some) {
|
|
priv->row_heights[row]++;
|
|
extra_some--;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < priv->children->len; i++) {
|
|
child = &children[i];
|
|
if (!nmt_newt_widget_get_visible (child->widget))
|
|
continue;
|
|
|
|
child_x = x;
|
|
for (col = 0; col < child->x; col++)
|
|
child_x += priv->col_widths[col];
|
|
|
|
if ((child->flags & NMT_NEWT_GRID_FILL_X) == NMT_NEWT_GRID_FILL_X) {
|
|
child_width = priv->col_widths[child->x];
|
|
} else {
|
|
child_width = child->req_width;
|
|
if (child->flags & NMT_NEWT_GRID_ANCHOR_RIGHT)
|
|
child_x += priv->col_widths[child->x] - child->req_width;
|
|
else if (!(child->flags & NMT_NEWT_GRID_ANCHOR_LEFT))
|
|
child_x += (priv->col_widths[child->x] - child->req_width) / 2;
|
|
}
|
|
|
|
child_y = y;
|
|
for (row = 0; row < child->y; row++)
|
|
child_y += priv->row_heights[row];
|
|
|
|
if ((child->flags & NMT_NEWT_GRID_FILL_Y) == NMT_NEWT_GRID_FILL_Y) {
|
|
child_height = priv->row_heights[child->y];
|
|
} else {
|
|
child_height = child->req_height;
|
|
if (child->flags & NMT_NEWT_GRID_ANCHOR_BOTTOM)
|
|
child_y += priv->row_heights[child->y] - child->req_height;
|
|
else if (!(child->flags & NMT_NEWT_GRID_ANCHOR_TOP))
|
|
child_y += (priv->row_heights[child->y] - child->req_height) / 2;
|
|
}
|
|
|
|
nmt_newt_widget_size_allocate (child->widget,
|
|
child_x, child_y,
|
|
child_width, child_height);
|
|
}
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_find_size (NmtNewtGrid *grid)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
NmtNewtGridChild *children = (NmtNewtGridChild *)priv->children->data;
|
|
int i;
|
|
|
|
priv->max_x = priv->max_y = 0;
|
|
for (i = 0; i < priv->children->len; i++) {
|
|
if (children[i].x > priv->max_x)
|
|
priv->max_x = children[i].x;
|
|
if (children[i].y > priv->max_y)
|
|
priv->max_y = children[i].y;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nmt_newt_grid_add:
|
|
* @grid: an #NmtNewtGrid
|
|
* @widget: the widget to add
|
|
* @x: x coordinate
|
|
* @y: y coordinate
|
|
*
|
|
* Adds @widget to @grid at @x, @y. See the discussion above for more
|
|
* details of exactly how this works.
|
|
*/
|
|
void
|
|
nmt_newt_grid_add (NmtNewtGrid *grid,
|
|
NmtNewtWidget *widget,
|
|
int x,
|
|
int y)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
NmtNewtGridChild child;
|
|
|
|
NMT_NEWT_CONTAINER_CLASS (nmt_newt_grid_parent_class)->add (NMT_NEWT_CONTAINER (grid), widget);
|
|
|
|
memset (&child, 0, sizeof (child));
|
|
child.widget = widget;
|
|
child.x = x;
|
|
child.y = y;
|
|
child.flags = NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y;
|
|
g_array_append_val (priv->children, child);
|
|
|
|
if (x > priv->max_x)
|
|
priv->max_x = x;
|
|
if (y > priv->max_y)
|
|
priv->max_y = y;
|
|
}
|
|
|
|
static int
|
|
find_child (NmtNewtGrid *grid,
|
|
NmtNewtWidget *widget)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
NmtNewtGridChild *children = (NmtNewtGridChild *)priv->children->data;
|
|
int i;
|
|
|
|
for (i = 0; i < priv->children->len; i++) {
|
|
if (children[i].widget == widget)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_remove (NmtNewtContainer *container,
|
|
NmtNewtWidget *widget)
|
|
{
|
|
NmtNewtGrid *grid = NMT_NEWT_GRID (container);
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
int i;
|
|
|
|
i = find_child (grid, widget);
|
|
if (i != -1) {
|
|
g_array_remove_index (priv->children, i);
|
|
nmt_newt_grid_find_size (grid);
|
|
}
|
|
|
|
NMT_NEWT_CONTAINER_CLASS (nmt_newt_grid_parent_class)->remove (container, widget);
|
|
}
|
|
|
|
/**
|
|
* nmt_newt_grid_move:
|
|
* @grid: an #NmtNewtGrid
|
|
* @widget: a child of @grid
|
|
* @x: x coordinate
|
|
* @y: y coordinate
|
|
*
|
|
* Moves @widget to the given new coordinates.
|
|
*/
|
|
void
|
|
nmt_newt_grid_move (NmtNewtGrid *grid,
|
|
NmtNewtWidget *widget,
|
|
int x,
|
|
int y)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
NmtNewtGridChild *children = (NmtNewtGridChild *)priv->children->data;
|
|
int i;
|
|
|
|
i = find_child (grid, widget);
|
|
if (i != -1 && (children[i].x != x || children[i].y != y)) {
|
|
children[i].x = x;
|
|
children[i].y = y;
|
|
nmt_newt_grid_find_size (grid);
|
|
nmt_newt_widget_needs_rebuild (NMT_NEWT_WIDGET (grid));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NmtNewtGridFlags:
|
|
* @NMT_NEWT_GRID_EXPAND_X: The widget's cell should expand
|
|
* horizontally if the grid as a whole is given more width than
|
|
* it requested.
|
|
* @NMT_NEWT_GRID_EXPAND_Y: The widget's cell should expand
|
|
* vertically if the grid as a whole is given more height than
|
|
* it requested.
|
|
* @NMT_NEWT_GRID_ANCHOR_LEFT: If the widget's cell is wider than
|
|
* the widget requested, the widget should be anchored to the
|
|
* left of its cell rather than being centered.
|
|
* @NMT_NEWT_GRID_ANCHOR_RIGHT: If the widget's cell is wider than
|
|
* the widget requested, the widget should be anchored to the
|
|
* right of its cell rather than being centered.
|
|
* @NMT_NEWT_GRID_FILL_X: If the widget's cell is wider than
|
|
* the widget requested, the widget should be allocated the
|
|
* full width of the cell; this is equivalent to specifying
|
|
* both %NMT_NEWT_GRID_ANCHOR_LEFT and %NMT_NEWT_GRID_ANCHOR_RIGHT.
|
|
* @NMT_NEWT_GRID_ANCHOR_TOP: If the widget's cell is taller than
|
|
* the widget requested, the widget should be anchored to the
|
|
* top of its cell rather than being centered.
|
|
* @NMT_NEWT_GRID_ANCHOR_BOTTOM: If the widget's cell is taller than
|
|
* the widget requested, the widget should be anchored to the
|
|
* bottom of its cell rather than being centered.
|
|
* @NMT_NEWT_GRID_FILL_Y: If the widget's cell is taller than
|
|
* the widget requested, the widget should be allocated the
|
|
* full height of the cell; this is equivalent to specifying
|
|
* both %NMT_NEWT_GRID_ANCHOR_TOP and %NMT_NEWT_GRID_ANCHOR_BOTTOM.
|
|
*
|
|
* Flags describing how a widget is placed within its grid cell.
|
|
*/
|
|
|
|
/**
|
|
* nmt_newt_grid_set_flags:
|
|
* @grid: an #NmtNewtGrid
|
|
* @widget: a child of @grid
|
|
* @flags: #NmtNewtGridFlags for @widget
|
|
*
|
|
* Sets the #NmtNewtGridFlags on @widget
|
|
*/
|
|
void
|
|
nmt_newt_grid_set_flags (NmtNewtGrid *grid,
|
|
NmtNewtWidget *widget,
|
|
NmtNewtGridFlags flags)
|
|
{
|
|
NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE (grid);
|
|
NmtNewtGridChild *children = (NmtNewtGridChild *)priv->children->data;
|
|
int i;
|
|
|
|
i = find_child (grid, widget);
|
|
if (i != -1)
|
|
children[i].flags = flags;
|
|
}
|
|
|
|
static void
|
|
nmt_newt_grid_class_init (NmtNewtGridClass *grid_class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (grid_class);
|
|
NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS (grid_class);
|
|
NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS (grid_class);
|
|
|
|
g_type_class_add_private (grid_class, sizeof (NmtNewtGridPrivate));
|
|
|
|
/* virtual methods */
|
|
object_class->finalize = nmt_newt_grid_finalize;
|
|
|
|
widget_class->get_components = nmt_newt_grid_get_components;
|
|
widget_class->size_request = nmt_newt_grid_size_request;
|
|
widget_class->size_allocate = nmt_newt_grid_size_allocate;
|
|
|
|
container_class->remove = nmt_newt_grid_remove;
|
|
}
|