/* $Id: Rowcolumn.c,v 1.1 1997/05/10 09:21:33 archer Exp $
 *	ROWCOLUMN.C - General purpose geometry managing object
 *
 * The row-column lists a number of widgets in a single column, centering each
 * object, unless that object is X-locked or is used as label for the next.
 */
#include <Widget/FormP.h>


/**
 ** Internal: ROW_COLUMN_RECURSIVE_LINE
 **	Recursively relocate all widgets on the same line
 **/
static int rowcolumn_recursive_line(X11_widget col, X11_widget o,
						int y, int h, boolean fixed,
						int last_x, int margin)
{
X11_widget	sibling;
int		new_w, new_h;
int		delta;
/*
 * End of recursion
 */
	if (o == 0) {
		delta = col->w - (col->border2 + last_x + margin);
/*
 * All fixed size? Center the whole thing (incrementally at each change)
 */
		if (fixed)
			delta /= 2;
		return delta;
	}
	if (o->labelling)
		sibling = o->sibling;
	else
		sibling = 0;
	if (o->unmanaged)
		return rowcolumn_recursive_line(col, sibling, y, h, fixed,
							last_x, margin);
/*
 * Increase the margin
 */
	if (o->margin_w > margin)
		margin = o->margin_w;
	last_x += margin;
/*
 * Where are we located?
 */
	if (o->hint_x > 0)
		last_x = o->hint_x;
/*
 * Recursion occurs first
 */
	delta = rowcolumn_recursive_line(col, sibling, y, h, fixed, last_x+o->w,
								o->margin_w);
/*
 * Now, we should know the size
 */
	if (o->fixed_h)
		new_h = o->h_min;
	else
		new_h = h;
	if (o->fixed_w)
		new_w = o->w_min;
	else
		new_w = o->w + delta;
	if (new_w < o->w_min)
		new_w = o->w_min;
	delta += o->w - new_w;
/*
 * Change size
 */
	if (new_h != o->h || new_w != o->w) {
		o->w = new_w;
		o->h = new_h;
		(*o->on_resize_window)(o, (XEvent *)0);
	}
/*
 * Position shifts
 */
	if (new_h != h)
		y += (h - new_h) / 2;
	last_x += delta;
	if (o->hint_x > 0)
		last_x = o->hint_x;
	else
		if (o->hint_x < 0)
			last_x = col->w + o->hint_x - (col->border2 + new_w);
	if (o->x != last_x || o->y != y) {
		o->x = last_x;
		o->y = y;
		if (o->id)
			XMoveWindow(x11__screen, o->id, last_x, y);
	}
/*
 * Return value
 */
	return delta;
}


/**
 ** Internal: ROW_COLUMN_RECURSIVE_RESIZE
 **	Recursively change the various widgets on a line
 **/
static int rowcolumn_recursive_resize(X11_widget col, X11_widget o,
						int last_y, int margin)
{
X11_widget	sibling;
unsigned int	next_margin;
int		h, h_min;
int		delta;
boolean		fixed_w, fixed_h;
/*
 * End of recursion
 */
	if (o == 0)
		return col->h - (col->border2 + last_y + margin);
	if (o->unmanaged)
		return rowcolumn_recursive_resize(col, o->sibling, last_y, margin);
/*
 * Now, compute row margin & supposed height
 */
	next_margin = 0;
	h       = 0;
	h_min   = 0;
	fixed_w = True;
	fixed_h = True;
	for (sibling = o; sibling; sibling = sibling->sibling) {
		if (!sibling->unmanaged) {
			if (next_margin < sibling->margin_h)
				next_margin = sibling->margin_h;
			if (h < sibling->h)
				h = sibling->h;
			if (h_min < sibling->h_min)
				h_min = sibling->h_min;
			if (!sibling->fixed_w)
				fixed_w = False;
			if (!sibling->fixed_h)
				fixed_h = False;
		}
		if (!sibling->labelling)
			break;
	}
/*
 * Add the margin around widget
 */
	if (next_margin > margin)
		last_y += next_margin;
	else
		last_y += margin;
/*
 * Recursively resize & relocate the entire row
 */
	(void)rowcolumn_recursive_line(col, o, last_y, h, fixed_w, 0, 0);
/*
 * Enter next recursion stage
 */
	for (sibling = o; sibling; sibling = sibling->sibling)
		if (!sibling->labelling)
			break;
	if (sibling)
		sibling = sibling->sibling;
	delta = rowcolumn_recursive_resize(col, sibling, last_y + h, next_margin);
/*
 * No delta height?
 */
	if (!delta)
		return 0;
/*
 * Has this row a fixed size? Just move vertically the widget then
 */
	if (fixed_h) {
		for (sibling = o; sibling; sibling = sibling->sibling) {
			if (!sibling->unmanaged) {
				sibling->y += delta;
				if (sibling->id)
					XMoveWindow(x11__screen, sibling->id,
							sibling->x, sibling->y);
			}
			if (!sibling->labelling)
				break;
		}
		return delta;
	}
/*
 * This row has a variable size. Adjust
 */
	h += delta;
	delta = 0;
/*
 * We cannot shrink more than the minimal size of the largest sub-object.
 * In that case, the objects shrink, then go upward.
 */
	if (h < h_min) {
		delta   = h - h_min;
		h       = h_min;
	}
	(void)rowcolumn_recursive_line(col, o, last_y+delta, h, fixed_w, 0, 0);
/*
 * Return newest delta, if any
 */
	return delta;
}

/**
 ** Internal: ROW_COLUMN_RESIZE
 **	Geometry of a column has changed...
 **/
static void rowcolumn_resize(X11_widget wdg, XEvent *dummy)
{
/*
 * Resize the XWindow object
 */
	x11__core_resize(wdg, dummy);
/*
 * Now, apply recursively the moves & resizes
 */
	(void)rowcolumn_recursive_resize(wdg, wdg->child, 0, 0);
}


/**
 ** Internal: ROW_COLUMN_GEOMETRY
 **	Apply geometry from a column
 **/
static void rowcolumn_geometry(X11_widget wdg, XEvent *dummy)
{
X11_widget	child, sibling;
unsigned int	w, w_min;
unsigned int	h, h_min;
unsigned int	y, y_min;
unsigned int	margin_h, margin_w, next_margin;
int		x, x_min;
int		hint_x;
/*
 * The first thing we do is check the geometries on all sub-objects
 */
	wdg->fixed_w = True;
	wdg->fixed_h = True;
	for (child = wdg->child; child; child = child->sibling)
		if (!child->unmanaged) {
			(*child->on_geometry_check)(child, dummy);
/*
 * While we're at it, are any of those fixed sizes?
 */
			if (!child->fixed_w)
				wdg->fixed_w = False;
			if (!child->fixed_h)
				wdg->fixed_h = False;
		}
/*
 * Initial values
 */
	margin_h  = 0;
	w = w_min = 0;
	h = h_min = 0;
/*
 * We begin a new line of children
 */
	for (child = wdg->child; child; child = child->sibling) {
		if (child->unmanaged)
			continue;
/*
 * Handle the vertical margin
 */
		next_margin = 0;
		for (sibling = child; sibling; sibling = sibling->sibling) {
			if (!sibling->unmanaged)
				if (next_margin < sibling->margin_h)
					next_margin = sibling->margin_h;
			if (!sibling->labelling)
				break;
		}
		if (next_margin > margin_h)
			margin_h = next_margin;
/*
 * Minimum sizes needed for this row
 */
		y     = child->h;
		y_min = child->h_min;
/*
 * Potential X position of right border
 */
		margin_w = child->margin_w;
		if ((x = child->hint_x) == 0)
			x = margin_w;
		else
			if (x < 0)
				x = 0-x;
		x_min = x + child->w_min;
		x    +=     child->w;
/*
 * Now, all bets are off if we have a labelling item
 */
		while (child->labelling && child->sibling) {
			child = child->sibling;
			if (child->unmanaged)
				continue;
/*
 * Potential vertical size top position
 */
			if (child->h > y)
				y = child->h;
			if (child->h_min > y_min)
				y_min = child->h_min;
/*
 * Margin is the largest of both H-margins
 */
			if (child->margin_w > margin_w)
				margin_w = child->margin_w;
			x     += margin_w;
			x_min += margin_w;
/*
 * Is there a positional hint?
 */
			hint_x = child->hint_x;
			if (hint_x > 0) {
				if (hint_x > x)
					x = hint_x;
				if (hint_x > x_min)
					x_min = hint_x;
			} else
				if (hint_x < 0) {
					x     -= hint_x;
					x_min -= hint_x;
				}
			x_min += child->w_min;
			x     += child->w;
			if (hint_x >= 0)
				margin_w = child->margin_w;
			else
				margin_w = 0;	/* Already added */
		}
/*
 * Now, new widths
 */
		x     += margin_w;
		x_min += margin_w;
		if (x > w)
			w = x;
		if (x_min > w_min)
			w_min = x_min;
/*
 * Next line
 */
		h     += margin_h + y;
		h_min += margin_h + y_min;
		margin_h = next_margin;
	}
	h     += margin_h;
	h_min += margin_h;
/*
 * Then, check the sizes...
 */
	if (w < w_min)
		w = w_min;
	if (h < h_min)
		h = h_min;
/*
 * Add the border, if any
 */
	w     += wdg->border2;
	h     += wdg->border2;
	w_min += wdg->border2;
	h_min += wdg->border2;
/*
 * Apply new geometry
 */
	wdg->w_min = w_min;
	wdg->h_min = h_min;
	if (wdg->w >= w_min && !wdg->fixed_w)
		w = wdg->w;
	if (wdg->h >= h_min && !wdg->fixed_h)
		h = wdg->h;
/*
 * New default size
 */
	if (wdg->w != w || wdg->h != h) {
		wdg->w = w;
		wdg->h = h;
		rowcolumn_resize(wdg, dummy);
	}
}


/**
 ** X11_ROW_COLUMN
 **	Create a row/column window
 **/
X11_widget X11_RowColumn(X11_widget parent, char *name)
{
x11__form	*object;
/*
 * Default name
 */
	if (!name)
		name = "rowcolumn";
/*
 * Get resource
 */
	object = allocator(x11__form);
	x11__core_setup(&object->core, parent, "Column", name);
/*
 * Initialize fields
 */
	object->core.on_geometry_check = rowcolumn_geometry;
	object->core.on_resize_window  = rowcolumn_resize;
	x11__resource_basic(&object->core);
/*
 * Special look and feel initializations
 */
	object->core.is_active  = parent->is_active;
	object->core.can_active = parent->can_active;
/*
 * Ok
 */
	return (X11_widget)object;
}
