#ifndef lint
static char	sccsid[] = "@(#)group.c	2.23 91/08/14 Copyright 1991 Sun Microsystems";
#endif

/*
 * Routines for relative layout support through groups
 */

#include	"group_impl.h"

static	void	layout_none();
static	void	layout_row();
static	void	layout_col();
static	void	layout_rowcol();

static	void		group_set_xy();
static	void		group_set_members();
static	Rect		*get_rect_for_group();
static	Group_public	*get_parent_group();
static	void		get_compass_point();
static	void		get_rowcol_info();
static	int		get_value_x();
static	void		place_cell();

/*
 * Package initialization, called on xv_create
 */
/*ARGSUSED*/
Pkg_private int
group_init(owner, group_public, avlist)
	Xv_opaque	owner;
	Group_public	*group_public;
	Attr_avlist	avlist;
{
	Group_private	*group_private = xv_alloc(Group_private);

	if (!group_private)
		return XV_ERROR;

	group_public->private_data = (Xv_opaque)group_private;
	group_private->public_self = (Xv_opaque)group_public;

	/*
	 * Initialize the defaults for certain values
	 */
	group_private->group_type = GROUP_NONE;
	group_private->hspacing = 10;
	group_private->vspacing = 10;
	group_private->row_alignment = GROUP_TOP_EDGES;
	group_private->col_alignment = GROUP_LEFT_EDGES;
	group_private->reference_point = GROUP_NORTHWEST;
	group_private->hoffset = 10;
	group_private->voffset = 10;
	group_private->flags |= (SHOWING|LAYOUT);

	return XV_OK;
}

/*
 * Package set function, called on xv_set
 */
Pkg_private Xv_opaque
group_set(group_public, avlist)
	Group_public	*group_public;
	Attr_avlist	avlist;
{
	int		i;
	int		x;
	int		x_changed = FALSE;
	int		y;
	int		y_changed = FALSE;
	int		replaced;
	int		need_layout = FALSE;
	int		initial_position = FALSE;
	Xv_opaque	old, new;
	Attr_attribute	*attrs;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	for (attrs = avlist; *attrs; attrs = attr_next(attrs)) {
		switch ((int) attrs[0]) {
		case XV_X:
			x = (int)attrs[1];
			x_changed = TRUE;
			if (!(group_private->flags & CREATED)) {
				group_private->initial_x = x;
				group_private->group_rect.r_left = x;
			}
			ATTR_CONSUME(attrs[0]);
			break;

		case XV_Y:
			y = (int)attrs[1];
			y_changed = TRUE;
			if (!(group_private->flags & CREATED)) {
				group_private->initial_y = y;
				group_private->group_rect.r_top = y;
			}
			ATTR_CONSUME(attrs[0]);
			break;

		case PANEL_VALUE_X:
			x = group_private->group_rect.r_left + 
			    ((int)attrs[1] - group_private->value_rect.r_left);
			x_changed = TRUE;
			ATTR_CONSUME(attrs[0]);
			break;
			
		case XV_SHOW:
			for (i = 0; group_private->members[i]; i++) {
				xv_set(group_private->members[i],
					XV_SHOW, attrs[1], NULL);
			}
			if ((int)attrs[1])
				group_private->flags |= SHOWING;
			else
				group_private->flags &= ~SHOWING;
			break;

		case PANEL_INACTIVE:
			for (i = 0; group_private->members[i]; i++) {
				xv_set(group_private->members[i],
					PANEL_INACTIVE, (int)attrs[1], NULL);
			}
			if ((int)attrs[1])
				group_private->flags |= INACTIVE;
			else
				group_private->flags &= ~INACTIVE;
			break;

		case GROUP_TYPE:
			need_layout = TRUE;
			group_private->group_type = (GROUP_TYPES)attrs[1];
			break;

		case GROUP_ROWS:
			need_layout = TRUE;
			if ((group_private->rows = (int)attrs[1]) > 0) {
				group_private->flags |= ROWFIRST;
				group_private->flags &= ~COLFIRST;
			}
			break;

		case GROUP_COLUMNS:
			need_layout = TRUE;
			if ((group_private->cols = (int)attrs[1]) > 0) {
				group_private->flags |= COLFIRST;
				group_private->flags &= ~ROWFIRST;
			}
			break;

		case GROUP_HORIZONTAL_SPACING:
			need_layout = TRUE;
			group_private->hspacing = (int)attrs[1];
			break;

		case GROUP_VERTICAL_SPACING:
			need_layout = TRUE;
			group_private->vspacing = (int)attrs[1];
			break;

		case GROUP_ROW_ALIGNMENT:
			need_layout = TRUE;
			group_private->row_alignment = (GROUP_ROW_ALIGNMENTS)attrs[1];
			break;

		case GROUP_COLUMN_ALIGNMENT:
			need_layout = TRUE;
			group_private->col_alignment = (GROUP_COLUMN_ALIGNMENTS)attrs[1];
			break;

		case GROUP_REPLACE_MEMBER:
			old = (Xv_opaque)attrs[1];
			new = (Xv_opaque)attrs[2];

			replaced = FALSE;

			for (i = 0; group_private->members[i]; i++) {
				if (group_private->members[i] == old) {
					replaced = TRUE;
					need_layout = TRUE;
					group_private->members[i] = new;
				}
			}
			
			if (!replaced)
				xv_error((Group)group_public,
				    ERROR_STRING, "member not found, not replaced",
				    ERROR_PKG,    GROUP,
				    NULL);

			break;

		case GROUP_MEMBERS:
			group_set_members(group_public, &attrs[1]);
			need_layout = TRUE;
			break;

		case GROUP_MEMBERS_PTR:
			group_set_members(group_public, (Xv_opaque *)attrs[1]);
			need_layout = TRUE;
			break;

		case GROUP_LAYOUT:
			if ((int)attrs[1]) {
				need_layout = TRUE;
				group_private->flags |= LAYOUT;
			} else
				group_private->flags &= ~LAYOUT;
			break;

		case GROUP_ANCHOR_OBJ:
			need_layout = TRUE;
			group_private->anchor_obj = (Xv_opaque)attrs[1];
			break;

		case GROUP_ANCHOR_POINT:
			need_layout = TRUE;
			group_private->anchor_point =
				(GROUP_COMPASS_POINTS)attrs[1];
			break;

		case GROUP_REFERENCE_POINT:
			need_layout = TRUE;
			group_private->reference_point =
				(GROUP_COMPASS_POINTS)attrs[1];
			break;

		case GROUP_HORIZONTAL_OFFSET:
			need_layout = TRUE;
			group_private->hoffset = (int)attrs[1];
			break;

		case GROUP_VERTICAL_OFFSET:
			need_layout = TRUE;
			group_private->voffset = (int)attrs[1];
			break;

		case XV_END_CREATE:
			need_layout = TRUE;
			initial_position = TRUE;
			group_private->flags |= CREATED;
			break;

		default:
			xv_check_bad_attr(GROUP, attrs[0]);
			break;
		}
	}

	/*
	 * Don't go any further until object has been created
	 */
	if (!(group_private->flags & CREATED))
		return (Xv_opaque)XV_OK;

	/*
	 * If something changed that would change the layout of
	 * the group, percolate!
	 */
	if (need_layout && (group_private->flags & LAYOUT))
		group_layout((Group)group_public);

	/*
	 * Check to see if x or y were set, if so we need to move the group.
	 */
	if (x_changed || y_changed) {
		if (!x_changed)
			x = group_private->group_rect.r_left;
		if (!y_changed)
			y = group_private->group_rect.r_top;

		group_set_xy(group_public, x, y);
	}
	
	/*
	 * If this is the first time this group has been positioned,
	 * make sure it is placed correctly if it is not anchored.  If
	 * it is anchored it was placed correctly earlier.
	 */
	if (initial_position && !group_private->anchor_obj) {
		group_set_xy(group_public,
			     group_private->initial_x,
			     group_private->initial_y);
	}

	return (Xv_opaque)XV_OK;
}

/*
 * Package get function, called on xv_get
 */
/*ARGSUSED*/
Pkg_private Xv_opaque
group_get(group_public, status, attr, args)
	Group_public	*group_public;
	int		*status;
	Attr_attribute	attr;
	Attr_avlist	args;
{
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	switch ((int) attr) {
		case XV_X:
			return (Xv_opaque)group_private->group_rect.r_left;

		case PANEL_VALUE_X:
			return (Xv_opaque)group_private->value_rect.r_left;

		case XV_Y:
			return (Xv_opaque)group_private->group_rect.r_top;

		case PANEL_VALUE_Y:
			return (Xv_opaque)group_private->value_rect.r_top;

		case XV_WIDTH:
			return (Xv_opaque)group_private->group_rect.r_width;

		case XV_HEIGHT:
			return (Xv_opaque)group_private->group_rect.r_height;

		case XV_RECT:
			return (Xv_opaque)&(group_private->group_rect);

		case XV_SHOW:
			return (Xv_opaque)(group_private->flags & SHOWING);

		case PANEL_INACTIVE:
			return (Xv_opaque)(group_private->flags & INACTIVE);

		case GROUP_TYPE:
			return (Xv_opaque)group_private->group_type;

		case GROUP_ROWS:
			return (Xv_opaque)group_private->rows;

		case GROUP_COLUMNS:
			return (Xv_opaque)group_private->cols;

		case GROUP_HORIZONTAL_SPACING:
			return (Xv_opaque)group_private->hspacing;

		case GROUP_VERTICAL_SPACING:
			return (Xv_opaque)group_private->vspacing;

		case GROUP_ROW_ALIGNMENT:
			return (Xv_opaque)group_private->row_alignment;

		case GROUP_COLUMN_ALIGNMENT:
			return (Xv_opaque)group_private->col_alignment;

		case GROUP_MEMBERS:
			return (Xv_opaque)group_private->members;

		case GROUP_LAYOUT:
			return (Xv_opaque)(group_private->flags & LAYOUT);

		case GROUP_ANCHOR_OBJ:
			return (Xv_opaque)group_private->anchor_obj;

		case GROUP_ANCHOR_POINT:
			return (Xv_opaque)group_private->anchor_point;

		case GROUP_REFERENCE_POINT:
			return (Xv_opaque)group_private->reference_point;

		case GROUP_HORIZONTAL_OFFSET:
			return (Xv_opaque)group_private->hoffset;

		case GROUP_VERTICAL_OFFSET:
			return (Xv_opaque)group_private->voffset;

		case GROUP_PARENT:
			return (Xv_opaque)get_parent_group(group_public);

		default:
			if (xv_check_bad_attr(GROUP, attr) == XV_ERROR)
				*status = XV_ERROR;
			break;
	}

	return (Xv_opaque)XV_OK;
}

/*
 * Package destroy function, called on xv_destroy
 */
Pkg_private int
group_destroy(group_public, status)
	Group_public	*group_public;
	Destroy_status	status;
{
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	if (status == DESTROY_CLEANUP) {
		/*
		 * Mark all members free of this group, then free up
		 * space allocated for members list.
		 */
		if (group_private->members) {
			/*
			 * MOOSE - we could only do this if we could tell
			 * whether the XView handle is still valid...
			 *
			for (i = 0; group_private->members[i]; i++)
				xv_set(group_private->members[i],
					XV_KEY_DATA, GROUP_PARENT, NULL, NULL);
			 */

			xv_free(group_private->members);
		}

		xv_free(group_private);
	}

	return XV_OK;
}

/*
 * Layout a group according to it's constraints
 */
void
group_layout(group_public)
	Group	group_public;
{
	Rect		*r;
	Group_public	*parent;
	Group_private	*group_private;

	if (!group_public)
		return;

	group_private = GROUP_PRIVATE(group_public);

	if (!group_private || !group_private->members)
		return;

	switch (group_private->group_type) {
	case GROUP_NONE:
		layout_none(group_public);
		break;
	case GROUP_ROW:
		layout_row(group_public);
		break;
	case GROUP_COLUMN:
		layout_col(group_public);
		break;
	case GROUP_ROWCOLUMN:
		layout_rowcol(group_public);
		break;
	default:
		break;
	}

	/*
	 * Update x/y/width/height for this group, values may have changed
	 */
	r = get_rect_for_group(group_public);
	group_private->group_rect.r_left = r->r_left;
	group_private->group_rect.r_top = r->r_top;
	group_private->group_rect.r_width = r->r_width;
	group_private->group_rect.r_height = r->r_height;

	if (group_private->members) {
		group_private->value_rect.r_left = 
			(int)xv_get(group_private->members[0], PANEL_VALUE_X);
		group_private->value_rect.r_top = 
			(int)xv_get(group_private->members[0], PANEL_VALUE_Y);
	} else {
		group_private->value_rect.r_left = group_private->group_rect.r_left;
		group_private->value_rect.r_top = group_private->group_rect.r_top;
	}

	/*
	 * If this group belongs to a parent group, we need to recurse
	 * upwards and layout the parent group now.
	 *
	 * Otherwise, if this group is anchored, place it relative to anchor
	 */
	if (parent = get_parent_group(group_public))
		group_layout((Group)parent);
	else if (group_private->anchor_obj)
		group_anchor((Group)group_public);
}

/*
 * Allocate space for an array of Xv_opaques to store new members.
 */
static void
group_set_members(group_public, members)
	Group_public	*group_public;
	Xv_opaque	*members;
{
	int		i;
	int		num;
	Xv_opaque	*xvptr = members;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	/*
	 * Free up space from old members list
	 */
	if (group_private->members)
		xv_free(group_private->members);

	if (!members) {
		group_private->members = NULL;
		return;
	}

	/*
	 * Count number of new members
	 */
	for (num = 0; *xvptr++; num++)
		;

	/*
	 * Allocate space for new members list.  Should use xv_alloc_n
	 * here, but it was incorrect in V2 and we want this package
	 * to stay portable back to V2.
	 */
	group_private->members = (Xv_opaque *)calloc(num+1, sizeof(Xv_opaque));

	/*
	 * Walk through list and store members, mark each
	 * new member in this group.
	 */
	for (i = 0; i < num; i++) {
		group_private->members[i] = members[i];
		xv_set(group_private->members[i],
			XV_KEY_DATA, GROUP_PARENT, group_public, NULL);
	}
}

/*
 * Move a group to a new (absolute) x/y position
 */
static void
group_set_xy(group_public, x, y)
	Group_public	*group_public;
	int		x;
	int		y;
{
	int		i;
	Xv_opaque	cur;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	if (!group_private->members)
		return;

	for (i = 0; group_private->members[i]; i++) {
		cur = group_private->members[i];
		xv_set(cur, XV_X, x + (xv_get(cur, XV_X) - group_private->group_rect.r_left),
			    XV_Y, y + (xv_get(cur, XV_Y) - group_private->group_rect.r_top),
			    NULL);
	}

	group_private->group_rect.r_left = x;
	group_private->group_rect.r_top = y;

	if (group_private->members) {
		group_private->value_rect.r_left =
			xv_get(group_private->members[0], PANEL_VALUE_X);
		group_private->value_rect.r_top =
			xv_get(group_private->members[0], PANEL_VALUE_X);
	} else {
		group_private->value_rect.r_left = x;
		group_private->value_rect.r_top = y;
	}
}

/*
 * Anchor a group
 */
void
group_anchor(group_public)
	Group	group_public;
{
	int		new_x;
	int		new_y;
	int		anchor_x;
	int		anchor_y;
	int		ref_x;
	int		ref_y;
	Group_private	*group_private;

	if (!group_public)
		return;

	group_private = GROUP_PRIVATE(group_public);

	if (!group_private || !group_private->anchor_obj)
		return;

	get_compass_point(group_private->anchor_obj, group_private->anchor_point,
			  &anchor_x, &anchor_y);
	get_compass_point(group_public, group_private->reference_point,
			  &ref_x, &ref_y);

	if (xv_get(group_private->anchor_obj, XV_OWNER) !=
	    xv_get(group_public, XV_OWNER)) {
		anchor_x -= (int)xv_get(group_private->anchor_obj, XV_X);
		anchor_y -= (int)xv_get(group_private->anchor_obj, XV_Y);
	}

	new_x = anchor_x + (group_private->group_rect.r_left - ref_x) +
		group_private->hoffset;
	new_y = anchor_y + (group_private->group_rect.r_top - ref_y) +
		group_private->voffset;

	group_set_xy(group_public, new_x, new_y);
}

/*
 * Layout an as-is group.  Usually means do nothing, check to see
 * if members are anchored first though.
 */
static void
layout_none(group_public)
	Group_public	*group_public;
{
	int		i;
	Xv_opaque	cur;
	Xv_opaque	obj;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	for (i = 0; group_private->members[i]; i++) {
		cur = group_private->members[i];

		/*
		 * Check to see if this member is a group, if so
		 * lay it out so it will be anchored correctly.
		 */
		XV_OBJECT_TO_STANDARD(cur, "GROUP", obj);

		if (obj && (((Xv_base *) obj)->pkg == GROUP)) {
			group_anchor((Group)cur);
		}
	}

}

/*
 * Layout a row group
 */
static void
layout_row(group_public)
	Group_public	*group_public;
{
	int		i;
	int		base_y;
	int		new_y;
	Xv_opaque	cur;
	Xv_opaque	prev;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	switch (group_private->row_alignment) {
	case GROUP_TOP_EDGES:
		base_y = xv_get(group_private->members[0], XV_Y);
		break;

	case GROUP_HORIZONTAL_CENTERS:
		base_y = xv_get(group_private->members[0], XV_Y) + 
			 xv_get(group_private->members[0], XV_HEIGHT)/2;
		break;

	case GROUP_BOTTOM_EDGES:
		base_y = xv_get(group_private->members[0], XV_Y) + 
			 xv_get(group_private->members[0], XV_HEIGHT);
		break;
	}

	for (i = 1; group_private->members[i]; i++) {
		cur = group_private->members[i];
		prev = group_private->members[i-1];

		switch (group_private->row_alignment) {
		case GROUP_TOP_EDGES:
			new_y = base_y;
			break;

		case GROUP_HORIZONTAL_CENTERS:
			new_y = base_y - xv_get(cur, XV_HEIGHT)/2;
			break;

		case GROUP_BOTTOM_EDGES:
			new_y = base_y - xv_get(cur, XV_HEIGHT);
			break;
		}

		xv_set(cur, XV_X, xv_get(prev, XV_X) +
				  xv_get(prev, XV_WIDTH) +
				  group_private->hspacing,
			    XV_Y, new_y,
			    NULL);
	}
}

/*
 * Layout a column group
 */
static void
layout_col(group_public)
	Group_public	*group_public;
{
	int		i;
	int		new_x;
	int		base_x;
	Xv_opaque	cur;
	Xv_opaque	prev;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	switch (group_private->col_alignment) {
	case GROUP_LEFT_EDGES:
		base_x = xv_get(group_private->members[0], XV_X);
		break;

	case GROUP_LABELS:
		base_x = xv_get(group_private->members[0], PANEL_VALUE_X);
		break;

	case GROUP_VERTICAL_CENTERS:
		base_x = xv_get(group_private->members[0], XV_X) + 
			 xv_get(group_private->members[0], XV_WIDTH)/2;
		break;

	case GROUP_RIGHT_EDGES:
		base_x = xv_get(group_private->members[0], XV_X) + 
			 xv_get(group_private->members[0], XV_WIDTH);
		break;
	}

	for (i = 1; group_private->members[i]; i++) {
		cur = group_private->members[i];
		prev = group_private->members[i-1];

		switch (group_private->col_alignment) {
		case GROUP_LEFT_EDGES:
		case GROUP_LABELS:
			new_x = base_x;
			break;

		case GROUP_VERTICAL_CENTERS:
			new_x = base_x - xv_get(cur, XV_WIDTH)/2;
			break;

		case GROUP_RIGHT_EDGES:
			new_x = base_x - xv_get(cur, XV_WIDTH);
			break;
		}

		if (group_private->col_alignment == GROUP_LABELS) {
			xv_set(cur, PANEL_VALUE_X, new_x,
				    XV_Y, xv_get(prev, XV_Y) +
					  xv_get(prev, XV_HEIGHT) +
					  group_private->vspacing,
				    NULL);
		} else {
			xv_set(cur, XV_X, new_x,
				    XV_Y, xv_get(prev, XV_Y) +
					  xv_get(prev, XV_HEIGHT) +
					  group_private->vspacing,
				    NULL);
		}
	}
}

/*
 * Layout a row/column group
 */
static void
layout_rowcol(group_public)
	Group_public	*group_public;
{
	int		i;
	int		vx = 0;
	int		current_row = 0;
	int		current_col = 0;
	int		cell_width;
	int		cell_height;
	Group_private	*group_private = GROUP_PRIVATE(group_public);
	Xv_opaque	*members = group_private->members;

	get_rowcol_info(group_public, &cell_width, &cell_height);

	if (group_private->col_alignment == GROUP_LABELS)
		vx = get_value_x(group_public, 0);

	/*
	 * Walk through the list, place each object in it's "cell". 
	 */
	for (i = 0; members[i]; i++) {
		place_cell(group_public, i, cell_width, cell_height,
			   vx, current_row, current_col);

		if (group_private->flags & ROWFIRST) {
			if (++current_col >= group_private->cols) {
				current_row++;
				current_col = 0;
			}
			if ((group_private->col_alignment == GROUP_LABELS) &&
			    members[i+1])
				vx = get_value_x(group_public, current_col);
		} else {
			if (++current_row >= group_private->rows) {
				current_col++;
				current_row = 0;
				if ((group_private->col_alignment == GROUP_LABELS) &&
				    members[i+1])
					vx = get_value_x(group_public, current_col);
			}
		}
	}
}

static void
get_rowcol_info(group_public, cell_width, cell_height)
	Group_public	*group_public;
	int		*cell_width;
	int		*cell_height;
{
	int		i;
	int		lw;
	int		vw;
	int		max_lw = -1;
	int		max_vw = -1;
	int		count;
	Group_private	*group_private = GROUP_PRIVATE(group_public);
	Xv_opaque	cur;

	/*
	 * Calculate rows and column based on number of members
	 * and current fill order.
	 */
	if ((group_private->rows == 0) && (group_private->cols == 0))
		group_private->rows = 1;

	for (count = 0; group_private->members[count]; count++)
		;

	if (group_private->flags & ROWFIRST) {
		group_private->cols = count / group_private->rows;

		if (count % group_private->rows)
			group_private->cols++;
	} else {
		group_private->rows = count  / group_private->cols;

		if (count % group_private->cols)
			group_private->rows++;
	}

	/*
	 * Walk through the list, find maximum cell size.  Row/Col
	 * groups aligned on labels are special.  The widest item may
	 * not determine the cell size.  It is determined by the
	 * widest label plus the widest value field, phew.
	 */
	*cell_width = -1;
	*cell_height = -1;

	if ((group_private->group_type == GROUP_ROWCOLUMN) &&
	    (group_private->col_alignment == GROUP_LABELS)) {
		for (i = 0; group_private->members[i]; i++) {
			cur = group_private->members[i];

			lw = xv_get(cur, PANEL_VALUE_X) - xv_get(cur, XV_X);
			vw = xv_get(cur, XV_WIDTH) - lw;

			if (lw > max_lw)
				max_lw = lw;
			if (vw > max_vw)
				max_vw = vw;
			if ((int)xv_get(cur, XV_HEIGHT) > *cell_height)
				*cell_height = xv_get(cur, XV_HEIGHT);
		}

		*cell_width = max_lw + max_vw;
	} else {
		for (i = 0; group_private->members[i]; i++) {
			cur = group_private->members[i];

			if ((int)xv_get(cur, XV_WIDTH) > *cell_width)
				*cell_width = xv_get(cur, XV_WIDTH);
			if ((int)xv_get(cur, XV_HEIGHT) > *cell_height)
				*cell_height = xv_get(cur, XV_HEIGHT);
		}
	}
}

static int
get_value_x(group_public, col)
	Group_public	*group_public;
	int		col;
{
	int		i;
	int		j;
	int		tmp;
	int		start;
	int		incr;
	int		vx = -1;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	/*
	 * Walk through the list, find maximum cell size
	 */
	if (group_private->flags & ROWFIRST) {
		start = col;
		incr = group_private->cols;
	} else {
		start = col * group_private->rows;
		incr = 1;
	}

	for (j = 0, i = start;
	     group_private->members[i] && j < group_private->rows;
	     i += incr, j++) {
		tmp = xv_get(group_private->members[i], PANEL_VALUE_X) -
		      xv_get(group_private->members[i], XV_X);

		if (tmp > vx)
			vx = tmp;
	}

	return vx;
}

/*
 * Place an object correctly inside a cell in a row/col group
 */
static void
place_cell(group_public, i, cell_width, cell_height, vx, row, col)
	Group_public	*group_public;
	int		i;
	int		cell_width;
	int		cell_height;
	int		vx;
	int		row;
	int		col;
{
	int		x;
	int		y;
	int		cell_x;
	int		cell_y;
	Group_private	*group_private = GROUP_PRIVATE(group_public);
	Xv_opaque	cur = group_private->members[i];

	/*
	 * Calculate the upper left corner for this cell
	 */
	cell_x = group_private->group_rect.r_left +
		 col * (cell_width + group_private->hspacing);
	cell_y = group_private->group_rect.r_top +
		 row * (cell_height + group_private->vspacing);

	switch (group_private->col_alignment) {
	case GROUP_LEFT_EDGES:
		x = cell_x;
		break;
	case GROUP_LABELS:
		x = xv_get(cur, XV_X) +
		    ((cell_x + vx) - xv_get(cur, PANEL_VALUE_X));
		break;
	case GROUP_VERTICAL_CENTERS:
		x = (cell_x + cell_width/2) - xv_get(cur, XV_WIDTH)/2;
		break;
	case GROUP_RIGHT_EDGES:
		x = (cell_x + cell_width) - xv_get(cur, XV_WIDTH);
		break;
	}

	switch (group_private->row_alignment) {
	case GROUP_TOP_EDGES:
		y = cell_y;
		break;
	case GROUP_HORIZONTAL_CENTERS:
		y = (cell_y + cell_height/2) - xv_get(cur, XV_HEIGHT)/2;
		break;
	case GROUP_BOTTOM_EDGES:
		y = (cell_y + cell_height) - xv_get(cur, XV_HEIGHT);
		break;
	}

	xv_set(cur, XV_X, x, XV_Y, y, NULL);
}

/*
 * Return the bounding rectangle for a group
 */
static Rect *
get_rect_for_group(group_public)
	Group_public	*group_public;
{
	int		i;
	int		cell_width;
	int		cell_height;
	static Rect	bbox;
	Rect		r;
	Rect		*r1;
	Group_private	*group_private = GROUP_PRIVATE(group_public);

	r = rect_null;

	if (!group_private->members) {
		bbox = r;
		return &bbox;
	}

	if (group_private->group_type == GROUP_ROWCOLUMN) {
		get_rowcol_info(group_public, &cell_width, &cell_height);
		r.r_left = group_private->group_rect.r_left;
		r.r_top = group_private->group_rect.r_top;
		r.r_width = (group_private->cols * cell_width) +
			    ((group_private->cols-1) * group_private->hspacing);
		r.r_height = (group_private->rows * cell_height) +
			    ((group_private->rows-1) * group_private->vspacing);
	} else {
		for (i = 0; group_private->members[i]; i++) {
			if (r1 = (Rect *)xv_get(group_private->members[i], XV_RECT))
				r = rect_bounding(&r, r1);
		}
	}

	bbox = r;
	return &bbox;
}

/*
 * Return a pointer to a parent group, NULL if none
 */
static Group_public *
get_parent_group(group_public)
	Group_public	*group_public;
{
	return (Group_public *)xv_get((Group)group_public,
				      XV_KEY_DATA, GROUP_PARENT);
}

/*
 * Calculate the x/y locatiions for a compass point on an object
 */
static void
get_compass_point(handle, point, x, y)
	Xv_opaque		handle;
	GROUP_COMPASS_POINTS	point;
	int			*x;
	int			*y;
{
	switch (point) {
		case GROUP_NORTHWEST:
		case GROUP_WEST:
		case GROUP_SOUTHWEST:
			*x = xv_get(handle, XV_X);
			break;
		case GROUP_NORTH:
		case GROUP_CENTER:
		case GROUP_SOUTH:
			*x = xv_get(handle, XV_X) + xv_get(handle, XV_WIDTH)/2;
			break;
		case GROUP_NORTHEAST:
		case GROUP_EAST:
		case GROUP_SOUTHEAST:
			*x = xv_get(handle, XV_X) + xv_get(handle, XV_WIDTH);
			break;
	}

	switch (point) {
		case GROUP_NORTHWEST:
		case GROUP_NORTH:
		case GROUP_NORTHEAST:
			*y = xv_get(handle, XV_Y);
			break;
		case GROUP_WEST:
		case GROUP_CENTER:
		case GROUP_EAST:
			*y = xv_get(handle, XV_Y) + xv_get(handle, XV_HEIGHT)/2;
			break;
		case GROUP_SOUTHWEST:
		case GROUP_SOUTH:
		case GROUP_SOUTHEAST:
			*y = xv_get(handle, XV_Y) + xv_get(handle, XV_HEIGHT);
			break;
	}
}
