#include "portab.h"				/* portable coding conv	*/
#include "machine.h"				/* machine depndnt conv	*/
#include "obdefs.h"				/* object definitions	*/
#include "gembind.h"				/* gem binding structs	*/
#include "taddr.h"

#define	M1_ENTER	0x0000
#define	M1_EXIT		0x0001

#define BS	0x0008
#define	TAB	0x0009
#define	CR	0x000D
#define ESC	0x001B
#define	BTAB	0x0f00
#define	UP	0x4800
#define	DOWN	0x5000
#define	DEL	0x5300
					/* Global variables used by */
					/* 'mapped' functions	    */
MLOCAL	GRECT	br_rect;		/* Current break rectangle  */
MLOCAL	WORD	br_mx, br_my, br_togl;	/* Break mouse posn & flag  */ 
MLOCAL	WORD	fn_obj;			/* Found tabable object	    */
MLOCAL	WORD	fn_last;		/* Object tabbing from	    */
MLOCAL	WORD	fn_prev;		/* Last EDITABLE obj seen   */
MLOCAL	WORD	fn_dir;			/* 1 = TAB, 0 = BACKTAB	    */

/************* Utility routines for new forms manager ***************/

	VOID
objc_toggle(tree, obj)			/* Reverse the SELECT state */
	LONG	tree;			/* of an object, and redraw */
	WORD	obj;			/* it immediately.	    */
	{
	WORD	state, newstate;
	GRECT	root, ob_rect;

	objc_xywh(tree, ROOT, &root);
	state = LWGET(OB_STATE(obj));
	newstate = state ^ SELECTED;
	objc_change(tree, obj, 0, root.g_x, root.g_y, 
		root.g_w, root.g_h, newstate, 1);
	}

	VOID				/* If the object is not already */
objc_sel(tree, obj)			/* SELECTED, make it so.	*/
	LONG	tree;
	WORD	obj;
	{
	if ( !(LWGET(OB_STATE(obj)) & SELECTED) )
		objc_toggle(tree, obj);
	}

	VOID				/* If the object is SELECTED,	*/
objc_dsel(tree, obj)			/* deselect it.			*/
	LONG	tree;
	WORD	obj;
	{
	if (LWGET(OB_STATE(obj)) & SELECTED)
		objc_toggle(tree, obj);
	}

	VOID				/* Return the object's GRECT  	*/
objc_xywh(tree, obj, p)			/* through 'p'			*/
	LONG	tree;
	WORD	obj;
	GRECT	*p;
	{
	objc_offset(tree, obj, &p->g_x, &p->g_y);
	p->g_w = LWGET(OB_WIDTH(obj));
	p->g_h = LWGET(OB_HEIGHT(obj));
	}

	VOID				/* Non-cursive traverse of an	*/
map_tree(tree, this, last, routine)	/* object tree.  This routine	*/
	LONG		tree;		/* is described in PRO GEM #5.	*/
	WORD		this, last;
	WORD		(*routine)();
	{
	WORD		tmp1;

	tmp1 = this;		/* Initialize to impossible value: */
				/* TAIL won't point to self!	   */
				/* Look until final node, or off   */
				/* the end of tree		   */ 
	while (this != last && this != NIL)
				/* Did we 'pop' into this node	   */
				/* for the second time?		   */
		if (LWGET(OB_TAIL(this)) != tmp1)
			{
			tmp1 = this;	/* This is a new node       */
			this = NIL;
					/* Apply operation, testing  */
					/* for rejection of sub-tree */
			if ((*routine)(tree, tmp1))
				this = LWGET(OB_HEAD(tmp1));
					/* Subtree path not taken,   */
					/* so traverse right	     */	
			if (this == NIL)
				this = LWGET(OB_NEXT(tmp1));
			}
		else			/* Revisiting parent: 	     */
					/* No operation, move right  */
			{
			tmp1 = this;
			this = LWGET(OB_NEXT(tmp1));
			}
	}

	WORD				/* Find the parent object of 	*/
get_parent(tree, obj)			/* by traversing right until	*/
	LONG		tree;		/* we find nodes whose NEXT	*/
	WORD		obj;		/* and TAIL links point to 	*/
	{				/* each other.			*/
	WORD		pobj;

	if (obj == NIL)
		return (NIL);
	pobj = LWGET(OB_NEXT(obj));
	if (pobj != NIL)
	{
	  while( LWGET(OB_TAIL(pobj)) != obj ) 
	  {
	    obj = pobj;
	    pobj = LWGET(OB_NEXT(obj));
	  }
	}
	return(pobj);
	} 

	WORD
inside(x, y, pt)		/* determine if x,y is in rectangle	*/
	WORD		x, y;
	GRECT		*pt;
	{
	if ( (x >= pt->g_x) && (y >= pt->g_y) &&
	    (x < pt->g_x + pt->g_w) && (y < pt->g_y + pt->g_h) )
		return(TRUE);
	else
		return(FALSE);
	} 

	WORD
rc_intersect(p1, p2)		/* compute intersection of two GRECTs	*/
	GRECT		*p1, *p2;
	{
	WORD		tx, ty, tw, th;

	tw = min(p2->g_x + p2->g_w, p1->g_x + p1->g_w);
	th = min(p2->g_y + p2->g_h, p1->g_y + p1->g_h);
	tx = max(p2->g_x, p1->g_x);
	ty = max(p2->g_y, p1->g_y);
	p2->g_x = tx;
	p2->g_y = ty;
	p2->g_w = tw - tx;
	p2->g_h = th - ty;
	return( (tw > tx) && (th > ty) );
	}

	VOID
rc_copy(psbox, pdbox)		/* copy source to destination rectangle	*/
	GRECT	*psbox;
	GRECT	*pdbox;
	{
	pdbox->g_x = psbox->g_x;
	pdbox->g_y = psbox->g_y;
	pdbox->g_w = psbox->g_w;
	pdbox->g_h = psbox->g_h;
	}

/************* "Hot-spot" manager and subroutines  ***************/

	WORD
break_x(pxy)
	WORD	*pxy;
	{				/* Breaking object is right of	*/
	if (br_mx < pxy[0])		/* mouse.  Reduce width of 	*/
		{			/* bounding rectangle.		*/
		br_rect.g_w = pxy[0] - br_rect.g_x;
		return (TRUE);
		}
	if (br_mx > pxy[2])		/* Object to left.  Reduce width*/
		{			/* and move rect. to right	*/
		br_rect.g_w += br_rect.g_x - pxy[2] - 1;
		br_rect.g_x = pxy[2] + 1;
		return (TRUE);
		}
	return (FALSE);			/* Mouse within object segment.	*/
	}				/* Break attempt fails.		*/

	WORD
break_y(pxy)
	WORD	*pxy;
	{
	if (br_my < pxy[1])		/* Object below mouse.  Reduce	*/
		{			/* height of bounding rect.	*/
		br_rect.g_h = pxy[1] - br_rect.g_y;
		return (TRUE);
		}
	if (br_my > pxy[3])		/* Object above mouse.  Reduce	*/
		{			/* height and shift downward.	*/
		br_rect.g_h += br_rect.g_y - pxy[3] - 1;
		br_rect.g_y = pxy[3] + 1;
		return (TRUE); 
		}
	/* Emergency escape test! Protection vs. turkeys who nest */
	/* non-selectable objects inside of selectables.          */
	if (br_mx >= pxy[0] && br_mx <= pxy[1])
		{				/* Will X break fail?	  */
		br_rect.g_x = br_mx;		/* If so, punt!		  */
		br_rect.g_y = br_my;
		br_rect.g_w = br_rect.g_h = 1;
		return (TRUE);
		}
	return (FALSE);
	}

	WORD
break_obj(tree, obj)			/* Called once per object to	*/
	LONG	tree;			/* check if the bounding rect.	*/
	WORD	obj;			/* needs to be modified.	*/
	{
	GRECT	s;
	WORD	flags, broken, pxy[4];

	objc_xywh(tree, obj, &s);
	grect_to_array(&s, pxy);
	if (!rc_intersect(&br_rect, &s))
		return (FALSE);		/* Trivial rejection case 	*/

	flags = LWGET(OB_FLAGS(obj));	/* Is this object a potential	*/
	if (flags & HIDETREE)		/* hot-spot?		     	*/
		return (FALSE);
	if ( !(flags & SELECTABLE) )
		return (TRUE);
	if (LWGET(OB_STATE(obj)) & DISABLED)
		return (TRUE);

	for (broken = FALSE; !broken; ) /* This could take two passes 	*/
		{			/* if the first break fails.   	*/
		if (br_togl)
			broken = break_x(pxy);
		else
			broken = break_y(pxy);
		br_togl = !br_togl;
		}
	return (TRUE);
	}

	WORD				/* Manages mouse rectangle events */
form_hot(tree, hot_obj, mx, my, rect, mode)
	LONG	tree;
	WORD	hot_obj, mx, my, *mode;
	GRECT	*rect;
	{
	GRECT	root;
	WORD	state;

	objc_xywh(tree, ROOT, &root);	/* If there is already a hot-spot */
	if (hot_obj != NIL)		/* turn it off.			  */
		objc_toggle(tree, hot_obj);

	if (!(inside(mx, my, &root)) )	/* Mouse has moved outside of 	  */
		{			/* the dialog.  Wait for return.  */
		*mode = M1_ENTER;
		rc_copy(&root, rect);
		return (NIL);
		}
					/* What object is mouse over?	  */
					/* (Hit is guaranteed.)           */
	hot_obj = objc_find(tree, ROOT, MAX_DEPTH, mx, my);
					/* Is this object a hot-spot?	  */
	state = LWGET(OB_STATE(hot_obj));
	if (LWGET(OB_FLAGS(hot_obj)) & SELECTABLE)
	if ( !(state & DISABLED) )
		{			/* Yes!  Set up wait state.	  */
		*mode = M1_EXIT;
		objc_xywh(tree, hot_obj, rect);
		if (state & SELECTED)	/* But only toggle if it's not	  */
			return (NIL);	/* already SELECTED!		  */
		else
			{
			objc_toggle(tree, hot_obj);
			return (hot_obj);
			}
		}

	rc_copy(&root, &br_rect);	/* No hot object, so compute	*/
	br_mx = mx;			/* mouse bounding rectangle.	*/
	br_my = my;
	br_togl = 0;
	map_tree(tree, ROOT, NIL, break_obj);
	rc_copy(&br_rect, rect);	/* Then return to wait state.	*/
	*mode = M1_EXIT;
	return (NIL);
	}

/************* Keyboard manager and subroutines ***************/

	WORD
find_def(tree, obj)		/* Check if the object is DEFAULT	*/
	LONG	tree;
	WORD	obj;
	{			/* Is sub-tree hidden?			*/
	if (HIDETREE & LWGET(OB_FLAGS(obj)))
		return (FALSE);
				/* Must be DEFAULT and not DISABLED	*/
	if (DEFAULT & LWGET(OB_FLAGS(obj)))
	if ( !(DISABLED & LWGET(OB_STATE(obj))) )
		fn_obj = obj;	/* Record object number			*/
	return (TRUE);
	}

	WORD
find_tab(tree, obj)		/* Look for target of TAB operation.	*/
	LONG	tree;
	WORD	obj;
	{			/* Check for hiddens subtree.		*/
	if (HIDETREE & LWGET(OB_FLAGS(obj)))
		return (FALSE);
				/* If not EDITABLE, who cares?		*/
	if ( !(EDITABLE & LWGET(OB_FLAGS(obj))) )
		return (TRUE);
				/* Check for forward tab match		*/
	if (fn_dir && fn_prev == fn_last)
		fn_obj = obj;
				/* Check for backward tab match		*/
	if (!fn_dir && obj == fn_last)
		fn_obj = fn_prev;
	fn_prev = obj;		/* Record object for next call.		*/
	return (TRUE);
	}	

	WORD
form_keybd(tree, edit_obj, next_obj, kr, out_obj, okr)
	LONG	tree;
	WORD	edit_obj, next_obj, kr, *out_obj, *okr;
	{
	if (LLOBT(kr))		/* If lower byte valid, mask out	*/
		kr &= 0xff;	/* extended code byte.			*/
	fn_dir = 0;		/* Default tab direction if backward.	*/
	switch (kr) {
		case CR:	/* Zap character.			*/
			*okr = 0;
				/* Look for a DEFAULT object.		*/
			fn_obj = NIL;
			map_tree(tree, ROOT, NIL, find_def);
				/* If found, SELECT and force exit.	*/
			if (fn_obj != NIL)
				{
				objc_sel(tree, fn_obj);
				*out_obj = fn_obj;
				return (FALSE);
				}		/* Falls through to 	*/ 
		case TAB:			/* tab if no default 	*/
		case DOWN:	
			fn_dir = 1;		/* Set fwd direction 	*/
		case BTAB:
		case UP:
			*okr = 0;		/* Zap character	*/
			fn_last = edit_obj;
			fn_prev = fn_obj = NIL; /* Look for TAB object	*/
			map_tree(tree, ROOT, NIL, find_tab);
			if (fn_obj == NIL)	/* try to wrap around 	*/
				map_tree(tree, ROOT, NIL, find_tab);
			if (fn_obj != NIL)
				*out_obj = fn_obj;
			break;
		default:			/* Pass other chars	*/
			return (TRUE);
		}
	return (TRUE);
	}

/************* Mouse button manager and subroutines ***************/

	WORD
do_radio(tree, obj)
	LONG	tree;
	WORD	obj;
	{
	GRECT	root;
	WORD	pobj, sobj, state;

	objc_xywh(tree, ROOT, &root);
	pobj = get_parent(tree, obj);		/* Get the object's parent */

	for (sobj = LWGET(OB_HEAD(pobj)); sobj != pobj;
		sobj = LWGET(OB_NEXT(sobj)) )
		{				/* Deselect all but...	   */
		if (sobj != obj)
			objc_dsel(tree, sobj);
		}
	objc_sel(tree, obj);			/* the one being SELECTED  */
	}

	WORD					/* Mouse button handler	   */
form_button(tree, obj, clicks, next_obj, hot_obj)
	LONG	tree;
	WORD	obj, clicks, *next_obj, *hot_obj;
	{
	WORD	flags, state, hibit, texit, sble, dsbld, edit;
	WORD	in_out, in_state;

	flags = LWGET(OB_FLAGS(obj));		/* Get flags and states   */
	state = LWGET(OB_STATE(obj));
	texit = flags & TOUCHEXIT;
	sble = flags & SELECTABLE;
	dsbld = state & DISABLED;
	edit = flags & EDITABLE;

	if (!texit && (!sble || dsbld) && !edit) /* This is not an  	*/
		{				 /* interesting object	*/
		*next_obj = 0;
		return (TRUE);
		}

	if (texit && clicks == 2)		/* Preset special flag	*/
		hibit = 0x8000;
	else
		hibit = 0x0;

	if (sble && !dsbld)			/* Hot stuff!		*/
		{
		if (flags & RBUTTON)		/* Process radio buttons*/
			do_radio(tree, obj);	/* immediately!		*/ 
		else if (!texit)
			{
			in_state = (obj == *hot_obj)?	/* Already toggled ? */
				state: state ^ SELECTED;	
			if (!graf_watchbox(tree, obj, in_state, 
				in_state ^ SELECTED))
				{			/* He gave up...  */
				*next_obj = 0;
				*hot_obj = NIL;
				return (TRUE);
				}
			}
		else /* if (texit) */
			if (obj != *hot_obj)	/* Force SELECTED	*/
				objc_toggle(tree, obj);
		}

	if (obj == *hot_obj)		/* We're gonna do it! So don't	*/
		*hot_obj = NIL;		/* turn it off later.		*/

	if (texit || (flags & EXIT) )	/* Exit conditions.		*/
		{
		*next_obj = obj | hibit;
		return (FALSE);		/* Time to leave!		*/
		}
	else if (!edit)			/* Clear object unless tabbing	*/
		*next_obj = 0;

	return (TRUE);
	}

/************* New forms manager: Entry point and main loop *************/

	WORD
form_do(tree, start_fld)
	REG LONG	tree;
	WORD		*start_fld;
	{
	REG WORD	edit_obj;
	WORD		next_obj, hot_obj, hot_mode;
	WORD		which, cont;
	WORD		idx;
	WORD		mx, my, mb, ks, kr, br;
	GRECT		hot_rect;
	WORD		(*valid)();
						/* Init. editing	*/
	next_obj = *start_fld;
	edit_obj = 0;
						/* Initial hotspot cndx */
	hot_obj = NIL; hot_mode = M1_ENTER;
	objc_xywh(tree, ROOT, &hot_rect);
						/* Main event loop	*/
	cont = TRUE;
	while (cont)
	  {
						/* position cursor on	*/
						/*   the selected 	*/
						/*   editting field	*/
	  if (edit_obj != next_obj)
	  if (next_obj != 0)
	  	{
	    	edit_obj = next_obj;
	    	next_obj = 0;
	    	objc_edit(tree, edit_obj, 0, &idx, EDINIT);
	  	}
						/* wait for button or   */
						/* key or rectangle	*/
	  which = evnt_multi(MU_KEYBD | MU_BUTTON | MU_M1, 
			0x02, 0x01, 0x01,
			hot_mode, hot_rect.g_x, hot_rect.g_y, 
				hot_rect.g_w, hot_rect.g_h, 
			0, 0, 0, 0, 0,
			0x0L,
			0, 0,
			&mx, &my, &mb, &ks, &kr, &br);

	  if (which & MU_M1)			/* handle rect. event 	*/
	  	hot_obj = form_hot(tree, hot_obj, mx, my, &hot_rect, &hot_mode);
						/* handle keyboard event*/
	  if (which & MU_KEYBD)
	  	{				/* Control char filter	*/
	    	cont = form_keybd(tree, edit_obj, next_obj, kr, &next_obj, &kr);
	    	if (kr && edit_obj)		/* Add others to object	*/
			objc_edit(tree, edit_obj, kr, &idx, EDCHAR);
	  	}
						/* handle button event	*/
	  if (which & MU_BUTTON)
	  	{				/* Which object hit?	*/
	    	next_obj = objc_find(tree, ROOT, MAX_DEPTH, mx, my);
 	    	if (next_obj == NIL)
		      	next_obj = 0;
	    	else				/* Process a click	*/
	    		cont = form_button(tree, next_obj, br, 
				&next_obj, &hot_obj);
	  	}
						/* handle end of field	*/
						/*   clean up		*/
	  if (!cont || (next_obj != edit_obj && next_obj != 0))
	  if (edit_obj != 0) 
	  	objc_edit(tree, edit_obj, 0, &idx, EDEND);
	  }
						/* If defaulted, may	*/
						/* need to clear hotspot*/
	if (hot_obj != (next_obj & 0x7fff))
	if (hot_obj != NIL)
		objc_toggle(tree, hot_obj);
						/* return exit object	*/
						/*   hi bit may be set	*/
						/*   if exit obj. was	*/
						/*   double-clicked	*/
	*start_fld = edit_obj;
	return(next_obj);
	}
