
/**************************************************************************
 *                                                                        *
 * Copyright (c) 1996 Michael Richmond and Richard Treffers               *
 *                                                                        *
 *                    This software may be copied and distributed for     *
 *                    educational, research and not for profit services   *
 *                    provided that this copyright and statement are      *
 *                    included in all such copies.                        *
 *                                                                        *
 **************************************************************************/


/*	
	monitor switch yard - assign functions depending on monitor type as
	    indicated by the TV environment variable

	9/22/92 - strip switch yard out. Use X-windows only 
	9/23/92 - add circle capability -rrt
	12/13/92- modified CURSOR state buttons to be similar to NORMAL
	              state buttons (Button1=return values and continue,
	              Button2=slide colormap, Button3=quit, return no value)
	          ditto BOX_CENTER and BOX_SIZE state buttons
	          ditto CIRCLE_CENTER and CIRCLE_SIZE state buttons
	             ... MWR
	7/2/94 clean up function prototypes.
	7/13/94 add cursor stuff
	7/28/96 fixed bug in "box_size_handle" and "circle_size_handle": 
	              needed to make cr, cc 
	              into static variables (evidently, earlier compilers
	              treated them as static).  MWR
	10/22/96 - added undocumented "set_falsecolor" function, which can
	              be activated by "falsecolor" keyword in TV command
	10/22/96 - improved the internal cleaning up done when we quit
	              a TV process; we call new function "cleanup_profile".
*/ 

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/signal.h>
#include <signal.h>
#include "pcvista.h"
#include "pcvistax.h"		
#include "pcvimage.h"
#include "fits.h"

#undef DEBUG

	/* 
	 * this macro is 1 if the given return value from 
	 * XCreateWindow is invalid 
	 */
#define ISBADWINDOW(x)    ((x == BadAlloc) || (x == BadColor) || \
                           (x == BadCursor) || (x == BadMatch) ||  \
                           (x == BadPixmap) || (x == BadValue) || \
                           (x == BadWindow))

	/* 
	 * this macro is 1 if the given return value from 
	 * XCreatePixmap is invalid 
	 */
#define ISBADPIXMAP(x)    ((x == BadAlloc) || (x == BadDrawable) || \
                           (x == BadPixmap) || (x == BadValue))

/*
 * I don't know why we need to do this under SunOs 5.5
 */
#ifndef LINUX
extern double hypot(double x, double y);
#endif


#ifdef PROTO
static void check_invert( void);
static void	get_GC( Window window, GC *gc, XFontStruct *fontinfo );
static void get_rubber_GC( Window window, GC *gc, XFontStruct *fontinfo );
static void my_put_pixel( XImage *im, int nbuf, char *image_buf);
static void normal_handle( int *state, XEvent *event );
static void cursor_handle( int *state, XEvent *event );
static void box_center_handle( int *state, XEvent *event );
static void box_size_handle( int *state, XEvent *event );
static void circle_size_handle( int *state, XEvent *event );
static void circle_center_handle( int *state, XEvent *event );
static void circle_show(void);
static void clear_text_line(void);
static void number_show(void);
static void box_show( void );
static void change_state( int old_state, int state );
static void set_greyscale( int row, int col );
static void set_falsecolor(int row, int col );
static void get_rowcol( int *nrow, int *ncol ,int * , int *);
static void sig_trap(int);
#else
static void check_invert( /* */ );
static void	get_GC( /* Window window, GC *gc, XFontStruct *fontinfo */ );
static void get_rubber_GC( /* Window window, GC *gc, XFontStruct *fontinfo */ );
static void my_put_pixel(/* Ximage *im, int nbuf, char *image_buf, 
					int unit, bitorder, pad */ );
static void normal_handle( /* int *state, XEvent *event */ );
static void cursor_handle( /* int *state, XEvent *event */ );
static void box_center_handle( /* int *state, XEvent *event */ );
static void box_size_handle( /* int *state, XEvent *event */ );
static void box_show();
static void circle_show();
static void number_show();
static void circle_size_handle( /* int *state, XEvent *event */ );
static void circle_center_handle( /* int *state, XEvent *event */ );
static void change_state( /* int old_state, state */ );
static void image_quit();
static void set_greyscale( /* int row, col */ );
static void get_rowcol( /* int *nrow, *ncol */ );
static void sig_trap(/*int*/);
#endif

int image_matrix[] = {1, 0, 0, 1};	/* row column to x y rotation matrix */
									/* so that 	row=[0]*x + [1]*y
												col=[2]*x +	[3]*y		*/
int char_height;					/* pixel size of characters */
int image_max_color;
int image_zero_pix;
int image_xor_color;
int image_page = 0;
static int invert_color = 0;		/* if == 1, invert the colormap */

extern char *progname;

#define BORDER_WIDTH    5		/* width of created window's border */
#define FONTNAME   "9x15"
#define LINEWIDTH       6
#define LINESTYLE   LineSolid
#define CAPSTYLE    CapButt
#define JOINSTYLE   JoinMiter
	

#define COLOR_MAX   65535	/* max intensity value per color gun */
#define COLOR_RESET    -1	/* tells 'set_greyscale()' to use default colormap */
#define MIN_CELL       32	/* try to leave this many color cells for the window manager */



	/* these three variables may be accessed by functions in other files */
Display *display;
int screen;
Window window;
GC gc, copygc, rubbergc;

	
	/* but these may not.... */
static char *display_name = NULL;
static unsigned int display_width, display_height;
static int display_depth;		/* depth of display in bits (mono=1, etc.) */
static Cursor norm_cursor, curs_cursor, box_cursor, box2_cursor;
static XSizeHints size_hints;
static XGCValues values;
static Atom atom_state, atom_actual;
#if 0
static XStandardColormap map_info[100];
static Status status;
static XSetWindowAttributes attrib;
static unsigned long attribmask;
#endif
static XFontStruct *fontinfo;
static XEvent event;
static Window X_win_id;					/* X window id for X calls herein */
static Pixmap pixmap;					/* bitmap of image in memory */
static XWindowAttributes xwa;
static KeySym key;
static XComposeStatus compose;
static Visual *vis;
static Colormap mycmap;

static struct s_prop prop;

static unsigned int win_height, win_width;	/* size of the image window */



void 
image_init(int colormap)
{

	/* attempt to open an X display */
	if ((display = XOpenDisplay(display_name)) == NULL)
		error(1, "Sorry, can't open display - are you in X-Windows?");
	screen = DefaultScreen(display);
	display_width = DisplayWidth(display, screen);
	display_height = DisplayHeight(display, screen);
	display_depth = DefaultDepth(display, screen);
	if ((fontinfo = XLoadQueryFont(display, FONTNAME)) == NULL)
		error(1, "can't open font");
	values.function = GXcopy;
	copygc = XCreateGC(display, RootWindow(display, screen), 
			GCFunction, &values);
	norm_cursor = XCreateFontCursor(display, XC_arrow);
	curs_cursor = XCreateFontCursor(display, XC_pencil);
	box_cursor = XCreateFontCursor(display, XC_dotbox);
	box2_cursor = XCreateFontCursor(display, XC_crosshair);
	atom_state = XInternAtom(display, ATOM_STATE, False);

	if (display_depth == 1) {
		image_max_color = 1;
		image_zero_pix = 0;
	} else {
				/* image_max_color and image_zero_pix are set by
			   in the 'set_greyscale()' routine below */
		check_invert();
		if (colormap == TV_FALSECOLOR) {
			set_falsecolor(COLOR_RESET, COLOR_RESET);
		} 
		else {
			set_greyscale(COLOR_RESET, COLOR_RESET);
		}
	}
	image_xor_color = 0; 	/* get rid of this ? */
	char_height = fontinfo->max_bounds.ascent + fontinfo->max_bounds.descent;
}

/*	 quick exit.. mainly used by propinit
*/
void image_abort()
{
	XCloseDisplay(display);
}

void
invert_cmap()
{
	int val;
	char *cp, str[20];

	if ((cp = getsym("invert")) != NULL) {
		if (sscanf(cp, "%d", &val) == 1)
			invert_color = (val == 1 ? 0 : 1);
	}
	else {
		invert_color = (invert_color == 1 ? 0 : 1);
	}
	sprintf(str, "invert=%d", invert_color);
	putsym(str);
	set_greyscale(COLOR_RESET, COLOR_RESET);
	
}

	/* check the current value if the 'invert' symbol, and set 
	   the 'invert_color' flag accordingly */

static void
check_invert()
{
	int val;
	char *cp;

	if ((cp = getsym("invert")) != NULL) {
		if (sscanf(cp, "%d", &val) == 1) 
			invert_color = val;
	}
}






	/* X-Windows routines */


	/* create a new window, starting at the given position and with the
	   specified size (where rows go horizontally and cols vertically). 

	   Return the window ID if all went well, or die with an error 
	   message if the window could not be created. */

int
open_window(name, x_start, y_start, nr, nc, zoom, bin)
char *name;
int x_start, y_start, nr, nc, zoom, bin;
{
	char buf[50];
	int border_width, text_width;
	int winfo_mask;
	long event_mask;
	int data[PROP_NITEMS];
	XSetWindowAttributes winfo;

	if ((fontinfo = XLoadQueryFont(display, FONTNAME)) == NULL)
		error(1, "can't open font");
	char_height = fontinfo->max_bounds.ascent - fontinfo->max_bounds.descent;
	sprintf(buf, " R = %-4d  C = %-4d  %-6d ", 0, 0, 0);
	text_width = XTextWidth(fontinfo, buf, strlen(buf));

	border_width = BORDER_WIDTH;
	if ((win_width = nc*zoom/bin) < text_width)
		win_width = text_width;
	win_height = nr*zoom/bin + 2*char_height;

	winfo.background_pixel = BlackPixel(display, screen);
	winfo.border_pixel = WhitePixel(display, screen);
	winfo.colormap = mycmap;
	winfo_mask = CWColormap | CWBackPixel | CWBorderPixel;

	window = XCreateWindow(display, RootWindow(display, screen),
			x_start, y_start, win_width, win_height, border_width,
			display_depth, CopyFromParent, vis, winfo_mask, &winfo);
	if (ISBADWINDOW(window)) {
		error(1, "error in XCreateWindow");
	}


	/* now we set up some properties of the new window */
	size_hints.flags = USPosition | USSize;
	size_hints.width = win_width;
	size_hints.height = win_height;
	size_hints.x = x_start;
	size_hints.y = y_start;
	size_hints.min_width = win_width;
	size_hints.min_height = win_height;
	if (XSetStandardProperties(display, window, name, name, (Pixmap) NULL,
			(char **) NULL, 0, &size_hints) < 0)
		error(1, "error in XSetStandardProperties");
	event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | 
				PointerMotionMask | KeyPressMask;
	if (XSelectInput(display, window, event_mask) < 0)
		error(1, "error in XSelectInput for image window");
	if (XGetWindowAttributes(display, window, &xwa) < 0)
		error(1, "error in XGetWindowAttributes\n");


	event_mask = PropertyChangeMask;
	if (XSelectInput(display, DefaultRootWindow(display), event_mask) < 0)
		error(1, "error in XSelectInput for DefaultRootWindow");

	get_GC(window, &gc, fontinfo);
	get_rubber_GC(window, &rubbergc, fontinfo);
	XDefineCursor(display, window, norm_cursor);

	XMapWindow(display, window);

	/* signal that a new image window has been created, by incrementing the
	   first element of the property data. */
	read_property(data);
	data[0]++;
	set_property(data);


	if (XGetWindowAttributes(display, window, &xwa) < 0)
		error(1, "error in XGetWindowAttributes\n");
	

	/* and create a pixmap for storing the image */
	pixmap = XCreatePixmap(display, window, win_width, win_height, 
					xwa.depth);
	if (ISBADPIXMAP(pixmap))
		error(1, "can't allocate for screen pixmap");

	/* don't return until the window has been exposed - otherwise, we might
	   draw on it before it's there, and lose part of the image */
	while (1) {
		XNextEvent(display, &event);
		if (event.type == Expose) 
			break;
	}

	return(window);
}

	/* create a Graphics Context for this window */

static void
get_GC(window, gc, fontinfo)
Window window;
GC *gc;
XFontStruct *fontinfo;
{
	unsigned long valuemask = GCFunction;
	XGCValues values;
	unsigned int line_width = LINEWIDTH;
	int line_style = LINESTYLE;
	int cap_style = CAPSTYLE;
	int join_style = JOINSTYLE;
	int dash_offset = 0;
	static char dash_list[] = { 12, 24 };
	int list_length = 2;

	values.function = GXxor;
	values.plane_mask = AllPlanes;
	*gc = XCreateGC(display, window, valuemask, &values);
	XSetFont(display, *gc, fontinfo->fid);
	XSetForeground(display, *gc, WhitePixel(display, screen));
	XSetLineAttributes(display, *gc, line_width, line_style, cap_style,
			join_style);
	XSetDashes(display, *gc, dash_offset, dash_list, list_length);
}

	/* create a Graphics Context for drawing rubber-band boxes in this window */

static void
get_rubber_GC(window, gc, fontinfo)
Window window;
GC *gc;
XFontStruct *fontinfo;
{
	unsigned long valuemask = GCFunction;
	XGCValues values;
	unsigned int line_width = 1;
	int line_style = LineSolid;
	int cap_style = CapButt;
	int join_style = JoinMiter;
	int dash_offset = 0;
	static char dash_list[] = { 12, 24 };
	int list_length = 2;

	values.function = GXinvert;
	values.plane_mask = AllPlanes;
	*gc = XCreateGC(display, window, valuemask, &values);
	XSetFont(display, *gc, fontinfo->fid);
	XSetForeground(display, *gc, BlackPixel(display, screen));
	XSetLineAttributes(display, *gc, line_width, line_style, cap_style,
			join_style);
	XSetDashes(display, *gc, dash_offset, dash_list, list_length);
}
	
	

	/* sets the X window ID for subsequent graphics calls */

void
set_win_id(id)
int id;
{
	X_win_id = id;
}

	/* save the image from its current form on the screen to memory - use if
	   we've added text, boxes, etc. */

void
image_save()
{
	XMapWindow(display, X_win_id);
	XCopyArea(display, window, pixmap, copygc, 0, 0, win_width, win_height, 
			0, 0);
}

	/* wait for the next X event - redraw the window data if it
	   has been uncovered  */

void 
image_wait()
{
	int state, old_state, charcount;
	char buf[NBUF];

	state = STATE_NORMAL;
	old_state = STATE_NORMAL;

	while (1) {

		XNextEvent(display, &event);
		if (event.type == Expose) {
			XCopyArea(display, pixmap, window, copygc, 0, 0, 
						win_width, win_height, 0, 0);
			continue;
		}
		/* note that KeyPress events are only valid when STATE_NORMAL */
		if (event.type == KeyPress) {
			if (state != STATE_NORMAL) {
				continue;
			}
			charcount = XLookupString((XKeyEvent *) &event, buf, NBUF, &key, &compose);
			switch (key) {
			case ZOOM_IN_KEY:
			case ZOOM_OUT_KEY:
				do_zoom(&event, &key);
				continue;
			case LEFT_KEY:
			case RIGHT_KEY:
			case UP_KEY:
			case DOWN_KEY:
				do_cursor_key(&event, &key);
				continue;
			case PROFILE_KEY:
				do_profile_key(&event, &key);
				continue;
			case APERTURE_KEY:
				do_aperture_key(&event, &key);
				continue;
			case CURVEOFG_KEY:
				do_curveofgrowth_key(&event, &key);
				continue;
			default:
				continue;
			}		
		}
		old_state = state;
		switch (state) {
			case STATE_NORMAL:
			case STATE_FROZEN:
				normal_handle(&state, &event);
				break;
			case STATE_CURSOR:
				cursor_handle(&state, &event);
				break;
			case STATE_BOX_CENTER:
				box_center_handle(&state, &event);
				break;
			case STATE_BOX_SIZE:
				box_size_handle(&state, &event);
				break;
			case STATE_CIRCLE_CENTER:
				circle_center_handle(&state, &event);
				break;
			case STATE_CIRCLE_SIZE:
				circle_size_handle(&state, &event);
				break;
			case STATE_COMMUNICATE:
				break;
			default:
				fprintf(stderr, "unknown state in image_wait!\n");
				break;
		}
		if (state != old_state) {
			change_state(old_state, state);
			old_state = state;
		}
	}
}

	/* handle an event when we're in a "normal" state.
	       Button1 press       toggle FROZEN <-> NORMAL
	       Button2 press       enable SLIDING colormap
	       Button3 press       quit
	       Button1 release     ignored
	       Button2 release     disable SLIDING colormap
	       Button3 release     ignored
	       MotionNotify        show x,y,val if NORMAL; 
	                           change colormap if SLIDING
	       PropertyNotify      if BOX_SHOW, show the box. or,
	                           if not COMMUNICATE, switch state accordingly
	*/

static void
normal_handle(state, event)
int *state;
XEvent *event;
{
	int row, col, bin, zoom, data[PROP_NITEMS];
	static int sliding = NOT_SLIDING;
	int16 pixval;
	char *fname, buf[100];

	if (event->type == ButtonPress) {	
		if (event->xbutton.button == Button1)
			*state = (*state == STATE_FROZEN ? STATE_NORMAL : STATE_FROZEN);
		else if (event->xbutton.button == Button2)
			sliding = SLIDING;	
		else if (event->xbutton.button == Button3) 
			image_quit();
		else {
			/* do nothing for other buttons */	
			/* but this line placates compiler */
			pixval = pixval;
		}
	}
	else if (event->type == ButtonRelease) {
		if (event->xbutton.button == Button2)
			sliding = NOT_SLIDING;
	}
	else if (event->type == MotionNotify) {
		if (get_data(event->xmotion.y, event->xmotion.x, &row, &col, &pixval, 
					1, &fname, &bin, &zoom) == 1) {
			if (sliding == SLIDING)
				set_greyscale(row, col);
			if (*state == STATE_NORMAL) {
				sprintf(buf, " R = %-4d  C = %-4d  %-6d ", row, col, pixval);
				clear_text_line();

				/* 
 				 * changed 'gc' to 'copygc' -- works better in Vermont.  
				 *    MWR Aug 3, 1996
 			 	 */ 
				XDrawImageString(display, window, copygc, 0, 
						win_height - char_height/2, buf, strlen(buf));
			}
		}
	}
	else if (event->type == PropertyNotify) {
		read_property(data);
		if (prop.data[1] == STATE_BOX_SHOW)
			box_show();
		else if (prop.data[1] == STATE_CROSSHAIR)
			do_crosshair();
		else if (prop.data[1] == STATE_CIRCLE_SHOW)
			circle_show();
		else if (prop.data[1] == STATE_NUMBER)
			number_show();
		else if (prop.data[1] != STATE_COMMUNICATE)
			*state = prop.data[1];
	}
}

	/* handle an event when we're in a "cursor" state.
	       Button1 press       change prop to r, c, val, CURSOR_CONTINUE
	       Button2 press       enable SLIDING colormap
	       Button3 press       change prop to r, c, val, CURSOR_STOP
	       Button1 release     ignored
	       Button2 press       disable SLIDING colormap
	       Button3 release     ignored
	       MotionNotify        show r,c,val 
	                           change colormap if SLIDING
	       PropertyNotify      if prop[1] == NORMAL, change state to NORMAL

	   note that we WAIT for the property values[1] to return COMMUNICATE 
	   to NORMAL after having changed them; that signals that the 'cursor'
	   program has read the values correctly. 

	   modified button functions 12/13/92  MWR
	*/

static void
cursor_handle(state, event)
int *state;
XEvent *event;
{
	static int row, col;
	static int sliding = NOT_SLIDING;
	static int16 pixval;
	int bin, zoom;
	char *fname, buf[100];
	int data[PROP_NITEMS];

	if (event->type == ButtonPress) {
		if (event->xbutton.button == Button1) {
			read_property(data);
			data[1] = STATE_COMMUNICATE;
			data[2] = row;
			data[3] = col;
			data[4] = (int) pixval;
			data[5] = CURSOR_CONTINUE;
			set_property(data);

			/* now wait for the property values [1] to be re-set 
			   by the "cursor" program before continuing. */
			while (1) {
				read_property(data);
				if (data[1] != STATE_COMMUNICATE)
					break;
			}
			*state = data[1];
		}
		else if (event->xbutton.button == Button2) {
			sliding = SLIDING;	
		}
		else if (event->xbutton.button == Button3) {
			read_property(data);
			data[1] = STATE_COMMUNICATE;
			data[2] = row;
			data[3] = col;
			data[4] = (int) pixval;
			data[5] = CURSOR_STOP;
			set_property(data);

			/* now wait for the property values [1] to be re-set 
			   by the "cursor" program before continuing. */
			while (1) {
				read_property(data);
				if (data[1] != STATE_COMMUNICATE)
					break;
			}
			*state = data[1];
		}
	}
	else if (event->type == ButtonRelease) {
		if (event->xbutton.button == Button2)
			sliding = NOT_SLIDING;
	}
	else if (event->type == MotionNotify) {
		if (get_data(event->xmotion.y, event->xmotion.x, &row, &col, &pixval, 
					1, &fname, &bin, &zoom) == 1) {
			if (sliding == SLIDING)
				set_greyscale(row, col);
			sprintf(buf, " R = %-4d  C = %-4d  %-6d ", row, col, pixval);
			clear_text_line();
			/* 
 			 * changed 'gc' to 'copygc' -- works better in Vermont.  
			 *    MWR Aug 3, 1996
 		 	 */ 
			XDrawImageString(display, window, copygc, 0, 
					win_height - char_height/2, buf, strlen(buf));
		}
	}
	else if (event->type == PropertyNotify) {
		read_property(data);
		if (data[1] == STATE_NORMAL)
			*state = STATE_NORMAL;
	}	
}

	/* handle an event when we're in a "box_center" state.
	       Button1 press       set prop row, col; change state to BOX_SIZE
	       Button2 press       enable SLIDING colomap
	       Button3 press       ignored
	       Button1 release     ignored
	       Button2 release     disable SLIDING colomap
	       Button3 release     ignored
	       MotionNotify        show r,c,val 
	       PropertyNotify      change state to prop[0]
	                           change colormap if SLIDING
	*/

static void
box_center_handle(state, event)
int *state;
XEvent *event;
{
	static int row, col;
	static int sliding = NOT_SLIDING;
	int16 pixval;
	int bin, zoom, xc, yc;
	char *fname, buf[100];
	int data[PROP_NITEMS];

	if (event->type == ButtonPress) {
		if (event->xbutton.button == Button1) {
			read_property(data);
			data[1] = STATE_BOX_SIZE;
			data[2] = row;
			data[3] = col;
			set_property(data);
			*state = STATE_BOX_SIZE;
		}
		else if (event->xbutton.button == Button2) {
			sliding = SLIDING;	
		}
	}
	else if (event->type == ButtonRelease) {
		if (event->xbutton.button == Button2)
			sliding = NOT_SLIDING;
	}
	else if (event->type == MotionNotify) {
		xc = event->xmotion.x;
		yc = event->xmotion.y;
		if (get_data(yc, xc, &row, &col, &pixval, 1, &fname, &bin, &zoom) == 1) {
			if (sliding == SLIDING)
				set_greyscale(row, col);
			sprintf(buf, " R = %-4d  C = %-4d  %-6d ", row, col, pixval);
			clear_text_line();
			/* 
 			 * changed 'gc' to 'copygc' -- works better in Vermont.  
			 *    MWR Aug 3, 1996
 		 	 */ 
			XDrawImageString(display, window, copygc, 0, 
					win_height - char_height/2, buf, strlen(buf));
		}
	}
	else if (event->type == PropertyNotify) {
		read_property(data);
		*state = data[1];
	}	
}
		
	/* handle an event when we're in a "box_size" state.
	       Button1 press       ignored
	       Button2 press       enable SLIDING colormap
	       Button3 press       ignored
	       Button1 release     erase box, change prop to cr, cc, sr, sc;
	                               COMMUNICATE it; wait; change state to NORMAL
	       Button2 release     disable SLIDING colormap
	       Button3 release     ignored
	       MotionNotify        show r,c,val; draw rubber box 
	       PropertyNotify      change state to prop[1]
	                           change colormap if SLIDING
	*/

static void
box_size_handle(state, event)
int *state;
XEvent *event;
{
	static int xc, yc, x1, y1, first, undraw, width, height;
	static int sliding = NOT_SLIDING;
	static int row, col;
	static int cr, cc;
	int x, y, r, c, tx, ty;
	int16 pixval;
	int bin, zoom;
	char *fname, buf[100];
	int data[PROP_NITEMS];
	XEvent next;

	/* if it's the first time into this routine, read the x,y coords of the
	   central point, and find the corresponding row, col. if the user 
	   immediately lets up on button, he'll get a zero-size box centered 
	   on the center. */
	if (first == 0) {
		first = 1;
		undraw = 0;
		read_property(data);
		cr = data[2];
		cc = data[3];
		if (xform_in_window(0, cr, cc, &xc, &yc) != 1)
			error(1, "box_size can't xform_in_window for xc, yc");
	}
		
	if (event->type == ButtonPress) {
		if (event->xbutton.button == Button2) {
			sliding = SLIDING;	
		}
	}
	else if (event->type == ButtonRelease) {
		if (event->xbutton.button == Button1) {

			/* erase the rubber box */
			XDrawRectangle(display, window, rubbergc, x1, y1, width, height);	
			first = 0;

			read_property(data);
			data[1] = STATE_COMMUNICATE;
			data[2] = cr;
			data[3] = cc;
			data[4] = row;
			data[5] = col;
			set_property(data);
			*state = STATE_BOX_SIZE;

			/* now wait for the property values [1] to be re-set to NORMAL 
			   by the "box" program before continuing. */
			while (1) {
				read_property(data);
				if (data[1] == STATE_NORMAL)
					break;
			}
			*state = STATE_NORMAL;
		}
		else if (event->xbutton.button == Button2)
			sliding = NOT_SLIDING;
	}
	else if (event->type == MotionNotify) {

		/* take care of all the motion that has occurred since the last 
		   update at once - much faster than processing each event separately */
		while (XEventsQueued(display, QueuedAfterReading) > 0) {
			XPeekEvent(display, &next);
			if ((next.xmotion.window != window) || (next.type != MotionNotify))
				break;
			XNextEvent(display, event);
		}
		x = event->xmotion.x;
		y = event->xmotion.y;

		/* make sure that both corners of the box are inside the 
		   window. first do the corner diagonally opposite to the
		   cursor position.  */
		if (x > xc)
			tx = xc - (x - xc);
		else
			tx = xc + (xc - x);
		if (y > yc)
			ty = yc - (y - yc);
		else
			ty = yc + (yc - y);
		if (get_data(ty, tx, &r, &c, &pixval, 1, &fname, &bin, &zoom) != 1)
			return;

		/* this the real cursor position. */
		if (get_data(y, x, &r, &c, &pixval, 1, &fname, &bin, &zoom) != 1)
			return;

		if (undraw == 0)
			undraw = 1;
		else
			XDrawRectangle(display, window, rubbergc, x1, y1, width, height);	

		row = r;
		col = c;
		if (sliding == SLIDING)
			set_greyscale(row, col);
		x1 = xc - abs(xc - x);
		y1 = yc - abs(yc - y);
		width = 2*abs(xc - x);
		height = 2*abs(yc - y);
		sprintf(buf, " R = %-4d  C = %-4d  %-6d ", row, col, pixval);
		clear_text_line();
		/* 
 		 * changed 'gc' to 'copygc' -- works better in Vermont.  
		 *    MWR Aug 3, 1996
 	 	 */ 
		XDrawImageString(display, window, copygc, 0, win_height - char_height/2,
				buf, strlen(buf));
		XDrawRectangle(display, window, rubbergc, x1, y1, width, height);	
	}
	else if (event->type == PropertyNotify) {
		first = 0;
		read_property(data);
		if (data[1] == STATE_NORMAL)
			*state = STATE_NORMAL;
	}	
}

	/* handle an event when we're in a "circle_size" state.
	       Button1 press       ignored
	       Button2 press       enable SLIDING colormap
	       Button3 press       ignored
	       Button1 release     erase box, change prop to cr, cc, rad;
	                               COMMUNICATE it; wait; change state to NORMAL
	       Button2 release     disable SLIDING colormap
	       Button3 release     ignored
	       MotionNotify        show r,c,val; draw rubber circle 
	       PropertyNotify      change state to prop[1]
	                           change colormap if SLIDING
	*/

static void
circle_size_handle(state, event)
int *state;
XEvent *event;
{
	static int xc, yc, x1, y1, first, undraw, rad;
	static int sliding = NOT_SLIDING;
	static int row, col;
	static int cr, cc;
	int x, y, r, c, tx, ty;
	int16 pixval;
	int bin, zoom;
	char *fname, buf[100];
	int data[PROP_NITEMS];
	XEvent next;


	/* if it's the first time into this routine, read the x,y coords of the
	   central point, and find the corresponding row, col. if the user 
	   immediately lets up on button, he'll get a zero-size box centered 
	   on the center. */
	if (first == 0) {
		first = 1;
		undraw = 0;
		read_property(data);
		cr = data[2];
		cc = data[3];
		if (xform_in_window(0, cr, cc, &xc, &yc) != 1)
			error(1, "circle_size can't xform_in_window for xc, yc");
	}
		
	if (event->type == ButtonPress) {
		if (event->xbutton.button == Button2) {
			sliding = SLIDING;	
		}
	}
	else if (event->type == ButtonRelease) {
		if (event->xbutton.button == Button1) {

			/* erase the rubber circle */
			XDrawArc(display, window, rubbergc,
				xc-rad, yc-rad,2*rad, 2*rad,0,64*360);	
			first = 0;

			read_property(data);
			data[1] = STATE_COMMUNICATE;
			data[2] = cr;
			data[3] = cc;
			data[4] = row;
			data[5] = col;
			set_property(data);
			*state = STATE_CIRCLE_SIZE;

			/* now wait for the property values [1] to be re-set to NORMAL 
			   by the "circle" program before continuing. */
			while (1) {
				read_property(data);
				if (data[1] == STATE_NORMAL)
					break;
			}
			*state = STATE_NORMAL;
		}
		else if (event->xbutton.button == Button2) {
			sliding = NOT_SLIDING;	
		}
	}
	else if (event->type == MotionNotify) {

		/* take care of all the motion that has occurred since the last 
		   update at once - much faster than processing each event separately */
		while (XEventsQueued(display, QueuedAfterReading) > 0) {
			XPeekEvent(display, &next);
			if ((next.xmotion.window != window) || (next.type != MotionNotify))
				break;
			XNextEvent(display, event);
		}
		x = event->xmotion.x;
		y = event->xmotion.y;

		/* make sure that both corners of the box are inside the 
		   window. first do the corner diagonally opposite to the
		   cursor position.  */
		if (x > xc)
			tx = xc - (x - xc);
		else
			tx = xc + (xc - x);
		if (y > yc)
			ty = yc - (y - yc);
		else
			ty = yc + (yc - y);
		if (get_data(ty, tx, &r, &c, &pixval, 1, &fname, &bin, &zoom) != 1)
			return;

		/* this the real cursor position. */
		if (get_data(y, x, &r, &c, &pixval, 1, &fname, &bin, &zoom) != 1)
			return;

		if (undraw == 0)
			undraw = 1;
		else
			XDrawArc(display, window, rubbergc, 
				xc-rad, yc-rad, 2*rad, 2*rad,0,64*360);	

		row = r;
		col = c;
		if (sliding == SLIDING)
			set_greyscale(row, col);
		x1 = xc - abs(xc - x);
		y1 = yc - abs(yc - y);
		rad = (int) hypot((double)(xc - x),(double)(yc - y));
		sprintf(buf, " R = %-4d  C = %-4d  %-6d ", row, col, pixval);
		clear_text_line();
		/* 
 		 * changed 'gc' to 'copygc' -- works better in Vermont.  
		 *    MWR Aug 3, 1996
 	 	 */ 
		XDrawImageString(display, window, copygc, 0, win_height - char_height/2,
				buf, strlen(buf));
		XDrawArc(display, window, rubbergc, 
			xc-rad, yc-rad, 2*rad, 2*rad,0,64*360);	
	}
	else if (event->type == PropertyNotify) {
		first = 0;
		read_property(data);
		if (data[1] == STATE_NORMAL)
			*state = STATE_NORMAL;
	}	
}


	/* handle an event when we're in a "circle_center" state.
	       Button1 press       set prop row, col; change state to CIRCLE_SIZE
	       Button2 press       enable SLIDING colormap
	       Button3 press       ignored
	       Button1 release     ignored
	       Button2 release     disable SLIDING colormap
	       Button3 release     ignored
	       MotionNotify        show r,c,val 
	       PropertyNotify      change state to prop[0]
	*/

static void
circle_center_handle(state, event)
int *state;
XEvent *event;
{
	static int row, col;
	static int sliding = NOT_SLIDING;
	int16 pixval;
	int bin, zoom, xc, yc;
	char *fname, buf[100];
	int data[PROP_NITEMS];

	if (event->type == ButtonPress) {
		if (event->xbutton.button == Button1) {
			read_property(data);
			data[1] = STATE_CIRCLE_SIZE;
			data[2] = row;
			data[3] = col;
			set_property(data);
			*state = STATE_CIRCLE_SIZE;
		}
		else if (event->xbutton.button == Button2) {
			sliding = SLIDING;
		}
	}
	else if (event->type == ButtonRelease) {
		if (event->xbutton.button == Button2) {
			sliding = NOT_SLIDING;
		}
	}
	else if (event->type == MotionNotify) {
		xc = event->xmotion.x;
		yc = event->xmotion.y;
		if (get_data(yc, xc, &row, &col, &pixval, 1, &fname, &bin, &zoom) == 1) {
			if (sliding == SLIDING)
				set_greyscale(row, col);
			sprintf(buf, " R = %-4d  C = %-4d  %-6d ", row, col, pixval);
			clear_text_line();
			/* 
 			 * changed 'gc' to 'copygc' -- works better in Vermont.  
			 *    MWR Aug 3, 1996
 		 	 */ 
			XDrawImageString(display, window, copygc, 0, 
					win_height - char_height/2, buf, strlen(buf));
		}
	}
	else if (event->type == PropertyNotify) {
		read_property(data);
		*state = data[1];
	}	
}
		
	/* handle getting a "box_show" state: all this means is "draw the
	   given box."

	   if the given box doesn't fit within this window, just do nothing.
	*/

static void
box_show()
{
	int x1, y1, x2, y2, sr, sc, er, ec, width, height;
	int data[PROP_NITEMS];

	/* get the information on the box to be drawn from the RootWindow
	   property */

	read_property(data);
	sr = data[2];
	sc = data[3];
	er = data[4];
	ec = data[5];

	if (xform_in_window(0, sr, sc, &x1, &y1) != 1) {
		return;
	}
	if (xform_in_window(0, er, ec, &x2, &y2) != 1) {
		return;
	}
	width = x2 - x1;
	height = y2 - y1;

	XDrawRectangle(display, window, rubbergc, x1, y1, width, height);	
}

	/* handle getting a "circle_show" state: all this means is "draw the
	   given circle."

	   if the given circle doesn't fit within this window, just do nothing.
	*/

static void
circle_show()
{
	int xcen, ycen, cr, cc, rad, width, height; 
	int x1, y1, x2, y2;
	int data[PROP_NITEMS];

	/* get the information on the box to be drawn from the RootWindow
	   property */

	read_property(data);
	cr = data[2];
	cc = data[3];
	rad = data[4];

	if (xform_in_window(0, cr, cc, &xcen, &ycen) != 1) {
		return;
	}
	if (xform_in_window(0, cr-rad, cc-rad, &x1, &y1) != 1) {
		return;
	}
	if (xform_in_window(0, cr+rad, cc+rad, &x2, &y2) != 1) {
		return;
	}
	width = abs(x2 - x1);
	height = abs(y2 - y1);

	XDrawArc(display, window, rubbergc, x1, y1, width, height, 0, 64*360);	
}

		
	/* handle getting a "number_show" state: all this means is write
		the number on the screen
		the inputs are row,col, number
	*/

static void
number_show()
{
	int xcen, ycen, cr, cc; 
	int data[PROP_NITEMS],num;
	char buf[10];

	/* get the information on the box to be drawn from the RootWindow
	   property */

	read_property(data);
	cr = data[2];
	cc = data[3];
	num = data[4];

#ifdef DEBUG
	printf("number_show: I should print a ..%d.. at row=%d, col=%d\n",
			num, cr, cc);
#endif

	if (xform_in_window(0, cr, cc, &xcen, &ycen) != 1) {
		return;
	}

	sprintf(buf,"%d",num);

	/*
	 * originally, this was 'rubbergc', but it doesn't show up
	 * as of 1/7/96.  Try 'copygc'.
	 *
	 * Tests on Sparc IPX on 7/28/96 indicate that 'rubbergc' works
	 * pretty well.   MWR
	 */
	XDrawString(display, window, rubbergc,  xcen - char_height/2, ycen,
					buf, strlen(buf));
}

		
		
	/* take care of things which have to be done when switching from one
	   state to another - such as changing the cursor */

static void
change_state(old_state, new_state)
int old_state, new_state;
{
	int data[PROP_NITEMS];

	switch (new_state) {
		case STATE_NORMAL:
		case STATE_FROZEN:
			XDefineCursor(display, window, norm_cursor);
			break;
		case STATE_CURSOR:
			XDefineCursor(display, window, curs_cursor);
			break;
		case STATE_BOX_CENTER:
			XDefineCursor(display, window, box_cursor);
			break;
		case STATE_BOX_SIZE:
			XDefineCursor(display, window, box2_cursor);
			break;
		default:
			break;
	}

	/* send the new state property to the DefaultRootWindow */
	read_property(data);
	data[1] = new_state;
	set_property(data);
}

	/* clean up things before exiting, then delete the window and exit */

void
image_quit()
{
	int data[PROP_NITEMS];

	XUnmapWindow(display, window);
	XUnloadFont(display, fontinfo->fid);
	XFreeGC(display, gc);
	XFreeGC(display, copygc);
	XFreePixmap(display, pixmap);
	XFreeCursor(display, norm_cursor);
	XFreeCursor(display, curs_cursor);
	XFreeCursor(display, box_cursor);
	XFreeCursor(display, box2_cursor);

	/* decrement the number of open windows */
	read_property(data);
	data[0]--;
	set_property(data);

	/* clean up after any profile-plotting windows we made */
	cleanup_profile();

	XCloseDisplay(display);
	exit(0);
}

	/* clear the line at the bottom of a window used for displaying 
	   the cursor position and pixel value */

static void
clear_text_line()
{
	static char blank_line[NBUF];
	static int first_time = 0;
	static int len, width;

	if (first_time == 0) {
		first_time = 1;
		sprintf(blank_line, "                            ");
		len = strlen(blank_line);
		width = XTextWidth(fontinfo, blank_line, len);
	}
	/* 
 	 * changed 'gc' to 'copygc' -- works better in Vermont.  
	 *    MWR Aug 3, 1996
 	 */ 
	XDrawImageString(display, window, copygc, 0, win_height - char_height/2,
			blank_line, len);
}


	/* write image data to the given window on the screen. This
	   writes a row of data, starting at (x,y) within the window
	   and continuing for 'nbuf' bytes. 
	*/

#define BITMAP_PAD   16		/* this is a guess */

static unsigned char fill_buf[NMAX];

void
X_fill(x, y, nbuf, image_buf)
int x, y, nbuf;
char *image_buf;
{
	register char *p;
	static int first = 0;
	int j;
	static XImage *im;

	if (first == 0) {
		first = 1;
		if ((im = XCreateImage(display, vis,
					display_depth, ZPixmap, 0, (char *)fill_buf, 
					nbuf, 1, BITMAP_PAD, nbuf)) ==NULL )
			/* I hope this is right */
			error(1, "can't create X image");
	}


#ifdef MONO
	/* first we change the image data from being in chars to being a
	   Bitmap. */
	nchars = (nbuf/8) + 1;
	for (k = 0; k < nchars; k++)
		fill_buf[k] = 0;

	for (j = 0, k = 0, l = 0; j < nbuf; j++) {
		if (image_buf[j] > 0)
			fill_buf[k] |= 1 << l;
		if (++l > 7) {
			l = 0;
			k++;
		}
	}
#endif

#ifdef MONO
	if ((line_of_pix = XCreateBitmapFromData(display, X_win_id, 
				(char *)fill_buf, nbuf, 1)) < 0)
		error(1, "can't create line_of_pix");
#else
	/* I use my own routine in place of XPutPixel for RT (which has
	   display depth 1) because XPutPixel is toooo slooowwww */
	if (display_depth == 1) {
		my_put_pixel(im, nbuf, image_buf);
	}
	else { 
		p = image_buf;
		for (j = 0; j < nbuf; j++) {
			XPutPixel(im, j, 0, (unsigned long) *p++);
		}
	}
#endif


#ifdef MONO
	XCopyArea(display, line_of_pix, window, copygc, 0, 0, nbuf, 1, y, x);
	XCopyArea(display, line_of_pix, pixmap, copygc, 0, 0, nbuf, 1, y, x);
	XFreePixmap(display, line_of_pix);
#else
	XPutImage(display, window, copygc, im, 0, 0, y, x, nbuf, 1);
	XPutImage(display, pixmap, copygc, im, 0, 0, y, x, nbuf, 1);
#endif
	
}

	/* a quicker version of XPutPixel for displays with depth 1 */

static void
my_put_pixel(im, nbuf, image_buf)
XImage *im;
int nbuf;
char *image_buf;
{
	int i, j, k, l;

	/* number of chars we need to zero out first ... */
	k = ((nbuf/8) + 1)*(im->bitmap_unit/8);
	for (i = 0; i < k; i++)
		im->data[i] = 0;
		
	/* now change the bytes of image_buf[] into bits in im->data[] */
	for (j = 0, k = 0, l = 0; j < nbuf; j++) {
		if (image_buf[j] > 0) {
			if (im->bitmap_bit_order == LSBFirst)
				im->data[k] |= 1 << l;
			else
				im->data[k] |= 0x80 >> l;
		}
		if (++l > 7) {
			l = 0;
			k++;
		}
	}

}
	
	


	/* draw a box in the given window with upper-left corner at
	   (x1, y1) and lower-right corner at (x2, y2). Make the box
	   lines XOR. */

void
X_box(x1, y1, x2, y2)
int x1, y1, x2, y2;
{
	XDrawRectangle(display, RootWindow(display, screen), gc, x1, y1, 
			(x2 - x1), (y2 - y1));
}


	/* draw a circle in the given window with the given center and
	   radius. Make the circle line XOR. */

/******
X_circle(win_id, x, y, radius)
int win_id, x, y, radius;
{
*****/
	

	/* read the property of the RootWindow and put its data values
	   into the passed arrays of integers. */

void
read_property(data)
int data[];
{
	int i;

	XGetWindowProperty(display, DefaultRootWindow(display), 
			atom_state, (long) 0, (long) PROP_NITEMS, False, 
			XA_INTEGER, &atom_actual, &(prop.format), 
#if XGETUNSIGNED == 1
			&(prop.nitems), &(prop.bytes), (unsigned char **) &(prop.data));
#else
			&(prop.nitems), &(prop.bytes), (char **) &(prop.data));
#endif
	if (prop.nitems != PROP_NITEMS) {
		error(1, "read_property: XGetWindowProperty returns wrong number of items");
	}
	for (i = 0; i < PROP_NITEMS; i++)
		data[i] = prop.data[i];
}

	/* change the RootWindow property so that it has the given data
	   values.  */

void
set_property(data)
int data[];
{
	XChangeProperty(display, DefaultRootWindow(display), atom_state,
			XA_INTEGER, 32, PropModeReplace, (unsigned char *) data, 
			PROP_NITEMS);
}


	/* change the colormap in X so that we get a greyscale map with
	   many shades of grey; take no action if this is a monochrome 
	   display.

	   modified 3/31/92 MWR to create a colormap which depends on the
	   two parameters passed: 'col' is the col at which the cursor
	   currently is - it controls where the middle of the intensity
	   range is mapped.  'row' is the current row position of the
	   cursor, and controls the stepsize of the colormap.  By passing
	   COLOR_RESET for both parameters, the default best greyscale
	   is restored. */


static void
set_greyscale(row, col)
int row, col;
{
	static int first = 0;
	int i, num, found, intensity, offset, stepsize, srow, scol;
	int max_depth, image_num_colors;
	static int numcells, nrow, ncol;
	double x;
	long vmask = VisualNoMask;
	Colormap *xcmap;
	static XColor colors[256];
	static XVisualInfo *visualList, vinfo, *v;

#ifdef DEBUG
	printf("entering set_greyscale, row=%d  col=%d\n", row, col);
#endif

	/* if we're on a monochome screen, just return immediately */
	vis = DefaultVisual(display, screen);
	if ((numcells = vis->map_entries) < 2)
		return;

	if (first == 0) {
		first = 1;
		/* find visual that can display as many shades of grey as possible */
		found = 0;
		max_depth = 0;
		visualList = XGetVisualInfo(display, vmask, &vinfo, &i);
		for (v = visualList; v < visualList + i; v++) {
			if ((v->depth > max_depth) && (v->class == PseudoColor)) {
				found = 1;
				max_depth = v->depth;
				vinfo = *v;
				break;
			}
		}
		if (found == 0)
			return;
		vis = vinfo.visual;
	
		if (numcells > MIN_CELL) {
			image_max_color = numcells - 1;
			image_zero_pix = MIN_CELL;
		} 
		else {
			image_zero_pix = 0;
			image_max_color = numcells - 1;	
		}
		/* this is the actual number of color cells we're going to use */
		image_num_colors = 1 + image_max_color - image_zero_pix;

		/* first, we initialize the colors[] array so that we can use QueryColors
		   to read in the pre-existing colormap */
		for (i = 0; i < numcells; i++) {
			colors[i].pixel = i;
			colors[i].flags = DoRed | DoGreen | DoBlue;
		}
		
		/* next, we create a colormap and initialize it with the current
		   colormap values */
		if ((mycmap = XCreateColormap(display, RootWindow(display, screen), 
						vis, AllocAll)) == 0) {
			fprintf(stderr, "can't XCreateColormap \n");
		}
		xcmap = XListInstalledColormaps(display, RootWindow(display, screen), &num);	
		XQueryColors(display, *xcmap, colors, numcells);
#ifdef DEBUG
		XSync(display, False);
		printf("initial colormap is ...\n");
		for (i = 0; i < numcells; i++) {
 			printf(" %3d=%-5d %-5d %-5d  ", i, colors[i].red, 
					colors[i].green, colors[i].blue);
		}
		printf("\n");
		XFlush(display);
		XSync(display, False);
#endif

		/*
		 * this is a not a very nice way to create a greyscale,
		 * but it seems to be the most portable :-(
		 */
		for (i = image_zero_pix; i <= image_max_color; i++) {
			colors[i].red = (i - image_zero_pix)*(65536/image_num_colors);
			colors[i].green = (i - image_zero_pix)*(65536/image_num_colors);
			colors[i].blue = (i - image_zero_pix)*(65536/image_num_colors);
		}

		XStoreColors(display, mycmap, colors, numcells);
#ifdef DEBUG
		printf("after changing colormap ...\n");
		for (i = 0; i < numcells; i++) {
 			printf(" %3d=%-5d %-5d %-5d  ", i, colors[i].red, 
					colors[i].green, colors[i].blue);
		}
		printf("\n");
		XFlush(display);
#endif
	
	}


	get_rowcol(&srow, &scol, &nrow, &ncol);
	if (col == COLOR_RESET)
		x = 0.5;
	else
		x = ((double) (col - scol)/(double) ncol);
	offset = (int) (x*numcells);

	if (row == COLOR_RESET)
		stepsize = COLOR_MAX/numcells;
	else {
		x = (double) (row - srow)/(double) nrow;
		if (x < 0.5) {
			x = sqrt(x)/1.414;
		}
		else {
			x = 1.0 - ((sqrt(1.0 - x))/1.414);
		}
		stepsize = (int) (0.5 + pow((double) COLOR_MAX, x)); 
	}

#ifdef DEBUG
	printf("after modifying lookup table ...\n");
#endif
	for (i = image_zero_pix; i <= image_max_color; i++) {
		colors[i].pixel = i;
		if (invert_color == 0) {
			if ((intensity = (((i - image_zero_pix) - offset)*stepsize + (COLOR_MAX/2))) < 0)
				intensity = 0;
			else if (intensity > COLOR_MAX)
				intensity = COLOR_MAX;
		}
		else {
			intensity = COLOR_MAX - (((i - image_zero_pix) - offset)*stepsize + (COLOR_MAX/2));
			if (intensity < 0)
				intensity = 0;
			else if (intensity > COLOR_MAX)
				intensity = COLOR_MAX;
		}
		colors[i].red   = intensity;
		colors[i].blue  = intensity;
		colors[i].green = intensity;
		colors[i].flags = DoRed | DoGreen | DoBlue;
#ifdef DEBUG
		printf(" %3d=%-5d %-5d %-5d  ", i, colors[i].red, 
					colors[i].green, colors[i].blue);
#endif
	}
	XStoreColors(display, mycmap, colors, numcells);
	XInstallColormap(display, mycmap);

	XFlush(display);	

}




	/*
	 * create a colormap which we'll use for false-color images.
	 * The colormap uses 4 bits each for 2 colors, so we can only
	 * combine two images, and we can't make a true RGB image.
	 * 
	 * If we can't display at least 64 bits per pixel, just return
	 * with no action.
	 * 
	 * Sometime in the future, make a better map ...
	 */

static void
set_falsecolor(row, col)
int row, col;
{
	static int first = 0;
	int i, num, found;
	int value;
	int max_depth, image_num_colors;
	static int numcells;
	long vmask = VisualNoMask;
	Colormap *xcmap;
	static XColor colors[256];
	static XVisualInfo *visualList, vinfo, *v;

#ifdef DEBUG
	printf("entering set_falsecolor, row=%d  col=%d\n", row, col);
#endif

	/* if we're on a monochome screen, just return immediately */
	vis = DefaultVisual(display, screen);
	if ((numcells = vis->map_entries) < 2)
		return;

	if (first == 0) {
		first = 1;
		/* find visual that can display as many shades of grey as possible */
		found = 0;
		max_depth = 0;
		visualList = XGetVisualInfo(display, vmask, &vinfo, &i);
		for (v = visualList; v < visualList + i; v++) {
			if ((v->depth > max_depth) && (v->class == PseudoColor)) {
				found = 1;
				max_depth = v->depth;
				vinfo = *v;
				break;
			}
		}
		if (found == 0)
			return;
		vis = vinfo.visual;
	
		if (numcells > MIN_CELL) {
			image_max_color = numcells - 1;
			image_zero_pix = MIN_CELL;
		} 
		else {
			image_zero_pix = 0;
			image_max_color = numcells - 1;	
		}
		/* this is the actual number of color cells we're going to use */
		image_num_colors = 1 + image_max_color - image_zero_pix;

		/* make sure we have at least 64 cells */
		if (image_num_colors < 64) {
			printf("only %d cells, can't create false color map\n",
					image_num_colors);
			return;
		}

		/* first, we initialize the colors[] array so that we can use QueryColors
		   to read in the pre-existing colormap */
		for (i = 0; i < numcells; i++) {
			colors[i].pixel = i;
			colors[i].flags = DoRed | DoGreen | DoBlue;
		}
		
		/* next, we create a colormap and initialize it with the current
		   colormap values */
		if ((mycmap = XCreateColormap(display, RootWindow(display, screen), 
						vis, AllocAll)) == 0) {
			fprintf(stderr, "can't XCreateColormap \n");
		}
		xcmap = XListInstalledColormaps(display, RootWindow(display, screen), &num);	
		XQueryColors(display, *xcmap, colors, numcells);
#ifdef DEBUG
		XSync(display, False);
		printf("initial colormap is ...\n");
		for (i = 0; i < numcells; i++) {
 			printf(" %3d=%-5d %-5d %-5d  \n", i, colors[i].red, 
					colors[i].green, colors[i].blue);
		}
		printf("\n");
		XFlush(display);
		XSync(display, False);
#endif

		/*
		 * make a false color map, using 4 bits each for 
		 *     red and blue-green.  However, since we'll typically
		 *     have only 224 cells, not 256, compensate by multiplying
		 *     the red values by more than the "proper" value of 4096;
		 *     let's say we get 14 shades of red -> mult by 4680
		 *                      15 shades of blue-green -> mult by 4368
		 */
		for (i = image_zero_pix; i <= image_max_color; i++) {
			value = i - image_zero_pix;
			colors[i].red = ((value & 0xF0) >> 4)*4680;
			colors[i].green = (value & 0x0F)*4368;
			colors[i].blue = (value & 0x0F)*4368;
		}

		XStoreColors(display, mycmap, colors, numcells);
#ifdef DEBUG
		printf("after changing colormap ...\n");
		for (i = 0; i < numcells; i++) {
 			printf(" %3d=%-5d %-5d %-5d  \n", i, colors[i].red, 
					colors[i].green, colors[i].blue);
		}
		printf("\n");
		XFlush(display);
#endif
	
	}

	XInstallColormap(display, mycmap);
	XFlush(display);	

}



	/* return the starting row and col, and number of rows and cols, 
	   in the current image */

static void
get_rowcol(srow, scol, nrow, ncol)
int *srow, *scol, *nrow, *ncol;
{
	char *fname;
	int xo, xe, yo, ye, zoom, sr, er, sc, ec, bin, zero, span, dither;

	get_win_data(&fname, &xo, &xe, &yo, &ye, &zoom, &sr, &er, &sc, &ec,
			&bin, &zero, &span, &dither);
	*srow = sr;
	*scol = sc;
	*nrow = er - sr;
	*ncol = ec - sc;
}


#define MAX_TRIES 5 
static int my_data[PROP_NITEMS];
	/* send a message to the DefaultRootWindow that changes the image_state 
	   atom to the "cursor" mode.  then wait for the image_state
	   atom to revert to "normal" mode.  When it does, read the
	   x, y, and pixel values from the atom and return them to the calling
	   process. */

int
image_curpos(number,quiet)
int number,quiet;
{
	int row, col, tries;
	int16 value;
	XEvent event;


	/* try a number of times to connect to the RootWindow and read its
	   property value; if the first element indicates that there are 
	   no image windows present, sleep a second and try again. */
	for (tries = 0; tries < MAX_TRIES; tries++) {
		read_property(my_data);
		if (my_data[0] < 1) {

			fprintf(stderr, 
				"waiting for an image window to appear (%2d of %2d)\r", 
				tries, MAX_TRIES);
			sleep(1);
		}
		else
			break;
	}
	if (tries == MAX_TRIES) {
		XCloseDisplay(display);
		error(1, "no image window apparent                                  ");
	}
	else
		if(!quiet)
			fprintf(stderr, "click Left for value, Right to stop          \n");

	/* if the property isn't in the NORMAL or FROZEN states, then
	   print an error message and exit; we don't want to conflict
	   with other 'box' or 'cursor' processes. */
	if ((my_data[1] != STATE_NORMAL) && (my_data[1] != STATE_FROZEN))
		error(1, "can't run 'cursor' now; another process is busy");

	signal(SIGINT, sig_trap);
	signal(SIGQUIT, sig_trap);
	signal(SIGTERM, sig_trap);

	my_data[1] = STATE_CURSOR;
	set_property(my_data);

	/* go into a loop in which we wait for the ATOM_STATE property of
	   the DefaultRootWindow to be changed by the image. ignore
	   all properties which do not have STATE_COMMUNICATE. 
	   use the values from the property to find the x, y, val of the 
	   selected position.  Then, change the property value[1] to indicate
	   that we're read x, y, etc. correctly.

	   Based on the last property data value, continue waiting for more 
	   cursor values, stop and return state to NORMAL. */

	XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask);
	while (1) {
		XNextEvent(display, &event);
		if (event.type != PropertyNotify)
			error(1, "cursor process received wrong X event type");
		read_property(my_data);
		if (my_data[1] != STATE_COMMUNICATE)
			continue;
		row = my_data[2];
		col = my_data[3];
		value = (int16) my_data[4];	
		if (my_data[5] == CURSOR_STOP)
			break;
		if(number == 0)

			printf("%5d %5d %6d %40s\n", row, col, value, " ");
		else
			printf("%5d %5d %5d %6d %40s\n", number++,row, col, value, " ");
		fflush(stdout);
		my_data[1] = STATE_CURSOR;
		set_property(my_data);
	}
	my_data[1] = STATE_NORMAL;
	set_property(my_data);

	XCloseDisplay(display);
	return(0);
}


	/* if we catch an interrupt-type signal, restore the property to the
	   normal state, then exit */

static void
sig_trap(x)
int x;
{
	read_property(my_data);
	my_data[1] = STATE_NORMAL;
	set_property(my_data);

	XCloseDisplay(display);
	exit(0);
}
