/*
 * 	blk	- Automatic Requester formatter and generator.
 *
 * Reads a requester description and formats graphically then generates the
 * appropriate C-code to specify the requester.  See the examples and the
 * documentaion for using the program.
 *
 * Process flow:  Box structure read by recursive-descent.  Boxes formatted
 * and layed-out, also recursively.  Borders generated and optimized into a
 * minimum set of Border structs.  Working preview displayed for fiddling
 * with.  Output declarations written to file.
 *
 * Problems:  In a nutshell -- error recovery.  It doesn't do too well if
 * it runs out of memory, especially in the Border optimization code.
 * Also, the user error messages on parse errors could be much better --
 * like giving line number or a sample of the code at that point -- something
 * that would give a clue as to what went wrong.  Other than that, it's
 * pretty good.
 *
 * Differences from version 1:  Version 1 was distributed on a Fish disk
 * and was a real hack.  This version supports a 'C'-like preprocessor 
 * with #include's and #define's and macros with arguments.  This makes
 * the Requester source files look much nicer since the underlying 
 * grammar is so unreadable.  
 *
 * Disclaimer: This is a tool I hacked up for my own use in creating requesters
 * for Modeler 3D.  It works for me, but I make no claim as to the robustness
 * or other quality of the code.  It's not mouse-driven, but it's such a
 * useful tool that it's worth learning anyway.  Besides, you can put it in
 * your makefile's and have it work just like any other compiler.
 *
 * I'm making this available as a service to Amiga developers.  You are
 * encouraged to enhance or modify as you need to make it more useful for
 * your own purposes.  If you make any changes that make this a better
 * general-purpose tool, let me know about them.
 *
 *	Stuart Ferguson		1/89
 *	(shf@well.UUCP)
 */

#include <stdio.h>
#include <functions.h>
#include <exec/types.h>
#include <intuition/intuition.h>

/* STD.H is my collection of useful macros.
 */
#include "std.h"

/* LEX.H is the lexical analysis defines.
 */
#include "lex.h"


#define abs(x)          ((x)<0 ? -(x) : (x))

/* Size of font for text boxes (fixed width assumed).
 */
#define FONTWIDTH	8
#define FONTHEIGHT	8

/* Box types
 */
#define HBOX	1
#define VBOX	2
#define FILL	3
#define BLOK	4
#define TEXT	5
#define HRULE	6
#define VRULE	7

/*
 * Extended Gadget structure to hold an optional special gadget ID name
 * and the parent box for this gadget.
 */
struct SuperGadget {
	struct Gadget	g;
	struct Box	*box;
	char		*gnam;
};

/*
 * A requester is constructed from a tree of Boxes.  Boxes are arranged in a
 * binary tree, one subtree is what is inside the Box (sub), the other is the
 * remaining Boxes at the same level (next). 
 */
typedef struct Box {
	struct SuperGadget	*gad;	/* gadget - if any */
	struct Box     *next, *sub;	/* binary tree links */
	short           type,		/* box type - possibilities above */
	                col;	/* color - for borders and text */
	short           xs, ys,	/* box size (x,y) */
	                x, y,	/* box position (x,y) */
	                nfil;	/* number of filers inside this box */
	char           *val;		/* string for TEXT boxes */
};


/* GLOBAL */

int		infoLevel = 1,	/* degree of verbosity (0=quiet, 
				 * 1=normal, 2=verbose)
				 */
		printBoxes = 0,	/* printout flag (false normally) */
		showPreview;	/* preview flag (depends on whether there's
				 * an output file and on the '-d' flag.
				 */
char           *globStr = "static ";	/* structures normally static */

FILE           *file;		/* output file */
char           *base;		/* base name */
short           def_bcol = 1, 	/* default border and */
		def_tcol = 1;	/* text colors */

/* String pointer ID's returned from the lexer.
 */
char           *fill_pstr, *tbox_pstr, *hbox_pstr, *vbox_pstr,
	       *blok_pstr, *s_pstr, *p_pstr, *pv_pstr, *ph_pstr;

/* The Requester structures, the lists of parts and the guy itself.
 */
struct Border    *blst = NULL;	/* list header for border structs */
struct IntuiText *itlst = NULL;	/* list header for text structs */
struct Gadget    *glst = NULL;	/* list header for gadgets */

#define LATER	0

struct Requester mreq = {
	NULL,5,5,LATER,LATER,0,0,LATER,LATER,LATER,
	0,0,NULL,{NULL},NULL,NULL,{NULL}
};

/*
 * Generic templates for creating Intuition structures.
 */
struct Border
	generic_border = {0, 0, LATER, 0, JAM1, LATER, LATER, LATER};
struct Gadget
	generic_gadget = {
		LATER, LATER, LATER, LATER, LATER,
		GADGHCOMP,RELVERIFY,BOOLGADGET|REQGADGET,
		LATER, NULL, LATER, 0, LATER, LATER, NULL
	};
struct IntuiText
	generic_itext = {LATER, 0, JAM2, LATER, LATER, LATER, LATER, LATER};
struct StringInfo
	generic_sinfo = {LATER, NULL, 0, LATER, 0};
struct PropInfo
	generic_pinfo = {AUTOKNOB|PROPBORDERLESS,0x8000,0x8000,0x8000,0x8000};

/* Two macros to extract GadgetType info from a Gadget structure.
 */
#define GADGETTYPEBITS	(~GADGETTYPE)
#define GTYPE(g)	(((g)->GadgetType)&GADGETTYPEBITS)

/*
 * The preview window.
 */
struct NewWindow nwin = {
	0, 0, 300, 150, -1, -1,
	CLOSEWINDOW | REQCLEAR | MOUSEMOVE | GADGETDOWN | GADGETUP | VANILLAKEY,
	WINDOWDEPTH | WINDOWDRAG,
	NULL, NULL, (UBYTE *) "Preview", NULL,
	NULL, 0, 0, 0, 0, WBENCHSCREEN
};

struct IntuitionBase *IntuitionBase;


struct Box * ReadBoxList ();
short Layin ();
char * IMClass ();

/* Lexer interface functions.
 */
char * FindString ();
short NextToken ();


/*
 * Returns a new, initialized Box struct.
 */
struct Box * NewBox (type)
	short           type;
{
	struct Box     *b;

	if (!(b = NEW (struct Box))) {
		MemError ();
		return NULL;
	}
	b->type = type;
	b->nfil = 0;
	b->gad = NULL;
	b->val = NULL;
	b->next = b->sub = NULL;
	return b;
}


/*
 * Recursively frees Box tree.
 */
FreeBox (box)
	struct Box *box;
{
	register struct Gadget	*g;
	register struct StringInfo *si;

	if (!box) return;

	FreeBox (box->sub);
	FreeBox (box->next);

	if (g = (struct Gadget *) box->gad) {
		if (GTYPE(g) == STRGADGET) {
			si = (struct StringInfo *) g->SpecialInfo;
			FREE_X (si, struct StringInfo, si->MaxChars);
		} else if (GTYPE(g) == PROPGADGET) {
			FREE (g->SpecialInfo, struct PropInfo);
			FREE (g->GadgetRender, struct Image);
		}
		FREE (g, struct SuperGadget);
	}
	FREI (box);
}


/*
 * Recursively examine all nodes of Box tree and allocate Border structs for
 * all the HRULE and VRULE boxes.  Adds new Borders to the main list. 
 * Returns 0 for failure, 1 for sucess.
 */
int CreateBorder (box)
	register struct Box    *box;
{
	register struct Border *bd;

	if (!box) return 1;

	if (box->type == HRULE || box->type == VRULE) {
		if (!(bd = NEW (struct Border))) {
			MemError ();
			FreeBorder ();
			return 0;
		}
		*bd = generic_border;
		bd->FrontPen = box->col;
		bd->Count = 2;
		if (!(bd->XY = NEW_N (SHORT, 4))) {
			MemError ();
			FREI (bd);
			FreeBorder ();
			return 0;
		}
		bd->XY[0] = bd->XY[2] = box->x;
		bd->XY[1] = bd->XY[3] = box->y;
		if (box->type == HRULE) bd->XY[2] += box->xs - 1;
		  else bd->XY[3] += box->ys - 1;

		bd->NextBorder = blst;
		blst = bd;
	}
	if (!CreateBorder (box->sub)) return 0;
	return (CreateBorder (box->next));
}


/*
 * Frees all Border structs in main Border list.
 */
FreeBorder ()
{
	register struct Border *b, *nxt;

	for (b = blst; b; b = nxt) {
		nxt = b->NextBorder;
		FREE_N (b->XY, SHORT, b->Count * 2);
		FREI (b);
	}
	blst = NULL;
}


/*
 * Recursively examine all nodes of Box tree and allocate IntuiText structs
 * for TEXT boxes that are not string gadgets.  Adds new Borders to the main
 * list.  Returns 1 for sucess, 0 for failure.
 */
int CreateIText (box)
	register struct Box     *box;
{
	struct IntuiText	*it;

	if (!box) return 1;

	/*
	 * "box->val" may have been zero-ed by a string gadget grabbing
	 * that text.  If so, this is not an IntuiText.
	 */
	if (box->type == TEXT && box->val) {
		if (!(it = NEW(struct IntuiText))) {
			MemError ();
			FreeIText ();
			return 0;
		}
		*it = generic_itext;
		it->IText = (UBYTE *) box->val;
		it->LeftEdge = box->x;
		it->TopEdge = box->y;
		it->FrontPen = box->col;
		it->NextText = itlst;
		itlst = it;
	}
	if (!CreateIText (box->sub)) return 0;
	return (CreateIText (box->next));
}


/*
 * Frees all IntuiText structs in the main list.  (No need to free the
 * text itself since that is managed by the lexer.)
 */
FreeIText ()
{
	register struct IntuiText *it, *nxt;

	for (it = itlst; it; it = nxt) {
		nxt = it->NextText;
		FREI (it);
	}
	itlst = NULL;
}


/*
 * First pass at merging redundant Borders:  Examines all the Borders in
 * the list for adjacency.  Any borders that could use the same set of
 * polyline commands are merged into a single struct. 
 */
MergeBorders ()
{
	register struct Border *a, *b;
	short           i0, i1, x, y, *xy, j;
	register short  i, ac, bc, merge;

	do {
		merge = -1;
		/*
		 * Examine all pairs of borders, "a" and "b", that
		 * are drawn with the same color, seaching for a pair
		 * that can be merged.  When loop exits with merge=-1,
		 * all pairs have been merged.
		 */
		for (a = blst; a; a = a->NextBorder) {
			for (b = a->NextBorder; b; b = b->NextBorder) {
				if (a->FrontPen != b->FrontPen) continue;

				/*
				 * Examine the 4 pairs of endpoints of each
				 * polyline to see if any are adjacent to
				 * each other.  If any are found, the pairs
				 * located are encoded into "merge" and
				 * the search loop exits.
				 */
				ac = a->Count;
				bc = b->Count;
				for (i0 = 0; i0 < 2; i0++)
					for (i1 = 0; i1 < 2; i1++) {
						x = a->XY[i0*2 * (ac - 1)]
						  - b->XY[i1*2 * (bc - 1)];
						y = a->XY[i0*2 * (ac - 1) + 1]
						  - b->XY[i1*2 * (bc - 1) + 1];
						if (abs (x) + abs (y) == 1)
							merge = (i0 << 1) + i1;
					}
				if (merge != -1)
					break;
			}
			if (merge != -1)
				break;
		}
		if (merge == -1) continue;

		/*
		 * Merging: Create a new polyline data array and move
		 * the two parent polylines into the new one, possibly
		 * reversing one or both in the process.
		 * -- HELP ME:	Is there a nice way out if this
		 *		allocation fails...?
		 */
		xy = NEW_N (SHORT, (bc + ac) * 2);
		x = ((merge & 2) == 0);		/* x = reverse "a" */
		y = ((merge & 1) == 1);		/* y = reverse "b" */
		j = 0;
		for (i = 0; i < ac; i++) {
			i0 = (x ? ac - 1 - i : i) * 2;
			xy[j++] = a->XY[i0];
			xy[j++] = a->XY[i0 + 1];
		}
		for (i = 0; i < bc; i++) {
			i0 = (y ? bc - 1 - i : i) * 2;
			xy[j++] = b->XY[i0];
			xy[j++] = b->XY[i0 + 1];
		}

		/*
		 * Set "a" to have the new polyline data array.
		 */
		a->Count = j / 2;
		FREE_N (a->XY, SHORT, ac * 2);
		a->XY = xy;

		/*
		 * Find "b's" predecessor and remove "b" from list.
		 */
		for (a = blst; a && a->NextBorder != b; a = a->NextBorder);
		a->NextBorder = b->NextBorder;
		FREE_N (b->XY, SHORT, bc * 2);
		FREE (b, struct Border);

	} while (merge != -1);
}


/*
 * Second pass of Border merging: Eliminates linear segments from all
 * Borders XY lists.  The first pass will create lots of redundant points
 * along linear line segments.  This pass will compress those out.
 */
MergeLinear ()
{
	register struct Border *b;
	register short  i0, i1, i2, k, *xy;

	/*
	 * Examine all borders with more than 1 line segment.
	 */
	for (b = blst; b; b = b->NextBorder) {
		if (b->Count < 3) continue;

		/*
		 * Scan along the polyline list and compress out linear
		 * segments by skiping over them.
		 */
		xy = b->XY;
		i0 = 0;
		i1 = 1;
		i2 = 2;
		k = 2;
		while (i2 < b->Count) {
			/*
			 * Skip past linear segments. (I.e. find the bend.)
			 */
			while (i2 < b->Count &&
			       (xy[i0 * 2] == xy[i1 * 2]
				 && xy[i1 * 2] == xy[i2 * 2] ||
				xy[i0 * 2 + 1] == xy[i1 * 2 + 1]
				 && xy[i1 * 2 + 1] == xy[i2 * 2 + 1])) {
				i1++;
				i2++;
			}
			if (i2 >= b->Count) continue;

			/*
			 * Move polyline data to itself after skipping.
			 */
			xy[k++] = xy[i1 * 2];
			xy[k++] = xy[i1 * 2 + 1];
			i0 = i1;
			i1 = i2;
			i2 = i1 + 1;
		}
		xy[k++] = xy[i1 * 2];
		xy[k++] = xy[i1 * 2 + 1];

		k /= 2;
		if (k == b->Count) continue;

		/*
		 * If this border has gotten shorter, allocate a new
		 * array and transfer the new polyline data.
		 */
		xy = NEW_N (SHORT, k * 2);
		for (i0 = 0; i0 < k * 2; i0++) xy[i0] = b->XY[i0];
		FREE_N (b->XY, SHORT, b->Count * 2);
		b->XY = xy;
		b->Count = k;
	}
}


/*
 * Set the XSize and YSize fields for this box and all below.
 */
Format (box)
	struct Box     *box;
{
	struct Box     *b;
	short           mx, my, sx, sy, nf;

	ASSERT (box);

	/*
	 * Deal with the basis (leaf) cases.
	 */
	switch (box->type) {
	
	    /* Blok and text nodes have fixed, already computed size.
	     */
	    case BLOK:
	    case TEXT:
		return;

	    /* Fill node has no intrinsic size.
	     */
	    case FILL:
		box->xs = box->ys = 0;
		box->nfil = 1;
		return;

	    /* H and VRULES have no intrinsic X or Y size, respectively.
	     */
	    case HRULE:
		box->xs = 0;
		return;
	    case VRULE:
		box->ys = 0;
		return;
	}

	/*
	 * H and VBOXes are the recursive case.  First format each
	 * internal box.
	 */
	for (b = box->sub; b; b = b->next) Format (b);

	/*
	 * Compute total and max sizes in each direction. Total (sx,sy) is sum
	 * of all sub-boxes, max (mx,my) is max of sub-boxes. Also inherit
	 * filler count.
	 */
	my = mx = sx = sy = nf = 0;
	for (b = box->sub; b; b = b->next) {
		sx += b->xs;
		sy += b->ys;
		if (b->type == box->type || b->type == FILL) nf += b->nfil;
		if (b->xs > mx) mx = b->xs;
		if (b->ys > my)	my = b->ys;
	}
	box->nfil = nf;

	/*
	 * For horizontal boxes, bounding box is sum in x and max in y; for
	 * vertical, bouding box is max in x and sum in y.  This is the
	 * minimum size of the containing box for the given subboxes.  It
	 * may still expand due to fillers.
	 */
	if (box->type == HBOX) {
		box->xs = sx;
		box->ys = my;
	} else if (box->type == VBOX) {
		box->xs = mx;
		box->ys = sy;
	}
}


/*
 * Compute the position of the boxes internal to this box given that this
 * box has correct location.  The box size computed by Format() is a minimum
 * size, "mx" and "my" are the max that the box can be expanded by filler.
 */
Layout (box, mx, my)
	struct Box     *box;
	short           mx, my;
{
	struct Box     *b;
	short           ish, z, nfil;
	long            gap, ifil;

	ASSERT (box);

	/*
	 * Rules fill out to their max possible size.
	 */
	if (box->type == HRULE) box->xs = mx;
	 else if (box->type == VRULE) box->ys = my;

	/*
	 * Process only HBOX and VBOX cases recursively.  Any other case (a
	 * basis case) has its position set correctly (see assumptions at
	 * head of function).
	 */
	if (box->type != HBOX && box->type != VBOX) return;

	/* Get important values.  Set the "is-hbox" (ish) flag.  Get the
	 * current X size for HBOXes or the Y size for VBOXes as "z".
	 * "gap" is the differece between the max size and minimum size 
	 * given by the Format(), and is how much fillers can expand.
	 */
	ish = (box->type == HBOX);
	z = (ish ? box->x : box->y);
	gap = (ish ? mx - box->xs : my - box->ys);

	/*
	 * Set positions by setting filler sizes.
	 */
	ifil = 0;
	Layin (box, &ifil, ish, z, box->nfil, gap);

	/* Update box size.  If it had fillers, it got as big as
	 * it could.
	 */
	if (box->nfil) {
		if (ish) box->xs = mx;
		    else box->ys = my;
	}
}


/*
 * Layout internal boxes.  Having this as a recursive function deals with
 * the case of VBOXes within VBOXes and HBOXes within HBOXes.
 *
 * NOTE: I'd comment this function, but I can't figure it out.  It seems to
 * figure out the horizonal position of each box and update it as it goes
 * along.  It also calls itself when there are nested same-class boxes.
 * Oh well.  There's probably a better way to do it anyway.
 */
short Layin (box, ifil, ish, z, nfil, gap)
	struct Box     *box;
	short          *ifil, ish, z, nfil;
	long            gap;
{
	struct Box     *b;
	short           t;

	for (b = box->sub; b; b = b->next) {
		if (ish) {
			b->x = z;
			b->y = box->y;
		} else {
			b->x = box->x;
			b->y = z;
		}

		if (b->type == FILL) {
			t = (gap * (*ifil + 1)) / nfil - (gap ** ifil) / nfil;
			(*ifil)++;
			if (ish) b->xs = t;
			    else b->ys = t;

		} else if ((ish && b->type == HBOX)
		       || (!ish && b->type == VBOX)) {
			if (ish) b->ys = box->ys;
			    else b->xs = box->xs;
			t = Layin (b, ifil, ish, z, nfil, gap) - z;
			if (ish) b->xs = t;
			    else b->ys = t;

		} else Layout (b, box->xs, box->ys);

		z += (ish ? b->xs : b->ys);
	}
	return z;
}


/*
 * Use the computed position of the boxes to set the position of
 * the associated gadgets.
 */
PositionGadgets ()
{
	struct Box	*b;
	struct Gadget	*g;

	for (g = glst; g; g = g->NextGadget) {
		b = ((struct SuperGadget *) g)->box;
		g->LeftEdge = b->x;
		g->TopEdge = b->y;
		g->Width = b->xs;
		g->Height = b->ys;
	}
}


/*
 * Returns pointer to string containing box type name for printout.
 */
char * BoxType (typ)
	short           typ;
{
	switch (typ) {
	    case HBOX:	return ("HBOX");
	    case VBOX:	return ("VBOX");
	    case BLOK:	return ("BLOK");
	    case TEXT:	return ("TEXT");
	    case FILL:	return ("FILL");
	    case HRULE:	return ("HRULE");
	    case VRULE:	return ("VRULE");
	}
}


/*
 * Recursively prints this box and all its contents.
 */
PrintBox (box, lev)
	struct Box     *box;
	short           lev;
{
	int             i;

	if (!box) return;

	for (i = 0; i < lev; i++) printf ("  ");

	printf ("%s (%d,%d) %dx%d", BoxType (box->type),
		box->x, box->y, box->xs, box->ys);
	if (box->type == TEXT)	printf (" <%s>", box->val);
	if (box->gad) 		printf (" [gadget]");
	printf ("\n");

	PrintBox (box->sub, lev + 1);
	PrintBox (box->next, lev);
}



/*
 * ==== INPUT SECTION ====
 *
 * File input uses the "lex" front-end for macro processing.  Main entry
 * points for this package are the NextToken() and Backspace() functions.
 * NextToken() returns the code for the next lexical item in the input 
 * stream and sets a buffer pointer to point to its value.  Backspace()
 * resets the lex package to re-read the last token read, so that the
 * file is effectively backspaced one token.  FindString() is also used
 * to get the unique identifer pointer for a string from the hash table.
 */


/*
 * Read a number if there is one.  Otherwise return false and don't
 * change n's value.
 */
BOOL Qnum (n, radix)
	short          *n, radix;
{
	short           i = 0, tok;
	char           *buf;

	tok = NextToken (&buf);
	if (tok != RT_NUM) {
		Backspace ();
		return 0;
	}
	for (; *buf >= '0' && *buf <= '9'; buf++) {
		i = i * radix + (*buf - '0');
	}
	*n = i;
	return 1;
}


/*
 * Reads a double-quoted string like
 *	"stuff"
 * from the file.  Returns pointer to the string contents. 
 */
char * ReadString ()
{
	short           tok;
	char           *buf;

	tok = NextToken (&buf);
	if (tok != RT_STR) {
		fprintf (stderr, "String not found.\n");
		Backspace ();
		return NULL;
	}
	return buf;
}


/*
 * Read gadget ID of the form 
 *	:number
 * if there is one.  Read as hex.  If there is one, create a new
 * SuperGadget structure and add to the main gadget list.
 */
struct SuperGadget * ReadOptGadget (box)
	struct Box *box;
{
	struct SuperGadget *sg;
	short           tok, id;
	char           *buf;

	tok = NextToken (&buf);
	if (tok != RT_CHR || *buf != ':') {
		Backspace ();
		return NULL;
	}
	if (!Qnum (&id, 16)) {
		fprintf (stderr, "Error reading gadget ID number\n");
		return NULL;
	}

	if (!(sg = NEW (struct SuperGadget))) {
		MemError ();
		return NULL;
	}
	sg->g = generic_gadget;
	sg->gnam = NULL;
	sg->box = box;
	sg->g.GadgetID = id;
	sg->g.NextGadget = glst;
	glst = (struct Gadget *) sg;
	return sg;
}


/*
 * Get a box from the open file.  Boxes are either single tokens ("f"
 * for FILL box, "-" and "|" for ordinary rules) or is a
 * composite of the form "("type data")".  Type can be "h" for HBOX,
 * "v" for VBOX, "b" for BLOK, "t" for TEXT, or "-" and "|" again for 
 * special rules.
 *
 * If there isn't a box here, ReadBox() returns NULL with the lexical
 * stream positioned back to read whatever was really there.
 */
struct Box * ReadBox ()
{
	short           tok, i;
	char           *buf, c;
	struct Box     *b;

	tok = NextToken (&buf);

	if (tok == RT_ID && buf == fill_pstr) return NewBox (FILL);

	if (tok != RT_CHR) {
		Backspace ();
		return NULL;
	}

	c = *buf;
	if (c == '-') {
		if (!(b = NewBox (HRULE))) return NULL;
		b->ys = 1;
		b->col = def_bcol;
		return b;
	}
	if (c == '|') {
		if (!(b = NewBox (VRULE))) return NULL;
		b->xs = 1;
		b->col = def_bcol;
		return b;
	}
	if (c != '(') {
		Backspace ();
		return NULL;
	}

	/*
	 * Decode the value inside the '('.
	 */
	tok = NextToken (&buf);
	c = *buf;
	if (tok == RT_ID)
		if (buf == hbox_pstr) {
			if (!(b = NewBox (HBOX))) return NULL;
			b->sub = ReadBoxList ();
		} else if (buf == vbox_pstr) {
			if (!(b = NewBox (VBOX))) return NULL;
			b->sub = ReadBoxList ();
		} else if (buf == tbox_pstr) {
			if (!(b = NewBox (TEXT))) return NULL;
			b->col = def_tcol;
			Qnum (&b->col, 10);
			if (!(b->val = ReadString ())) {
				FreeBox (b);
				return NULL;
			}
			b->xs = strlen (b->val) * FONTWIDTH;
			b->ys = FONTHEIGHT;
		} else if (buf == blok_pstr) {
			if (!(b = NewBox (BLOK))) return NULL;
			i = Qnum (&b->xs, 10);
			i &= Qnum (&b->ys, 10);
			if (!i) {
				fprintf (stderr, "Block needs X and Y sizes\n");
				return NULL;
			}
		} else {
			fprintf (stderr, "Unrecognized box type <%s>\n", buf);
			return NULL;
		}
	else if (tok == RT_CHR)
		switch (c) {
		    case '-':
			if (!(b = NewBox (HRULE))) return NULL;
			if (!Qnum (&b->ys, 10)) {
				fprintf (stderr, "Bad hrule structure.\n");
				return NULL;
			}
			b->col = def_bcol;
			Qnum (&b->col, 10);
			break;
		    case '|':
			if (!(b = NewBox (VRULE))) return NULL;
			if (!Qnum (&b->xs, 10)) {
				fprintf (stderr, "Bad vrule structure\n");
				return NULL;
			}
			b->col = def_bcol;
			Qnum (&b->col, 10);
			break;
		    default:
			fprintf (stderr, "Unrecognized box type <%c>\n", c);
			return NULL;
		}
	else {
		fprintf (stderr, "Unrecognized box type <%s>\n", buf);
		return NULL;
	}
	/*
	 * Pick up the closing ')'.
	 */
	tok = NextToken (&buf);
	if (tok != RT_CHR || *buf != ')') {
		fprintf (stderr, "Parse error - expected ')' !\n");
		FreeBox (b);
		return NULL;
	}

	/*
	 * Read the optional Gadget for this box (as ":id").
	 */
	b->gad = ReadOptGadget (b);
	return b;
}


/*
 * Read a list of boxes from the file stream.  Recursive: read a box,
 * then read a list. 
 */
struct Box * ReadBoxList ()
{
	struct Box      *b;

	b = ReadBox ();
	if (!b) return NULL;

	b->next = ReadBoxList ();
	return b;
}


/*
 * Create a new StringInfo struct and initialize to point to the 
 * given string buffer.  Allocates space for the buffer along with
 * the info struct itself (NEW_X).  Removes trailing spaces.
 */
APTR NewStrInfo (buf)
	char *buf;
{
	struct StringInfo	*si;
	short			i;
	char			*str;

	i = strlen (buf) + 1;
	if (!(si = NEW_X (struct StringInfo, i))) {
		MemError ();
		return NULL;
	}
	*si = generic_sinfo;
	si->Buffer = (UBYTE *) (str = (char *) (si+1));
	si->MaxChars = i;
	strcpy (str, buf);
	for (i -= 2; i>=0 && str[i] == ' '; i--) str[i] = 0;
	return (APTR) si;
}


/* Create new PropInfo struct.  Set the free motion flag based on the
 * id for this gadget "pv" = vert prop, "ph" = horiz prop, "p" = h+v prop.
 */
APTR NewPropInfo (id)
	char *id;
{
	register struct PropInfo	*pi;

	if (!(pi = NEW (struct PropInfo))) {
		MemError ();
		return NULL;
	}
	*pi = generic_pinfo;
	if (id == p_pstr || id == pv_pstr) pi->Flags |= FREEVERT;
	if (id == p_pstr || id == ph_pstr) pi->Flags |= FREEHORIZ;
	return (APTR) pi;
}


/*
 * Reads the list of gadget info from the end of the file.  Reads as much
 * as there is.  Format is:
 *	number {s|p|pv|ph} {:string} string
 * stuff in {}'s is optional.  Each entry gives extra info for the numbered
 * gadget.  {s|p} is string or prop flag.  {:string} is the optional named
 * value rather than just the nubmer.  The last string is the gadget flags.
 * Each set of info gets added to the corresponding gadget structure in
 * the main list.
 */
ReadGadInfo ()
{
	struct Gadget  *g;
	struct Box     *box;
	short           tok;
	char           *buf, c, *actf;
	short           i;
	USHORT		flag;

	while (Qnum (&i, 16)) {
		/*
		 * Locate the gadget in question and it's associated box.
		 */
		for (g = glst; g; g = g->NextGadget)
			if (g->GadgetID == i) break;
		if (!g) {
			fprintf (stderr, "Unknown gadget ID: %x\n", i);
			continue;
		}
		box = ((struct SuperGadget *) g)->box;

		/* Get the optional string or prop flag.
		 */
		tok = NextToken (&buf);
		if (tok == RT_ID) {
			if (buf == s_pstr) {
				g->GadgetType &= ~GADGETTYPEBITS;
				g->GadgetType |= STRGADGET;
				if (!(g->SpecialInfo = NewStrInfo (box->val)))
					return;
				box->val = NULL;
			} else if (buf == p_pstr
			    || buf == ph_pstr
			    || buf == pv_pstr) {
				g->GadgetType &= ~GADGETTYPEBITS;
				g->GadgetType |= PROPGADGET;
				if (!(g->SpecialInfo = NewPropInfo (buf)))
					return;
				if (!(g->GadgetRender =
				    (APTR) NEW (struct Image))) {
					MemError ();
					FREE (g->SpecialInfo, struct PropInfo);
					return;
				}
			} else {
				fprintf (stderr,
				    "Expected \"s\" or \"p\": <%s>\n", buf);
				break;
			}
			tok = NextToken (&buf);
		}

		/* Get optional gadget ID name string.
		 */
		if (tok == RT_CHR && *buf == ':') {
			((struct SuperGadget *) g)->gnam = ReadString ();
			tok = NextToken (&buf);
		}
		Backspace ();

		/* Get and process required activation flags string.
		 */
		actf = ReadString ();
		g->Activation &= ~RELVERIFY;
		for (; *actf; actf++) {
			switch (*actf) {
			    case 'B':
				g->Flags &= ~GADGHIGHBITS;
				g->Flags |= GADGHBOX;
				flag = 0;
				break;
			    case 't':
				flag = TOGGLESELECT;
				break;
			    case 'v':
				flag = RELVERIFY;
				break;
			    case 'e':
				flag = ENDGADGET;
				break;
			    case 'i':
				flag = GADGIMMEDIATE;
				break;
			    case 'c':
				flag = STRINGCENTER;
				break;
			    case 'f':
				flag = FOLLOWMOUSE;
				break;
			}
			g->Activation |= flag;
		}
	}
}


/*
 * Get values for the identifier strings from the lexical analyzer.
 * The lexer will return the same pointer for any identifier which
 * matches.
 */
AssignStrings ()
{
	fill_pstr = FindString ("f");
	hbox_pstr = FindString ("h");
	vbox_pstr = FindString ("v");
	tbox_pstr = FindString ("t");
	blok_pstr = FindString ("b");
	s_pstr = FindString ("s");
	p_pstr = FindString ("p");
	ph_pstr = FindString ("ph");
	pv_pstr = FindString ("pv");
}


/*
 * To read file: open, read base name, read optional default border and text
 * colors, read a box (a BIG box), read gadget info blocks, close.
 */
struct Box * ReadFile ()
{
	struct Box     *box;
	short           i, tok;
	char	       *buf;

	AssignStrings ();
	tok = NextToken (&base);
	if (tok != RT_ID) {
		fprintf (stderr, "Cannot find base name\n");
		return NULL;
	}
	Qnum (&def_bcol, 10);
	Qnum (&def_tcol, 10);

	if (infoLevel > 1) printf ("base name: \"%s\"\ndefault border color:"
		" %d\ndefault text color: %d\n", base, def_bcol, def_tcol);

	box = ReadBox ();
	ReadGadInfo ();

	/*
	 * Make sure we're at the end of the file to make the
	 * lexer happy.  Print up to 10 error messages unless there
	 * is no box from the previous call in which case there's something
	 * wrong anyway.  (Unless in verbose mode, then show 'em all.)
	 */
	i = ((box || infoLevel > 1) ? 10 : 0);
	while (NextToken (&buf) != RT_EOF) {
		if (i) {
			fprintf (stderr,
			    "Token found after end of data: <%s>\n", buf);
			if (!--i) fprintf (stderr, "... etc.\n");
		}
	}

	return box;
}


/*
 * ====  OUTPUT SECTION  ====
 *
 * Dumps structures created during the input and resolution phases of
 * the processing.  Just takes a pointer to a Requester in WriteRequester()
 * and dumps the related structures as well.
 */

/*
 * Write string info and buffer declarations from string gadgets
 * (if any).
 */
WriteStrGad (glist)
	struct Gadget		*glist;
{
	struct Gadget		*g;
	struct StringInfo	*si;
	int			i, n;

	/* Count number of string gadgets.
	 */
	for (n = 0, g = glist; g; g = g->NextGadget)
		if (GTYPE(g) == STRGADGET) n++;

	if (!n) return;

	/* Write the necessary buffers for the string infos.
	 */
	fprintf (file, "\n%sUBYTE %s_nbuf[%d][NUMCHR] = {\n\t",
		globStr, base, n);
	i = n;
	for (g = glist; g; g = g->NextGadget) {
		if (GTYPE(g) != STRGADGET) continue;

		si = (struct StringInfo *) g->SpecialInfo;
		fprintf (file, " \"%s\"", si->Buffer);
		if (--i) fprintf (file, ",");
	}

	fprintf (file, "\n};\n\n%sstruct StringInfo %s_sinfo[] = {\n",
		globStr, base);
	i = 0;
	for (g = glist; g; g = g->NextGadget) {
		if (GTYPE(g) != STRGADGET) continue;

		si = (struct StringInfo *) g->SpecialInfo;
		fprintf (file, "\t{&%s_nbuf[%d][0],undo,0,NUMCHR,0}",
			base, i++);
		if (--n) fprintf (file, ",");
		fprintf (file, "\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d StringInfo structs\n", i);
}


/*
 * Write prop info and image declarations for prop gadgets (if any).
 */
WritePropGad (glist)
	struct Gadget		*glist;
{
	struct Gadget		*g;
	struct PropInfo		*pi;
	int			i, n;

	/* Count number of prop gadgets.
	 */
	for (n = 0, g = glist; g; g = g->NextGadget)
		if (GTYPE(g) == PROPGADGET) n++;

	if (!n) return;

	/* Write the necessary images for the autoknobs.
	 */
	fprintf (file, "\n%sstruct Image %s_pimg[%d];\n", globStr, base, n);

	/* Write the PropInfo structures themselves.
	 */
	fprintf (file, "\n%sstruct PropInfo %s_pinfo[] = {\n", globStr, base);
	i = n;
	for (g = glist; g; g = g->NextGadget) {
		if (GTYPE(g) != PROPGADGET) continue;

		pi = (struct PropInfo *) g->SpecialInfo;
		fprintf (file, "\t{%u,%u,%u,%u,%u}", pi->Flags,
			pi->HorizPot, pi->VertPot,
			pi->HorizBody, pi->VertBody);
		if (--i) fprintf (file, ",");
		fprintf (file, "\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d PropInfo structs\n", n);
}


/*
 * Write the gadgets from the main gadget list.  Returns number of
 * gadgets written.
 */
int WriteGadgets (glist)
	struct Gadget	*glist;
{
	struct Gadget	*g;
	int		k = 1, nimg=0, nprp=0, nstr=0;
	char		*nam;

	if (!glist) return 0;

	WriteStrGad (glist);
	WritePropGad (glist);

	fprintf (file, "\n%sstruct Gadget %s_gad[] = {\n", globStr, base);

	for (g = glist; g; g = g->NextGadget) {
		if (g->NextGadget)
			fprintf (file, "\t{&%s_gad[%d]", base, k++);
		else
			fprintf (file, "\t{NULL");

		fprintf (file, ",%d,%d,%d,%d,%u,%u,%u,", g->LeftEdge,
			g->TopEdge, g->Width, g->Height, g->Flags,
			g->Activation, g->GadgetType);

		if (GTYPE(g) == PROPGADGET)
			fprintf (file, "(APTR)&%s_pimg[%d]", base, nimg++);
		else
			fprintf (file, "NULL");

		fprintf (file, ",\n\t NULL,NULL,0,(APTR)");

		if (GTYPE(g) == PROPGADGET)
			fprintf (file, "&%s_pinfo[%d]", base, nprp++);
		else if (GTYPE(g) == STRGADGET)
			fprintf (file, "&%s_sinfo[%d]", base, nstr++);
		else
			fprintf (file, "NULL");

		if (nam = ((struct SuperGadget *) g)->gnam)
			fprintf (file, ",%s", nam);
		else
			fprintf (file, ",0x%x", g->GadgetID);

		if (g->NextGadget)
			fprintf (file, "},\n");
		else
			fprintf (file, "}\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d Gadget structs\n", k);
	return k;
}


/*
 * Write out list of IntuiText structs for main list.  Returns number
 * of structures written.
 */
int WriteText (tlist)
	struct IntuiText	*tlist;
{
	struct IntuiText	*it;
	int			k = 1;

	if (!tlist) return 0;

	fprintf (file, "\n%sstruct IntuiText %s_txt[] = {\n", globStr, base);

	for (it = tlist; it; it = it->NextText) {
		fprintf (file, "\t{%d,%d,%d,%d,%d,&ta,(UBYTE*)\"%s\",",
			it->FrontPen, it->BackPen, it->DrawMode,
			it->LeftEdge, it->TopEdge, it->IText);
		if (it->NextText)
			fprintf (file, "&%s_txt[%d]},\n", base, k++);
		else
			fprintf (file, "NULL},\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d IntuiText structs\n", k);
	return k;
}


/*
 * Write out list of XY arrays from Border struct main list 
 */
WriteBorderXY (lst)
	struct Border *lst;
{
	register struct Border *b;
	register short  i;

	fprintf (file, "\n%sshort %s_brd_XY[] = {\n", globStr, base);
	for (b = lst; b; b = b->NextBorder) {
		fprintf (file, "\t");
		for (i = 0; i < b->Count; i++) {
			fprintf (file, "%d,%d", b->XY[i * 2], b->XY[i * 2 + 1]);
			if (i != b->Count - 1 || b->NextBorder)
				fprintf (file, ", ");
		}
		fprintf (file, "\n");
	}
	fprintf (file, "};\n");
}


/*
 * Write out list of Border structs from main list.  Returns nubmer of
 * structures written.
 */
int WriteBorder (lst)
	struct Border *lst;
{
	register struct Border *b;
	register short  i = 0, k = 1;

	if (!lst) return 0;

	WriteBorderXY (lst);

	fprintf (file, "\n%sstruct Border %s_brd[] = {\n", globStr, base);
	for (b = lst; b; b = b->NextBorder) {
		fprintf (file, "\t{0,0,%d,0,JAM1,%d,&%s_brd_XY[%d],",
			 b->FrontPen, b->Count, base, i);
		i += b->Count * 2;
		if (b->NextBorder)
			fprintf (file, "&%s_brd[%d]},\n", base, k++);
		else
			fprintf (file, "NULL}\n");
	}
	fprintf (file, "};\n");

	if (infoLevel > 1) printf ("wrote %d Border structs\n", k);
	return k;
}


/*
 * Reverse the gadget list so it will make more sense to the client.
 * This way they will appear in the arrays in the order that they 
 * appear in the description file.
 */
struct Gadget * ReverseGadList (head)
	struct Gadget *head;
{
	struct Gadget *newhead = NULL, *nxt;

	for (; head; head = nxt) {
		nxt = head->NextGadget;
		head->NextGadget = newhead;
		newhead = head;
	}
	return newhead;
}


/*
 * The main output function.
 */
WriteRequester (name, req)
	char             *name;
	struct Requester *req;
{
	short           i, ng, nt, nb;

	if (!(file = fopen (name, "w"))) {
		fprintf (stderr, "Can't open output file\n");
		return;
	}

	req->ReqGadget = ReverseGadList (req->ReqGadget);
	ng = WriteGadgets (req->ReqGadget);
	nt = WriteText (req->ReqText);
	nb = WriteBorder (req->ReqBorder);

	/*
	 * The requester itself.
	 */
	fprintf (file, "\n%sstruct Requester %s_req = {\n\
\tNULL,0,0,%d,%d,0,0,", globStr, base, req->Width, req->Height);
	if (ng) fprintf (file, "%s_gad,", base);
	else	fprintf (file, "NULL,");
	if (nb) fprintf (file, "%s_brd,", base);
	else	fprintf (file, "NULL,");
	if (nt) fprintf (file, "%s_txt,", base);
	else	fprintf (file, "NULL,");
	fprintf (file, "0,0,\n\tNULL,{NULL},NULL,NULL,{NULL}\n};\n");

	fclose (file);
}


MemError ()
{
	fprintf (stderr, "Out of memory.\n");
}


/* Main entry point.  Decode args and call body function.  Args are:
 *
 *	-p	: Print box description
 *	-q	: Run silent, run deep
 *	-v	: Verbose (not much different than normal, really)
 *	-d	: Display preview (is default unless output requested)
 *	-s	: Send Requester declarations to stdout
 */
main (argc, argv)
	int             argc;
	char           *argv[];
{
	int	i, junk = 0, prev = 0, tostdout = 0;
	char	*infile = NULL, *outfile = NULL;

	/*
	 * Decode arguments.
	 */
	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
			    case 'p':
				printBoxes = 1;
				break;
			    case 'q':
				infoLevel = 0;
				break;
			    case 'v':
				infoLevel = 2;
				break;
			    case 'd':
				prev = 1;
				break;
			    case 's':
				tostdout = 1;
				break;
			    case 'g':
				globStr = "";
				break;
			    default:
				junk = 1;
			}
		} else {
			if (!infile) infile = argv[i];
			  else if (!outfile) outfile = argv[i];
			  else junk = 1;
		}
	}
	if (junk || !infile) {
		printf ("Usage: %s [-p|q|v|d|s|g] <file> [<outfile>]\n",
			argv[0]);
		exit (1);
	}
	if (tostdout) {
		outfile = "*";
		infoLevel = 0;
	}
	showPreview = (!outfile || prev);

	if (IntuitionBase = (struct IntuitionBase *)
	    OpenLibrary ("intuition.library", 0L)) {
		Body (infile, outfile);
		CloseLibrary (IntuitionBase);
	}
}


Body (infile, outfile)
	char           *infile, *outfile;
{
	struct Window  *win;
	struct Box     *b;
	short           h, w;

	if (infoLevel > 0) printf (
		"Requester generator v2   Jan 1989  Stuart Ferguson\n");
	if (!OpenLexFile (infile)) {
		fprintf (stderr, "Cannot open %s\n", infile);
		return;
	}
	if (b = ReadFile ()) {
		Format (b);
		b->x = b->y = 0;
		Layout (b, b->xs, b->ys);
		if (printBoxes) PrintBox (b, 0);

		if (CreateIText (b)) {
			if (CreateBorder (b)) {
				MergeBorders ();
				MergeLinear ();
				PositionGadgets ();

				mreq.Width = b->xs;
				mreq.Height = b->ys;
				mreq.ReqGadget = glst;
				mreq.ReqText = itlst;
				mreq.ReqBorder = blst;

				if (showPreview) PreviewRequester (&mreq);
				if (outfile) WriteRequester (outfile, &mreq);

				FreeBorder ();
			}
			FreeIText ();
		}
		FreeBox (b);
	} else fprintf (stderr, "Error reading box description.\n");
	LexCleanup ();
}


/*
 * Open a window to preview the requester layout.
 */
PreviewRequester (req)
	struct Requester *req;
{
	struct Window  *win;
	short           h, w;

	w = req->Width + 12;
	h = req->Height + 16;
	if (w > 640 || h > 200) {
		fprintf (stderr, "Requester too large for preview.\n");
		return;
	}
	if (w < 150) w = 150;
	if (h < 60) h = 60;

	nwin.Width = w;
	nwin.Height = h;
	if (!(win = OpenWindow (&nwin))) {
		fprintf (stderr, "Unable to open preview window.\n");
		return;
	}

	req->LeftEdge =
		(w - win->BorderRight + win->BorderLeft - req->Width) / 2;
	req->TopEdge =
		(h - win->BorderBottom + win->BorderTop - req->Height) / 2;

	RequesterLoop (win, req);
	CloseWindow (win);
}


RequesterLoop (win, req)
	struct Window *win;
	struct Requester *req;
{
	struct Gadget *g;
	struct IntuiMessage *im;
	ULONG class, oldflags;
	USHORT code;
	int gend = 0, looping;

	/*
	 * Determine if this requester can be terminated with a gadget.
	 * If not, provide an alternate exit facility.
	 */
	for (g = req->ReqGadget; g; g = g->NextGadget)
		if (g->Activation & ENDGADGET) {
			gend = 1;
			break;
		}
	oldflags = req->Flags;
	if (!gend) {
		if (infoLevel > 0) printf (
			"No Endgadget -- Press ESC to exit Requester.\n");
		req->Flags |= NOISYREQ;
	}

	if (!Request (req, win)) {
		fprintf (stderr, "Unable to post Requester.\n");
		req->Flags = oldflags;
		return;
	}

	looping = 1;
	while (looping) {
		im = (struct IntuiMessage *) GetMsg(win->UserPort);
		if (!im) {
			WaitPort (win->UserPort);
			continue;
		}
		class = im->Class;
		code = im->Code;
		ReplyMsg (im);
		if (class == VANILLAKEY && code == 27) break;
		if (infoLevel > 0) printf ("Message : %s\n", IMClass (class));
		if (class == REQCLEAR) looping = 0;
	}
	if (looping) EndRequest (req, win);
	req->Flags = oldflags;
}


/*
 * Returns name of message class.  Lots more classes here than are
 * possible, but what the hell.
 */
char * IMClass (class)
	ULONG class;
{
	switch (class) {
	    case SIZEVERIFY:	return ("SIZEVERIFY");
	    case NEWSIZE:	return ("NEWSIZE");
	    case REFRESHWINDOW:	return ("REFRESHWINDOW");
	    case MOUSEBUTTONS:	return ("MOUSEBUTTONS");
	    case MOUSEMOVE:	return ("MOUSEMOVE");
	    case GADGETDOWN:	return ("GADGETDOWN");
	    case GADGETUP:	return ("GADGETUP");
	    case REQSET:	return ("REQSET");
	    case MENUPICK:	return ("MENUPICK");
	    case CLOSEWINDOW:	return ("CLOSEWINDOW");
	    case RAWKEY:	return ("RAWKEY");
	    case REQVERIFY:	return ("REQVERIFY");
	    case REQCLEAR:	return ("REQCLEAR");
	    case MENUVERIFY:	return ("MENUVERIFY");
	    case NEWPREFS:	return ("NEWPREFS");
	    case DISKINSERTED:	return ("DISKINSERTED");
	    case DISKREMOVED:	return ("DISKREMOVED");
	    case WBENCHMESSAGE:	return ("WBENCHMESSAGE");
	    case ACTIVEWINDOW:	return ("ACTIVEWINDOW");
	    case INACTIVEWINDOW:	return ("INACTIVEWINDOW");
	    case DELTAMOVE:	return ("DELTAMOVE");
	    case VANILLAKEY:	return ("VANILLAKEY");
	    case INTUITICKS:	return ("INTUITICKS");
	}
}


#define DEBUG

/*
 * Debug routines.
 */
#ifdef DEBUG

PrintBorder ()
{
	struct Border *b;
	short i;

	printf ("Borders:\n");
	for (b=blst; b; b=b->NextBorder) {
		printf ("%d %d %d %d\n:: ", b->LeftEdge,
			b->TopEdge, b->FrontPen, b->Count);
		for (i=0; i<b->Count; i++)
			printf ("%d,%d ", b->XY[i*2],b->XY[i*2+1]);
		printf ("\n");
	}
}


PrintGadget ()
{
	USHORT		typ;
	struct Gadget	*g;

	printf ("Gadgets:\n");
	for (g=glst; g; g=g->NextGadget) {
		printf ("%d,%d %d,%d  ", g->LeftEdge, g->TopEdge,
			g->Width, g->Height);
		typ = GTYPE(g);
		if (typ == PROPGADGET)	printf ("PROP");
		if (typ == STRGADGET)	printf ("STRING");
		if (typ == BOOLGADGET)	printf ("BOOL");
		printf ("\n");
	}
}
#endif
