/*****************************************************************************
 * OBJ_DRAW.C - A reentrant objc_draw() that is limited to certain ob_types.
 *
 *	This can do STRINGS, BUTTONS, BOXES, TEXT.
 *	It cannot do FTEXT, IMAGES, ICONS, or the newer 3D-style objects.
 *	It can render ob_states of SELECTED, OUTLINED, and DISABLED.
 *	It cannot do SHADOWED, CHECKED or CROSSED.
 *
 *	A special object type, G_RSTRING is also supported.  This is a string
 *	object that completely replaces an existing string; think of it as a
 *	bordeless opaque background-filled left-justified BOXTEXT object.
 *
 *	Other than the above limitations, it behaves just like objc_draw.  It
 *	exists primarily to support some of the new extended GemFast objects
 *	such as the text scroller and numeric slider.
 ****************************************************************************/

#include "gemfintl.h"

/*----------------------------------------------------------------------------
 * constants defining interior, border, and text colors for stock objects.
 *--------------------------------------------------------------------------*/

#define DEFAULT_COLORS		0x1180
#define OUTLINED_COLORS 	0x0000
#define SELECTED_COLORS 	0x0071
#define DISABLED_COLORS 	0x0040

static short sysfont_height = 0;

/*----------------------------------------------------------------------------
 * internal parm block, passed to all internal functions.
 *	 this is just a bunch of data that all the low-level functions need,
 *	 all glommed together in one structure so we can pass it around quickly.
 *	 (eat your heart out Jim, I still think this is a Good Idea. ::grin::)
 *--------------------------------------------------------------------------*/

typedef struct internal_parm_block {
	short	vhandle;	/* handle of VDI shared workstation.				*/
	GRECT	gdrawrect;	/* rectangle of object currently being rendered.	*/
	VRECT	vdrawrect;	/* same drawing rectangle, pre-converted to VRECT.	*/
	GRECT	gcliprect;	/* clipping rectangle specified in objc_draw() call.*/
	VRECT	vcliprect;	/* same clipping rectangle, pre-converted to VRECT. */
	short	obtype; 	/* current object's type, w/extended type stripped. */
	short	color;		/* current object's color word.                     */
	short	frame_width;/* current object's border width.                   */
	union { 			/* current object's ob_spec, after processing       */
	  long	   lval;	/* INDIRECT flag (if present).						*/
	  char	   *pstring;/*													*/
	  TEDINFO  *pted;	/*													*/
	  XUSERBLK *pub;
	}		obspec;
} IPB;

/*----------------------------------------------------------------------------
 * internal functions...
 *--------------------------------------------------------------------------*/

#ifdef GEMFAST_PROTOS

  INTERNAL_VFUNC draw_shadow(IPB * pb, short color, short width);
  INTERNAL_VFUNC draw_frame(IPB * pb, short color, short width);
  INTERNAL_VFUNC draw_interior(IPB * pb, short color);
  INTERNAL_VFUNC draw_text(IPB * pb, short color, short justify, short font, char *string);
  INTERNAL_VFUNC render_box(IPB * pb);
  INTERNAL_VFUNC render_string(IPB * pb);
  INTERNAL_VFUNC render_button(IPB * pb);
  INTERNAL_VFUNC render_text(IPB * pb);
  INTERNAL_IFUNC render_userdef(IPB * pb, OBJECT *ptree, short obj, short prevstate, short currstate);
  INTERNAL_VFUNC render_obstate(IPB *pb, short obstate);
  INTERNAL_VFUNC render_object(IPB *pb, OBJECT *ptree, short obj, short xoff, short yoff);
  INTERNAL_VFUNC render_tree(IPB *pb, OBJECT *ptree, short obj, short depth, short do_siblings, short xoff, short yoff);
  INTERNAL_VFUNC special_render_selected_state(OBJECT *ptree, short obj, GRECT *cliprect);

#endif

#if 0 /* de-implement this for now */

INTERNAL_VFUNC draw_shadow(pb, color, width)
	IPB *	pb;
	short	color;
	short	width;
/*----------------------------------------------------------------------------
 * draw a shadow under and to the right of an object.
 *	 this doesn't do AES-style shadows, it does real-looking ones. :-)
 *--------------------------------------------------------------------------*/
{
	short	pxy[12];
	short	x;
	short	y;
	short	hbox2;
	short	wbox2;
	short	medrez_kludge;

	wbox2 = gl_wbox / 2;
	hbox2 = gl_hbox / 2;

	x = pb->vdrawrect.v_x2;
	y = pb->vdrawrect.v_y2;

	if (width == 0) {
		return;
	} else if (width < 0) {
		x -= width;
		y -= width;
	}

	if (gl_rfscrn.g_w >= 640 && gl_rfscrn.g_h <= 200) {
		medrez_kludge = 3;	/* this adjusts for bogus medrez aspect ratio	*/
	} else {
		medrez_kludge = 0;
	}

	pxy[0]	=  pxy[2]  =  pb->vdrawrect.v_x1 + wbox2 / 2;
	pxy[1]	=  pxy[11] =  y;
	pxy[3]	=  pxy[5]  =  y + hbox2;
	pxy[4]	=  pxy[6]  =  x + wbox2 - medrez_kludge;
	pxy[7]	=  pxy[9]  =  pb->vdrawrect.v_y1 + hbox2 / 2;
	pxy[8]	=  pxy[10] =  x;

	color >>= 12;

	if (color != 1) {
		vsf_color(pb->vhandle, color);
	}

	vswr_mode(pb->vhandle, MD_TRANS);
	vsf_interior(pb->vhandle, IS_PATTERN);
	vsf_style(pb->vhandle, 4);
	vsf_perimeter(pb->vhandle, FALSE);

	v_fillarea(pb->vhandle, 6, pxy);	/* draw L-shaped shadow 			*/

	vswr_mode(pb->vhandle, MD_REPLACE);
	vsf_interior(pb->vhandle, IS_SOLID);
	vsf_style(pb->vhandle, 8);
	vsf_perimeter(pb->vhandle, TRUE);

	if (color != 1) {
		vsf_color(pb->vhandle, 1);
	}

}

#endif /* end of de-implemented shadow rendering */

INTERNAL_VFUNC draw_frame(pb, color, width)
	IPB *	pb;
	short	color;
	short	width;
/*----------------------------------------------------------------------------
 * draw a frame (ibox) of a given line width.
 *--------------------------------------------------------------------------*/
{
	short	pxy[10];
	short	adjust;
	short	direction;

	if (width == 0) {
		return;
	} else if (width < 0) {
		direction = 1;
		adjust	  = width;
	} else {
		direction = -1;
		adjust	  = width - 1;
	}

	color >>= 12;

	if (color != 1) {
		vsl_color(pb->vhandle, color);
	}

	while (width) {
		pxy[0] = pxy[6] = pxy[8] = pb->vdrawrect.v_x1 + adjust;
		pxy[1] = pxy[3] = pxy[9] = pb->vdrawrect.v_y1 + adjust;
		pxy[2] = pxy[4] =		   pb->vdrawrect.v_x2 - adjust;
		pxy[5] = pxy[7] =		   pb->vdrawrect.v_y2 - adjust;
		v_pline(pb->vhandle, 5, pxy);
		adjust += direction;
		width  += direction;
	}

	if (color != 1) {
		vsl_color(pb->vhandle, 1);
	}
}

INTERNAL_VFUNC draw_interior(pb, color)
	IPB *	pb;
	short	color;
/*----------------------------------------------------------------------------
 * draw the interior of a box.
 *--------------------------------------------------------------------------*/
{
	short style;

	style  = (color >> 4) & 0x0007;
	color &= 0x000F;

	if (color != 1) {
		vsf_color(pb->vhandle, color);
	}

	switch (style) {
	  case 0:
		vsf_interior(pb->vhandle, IS_HOLLOW);
		break;
	  case 7:
		break;	/* style 7 is the default already set on the workstation	*/
	  default:
		vsf_interior(pb->vhandle, IS_PATTERN);
		vsf_style(pb->vhandle, style);
		break;
	}

	vr_recfl(pb->vhandle, (short *)&pb->vdrawrect);

	if (color != 1) {
		vsf_color(pb->vhandle, 1);
	}

	if (style != 7) {
		vsf_interior(pb->vhandle, IS_SOLID);
		vsf_style(pb->vhandle, 8);
	}
}

INTERNAL_VFUNC draw_text(pb, color, justify, font, string)
	IPB *	pb;
	short	color;
	short	justify;
	short	font;
	char	*string;
/*----------------------------------------------------------------------------
 * draw some text; will clip text to object's boundaries.
 *--------------------------------------------------------------------------*/
{
	short	mode;
	short	wchar;
	short	hchar;
	short	textwidth;
	short	dmy;
	short	x;
	short	y;
	GRECT	gcliprect;
	VRECT	vcliprect;

	mode  = color & 0x0080;
	color = (color >> 8) & 0x000F;

	if (color != 1) {
		vst_color(pb->vhandle, color);
	}
	if (mode == 0) {
		vswr_mode(pb->vhandle, MD_TRANS);
	}
	if (font == TE_SMALL) {
		vst_height(pb->vhandle, 4, &wchar, &hchar, &dmy, &dmy);
	} else {
		wchar = gl_wchar;
		hchar = gl_hchar;
	}

	textwidth = wchar * (short)strlen(string);
	switch (justify) {
		case TE_LEFT:
			x = pb->gdrawrect.g_x;
			break;
		case TE_RIGHT:
			x = pb->gdrawrect.g_x + (pb->gdrawrect.g_w - textwidth);
			break;
		case TE_CNTR:
			x = pb->gdrawrect.g_x + ((pb->gdrawrect.g_w - textwidth) / 2);
			break;
	}
	y = pb->gdrawrect.g_y + ((pb->gdrawrect.g_h - hchar) / 2);
	vst_alignment(pb->vhandle, 0, 5, &dmy, &dmy);

	gcliprect = pb->gcliprect;
	if (rc_intersect(&pb->gdrawrect, &gcliprect)) {
		rc_gtov(&gcliprect, &vcliprect);
		vs_clip(pb->vhandle, TRUE, (short *)&vcliprect);
		v_gtext(pb->vhandle, x, y, string);
		vs_clip(pb->vhandle, TRUE, (short *)&pb->vcliprect);
	}

	vst_alignment(pb->vhandle, 0, 0, &dmy, &dmy);

	if (color != 1) {
		vst_color(pb->vhandle, 1);
	}
	if (mode == 0) {
		vswr_mode(pb->vhandle, MD_REPLACE);
	}
	if (font == TE_SMALL) {
		vst_height(pb->vhandle, sysfont_height, &dmy, &dmy, &dmy, &dmy);
	}
}

INTERNAL_VFUNC render_box(pb)
	IPB *	pb;
/*----------------------------------------------------------------------------
 * render a box, ibox, or boxchar object.
 *--------------------------------------------------------------------------*/
{
	char	str[2];

	if (pb->obtype != G_IBOX) {
		draw_interior(pb, pb->color);
	}

	if (pb->obtype == G_BOXCHAR) {
		str[0] = (char)(pb->obspec.lval >> 24);
		str[1] = 0;
		draw_text(pb, pb->color, TE_CNTR, TE_SYSTEM, str);
	}

	draw_frame(pb, pb->color, pb->frame_width);

}

INTERNAL_VFUNC render_string(pb)
	IPB * pb;
/*----------------------------------------------------------------------------
 * render a string or title object.
 *--------------------------------------------------------------------------*/
{
	if (pb->obtype == G_RSTRING) {			/* special object: replace string,*/
		draw_interior(pb, DEFAULT_COLORS);	/* it's like a borderless boxtext;*/
	}										/* erases existing string.		*/

	draw_text(pb, DEFAULT_COLORS, TE_LEFT, TE_SYSTEM, pb->obspec.pstring);
}

INTERNAL_VFUNC render_button(pb)
	IPB * pb;
/*----------------------------------------------------------------------------
 * render a button object.
 *--------------------------------------------------------------------------*/
{
	draw_interior(pb, DEFAULT_COLORS);
	draw_text(pb, DEFAULT_COLORS, TE_CNTR, TE_SYSTEM, pb->obspec.pstring);
	draw_frame(pb, DEFAULT_COLORS, pb->frame_width);
}

INTERNAL_VFUNC render_text(pb)
	IPB * pb;
/*----------------------------------------------------------------------------
 * render a text or boxtext (but not ftext or fboxtext) object.
 *--------------------------------------------------------------------------*/
{
	TEDINFO *pted = pb->obspec.pted;

	if (pb->obtype == G_BOXTEXT) {
		draw_interior(pb, pb->color);
	}

	draw_text(pb, pb->color, pted->te_just, pted->te_font, pted->te_ptext);

	if (pb->obtype == G_BOXTEXT) {
		draw_frame(pb, pb->color, pb->frame_width);
	}
}

INTERNAL_IFUNC render_userdef(pb, ptree, obj, prevstate, currstate)
	IPB *	pb;
	OBJECT *ptree;
	short	obj;
	short	prevstate;
	short	currstate;
/*----------------------------------------------------------------------------
 * render a userdef object, return unrendered-state info from user draw func.
 *--------------------------------------------------------------------------*/
{
	XPARMBLK	xpb;	/* a standard PARMBLK with sensibly-typed fields	*/

	xpb.ptree		= ptree;
	xpb.obj 		= obj;
	xpb.prevstate	= prevstate;
	xpb.currstate	= currstate;
	xpb.drawrect	= pb->gdrawrect;
	xpb.cliprect	= pb->gcliprect;
	xpb.pub 		= pb->obspec.pub;

	return (int)(*pb->obspec.pub->ub_draw)(&xpb);
}

INTERNAL_VFUNC render_obstate(pb, obstate)
	IPB *	pb;
	short	obstate;
/*----------------------------------------------------------------------------
 * render the object state effects.
 *--------------------------------------------------------------------------*/
{
	if (obstate & OUTLINED) {
		draw_frame(pb, DEFAULT_COLORS, -3);
		draw_frame(pb, OUTLINED_COLORS, -2);
	}

/*	if (obstate & SHADOWED) {												*/
/*	   draw_shadow(pb, pb->color, pb->frame_width); 						*/
/*	}																		*/

	if (obstate & SELECTED) {
		vswr_mode(pb->vhandle, MD_XOR);
		draw_interior(pb, SELECTED_COLORS);
		vswr_mode(pb->vhandle, MD_REPLACE);
	}

	if (obstate & DISABLED) {
		vswr_mode(pb->vhandle, MD_ERASE);
		draw_interior(pb, DISABLED_COLORS);
		vswr_mode(pb->vhandle, MD_REPLACE);
	}

	if (obstate & CHECKED) {	/* really should do this one next			*/
	}

	if (obstate & CROSSED) {	/* everyone's favorite state                */
	}

}

INTERNAL_VFUNC render_object(pb, ptree, obj, xoff, yoff)
	IPB *	pb;
	OBJECT *ptree;
	short	obj;
	short	xoff;
	short	yoff;
/*----------------------------------------------------------------------------
 * render a single object of any (currently supported) type.
 *--------------------------------------------------------------------------*/
{
	OBJECT	*pobj;
	short	obstate;

	pobj = &ptree[obj];
	obstate = pobj->ob_state;

	pb->gdrawrect.g_x = pobj->ob_x + xoff;
	pb->gdrawrect.g_y = pobj->ob_y + yoff;
	pb->gdrawrect.g_w = pobj->ob_width;
	pb->gdrawrect.g_h = pobj->ob_height;

	rc_gtov(&pb->gdrawrect, &pb->vdrawrect);

	if (pobj->ob_flags & INDIRECT) {
		pb->obspec.lval = *(long*)pobj->ob_spec;
	} else {
		pb->obspec.lval = (long)pobj->ob_spec;
	}

	pb->frame_width = 0;
	pb->color		= DEFAULT_COLORS;
	pb->obtype		= OBGTYPE(pobj);

	switch (pb->obtype) {
	  case G_BOX:
	  case G_IBOX:
	  case G_BOXCHAR:
		pb->frame_width = (char)(pb->obspec.lval >> 16);
		pb->color		= (short)(pb->obspec.lval & 0x0000FFFF);
		render_box(pb);
		break;
	  case G_TEXT:
	  case G_BOXTEXT:
		pb->frame_width = pb->obspec.pted->te_thickness;
		pb->color		= pb->obspec.pted->te_color;
		render_text(pb);
		break;
	  case G_BUTTON:
		pb->frame_width = -1;
		if (pobj->ob_flags & EXIT) {
			--pb->frame_width;
			if (pobj->ob_flags & DEFAULT) {
				--pb->frame_width;
			}
		}
		render_button(pb);
		break;
	  case G_TITLE:
	  case G_STRING:
	  case G_RSTRING:
		render_string(pb);
		break;
	  case G_USERDEF:
		obstate = render_userdef(pb, ptree, obj, obstate, obstate);
		break;
	}

	render_obstate(pb, obstate);
}

INTERNAL_VFUNC render_tree(pb, ptree, obj, depth, do_siblings, xoff, yoff)
	IPB *	pb;
	OBJECT *ptree;
	short	obj;
	short	depth;
	short	do_siblings;
	short	xoff;
	short	yoff;
/*----------------------------------------------------------------------------
 * render all objects in the tree/subtree, recursing to specified depth.
 *--------------------------------------------------------------------------*/
{
	OBJECT	*pobj;
	short	nxtobj;

	do	{
		pobj = &ptree[obj];
		if (!(pobj->ob_flags & HIDETREE)) {
			render_object(pb, ptree, obj, xoff, yoff);
			nxtobj = pobj->ob_head;
			if (depth > 1 && nxtobj != NO_OBJECT) {
				render_tree(pb, ptree, nxtobj, depth-1, TRUE,
							xoff+ptree[obj].ob_x, yoff+ptree[obj].ob_y);
			}
		}
		if (!do_siblings) {
			obj = NO_OBJECT;
		} else {
			nxtobj = pobj->ob_next;
			if (nxtobj == NO_OBJECT || ptree[nxtobj].ob_tail == obj) {
				obj = NO_OBJECT;
			} else {
				obj = nxtobj;
			}
		}
	} while (obj != NO_OBJECT);
}

INTERNAL_VFUNC special_render_selected_state(ptree, obj, cliprect)
	OBJECT *ptree;
	short	obj;
	GRECT *cliprect;
/*----------------------------------------------------------------------------
 * render a change in the SELECTED state.
 *	 this is a special speedup kludge for xob_change().  the state most
 *	 often changed is SELECTED, and for things like menu displays our
 *	 standard logic of erasing and redrawing the object is a bit slow; the
 *	 visual effect isn't very pleasing.  so, if the only state being
 *	 changed on an xob_change call is SELECTED, we come here and XOR the
 *	 object's interior quickly, instead of using the erase-and-redraw logic.
 *--------------------------------------------------------------------------*/
{
	IPB 	pb;

	if (0 == (pb.vhandle = apl_vshared())) {
		return;
	}

	obj_offxywh(ptree, obj, &pb.gdrawrect);
	rc_gtov(&pb.gdrawrect, &pb.vdrawrect);

	pb.gcliprect = *cliprect;

	if (rc_intersect(&gl_rfscrn, &pb.gcliprect)) {
		rc_gtov(&pb.gcliprect, &pb.vcliprect);
		v_hide_c(pb.vhandle);
		vs_clip(pb.vhandle, TRUE, (short *)&pb.vcliprect);
		vswr_mode(pb.vhandle, MD_XOR);
		draw_interior(&pb, SELECTED_COLORS);
		vswr_mode(pb.vhandle, MD_REPLACE);
		vs_clip(pb.vhandle, FALSE, (short *)&pb.vcliprect);
		v_show_c(pb.vhandle, TRUE);
	}

}

/*----------------------------------------------------------------------------
 * public functions...
 *--------------------------------------------------------------------------*/

void xob_offset(ptree, obj, px, py)
	OBJECT *ptree;
	short	obj;
	short * px;
	short * py;
/*****************************************************************************
 * return screen-adjusted object coordinates.
 ****************************************************************************/
{
	short	x = 0;
	short	y = 0;

	while (obj != ROOT) {
		x  += ptree[obj].ob_x;
		y  += ptree[obj].ob_y;
		obj = obj_parent(ptree, obj);
	}

	*px = x + ptree->ob_x;;
	*py = y + ptree->ob_y;

	return;
}

void xob_draw(ptree, obj, depth, cliprect)
	OBJECT *ptree;
	short	obj;
	short	depth;
	GRECT * cliprect;
/*****************************************************************************
 * draw object tree, as per AES function objc_draw().
 ****************************************************************************/
{
	IPB 	pb;
	short	xoff, yoff;

	if (0 == (pb.vhandle = apl_vshared())) {
		return;
	}

	if (sysfont_height == 0) {
		short attr[10];
		vqt_attributes(pb.vhandle, attr);
		sysfont_height = attr[7];
	}

	pb.gcliprect = *cliprect;

	if (obj == ROOT) {
		xoff = yoff = 0;
	} else {
		xob_offset(ptree, obj_parent(ptree, obj), &xoff, &yoff);
	}

	if (rc_intersect(&gl_rfscrn, &pb.gcliprect)) {
		rc_gtov(&pb.gcliprect, &pb.vcliprect);
		v_hide_c(pb.vhandle);
		vs_clip(pb.vhandle, TRUE, (short *)&pb.vcliprect);
		render_tree(&pb, ptree, obj, depth, FALSE, xoff, yoff);
		vs_clip(pb.vhandle, FALSE, (short *)&pb.vcliprect);
		v_show_c(pb.vhandle, TRUE);
	}

}

void xob_change(ptree, obj, cliprect, newstate, redraw)
	OBJECT *ptree;
	short	obj;
	GRECT * cliprect;
	short	newstate;
	short	redraw;
/*****************************************************************************
 * change an object's ob_state, with optional redraw.
 *	this does NOT work like the AES's equivelent function.  this one
 *	always redraws the object in its entirety.	in addition, the redraw
 *	starts with the object's parent and is clipped such that the bits
 *	outside the object (ie, OUTLINED and SHADOWED) are properly redrawn.
 *
 *	if the only state being changed is SELECTED, we use special logic to
 *	quickly XOR the object's interior instead of using the erase-and-redraw
 *	logic.	this gives a better visual effect on things like menus where
 *	the SELECTED state is changed rapidly while tracking the mouse.
 ****************************************************************************/
{
	short	oldstate;
	GRECT	userclip;
	GRECT	objclip;

	oldstate = ptree[obj].ob_state;
	ptree[obj].ob_state = newstate;

	if (!redraw || oldstate == newstate) {
		return;
	}

	if ((oldstate ^ newstate) == SELECTED) {
		special_render_selected_state(ptree, obj, cliprect);
	} else {
		userclip = *cliprect;
		obj_clcalc(ptree, obj, &objclip, NULL);
		if (rc_intersect(&userclip, &objclip)) {
			obj = obj_parent(ptree, obj);
			objc_draw(ptree, obj, MAX_DEPTH, RECTVALS(&objclip));
		}
	}
}


