/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
/****************************************************************
 * File: VnsBackground.c
 * Date: 03/06/91
 *
 * Description:
 *   This file contains the widget code for the VNS page 
 *   background.
 *
 * Revisions:
 ****************************************************************/
#include <stdio.h>
#include <math.h>
#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>

#include <VnsBackgroundP.h>

/* grid bitmap */
#include "grid.xbm"

#define BBW(w) (((VnsBackgroundWidget)(w))->vnsBackground)

static ClassInitialize();
static Initialize();
static Destroy();
static void Realize();
static Resize();
static ReDisplay();
static Boolean SetValues();
static XtGeometryResult GeometryManager() ;
static XtGeometryResult QueryGeometry();
static void ReManageChildren() ;

/* actions */
static ac_select() ;
static ac_unselect() ;
static ac_motion() ;
static ac_key() ;

#define SPACING 15

static char defaultTranslations[] = "\
        <Btn1Down>:             select()\n\
        <Btn1Motion>:           motion()\n\
        <Btn1Up>: 	            unselect()\n\
		<Key>:					key()";

static XtActionsRec actionsList[] =
{
        {"select",(XtActionProc)ac_select},
        {"motion",(XtActionProc)ac_motion},
        {"unselect",(XtActionProc)ac_unselect},
        {"key",(XtActionProc)ac_key},
} ;

/*
 * Constraint resources that apply to each child widget
 */
static XtResource constraint_resources[] = {
	{XtNgroup,
		XtCGroup,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(VnsBackgroundConstraints,group),
		XtRString,
		"FALSE"},
};

/*
 * general resources for the VnsBackground widget
 */
static XtResource resources[] = {
	{XtNselect,
		XtCCallback,
		XtRCallback,
		sizeof(XtPointer),
		XtOffset(VnsBackgroundWidget,vnsBackground.select),
		XtRPointer,
		NULL},
	{XtNkeyHit,
		XtCCallback,
		XtRCallback,
		sizeof(XtCallbackProc),
		XtOffset(VnsBackgroundWidget,vnsBackground.key_hit),
		XtRCallback,
		NULL},
	{XtNlocX,
		XtCLocX,
		XtRInt,
		sizeof(int),
		XtOffset(VnsBackgroundWidget,vnsBackground.loc_x),
		XtRString,
		"0"},
	{XtNlocY,
		XtCLocY,
		XtRInt,
		sizeof(int),
		XtOffset(VnsBackgroundWidget,vnsBackground.loc_y),
		XtRString,
		"0"},
	{XtNshowOutlines,
		XtCShowOutlines,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(VnsBackgroundWidget,vnsBackground.show_outlines),
		XtRString,
		"FALSE"},
	{XtNsnapToGrid,
		XtCSnapToGrid,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(VnsBackgroundWidget,vnsBackground.snap_to_grid),
		XtRString,
		"FALSE"},
	{XtNshowGrid,
		XtCShowGrid,
		XtRBoolean,
		sizeof(Boolean),
		XtOffset(VnsBackgroundWidget,vnsBackground.show_grid),
		XtRString,
		"FALSE"},
	{XtNresizeCallback,
		XtCCallback,
		XtRCallback,
		sizeof(XtCallbackProc),
		XtOffset(VnsBackgroundWidget,vnsBackground.resize_callback),
		XtRCallback,
		NULL},
} ;

#define superclass ((ConstraintWidgetClass)&constraintClassRec)

VnsBackgroundClassRec vnsBackgroundWidgetClassRec =
{
	{
		(WidgetClass) superclass,				/* superclass */
		"VnsBackground",						/* class name */
		sizeof(VnsBackgroundRec),				/* widget size */
		(XtWidgetClassProc)ClassInitialize,		/* class_initialize */
		NULL,									/* class_part_initialize */
		FALSE,									/* class inited */
		(XtInitProc)Initialize,					/* initialize */
		NULL,									/* initialize_hook */
		Realize,								/* realize */
		actionsList,							/* actions */
		XtNumber(actionsList),					/* num_actions */
		resources,								/* resources */
		XtNumber(resources),					/* num_resources */
		NULLQUARK,								/* xrm_class */
		TRUE,									/* compress_motion */
		TRUE,									/* compress_exposure */
		TRUE,									/* compress_enterleave */
		FALSE,									/* visible_interest */
		(XtWidgetProc)Destroy,					/* destroy */
		(XtWidgetProc)Resize,					/* resize */
		(XtExposeProc)ReDisplay,				/* expose */
		SetValues,								/* set_values */
		NULL,									/* set_values_hook */
		XtInheritSetValuesAlmost,				/* set_values_almost */
		NULL,									/* get_values_hook */
		XtInheritAcceptFocus,					/* accept_focus */
		XtVersion,								/* version */
		NULL,									/* callback private */
		defaultTranslations,					/* tm_table */
		QueryGeometry,							/* query_geometry */
		XtInheritDisplayAccelerator,			/* display_accelerator */
		NULL									/* extension */
	},
	{
		GeometryManager,
		ReManageChildren,
		XtInheritInsertChild,
		XtInheritDeleteChild,
		NULL,
	},
	{
		constraint_resources,
		XtNumber(constraint_resources),
		sizeof(VnsBackgroundConstraintRec),
		NULL,
		NULL,
		NULL,
		NULL,
	},
	{
		0,
	}
} ;

WidgetClass vnsBackgroundWidgetClass = (WidgetClass)&vnsBackgroundWidgetClassRec ;

/*ARGSUSED*/
static
ClassInitialize()
{
}

static
make_gcs(w)
	Widget w ;
{
	XGCValues values ;
	values.foreground = BlackPixelOfScreen(XtScreen(w)) ^ WhitePixelOfScreen(XtScreen(w)) ;
	values.background = WhitePixelOfScreen(XtScreen(w)) ;
	values.function = GXxor ;
	values.line_width = 0 ;
	values.plane_mask = values.foreground ;
	values.subwindow_mode = IncludeInferiors ;
	BBW(w).gc =
        XCreateGC(XtDisplay(w),DefaultRootWindow(XtDisplay(w)),
                (unsigned long)GCForeground |
                (unsigned long)GCBackground |
                (unsigned long)GCLineWidth  |
                (unsigned long)GCPlaneMask  |
                (unsigned long)GCFunction,
                &values) ;
	values.foreground =
		(w->core.background_pixel == BlackPixelOfScreen(XtScreen(w))) ?
			WhitePixelOfScreen(XtScreen(w)) :
			BlackPixelOfScreen(XtScreen(w)) ;
	BBW(w).gc_line =
        XCreateGC(XtDisplay(w),DefaultRootWindow(XtDisplay(w)),
                (unsigned long)GCForeground,
                &values);

	BBW(w).grid_pm = XCreatePixmapFromBitmapData(XtDisplay(w),
		DefaultRootWindow(XtDisplay(w)),grid_bits,grid_width,grid_height,
		w->core.border_pixel,w->core.background_pixel,
		DefaultDepth(XtDisplay(w),DefaultScreen(XtDisplay(w))));
}

static
release_gcs(w)
	Widget w ;
{
	XFreeGC(XtDisplay(w),BBW(w).gc);
	XFreeGC(XtDisplay(w),BBW(w).gc_line);
	XFreePixmap(XtDisplay(w),BBW(w).grid_pm);
}

/*ARGSUSED*/
static
Initialize(request,new)
	Widget request ;
	Widget new ;
{
	BBW(new).is_showing = FALSE;
	make_gcs(new);
}

static
void
Realize(w,mask,attr)
	Widget w;
	Mask *mask;
	XSetWindowAttributes *attr;
{
	XtCreateWindow(w,CopyFromParent,CopyFromParent,*mask,attr);

	if (BBW(w).show_grid)
	{
		XSetWindowBackgroundPixmap(XtDisplay(w),XtWindow(w),BBW(w).grid_pm);
	}
}

/*ARGSUSED*/
static
Destroy(w)
	Widget w ;
{
	release_gcs(w) ;
}

calc_minimum_box(w,rw,rh)
	Widget w;
	int *rw;
	int *rh;
{
	VnsBackgroundWidget bw = (VnsBackgroundWidget)w;
	Widget *children = bw->composite.children;
	Widget parent = XtParent(w);
	int nchild = bw->composite.num_children;
	int width = 0;
	int height = 0;
	int pad = 2;
	int i;

	/* determine the total width and height needed to hold children */
	for(i = 0; i< nchild; i++)
	{
		Widget child = children[i];
		Boolean group = ((VnsBackgroundConstraintRec *)child->core.constraints)->group;

		if (XtIsManaged(child))
		{
			int tw = child->core.x + child->core.width + child->core.border_width;
			int th = child->core.y + child->core.height + child->core.border_width;
			if (tw > width) width = tw;
			if (th > height) height = th;

			/* make sure that the object does not have negative coords */
			if (group)
			{
				if (child->core.x < 0 || child->core.y < 0)
				{
					child->core.x = 0;
					child->core.y = 0;
					XtMoveWidget(child,0,0);
				}
			}
		}
	}

	*rw = width + pad;
	*rh = height + pad;
}

static
do_resize(w)
	Widget w;
{
	int width;
	int height;
	Dimension rw, rh;

	calc_minimum_box(w,&width,&height);

	/* we don't want to resize smaller than the current window size */
	if (width < w->core.width)
	{
		width = w->core.width;
	}
	if (height < w->core.height)
	{
		height = w->core.height;
	}

	/* only issue resize request when needed */
	if ((width != w->core.width) || (height != w->core.height))
	{
		XtMakeResizeRequest(w,(Dimension)width,(Dimension)height,&rw,&rh);
	}
}

/*ARGSUSED*/
static
Resize(w)
	Widget w ;
{
}

static
draw_lines(w)
	Widget w ;
{
	int x = BBW(w).move_x ;
	int y = BBW(w).move_y ;

	XDrawLine(XtDisplay(w),XtWindow(w),BBW(w).gc,x,0,x,(int)(w->core.height - 1)) ;
	XDrawLine(XtDisplay(w),XtWindow(w),BBW(w).gc,0,y,(int)(w->core.width - 1),y) ;
}


/*ARGSUSED*/
static
ReDisplay(w, event)
	Widget w ;
	XExposeEvent *event ;
{
	int x = BBW(w).loc_x ;
	int y = BBW(w).loc_y ;

	{
		int x1 = x - 3 ;
		int x2 = x + 3 ;
		int y1 = y - 3 ;
		int y2 = y + 3 ;
		if (x1 < event->x)
		{
			x1 = event->x ;
		}
		if (y1 < event->y)
		{
			y1 = event->y ;
		}
		if (x2 >= event->x + event->width)
		{
			x2 = event->x + event->width - 1 ;
		}
		if (y2 >= event->y + event->height)
		{
			y2 = event->y + event->height - 1 ;
		}
		if (x1 < x2 && y1 < y2)
		{
			XDrawLine(XtDisplay(w),XtWindow(w),BBW(w).gc_line,x,y1,x,y2) ;
			XDrawLine(XtDisplay(w),XtWindow(w),BBW(w).gc_line,x1,y,x2,y) ;
		}
	}

	/* if grid is active, then redisplay the portion of the grid */
	if (BBW(w).show_grid)
	{
		XSetWindowBackgroundPixmap(XtDisplay(w),XtWindow(w),BBW(w).grid_pm);
	}
}

/*
 * update outlines based on state of show_outlines flag
 */
static
void
update_outlines(w)
	Widget w;
{
	VnsBackgroundWidget back = (VnsBackgroundWidget)w;
	Widget *children = back->composite.children;
	int nchild = back->composite.num_children;
	int bw;
	int i;

	/* set the border width if outlines are desired */
	if (BBW(w).show_outlines)
	{
		bw = 1;
	}
	else
	{
		bw = 0;
	}

	/* update border widths for all children (managed and unmanaged) */
	for (i = 0; i < nchild; i++)
	{
		XtVaSetValues(children[i],
			XtNborderWidth,bw,
			NULL);
	}
}

/*ARGSUSED*/
static
Boolean
SetValues(old, req, new)
	Widget old ;
	Widget req ;
	Widget new ;
{
	int tw, th;

	calc_minimum_box(new,&tw,&th);

	/* we only change the window size it the box has gotten larger */
	if (tw > new->core.width)
	{
		new->core.width = tw;
	}

	if (th > new->core.height)
	{
		new->core.height = th;
	}

	/* handle changes for the grid */
	if ((BBW(old).show_grid != BBW(new).show_grid) ||
		(old->core.border_pixel != new->core.border_pixel) ||
		(old->core.background_pixel != new->core.background_pixel))
	{
		release_gcs(old) ;
		make_gcs(new) ;

		if (BBW(new).show_grid)
		{
			XSetWindowBackgroundPixmap(XtDisplay(new),XtWindow(new),BBW(new).grid_pm);
		}
		else
		{
			XSetWindowBackground(XtDisplay(new),XtWindow(new),new->core.background_pixel);
		}
	}

	/* handle if the user has set loc_x and loc_y */
	if (BBW(old).loc_x != BBW(new).loc_x ||
		BBW(old).loc_y != BBW(new).loc_y)
	{
		int spacing = BBW(new).snap_to_grid ? SPACING : 1 ;
	
		draw_lines(new) ;

		BBW(new).is_showing = TRUE;
		BBW(new).orig_x = BBW(new).move_x = (BBW(new).loc_x / spacing) * spacing ;
		BBW(new).orig_y = BBW(new).move_y = (BBW(new).loc_y / spacing) * spacing ;
		BBW(new).loc_x = BBW(new).move_x;
		BBW(new).loc_y = BBW(new).move_y;

		draw_lines(new) ;
	}

	/* update object outlines */
	update_outlines(new);
	
	return True ;
}

/*ARGSUSED*/
static
void
ReManageChildren(w)
	Widget w ;
{
	do_resize(w);
	update_outlines(w);
}

/*ARGSUSED*/
static
XtGeometryResult
QueryGeometry(w,proposed,answer)
	Widget w;
	XtWidgetGeometry *proposed;
	XtWidgetGeometry *answer;
{
	VnsBackgroundStruct data;

	/* set the fields that we care about */
	answer->request_mode = CWWidth | CWHeight;

	/* calculate the minimum bounding box for the children */
	calc_minimum_box(w,&data.min_width,&data.min_height);

	/* inform any registered callbacks of the new developments */
	data.viewport_width = proposed->width;
	data.viewport_height = proposed->height;
	XtCallCallbacks(w,XtNresizeCallback,(XtPointer)&data);

	/* if the viewport is smaller than the minimum bounding box ... */
	if ((proposed->width < data.min_width) ||
		(proposed->height < data.min_height))
	{
		answer->width = data.min_width;
		answer->height = data.min_height;
		w->core.width = data.min_width;
		w->core.height = data.min_height;
		return XtGeometryNo;
	}

	return XtGeometryYes;
}

/*ARGSUSED*/
static
XtGeometryResult
GeometryManager(w,req,rep)
	Widget w ;
	XtWidgetGeometry *req ;
	XtWidgetGeometry *rep ;
{
	if ((req->request_mode & XtCWQueryOnly) && (rep != NULL))
	{
		Boolean group = ((VnsBackgroundConstraintRec *)w->core.constraints)->group;

		/* special processing for group members */
		if (group)
		{
			Widget parent = XtParent(w);
			int spacing = BBW(parent).snap_to_grid ? SPACING : 1 ;

			if (req->request_mode & CWWidth)
			{
				req->width = (Dimension)(req->width / spacing) * spacing;
			}
			if (req->request_mode & CWHeight)
			{
				req->height = (Dimension)(req->height / spacing) * spacing;
			}
			if (req->request_mode & CWX)
			{
				/* disallow moves off of the page */
				if (req->x < 0)
				{
					req->x = (Position)0;
				}
				else
				{
					req->x = (Position)(req->x / spacing) * spacing ;
				}
			}
			if (req->request_mode & CWY)
			{
				/* disallow moves off of the page */
				if (req->y < 0)
				{
					req->y = (Position)0;
				}
				else
				{
					req->y = (Position)(req->y / spacing) * spacing ;
				}
			}
			if (req->request_mode & CWBorderWidth)
			{
				req->border_width = req->border_width ;
			}
		}

		return XtGeometryYes;
	}
	/* if not a geometry query, then ... */
	else
	{
		Boolean group = ((VnsBackgroundConstraintRec *)w->core.constraints)->group;

		/* special processing for group members */
		if (group)
		{
			Widget parent = XtParent(w);
			int spacing = BBW(parent).snap_to_grid ? SPACING : 1 ;

			if (req->request_mode & CWWidth)
			{
				w->core.width = (req->width / spacing) * spacing;
			}
			if (req->request_mode & CWHeight)
			{
				w->core.height = (req->height / spacing) * spacing;
			}
			if (req->request_mode & CWX)
			{
				/* disallow moves off of the page */
				if (req->x < 0)
				{
					w->core.x = 0;
				}
				else
				{
					w->core.x = (req->x / spacing) * spacing ;
				}
			}
			if (req->request_mode & CWY)
			{
				/* disallow moves off of the page */
				if (req->y < 0)
				{
					w->core.y = 0;
				}
				else
				{
					w->core.y = (req->y / spacing) * spacing ;
				}
			}
			if (req->request_mode & CWBorderWidth)
			{
				w->core.border_width = req->border_width ;
			}
		}
		/* the rest of the children */
		else
		{
			if (req->request_mode & CWWidth)
			{
				w->core.width = req->width ;
			}
			if (req->request_mode & CWHeight)
			{
				w->core.height = req->height ;
			}
			if (req->request_mode & CWX)
			{
				w->core.x = req->x ;
			}
			if (req->request_mode & CWY)
			{
				w->core.y = req->y ;
			}
			if (req->request_mode & CWBorderWidth)
			{
				w->core.border_width = req->border_width ;
			}
		}
	}

	{
		VnsBackgroundStruct info;
		Widget back = XtParent(w);
		Dimension rw, rh;

		info.min_width = w->core.x + w->core.width + 2 * w->core.border_width;
		info.min_height = w->core.y + w->core.height + 2 * w->core.border_width;

		/* inform any registered callbacks of the new developments */
		XtCallCallbacks(XtParent(w),XtNresizeCallback,(XtPointer)&info);

		do_resize(XtParent(w));
	}

	rep->x = w->core.x;
	rep->y = w->core.y;
	rep->width = w->core.width;
	rep->height = w->core.height;
	rep->border_width = w->core.border_width;

	return XtGeometryYes ;
}

/****************************************************************
 *                 ACTIONS
 ****************************************************************/

/*ARGSUSED*/
static
ac_select(wd,event)
	Widget wd ;
	XButtonEvent *event ;
{
	int spacing = BBW(wd).snap_to_grid ? SPACING : 1 ;

	/* call select callback to allow deselection of previously
	   selected objects when the user clicks on the background */
	XtCallCallbacks(wd,XtNselect,(caddr_t)NULL) ;

	BBW(wd).is_showing = TRUE;
	BBW(wd).orig_x = BBW(wd).move_x = (event->x / spacing) * spacing ;
	BBW(wd).orig_y = BBW(wd).move_y = (event->y / spacing) * spacing ;

	draw_lines(wd) ;
}

/*ARGSUSED*/
static
ac_motion(wd,event)
	Widget wd ;
	XMotionEvent *event ;
{
	if (BBW(wd).is_showing)
	{
		int spacing = BBW(wd).snap_to_grid ? SPACING : 1 ;
		int new_x = (event->x / spacing) * spacing ;
		int new_y = (event->y / spacing) * spacing ;
		if (new_x != BBW(wd).move_x || new_y != BBW(wd).move_y)
		{
			draw_lines(wd) ;
			BBW(wd).move_x = new_x ;
			BBW(wd).move_y = new_y ;
			draw_lines(wd) ;
		}
	}
}

/*ARGSUSED*/
static
ac_unselect(wd,event)
	Widget wd ;
	XButtonEvent *event ;
{
	if (BBW(wd).is_showing)
	{
		draw_lines(wd) ;
		BBW(wd).is_showing = FALSE;
		XClearArea(XtDisplay(wd),XtWindow(wd),BBW(wd).loc_x - 3,BBW(wd).loc_y - 3,7,7,True) ;
		BBW(wd).loc_x = BBW(wd).move_x ;
		BBW(wd).loc_y = BBW(wd).move_y ;
		XClearArea(XtDisplay(wd),XtWindow(wd),BBW(wd).loc_x - 3,BBW(wd).loc_y - 3,7,7,True) ;
	}
}

/*ARGSUSED*/
static
ac_key(wd,event)
	Widget wd ;
	XKeyEvent *event ;
{
	static char buf[80] ;
	KeySym key ;
	XComposeStatus compose ;
	int bc = XLookupString(event,buf,sizeof buf,&key,&compose) ;
	if (bc > 0)
	{
		buf[bc] = 0 ;
		XtCallCallbacks(wd,XtNkeyHit,buf) ;
	}
}

/****************************************************************
 *                 PUBLIC FUNCTIONS
 ****************************************************************/

/****************************************************************
 * Function: change_loc 
 * Date: 03/06/91
 *
 * Description:
 *   Change the location of child widget on the VnsBackground.
 *
 * Linkage: void change_loc(child,x,y,width,height)
 *   Widget child - pointer to the child widget
 *   int x        - new x location of child on the background
 *   int y        - new y location of child on the background
 *   int width    - new width of the child
 *   int height   - new height of the child
 *
 * Revisions:
 ****************************************************************/
void
change_loc(child,x,y,width,height)
	Widget child;
{
	XtConfigureWidget(child,x,y,(unsigned)width,(unsigned)height,child->core.border_width);
}

/****************************************************************
 * Function: back_deselect 
 * Date: 03/06/91
 *
 * Description:
 *   This function deslects the page background by removing 
 *   the cross-hairs from the page and reseting the default
 *   location to the origin (0,0).
 *
 * Linkage: void back_deselect(w)
 *   Widget w - pointer to the VnsBackground widget
 *
 * Revisions:
 ****************************************************************/
back_deselect(w)
	Widget w;
{
	/* remove the cross hairs */
	XClearArea(XtDisplay(w),XtWindow(w),BBW(w).loc_x - 3,BBW(w).loc_y - 3,7,7,True) ;

	/* reset the location to the origin */
	BBW(w).loc_x = 0 ;
	BBW(w).loc_y = 0 ;
}

/****************************************************************
 * Function: VwGetCoordinates 
 * Date: 03/26/91
 *
 * Description:
 *   This function returns a set of valid coordiates given a 
 *   specific x,y location on the background. This allows the
 *   VnsBackground widget to provide snap-to-grid coordinates if
 *   active.
 *
 * Linkage: void VwGetCoordinates(w,x,y,rx,ry)
 *   Widget w - pointer to the VnsBackground widget
 *   int x    - x location on background
 *   int y    - y location on background
 *   int *rx  - valid x location (returned)
 *   int *ry  - valid y location (returned)
 *
 * Revisions:
 ****************************************************************/
void
VwGetCoordinates(w,x,y,rx,ry)
	Widget w;
	int x;
	int y;
	int *rx;
	int *ry;
{
	int spacing = BBW(w).snap_to_grid ? SPACING : 1 ;

	*rx = (x / spacing) * spacing;
	*ry = (y / spacing) * spacing;
}
