/*
 *
 *	customstatus.c
 *
 *	Routines for drawing the Custom Status View.
 *
 *	HNMS User Interface
 *	Version 2.0
 *
 *	February 1994
 *
 *	Leslie Schlecht
 *	Computer Sciences Corporation
 *	Numerical Aerodynamic Simulation Systems Division
 *	NASA Ames Research Center
 *
 *	Copyright (c) 1994 Leslie Schlecht
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 1, or (at your option)
 *	any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<math.h>

#include	<X11/StringDefs.h>
#include	<X11/Intrinsic.h>
#include	<Xm/Xm.h>
#include	<Xm/DrawingA.h>
#include	<Xm/Form.h>
#include	<Xm/Text.h>
#include	<Xm/ToggleB.h>

#include	"defines.h"
#include	"externs.h"
#include	"viewpanel.h"
#include	"view.h"
#include	"xsupport.h"
#include	"customstatus.h"

#define		RADIUS		7
#define		BLOCK		1
#define		LINE		2
#define		DOT		3
#define		ROUTE		labellen

typedef		struct	reptype	{
		int		class, color;
		float		x0, y0, x1, y1, r;
		char		*label;
		int		labellen;
		caddr_t		ctobj;
		} REPTYPE;

typedef		struct	custype	{
		caddr_t		obj;
		REPTYPE		*rep;
		int		class;
		struct custype	*ipparent;
		struct custype	*physparent;
		int		status, ifnumber;
		int		org;
		char		*name;
		} CUSTYPE;

typedef		struct customdata	{
		caddr_t		objlist;
		caddr_t		replist;
		caddr_t		routevar;
		Widget		da, route_text, object_text, route_onoff;
		char		*routedest;
		float		xmin, xmax, ymin, ymax;
		float		xscreen, yscreen, xscale, yscale;
		float		xmargin, ymargin;
		int		width, height;
		int		nprocs, nsubs;
		int		show_route;
		int		font, sublbllen, proclbllen, angledlabel;
		CUSTYPE		*selected;
		} CUSTOMDATA;

static caddr_t	reachvar, indexvar;
static char	*routestr = "ipRouteIfIndex.";
static int	expose = False;
static int	count=0, down=0;


/*
 *	Create a new custom status object.
 */
int
AddCustomStatus(view, obj, class, name, org)
VIEW	*view;
caddr_t	obj;
int	class;
char	*name;
int	org;
	{
	CUSTOMDATA	*cd;
	CUSTYPE		*ct;
	VIEWPANEL	*panel;
	
	panel = view->panel;
	cd = (CUSTOMDATA*)panel->wa;
	/* go away if it already exists */
	if (FindEntry(cd->objlist, CompareCustomStatusObject, obj, NULL, &ct))
		return(0);
	ct = (CUSTYPE*)myalloc(NULL, 1, sizeof(CUSTYPE));
	ct->name = name;
	ct->class = class;
	ct->obj = obj;
	ct->org = org;
	ct->status = STATUS_UNKNOWN;
	/* add object sorted by name */
	AddEntry(&(cd->objlist), ct, CompareCustomStatusObjectByName, 0);
	switch (class) {
	case OBJ_subnet:
		/* get the current reach status */
		Q_RequestObjectVariable(obj, view, reachvar);
		cd->nsubs ++;
		break;
	case OBJ_processor:
		/* get the current reach status and route dest */
		Q_RequestObjectVariable(obj, view, reachvar);
		if (cd->routevar)
			Q_RequestObjectVariable(obj, view, cd->routevar);
		cd->nprocs ++;
		break;
	case OBJ_ipaddr:
		/* get the current reach status and if index */
		Q_RequestObjectVariable(obj, view, reachvar);
		Q_RequestObjectVariable(obj, view, indexvar);
		break;
	default:;
		}
	return(1);
	}


/*
 *	Add an object to a custom status view.
 */
void
AddCustomStatusChild(view, obj, class, name, rel)
VIEW	*view;
caddr_t	obj;
int	class;
char	*name;
int	rel;
	{
	if (!AddCustomStatus(view, obj, class, name, 1)) return;
	switch (class) {
	case OBJ_subnet:
		/* get all ipaddresses in subnet */
		AddObjectChildToView(obj, view, OBJ_ipaddr, IPPARENT);
		break;
	case OBJ_processor:
		/* get all ipaddresses in processor */
		AddObjectChildToView(obj, view, OBJ_ipaddr, PHYSPARENT);
		break;
	case OBJ_ipaddr:
		/* add secondary ipaddress parent processor or subnet */
		if (rel == IPPARENT)
			AddObjectParentToView(obj, view, PHYSPARENT);
		else if (rel == PHYSPARENT)
			AddObjectParentToView(obj, view, IPPARENT);
		break;
	default:;
		}
	}


/*
 *	Add an object to a custom status view.
 */
void
AddCustomStatusObject(view, obj, class, name)
VIEW	*view;
caddr_t	obj;
int	class;
char	*name;
	{
	if (!AddCustomStatus(view, obj, class, name, 0)) return;
	switch (class) {
	case OBJ_network:
		/* get all subnets in network */
		AddObjectChildToView(obj, view, OBJ_subnet, IPPARENT);
		break;
	case OBJ_site:
		/* get all processors in site */
		AddObjectChildToView(obj, view, OBJ_processor, PHYSPARENT);
		break;
	case OBJ_subnet:
		/* get all ipaddresses in subnet */
		AddObjectChildToView(obj, view, OBJ_ipaddr, IPPARENT);
		break;
	case OBJ_processor:
		/* get all ipaddresses in processor */
		AddObjectChildToView(obj, view, OBJ_ipaddr, PHYSPARENT);
		break;
	default:;
		}
	}


/*
 *	Add a custom status parent object.
 */
void
AddCustomStatusParent(view, obj, class, name)
VIEW	*view;
caddr_t	obj;
int	class;
char	*name;
	{
	AddCustomStatus(view, obj, class, name, 2);
	}


/*
 *	Clear a Custom Status view of all objects and redraw.
 */
void
ClearCustomStatus(view)
VIEW	*view;
	{
	CUSTOMDATA	*cd;
	caddr_t		oe;
	CUSTYPE		*ct;
	
	cd = (CUSTOMDATA*)(view->panel->wa);
	oe = cd->objlist;
	while (NextEntry(&oe, &ct))
		RemoveViewFromObject(ct->obj, view);
	RemoveEntryList(cd->objlist, free);
	cd->objlist = NULL;
	RemoveEntryList(cd->replist, free);
	cd->replist = NULL;
	cd->nprocs = 0;
	cd->nsubs = 0;
	cd->xscale = 1;
	cd->yscale = 1;
	cd->xmargin = 0;
	cd->ymargin = 0;
	cd->sublbllen = 0;
	cd->proclbllen = 0;
	expose = True;
	ExposeCustomStatus(NULL, view->panel, NULL);
	}


/*
 *	Compare two CUSTYPE object pointers.
 */
int
CompareCustomStatusObject(data, obj, a2)
CUSTYPE	*data;
caddr_t	obj;
caddr_t	a2;
	{
	if (data->obj == obj)
		return(0);
	else
		return(-1);
	}


/*
 *	Compare two objects by name.
 */
int
CompareCustomStatusObjectByName(data, ct)
CUSTYPE	*data, *ct;
	{
	return(strcmp(data->name, ct->name)); 
	}


/*
 *	Apply a value from a record from the configuration file.
 */
void
ConfigureCustomStatus(view, var1, var2, value)
VIEW	*view;
char	*var1;
char	*var2;
char	*value;
	{
	CUSTOMDATA	*cd;

	cd = (CUSTOMDATA*)(view->panel->wa);
	if (strcmp(var1, "routedest") == 0) {
		XmTextSetString(cd->route_text, value);
		SetCustomStatusRouteDest(NULL, view, NULL);
		}
	else if (strcmp(var1, "showroute") == 0) {
		cd->show_route = atoi(value);
		XmToggleButtonSetState(cd->route_onoff, cd->show_route, False);
		}
	}
	
	
/*
 *	Destroy a Custom Status View work area.
 */
void
DestroyCustomStatus(panel)
VIEWPANEL	*panel;
	{
	CUSTOMDATA	*cd;
	
	cd = (CUSTOMDATA*)panel->wa;
	RemoveEntryList(cd->objlist, free);
	RemoveEntryList(cd->replist, free);
	if (cd->routedest) XtFree(cd->routedest);
	XtDestroyWidget(cd->da);
	free(cd);
	}


/*
 *	Scale and draw the custom status view display list.
 */
void
DrawCustomStatus(cd)
CUSTOMDATA	*cd;
	{
	caddr_t	n;
	REPTYPE	*rep;
	float	ys, xs, r;
	char	s[256];
	int	f, l;
	
	count = 0;
	down = 0;
	/* see if we can have text */
	if (TextFits(2, cd, &(cd->xmargin), &(cd->ymargin), &(cd->angledlabel)))
		cd->font = 2;
	else if (TextFits(1, cd, &(cd->xmargin), &(cd->ymargin),
		&(cd->angledlabel)))
		cd->font = 1;
	else
		cd->font = 0;
	if (cd->angledlabel)
		cd->xscale = ((float)(cd->width-cd->xmargin-0.5*cd->ymargin))/
			cd->width;
	else
		cd->xscale = ((float)(cd->width-cd->xmargin))/cd->width;
	cd->yscale = ((float)(cd->height-cd->ymargin))/cd->height;

	ys = cd->yscale*cd->yscreen;
	xs = cd->xscale*cd->xscreen;
	if (cd->font) SetFont(cd->font);

	/* draw processors */
	n = cd->replist;
	while (NextEntry(&n, &rep)) {
		if (rep->class != BLOCK) continue;
		/* reference line for ipaddresses */
		SetColor(GREY);
		SetLineWidth(1);
		DrawLine(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			cd->xmargin+rep->x1*xs,
			cd->ymargin+rep->y1*ys);
		/* processor */
		SetColor(rep->color);
		DrawBlock(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			rep->r*MINIMUM(xs,ys));
		count ++;
		if (rep->color == RED) down ++;
		/* processor label */
		if (!cd->font) continue;
		SetColor(WHITE);
		if (cd->angledlabel)
			DrawAngledText(cd->da,
				cd->xmargin+rep->x0*xs-TextWidth(cd->font,
				rep->label, 1)*0.5,
				cd->ymargin+(rep->y0-rep->r)*ys,
				0.4, 0.6, 
				rep->label, rep->labellen);
		else
			DrawText(cd->da,
				cd->xmargin+rep->x0*xs-TextWidth(cd->font,
				rep->label, rep->labellen)*0.5,
				cd->ymargin+(rep->y0-rep->r)*ys-1,
				rep->label, rep->labellen);
		}

	/* draw subnets */
	n = cd->replist;
	while (NextEntry(&n, &rep)) {
		if (rep->class != LINE) continue;
		SetColor(rep->color);
		SetLineWidth(1);
		DrawLine(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			cd->xmargin+rep->x1*xs,
			cd->ymargin+rep->y1*ys);
		count ++;
		if (rep->color == RED) down ++;
		/* text */
		if (!cd->font) continue;
		SetColor(WHITE);
		DrawText(cd->da,
			(float)cd->xmargin-TextWidth(cd->font,rep->label,
			rep->labellen),
			cd->ymargin+rep->y0*ys-1,
			rep->label, rep->labellen);
		}

	/* draw ipaddresses */
	n = cd->replist;
	while (NextEntry(&n, &rep)) {
		if (rep->class != DOT) continue;
		r = rep->r*MINIMUM(xs, ys);
		SetColor(rep->color);
		SetLineWidth(1);
		DrawDot(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			r+1);
		/* draw routes */
		if (cd->show_route && rep->ROUTE) {
			SetColor(ORANGE);
			SetLineWidth(2);
			DrawLine(cd->da,
				cd->xmargin+rep->x0*xs+2*r,
				cd->ymargin+rep->y0*ys,
				cd->xmargin+rep->x0*xs,
				cd->ymargin+rep->y0*ys-r-1);
			DrawLine(cd->da,
				cd->xmargin+rep->x0*xs+2*r,
				cd->ymargin+rep->y0*ys,
				cd->xmargin+rep->x0*xs,
				cd->ymargin+rep->y0*ys+r+1);
			SetLineWidth(1);
			}
		count ++;
		if (rep->color == RED) down ++;
		}
	sprintf(s, "%d, %d unreachable", count, down);
	XmTextSetString(cd->object_text, s);
	}


/*
 *	Assign status and draw one ipaddress.
 */
void
DrawCustomStatusIpaddr(cd, ipaddr)
CUSTOMDATA	*cd;
CUSTYPE		*ipaddr;
	{
	REPTYPE	*rep;
	float	ys, xs;
	
	rep = ipaddr->rep;
	if (!rep) return;
	if (rep->color == RED) down --;
	rep->color = StatusColor(ipaddr->status);
	if (rep->color == RED) down ++;
	DrawCustomStatusObject(cd, ipaddr);
	}


/*
 *	Draw an object.
 */
void
DrawCustomStatusObject(cd, ct)
CUSTOMDATA	*cd;
CUSTYPE	*ct;
	{
	REPTYPE	*rep;
	float	xs, ys, r;
	char	s[32];

	if (!ct->rep) return;
	rep = ct->rep;
	SetColor(rep->color);
	SetLineWidth(1);
	xs = cd->xscale*cd->xscreen;
	ys = cd->yscale*cd->yscreen;
	switch (rep->class) {
	case LINE:
		DrawLine(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			cd->xmargin+rep->x1*xs,
			cd->ymargin+rep->y1*ys);
		break;
	case BLOCK:
		DrawBlock(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			rep->r*MINIMUM(xs,ys));
		break;
	case DOT:
		r = rep->r*MINIMUM(xs, ys);
		DrawDot(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			r+1);
		/*
		if (cd->show_route && rep->ROUTE) {
			SetColor(ORANGE);
			SetLineWidth(2);
			DrawLine(cd->da,
				cd->xmargin+rep->x0*xs+2*r,
				cd->ymargin+rep->y0*ys,
				cd->xmargin+rep->x0*xs,
				cd->ymargin+rep->y0*ys-r-1);
			DrawLine(cd->da,
				cd->xmargin+rep->x0*xs+2*r,
				cd->ymargin+rep->y0*ys,
				cd->xmargin+rep->x0*xs,
				cd->ymargin+rep->y0*ys+r+1);
			SetLineWidth(1);
			}
		*/
		break;
	default:;
		}
	sprintf(s, "%d, %d unreachable", count, down);
	XmTextSetString(cd->object_text, s);
	}


/*
 *	Assign status and draw one processor.
 */
void
DrawCustomStatusProcessor(cd, proc)
CUSTOMDATA	*cd;
CUSTYPE		*proc;
	{
	REPTYPE	*rep;
	float	ys, xs;
	
	rep = proc->rep;
	if (!rep) return;
	if (rep->color == RED) down --;
	rep->color = StatusColor(proc->status);
	if (rep->color == RED) down ++;
	DrawCustomStatusObject(cd, proc);
	}


/*
 *	Assign status and draw one subnet.
 */
void
DrawCustomStatusSubnet(cd, subnet)
CUSTOMDATA	*cd;
CUSTYPE		*subnet;
	{
	REPTYPE	*rep;
	float	ys, xs;
	
	rep = subnet->rep;
	if (!rep) return;
	if (rep->color == RED) down --;
	rep->color = StatusColor(subnet->status);
	if (rep->color == RED) down ++;
	DrawCustomStatusObject(cd, subnet);
	}
	
	
/*
 *	Expose the Custom Status View.
 */
void
ExposeCustomStatus(widg, panel, call_data)
Widget	widg;
VIEWPANEL	*panel;
XmDrawingAreaCallbackStruct	*call_data;
	{
	CUSTOMDATA	*cd;
	
	if (!call_data && !expose) return;
	if (!XtIsRealized(panel->shell)) return;
	cd = (CUSTOMDATA*)panel->wa;
	ClearWindow(cd->da);
	DrawCustomStatus(cd);
	expose = False;
	}


/*
 *	Initialize variables for Custom Status views.
 */
void
InitializeCustomStatus()
	{
	CreateVariable("hnmsObjReachStatus.0", &reachvar);
	CreateVariable("hnmsIpaddrIfIndex.0", &indexvar);
	}


/*
 *	Handle a button event.
 */
void
InputCustomStatus(widg, cd, calldata)
Widget	widg;
CUSTOMDATA	*cd;
XmDrawingAreaCallbackStruct	*calldata;
	{
	XEvent	*e;
	caddr_t	n;
	REPTYPE	*rt;
	int	in;
	
	e = calldata->event;
	if (e->xbutton.button != Button1) return;
	/* find object */
	if (e->type == ButtonPress) {
		n = cd->replist;
		while (NextEntry(&n, &rt)) {
			in = InsideCustomStatus(cd, rt, e->xbutton.x,
				e->xbutton.y);
			if (in) cd->selected = (CUSTYPE*)rt->ctobj;
			}
		/* color selected object blue */
		if (cd->selected) {
			SetCurrentObject(cd->selected->obj);
			SelectCustomStatusObject(cd, cd->selected);
			}
		}
	/* redraw object */
	else if (e->type == ButtonRelease) {
		if (cd->selected) {
			DrawCustomStatusObject(cd, cd->selected);
			cd->selected = NULL;
			}
		}
	}


/*
 *	Determine if mouse pointer is inside an object.
 */
int
InsideCustomStatus(cd, rep, x, y)
CUSTOMDATA	*cd;
REPTYPE	*rep;
int	x, y;
	{
	float	xf, yf, xs, ys, rs;

	xf = cd->xscale*cd->xscreen;
	yf = cd->yscale*cd->yscreen;
	switch (rep->class) {
	case BLOCK:
	case DOT:
		rs = rep->r*MINIMUM(xf, yf)+1;
		xs = cd->xmargin+rep->x0*xf;
		ys = cd->ymargin+rep->y0*yf;
		if (x < xs-rs) return(0);
		if (y < ys-rs) return(0);
		if (x > xs+rs) return(0);
		if (y > ys+rs) return(0);
		return(1);
	case LINE:
		if (IntersectLine(x, y,
			cd->xmargin+rep->x0*xf,
			cd->ymargin+rep->y0*yf,
			cd->xmargin+rep->x1*xf,
			cd->ymargin+rep->y1*yf))
			return(1);
		break;
	default:;
		}
	return(0);
	}


/*
 *	Load the Custom Status view panel and its work area.
 */
void
LoadCustomStatus(view)
VIEW	*view;
	{
	VIEWPANEL	*panel;
	CUSTOMDATA	*cd;
	Widget		f;
	XmString	xs;
	
	panel = view->panel;
	cd = (CUSTOMDATA*)myalloc(NULL, 1, sizeof(CUSTOMDATA));
	panel->wa = (caddr_t)cd;
	cd->da = XtVaCreateManagedWidget("custom",
		xmDrawingAreaWidgetClass,
		panel->viewform,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM,
		XmNleftAttachment, XmATTACH_FORM,
		XmNrightAttachment, XmATTACH_FORM,
		XmNbackground, GetColor(BLACK),
		XmNresizePolicy, XmRESIZE_ANY,
		NULL);
	XtAddCallback(cd->da, XmNresizeCallback, ResizeCustomStatus, panel);
	XtAddCallback(cd->da, XmNresizeCallback, ExposeCustomStatus, panel);
	XtAddCallback(cd->da, XmNexposeCallback, ExposeCustomStatus, panel);
	XtAddCallback(cd->da, XmNinputCallback, InputCustomStatus, cd);
	f = XtVaCreateManagedWidget("form",
		xmFormWidgetClass,
		panel->extraform,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM,
		XmNleftAttachment, XmATTACH_NONE,
		XmNrightAttachment, XmATTACH_FORM,
		NULL);
	/* total objects showing in view */
	cd->object_text = labeled_text("text", f, 16, "Objects:");
	f = XtVaCreateManagedWidget("form",
		xmFormWidgetClass,
		panel->extraform,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM,
		XmNleftAttachment, XmATTACH_FORM,
		XmNrightAttachment, XmATTACH_WIDGET,
		XmNrightWidget, f,
		XmNrightOffset, 10,
		NULL);
	/* turn route on or off */
	cd->route_onoff = XtVaCreateManagedWidget("toggle",
		xmToggleButtonWidgetClass,
		f,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM,
		XmNleftAttachment, XmATTACH_NONE,
		XmNrightAttachment, XmATTACH_FORM,
		XmNlabelString, (xs=X_STR("on/off")),
		NULL);
	XmStringFree(xs);
	XtAddCallback(cd->route_onoff, XmNvalueChangedCallback,
		SetCustomStatusRouteOnOff, view->panel);
	/* route destination widget */
	cd->route_text = labeled_text("route", f, 16, "Route Dest:");
	XtVaSetValues(cd->route_text,
		XmNeditable, True,
		XmNcursorPositionVisible, True,
		XmNrightAttachment, XmATTACH_WIDGET,
		XmNrightWidget, cd->route_onoff,
		NULL);
	XtAddCallback(cd->route_text, XmNactivateCallback,
		SetCustomStatusRouteDest, view);
	XmToggleButtonSetState(cd->route_onoff, 0, False);
	cd->xmin = 0;
	cd->xmax = ScreenWidth();
	cd->ymin = 0;
	cd->ymax = ScreenHeight();
	cd->xscreen = 1;
	cd->yscreen = 1;
	cd->xscale = 1;
	cd->yscale = 1;
	view->recvonly = 0;

	/* processors are interested in reach status and route dest */
	view->varlist[OBJ_processor] = (caddr_t*)myalloc(NULL, 2,
		sizeof(caddr_t));
	view->varcnt[OBJ_processor] = 1;
	view->varlist[OBJ_processor][0] = reachvar;

	/* subnets are interested in reach status */
	view->varlist[OBJ_subnet] = (caddr_t*)myalloc(NULL, 1,
		sizeof(caddr_t));
	view->varcnt[OBJ_subnet] = 1;
	view->varlist[OBJ_subnet][0] = reachvar;

	/* ipaddresses are interested in reach status and ifindex */
	view->varlist[OBJ_ipaddr] = (caddr_t*)myalloc(NULL, 2,
		sizeof(caddr_t));
	view->varcnt[OBJ_ipaddr] = 2;
	view->varlist[OBJ_ipaddr][0] = reachvar;
	view->varlist[OBJ_ipaddr][1] = indexvar;

	/* allow networks, sites, processors, and subnets to be announced */
	view->announce[OBJ_network] = 1;
	view->announce[OBJ_site] = 1;
	view->announce[OBJ_processor] = 1;
	view->announce[OBJ_subnet] = 1;
	GetHelp(view->panel, 11, 1);
	AddRemoveButton(view, view->panel);
	}


/*
 *	Remove an entry from the custom status object list.
 */
void
RemoveCUSTYPE(cd, ct)
CUSTOMDATA	*cd;
CUSTYPE	*ct;
	{
	if (ct->rep) RemoveEntry(&(cd->replist), ct->rep, free);
	RemoveEntry(&(cd->objlist), ct, free);
	}


/*
 *	Remove a custom status object.
 */
void
RemoveCustomStatusObject(panel, obj)
VIEWPANEL	*panel;
caddr_t	obj;
	{
	CUSTOMDATA	*cd;
	CUSTYPE		*ct;
	
	cd = (CUSTOMDATA*)panel->wa;
	if (!FindEntry(cd->objlist, CompareCustomStatusObject, obj, NULL, &ct))
		return;
	RemoveCUSTYPE(cd, ct);
	}


/*
 *	Determine coordinates of custom status objects.
 */
void
RenderCustomStatus(cd)
CUSTOMDATA	*cd;
	{
	float	xinc=0, yinc=0, blocksize, dotsize;
	float	xco, yco;
	caddr_t	n;
	CUSTYPE	*ctobj;
	
	/* increment between processors on x-axis */
	if (!cd->nprocs)
		xinc = cd->xmax-cd->xmin;
	else
		xinc = (cd->xmax-cd->xmin)/cd->nprocs;

	/* increment between subnets on y-axis */
	yinc = (cd->ymax-cd->ymin)/(cd->nsubs+1);

	/* object sizes */
	blocksize = MINIMUM(xinc, yinc)*0.4;
	dotsize = blocksize*0.3;

	/* starting coordinates */
	xco = xinc*0.5;
	yco = 1.5*yinc;
	
	/* assign coordinates to subnets and processors */
	n = cd->objlist;
	while (NextEntry(&n, &ctobj)) {
		switch (ctobj->class) {
		case OBJ_subnet:
			/* new display list entry */
			if (!ctobj->rep) {
				ctobj->rep = (REPTYPE*)myalloc(NULL, 1,
					sizeof(REPTYPE));
				ctobj->rep->class = LINE;
				ctobj->rep->color = StatusColor(ctobj->status);
				ctobj->rep->ctobj = (caddr_t)ctobj;
				ctobj->rep->label = ctobj->name;
				ctobj->rep->labellen = strlen(ctobj->name);
				cd->sublbllen = MAXIMUM(cd->sublbllen,
					ctobj->rep->labellen);
				AddEntry(&(cd->replist), ctobj->rep, NULL, 0);
				}
			/* subnet endpoints */
			ctobj->rep->x0 = cd->xmin;
			ctobj->rep->y0 = yco;
			ctobj->rep->x1 = cd->xmax;
			ctobj->rep->y1 = yco;
			ctobj->rep->r = 1;
			yco += yinc;
			break;
		case OBJ_processor:
			/* new display list entry */
			if (!ctobj->rep) {
				ctobj->rep = (REPTYPE*)myalloc(NULL, 1,
					sizeof(REPTYPE));
				ctobj->rep->class = BLOCK;
				ctobj->rep->color = StatusColor(ctobj->status);
				ctobj->rep->ctobj = (caddr_t)ctobj;
				ctobj->rep->label = ctobj->name;
				ctobj->rep->labellen = LabelLength(ctobj->name);
				cd->proclbllen = MAXIMUM(cd->proclbllen,
					ctobj->rep->labellen);
				AddEntry(&(cd->replist), ctobj->rep, NULL, 0);
				}
			/* processor center point and size */
			ctobj->rep->x0 = xco;
			ctobj->rep->x1 = xco;
			ctobj->rep->y0 = 0.5*yinc;
			ctobj->rep->y1 = 0.5*yinc;
			ctobj->rep->r = blocksize;
			xco += xinc;
			break;
		default:;
			}
		}

	/* assign coordinates to ipaddresses */
	n = cd->objlist;
	while (NextEntry(&n, &ctobj)) {
		if (ctobj->class != OBJ_ipaddr) continue;
		if (!ctobj->ipparent || !ctobj->physparent) continue;
		/* new display list entry */
		if (!ctobj->rep) {
			ctobj->rep = (REPTYPE*)myalloc(NULL, 1,sizeof(REPTYPE));
			ctobj->rep->class = DOT;
			ctobj->rep->color = StatusColor(ctobj->status);
			ctobj->rep->ctobj = (caddr_t)ctobj;
			AddEntry(&(cd->replist), ctobj->rep, NULL, 0);
			}
		/* ipaddress center point and radius */
		ctobj->rep->x0 = ctobj->physparent->rep->x0;
		ctobj->rep->y0 = ctobj->ipparent->rep->y0;
		ctobj->rep->r = dotsize;
		/* endpoint of reference line */
		if (ctobj->physparent->rep->y1 < ctobj->rep->y0)
			ctobj->physparent->rep->y1 = ctobj->rep->y0;
		}
	expose = True;
	}


/*
 *	Resize the Custom Status view.
 */
void
ResizeCustomStatus(widg, panel, call_data)
Widget	widg;
VIEWPANEL	*panel;
caddr_t		*call_data;
	{
	Dimension	w, h;
	CUSTOMDATA	*cd;
	
	cd = (CUSTOMDATA*)(panel->wa);
	XtVaGetValues(cd->da,
		XmNwidth, &w,
		XmNheight, &h,
		NULL);
	/* redraw only if the window size changed */
	if ((w != cd->width) || (h != cd->height)) {
		cd->width = w;
		cd->height = h;
		cd->xscreen = ((float)cd->width)/ScreenWidth();
		cd->yscreen = ((float)cd->height)/ScreenHeight();
		}
	}


/*
 *	Save the view to the configuration file.
 */
void
SaveCustomStatus(view)
VIEW	*view;
	{
	CUSTOMDATA	*cd;

	cd = (CUSTOMDATA*)(view->panel->wa);
	if (cd->routedest)
		PutConfiguration(ViewName(view), "routedest", cd->routedest, 2);
	PutConfiguration(ViewName(view), "showroute", cd->show_route, 1);
	}


/*
 *	Set the color of a selected object.
 */
void
SelectCustomStatusObject(cd, ct)
CUSTOMDATA	*cd;
CUSTYPE		*ct;
	{
	REPTYPE	*rep;
	float	xs, ys;

	if (!ct->rep) return;
	rep = ct->rep;
	SetColor(BLUE);
	SetLineWidth(1);
	xs = cd->xscale*cd->xscreen;
	ys = cd->yscale*cd->yscreen;
	switch (rep->class) {
	case LINE:
		DrawLine(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			cd->xmargin+rep->x1*xs,
			cd->ymargin+rep->y1*ys);
		break;
	case BLOCK:
		DrawBlock(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			rep->r*MINIMUM(xs,ys));
		break;
	case DOT:
		DrawDot(cd->da,
			cd->xmargin+rep->x0*xs,
			cd->ymargin+rep->y0*ys,
			rep->r*MINIMUM(xs, ys)+1);
		break;
	default:;
		}
	}


/*
 *	Set the route destination for the view.
 */
void
SetCustomStatusRouteDest(widg, view, a)
Widget	widg;
VIEW	*view;
caddr_t	a;
	{
	char	buf[BUFSIZ];
	CUSTOMDATA	*cd;
	caddr_t	n;
	CUSTYPE	*ctobj;
	int	subscribe, request=False;

	cd = (CUSTOMDATA*)(view->panel->wa);
	if (cd->routedest) XtFree(cd->routedest);
	cd->routedest = XmTextGetString(cd->route_text);
	/* cancel current route */
	if (!strlen(cd->routedest)) {
		if (view->varcnt[OBJ_processor] == 2)
			view->varcnt[OBJ_processor] = 1;
		if (cd->routevar)
			subscribe = True;
		else
			subscribe = False;
		cd->routevar = NULL;
		request = False;
		}
	/* create new route variable */
	else {
		strcpy(buf, routestr);
		strcat(buf, cd->routedest);
		CreateVariable(buf, &(cd->routevar));
		view->varcnt[OBJ_processor] = 2;
		view->varlist[OBJ_processor][1] = cd->routevar;
		subscribe = True;
		request = True;
		}

	/* add variable to all processors */
	if (subscribe) {
		n = cd->objlist;
		while (NextEntry(&n, &ctobj)) {
			if (ctobj->class == OBJ_processor) {
				ctobj->ifnumber = 0;
				if (request)
					Q_RequestObjectVariable(ctobj->obj,
						view, cd->routevar);
				SubscribeObjectVariables(ctobj->obj);
				}
			else if ((ctobj->class == OBJ_ipaddr) && ctobj->rep)
				ctobj->rep->ROUTE = 0;
			}
		if (cd->show_route) {
			expose = True;
			ExposeCustomStatus(NULL, view->panel, NULL);
			}
		}
	}


/*
 *	Set the route to show.
 */
void
SetCustomStatusRouteOnOff(widg, panel, cb)
Widget	widg;
VIEWPANEL	*panel;
XmToggleButtonCallbackStruct	*cb;
	{
	CUSTOMDATA	*cd;

	cd = (CUSTOMDATA*)panel->wa;
	cd->show_route = cb->set;
	/*
	if (cb->set) {
		if (cd->routedest)
			XmTextSetString(cd->route_text, cd->routedest);
		}
	else
		XmTextSetString(cd->route_text, "");
	*/
	expose = True;
	ExposeCustomStatus(NULL, panel, NULL);
	}


/*
 *	Determine whether or not text can be drawn in a specific font and
 *	what type of text.
 */
int
TextFits(fi, cd, xmargin, ymargin, angled)
int	fi;
CUSTOMDATA	*cd;
float	*xmargin, *ymargin;
int	*angled;
	{
	int	f, ym, xm, ang, tw, plw;
	
	/* get a generic character width */
	tw = TextWidth(fi, "_", 1);

	/* width of proc label */
	plw = tw*cd->proclbllen;

	/* x-axis margin */
	xm = cd->sublbllen*tw;

	/* largest font width */
	f = cd->width-xm;
	if (cd->nprocs)
		f = f/cd->nprocs;

	/* space for proc labels */
	if (f > plw) {
		ang = 0;
		ym = 2*FontAscent(fi);
		}

	/* space for angled proc labels */
	else if (f > (tw+1)) {
		ym = MINIMUM(10*FontAscent(fi), cd->proclbllen*FontAscent(fi));
		ang = 1;
		}

	/* largest font height */
	f = (cd->height-ym)/(cd->nsubs+1)-2;

	/* room for text */
	if (f >= FontAscent(fi)) {
		*ymargin = ym;
		*xmargin = xm;
		*angled = ang;
		return(1);
		}

	/* no room for text */
	else {
		*ymargin = 0;
		*xmargin = 0;
		*angled = 0;
		return(0);
		}
	}


/*
 *	Update an object's child object.
 */
void
UpdateCustomStatusChild(view, obj, child, rel)
VIEW	*view;
caddr_t	obj, child;
int	rel;
	{
	CUSTOMDATA	*cd;
	CUSTYPE		*ctobj, *ctchild, *ctipaddr;
	caddr_t		n;
	
	cd = (CUSTOMDATA*)(view->panel->wa);
	/* go away if we don't have this object */
	if (!FindEntry(cd->objlist, CompareCustomStatusObject, obj, NULL,
		&ctobj)) return;
	/* go away if this is a secondary parent, we don't want to add child */
	if (ctobj->org == 2) return;
	/* go away if we have the child, it will be updated by parent */
	if (FindEntry(cd->objlist, CompareCustomStatusObject, child, NULL,
		&ctchild)) return;
	/* otherwise add the child */
	Q_AddViewChild(view, obj, child, rel);
	}


/*
 *	Update a custom status object variable and draw object if necessary.
 */
void
UpdateCustomStatusObject(view, obj, var, value, tsc)
VIEW		*view;
caddr_t		obj;
caddr_t		var;
caddr_t		value;
unsigned int	tsc;
	{
	CUSTOMDATA	*cd;
	CUSTYPE		*ctobj, *ipaddr;
	caddr_t		n;
	VIEWPANEL	*panel;
	
	panel = (VIEWPANEL*)view->panel;
	cd = (CUSTOMDATA*)panel->wa;
	/* we are only interested in reach status, if index, or route dest */
	if (!((var==reachvar) || (var==indexvar) || (var==cd->routevar)))
		return;
	/* get the object if we have it */
	if (!FindEntry(cd->objlist, CompareCustomStatusObject, obj, NULL,
		&ctobj)) return;
	switch (ctobj->class) {
	case OBJ_processor:
		/* reach status */
		if (var == reachvar) {
			ctobj->status = (unsigned int)value;
			if (ctobj->status == STATUS_NOSTATUS)
				ctobj->status = STATUS_UNKNOWN;
			if (XtIsRealized(panel))
				DrawCustomStatusProcessor(cd, ctobj);
			}
		/* route dest */
		else {
			ctobj->ifnumber = (unsigned int)value;
			if (!ctobj->ifnumber) break;
			/* find corresponding ipaddress and make it the route */
			n = cd->objlist;
			while (NextEntry(&n, &ipaddr)) {
				if (ipaddr->class != OBJ_ipaddr) continue;
				if (ipaddr->physparent == ctobj) {
					if (!ipaddr->rep) continue;
					if (ipaddr->ifnumber == ctobj->ifnumber)
						ipaddr->rep->ROUTE = 1;
					else
						ipaddr->rep->ROUTE = 0;
					}
				}
			/* draw route if it is showing */
			if (cd->show_route) {
				expose = True;
				ExposeCustomStatus(NULL, panel, NULL);
				}
			}
		break;
	case OBJ_subnet:
		ctobj->status = (unsigned int)value;
		if (ctobj->status == STATUS_NOSTATUS)
			ctobj->status = STATUS_UNKNOWN;
		if (XtIsRealized(panel))
			DrawCustomStatusSubnet(cd, ctobj);
		break;
	case OBJ_ipaddr:
		/* reach status */
		if (var == reachvar) {
			ctobj->status = (unsigned int)value;
			if (ctobj->status == STATUS_NOSTATUS)
				ctobj->status = STATUS_UNKNOWN;
			if (XtIsRealized(panel))
				DrawCustomStatusIpaddr(cd, ctobj);
			}
		/* if index */
		else  {
			ctobj->ifnumber = (unsigned int)value;
			if (cd->routevar && ctobj->physparent)
				Q_RequestObjectVariable(ctobj->physparent->obj,
					view, cd->routevar);
			}
		break;
	default:;
		}
	/*
	printf("Update customstatus %s %d\n", ctobj->name, ctobj->status);
	*/
	}


/*
 *	Update an object's parent object.
 */
void
UpdateCustomStatusParent(view, obj, parent, rel)
VIEW	*view;
caddr_t	obj, parent;
int	rel;
	{
	CUSTOMDATA	*cd;
	CUSTYPE		*ctobj, *ctparent, *ctipaddr;
	caddr_t		n;
	
	cd = (CUSTOMDATA*)(view->panel->wa);
	/* go away if we don't have this object */
	if (!FindEntry(cd->objlist, CompareCustomStatusObject, obj, NULL,
		&ctobj)) return;
	/* find the parent if we have it */
	if (!FindEntry(cd->objlist, CompareCustomStatusObject, parent, NULL,
		&ctparent))
		ctparent = NULL;
	switch (ctobj->class) {
	case OBJ_processor:
		/* processor's old parent site is in the view, new parent */
		/* isn't, remove the processor */
		if (ctobj->physparent && !ctparent && (ctobj->org != 0)) {
			n = cd->objlist;
			/* remove children */
			while (NextEntry(&n, &ctipaddr))
				if (ctipaddr->physparent == ctobj)
					RemoveCUSTYPE(cd, ctipaddr);
			RemoveCUSTYPE(cd, ctobj);
			cd->nprocs --;
			/* redraw */
			RenderCustomStatus(cd);
			ExposeCustomStatus(NULL, view->panel, NULL);
			}
		/* change the parent */
		else
			ctobj->ipparent = ctparent;
		break;
	case OBJ_subnet:
		/* subnet's old parent network is in the view, new parent */
		/* isn't, remove subnet */
		if (ctobj->ipparent && !ctparent && (ctobj->org != 0)) {
			n = cd->objlist;
			/* remove children */
			while (NextEntry(&n, &ctipaddr))
				if (ctipaddr->ipparent == ctobj)
					RemoveCUSTYPE(cd, ctipaddr);
			RemoveCUSTYPE(cd, ctobj);
			cd->nsubs --;
			/* redraw */
			RenderCustomStatus(cd);
			ExposeCustomStatus(NULL, view->panel, NULL);
			}
		else
			ctobj->ipparent = ctparent;
		break;
	case OBJ_ipaddr:
		/* parent subnet */
		if (!ctparent) {
			AddObjectParentToView(obj, view, rel);
			break;
			}
		if (rel == IPPARENT) {
			if (ctobj->ipparent == ctparent) break;
			ctobj->ipparent = ctparent;
			RenderCustomStatus(cd);
			ExposeCustomStatus(NULL, view->panel, NULL);
			}
		/* parent processor */
		else if (rel == PHYSPARENT) {
			if (ctobj->physparent == ctparent) break;
			ctobj->physparent = ctparent;
			if (cd->routevar && ctobj->physparent)
				Q_RequestObjectVariable(ctobj->physparent->obj,
					view, cd->routevar);
			RenderCustomStatus(cd);
			ExposeCustomStatus(NULL, view->panel, NULL);
			}
		break;
	default:;
		}
	}
