/*
 *                                WSCRAWL
 *
 *  This is the source code to the program "wscrawl".  The word "wscrawl"
 *  stands for "window-scrawl", reflecting the history of wscrawl.  The user
 *  may think of wscrawl as a paint program shared by any number of people
 *  at the same time.  When wscrawl is run, it opens a separate window on 
 *  each participant's display.  From that point, each participant may draw
 *  in his or her window.  All participants see everything drawn by the other
 *  participants instantly.
 * 
 *  NOTES OF PORTABILITY: The routine "block_until_input()" uses a "select"
 *  system call.  Some systems do not have this call.  To patch for this
 *  problem, simply comment out all source inside that routine.  The other
 *  potential problem is with the "rand" function.  If the airbrush is not
 *  "scattering" properly, then take a look at the function "my_rand" and
 *  try to get it to return a random decimal between 0 and 1.
 *
 *  Feel free to use/change/modify/erase/whatever this source code.  Comments
 *  requests, hacked up versions of wscrawl should go to: brianw@cv.hp.com
 *
 *  This program was written by Brian Wilson of Hewlett Packard Co.
 *  Email: brianw@cv.hp.com
 *  
 *  To compile: "cc -o wscrawl wscrawl.c -lX11"
 *  To invoke: "wscrawl -d displayname1 -d displayname2 . . ."
 *  For a complete usage message, just do a: "wscrawl -help"
 */

#define INTERESTING_EVENTS ButtonPressMask | ButtonReleaseMask | KeyPressMask \
			   | StructureNotifyMask | ExposureMask

#define WSCRAWL_WIN_WIDTH   	550     /*initial window dimensions*/
#define WSCRAWL_WIN_HEIGHT   	550

#define MAX_NUM_DISPS   	10      /*general global variables*/
#define NUM_OF_MENUS    	5
#define TRUE			1
#define FALSE	 		0

#define NOT_PRESSED     	0       /*Pointer States*/
#define PRESSED			1
#define IN_MENU			2
#define BETWEEN_MENUS           3

#define SCRAWLING		1       /*modes of drawing*/
#define AIRBRUSHING		2
#define TYPING			3
#define ERASING			4

#define MENU_ITEM_WIDTH	        100     /*menu parameters*/
#define MENU_ITEM_HEIGHT	25
#define NO_ITEM_SELECTED	-1

#define MAX_RAND  	((1<<15) - 1)

#include <stdio.h>		/*standard i/o functions*/
#include <X11/Xos.h>            /*"time.h" necessary for the "select" stuff */
#include <X11/Xlib.h>		/*standard x defs*/
#include <X11/Xutil.h>		/*necessary for the XWMhints stuff*/
#include <X11/cursorfont.h>	/*we are gonna change the pointer*/

static char what[] = "@(#)WSCRAWL - Brian Wilson 6/15/90";

float my_rand();                /*hack, hack, cough, wheez, sputter, hack*/

XSetWindowAttributes menuwinvals =  /*struct for the menu windows*/
{
    None,                       /*default background pixmap*/
    1,                          /*background pixel*/
    CopyFromParent,             /*border_pixmap*/
    0, 0, 
    NorthWestGravity,           /*window gravity*/
    NotUseful,                  /*backing store*/
    0, 0, 0, 
    INTERESTING_EVENTS,         /*events we are interested in*/
    0, 
    True,                       /*override redirect flag*/
    0, 0
};

XSetWindowAttributes winvals =  /*struct for the window*/
{
    None,                       /*default background pixmap*/
    1,                          /*background pixel*/
    CopyFromParent,             /*border_pixmap*/
    0, 0, 
    NorthWestGravity,           /*window gravity*/
    Always,                     /*backing store*/
    0, 0, 0, 
    INTERESTING_EVENTS,         /*events we are interested in*/
    0, 
    False,                      /*override redirect flag*/
    0, 0
};

XGCValues menugcvalues =        /*menu graphics context values*/
{                               /*so we need a gc values structure*/
    GXxor,                       /*write rule*/
    0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,          /*bunch of placeholders*/
    None,			/*subwindow_mode*/
    0,0,0,0,0,0
};

XGCValues gcvalues =            /*We want to "include inferiors"*/
{                               /*so we need a gc values structure*/
    0,0,
    0,                          /*foreground color*/
    0,
    8,                          /*width of the line*/
    0,
    CapRound,                   /*CapRound looks good, but is VERY SLOW*/
    0,0,0,0,0,0,0,0,0,          /*bunch of placeholders*/
    None,			/*subwindow_mode*/
    0,0,0,0,0,0
};

XGCValues cursor_gc_values =    /*We want the cursor to have XOR */
{                               /*so we need a gc values structure*/
    GXor, 
    0,0,0,
    4,                          /*Line width*/
    0,0,0,0,0,0,0,0,0,0,0,      /*bunch of placeholders*/
    0,0,0,0,0,0,0
};

/*
 * The following text definitions deserve some explanation.  If you want a new
 * color, or font, or pen_width, simply add the desired value to these lists.
 * The routines automatically incorporate these new values and change menu
 * lengths.  If you want to add a brand new menu, then I recommend
 * you add create a menu_text5 list, then link it appropriately in 
 * "initialize_displays()", change the "#define NUM_OF_MENUS   5" to 6,
 * and add the new functionality to the "menu_selection()" routine.  Good
 * luck, as their are lots of little special cases to worry about, depending
 * on your particular functionality.  Remember to mail interesting versions 
 * to the above address.  :-)
 */
static char *menu_text0[] = {"Control", 
			     "Scrawl", 
			     "Airbrush", 
			     "Type", 
			     "Eraser",
			     "Clear Windows", 
			     "--------------", 
			     "Close Window", "****"};
static char *menu_text1[] = {"PenColor", "white", "magenta", "red", "blue",
			     "cyan", "LightSteelBlue", "navy blue", "green",
			     "coral", "light grey", "orange", "plum", "yellow",
			     "black", "****"};
static char *menu_text2[] = {"PenWidth", "1", "2", "4", "8", "15", "25", "30",
			     "45", "60", "100", "150", "500", "****"};
static char *menu_text3[] = {"PenCapStyle", "CapRound", "CapButt", 
			     "CapNotLast", "CapProjecting", "****"};
static char *menu_text4[] = {"Font", "fixed", "variable", 
			     "timR12", "helvO12", "courR12", "courR14",
			     "timBI18", "courR24", "timR24", "timBI24",
			     "ncenR24", "vbee-36", "vr-40", "vgl-40", 
			     "vsgn-57", "****"};
char **menu_text[NUM_OF_MENUS];

int num_of_disps = 1;               /*start with your home display*/
char disp_args[MAX_NUM_DISPS][50];  /*No more than 50 letters in display name*/
char PEN_COLOR[42];
char BACKGROUND_COLOR[42];
char MENU_HIGHLIGHT_COLOR[42];
char MENU_FOREGROUND_COLOR[42];
char MENU_BACKGROUND_COLOR[42];
char PEN_STYLE[42];
char FONT[256];
int NUM_DOTS;
int CAP_STYLE;
int TYPE_NOT_DRAW;


struct char_node	     /*for the history of typing (for backspacing)*/
{
    char the_char;
    int x, y;                /*where this letter was typed (only for <CR>)*/
    struct char_node *next;  /*pointer to the next character*/
};

struct a_menu	             /*one of these for every menu*/
{
    Window win_id;	     /*the window which IS the menu*/
    int num_items;           /*the number of items in this menu*/
    int item_selected;	     /*the current item which is selected*/
};

struct disp_info_struct      /*one of these exists for every "wscrawl" window*/
{
    Display *disp;           /*the display variable for this scrawl window*/
    Window win_id;           /*the window id of this scrawl window*/
    Window status_win_id;    /*the status window id for this scrawl window*/
    Window eraser_win_id;    /*the eraser window id for this scrawl window*/
    GC *win_gc;              /*one graphics context for every scrawl window*/
    GC fg_menu_gc;	     /*the graphics context for the menu foreground*/
    GC hi_menu_gc;           /*the graphics context for highlighting of menus*/
    GC bg_menu_gc;	     /*the graphics context for unhighlighting of mens*/
    int current_menu;	     /*the current menu that is pulled down(if one is)*/
    struct a_menu menu[NUM_OF_MENUS]; /*each menu in a scrawl window*/
    int in_session_bool;     /*whether this win is still in the session or not*/
    int connection_num;      /*socket that this window is on*/
    int pointer_state;       /*Three choices: NOT_PRESSED, PRESSED, IN_MENU*/
    int scrawl_mode;	     /*Three choices: SCRAWLING, AIRBRUSHING, TYPING*/
    int first_point_bool;    /*boolean: is this first point drawn in this win?*/
    int pen_width;	     /*pen width of this display*/
    XFontStruct *the_font_struct;
    struct char_node *type_history; /*history of all typed characters*/
    GC cursor_gc;            /*graphics context for the typing cursor*/
    int prompt_width;	     /*width of the typing cursor*/
    int orig_x, char_height; /*x origin and height of current font*/
    int cursor_on;	     /*boolean indication of whether cursor is shown*/
    XPoint cur_pos;          /*current position of the typing cursor*/
    int win_width;           /*current width of this window*/
    int win_height;          /*current height of this window*/
    XPoint last_point;       /*the last point drawn in this window*/
    Atom xa_WM_DELETE_WINDOW;/*for communication with the window manager*/
    Atom xa_WM_PROTOCOLS;    /*for communication with the window manager*/
};

struct disp_info_struct disp_info[MAX_NUM_DISPS];


main (argc, argv)
int argc;
char *argv[];
{
    int i, num_people_drawing = 0;

    parse_command_line(argv, argc);
    initialize_displays(&num_people_drawing);  /*open displays, windows, etc*/

    while (1)
    {      
	block_until_input();

	do
	{
	    for (i=0; i<num_of_disps; i++)
		 if (disp_info[i].in_session_bool)
		 {
                     XFlush(disp_info[i].disp);
	             process_event(i, &num_people_drawing);
		     switch (disp_info[i].pointer_state)
		     {
		         case PRESSED:
		            draw_on_screens(i);      /*draw on ALL displays*/
			    break;
			 case IN_MENU:
			    update_menu_highlights(i);
			    break;
			 case NOT_PRESSED:
			    if (disp_info[i].scrawl_mode == TYPING)
			        type_on_screens(i);  /*type on ALL displays*/
			    break;
			 default:
			    break;
		     }
		 }
	} while (num_people_drawing);    /*while someone is still drawing*/
    }
}


/* 
 * update_menu_highlights - this function highlights the proper menu selection.
 *             It does not have any affect except visual upon any operations.
 *             However, it could entail switching between menus on the menu
 *             bar if the user has moved the mouse too far off the menu.
 *             It also is responsible for updating the "item_selected" field
 *             of the disp_info structure.
 */
update_menu_highlights(disp_num)
{
    int new_item_num, num_items, menu_num;
    Window rr, cr;
    unsigned int mskr;
    int rxr, ryr, win_x, win_y, oldy, old_item_selected;


    menu_num = disp_info[disp_num].current_menu;
    old_item_selected = disp_info[disp_num].menu[menu_num].item_selected;
    num_items = disp_info[disp_num].menu[menu_num].num_items;
    XQueryPointer(disp_info[disp_num].disp, 
		  disp_info[disp_num].menu[menu_num].win_id,
	          &rr, &cr, &rxr, &ryr, &win_x, &win_y,&mskr);
    
    /*first figure out what item the pointer is pointing at*/
    for (new_item_num=0; new_item_num < num_items; new_item_num++)
    {
        if (win_y < (new_item_num * MENU_ITEM_HEIGHT)+ MENU_ITEM_HEIGHT)
	    break;
    }

    /*only draw highlight if not the menu title, and not already selected*/
    if ((new_item_num != 0) && (new_item_num != old_item_selected))
    {
	disp_info[disp_num].menu[menu_num].item_selected = new_item_num;
	/*erase old highlight*/
	oldy = (old_item_selected * MENU_ITEM_HEIGHT) + 4;
        XDrawRectangle(disp_info[disp_num].disp, 
		   disp_info[disp_num].menu[menu_num].win_id, 
		   disp_info[disp_num].bg_menu_gc, 2, oldy,
		   MENU_ITEM_WIDTH - 4, MENU_ITEM_HEIGHT - 6);

	/*draw new highlight*/
        XDrawRectangle(disp_info[disp_num].disp, 
		   disp_info[disp_num].menu[menu_num].win_id, 
		   disp_info[disp_num].hi_menu_gc, 2, 
		   4 + (MENU_ITEM_HEIGHT * new_item_num),
		   MENU_ITEM_WIDTH - 4, MENU_ITEM_HEIGHT - 6);
    }
    else if ((new_item_num == 0) && (new_item_num != old_item_selected))
    {
	disp_info[disp_num].menu[menu_num].item_selected = new_item_num;
	/*erase old highlight*/
	oldy = (old_item_selected * MENU_ITEM_HEIGHT) + 4;
        XDrawRectangle(disp_info[disp_num].disp, 
		   disp_info[disp_num].menu[menu_num].win_id, 
		   disp_info[disp_num].bg_menu_gc, 2, oldy,
		   MENU_ITEM_WIDTH - 4, MENU_ITEM_HEIGHT - 6);
    }
    else if ((new_item_num == 0) && (new_item_num == old_item_selected))
    {
        /*
	 * The following code decides if it is necessary to release a menu,
	 * and if so, it releases the old menu and pulls down a new one.
	 */
        if ((win_x < 0) && (menu_num != 0))   /*move to the left one menu*/
	{
            disp_info[disp_num].pointer_state = BETWEEN_MENUS;
	    draw_menu(disp_num);          /*retract the old menu*/

            disp_info[disp_num].current_menu = menu_num-1;
            disp_info[disp_num].pointer_state = IN_MENU;
	    disp_info[disp_num].menu[menu_num].item_selected = 0;
	    draw_menu(disp_num);          /*draw the menus with this new info*/
	}
        else if ((win_x>(MENU_ITEM_WIDTH+1)) && (menu_num!=(NUM_OF_MENUS-1))) 
	{   /*move to the right one menu*/
            disp_info[disp_num].pointer_state = BETWEEN_MENUS;
	    draw_menu(disp_num);          /*retract the old menu*/

            disp_info[disp_num].current_menu = menu_num + 1;
            disp_info[disp_num].pointer_state = IN_MENU;
	    disp_info[disp_num].menu[menu_num].item_selected = 0;
	    draw_menu(disp_num);          /*draw the menus with this new info*/
	}
    }
}


/*
 * block_until_input - this function blocks using a "select" until someone
 *             inputs on one of the windows involved with the "wscrawling" 
 *             session.  this function returns nothing.  the rest of the 
 *             program looks at the input queues to decide whether or not
 *             to scrawl and on which windows.
 */
block_until_input()
{
    int i, read_mask, top_con_num;
    struct timeval timeout;
    int num_of_inputs;

    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    for (i = read_mask = top_con_num = 0; i<num_of_disps; i++)
    {
        if (XPending(disp_info[i].disp) && (disp_info[i].in_session_bool==TRUE))
	{
	    return(1);  /*an event is pending on a live display; don't block*/
	}
	if (disp_info[i].in_session_bool)  /*only if it is in this session*/
	{
            read_mask |= 1 << disp_info[i].connection_num;
            if (disp_info[i].connection_num > top_con_num)
		top_con_num = disp_info[i].connection_num;
	}
    }

    num_of_inputs = select(top_con_num+1, &read_mask, (int *) 0, (int *) 0,
			       &timeout);
}


/*
 * process_event - this function takes an event off the event queue and
 *             calls the appropriate routine to handle this type of event.
 */
process_event(disp_num, num_people_drawing)
int disp_num;
int *num_people_drawing;
{
    XEvent the_event;
    
    if (XPending(disp_info[disp_num].disp))
    {
        XNextEvent(disp_info[disp_num].disp, &the_event);
        switch (the_event.type)
	{
	    case ButtonPress:
		handle_ButtonPress_event(&the_event, disp_num, 
					 num_people_drawing);
		break;
	    case ButtonRelease:
		handle_ButtonRelease_event(&the_event, disp_num, 
					   num_people_drawing);
		break;
	    case KeyPress:
		handle_KeyPress_event(&the_event, disp_num);
		break;
	    case Expose:
		handle_Expose_event(&the_event, disp_num);
		break;
	    case ConfigureNotify:
		handle_ConfigureNotify_event(&the_event, disp_num);
		break;
	    case ClientMessage:
		handle_ClientMessage_event(&the_event, disp_num);
		break;
	    default:
		break;
	}
    }
}


/*
 * handle_ButtonPress_event - this function handles a ButtonPress event in
 *                   this particular scrawler's window.  It is probably a
 *                   scrawler starting to draw, but it could be a menu selection
 *                   so that is checked for also.
 */
handle_ButtonPress_event(our_event, disp_num, num_people_drawing)
XEvent *our_event;
int disp_num, *num_people_drawing;
{
    XButtonPressedEvent *the_event;
    int menu_num, i;

    the_event = (XButtonPressedEvent *) our_event;

    if (the_event->button == Button1)
    {
	if (the_event->window == disp_info[disp_num].win_id)
	{
            disp_info[disp_num].first_point_bool = TRUE; 
	    if (disp_info[disp_num].scrawl_mode == TYPING)
                disp_info[disp_num].pointer_state = NOT_PRESSED;
	    else
                disp_info[disp_num].pointer_state = PRESSED;

	    if (disp_info[disp_num].scrawl_mode == ERASING)
	    {
                XMoveResizeWindow(disp_info[disp_num].disp, 
			 disp_info[disp_num].eraser_win_id, 
			 the_event->x - disp_info[disp_num].pen_width/2, 
			 the_event->y - disp_info[disp_num].pen_width/2,
			 disp_info[disp_num].pen_width - 4,
			 disp_info[disp_num].pen_width - 4);
                XMapWindow(disp_info[disp_num].disp, 
		       disp_info[disp_num].eraser_win_id);
            }

	    if (disp_info[disp_num].cursor_on == TRUE) /*erase the cursor*/
	    {
         	XDrawLine(disp_info[disp_num].disp, 
		  disp_info[disp_num].win_id,
		  disp_info[disp_num].cursor_gc, 
		  disp_info[disp_num].cur_pos.x, 
		  disp_info[disp_num].cur_pos.y, 
		  disp_info[disp_num].cur_pos.x + 
		  disp_info[disp_num].prompt_width, 
		  disp_info[disp_num].cur_pos.y);
            }

	    if (disp_info[disp_num].scrawl_mode != TYPING)
                (*num_people_drawing)++;        /*another dude is now drawing*/
	    return(1);
	} 
	else if ((the_event->window == disp_info[disp_num].status_win_id) ||
	         (the_event->window == disp_info[disp_num].eraser_win_id))
	{                                       /*it is in the status window*/
            return(1);    /*no action taken*/
	}
	else     /*it is in a menu, determine which menu*/
	{
	    for (i=0, menu_num = -1; i<NUM_OF_MENUS; i++)
	        if (the_event->window == disp_info[disp_num].menu[i].win_id)
		{
		    menu_num = i;
		    break;
		}
	    if (menu_num == -1)
	    {
		printf("\nSome error occured.  ButtonPress in WHAT window?\n");
		return(0);
            }

	    /*if we got this far, the button press is in a menu*/
            disp_info[disp_num].current_menu = menu_num;
            disp_info[disp_num].pointer_state = IN_MENU;
	    disp_info[disp_num].menu[menu_num].item_selected = 0;
	    if (disp_info[disp_num].scrawl_mode != TYPING)
                (*num_people_drawing)++;  /*another dude is now drawing*/
	    draw_menu(disp_num);          /*draw the menus with this new info*/
	}
    }
}


/*
 * handle_Expose_event - this function handles an Expose event in any window.
 *               The most important windows are the menu and status windows,
 *               which contain text that must always be there.  Therefore,
 *               this preserves that text.
 */
handle_Expose_event(our_event, disp_num)
XEvent *our_event;
int disp_num;
{
    XExposeEvent *the_event;
    int menu_num, i;

    the_event = (XExposeEvent *) our_event;
   
    if ((the_event->window == disp_info[disp_num].win_id) ||
        (the_event->window == disp_info[disp_num].eraser_win_id))
    {
        return(1);      /*we don't care about these window expose events*/
    }
    else if (the_event->window == disp_info[disp_num].status_win_id)
    {
	draw_status_win_message(disp_num);
        return(1);
    }
    else  /*It must be in a menu.  Determine which menu and draw text.*/
    {
        for (menu_num=0; menu_num<NUM_OF_MENUS; menu_num++)
        {
            if (the_event->window == disp_info[disp_num].menu[menu_num].win_id)
	        break;
        }
    
        if (menu_num >= NUM_OF_MENUS)
        {
            printf("\nERROR: unknown window in handle_Expose_event().\n");
	    exit(0);
        }
    
        draw_menu_text(disp_num, menu_num);          /*draw the menu text*/
    }
}


/*
 * handle_KeyPress_event - this function handles a KeyPress event in
 *                   this particular scrawler's window. 
 */
handle_KeyPress_event(our_event, disp_num, num_people_drawing)
XEvent *our_event;
int disp_num, *num_people_drawing;
{
    XKeyPressedEvent *the_event;
    int menu_num, i;

    the_event = (XKeyPressedEvent *) our_event;
   
    if ((disp_info[disp_num].scrawl_mode == TYPING) &&
        (disp_info[disp_num].pointer_state != IN_MENU))
    {
         XPutBackEvent(disp_info[disp_num].disp, the_event);
    }
}


/*
 * handle_ConfigureNotify_event - this function handles the resizing of a
 *                   window.  If a person resizes their window, it resizes
 *                   ALL the windows associated with this session.  In this
 *                   way, the scrawlers share the size of their window.
 *                   NOTE: This routine is not full proof.  I never figured
 *                   out how to remove all contention problems.  For instance,
 *                   if two people resize their windows simultaneously, there
 *                   occurs this wierd bouncing action.  If you can figure 
 *                   something better than this out, you're a stud.  As for
 *                   me, it's 3am and I'm going home now.
 */
handle_ConfigureNotify_event(our_event, disp_num)
XEvent *our_event;
int disp_num;
{
    XConfigureEvent *the_event, *tmp_event, *(event_history[50]);
    XEvent *the_new_event;
    XWindowAttributes attr_ret;
    int i, history_index;
    
    the_event = (XConfigureEvent *) our_event;
   
    if ((the_event->window != disp_info[disp_num].win_id) ||
	(disp_info[disp_num].in_session_bool == FALSE))
    {
	return(0);	/*we are not interested in this event*/
    }
    
    if ((the_event->width == disp_info[disp_num].win_width) &&
        (the_event->height == disp_info[disp_num].win_height))
    {
	return(0);	/*we are not interested in this event*/
    }
    else
    {
        disp_info[disp_num].win_width = the_event->width;   /*the new height*/
        disp_info[disp_num].win_height = the_event->height;
    }

    /*
     * first go through ALL the resize events for ALL scrawl windows and
     * throw away all that do not agree exactly with this one.  Put the
     * ones that do agree back on the queue, as those are for other windows.
     *
     * this was necessary to avoid a perpetual loop that occurs if two people
     * try to resize at the same time.
     */
    the_new_event = (XEvent *) malloc (sizeof(XEvent));

    for (i=0; i<num_of_disps; i++)
    {
	history_index=0;

	if (disp_info[i].in_session_bool == TRUE)
	{
            while (XCheckTypedWindowEvent(disp_info[i].disp, 
				          disp_info[i].win_id,
				          ConfigureNotify, the_new_event))
            {
                tmp_event = (XConfigureEvent *) the_new_event;

		if ((the_event->width == tmp_event->width) &&
		    (the_event->height == tmp_event->height))
		{
		    event_history[history_index] = tmp_event;
		    history_index++;
                    the_new_event = (XEvent *) malloc (sizeof(XEvent));
		}
            }

	    /*put all the events back onto the queue*/
	    for (history_index--; history_index>=0; history_index--)
	    {
                XPutBackEvent(disp_info[i].disp, event_history[history_index]);
	        free(event_history[history_index]);
	    }
	}
    }


    for (i=0; i<num_of_disps; i++)
    {
	if (disp_info[i].in_session_bool == TRUE)
	{
	    if ((disp_info[i].win_width != the_event->width) ||
	        (disp_info[i].win_height != the_event->height))
	    {
                XResizeWindow(disp_info[i].disp, disp_info[i].win_id, 
			  the_event->width, the_event->height);
	        disp_info[i].win_width = the_event->width;
	        disp_info[i].win_height = the_event->height;

                XSync(disp_info[i].disp, False);
	    }
	}
    }

    for (i=0; i<num_of_disps; i++)
    {
	if (disp_info[i].in_session_bool == TRUE)
	{
            XSync(disp_info[i].disp, False);
	    XClearWindow(disp_info[i].disp, disp_info[i].win_id);
            XFlush(disp_info[i].disp);

	    if (disp_info[i].cursor_on == TRUE)
	    {    /*then turn it BACK on from the "clear window turnoff"*/
                 XDrawLine(disp_info[i].disp, 
		         disp_info[i].win_id,
		         disp_info[i].cursor_gc, 
		         disp_info[i].cur_pos.x, 
		         disp_info[i].cur_pos.y, 
		         disp_info[i].cur_pos.x + 
		         disp_info[i].prompt_width, 
		         disp_info[i].cur_pos.y);
            }
	}
        XSync(disp_info[i].disp, False);       /*all the XSyncs are trying*/
    }                                          /*to avoid a bad timing prob*/

    for (i=0; i<num_of_disps; i++)
    {
        XSync(disp_info[i].disp, False);
        place_and_draw_status_win(i);
    }
}


/*
 * handle_ButtonRelease_event - this function handles a ButtonRelease event in
 *                  this particular scrawler's window.  It is probably a
 *                  scrawler stopping drawing, but it could be a menu selection
 *                  so that is checked for also.
 */
handle_ButtonRelease_event(our_event, disp_num, num_people_drawing)
XEvent *our_event;
int disp_num, *num_people_drawing;
{
    XButtonPressedEvent *the_event;
    int menu_num, item_selected, tot_num_items, i;

    the_event = (XButtonPressedEvent *) our_event;

    if (the_event->button == Button1)
    {
	if (the_event->window == disp_info[disp_num].win_id)
	{
            disp_info[disp_num].pointer_state = NOT_PRESSED;
	    if (disp_info[disp_num].scrawl_mode != TYPING)
                (*num_people_drawing)--;    /*a dude has stopped drawing*/

	    if (disp_info[disp_num].scrawl_mode == ERASING)
                XUnmapWindow(disp_info[disp_num].disp, 
		       disp_info[disp_num].eraser_win_id);
	    return(1);
	} 
	else if ((the_event->window == disp_info[disp_num].status_win_id) ||
	         (the_event->window == disp_info[disp_num].eraser_win_id))
	{                                       /*it is in the status window*/
            return(1);    /*no action taken*/
	}
	else     /*it is in a menu*/
	{
	    menu_num = disp_info[disp_num].current_menu;
	    item_selected = disp_info[disp_num].menu[menu_num].item_selected;
            tot_num_items = disp_info[disp_num].menu[menu_num].num_items;

	    if (disp_info[disp_num].scrawl_mode != TYPING)
                (*num_people_drawing)--;  /*a dude has stopped input for now*/

            if ((item_selected > 0) && (item_selected < tot_num_items))
	    {
                menu_selection(disp_num, menu_num, num_people_drawing, 
			       item_selected);
	    }

            disp_info[disp_num].pointer_state = NOT_PRESSED;
	    if (disp_info[disp_num].in_session_bool)
	        draw_menu(disp_num);      /*draw the menus with this new info*/
	}
    }
}


/*
 * handle_ClientMessage_event - this function handles a ClientMessage event in
 *                   this particular scrawler's window.  It is probably a
 *                   a "WM_DESTROY" from the window manager.
 */
handle_ClientMessage_event(the_event, disp_num)
XEvent *the_event;
int disp_num;
{
    XClientMessageEvent *temp_event;
    int i, alive;

    temp_event = (XClientMessageEvent *) the_event;
    if (temp_event->data.l[0] == disp_info[disp_num].xa_WM_DELETE_WINDOW)
    {
        XDestroyWindow(disp_info[disp_num].disp, disp_info[disp_num].win_id);
        disp_info[disp_num].in_session_bool = FALSE; /*no window*/
        XFlush(disp_info[disp_num].disp);
        for (i=alive=0; i< num_of_disps; i++)
        {
           if (disp_info[i].in_session_bool)
	   {
	       alive = TRUE;
	       break;
	   }
	}
	if (alive)
	{
            for (i=0; i<num_of_disps; i++)
	        place_and_draw_status_win(i);
	}
	else
	   exit(0);/*if no one is still in session, stop running*/
    }
    else
        printf("\n\nAn Error occurred in WM/client messages.\n\n");
}


/*
 * menu_selection - this function does the appropriate actions as specified
 *              by the menu referenced by the menu_num and item_num passed as
 *              parameters.
 */
menu_selection(disp_num, menu_num, num_people_drawing, item_num)
int disp_num, menu_num, *num_people_drawing, item_num;
{
     static char temp[256];
     int i, temp_num;
     XColor scrn_def_ret, exact_def_ret;
     XFontStruct *the_font_struct;
     XWindowAttributes attr_ret;

     switch (menu_num)
     {
	 case 0:                       /*Control menu*/
	     switch (item_num)
	     {
		 case 1:	      /*scrawl*/
	             if (disp_info[disp_num].cursor_on == TRUE)
	             {
			 disp_info[disp_num].cursor_on = FALSE;
         	         XDrawLine(disp_info[disp_num].disp, 
		         disp_info[disp_num].win_id,
		         disp_info[disp_num].cursor_gc, 
		         disp_info[disp_num].cur_pos.x, 
		         disp_info[disp_num].cur_pos.y, 
		         disp_info[disp_num].cur_pos.x + 
		         disp_info[disp_num].prompt_width, 
		         disp_info[disp_num].cur_pos.y);
                     }
		     if (disp_info[disp_num].scrawl_mode == TYPING)
                         (*num_people_drawing)--;  /*stopped input for now*/
		     disp_info[disp_num].scrawl_mode = SCRAWLING;
		     disp_info[disp_num].pen_width = 8;
	             for (i=0; i<num_of_disps; i++)
	             {
			 if (disp_info[i].in_session_bool)
			 {
                             gcvalues.line_width = 8;
	                     XChangeGC(disp_info[i].disp, 
			           disp_info[disp_num].win_gc[i], 
			           GCLineWidth, &gcvalues);
			 }
	             }
		     XDefineCursor(disp_info[disp_num].disp,
		        disp_info[disp_num].win_id,
			XCreateFontCursor(disp_info[disp_num].disp, XC_dot));
		     break;
		 case 2:	      /*airbrush*/
	             if (disp_info[disp_num].cursor_on == TRUE)
	             {
			 disp_info[disp_num].cursor_on = FALSE;
         	         XDrawLine(disp_info[disp_num].disp, 
		         disp_info[disp_num].win_id,
		         disp_info[disp_num].cursor_gc, 
		         disp_info[disp_num].cur_pos.x, 
		         disp_info[disp_num].cur_pos.y, 
		         disp_info[disp_num].cur_pos.x + 
		         disp_info[disp_num].prompt_width, 
		         disp_info[disp_num].cur_pos.y);
                     }
		     if (disp_info[disp_num].scrawl_mode == TYPING)
                         (*num_people_drawing)--;  /*stopped input for now*/
		     disp_info[disp_num].scrawl_mode = AIRBRUSHING;
		     disp_info[disp_num].pen_width = 40;
		     XDefineCursor(disp_info[disp_num].disp,
		        disp_info[disp_num].win_id,
			XCreateFontCursor(disp_info[disp_num].disp, 
			XC_spraycan));
		     break;
		 case 3:	      /*type*/
		     if (disp_info[disp_num].scrawl_mode != TYPING)
                         (*num_people_drawing)++;  /*started input for now*/
		     disp_info[disp_num].scrawl_mode = TYPING;
		     XDefineCursor(disp_info[disp_num].disp,
		        disp_info[disp_num].win_id,
			XCreateFontCursor(disp_info[disp_num].disp, XC_xterm));
		     break;
		 case 4:	       /*eraser*/
	             if (disp_info[disp_num].cursor_on == TRUE)
	             {
			 disp_info[disp_num].cursor_on = FALSE;
         	         XDrawLine(disp_info[disp_num].disp, 
		         disp_info[disp_num].win_id,
		         disp_info[disp_num].cursor_gc, 
		         disp_info[disp_num].cur_pos.x, 
		         disp_info[disp_num].cur_pos.y, 
		         disp_info[disp_num].cur_pos.x + 
		         disp_info[disp_num].prompt_width, 
		         disp_info[disp_num].cur_pos.y);
                     }
		     if (disp_info[disp_num].scrawl_mode == TYPING)
                         (*num_people_drawing)--;  /*stopped input for now*/
		     disp_info[disp_num].scrawl_mode = ERASING;
		     disp_info[disp_num].pen_width = 40;
		     XDefineCursor(disp_info[disp_num].disp,
		        disp_info[disp_num].win_id,
			XCreateFontCursor(disp_info[disp_num].disp, 
			XC_draped_box));
		     break;
		 case 5:	       /*clear all windows*/
	             for (i=0; i<num_of_disps; i++)
	             {
			 if (disp_info[i].in_session_bool)
			 {
			     XGetWindowAttributes(disp_info[i].disp, 
				              disp_info[i].win_id, &attr_ret);
			     XClearArea(disp_info[i].disp, 
				    disp_info[i].win_id, 0, 0, attr_ret.width,
				    attr_ret.height, False);
                             XFlush(disp_info[i].disp);

	                     if (disp_info[i].cursor_on == TRUE)
	                     {/*we must turn it BACK ON, because XClear offed*/
         	                 XDrawLine(disp_info[i].disp, 
		                 disp_info[i].win_id,
		                 disp_info[i].cursor_gc, 
		                 disp_info[i].cur_pos.x, 
		                 disp_info[i].cur_pos.y, 
		                 disp_info[i].cur_pos.x + 
		                 disp_info[i].prompt_width, 
		                 disp_info[i].cur_pos.y);
                             }
			 }
	             }
		     break;
		 case 6:	      /*dotted line (not a valid choice)*/
		     break;
		 case 7:	      /*close window. Succeed from the session*/
		     if (disp_info[disp_num].scrawl_mode == TYPING)
                         (*num_people_drawing)--;  /*stopped input for now*/
                     XDestroyWindow(disp_info[disp_num].disp, 
				    disp_info[disp_num].win_id);
                     disp_info[disp_num].in_session_bool = FALSE; /*no window*/
                     XFlush(disp_info[disp_num].disp);
                     for (i=temp_num=0; i< num_of_disps; i++)
                     {
                        if (disp_info[i].in_session_bool)
	                {
	                    temp_num = TRUE;
	                    break;
	                }
	             }
	             if (temp_num)
		     {
                         for (i=0; i<num_of_disps; i++)
	                 {
	                     if (disp_info[i].in_session_bool)
	                         place_and_draw_status_win(i);
	                 }
		     }
		     else
	                exit(0);/*if no one is still in session, stop running*/
		     break;
	     }
	     break;
	 case 1:                       /*PenColor menu*/
	     for (i=0; i<num_of_disps; i++)
	     {
                 if (disp_info[i].in_session_bool)
	         {
                     if (!XAllocNamedColor(disp_info[i].disp, DefaultColormap(
		         disp_info[i].disp, DefaultScreen(disp_info[i].disp)), 
		         menu_text[1][item_num], &scrn_def_ret, &exact_def_ret))
	             {
		         return(0);        /*sorry, no good. No colors left.*/
	             }
                     gcvalues.foreground = scrn_def_ret.pixel;
		     XChangeGC(disp_info[i].disp, 
		           disp_info[disp_num].win_gc[i], GCForeground, 
		           &gcvalues);
		 }
	     }
	     break;
	 case 2:                       /*PenWidth menu*/
	     sscanf(menu_text[2][item_num], "%d", &temp_num);
	     disp_info[disp_num].pen_width = temp_num;
	     for (i=0; i<num_of_disps; i++)
	     {
                 if (disp_info[i].in_session_bool)
	         {
                     gcvalues.line_width = temp_num;
	             XChangeGC(disp_info[i].disp, 
			   disp_info[disp_num].win_gc[i], 
			   GCLineWidth, &gcvalues);
		 }
	     }
	     break;
	 case 3:                       /*PenCapStyle menu*/
             switch(item_num)
	     {
		 case 1: temp_num = CapRound; break;
		 case 2: temp_num = CapButt; break;
		 case 3: temp_num = CapNotLast; break;
		 case 4: temp_num = CapProjecting; break;
	     }
	     for (i=0; i<num_of_disps; i++)
	     {
                 if (disp_info[i].in_session_bool)
	         {
                     gcvalues.cap_style = temp_num;
	             XChangeGC(disp_info[i].disp, 
			   disp_info[disp_num].win_gc[i], 
			   GCCapStyle, &gcvalues);
		 }
	     }
	     break;
	 case 4:                       /*Font menu*/
	     for (i=0; i<num_of_disps; i++)
	     {
                 if (disp_info[i].in_session_bool)
	         {
	             if ((the_font_struct = XLoadQueryFont(disp_info[i].disp,
		          menu_text[4][item_num])) != NULL)
	             {
		         gcvalues.font = the_font_struct->fid;
		         disp_info[disp_num].the_font_struct = 
				                  the_font_struct;
                         gcvalues.font = the_font_struct->fid;
		         XChangeGC(disp_info[i].disp, 
		                   disp_info[disp_num].win_gc[i], GCFont, 
		                   &gcvalues);
	             }
		     else 
		         printf("Couldn't allocate font.\n");
		 }
	     }

	     if (disp_info[disp_num].cursor_on == TRUE)     /*turn it off   */
	     {						    /*and don't tell*/
                 XDrawLine(disp_info[disp_num].disp, 
		     disp_info[disp_num].win_id,
	             disp_info[disp_num].cursor_gc, 
	             disp_info[disp_num].cur_pos.x, 
	             disp_info[disp_num].cur_pos.y, 
	             disp_info[disp_num].cur_pos.x + 
	             disp_info[disp_num].prompt_width, 
	             disp_info[disp_num].cur_pos.y);
             }

             disp_info[disp_num].prompt_width = 
	               disp_info[disp_num].the_font_struct->max_bounds.width;
	     disp_info[disp_num].char_height = 
	           (disp_info[disp_num].the_font_struct)->max_bounds.ascent +
	           (disp_info[disp_num].the_font_struct)->max_bounds.descent;
	     disp_info[disp_num].type_history = NULL;
         
	     if (disp_info[disp_num].cursor_on == TRUE)   /*it is really off*/
	     {                                            /*'cause look above*/
	         disp_info[disp_num].cursor_on = TRUE;
                 XDrawLine(disp_info[disp_num].disp, 
		     disp_info[disp_num].win_id,
	             disp_info[disp_num].cursor_gc, 
	             disp_info[disp_num].cur_pos.x, 
	             disp_info[disp_num].cur_pos.y, 
	             disp_info[disp_num].cur_pos.x + 
	             disp_info[disp_num].prompt_width, 
	             disp_info[disp_num].cur_pos.y);
             }
	     break;
     }
}


/*
 * draw_menu_text - this function draws the menu text specified by the display
 *              and the menu_num.  This is usually called in response to
 *              an expose event, etc.
 */
draw_menu_text(disp_num, menu_num)
int disp_num, menu_num;
{
    int i;

    /*
     * draw the bar under the menu heading
     */
    XDrawLine(disp_info[disp_num].disp,
	      disp_info[disp_num].menu[menu_num].win_id,
	      disp_info[disp_num].fg_menu_gc, 0, MENU_ITEM_HEIGHT+1, 
	      MENU_ITEM_WIDTH, MENU_ITEM_HEIGHT+1);

    /*
     * draw all the text
     */
    for (i=0; i<disp_info[disp_num].menu[menu_num].num_items; i++)
    {
        XDrawString(disp_info[disp_num].disp,
	       disp_info[disp_num].menu[menu_num].win_id, 
	       disp_info[disp_num].fg_menu_gc, 7, i*MENU_ITEM_HEIGHT + 18, 
	       menu_text[menu_num][i], strlen(menu_text[menu_num][i]));
    }
    XFlush(disp_info[disp_num].disp);
}


/*
 * draw_menu - this function draws the menus on the display whose index is
 *              passed as the argument.  This function is invoked usually
 *              when someone has just boinked the menu, causing it to unroll
 *              out with all the choices.  It can also be invoked when someone
 *              has let up the mouse button, probably making a choice. 
 *              The "current_menu" field of the disp_info struct should be
 *              set to the menu which changed.
 */
draw_menu(disp_num)
int disp_num;
{
    int i, height, menu_num;
    XWindowChanges new_dim;

    menu_num = disp_info[disp_num].current_menu;

    if (disp_info[disp_num].pointer_state != IN_MENU)    /*retract menu*/
    {
        new_dim.height = MENU_ITEM_HEIGHT;
    }
    else                                                 /*extend a menu*/
    {
        new_dim.height = disp_info[disp_num].menu[menu_num].num_items * 
		     MENU_ITEM_HEIGHT;
    }
    XConfigureWindow(disp_info[disp_num].disp, 
	       disp_info[disp_num].menu[menu_num].win_id, CWHeight, &new_dim);

    /*
     * The following is commented out because an expose event will occur,
     * causing the menu text to be displayed WITHOUT this line.  Why keep
     * it here you ask me?  Well, I can't answer that; it would reveal the
     * location of insidious bugs in certain HP systems.  :-)
     */
    /*
    draw_menu_text(disp_num, menu_num);
     */
}


/*
 * type_on_screens - this function types on the screens of all the people
 *                   by reading if any keys have been depressed, and if so,
 *                   it puts that character up on all the displays currently
 *                   open.  <-- Darn smart, eh?  You probably couldn't have
 *                   figured out that on your own.  ;-)
 */
type_on_screens(input_disp)
int input_disp;
{
    XEvent the_event;
    Window rr, cr;                 /* <-- These here are variables, son.*/
    KeySym ksr;
    unsigned int mskr;
    int i, rxr, ryr, win_x, win_y;
    char the_char[256], back_space_char;

    if (disp_info[input_disp].first_point_bool)
    {
        XQueryPointer(disp_info[input_disp].disp, 
			  disp_info[input_disp].win_id, &rr, &cr, &rxr, 
			  &ryr, &win_x, &win_y,&mskr);
	disp_info[input_disp].orig_x = win_x;
        disp_info[input_disp].cur_pos.x = win_x;
        disp_info[input_disp].cur_pos.y = win_y;
        disp_info[input_disp].first_point_bool = FALSE;
        disp_info[input_disp].prompt_width = 
	         disp_info[input_disp].the_font_struct->max_bounds.width;
	disp_info[input_disp].char_height = 
	   (disp_info[input_disp].the_font_struct)->max_bounds.ascent +
	   (disp_info[input_disp].the_font_struct)->max_bounds.descent;
	disp_info[input_disp].type_history = NULL;

	/*draw the cursor*/
	disp_info[input_disp].cursor_on = TRUE;
	XDrawLine(disp_info[input_disp].disp, disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);
    }

    if (XPending(disp_info[input_disp].disp))  /*check if there is a KeyPress*/
    {
	XNextEvent(disp_info[input_disp].disp, &the_event);
        if (the_event.type != KeyPress)
	{
            XPutBackEvent(disp_info[input_disp].disp, &the_event);
	    return(0);
	}
	else    /*it IS a KeyPressedEvent*/
	{
            the_char[0] = 0;
            XLookupString((XKeyEvent *) (&the_event),the_char,10, &ksr, NULL);
            if (the_char[0] == 0)
                return(0);                /*it was a shift or some such*/
            if (the_char[0] == 13)       /*13 == Carriage Return*/
            {
	        /*erase the cursor*/
	        disp_info[input_disp].cursor_on = FALSE;
	        XDrawLine(disp_info[input_disp].disp, 
		  disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);

	        push_letter_to_history(input_disp, the_char[0]);

                disp_info[input_disp].cur_pos.x = disp_info[input_disp].orig_x;
                disp_info[input_disp].cur_pos.y += 
					     disp_info[input_disp].char_height;

	        /*draw the cursor*/
	        disp_info[input_disp].cursor_on = TRUE;
	        XDrawLine(disp_info[input_disp].disp, 
		  disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);
            }
            else if (the_char[0] == 8)  /*8 == Backspace*/
            {
	        /*erase the cursor*/
	        disp_info[input_disp].cursor_on = FALSE;
	        XDrawLine(disp_info[input_disp].disp, 
		  disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);
		backspace_letter_from_history(input_disp);

	        /*draw the cursor*/
	        disp_info[input_disp].cursor_on = TRUE;
	        XDrawLine(disp_info[input_disp].disp, 
		  disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);
            }
            else     /*it is a valid letter*/
            {
		push_letter_to_history(input_disp, the_char[0]);

	        /*erase the cursor*/
	        disp_info[input_disp].cursor_on = FALSE;
	        XDrawLine(disp_info[input_disp].disp, 
		  disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);

                for (i=0; i< num_of_disps; i++)
                {
                    if (disp_info[i].in_session_bool) /*if window is alive*/
                    {
	                XDrawString(disp_info[i].disp, disp_info[i].win_id,
		            disp_info[input_disp].win_gc[i], 
			    disp_info[input_disp].cur_pos.x, 
			    disp_info[input_disp].cur_pos.y, the_char, 1);
                        XFlush(disp_info[i].disp);
                    }
                }
		disp_info[input_disp].cur_pos.x += 
		             XTextWidth(disp_info[input_disp].the_font_struct, 
		             the_char, 1);

	        /*draw the cursor*/
	        disp_info[input_disp].cursor_on = TRUE;
	        XDrawLine(disp_info[input_disp].disp, 
		  disp_info[input_disp].win_id,
		  disp_info[input_disp].cursor_gc, 
		  disp_info[input_disp].cur_pos.x, 
		  disp_info[input_disp].cur_pos.y, 
		  disp_info[input_disp].cur_pos.x + 
		  disp_info[input_disp].prompt_width, 
		  disp_info[input_disp].cur_pos.y);
            }
	}
    }
}


/*
 * push_letter_to_history - this function takes a pointer to the top of
 *              the history stack and adds the character passed in to that
 *              stack.  This is to save the characters for future backspacing.
 */
push_letter_to_history(input_disp, the_char)
int input_disp;
char the_char;
{
    struct char_node *temp;

    temp = (struct char_node *) malloc(sizeof(struct char_node));
    temp->the_char = the_char;
    temp->next = disp_info[input_disp].type_history;

    if (the_char == 13)   /*carriage return*/
    {
	temp->x = disp_info[input_disp].cur_pos.x;
	temp->y = disp_info[input_disp].cur_pos.y;
    }

    disp_info[input_disp].type_history = temp;
}


/*
 * backspace_letter_from_history - this function returns the most recently 
 *                 typed character from the history stack, and sets the top of
 *                 the stack pointer appropriately.
 */
backspace_letter_from_history(input_disp)
int input_disp;
{
    int dr, far, fdr, i;
    struct char_node *char_node;
    XCharStruct the_fnt_strct;

    if (disp_info[input_disp].type_history == NULL)
	return(0);
    else
    {
        char_node = disp_info[input_disp].type_history;

        if (char_node->the_char == 13)     /*it was a carriage return*/
	{
	    disp_info[input_disp].cur_pos.x = char_node->x;
	    disp_info[input_disp].cur_pos.y = char_node->y;
	}
        else                               /*it was a normal letter*/
	{
            disp_info[input_disp].cur_pos.x -= XTextWidth(
	      disp_info[input_disp].the_font_struct,&(char_node->the_char), 1);

            XTextExtents(disp_info[input_disp].the_font_struct, 
	      &(char_node->the_char), 1, &dr, &far, &fdr, &the_fnt_strct);

            for (i=0; i< num_of_disps; i++)
	    {
	        XClearArea(disp_info[i].disp, disp_info[i].win_id, 
		      disp_info[input_disp].cur_pos.x,
		      disp_info[input_disp].cur_pos.y - the_fnt_strct.ascent,
		      the_fnt_strct.lbearing + the_fnt_strct.rbearing,
		      the_fnt_strct.ascent + the_fnt_strct.descent, False);
	    }
	}

	/* now pop the top char node from the history stack */
        disp_info[input_disp].type_history = 
				      disp_info[input_disp].type_history->next;
        free(char_node);
    }
}


/*
 * draw_status_win_message - this function spits (in camel like fashion) out 
 *                      the status window message to the display indicated.
 */
draw_status_win_message(disp_num)
{
    int i, num_disps;
    char mesg[510], *cur_loc_ptr;

    for (i=0, num_disps=0; i< num_of_disps; i++)
	if (disp_info[i].in_session_bool)  /*only if this window is alive*/
            num_disps++;

    if (num_disps == 1)
        sprintf(mesg, "STATUS:  1 scrawl window currently open.  -->  ");
    else
        sprintf(mesg, "STATUS:  %d scrawl windows currently open.  -->  ", 
		num_disps);

    cur_loc_ptr = mesg + strlen(mesg);

    for (i=0; i< num_of_disps; i++)
    {
	if (disp_info[i].in_session_bool)  /*only if this window is alive*/
	{
	    strncpy(cur_loc_ptr, disp_args[i], 25);
	    cur_loc_ptr += strlen(disp_args[i]);
	    cur_loc_ptr[0] = ',';
	    cur_loc_ptr[1] = ' ';          /*space delineates*/
	    cur_loc_ptr += 2;
	}
    }
    cur_loc_ptr -= 2;
    cur_loc_ptr[0] = '\0';     /*end the string*/

    XClearWindow(disp_info[disp_num].disp, disp_info[disp_num].status_win_id);
    XDrawString(disp_info[disp_num].disp, disp_info[disp_num].status_win_id, 
		   disp_info[disp_num].fg_menu_gc, 10, 14, mesg, strlen(mesg));
    XFlush(disp_info[disp_num].disp);
}


/*
 * place_and_draw_status_win - this function places the status window at the
 *                      correct location on the scrawl window, and draws
 *                      the contents.  (Also updates the contents of all
 *                      other scrawl windows, but DOES NOT change their 
 *                      locations.)
 */
place_and_draw_status_win(disp_num)
int disp_num;
{
    int i, num_disps;
    XWindowAttributes window_attr;
    char mesg[510], *cur_loc_ptr;

    if (disp_info[disp_num].in_session_bool == FALSE)
	return(0);    /*This window is no longer active*/
	
    XGetWindowAttributes(disp_info[disp_num].disp, 
			 disp_info[disp_num].win_id, &window_attr);
    XMoveResizeWindow(disp_info[disp_num].disp, 
			 disp_info[disp_num].status_win_id, 
			 0, window_attr.height - 24, window_attr.width-4, 20);

    draw_status_win_message(disp_num);
}


/*
 * draw_on_screens - this function draws the segment that is indicated
 *                   by the current mouse position and previous mouse 
 *                   position on the display passed to it as an argument.
 *                   This segment is drawn to ALL displays currently open.
 *                   If this dude is airbrushing, then of course an airbrush
 *                   pattern is sprayed all over, and if it is an eraser,
 *                   then erasing happens.
 */
draw_on_screens(input_disp)
int input_disp;
{
    Window rr, cr;
    unsigned int mskr;
    int i, rxr, ryr, win_x, win_y;

    if (disp_info[input_disp].scrawl_mode == SCRAWLING)
    {
        if (disp_info[input_disp].first_point_bool)
        {
            XQueryPointer(disp_info[input_disp].disp, 
			  disp_info[input_disp].win_id, &rr, &cr, &rxr, 
			  &ryr, &win_x, &win_y,&mskr);
	    disp_info[input_disp].last_point.x = win_x;
	    disp_info[input_disp].last_point.y = win_y;
	    disp_info[input_disp].first_point_bool = FALSE;
        }

        XQueryPointer(disp_info[input_disp].disp, disp_info[input_disp].win_id, 
                      &rr, &cr, &rxr, &ryr, &win_x, &win_y, &mskr);
        for (i=0; i< num_of_disps; i++)
        {
	    if (disp_info[i].in_session_bool) /*only if this window is alive*/
	    {
                XDrawLine(disp_info[i].disp,disp_info[i].win_id,
		          disp_info[input_disp].win_gc[i],
	                  disp_info[input_disp].last_point.x, 
	                  disp_info[input_disp].last_point.y, win_x, win_y);
                XFlush(disp_info[i].disp);
	    }
        }
    
        disp_info[input_disp].last_point.x = win_x;
        disp_info[input_disp].last_point.y = win_y;
    }
    else if (disp_info[input_disp].scrawl_mode == AIRBRUSHING)
    {
        XQueryPointer(disp_info[input_disp].disp, disp_info[input_disp].win_id, 
                      &rr, &cr, &rxr, &ryr, &win_x, &win_y, &mskr);
        for (i=0; i< num_of_disps; i++)
        {
	    if (disp_info[i].in_session_bool) /*only if this window is alive*/
	    {
                airbrush(input_disp, disp_info[i].disp, disp_info[i].win_id, 
		         disp_info[input_disp].win_gc[i], win_x, win_y);
                XFlush(disp_info[i].disp);
	    }
        }
    }
    else if (disp_info[input_disp].scrawl_mode == ERASING)
    {
        XQueryPointer(disp_info[input_disp].disp, disp_info[input_disp].win_id, 
                      &rr, &cr, &rxr, &ryr, &win_x, &win_y, &mskr);
	XMoveWindow(disp_info[input_disp].disp, 
		    disp_info[input_disp].eraser_win_id, 
		    win_x - disp_info[input_disp].pen_width/2, 
		    win_y - disp_info[input_disp].pen_width/2);

        for (i=0; i< num_of_disps; i++)
        {
	    if (disp_info[i].in_session_bool) /*only if this window is alive*/
	    {
                XClearArea(disp_info[i].disp, disp_info[i].win_id, 
			  win_x - disp_info[input_disp].pen_width/2, 
			  win_y - disp_info[input_disp].pen_width/2, 
			  disp_info[input_disp].pen_width, 
			  disp_info[input_disp].pen_width, False);
                XFlush(disp_info[i].disp);
	    }
        }
    }
}


/*
 * parse_command_line - this function parses the command line and sets the
 *             various global variables for the opening display
 *             and color party coming up.  Sorry that this routine is so
 *             ugly, but just go slow, and it's not too bad.  I'd look at
 *             the "command_line_err()" routine to understand what each
 *             of the command line options mean.
 *             (ex. % wscrawl -d hpcvxbw:0 -d hpcvxca:0 -pc red -cs CapRound
 *                    -pw 45 -bs off -nd 50 -ps airbrush)
 */
parse_command_line(argv, argc)
char *argv[];
int argc;
{
     int i, current_arg, W_SET = 0;

     strncpy(PEN_COLOR, "magenta", 40);            /*defaults set here*/
     strncpy(BACKGROUND_COLOR, "white", 40);
     strncpy(MENU_FOREGROUND_COLOR, "black", 40);
     strncpy(MENU_BACKGROUND_COLOR, "wheat", 40);
     strncpy(MENU_HIGHLIGHT_COLOR, "black", 40);
     strncpy(PEN_STYLE, "dot", 40);
     strncpy(FONT, "vgl-40", 100);
     TYPE_NOT_DRAW = FALSE;
     CAP_STYLE = CapRound;
     NUM_DOTS = 50;
     for (i=0; i< num_of_disps; i++)
     {
         disp_info[i].pen_width = 8;
     }

     for (current_arg = 1; current_arg < argc; current_arg+=2)
     {
	 if (strcmp(argv[current_arg],"-d") == 0)
	 {
	     strncpy(disp_args[num_of_disps], argv[current_arg+1], 48);
	     num_of_disps++;
         }
	 else if (strcmp(argv[current_arg],"-mfg") == 0)
	 {
	     if (current_arg != argc-1)
	         strncpy(MENU_FOREGROUND_COLOR, argv[current_arg+1], 40);
	     else
		 command_line_err();     /*no color was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-mbg") == 0)
	 {
	     if (current_arg != argc-1)
	         strncpy(MENU_BACKGROUND_COLOR, argv[current_arg+1], 40);
	     else
		 command_line_err();     /*no color was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-mhc") == 0)
	 {
	     if (current_arg != argc-1)
	         strncpy(MENU_HIGHLIGHT_COLOR, argv[current_arg+1], 40);
	     else
		 command_line_err();     /*no color was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-bg") == 0)
	 {
	     if (current_arg != argc-1)
	         strncpy(BACKGROUND_COLOR, argv[current_arg+1], 40);
	     else
		 command_line_err();     /*no color was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-pc") == 0)
	 {
	     if (current_arg != argc-1)
	         strncpy(PEN_COLOR, argv[current_arg+1], 40);
	     else
		 command_line_err();     /*no color was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-fn") == 0)
	 {
	     if (current_arg != argc-1)
	         strncpy(FONT, argv[current_arg+1], 220);
	     else
		 command_line_err();     /*no color was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-tm") == 0)
	 {
	     TYPE_NOT_DRAW = TRUE;
 	     current_arg--;  /*type message is an argumentless option*/
	 }
	 else if (strcmp(argv[current_arg],"-nd") == 0)
	 {
	     if (current_arg != argc-1)
	     {
	         sscanf(argv[current_arg+1], "%d", &NUM_DOTS);
		 if ((NUM_DOTS < 1) || (NUM_DOTS > 500))
		     command_line_err(); /*incorrect range*/
	     }
	     else
		 command_line_err();     /*no number was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-ps") == 0)
	 {
	     if (current_arg != argc-1)
	     {
		 if ((strcmp(argv[current_arg+1],"dot") == 0) || 
		     (strcmp(argv[current_arg+1],"airbrush")) == 0)
		 {
		    strncpy(PEN_STYLE, argv[current_arg+1], 40);
		    if ((strcmp(PEN_STYLE,"airbrush") == 0) && !W_SET)
			disp_info[0].pen_width = 40; /*default penwidth*/
		 }
		 else
		     command_line_err();     /*incorrect pen style specified*/
	     }
	     else
		 command_line_err();     /*no pen style was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-pw") == 0)
	 {
	     if (current_arg != argc-1)
	     {
	         sscanf(argv[current_arg+1], "%d", 
			&(disp_info[0].pen_width));
		 W_SET = TRUE;           /*the width has been set*/
	     }
	     else
		 command_line_err();     /*no pen width was specified*/
	 }
	 else if (strcmp(argv[current_arg],"-cs") == 0)
	 {
	     if (current_arg != argc-1)
	     {
		 if (strcmp(argv[current_arg+1],"CapButt") == 0)
		     CAP_STYLE = CapButt;
		 else if (strcmp(argv[current_arg+1],"CapNotLast") == 0)
		     CAP_STYLE = CapNotLast;
		 else if (strcmp(argv[current_arg+1],"CapRound") == 0)
		     CAP_STYLE = CapRound;
		 else if (strcmp(argv[current_arg+1],"CapProjecting") == 0)
		     CAP_STYLE = CapProjecting;
		 else
                     command_line_err();     /*bad cap style was specified*/
	     }
	     else
                 command_line_err();     /*no cap style was specified*/
	 }
	 else
	 {
             command_line_err();
	 }
     }
}


/* 
 * command_line_err - this function spits out a standard error message and
 *                    exits the program.
 */
command_line_err()
{
    printf("\nBad command line option dude.\n");
    printf("Try: wscrawl\n");
    printf("  [-d displayname1 -d displayname2 . . .]\n");
    printf("  [-bg background_color]\n");
    printf("  [-mfg menu_foreground_color]\n");
    printf("  [-mbg menu_background_color]\n");
    printf("  [-mhc menu_highlight_color]\n");
    printf("  [-pc pen_color]\n");
    printf("  [-pw pen_width]\n");
    printf("  [-ps pen_style]  (dot,airbrush)\n");
    printf("  [-cs cap_style]  (CapButt,CapNotLast,CapRound,CapProjecting)\n");
    printf("  [-nd num_dots]   (number of dots in the airbrush (1-500))\n");
    printf("  [-tm]            (type a message, don't draw)\n");
    printf("  [-fn font]       (name of font to use if typing a message)\n\n");
    exit(0);
}


/*
 * initialize_displays - this function opens all the displays, including
 *                       the home one, opens a window on each and assigns
 *                       each window a gc, window id, menus, etc.
 */
initialize_displays(num_people_drawing)
int *num_people_drawing;
{
    unsigned long alter_these_mask = GCSubwindowMode | GCLineWidth | 
				     GCForeground | GCCapStyle | GCFont |
				     GCBackground;
    unsigned long alter_menu_mask =  GCSubwindowMode | GCForeground | GCFont |
				     GCBackground | GCLineWidth;
    unsigned long alter_these2_mask = CWEventMask | CWBackPixel | 
				   CWBackingStore | CWWinGravity |
				   CWBitGravity | CWSaveUnder;
    unsigned long alter_menu2_mask = CWEventMask | CWBackPixel | 
				     CWBackingStore | CWWinGravity;
    XSizeHints win_size_hints;
    Cursor the_cursor, menu_cursor;
    XColor scrn_def_ret, exact_def_ret, scrn_def_ret2, exact_def_ret2;
    int i, j, k, menu_num;
    char mesg[256];
    int menu_x_val;
    XWindowAttributes attr_ret;
    XFontStruct *the_font_struct;
    char tmp_str[50], tmp_str2[50];
    XGCValues cursor_gc_values;

    menu_text[0] = menu_text0;   /*connect the menu text to the global struct*/
    menu_text[1] = menu_text1;
    menu_text[2] = menu_text2;
    menu_text[3] = menu_text3;
    menu_text[4] = menu_text4;

    /*
     * The following opens all the displays and windows and initializes some
     * of the disp_info fields.  The cursor is defined for the window, and
     * the window manager is instructed how to behave with these new windows.
     */
    for (i=0; i<num_of_disps; i++)
    { 
	if (i==0)     /*open home display*/
	{
            if ((disp_info[0].disp = XOpenDisplay (NULL)) == NULL)
            {
                printf("XOpenDisplay failed on HOME Display.\n");
                exit(0);
            }
	    strncpy(disp_args[0], DisplayString(disp_info[0].disp), 30);
	}
	else         /*open this particular away display*/
	{
            if ((disp_info[i].disp = XOpenDisplay (disp_args[i])) == NULL)
            {
                printf("XOpenDisplay failed on display: %s\n", disp_args[i]);
                exit(0);
            }
        }

	winvals.backing_store = Always;
        if (!XAllocNamedColor(disp_info[i].disp, 
		DefaultColormap(disp_info[i].disp, 
	        DefaultScreen(disp_info[i].disp)), BACKGROUND_COLOR, 
	        &scrn_def_ret, &exact_def_ret))
        {
	     printf("\nThe color %s is not supported", BACKGROUND_COLOR);
	     printf(" on the display %s.\n", disp_args[i]);
	     exit(0);
	}
	winvals.background_pixel = scrn_def_ret.pixel;

	disp_info[i].win_id = XCreateWindow(disp_info[i].disp, RootWindow(
		  disp_info[i].disp, DefaultScreen(disp_info[i].disp)), 
		  5+(i*80), 5+(i*80), WSCRAWL_WIN_WIDTH, WSCRAWL_WIN_HEIGHT, 3,
		  0,CopyFromParent,CopyFromParent,alter_these2_mask, &winvals);


        disp_info[i].win_gc = (GC *) malloc(num_of_disps * sizeof(GC));

	disp_info[i].connection_num = ConnectionNumber(disp_info[i].disp);
        disp_info[i].first_point_bool = FALSE;
        disp_info[i].pointer_state = NOT_PRESSED;
	disp_info[i].scrawl_mode = SCRAWLING;
	disp_info[i].in_session_bool = TRUE;
        disp_info[i].type_history = NULL;

	cursor_gc_values.line_width = 5;
	cursor_gc_values.function = GXinvert;
	cursor_gc_values.background =  BlackPixelOfScreen(
		               DefaultScreenOfDisplay(disp_info[i].disp));
        cursor_gc_values.foreground =  WhitePixelOfScreen(
		               DefaultScreenOfDisplay(disp_info[i].disp));
        disp_info[i].cursor_gc = XCreateGC(disp_info[i].disp, 
	              disp_info[i].win_id,
	              GCFunction | GCBackground | GCForeground | GCLineWidth, 
		      &cursor_gc_values);

        if (!TYPE_NOT_DRAW)
            the_cursor = XCreateFontCursor(disp_info[i].disp, XC_dot);

        XDefineCursor(disp_info[i].disp, disp_info[i].win_id, the_cursor);

	win_size_hints.flags = USPosition | PPosition;
	win_size_hints.x = 5 + (i * 80);
	win_size_hints.y = 5 + (i * 80);

	XSetStandardProperties(disp_info[i].disp, disp_info[i].win_id, 
	        "WSCRAWL: a product of eXcess engineering", "wscrawl", None, 
		NULL, 0, &win_size_hints);

	/* the following three calls are to change window manager behavior*/
        disp_info[i].xa_WM_PROTOCOLS = XInternAtom(disp_info[i].disp, 
						   "WM_PROTOCOLS", FALSE);
        disp_info[i].xa_WM_DELETE_WINDOW = XInternAtom(disp_info[i].disp, 
					            "WM_DELETE_WINDOW", FALSE);
        XChangeProperty(disp_info[i].disp, disp_info[i].win_id, 
			disp_info[i].xa_WM_PROTOCOLS, 
			disp_info[i].xa_WM_PROTOCOLS, 32, PropModeReplace,
		        (char *) &(disp_info[i].xa_WM_DELETE_WINDOW), 1);
    }

    /*
     * The following changes the names in the "disp_args[]" array to reflect
     * the ACTUAL machine and display instead of things like "local:0" etc.
     */
    for (i=0; i<num_of_disps; i++)
    { 
        if ( (strncmp(disp_args[i], "local", 5) == 0) ||
            (strncmp(disp_args[i], "unix", 4) == 0) ||
            (strncmp(disp_args[i], ":", 1) == 0))
	{
            /*
	     * this is a wscrawl window on the invoker's home display, so
	     * change it to be meaningful.
	     */
            gethostname(tmp_str, 30);
            sprintf(tmp_str2, "%s%s", tmp_str, strpbrk(disp_args[i], ":"));
            strncpy(disp_args[i], tmp_str2, 30);
	}
    }

    /*
     * The following initializes the win_gc's for all the displays.
     */
    for (i=0; i<num_of_disps; i++)
    { 
	/*
	 * The following assigns this "user" N graphics contexts, where N
	 * is the number of open displays.  This "user" wants his particular
	 * color on every display, and since gc's are attached to displays,
	 * we need one with this color loaded on every display.  These gc's
	 * are then used for pen width, pen caps, etc.
	 */
	for (j=0; j<num_of_disps; j++)
	{
            if (XAllocNamedColor(disp_info[j].disp, DefaultColormap(
		 disp_info[j].disp, DefaultScreen(disp_info[j].disp)), 
		 PEN_COLOR, &scrn_def_ret, &exact_def_ret))
	    {
		;  /*great!  it worked!*/
	    }
	    else if (!XAllocNamedColor(disp_info[j].disp, DefaultColormap(
		 disp_info[j].disp, DefaultScreen(disp_info[j].disp)), 
		 "black", &scrn_def_ret, &exact_def_ret))
	    {
	        printf("The color %s is not", PEN_COLOR);
	        printf(" supported on the display %s.\n", disp_args[i]);
	        exit(0);
	    }
            gcvalues.foreground = scrn_def_ret.pixel;

            gcvalues.background = WhitePixelOfScreen(DefaultScreenOfDisplay(
						 disp_info[j].disp));
	    if (j == 0)
		gcvalues.line_width = disp_info[0].pen_width;
            else
                gcvalues.line_width = 8;          /*all capital names set in*/
            gcvalues.cap_style = CAP_STYLE;       /*"parse_command_line()"*/

            if ((the_font_struct = XLoadQueryFont(disp_info[j].disp, FONT)) 
		!= NULL)
	    {
	         gcvalues.font = the_font_struct->fid;
	    }
	    else if ((the_font_struct = XLoadQueryFont(disp_info[j].disp, 
		"fixed")) != NULL)
	    {
	         printf("WARNING: Couldn't allocate font: %s", FONT);
	         printf(" on display %s.  Using fixed.\n", disp_args[j]);
	         gcvalues.font = the_font_struct->fid;
	    }
	    else
	    {
	        printf("\nERROR: Couldn't allocate a font ");
		printf("on display: %s.\n", disp_args[j]);
	        exit(0);
            }

            disp_info[i].win_gc[j] = XCreateGC(disp_info[j].disp, 
			     disp_info[j].win_id, alter_these_mask, &gcvalues);
	    disp_info[i].the_font_struct = the_font_struct;
	}
    }

    /*
     * create and initialize the menus on each scrawl window
     */
    for (i=0; i<num_of_disps; i++)
    { 
	for (menu_num=0; menu_num<NUM_OF_MENUS; menu_num++)
	{
            if (XAllocNamedColor(disp_info[i].disp, 
		DefaultColormap(disp_info[i].disp, 
	        DefaultScreen(disp_info[i].disp)), MENU_BACKGROUND_COLOR, 
	        &scrn_def_ret, &exact_def_ret))
            {
	        menuwinvals.background_pixel = scrn_def_ret.pixel;
	    }
            else if (XAllocNamedColor(disp_info[i].disp, 
		DefaultColormap(disp_info[i].disp, 
	        DefaultScreen(disp_info[i].disp)), "white", 
	        &scrn_def_ret, &exact_def_ret))
            {
	        printf("The color %s is not", MENU_BACKGROUND_COLOR);
	        printf(" supported on display %s.\n", disp_args[i]);
	        printf("Using white for the menu background color.\n");
		strcpy(MENU_BACKGROUND_COLOR, "white");
	        menuwinvals.background_pixel = scrn_def_ret.pixel;
	    }
	    else
	    {
	        printf("The menu background color had extra problems on the");
		printf(" display %s.\n", disp_args[i]);
		exit(0);
            }

	    menu_x_val = menu_num*MENU_ITEM_WIDTH + (menu_num*2);

	    /* XXX uncomment this line to reproduce HP backingstore bug
	    menuwinvals.backing_store = Always;
             */

	    disp_info[i].menu[menu_num].win_id =XCreateWindow(disp_info[i].disp,
		   disp_info[i].win_id, menu_x_val, 0, 
		   MENU_ITEM_WIDTH, MENU_ITEM_HEIGHT, 2, 0, CopyFromParent, 
		   CopyFromParent, alter_menu2_mask, &menuwinvals);
            XMapWindow(disp_info[i].disp, disp_info[i].menu[menu_num].win_id);

	    menu_cursor = XCreateFontCursor(disp_info[i].disp, XC_right_ptr);
	    XDefineCursor(disp_info[i].disp, disp_info[i].menu[menu_num].win_id,
			  menu_cursor);

	    disp_info[i].menu[menu_num].item_selected = NO_ITEM_SELECTED;
        }

	/*
	 * find the number of items in each menu and assign it to the correct
	 * variable in the structure. "k" is the menu_item_num.
	 */
	for (menu_num=0; menu_num < NUM_OF_MENUS; menu_num++)
	{
	    for (k=0; strcmp(menu_text[menu_num][k], "****") != 0; k++)
	        ;
            disp_info[i].menu[menu_num].num_items = k;
        }

        if (!XAllocNamedColor(disp_info[i].disp, 
		DefaultColormap(disp_info[i].disp, 
	        DefaultScreen(disp_info[i].disp)), MENU_HIGHLIGHT_COLOR, 
	        &scrn_def_ret, &exact_def_ret))
        {
	        printf("The color %s is not", MENU_HIGHLIGHT_COLOR);
	        printf(" supported on the display %s.\n", disp_args[i]);
	        exit(0);
        }
        menugcvalues.foreground = scrn_def_ret.pixel;

        if (!XAllocNamedColor(disp_info[i].disp, 
	    DefaultColormap(disp_info[i].disp, 
	    DefaultScreen(disp_info[i].disp)), MENU_BACKGROUND_COLOR, 
	    &scrn_def_ret2, &exact_def_ret2))
        {
	    printf("The menu background color had second round problems ");
    	    printf("on the display %s.\n", disp_args[i]);
	    exit(0);
        }

        menugcvalues.background = scrn_def_ret2.pixel;

        menugcvalues.line_width = 2;
        menugcvalues.font = XLoadFont(disp_info[i].disp, "fixed");
	disp_info[i].hi_menu_gc = XCreateGC(disp_info[i].disp, 
	           disp_info[i].menu[0].win_id, alter_menu_mask, &menugcvalues);

	/*
	 * switch foreground and background colors for bg_menu_gc
	 */
        menugcvalues.foreground = scrn_def_ret2.pixel;
        menugcvalues.background = scrn_def_ret.pixel;
	disp_info[i].bg_menu_gc = XCreateGC(disp_info[i].disp, 
	           disp_info[i].menu[0].win_id, alter_menu_mask, &menugcvalues);

        if (!XAllocNamedColor(disp_info[i].disp, 
		DefaultColormap(disp_info[i].disp, 
	        DefaultScreen(disp_info[i].disp)), MENU_FOREGROUND_COLOR, 
	        &scrn_def_ret, &exact_def_ret))
        {
	        printf("The color %s is not", MENU_FOREGROUND_COLOR);
	        printf(" supported on the display %s.\n", disp_args[i]);
	        exit(0);
        }
        menugcvalues.foreground = scrn_def_ret.pixel;
        menugcvalues.background = scrn_def_ret2.pixel;
	disp_info[i].fg_menu_gc = XCreateGC(disp_info[i].disp, 
	           disp_info[i].menu[0].win_id, alter_menu_mask, &menugcvalues);

	for (menu_num=0; menu_num<NUM_OF_MENUS; menu_num++)
	{
	    disp_info[i].current_menu = menu_num;
	    draw_menu(i);
	}
    }

    /*
     * create the status window and eraser window for each scrawl window 
     */
    for (i=0; i<num_of_disps; i++)
    { 
        if (!XAllocNamedColor(disp_info[i].disp, 
		DefaultColormap(disp_info[i].disp, 
	        DefaultScreen(disp_info[i].disp)), MENU_BACKGROUND_COLOR, 
	        &scrn_def_ret, &exact_def_ret))
        {
	    printf("The color %s is not", MENU_BACKGROUND_COLOR);
	    printf(" supported on the display %s.\n", disp_args[i]);
	    exit(0);
        }
        menuwinvals.background_pixel = scrn_def_ret.pixel;
	disp_info[i].eraser_win_id = XCreateWindow(disp_info[i].disp,
		   disp_info[i].win_id, 0, 350, 550, 20, 2, 0, CopyFromParent, 
		   CopyFromParent, alter_menu2_mask, &menuwinvals);
	disp_info[i].status_win_id = XCreateWindow(disp_info[i].disp,
		   disp_info[i].win_id, 0, 350, 550, 20, 2, 0, CopyFromParent, 
		   CopyFromParent, alter_menu2_mask, &menuwinvals);
        XMapWindow(disp_info[i].disp, disp_info[i].status_win_id);
	XDefineCursor(disp_info[i].disp, disp_info[i].status_win_id,
			  XCreateFontCursor(disp_info[i].disp, XC_star));
	XDefineCursor(disp_info[i].disp, disp_info[i].eraser_win_id,
			  XCreateFontCursor(disp_info[i].disp, XC_draped_box));
	XLowerWindow(disp_info[i].disp, disp_info[i].eraser_win_id);
    }

    /*
     * if typing, set cursor, font, etc correctly with a call to 
     * menu_selection (which is usually called when you pull down a menu).
     */
    for (i=0; i<num_of_disps; i++)
    {
        if (TYPE_NOT_DRAW)
             menu_selection(i, 0, num_people_drawing, 3);
    }

    /*
     * bring all the windows up with all this fun new information.
     */
    for (i=0; i<num_of_disps; i++)
    { 
	XMapWindow(disp_info[i].disp, disp_info[i].win_id);
        XSync(disp_info[i].disp, False);
    }

    for (i=0; i<num_of_disps; i++)
    {
	place_and_draw_status_win(i);
    }

    for (i=0; i<num_of_disps; i++)
    { 
        XGetWindowAttributes(disp_info[i].disp, disp_info[i].win_id,
				 &attr_ret);
	disp_info[i].win_width = attr_ret.width;
	disp_info[i].win_height = attr_ret.height;
    }
}


/*
 * airbrush - this function draws a "spray can" type stipple on the window
 *            window passed as an argument.  Thanks to Mark Cook for supplying
 *            this algorithm.
 */
airbrush(input_disp, disp, win, win_gc, win_x, win_y)
Display *disp;
Window win; 
GC win_gc;
int win_x, win_y;
{
    int i, rx, ry, RADIUS;
    XPoint spray_points[502];

    RADIUS = (disp_info[input_disp].pen_width)/2;

    for (i=0; i<NUM_DOTS; i++)
    {
        rx = (my_rand()*RADIUS) - (my_rand()*RADIUS);
        ry = (my_rand()*RADIUS) - (my_rand()*RADIUS);
        if (rx < (-RADIUS))
	    rx = (-RADIUS);
        if (rx > (RADIUS))
	    rx = RADIUS;
        if (ry < (-RADIUS))
	    ry = (-RADIUS);
        if (ry > (RADIUS))
	    ry = RADIUS;
        spray_points[i].x = win_x + rx;
        spray_points[i].y = win_y + ry;
    }
    XDrawPoints(disp, win, win_gc, spray_points, NUM_DOTS, CoordModeOrigin);
}


/*
 * my_rand - NOTE: THIS MAY REQUIRE A PORT: due to the non-standard "rand"
 *           function, this routine is not entirely portable.  The reason
 *           this function is here is to allow you to hack on it to get it
 *           to return a value between 0 and 1.  This only affects the 
 *           airbrush.
 */
float my_rand()
{
    float ret_val;

    ret_val = (float) ((double)rand()/(double)MAX_RAND);

    if (ret_val > 1.0)
        ret_val = (ret_val - ((float) (1 * ((int) ret_val))));

    return(ret_val);
}

