/* $Header: /afs/athena.mit.edu/astaff/project/atdev/src/layout/RCS/Layout.c,v 3.1 91/01/03 17:44:36 crcraig Exp $ */

/*******************************************************************
  Copyright (C) 1990 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.

***************************************************************** */

#include <ctype.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#ifdef _AtDevelopment_
#include "LayoutP.h"
#else
#include <At/LayoutP.h>
#endif

#define offset(field) XtOffset(AtLayoutWidget, field)
static XtResource resources[] = {
{XtNlayout, XtCLayout, XtRString, sizeof(String),
     offset(layout.layout), XtRString, NULL},
{XtNdefaultSpacing, XtCDefaultSpacing, XtRInt, sizeof(int),
     offset(layout.defaultSpacing), XtRImmediate, (caddr_t) 10},
};
#undef offset

static void Initialize(Widget, Widget);
static Boolean SetValues(Widget, Widget, Widget);
static void Resize(Widget);
static void ChangeManaged(Widget);
static void InsertChild(Widget);
static void DeleteChild(Widget);
static XtGeometryResult GeometryManager(), PreferredGeometry();

static void LayoutChildren(AtLayoutWidget, Boolean);

#define superclass (&compositeClassRec)

AtLayoutClassRec atLayoutClassRec = {  {
    /* core_class fields */
    /* superclass         */    (WidgetClass) superclass,
    /* class_name         */    "AtLayout",
    /* widget_size        */    sizeof(AtLayoutRec),
    /* class_initialize   */    NULL,
    /* class_part_init    */    NULL,
    /* class_inited       */    FALSE,
    /* initialize         */    Initialize,
    /* initialize_hook    */    NULL,
    /* realize            */    XtInheritRealize,
    /* actions            */    NULL,
    /* num_actions        */    0,
    /* resources          */    resources,
    /* num_resources      */    XtNumber(resources),
    /* xrm_class          */    NULLQUARK,
    /* compress_motion    */    TRUE,
    /* compress_exposure  */    TRUE,
    /* compress_enterleave*/    TRUE,
    /* visible_interest   */    FALSE,
    /* destroy            */    NULL,
    /* resize             */    Resize,
    /* expose             */    XtInheritExpose,
    /* set_values         */    SetValues,
    /* set_values_hook    */    NULL,
    /* set_values_almost  */    XtInheritSetValuesAlmost,
    /* get_values_hook    */    NULL,
    /* accept_focus       */    NULL,
    /* version            */    XtVersion,
    /* callback_private   */    NULL,
    /* tm_table           */    NULL,
    /* query_geometry     */    PreferredGeometry,
    /* display_accelerator*/    XtInheritDisplayAccelerator,
    /* extension          */    NULL
  },
  { /* composite_class fields */
    /* geometry_manager   */   GeometryManager,
    /* change_managed     */   ChangeManaged,
    /* insert_child       */   InsertChild,
    /* delete_child       */   DeleteChild,
    /* extension          */   NULL
  },
};

WidgetClass atLayoutWidgetClass = (WidgetClass) &atLayoutClassRec;


static void InitLexer(AtLayoutWidget w)
{
    w->layout.nextc = w->layout.layout;
    w->layout.idval = NULL;
    w->layout.numval = 0;
    w->layout.curtok = -1;
}

static int GetToken(AtLayoutWidget w)
{
    int len;
    char *mark;

    if (w->layout.curtok != -1) return w->layout.curtok;

    if (w->layout.nextc == NULL) {
	w->layout.curtok = END;
	return END;
    }
    
    /* skip whitespace */
    while (isspace((int)*w->layout.nextc)) w->layout.nextc++;
	
    switch (*w->layout.nextc) {
    case '\0': w->layout.curtok = END; break;
    case '[':  w->layout.curtok = LBRACKET; break;
    case ']':  w->layout.curtok = RBRACKET; break;
    case '(':  w->layout.curtok = LPAREN; break;
    case ')':  w->layout.curtok = RPAREN; break;
    case '+':  w->layout.curtok = PLUS; break;
    case '-':  w->layout.curtok = MINUS; break;
    }
    if (w->layout.curtok != -1) {
	w->layout.nextc++;
	return w->layout.curtok;
    }

    if (!isalnum((int)*w->layout.nextc)) {
	XtWarning("AtLayout: bad character in layout string.");
	w->layout.curtok = ERROR;
    }
    else if (isdigit((int)*w->layout.nextc)) {
	w->layout.numval = 0;
	while (isdigit((int)*w->layout.nextc)) {
	    w->layout.numval *= 10;
	    w->layout.numval += (int)(*w->layout.nextc - '0');
	    w->layout.nextc++;
	}
	w->layout.curtok = NUMBER;
    }
    else if (strncasecmp(w->layout.nextc, "row", 3) == 0) {
	w->layout.nextc += 3;
	w->layout.curtok = ROW;
    }
    else if (strncasecmp(w->layout.nextc, "col", 3) == 0) {
	w->layout.nextc += 3;
	w->layout.curtok = COL;
    }
    else if (strncasecmp(w->layout.nextc, "space", 5) == 0) {
	w->layout.nextc += 5;
	w->layout.curtok = SPACE;
    }
    else if (strncasecmp(w->layout.nextc, "fill", 4) == 0) {
	w->layout.nextc += 4;
	w->layout.curtok = FILL;
    }
    else {/* its an ident.  Figure out how long and copy it. */
	mark = w->layout.nextc;
	for(len=0; *w->layout.nextc && isalnum((int)*w->layout.nextc);
	    len++,w->layout.nextc++);
	w->layout.idval = XtMalloc(len+1);
	strncpy(w->layout.idval, mark, len);
	w->layout.idval[len] = '\0';
	w->layout.curtok = IDENT;
    }
    return w->layout.curtok;
}

static void NextToken(AtLayoutWidget w)
{
    w->layout.curtok = -1;
}

static void parse_items(AtLayoutWidget w, Layout *l)
{
    int tok;
    Layout *item, *itemlist;
    static Layout *Parse();
    
    if (GetToken(w) != LBRACKET) {
	XtWarning("AtLayout: '[' expected in layout string.");
	return;
    }
    NextToken(w);
    
    item = itemlist = NULL;
    while ((tok = GetToken(w)) != RBRACKET) {
	if (tok == ERROR) return;
	if (tok == END) {
	    XtWarning("AtLayout: ']' expected in layout string.");
	    return;
	}
	if (l->items == NULL) {/* first time only */
	    l->items = Parse(w);
	    item = l->items;
	}
	else {
	    item->next = Parse(w);
	    item = item->next;
	}
	l->num++;
	if (item == NULL) return;
    }
    NextToken(w);
}	

static void parse_stretchability(AtLayoutWidget w, Layout *l)
{
    int tok;

    if (GetToken(w) != LPAREN) return;

    NextToken(w);
    while((tok = GetToken(w)) != RPAREN) {
	if (tok == ERROR) return;
	if (tok == END) {
	    XtWarning("AtLayout: ')' expected in layout string");
	    return;
	}
	if (tok == PLUS) {
	    NextToken(w);
	    tok = GetToken(w);
	    if (tok == NUMBER) {
		l->stretch = w->layout.numval;
		NextToken(w);
	    }
	    else goto error;
	}
	else if (tok == MINUS) {
	    NextToken(w);
	    tok = GetToken(w);
	    if (tok == NUMBER) {
		l->shrink = w->layout.numval;
		NextToken(w);
	    }
	    else goto error;
	}
	else {
error:	    XtWarning("AtLayout: syntax error in layout string");
	    return;
	}
    }
    NextToken(w);
}

static Layout *Parse(AtLayoutWidget w)
{
    int tok;
    Layout *l;
    
    tok = GetToken(w);

    if ((tok == END)  || (tok == ERROR)) return NULL;

    l = (Layout *)XtMalloc(sizeof(Layout));
    l->name = (XrmQuark)0;
    l->widget = NULL;
    l->x = l->y = l->w = l->h = l->stretch = l->shrink = 0;
    l->items = NULL;
    l->num = 0;
    
    switch (tok) {
    case ROW:
	l->type = ROWTYPE;
	NextToken(w);
	parse_items(w,l);
	break;
    case COL:
	l->type = COLTYPE;
	NextToken(w);
	parse_items(w,l);
	break;
    case NUMBER:
	l->type = SPACETYPE;
	l->w = l->h = w->layout.numval;
	NextToken(w);
	parse_stretchability(w,l);
	break;
    case SPACE:
	l->type = SPACETYPE;
	l->w = l->h = w->layout.defaultSpacing;
	l->stretch = l->shrink = 0;
	NextToken(w);
	break;
    case FILL:
	l->type = SPACETYPE;
	l->w = l->h = 0;
	l->stretch = 1000;
	l->shrink = 0;
	NextToken(w);
	break;
    case IDENT:
	l->type = WIDGETTYPE;
	l->name = XrmStringToQuark(w->layout.idval);
	XtFree(w->layout.idval);
	NextToken(w);
	parse_stretchability(w,l);
	break;
    default:
	XtWarning("AtLayout: syntax error in layout string");
	XtFree(l);
	return NULL;
    }
    
    return l;
}

static void Initialize(Widget request, Widget new)
{
    AtLayoutWidget tw = (AtLayoutWidget) new;

    InitLexer(tw);
    tw->layout.lo = Parse(tw);
}


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

/*
 * the next two procedures are a variation on the TeX boxes and glue
 * layout algorithm.  See the TeXbook by Donald Knuth for more info.
 * The layout algorithm is described on page 77, right after exercise 12.7
 * The procedures below do  not implement infinity or orders of infinity,
 * and boxes (widgets) can have stretchablity and shrinkablity.
 */

static void compute_natural_size(Layout *l)
{
    Layout *item;
    
    switch (l->type) {
    case SPACETYPE:
	break;   /* nothing to compute */
    case WIDGETTYPE:
	if ((l->widget) && (XtIsManaged(l->widget))) {
	    l->w = l->widget->core.width;  /* or should I use XtQueryGeometry*/
	    l->h = l->widget->core.height;
	}
	else
	    l->w = l->h = 0;
	break;
    case ROWTYPE:
	l->w = l->h = l->stretch = l->shrink = 0;
	for(item = l->items; item != NULL; item = item->next) {
	    compute_natural_size(item);
	    if (item->type == SPACETYPE) item->h = 0;
	    l->w += item->w;
	    if (item->type != COLTYPE) {
		l->stretch += item->stretch;
		l->shrink += item->shrink;
	    }
	    if (item->h > l->h) l->h = item->h;
	}
	break;
    case COLTYPE:
	l->w = l->h = l->stretch = l->shrink = 0;
	for(item = l->items; item != NULL; item = item->next) {
	    compute_natural_size(item);
	    if (item->type == SPACETYPE) item->w = 0;
	    l->h += item->h;
	    if (item->type != ROWTYPE) {
		l->stretch += item->stretch;
		l->shrink += item->shrink;
	    }
	    if (item->w > l->w) l->w = item->w;
	}
	break;
    }
}

static void configure(Layout *l, int x, int y, int width, int height)
{
    Layout *item;
    double r;    /* "glue set ratio" */
    Boolean stretch = True;
    int x1, y1, w1, h1;

    switch (l->type) {
    case WIDGETTYPE:
	if (l->widget) {
	    XtConfigureWidget(l->widget, x, y,
			      width, height, l->widget->core.border_width);
	}
	break;
    case ROWTYPE:
	if (l->w < width) { /* stretch */
	    if (l->stretch == 0) r = 0.0;
	    else r = (width - l->w)/(double)l->stretch;
	    stretch = True;
	}
	else if (l->w > width) { /* shrink */
	    if (l->shrink == 0) r = 0.0;
	    else r = (l->w - width)/(double)l->shrink;
	    if (r > 1.0) r = 1.0;  /* don't exceed maximum shrinkablity */
	    stretch = False;
	}
	else r = 0.0;

	x1 = x;
	for(item = l->items; item != NULL; item = item->next) {
	    w1 = item->w;
	    if (item->type != COLTYPE) {
		if (stretch) w1 += (int)(item->stretch * r);
		else w1 -= (int)(item->shrink * r);
	    }
	    configure(item, x1, y, w1, height);
	    x1 += w1;
	}
	break;

    case COLTYPE:
	if (l->h < height) { /* stretch */
	    if (l->stretch == 0) r = 0.0;
	    else r = (height - l->h)/(double)l->stretch;
	    stretch = True;
	}
	else if (l->h > height) { /* shrink */
	    if (l->shrink == 0) r = 0.0;
	    else r = (l->h - height)/(double)l->shrink;
	    if (r > 1.0) r = 1.0;  /* don't exceed maximum shrinkablity */
	    stretch = False;
	}
	else r = 0.0;

	y1 = y;
	for(item = l->items; item != NULL; item = item->next) {
	    h1 = item->h;
	    if (item->type != ROWTYPE) {
		if (stretch) h1 += (int)(item->stretch * r);
		else h1 -= (int)(item->shrink * r);
	    }
	    configure(item, x, y1, width, h1);
	    y1 += h1;
	}
	break;
    }
}
	

static void LayoutChildren(AtLayoutWidget w, Boolean resize)
{
    compute_natural_size(w->layout.lo);
    
    if (resize &&
	((w->layout.lo->w!=w->core.width) || (w->layout.lo->h!=w->core.height))) {
	XtGeometryResult result;
	Dimension compromisew, compromiseh;
	result = XtMakeResizeRequest(w, w->layout.lo->w, w->layout.lo->h,
				     &compromisew, &compromiseh);
	if (result == XtGeometryAlmost)
	    XtMakeResizeRequest(w, compromisew, compromiseh, NULL, NULL);
    }
    
    configure(w->layout.lo, 0, 0, w->core.width, w->core.height);
}

static void Resize(Widget w)
{
    LayoutChildren((AtLayoutWidget) w, False);
}

static void ChangeManaged(Widget w)
{
    LayoutChildren((AtLayoutWidget) w, True);
}

static Layout *find_layout_by_name(Layout *l, XrmQuark name)
{
    Layout *item, *result;

    if  (l == NULL) return NULL;
    
    switch(l->type) {
    case SPACETYPE:
	return NULL;
    case WIDGETTYPE:
	if (l->name == name) return l; else return NULL;
    case ROWTYPE:
    case COLTYPE:
	for(item = l->items; item != NULL; item = item->next) {
	    result = find_layout_by_name(item, name);
	    if (result) return result;
	}
	return NULL;
    }
}

static void InsertChild(Widget w)
{
    AtLayoutWidget p = (AtLayoutWidget)XtParent(w);
    Layout *l;
    char errbuf[100];

    /* actually insert it into the list */
    (*superclass->composite_class.insert_child)(w);

    l = find_layout_by_name(p->layout.lo, w->core.xrm_name);

    if (l == NULL) {
	sprintf(errbuf, "AtLayout: no layout information for child named %s",
		XrmQuarkToString(w->core.xrm_name));
	XtWarning(errbuf);
    }
    else
	l->widget = w;
}

static void DeleteChild(Widget w)
{
    AtLayoutWidget p = (AtLayoutWidget)XtParent(w);
    Layout *l;

    /* actually delete it from the list */
    (*superclass->composite_class.delete_child)(w);

    l = find_layout_by_name(p->layout.lo, w->core.xrm_name);
    if (l != NULL) l->widget = NULL;
}

/* stolen from Xaw Form widget.  Hope it works right */
/* ARGSUSED */
static XtGeometryResult GeometryManager(w, request, reply)
    Widget w;
    XtWidgetGeometry *request;
    XtWidgetGeometry *reply;	/* RETURN */
{
    XtWidgetGeometry allowed;

    if (request->request_mode & ~(XtCWQueryOnly | CWWidth | CWHeight))
	return XtGeometryNo;

    if (request->request_mode & CWWidth)
	allowed.width = request->width;
    else
	allowed.width = w->core.width;

    if (request->request_mode & CWHeight)
	allowed.height = request->height;
    else
	allowed.height = w->core.height;

    if (allowed.width == w->core.width && allowed.height == w->core.height)
	return XtGeometryNo;

    if (!(request->request_mode & XtCWQueryOnly)) {
        /* reset virtual width and height. */
      	w->core.width = allowed.width;
	w->core.height = allowed.height;
	LayoutChildren((AtLayoutWidget)XtParent(w), True);
    }
    return XtGeometryYes;
}


/* stolen from Xaw Form widget.  Hope it works right */
static XtGeometryResult PreferredGeometry(widget, request, reply)
    Widget widget;
    XtWidgetGeometry *request, *reply;
{
    AtLayoutWidget w = (AtLayoutWidget)widget;
    
    reply->width = w->layout.lo->w;
    reply->height = w->layout.lo->h;
    reply->request_mode = CWWidth | CWHeight;
    if (  request->request_mode & (CWWidth | CWHeight) ==
	    reply->request_mode & CWWidth | CWHeight
	  && request->width == reply->width
	  && request->height == reply->height)
	return XtGeometryYes;
    else if (reply->width == w->core.width && reply->height == w->core.height)
	return XtGeometryNo;
    else
	return XtGeometryAlmost;
}
