/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
/* @(#)VList.c	1.11  6/18/90 */
#include <stdio.h>
/*
 *		Bulletin Board Widget.
 */
#ifdef MOTIF
#include <Xm/Xm.h>
#include <Xm/XmP.h>
#endif
#ifdef X11R3
typedef char *XtPointer ;
#endif
#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>
#include <malloc.h>

#include <VListP.h>

#define BBW(w) (((VListWidget)(w))->vList)

static ClassInitialize();
static Initialize();
static Destroy();
static Resize();
static ReDisplay();
static Boolean SetValues();
static void ReManageChildren() ;
static XtGeometryResult GeometryManager() ;
static int default_horiz = VL_LEFT ;
static int default_vert = VL_TOP ;
char *calloc() ;

static XtResource constraintResources[] =
{
        {XtNstretchVert,
                XtCStretchVert,
                XtRBoolean,
                sizeof(Boolean),
                XtOffset(VListConstraints,stretch_vert),
                XtRString,
                "FALSE"},
        {XtNstretchHoriz,
                XtCStretchHoriz,
                XtRBoolean,
                sizeof(Boolean),
                XtOffset(VListConstraints,stretch_horiz),
                XtRString,
                "FALSE"},
        {XtNrow,
                XtCRow,
                XtRInt,
                sizeof(int),
                XtOffset(VListConstraints,row),
                XtRString,
                "-1"},
        {XtNvAlign,
                XtCVAlign,
                XtRInt,
                sizeof(int),
                XtOffset(VListConstraints,v_align),
                XtRInt,
                (XtPointer)&default_vert},
        {XtNhAlign,
                XtCHAlign,
                XtRInt,
                sizeof(int),
                XtOffset(VListConstraints,h_align),
                XtRInt,
                (XtPointer)&default_horiz},
        {XtNcolumn,
                XtCColumn,
                XtRInt,
                sizeof(int),
                XtOffset(VListConstraints,col),
                XtRString,
                "-1"},
} ;

static XtResource resources[] = {
        {XtNrowsSameSize,
                XtCRowsSameSize,
                XtRBoolean,
                sizeof(Boolean),
                XtOffset(VListWidget,vList.rows_same_size),
                XtRString,
                "FALSE"},
        {XtNcolsSameSize,
                XtCColsSameSize,
                XtRBoolean,
                sizeof(Boolean),
                XtOffset(VListWidget,vList.cols_same_size),
                XtRString,
                "FALSE"},
        {XtNallowResizeX,
                XtCAllowResizeX,
                XtRBoolean,
                sizeof(Boolean),
                XtOffset(VListWidget,vList.allow_resize_x),
                XtRString,
                "TRUE"},
        {XtNallowResizeY,
                XtCAllowResizeY,
                XtRBoolean,
                sizeof(Boolean),
                XtOffset(VListWidget,vList.allow_resize_y),
                XtRString,
                "TRUE"},
        {XtNvSpacing,
                XtCVSpacing,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.vspacing),
                XtRString,
                "0"},
        {XtNvPad,
                XtCVPad,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.vpad),
                XtRString,
                "0"},
        {XtNhPad,
                XtCHPad,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.hpad),
                XtRString,
                "0"},
        {XtNhSpacing,
                XtCHSpacing,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.hspacing),
                XtRString,
                "0"},
        {XtNnumColumns,
                XtCNumColumns,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.num_columns),
                XtRString,
                "1"},
        {XtNrowPivot,
                XtCRowPivot,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.row_pivot),
                XtRString,
                "-1"},
        {XtNcolPivot,
                XtCColPivot,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.col_pivot),
                XtRString,
                "-1"},
        {XtNnumRows,
                XtCNumRows,
                XtRInt,
                sizeof(int),
                XtOffset(VListWidget,vList.num_rows),
                XtRString,
                "0"},
} ;

VListClassRec vListWidgetClassRec =
{
	{
#ifdef MOTIF
		(WidgetClass)&xmManagerClassRec,		/* superclass */
#else
		(WidgetClass)&constraintClassRec,		/* superclass */
#endif
		"VList",								/* class name */
		sizeof(VListRec),					/* widget size */
		(XtWidgetClassProc)ClassInitialize,		/* class_initialize */
		NULL,									/* class_part_initialize */
		FALSE,									/* class inited */
		(XtInitProc)Initialize,					/* initialize */
		NULL,									/* initialize_hook */
		XtInheritRealize,						/* realize */
		NULL,									/* actions */
		0,										/* num_actions */
		resources,								/* resources */
		XtNumber(resources),					/* num_resources */
		NULLQUARK,								/* xrm_class */
		TRUE,									/* compress_motion */
		TRUE,									/* compress_exposure */
		TRUE,									/* compress_enterleave */
		FALSE,									/* visible_interest */
		(XtWidgetProc)Destroy,					/* destroy */
		(XtWidgetProc)Resize,					/* resize */
		(XtExposeProc)ReDisplay,				/* expose */
		SetValues,								/* set_values */
		NULL,									/* set_values_hook */
		XtInheritSetValuesAlmost,				/* set_values_almost */
		NULL,									/* get_values_hook */
		XtInheritAcceptFocus,					/* accept_focus */
		XtVersion,								/* version */
		NULL,									/* callback private */
		NULL,									/* tm_table */
		NULL,									/* query_geometry */
		XtInheritDisplayAccelerator,			/* display_accelerator */
		NULL									/* extension */
	},
	{
		GeometryManager,
		ReManageChildren,
		XtInheritInsertChild,
		XtInheritDeleteChild,
		NULL,
	},
	{
        constraintResources,
        XtNumber(constraintResources),
        sizeof(VListConstraintRec),
		NULL,
		NULL,
		NULL,
		NULL,
	},
#ifdef MOTIF
	{
		NULL,
		NULL,
		0,
		NULL,
		0,
		NULL,
	},
#endif
	{
		0,
	}
} ;

WidgetClass vListWidgetClass = (WidgetClass)&vListWidgetClassRec ;

/*ARGSUSED*/
static
ClassInitialize()
{
}

/*ARGSUSED*/
static
Initialize(request,new)
	Widget request ;
	Widget new ;
{
	/* Make sure we do not have a bad window size */
	if (!new->core.width)
	{
		new->core.width = 1 ;
	}
	if (!new->core.height)
	{
		new->core.height = 1 ;
	}
}

/*ARGSUSED*/
static
Destroy(w)
	Widget w ;
{
}

/*ARGSUSED*/
static
Resize(w)
	Widget w ;
{
	redo_locations(w,False,True) ;
}

/*ARGSUSED*/
static
ReDisplay(w, event)
	Widget w ;
	XExposeEvent *event ;
{
}

/*ARGSUSED*/
static
Boolean
SetValues(old, request, new)
	Widget old ;
	Widget request ;
	Widget new ;
{
	return True ;
}

/*ARGSUSED*/
static
void
ReManageChildren(w)
	Widget w ;
{
	redo_locations(w,False,False) ;
}

static
redo_locations(w,query_only,being_resized)
	Widget w ;
{
	Widget *children = ((VListWidget)w)->composite.children ;
	int nchild = ((VListWidget)w)->composite.num_children ;
	int i ;
	int *rows ;
	int *cols ;
	int nr = BBW(w).num_rows ;
	int nc = BBW(w).num_columns ;
	int total_width ;
	int total_height ;

	/* Find out the # of rows present */
	if (!nr)
	{
		int pos = 0 ;
		for(i = 0; i < nchild; i++)
		{
			Widget child = children[i] ;
			if (XtIsManaged(child))
			{
				VListConstraintRec *rec = (VListConstraintRec *)child->core.constraints ;
				int row = rec->row < 0 ? (pos / nc) : rec->row ;
				if (row >= nr)
				{
					nr = row + 1 ;
				}
				pos++ ;
			}
		}
	}
	else
	{
		int pos = 0 ;
		nc = 0 ;
		for(i = 0; i < nchild; i++)
		{
			Widget child = children[i] ;
			if (XtIsManaged(child))
			{
				VListConstraintRec *rec = (VListConstraintRec *)child->core.constraints ;
				int col = rec->col < 0 ? (pos / nr) : rec->col ;
				if (col >= nc)
				{
					nc = col + 1 ;
				}
				pos++ ;
			}
		}
	}
	if (!nr || !nc)
	{
		return 0 ;
	}

	/* find out the positions of all of the rows and columns by finding
	   determining the max width and height of each widget per column and
	   per row */
	rows = (int *)calloc((unsigned)nr,sizeof(int)) ;
	cols = (int *)calloc((unsigned)nc,sizeof(int)) ;
	{
		int max_width = 0 ;
		int max_height = 0 ;
		int pos = 0 ;
		for(i = 0; i < nchild; i++)
		{
			Widget child = children[i] ;
			if (XtIsManaged(child))
			{
				VListConstraintRec *rec = (VListConstraintRec *)child->core.constraints ;
				int col = rec->col < 0 ? pos % nc : rec->col ;
				int row = rec->row < 0 ? pos / nc : rec->row ;
				int child_width = child->core.width + 2 * child->core.border_width ;
				int child_height = child->core.height + 2 * child->core.border_width ;
				if (child_width > cols[col])
				{
					cols[col] = child_width ;
					if (child_width > max_width)
					{
						max_width = child_width ;
					}
				}
				if (child_height > rows[row])
				{
					rows[row] = child_height ;
					if (child_height > max_height)
					{
						max_height = child_height ;
					}
				}
				pos++ ;
			}
		}
		if (BBW(w).rows_same_size)
		{
			for(i = 0; i < nr ; rows[i++] = max_height) ;
		}
		if (BBW(w).cols_same_size)
		{
			for(i = 0; i < nr ; cols[i++] = max_width) ;
		}
	}

	{
		int pos = BBW(w).hpad ;
		for(i = 0; i < nc; i++)
		{
			int col_w = cols[i] ;
			cols[i] = pos ;
			pos += col_w + ((i == nc - 1) ? BBW(w).hpad : BBW(w).hspacing) ;
		}
		total_width = pos ;
	}

	{
		int pos = BBW(w).vpad ;
		for(i = 0; i < nr; i++)
		{
			int row_h = rows[i] ;
			rows[i] = pos ;
			pos += row_h + ((i == nr - 1) ? BBW(w).vpad : BBW(w).vspacing) ;
		}
		total_height = pos ;
	}

	/* Now resize ourselves to fit exactly */
	if (!being_resized)
	{
		XtWidgetGeometry geom_req ;
		XtWidgetGeometry geom_reply ;
		geom_req.request_mode = 0 ;
		if (BBW(w).allow_resize_x)
		{
			geom_req.request_mode |= CWWidth ;
			geom_req.width = total_width ;
		}
		if (BBW(w).allow_resize_y)
		{
			geom_req.request_mode |= CWHeight ;
			geom_req.height = total_height ;
		}
		if (query_only)
		{
			geom_req.request_mode |= XtCWQueryOnly ;
		}
		switch(XtMakeGeometryRequest(w,&geom_req,&geom_reply))
		{
			case XtGeometryYes :
				break ;
			default :
				if (BBW(w).allow_resize_x && geom_req.width > w->core.width ||
				    BBW(w).allow_resize_y && geom_req.height > w->core.height)
				{
					free((char *)rows) ;
					free((char *)cols) ;
					return -1 ;
				}
				break ;
		}
	}

	if (BBW(w).col_pivot >= 0)
	{
		int delta = w->core.width - total_width ;
		for(i = BBW(w).col_pivot; i < nc; i++)
		{
			cols[i] += delta ;
		}
	}
	total_width = w->core.width ;

	if (BBW(w).row_pivot >= 0)
	{
		int delta = w->core.height - total_height ;
		for(i = BBW(w).row_pivot; i < nr; i++)
		{
			rows[i] += delta ;
		}
	}
	total_height = w->core.height ;

	if (!query_only)
	{
		int i ;

		/* Now move all widgets into place */
		int pos = 0 ;
		for(i = 0; i < nchild; i++)
		{
			Widget child = children[i] ;
			if (XtIsManaged(child))
			{
				VListConstraintRec *rec = (VListConstraintRec *)child->core.constraints ;
				int col = rec->col < 0 ? pos % nc : rec->col ;
				int row = rec->row < 0 ? pos / nc : rec->row ;
				int x = cols[col] ;
				int y = rows[row] ;
				int bw = child->core.border_width ;
				int cw = child->core.width ;
				int ch = child->core.height ;
				int child_w = cw + 2 * bw ;
				int child_h = ch + 2 * bw ;
				int right = ((col == nc - 1) ?
					(total_width - BBW(w).hpad - 1) :
					 cols[col + 1] - BBW(w).hspacing - 1) ;
				int bottom = ((row == nr - 1) ?
					(total_height - BBW(w).vpad - 1) :
					 rows[row + 1] - BBW(w).vspacing - 1) ;
				if (rec->stretch_horiz)
				{
					cw = right - x + 1 - 2 * bw ;
				}
				else if (rec->h_align == VL_CENTER)
				{
					x = (right + x - child_w) / 2 ;
				}
				else if (rec->h_align == VL_RIGHT)
				{
					x = right - child_w ;
				}
				if (rec->stretch_vert)
				{
					ch = bottom - y + 1 - 2 * bw ;
				}
				else if (rec->v_align == VL_CENTER)
				{
					y = (bottom + y - child_h) / 2 ;
				}
				else if (rec->v_align == VL_BOTTOM)
				{
					y = bottom - child_h ;
				}
				XtConfigureWidget(child,x,y,cw,ch,bw) ;
				pos++ ;
			}
		}
	}
	free((char *)rows) ;
	free((char *)cols) ;
	return 0 ;
}

/*ARGSUSED*/
static
XtGeometryResult
GeometryManager(w,req,rep)
	Widget w ;
	XtWidgetGeometry *req ;
	XtWidgetGeometry *rep ;
{
	Dimension height ;
	Dimension width ;
	Dimension border_width ;
	Widget bw = w->core.parent ;
	XtGeometryMask mode = req->request_mode ;

	/* Accept width and height changes only */
	if (mode & ~(XtCWQueryOnly | CWWidth | CWHeight | CWBorderWidth))
	{
		return XtGeometryNo ;
	}

	/* Get requested border width */
	border_width = (mode & CWBorderWidth) ?
		req->border_width :
		w->core.border_width ;

	/* Get requested width */
	width = (mode & CWWidth) ?
		req->width :
		w->core.width ;

	/* Get requested height */
	height = (mode & CWHeight) ?
		req->height :
		w->core.height ;

	/* If same as before do nothing */
	if (width == w->core.width && height == w->core.height &&
		border_width == w->core.border_width)
	{
		return XtGeometryYes ;
	}

	{
		int safe_width = w->core.width ;
		int safe_height = w->core.height ;
		int safe_bw = w->core.border_width ;
		w->core.width = width ;
		w->core.height = height ;
		w->core.border_width = border_width ;

		/* If just asking, try it out */
		if (mode & XtCWQueryOnly)
		{
			int rc = redo_locations(bw,TRUE,False) ;
			w->core.width = safe_width ;
			w->core.height = safe_height ;
			w->core.border_width = safe_bw ;
			return (rc < 0) ? XtGeometryNo : XtGeometryYes ;
		}

		/* Try and do it */
		if (redo_locations(bw,FALSE,False) < 0)
		{
			w->core.width = safe_width ;
			w->core.height = safe_height ;
			w->core.border_width = safe_bw ;
			return XtGeometryNo ;
		}
	}

	return XtGeometryYes ;
}
