#include <stdio.h>
#include <math.h>
#include <string.h>
#include <malloc.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xlib.h>
#include "gd.h"

void About(Widget w, XtPointer client_data, XtPointer call_data);
void New(Widget w, XtPointer client_data, XtPointer call_data);
void OpenMap(Widget w, XtPointer client_data, XtPointer call_data);
void Save(Widget w, XtPointer client_data, XtPointer call_data);
void SaveAs(Widget w, XtPointer client_data, XtPointer call_data);
void Quit(Widget w, XtPointer client_data, XtPointer call_data);
void Url(Widget w, XtPointer client_data, XtPointer call_data);
void UrlDefault(Widget w, XtPointer client_data, XtPointer call_data);

static Widget top;
static XtAppContext appContext;
static Widget imageCore;
static int sx, sy;
static Widget polyToggle, rectToggle, circleToggle, deleteToggle, testToggle;

void ImagePointerMoved(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void Button1Down(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void Button2Down(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void Button3Down(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void ExposeHandler(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

static unsigned long cmaptox[gdMaxColors];
static unsigned long colorsAllocated[gdMaxColors];
int colorsAllocatedTotal = 0;
static XImage *image = 0;
static char *imageData = 0;
static Pixmap pixmap = None;
static GC drawGc;
static GC xorGc;
static Widget urlText;
static Widget urlCommentText;
static Widget urlTransientShell;
static Widget urlDefaultText;
static Widget urlDefaultTransientShell;
static char *defaultUrl = 0;
static char *mapfilename = 0;
static char *giffilename = 0;
static int changed = 0;

void UrlOK();
void UrlCancel();
void UrlDelete();
void UrlDefaultOK();
void UrlDefaultCancel();
void UrlDefaultDelete();

typedef enum {
	polyPosition, polyAddpoints, polyUrl, circlePosition, circleDrag,
	circleUrl, rectPosition, rectDrag, rectUrl, deleting, testing, nostate
} editState;

static editState state = nostate;

typedef struct {
	int x, y;
} point;

typedef enum {
	notype, polygon, circle, rectangle
} objectType;

#define objectsMin 5
#define pointsMin 3

typedef struct {
	objectType t;
	int complete;
	point *points;
	int pointsTotal;
	int pointsSpace;
	int expiring;
	int selected;
	char *url;
	char *comments;
} object;		

static int objectsTotal = 0;
static int objectsSpace = objectsMin;
static object *objects;

static char *trailingComments = 0;

void objectsSetup();

void objectsSetup() {
	int i;
	objects = (object *) malloc(sizeof(object) * objectsMin);
	objectsSpace = objectsMin;
	for (i=0; (i < objectsSpace); i++) {
		objects[i].t = notype;
		objects[i].points = (point *) 
			malloc(sizeof(point) * pointsMin);
		objects[i].pointsSpace = pointsMin;	
		objects[i].pointsTotal = 0;	
		objects[i].complete = 0;
		objects[i].expiring = 0;
		objects[i].url = 0;
		objects[i].comments = 0;
		objects[i].selected = 0;
	}	
}

void objectsEnsure(int x) {
	int oldspace = objectsSpace;
	while (objectsSpace <= x) {
		int i;
		objectsSpace *= 2;
		objects = (object *) realloc(objects, 
			sizeof(object) * objectsSpace);
		for (i=oldspace; (i < objectsSpace); i++) {
			objects[i].t = notype;
			objects[i].points = (point *) 
				malloc(sizeof(point) * pointsMin);
			objects[i].pointsSpace = pointsMin;	
			objects[i].pointsTotal = 0;	
			objects[i].complete = 0;
			objects[i].expiring = 0;
			objects[i].url = 0;
			objects[i].comments = 0;
			objects[i].selected = 0;
		}
	}
	objects[x].t = notype;
}

void pointsEnsure(object *o, int x) {
        while (o->pointsSpace <= x) {
                o->pointsSpace *= 2;
                o->points = (point*) realloc(o->points,
                        sizeof(point) * o->pointsSpace);
        }
}

#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif

/*  Polygon portion of this code is from Wm. Randolph Franklin, 
    wrf@ecse.rpi.edu, a professor at RPI, with some minor modifications 
    for speed by Scott Anguish, sanguish@digifix.com. Minor adaptations 
    to suit my data types. Drawn from the comp.graphics.algorithms FAQ. */

int objectContainsPoint(object *o, int x, int y) {
        int i, j, c = 0;
        point *pti;
        point *ptj;
	long rx, ry, rsq1, rsq2;
	point sorted[2];
	switch (o->t) {
		case polygon:
		for (i = 0, j = o->pointsTotal-1; 
			i < o->pointsTotal; j = i++) {
			pti = &o->points[i];
			ptj = &o->points[j];
			if ((((pti->y <= y) && (y < ptj->y)) ||
				((ptj->y <= y) && (y < pti->y))) &&
				(x < (ptj->x - pti->x) * (y - pti->y) /
				(ptj->y - pti->y) + pti->x)) {
				c = !c;
			}
		}
		return c;
		case circle:
		pti = &o->points[0];
		ptj = &o->points[1];
		rx = (x - (pti->x));
		rx *= rx; 		
		ry = (y - (pti->y));
		ry *= ry; 		
		rsq1 = rx + ry;
		rx = ((ptj->x) - (pti->x));
		rx *= rx; 		
		ry = ((ptj->y) - (pti->y));
		ry *= ry; 		
		rsq2 = rx + ry;
		if (rsq1 <= rsq2) {
			return 1;
		} else {
			return 0;
		}
		case rectangle:
		pti = &o->points[0];
		ptj = &o->points[1];
		sorted[0].x = min(pti->x, ptj->x);
		sorted[0].y = min(pti->y, ptj->y);	
		sorted[1].x = max(pti->x, ptj->x);
		sorted[1].y = max(pti->y, ptj->y);	
		if (((x >= (sorted[0].x)) && (x <= (sorted[1].x))) &&
			((y >= (sorted[0].y)) && (y <= (sorted[1].y)))) {
			return 1;
		} else {
			return 0;
		}
	}		
	return 0;
}

void Poly(Widget w, XtPointer client_data, XtPointer call_data);
void Rect(Widget w, XtPointer client_data, XtPointer call_data);
void Circle(Widget w, XtPointer client_data, XtPointer call_data);
void Test(Widget w, XtPointer client_data, XtPointer call_data);
void Delete(Widget w, XtPointer client_data, XtPointer call_data);

void objectDraw(object *o, Display *display, Drawable d);

void sensitivity(int setting);

typedef enum {
	modalInfo, modalConfirm
} modalType;

void modalPopup(char *text, modalType t, 
	void (*callback)(void *client_data, int result), 
	void *client_data);

char *mystrdup(char *s) {
	char *n;
	n = (char *) malloc(sizeof(char) * (strlen(s) + 1));
	strcpy(n, s);
	return n;
}

static int lastSelected = (-1);

typedef struct {
	Boolean cern;
} AppData;

AppData appData;

#define XtNcern "cern"
#define XtCCern "Cern"

static XtResource resources[] = {
	{
		XtNcern,
		XtCCern,
		XtRBoolean,
		sizeof(Boolean),
		XtOffsetOf(AppData, cern),
		XtRImmediate,
		(XtPointer) False
	}
};

int main(int argc, char *argv[]) {
	Widget paned, menuBox, paletteBox;
	Widget fileMenuButton, fileMenu;
	Widget aboutSmeBSB, openMapSmeBSB;
	Widget quitSmeBSB;
	Widget viewport;
	Widget UrlDefaultSmeBSB;
	Widget saveSmeBSB, saveAsSmeBSB;
	XGCValues gcvalues;
	static char transTable[] = 
		"<PtrMoved>: ImagePointerMoved()\n"\
		"<Btn1Down>: Button1Down()\n"\
		"<Btn2Down>: Button2Down()\n"\
		"<Btn3Down>: Button3Down()\n"\
		"<Expose>: ExposeHandler()\n";
	static XtActionsRec actions[] = { 
		{ "ImagePointerMoved", ImagePointerMoved },
		{ "Button1Down", Button1Down },
		{ "Button2Down", Button2Down },
		{ "Button3Down", Button3Down },
		{ "ExposeHandler", ExposeHandler }
	};	
	XtTranslations translations;
	top = XtVaAppInitialize(
		&appContext,
		"Mapedit",
		NULL, 0,
		&argc, argv,
		NULL,
		XtNwidth, 400,
		XtNheight, 400,
		XtNallowShellResize, True,
		NULL);
	XtVaGetApplicationResources(top,
		&appData,
		resources,
		XtNumber(resources),
		NULL);
	paned = XtVaCreateManagedWidget(
		"paned",
		panedWidgetClass,
		top,	
		XtNresizeToPreferred, True,
		top,
		NULL);
	menuBox = XtVaCreateManagedWidget(
		"menuBox",
		boxWidgetClass,
		paned,
		XtNallowResize, False,
		XtNorientation, XtorientHorizontal,
		XtNhSpace, 1,	
		XtNvSpace, 1,	
		NULL);
	paletteBox = XtVaCreateManagedWidget(
		"paletteBox",
		boxWidgetClass,
		paned,
		XtNborderWidth, 0,
		XtNallowResize, False,
		XtNorientation, XtorientHorizontal,
		XtNhSpace, 1,	
		XtNvSpace, 1,	
		NULL);
#if 0
	viewBox = XtVaCreateManagedWidget(
		"viewBox",
		boxWidgetClass,
		paned, 
		XtNborderWidth, 0,
		XtNallowResize, True,
		NULL);
#endif
	viewport = XtVaCreateManagedWidget(
		"viewport",
		viewportWidgetClass,
		paned,
#if 0
		viewBox,
#endif
		XtNallowResize, True,
		XtNborderWidth, 0,
		XtNallowHoriz, True,
		XtNallowVert, True,
		XtNuseBottom, True,
		XtNuseRight, True,
		NULL);
	imageCore = XtVaCreateManagedWidget(
		"imageCore",
		coreWidgetClass,
		viewport,
		XtNborderWidth, 0,
		XtNwidth, 16,
		XtNheight, 16,
		XtNallowResize, True,
		NULL);
	translations = XtParseTranslationTable(transTable);
	XtOverrideTranslations(imageCore, translations);
	XtAppAddActions(appContext, actions, XtNumber(actions));
	fileMenuButton = XtVaCreateManagedWidget(
		"File",
		menuButtonWidgetClass,
		menuBox,
		XtNmenuName, "fileMenu",
		NULL);	
	polyToggle =  XtVaCreateManagedWidget(
		"Poly",
		toggleWidgetClass,
		paletteBox,
		XtNsensitive, False,
		NULL);	
	XtAddCallback(polyToggle, XtNcallback, Poly, 0);
	rectToggle =  XtVaCreateManagedWidget(
		"Rect",
		toggleWidgetClass,
		paletteBox,
		XtNradioGroup, polyToggle,
		XtNsensitive, False,
		NULL);	
	/* The first one now has something to correspond with */
	XtVaSetValues(polyToggle,
		XtNradioGroup, rectToggle,
		XtNsensitive, False,
		NULL);
	XtAddCallback(rectToggle, XtNcallback, Rect, 0);
	circleToggle =  XtVaCreateManagedWidget(
		"Circle",
		toggleWidgetClass,
		paletteBox,
		XtNradioGroup, polyToggle,
		XtNsensitive, False,
		NULL);	
	XtAddCallback(circleToggle, XtNcallback, Circle, 0);
	deleteToggle =  XtVaCreateManagedWidget(
		"Delete",
		toggleWidgetClass,
		paletteBox,
		XtNradioGroup, polyToggle,
		XtNsensitive, False,
		NULL);	
	XtAddCallback(deleteToggle, XtNcallback, Delete, 0);
	testToggle =  XtVaCreateManagedWidget(
		"Test",
		toggleWidgetClass,
		paletteBox,
		XtNradioGroup, polyToggle,
		XtNsensitive, False,
		NULL);	
	XtAddCallback(testToggle, XtNcallback, Test, 0);
	fileMenu = XtVaCreatePopupShell(
		"fileMenu",
		simpleMenuWidgetClass,
		fileMenuButton,
		NULL);
	aboutSmeBSB = XtVaCreateManagedWidget(
		"About Mapedit...",
		smeBSBObjectClass,
		fileMenu, 
		NULL);
	XtAddCallback(aboutSmeBSB, XtNcallback, About, 0);
	openMapSmeBSB = XtVaCreateManagedWidget(
		"Open/Create Map...",
		smeBSBObjectClass,
		fileMenu,
		NULL);
	XtAddCallback(openMapSmeBSB, XtNcallback, OpenMap, 0);
	saveSmeBSB = XtVaCreateManagedWidget(
		"Save Map",
		smeBSBObjectClass,
		fileMenu,
		NULL);
	XtAddCallback(saveSmeBSB, XtNcallback, Save, 0);
	saveAsSmeBSB = XtVaCreateManagedWidget(
		"Save Map As...",
		smeBSBObjectClass,
		fileMenu,
		NULL);
	XtAddCallback(saveAsSmeBSB, XtNcallback, SaveAs, 0);
	UrlDefaultSmeBSB = XtVaCreateManagedWidget(
		"Edit Default URL...",
		smeBSBObjectClass,
		fileMenu,
		NULL);
	XtAddCallback(UrlDefaultSmeBSB, XtNcallback, UrlDefault, 0);
	quitSmeBSB = XtVaCreateManagedWidget(
		"Quit",
		smeBSBObjectClass,
		fileMenu,
		NULL);
	XtAddCallback(quitSmeBSB, XtNcallback, Quit, 0);
	XtRealizeWidget(top);
	drawGc = XCreateGC(XtDisplay(imageCore), 
		XtWindow(imageCore), 0, &gcvalues);
	gcvalues.foreground = WhitePixelOfScreen(XtScreen(imageCore)) ^
		BlackPixelOfScreen(XtScreen(imageCore));
	gcvalues.background = BlackPixelOfScreen(XtScreen(imageCore));
	gcvalues.function = GXxor;
	xorGc = XCreateGC(XtDisplay(imageCore), XtWindow(imageCore), 
		GCFunction | GCForeground | GCBackground, &gcvalues);	
	objectsSetup();
	XtAppMainLoop(appContext);
}

void About(Widget w, XtPointer client_data, XtPointer call_data) {
	static Widget transientShell;
	static Widget form;
	static Widget label;
	static Widget text;
	static Widget command;
	static int init = 0;
	if (!init) {
		transientShell = XtVaCreatePopupShell(
			"About Mapedit",
			transientShellWidgetClass,
			top,
			XtNallowShellResize, True,
			XtNinput, True,
			XtNtransientFor, top,		
			NULL);
		form = XtVaCreateManagedWidget(
			"aboutForm",
			formWidgetClass,
			transientShell,
			NULL);
		label = XtVaCreateManagedWidget(
			"About Mapedit",
			labelWidgetClass,
			form,
			XtNborderWidth, 0,
			NULL);
		text = XtVaCreateManagedWidget(
			"aboutText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextRead,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNfromVert, label,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapWord,
			XtNwidth, 320,
			XtNheight, 200,
			XtNstring,
	"Copyright 1994 by Thomas Boutell. THIS IS NOT PUBLIC DOMAIN "\
	"SOFTWARE. Commercial users must pay for it to continue using it "\
	"after 30 days. "\
	"You need not register again when new versions are released. "\
	"Please remit $25 to:\n\nThomas Boutell\n426 Bellevue Ave. E Apt. 1\n"\
	"Seattle, WA 98102 USA\n\n"\
	"Nonprofit and educational users are required to send me a postcard!",
			NULL);
		command = XtVaCreateManagedWidget(
			"OK",
			commandWidgetClass,
			form,
			XtNfromVert, text,
			NULL);
		XtAddCallback(command, XtNcallback, About, 0);
		init = 1;
	}
	if (w == command) { 
		XtPopdown(transientShell);
	} else {
		XtPopup(transientShell, XtGrabNone);
	}
}

void New(Widget w, XtPointer client_data, XtPointer call_data) {
}

static Widget openMapText;
static Widget openGifText;
static Widget openMapTransientShell;

void OpenMapReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void OpenMap(Widget w, XtPointer client_data, XtPointer call_data) {
	static Widget form;
	static Widget label;
	static Widget prompt1, prompt2; 
	static Widget cancelCommand;
	static Widget okCommand;
	static Widget cernCommand;
	static Widget styleMenu, styleMenuButton;
	static Widget ncsaSmeBSB;
	static Widget cernSmeBSB;
	static int init = 0;
	static XtActionsRec actions [] = {
		{ "ReturnAction", OpenMapReturnAction }
	};	
	if (!init) {
		XtTranslations TextFieldTranslations;
		openMapTransientShell = XtVaCreatePopupShell(
			"Open Map",
			transientShellWidgetClass,
			top,
			XtNallowShellResize, True,
			XtNinput, True,
			XtNtransientFor, top,		
			NULL);
		form = XtVaCreateManagedWidget(
			"openMapForm",
			formWidgetClass,
			openMapTransientShell,
			NULL);
		label = XtVaCreateManagedWidget(
			"Open Map",
			labelWidgetClass,
			form,
			XtNborderWidth, 0,
			NULL);
		styleMenuButton = XtVaCreateManagedWidget(
			"Style: NCSA",
			menuButtonWidgetClass,
			form,
			XtNfromHoriz, label,
			XtNmenuName, "styleMenu",
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		styleMenu = XtVaCreatePopupShell(
			"styleMenu",
			simpleMenuWidgetClass,
			styleMenuButton,
			NULL);
		ncsaSmeBSB = XtVaCreateManagedWidget(
			"NCSA",
			smeBSBObjectClass,
			styleMenu, 
			NULL);
		XtAddCallback(ncsaSmeBSB, XtNcallback, OpenMap, 0);
		cernSmeBSB = XtVaCreateManagedWidget(
			"CERN",
			smeBSBObjectClass,
			styleMenu, 
			NULL);
		XtAddCallback(cernSmeBSB, XtNcallback, OpenMap, 0);
		prompt1 = XtVaCreateManagedWidget(
			"Map Filename:",
			labelWidgetClass,
			form,
			XtNfromVert, label,
			XtNborderWidth, 0,
			NULL);
		openMapText = XtVaCreateManagedWidget(
			"openMapText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextEdit,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNscrollHorizontal, XawtextScrollAlways,
			XtNfromVert, label,
			XtNfromHoriz, prompt1,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapNever,
			XtNwidth, 320,
			XtNheight, 32,
			NULL);
		prompt2 = XtVaCreateManagedWidget(
			"Gif Filename:",
			labelWidgetClass,
			form,
			XtNfromVert, openMapText,
			XtNborderWidth, 0,
			NULL);	
		openGifText = XtVaCreateManagedWidget(
			"openGifText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextEdit,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNscrollHorizontal, XawtextScrollAlways,
			XtNfromVert, openMapText,
			XtNfromHoriz, prompt2,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapNever,
			XtNwidth, 320,
			XtNheight, 32,
			NULL);
		TextFieldTranslations = XtParseTranslationTable
			("<Key>Return: ReturnAction()\n\
				Ctrl<Key>R: no-op(RingBell)\n\
				Ctrl<Key>S: no-op(RingBell)\n");
		XtOverrideTranslations(openMapText, TextFieldTranslations);
		XtOverrideTranslations(openGifText, TextFieldTranslations);
		XtAppAddActions(appContext, actions, XtNumber(actions));	
		okCommand = XtVaCreateManagedWidget(
			"OK",
			commandWidgetClass,
			form,
			XtNfromVert, openGifText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		cancelCommand = XtVaCreateManagedWidget(
			"Cancel",
			commandWidgetClass,
			form,
			XtNfromHoriz, okCommand,
			XtNfromVert, openGifText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		XtAddCallback(okCommand, XtNcallback, OpenMap, 0);
		XtAddCallback(cancelCommand, XtNcallback, OpenMap, 0);
		init = 1;
	}
	if (w == cancelCommand) { 
		XtPopdown(openMapTransientShell);
	} else if (w == okCommand) {
		/* Simulate Return */
		OpenMapReturnAction(openMapText, 0, 0, 0);
	} else if (w == cernSmeBSB) {
		appData.cern = 1;
		XtVaSetValues(styleMenuButton,
			XtNlabel, "Style: CERN",
			NULL);
	} else if (w == ncsaSmeBSB) {
		appData.cern = 0;
		XtVaSetValues(styleMenuButton,
			XtNlabel, "Style: NCSA",
			NULL);
	} else {
		if (appData.cern) {
			XtVaSetValues(styleMenuButton,
				XtNlabel, "Style: CERN",
				NULL);
		} else {
			XtVaSetValues(styleMenuButton,
				XtNlabel, "Style: NCSA",
				NULL);
		}
		XtPopup(openMapTransientShell, XtGrabNone);
	}
}

int loadfiles(char *filename, char *mapname);

void OpenMapOK(void *client_data, int result);

void OpenMapReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	char *filename, *mapname;
	FILE *in;
	XtPopdown(openMapTransientShell);
	XtVaGetValues(openGifText,
		XtNstring, &filename,
		NULL);
	XtVaGetValues(openMapText,
		XtNstring, &mapname,
		NULL);
	if (!(in = fopen(filename, "rb"))) {
		modalPopup("Gif file not found (or we don't have\n"\
			"permission to open it).", modalInfo, 0, 0);	
		return;
	}
	fclose(in);
	if (giffilename) {
		free(giffilename);
	}
	giffilename = mystrdup(filename);
	if (!strlen(mapname)) {
		modalPopup("No map filename specified.\n", modalInfo, 0, 0);
		return;
	}
	if (!(in = fopen(mapname, "rb"))) {
		modalPopup("Map file not found. Create it?",
			modalConfirm, OpenMapOK, 0);	
		return;
	}
	fclose(in);
	if (mapfilename) {
		free(mapfilename);
	}
	mapfilename = mystrdup(mapname);
	if (!loadfiles(filename, mapname)) {
		sensitivity(False);
	} else {
		sensitivity(True);
		changed = 0;
	}
}

void OpenMapOK(void *client_data, int result) {
	char *mapname;
	if (!result) {
		return;
	}
	XtVaGetValues(openMapText,
		XtNstring, &mapname,
		NULL);
	if (!loadfiles(giffilename, 0)) {
		sensitivity(False);
	} else {
		sensitivity(True);
		changed = 0;
	}
	if (mapfilename) {
		free(mapfilename);
	}
	mapfilename = mystrdup(mapname);	
}

void sensitivity(int setting) {
	if (!setting) {
		if (giffilename) {
			free(giffilename);
		}
		giffilename = 0;
		if (mapfilename) {
			free(mapfilename);
		}
		mapfilename = 0;
	}
	XtVaSetValues(polyToggle,
		XtNsensitive, setting,	
		NULL);
	XtVaSetValues(rectToggle,
		XtNsensitive, setting,	
		NULL);
	XtVaSetValues(circleToggle,
		XtNsensitive, setting,	
		NULL);
	XtVaSetValues(deleteToggle,
		XtNsensitive, setting,	
		NULL);
	XtVaSetValues(testToggle,
		XtNsensitive, setting,	
		NULL);
	XtVaSetValues(testToggle,
		XtNsensitive, setting,	
		NULL);
}

void writeComments(FILE *out, char *comments);

void Save(Widget w, XtPointer client_data, XtPointer call_data) {
	FILE *out;
	object *o;
	int i;
	if (!mapfilename) {
		modalPopup("No map to save.", modalInfo, 0, 0);
		return;
	}	
	out = fopen(mapfilename, "w");
	if (!out) {
		modalPopup("Unable to write to map file!", modalInfo, 0, 0);
		return;
	}
	if (defaultUrl) {
		fprintf(out, "default %s\n", defaultUrl);
	}
	for (i=0; (i<objectsTotal); i++) {
		int j;
		o = &objects[i];
		writeComments(out, o->comments);
		switch(o->t) {
			case polygon:
			fprintf(out, "poly ");
			break;
			case circle: 
			fprintf(out, "circle ");
			break;
			case rectangle: 
			fprintf(out, "rect ");
			break;
		}
		if (!appData.cern) {
			if (o->url) {
				fprintf(out, "%s ", o->url);
			} else {
				fprintf(out, "NOURL ");
			}
		}
		for (j=0; (j < o->pointsTotal); j++) {
			if (appData.cern) {
				if ((o->t == circle) &&
					(j == 1)) {
					int dx, dy;
					dx = o->points[1].x - o->points[0].x;
					dy = o->points[1].y - o->points[0].y;
					fprintf(out, "%d ",
						(int)sqrt(dx * dx + dy * dy));
				} else {
					fprintf(out, "(%d,%d) ", 
						o->points[j].x,
						o->points[j].y);
				}
			} else {
				fprintf(out, "%d,%d ", o->points[j].x,
					o->points[j].y);
			}
		}		
		if (appData.cern) {
			if (o->url) {
				fprintf(out, "%s", o->url);
			} else {
				fprintf(out, "NOURL");
			}
		}
		fprintf(out, "\n");
	}
	writeComments(out, trailingComments);
	fclose(out);
	changed = 0;
	modalPopup("Map saved.", modalInfo, 0, 0);
}

void writeComments(FILE *out, char *comments) {
	char *cr;
	if (!comments) {
		return;
	}
	while(1) {
		cr = strchr(comments, '\n');
		if (cr) {
			*cr = '\0';
			fprintf(out, "#%s\n", comments);
			*cr = '\n';
			comments = cr+1;
		} else {
			if (strlen(comments)) {
				fprintf(out, "#%s\n", comments);		
			}
			break;
		}
	}
}

void QuitConfirm(void *client_data, int result);

void Quit(Widget w, XtPointer client_data, XtPointer call_data) {
	if (!changed) {
		exit(0);
	} else {
		modalPopup("UNSAVED CHANGES! Are you sure you want to exit?",
			modalConfirm, QuitConfirm, 0);
	}
}

void QuitConfirm(void *client_data, int result) {
	if (result) {
		exit(0);
	}
}

void Poly(Widget w, XtPointer client_data, XtPointer call_data) {
	object *o;
	if (call_data) {
		state = polyPosition;
	} else {
		o = &objects[objectsTotal];
#if 0
		/* This should work */
		o->expiring = 1;
		objectDraw(o, XtDisplay(w), XtWindow(w));
#endif
		o->t = notype;
		/* This shouldn't be necessary */
		ExposeHandler(imageCore, 0, 0, 0);
		state = nostate;
	}
}

void Rect(Widget w, XtPointer client_data, XtPointer call_data) {
	object *o;
	if (call_data) {
		if (state != rectDrag) {
			state = rectPosition;
		}
	} else {
		o = &objects[objectsTotal];
#if 0
		/* This should work */
		o->expiring = 1;
		objectDraw(o, XtDisplay(w), XtWindow(w));
#endif
		o->t = notype;
		/* This shouldn't be necessary */
		ExposeHandler(imageCore, 0, 0, 0);
		state = nostate;
		state = nostate;
	}
}

void Circle(Widget w, XtPointer client_data, XtPointer call_data) {
	object *o;
	if (call_data) {
		if (state != circleDrag) {
			state = circlePosition;
		}
	} else {
		o = &objects[objectsTotal];
#if 0
		/* This should work */
		o->expiring = 1;
		objectDraw(o, XtDisplay(w), XtWindow(w));
#endif
		o->t = notype;
		/* This shouldn't be necessary */
		ExposeHandler(imageCore, 0, 0, 0);
		state = nostate;
		state = nostate;
	}
}

void Delete(Widget w, XtPointer client_data, XtPointer call_data) {
	/* Inherit test behavior */
	Test(w, client_data, call_data);
	if (call_data) {
		state = deleting;
	} else {
		state = nostate;
	}
}

void Test(Widget w, XtPointer client_data, XtPointer call_data) {
	object *o;
	if (call_data) {
		state = testing;
	} else {
		if (lastSelected != (-1)) {
			o = &objects[lastSelected];
			/* Blit back out */
			objectDraw(o, XtDisplay(w), XtWindow(w));
			objects[lastSelected].selected = 0;
			/* Draw normally */
			objectDraw(o, XtDisplay(w), XtWindow(w));
		}
		lastSelected = (-1);
		state = nostate;
		XtPopdown(urlTransientShell);
	}
}

void ImagePointerMoved(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	XMotionEvent *e;
	object *o;
	e = (XMotionEvent *) event;
	switch (state) {
		case polyAddpoints:
		o = &objects[objectsTotal];
		objectDraw(o, XtDisplay(w), XtWindow(w));
		o->points[o->pointsTotal].x = e->x;
		o->points[o->pointsTotal].y = e->y;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		break;
		/* Deliberate fallthrough */
		case rectDrag:
		case circleDrag:	
		o = &objects[objectsTotal];
		objectDraw(o, XtDisplay(w), XtWindow(w));
		o->points[1].x = e->x;
		o->points[1].y = e->y;
		objectDraw(o, XtDisplay(w), XtWindow(w));

		break;
	}
}

void Button1Down(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	XButtonEvent *e;
	object *o;
	int i;
	int succeeded = 0;
	e = (XButtonEvent *) event;
	switch(state) {
		case polyPosition:
		objectsEnsure(objectsTotal);
		o = &objects[objectsTotal];
		o->complete = 0;
		o->pointsTotal = 0;
		o->t = polygon;
		pointsEnsure(o, o->pointsTotal);
		o->points[o->pointsTotal].x = e->x;			
		o->points[o->pointsTotal].y = e->y;
		o->pointsTotal++;
		pointsEnsure(o, o->pointsTotal);
		o->points[o->pointsTotal].x = e->x;			
		o->points[o->pointsTotal].y = e->y;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		state = polyAddpoints;	
		break;
		case polyAddpoints:
		o = &objects[objectsTotal];
		pointsEnsure(o, o->pointsTotal);
		/* Blit it out */
		objectDraw(o, XtDisplay(w), XtWindow(w));
		o->points[o->pointsTotal].x = e->x;			
		o->points[o->pointsTotal].y = e->y;
		o->pointsTotal++;
		pointsEnsure(o, o->pointsTotal);
		o->points[o->pointsTotal].x = e->x;			
		o->points[o->pointsTotal].y = e->y;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		break;
		case circlePosition:
		objectsEnsure(objectsTotal);
		o = &objects[objectsTotal];
		pointsEnsure(o, 2);
		o->t = circle;
		o->complete = 0;
		o->pointsTotal = 2;
		o->points[0].x = e->x;
		o->points[0].y = e->y;
		o->points[1] = o->points[0];
		state = circleDrag;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		break;
		case circleDrag:
		o = &objects[objectsTotal];
		/* Blit it back out */
		objectDraw(o, XtDisplay(w), XtWindow(w));
		state = circlePosition;
		break;
		case rectPosition:
		objectsEnsure(objectsTotal);
		o = &objects[objectsTotal];
		pointsEnsure(o, 2);
		o->t = rectangle;
		o->complete = 0;
		o->pointsTotal = 2;
		o->points[0].x = e->x;
		o->points[0].y = e->y;
		o->points[1] = o->points[0];
		state = rectDrag;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		break;
		case rectDrag:
		/* Blit it back out */
		o = &objects[objectsTotal];
		objectDraw(o, XtDisplay(w), XtWindow(w));
		state = rectPosition;
		case testing:
		case deleting:
		for (i=0; (i<objectsTotal); i++) {
			o = &objects[i];
			if (objectContainsPoint(o, e->x, e->y)) {
				if (!(o->selected)) {
					if (lastSelected != (-1)) {
						object *old;
						old = &objects[lastSelected];
						/* Blit back out */
						objectDraw(old, XtDisplay(w), XtWindow(w));
						objects[lastSelected].selected = 0;
						/* Draw normally */
						objectDraw(old, XtDisplay(w), XtWindow(w));
					}
					lastSelected = i;
					o->selected = 1;
					objectDraw(o, XtDisplay(w),
						XtWindow(w));
					Url(w, 0, 0);
				} else {
					/* Just make sure it's popped up
						on a reselect */	
					XtPopup(urlTransientShell, XtGrabNone);
				}
				succeeded = 1;
				break;
			}
		} 
		if (!succeeded) {
			/* No object selected */
			if (lastSelected != (-1)) {
				o = &objects[lastSelected];
				/* Blit back out */
				objectDraw(o, XtDisplay(w), XtWindow(w));
				objects[lastSelected].selected = 0;
				/* Draw normally */
				objectDraw(o, XtDisplay(w), XtWindow(w));
				lastSelected = (-1);
				/* Hide the URL window */
				XtPopdown(urlTransientShell);	
			}
		}
	}
}

void Button2Down(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	XButtonEvent *e;
	object *o;
	e = (XButtonEvent *) event;
	switch(state) {
		case polyAddpoints:
		o = &objects[objectsTotal];
		if (o->pointsTotal > 2) {
			state = polyUrl;
			Url(imageCore, 0, 0);
		}
		break;
		case circleDrag:
		o = &objects[objectsTotal];
		o->points[1].x = e->x;
		o->points[1].y = e->y;
		state = circleUrl;
		Url(imageCore, 0, 0);
		break;			
		case rectDrag:
		o = &objects[objectsTotal];
		o->points[1].x = e->x;
		o->points[1].y = e->y;
		state = rectUrl;
		Url(imageCore, 0, 0);
		break;			
		case deleting:
		UrlDelete(); 
		break;
	}
}

void Button3Down(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	object *o;
	switch(state) {
		case polyAddpoints:
		o = &objects[objectsTotal];
		o->expiring = 1;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		o->t = notype;
		state = polyPosition;
		break;
		case rectDrag:
		o = &objects[objectsTotal];
		o->expiring = 1;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		o->t = notype;
		state = rectPosition;
		break;			
		case circleDrag:
		o = &objects[objectsTotal];
		o->expiring = 1;
		objectDraw(o, XtDisplay(w), XtWindow(w));
		o->t = notype;
		state = circlePosition;
		break;			
	}
}

void ExposeHandler(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	int i;
	object *o;
	if (pixmap != None) {
		XCopyArea(XtDisplay(w), pixmap, XtWindow(w), 
			drawGc, 0, 0, sx, sy, 0, 0);
		/* We draw the incomplete object as well */
		for (i=0; (i<=objectsTotal); i++) {
			o = &objects[i];
			objectDraw(o, XtDisplay(w), XtWindow(w));
		}
	}
}

void objectDraw(object *o, Display *display, Drawable d) {
	int lx, uy;
	long rx, ry, radius;
	int cx, cy;
	int j;
	static XPoint *XPoints;
	int XPointsSpace = 0;
	GC mainGc;
	mainGc = drawGc;
	if (o->expiring) {
		o->expiring = 0;
		mainGc = xorGc;
	}
	switch(o->t) {
		case notype:
		break;
		case polygon:
		if (!o->complete) {
			mainGc = xorGc;
		}
		/* Make sure we have room for points */
		if (XPointsSpace < o->pointsTotal) {
			if (!XPoints) {
				XPoints = (XPoint *) malloc(sizeof(XPoint) *
					o->pointsTotal);
			} else {	
				XPoints = (XPoint *) realloc(XPoints, 
					sizeof(XPoint) * o->pointsTotal);
			}
			XPointsSpace = o->pointsTotal;
		}
		if ((o->selected) && (o->complete)) {
			/* Unusual case: selected polygon */
			for (j=0; (j < (o->pointsTotal)); j++) {		
				XPoints[j].x = o->points[j].x;
				XPoints[j].y = o->points[j].y;
			}
			XFillPolygon(display, d, xorGc, XPoints,
				o->pointsTotal, Complex, CoordModeOrigin);
			break;
		}
		for (j=0; (j < (o->pointsTotal-1)); j++) {
			XDrawLine(display, d, mainGc, 
				o->points[j].x, o->points[j].y,
				o->points[j+1].x, o->points[j+1].y);
		}
		if (o->complete) {
			XDrawLine(display, d, mainGc, 
				o->points[o->pointsTotal-1].x, 
				o->points[o->pointsTotal-1].y,
				o->points[0].x, o->points[0].y);
		} 
		if (!o->complete) {
			XDrawLine(display, d, xorGc, 
				o->points[o->pointsTotal-1].x, 
				o->points[o->pointsTotal-1].y,
				o->points[o->pointsTotal].x, 
				o->points[o->pointsTotal].y);
		}	
		break;
		case circle:
		cx = o->points[0].x;
		cy = o->points[0].y;
		rx = (o->points[1].x - cx);
		ry = (o->points[1].y - cy);
		radius = (int)sqrt((double)(rx * rx) + (ry * ry));
		if ((o->complete) && (o->selected)) {
			XFillArc(display, d, xorGc, cx - radius, cy - radius,
				radius * 2, radius * 2, 0, 23040);
			break;
		}
		if (!o->complete) {
			XDrawArc(display, d, xorGc, cx - radius, cy - radius,
				radius * 2, radius * 2, 0, 23040);
		} else {
			XDrawArc(display, d, mainGc, cx - radius, cy - radius,
				radius * 2, radius * 2, 0, 23040);
		}
		break;
		case rectangle:
		lx = min(o->points[0].x, o->points[1].x);
		uy = min(o->points[0].y, o->points[1].y);
		if ((o->complete) && (o->selected)) {
			XFillRectangle(display, d, xorGc,
				lx, uy, 
				abs(o->points[1].x - o->points[0].x), 
				abs(o->points[1].y - o->points[0].y));
			break;
		}
		if (!o->complete) {
			XDrawRectangle(display, d, xorGc,
				lx, uy, 
				abs(o->points[1].x - o->points[0].x), 
				abs(o->points[1].y - o->points[0].y));
		} else {
			XDrawRectangle(display, d, mainGc,
				lx, uy, 
				abs(o->points[1].x - o->points[0].x), 
				abs(o->points[1].y - o->points[0].y));
		}
		break;
	}
}

typedef enum {
	ditherNone, ditherBw, ditherColor
} ditherType;

int loadfiles(char *filename, char *mapname) {
	gdImage *im;
	FILE *in;
	int i;
	int d;
	Colormap cmap;
	int x, y;
	char *current;
	char estring[161];
	int linenum = 0;
	int failed = 0;
	int rAcc, bAcc, gAcc;
	ditherType dither;
	int ditherLevel;
	XRectangle rect[1];
	char *complaint;
	int threshold;	
	int gain;
	int rconst;
	int gconst;
	int bconst;
	char *comments;
	int commentsSpace;	
	int commentsLength;
	dither = ditherNone;
	if (!(in = fopen(filename, "rb"))) {
		modalPopup("Gif file not found (or we don't have\n"\
			"permission to open it).", modalInfo, 0, 0);	
		return 0;
	}
	im = gdImageCreateFromGif(in);
	if (!im) {
		modalPopup("Not a valid gif file!", modalInfo, 0, 0);
		return 0;
	}
	fclose(in);
	cmap = DefaultColormapOfScreen(XtScreen(imageCore));
	/* Free old color allocations */
	if (colorsAllocatedTotal) {
		XFreeColors(XtDisplay(imageCore), cmap,
			colorsAllocated, colorsAllocatedTotal, 0);
	}
	colorsAllocatedTotal = 0;
	/* First we try to get them all */
	d = DefaultDepthOfScreen(XtScreen(imageCore));
	if ((1 << d) >= im->colorsTotal) {
		for (i=0; (i < im->colorsTotal); i++) {
			XColor color;
			color.red = (im->red[i]) << 8;
			color.green = (im->green[i]) << 8;
			color.blue = (im->blue[i]) << 8;
			if (!XAllocColor(XtDisplay(imageCore), cmap, &color)) {
				failed = 1;
				break;
			} else {
				colorsAllocated[colorsAllocatedTotal++] = color.pixel;
				cmaptox[i] = color.pixel;
			}
		}
	} else {
		failed = 1;
	}
	if (failed) {
		int j;
		/* Free them and start working on a dither */
		if (colorsAllocatedTotal) {
			XFreeColors(XtDisplay(imageCore), cmap,
				colorsAllocated, colorsAllocatedTotal, 0);
		}
		colorsAllocatedTotal = 0;
		/* Shoot for the color dithers first */
		dither = ditherColor;
		for (j=5; (j>=2); j--) {
			failed = 0;
			if ((1 << d) < (j * j * j)) {
				failed = 1;
				continue;
			}
			for (i=0; (i < (j * j * j)); i++) {
				XColor color;
				int r, g, b;
				r = (i % j);
				g = (i / j) % j;
				b = (i / (j * j));
				color.red = r * 65535L / (j-1);
				color.green = g * 65535L / (j-1);
				color.blue = b * 65535L / (j-1);
				if (!XAllocColor(XtDisplay(imageCore), cmap, &color)) {
					failed = 1;
					XFreeColors(XtDisplay(imageCore), cmap,
					colorsAllocated, colorsAllocatedTotal, 0);
					colorsAllocatedTotal = 0;
					break;
				} else {
					colorsAllocated[colorsAllocatedTotal++] = color.pixel;
					cmaptox[i] = color.pixel;
				}
			}
			if (!failed) {
				ditherLevel = j;
				break;
			}
		}
	}
	if (failed) { /* Black and white dither, then. Can't fail. */
		dither = ditherBw;
		colorsAllocatedTotal = 0;
		cmaptox[0] = BlackPixelOfScreen(XtScreen(imageCore));
		cmaptox[1] = WhitePixelOfScreen(XtScreen(imageCore));
		failed = 0;
	}		
	imageData = (char *) malloc(sizeof(char) * im->sx * im->sy);
	current = imageData;
	rAcc = 0;
	bAcc = 0;
	gAcc = 0;
	switch(dither) {
		case ditherNone:
		for (y=0; (y < im->sy); y++) {
			for (x=0; (x < im->sx); x++) {
				*current = cmaptox[gdImageGetPixel(im, x, y)];
				current++;
			}
		}
		break;
		case ditherColor:
		threshold = 255 / (ditherLevel-1);
		rconst = 1;
		gconst = ditherLevel;
		bconst = ditherLevel * ditherLevel;
		for (y=0; (y < im->sy); y++) {
			for (x=0; (x < im->sx); x++) {
				int r, g, b, p, np;
				p = gdImageGetPixel(im, x, y);
				np = 0;
				r = im->red[p];
				g = im->green[p];
				b = im->blue[p];
				rAcc += r;
				gain = rAcc / threshold;
				rAcc -= gain * threshold;
				np += gain * rconst;
				gAcc += g;
				gain = gAcc / threshold;
				gAcc -= gain * threshold;
				np += gain * gconst;
				bAcc += b;
				gain = bAcc / threshold;
				bAcc -= gain * threshold;
				np += gain * bconst;
				*current = cmaptox[np];
				current++;
			}
		}
		break;
		case ditherBw:
		for (y=0; (y < im->sy); y++) {
			for (x=0; (x < im->sx); x++) {
				int r, p, np;
				p = gdImageGetPixel(im, x, y);
				np = 0;
				r = (im->red[p]+im->green[p]+im->blue[p])/3;
				rAcc += r;
				if (rAcc >= 255) {
					np += 1;
					rAcc -= 255;
				}		
				*current = cmaptox[np];
				current++;
			}
		}
		break;
	}
	image = XCreateImage(XtDisplay(imageCore), 
		DefaultVisualOfScreen(XtScreen(imageCore)), 
		DefaultDepthOfScreen(XtScreen(imageCore)), ZPixmap,
		0, imageData, im->sx, im->sy, 8, 0);
	if (pixmap != None) {
		XFreePixmap(XtDisplay(imageCore), pixmap);
	}
	sx = im->sx;
	sy = im->sy;
	rect[0].x = 0;	
	rect[0].y = 0;
	rect[0].width = sx;
	rect[0].height = sy;
	/* Set up clip rectangles to avoid rubber-banding into border areas */
	XSetClipRectangles(XtDisplay(imageCore), drawGc, 0, 0, rect, 1,
		YXSorted);
	XSetClipRectangles(XtDisplay(imageCore), xorGc, 0, 0, rect, 1,
		YXSorted);
	pixmap = XCreatePixmap(XtDisplay(imageCore), XtWindow(imageCore), 
		im->sx, im->sy, DefaultDepthOfScreen(XtScreen(imageCore)));
	XPutImage(XtDisplay(imageCore), pixmap, drawGc, image, 0, 0, 0, 0,
		im->sx, im->sy); 		
	XDestroyImage(image);
	XtVaSetValues(imageCore,
		XtNwidth, sx,
		XtNheight, sy,
		NULL);	
	gdImageDestroy(im);
	/* Drop objects, if any (reuse old space, though) */
	for (i=0; (i<objectsTotal); i++) {
		if (objects[i].url) {
			free(objects[i].url);
			objects[i].url = 0;
		}
		if (objects[i].comments) {
			free(objects[i].comments);
			objects[i].comments = 0;
		}
	}
	objectsTotal = 0;
	objectsEnsure(0);
	objects[0].t = notype;
	defaultUrl = 0;
	XClearWindow(XtDisplay(imageCore), XtWindow(imageCore));
	if (!mapname) {
		ExposeHandler(imageCore, 0, 0, 0);
		return 1;
	}
	if (!(in = fopen(mapname, "rb"))) {
		modalPopup("Map file not found (or we don't have\n"\
			"permission to open it).", modalInfo, 0, 0);	
		return 0;
	}
	commentsSpace = 80;
	comments = (char*) malloc(sizeof(char) * commentsSpace);
	strcpy(comments, "");
	commentsLength = 0;
	while (!feof(in)) {
		/* Should be plenty even for a 100-vertex jobbie */
		char line[1024];
		char *text;
		char *word;
		if (!fgets(line, 1024, in)) {
			break;
		}
		linenum++;
		if ((line[0] == '\n') || (!line[0])) {
			continue;
		}
		if (line[0] == '#') {
			text = line+1;
			commentsLength += strlen(text);
			while (commentsSpace < (commentsLength+1)) {
				commentsSpace *= 2;
				if (commentsSpace >= (commentsLength+1)) {
					comments = (char *) realloc(
						comments, sizeof(char) *
						commentsSpace);
				}
			}		
			strcat(comments, text);
			continue;
		}
		/* Parentheses == CERN! */
		if (strchr(line, '(')) {
			appData.cern = 1;
		}
		word = strtok(line, " ,\n");
		if (!word) {
			break;
		}
		if (!strncmp(word, "def", 3)) {
			if (defaultUrl) {
				free(defaultUrl);
			}
			word = strtok(0, " ,()\n");
			defaultUrl = mystrdup(word);
		} else if (!strncmp(word, "rect", 4)) {
			object *o;
			objectsEnsure(objectsTotal);
			o = &objects[objectsTotal];
			o->t = rectangle;
			o->complete = 0;
			o->comments = comments;
			commentsSpace = 80;
			comments = (char*) malloc(sizeof(char) * commentsSpace);
			strcpy(comments, "");
			commentsLength = 0;
			/* NCSA puts URL right after command */
			if (!appData.cern) {
				if (!(word = strtok(0, " ,()\n"))) {
					complaint = "No URL after rect";
					goto fail;
				}
				o->url = mystrdup(word);
			}
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "No vertexes after rect";
				goto fail;
			}
			pointsEnsure(o, 2);
			o->points[0].x = atoi(word);	
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "Data missing after rect";
				goto fail;
			}
			o->points[0].y = atoi(word);	
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "Data missing after rect";
				goto fail;
			}
			o->points[1].x = atoi(word);	
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "Data missing after rect";
				goto fail;
			}
			o->points[1].y = atoi(word);	
			o->complete = 1;
			o->pointsTotal = 2;
			/* CERN puts URL at the end */
			if (appData.cern) {
				if (!(word = strtok(0, " ,()\n"))) {
					complaint = "No URL after rect";
					goto fail;
				}
				o->url = mystrdup(word);
			}
			objectsTotal++;
		} else if (!strncmp(word, "circ", 4)) {
			object *o;
			objectsEnsure(objectsTotal);
			o = &objects[objectsTotal];
			o->t = circle;
			o->complete = 0;
			o->url = mystrdup(word);
			o->comments = comments;
			commentsSpace = 80;
			comments = (char*) malloc(sizeof(char) * commentsSpace);
			strcpy(comments, "");
			commentsLength = 0;
			/* NCSA puts URL right after command */
			if (!appData.cern) {
				if (!(word = strtok(0, " ,()\n"))) {
					complaint = "No URL after circle";
					goto fail;
				}
				o->url = mystrdup(word);
			}
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "Data missing after circle";
				goto fail;
			}
			o->points[0].x = atoi(word);	
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "Data missing after circle";
				goto fail;
			}
			o->points[0].y = atoi(word);	
			if (!(word = strtok(0, " ,()\n"))) {
				complaint = "Radius missing after circle";
				goto fail;
			}
			if (appData.cern) {
				o->points[1].y = o->points[0].y + atoi(word);
				o->points[1].x = o->points[0].x;
			} else {
				o->points[1].x = atoi(word);	
				if (!(word = strtok(0, " ,()\n"))) {
					complaint = "Data missing after circle";
					goto fail;
				}
				o->points[1].y = atoi(word);	
			}
			o->complete = 1;
			o->pointsTotal = 2;
			/* CERN puts URL at the end */
			if (appData.cern) {
				if (!(word = strtok(0, " ,()\n"))) {
					complaint = "No URL after circle";
					goto fail;
				}
				o->url = mystrdup(word);
			}
			objectsTotal++;
		} else if (!strncmp(word, "poly", 4)) {
			object *o;
			objectsEnsure(objectsTotal);
			o = &objects[objectsTotal];
			o->t = polygon;
			o->complete = 0;
			o->url = mystrdup(word);
			o->pointsTotal = 0;
			o->comments = comments;
			commentsSpace = 80;
			comments = (char*) malloc(sizeof(char) * commentsSpace);
			strcpy(comments, "");
			commentsLength = 0;
			/* NCSA puts URL right after command */
			if (!appData.cern) {
				if (!(word = strtok(0, " ,()\n"))) {
					complaint = "No URL after poly";
					goto fail;
				}
				o->url = mystrdup(word);
			}
			while(1) {
				pointsEnsure(o, o->pointsTotal);
				if (!(word = strtok(0, " ,()\n"))) {
					/* OK -- usually */
					if (o->pointsTotal < 3) {
						complaint =
					"Poly with less than 3 vertexes";
						goto fail;
					}
					if (appData.cern) {
						complaint =
				"URL missing for poly";
						goto fail;
					}
					break;
				}
				/* CERN puts the URL at the end. Fortunately
					it can never begin with a digit. */
				if (appData.cern) {
					if (!isdigit(word[0])) {
						o->url = mystrdup(word);
						break;
					}
				}		
				o->points[o->pointsTotal].x = atoi(word);
				if (!(word = strtok(0, " ,()\n"))) {
		complaint = "Y coordinate missing for vertex in poly";
					goto fail;
				}
				o->points[o->pointsTotal].y = atoi(word);
				o->pointsTotal++;
			}
			o->complete = 1;
			objectsTotal++;
		}
	}
	fclose(in);			
	trailingComments = comments;
	ExposeHandler(imageCore, 0, 0, 0);
	return 1;
fail:	
	fclose(in);
	for (i=0; (i<objectsTotal); i++) {
		objects[i].t = notype;
	}
	objectsTotal = 0;
	XFreePixmap(XtDisplay(imageCore), pixmap);
	pixmap = 0;
	XClearWindow(XtDisplay(imageCore), XtWindow(imageCore));
	sprintf(estring, "Line %d: %s", linenum, complaint);
	modalPopup(estring, modalInfo, 0, 0);
	return 0;
}

static void (*modalCallbackTo)(void *client_data, int result);
static void *modalClientDataKeep;
static Widget modalCommandOK;
static Widget modalCommandCancel;
static Widget modalTransientShell;
static void ModalCallback(Widget w, XtPointer client_data, 
	XtPointer call_data);

void modalPopup(char *s, modalType t, 
	void (*callback)(void *client_data, int result), 
	void *client_data) {
	static Widget form;
	static Widget text;
	static int init = 0;
	if (!init) {
		modalTransientShell = XtVaCreatePopupShell(
			"modalDialog",
			transientShellWidgetClass,
			top,
			XtNallowShellResize, True,
			XtNinput, True,
			XtNtransientFor, top,		
			NULL);
		form = XtVaCreateManagedWidget(
			"modalDialogForm",
			formWidgetClass,
			modalTransientShell,
			NULL);
		text = XtVaCreateManagedWidget(
			"modalDialogText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextRead,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapWord,
			XtNwidth, 320,
			XtNheight, 80,
			NULL);
		modalCommandOK = XtVaCreateManagedWidget(
			"OK",
			commandWidgetClass,
			form,
			XtNfromVert, text,
			NULL);
		modalCommandCancel = XtVaCreateManagedWidget(
			"Cancel",
			commandWidgetClass,
			form,
			XtNfromVert, text,
			XtNfromHoriz, modalCommandOK,
			NULL);
		XtAddCallback(modalCommandOK, XtNcallback, ModalCallback, 0);
		XtAddCallback(modalCommandCancel, XtNcallback, 
			ModalCallback, 0);
		init = 1;
	}
	XtVaSetValues(text,
		XtNstring, s,
		NULL); 
	modalCallbackTo = callback;
	modalClientDataKeep = client_data;
	XtPopup(modalTransientShell, XtGrabExclusive);
	if (t == modalInfo) {
		XtUnmapWidget(modalCommandCancel);
	} else {
		XtMapWidget(modalCommandCancel);
	}
	
}

static void ModalCallback(Widget w, XtPointer client_data, XtPointer call_data) {
	if (w == modalCommandOK) { 
		XtPopdown(modalTransientShell);
		if (modalCallbackTo) {
			modalCallbackTo(modalClientDataKeep, 1);
		}
	} else if (w == modalCommandCancel) {
		XtPopdown(modalTransientShell);
		if (modalCallbackTo) {
			modalCallbackTo(modalClientDataKeep, 0);
		}
	} 
}

void UrlReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void Url(Widget w, XtPointer client_data, XtPointer call_data) {
	static Widget form;
	static Widget label;
	static Widget prompt, promptComment;
	static Widget cancelCommand;
	static Widget okCommand;
	static Widget deleteCommand;
	static int init = 0;
	static XtActionsRec actions [] = {
		{ "ReturnAction", UrlReturnAction }
	};	
	if (!init) {
		XtTranslations TextFieldTranslations;
		urlTransientShell = XtVaCreatePopupShell(
			"Edit URL",
			transientShellWidgetClass,
			top,
			XtNallowShellResize, True,
			XtNinput, True,
			XtNtransientFor, top,		
			NULL);
		form = XtVaCreateManagedWidget(
			"urlForm",
			formWidgetClass,
			urlTransientShell,
			NULL);
		label = XtVaCreateManagedWidget(
			"Edit URL",
			labelWidgetClass,
			form,
			XtNborderWidth, 0,
			NULL);
		prompt = XtVaCreateManagedWidget(
			"URL:",
			labelWidgetClass,
			form,
			XtNfromVert, label,
			XtNborderWidth, 0,
			NULL);
		urlText = XtVaCreateManagedWidget(
			"urlText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextEdit,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNscrollHorizontal, XawtextScrollAlways,
			XtNfromVert, prompt,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapNever,
			XtNwidth, 320,
			XtNheight, 32,
			NULL);
		promptComment = XtVaCreateManagedWidget(
			"Comments:",
			labelWidgetClass,
			form,
			XtNfromVert, urlText,
			XtNborderWidth, 0,
			NULL);	
		urlCommentText = XtVaCreateManagedWidget(
			"urlCommentText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextEdit,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNscrollVertical, XawtextScrollAlways,
			XtNfromVert, promptComment,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapWord,
			XtNwidth, 320,
			XtNheight, 240,
			NULL);	
		TextFieldTranslations = XtParseTranslationTable
			("<Key>Return: ReturnAction()\n\
				Ctrl<Key>R: no-op(RingBell)\n\
				Ctrl<Key>S: no-op(RingBell)\n");
		XtOverrideTranslations(urlText, TextFieldTranslations);
		XtAppAddActions(appContext, actions, XtNumber(actions));	
		okCommand = XtVaCreateManagedWidget(
			"OK",
			commandWidgetClass,
			form,
			XtNfromVert, urlCommentText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		cancelCommand = XtVaCreateManagedWidget(
			"Cancel",
			commandWidgetClass,
			form,
			XtNfromHoriz, okCommand,
			XtNfromVert, urlCommentText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		deleteCommand = XtVaCreateManagedWidget(
			"Delete",
			commandWidgetClass,
			form,
			XtNfromHoriz, cancelCommand,
			XtNfromVert, urlCommentText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		XtAddCallback(okCommand, XtNcallback, Url, 0);
		XtAddCallback(cancelCommand, XtNcallback, Url, 0);
		XtAddCallback(deleteCommand, XtNcallback, Url, 0);
		init = 1;
	}
	if (w == cancelCommand) { 
		UrlCancel();
	} else if (w == okCommand) {
		UrlOK();
	} else if (w == deleteCommand) {
		UrlDelete();
	} else {
		if ((state == testing) || (state == deleting)) {
			if (lastSelected != (-1)) {
				if (objects[lastSelected].url) {
					XtVaSetValues(urlText,
						XtNstring, 
						objects[lastSelected].url,
						NULL);
				} else {
					XtVaSetValues(urlText,
						XtNstring, "",
						NULL);
				}
				if (objects[lastSelected].comments) {
					XtVaSetValues(urlCommentText,
						XtNstring, 
						objects[lastSelected].comments,
						NULL);
				} else {
					XtVaSetValues(urlCommentText,
						XtNstring, "",
						NULL);
				}
			}
		} else {
			XtVaSetValues(urlText,
				XtNstring, "",
				NULL);
			XtVaSetValues(urlCommentText,
				XtNstring, "",
				NULL);
		}
		XtPopup(urlTransientShell, XtGrabNone);
		if (state != deleting) {
			XtUnmapWidget(deleteCommand);
		} else {
			XtMapWidget(deleteCommand);
		}
	}
}

void UrlReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	UrlOK();
}

void UrlDelete() {
	object *o;
	object copy;
	int i;
	XtPopdown(urlTransientShell);
	if (lastSelected == (-1)) {
		return;
	}
	o = &objects[lastSelected];
	free(o->url);
	free(o->comments);
	o->url = 0;
	o->pointsTotal = 0;
	o->t = notype;
	o->complete = 0;
	o->selected = 0;
	/* We'll give our points space, etc away to the last position.
		Tricky, eh? */
	copy = *o;
	/* Move everyone down */
	for (i=lastSelected; (i < (objectsTotal-1)); i++) {	
		objects[i] = objects[i+1];
	}
	lastSelected = (-1);
	objects[objectsTotal-1] = copy;
	objectsTotal--;
	changed = 1;
	ExposeHandler(imageCore, 0, 0, 0);
}
			
void UrlOK() {
	char *url, *comments;
	object *o;
	XtPopdown(urlTransientShell);
	changed = 1;
	XtVaGetValues(urlText,
		XtNstring, &url,
		NULL);
	XtVaGetValues(urlCommentText,
		XtNstring, &comments,
		NULL);
	switch(state) {
		case polyUrl:
		o = &objects[objectsTotal];
		if (o->comments) {
			free(o->comments);
		}
		o->comments = mystrdup(comments);	
		if (o->url) {
			free(o->url);
		}
		o->url = mystrdup(url);
		objectDraw(o, XtDisplay(imageCore), XtWindow(imageCore));
		o->complete = 1;
		objectsTotal++;	
		changed = 1;
		objectsEnsure(objectsTotal);
		objects[objectsTotal].complete = 0;
		objects[objectsTotal].pointsTotal = 0;
		state = polyPosition;
		objectDraw(o, XtDisplay(imageCore), XtWindow(imageCore));
		break;
		case circleUrl:
		o = &objects[objectsTotal];
		if (o->comments) {
			free(o->comments);
		}
		o->comments = mystrdup(comments);	
		if (o->url) {
			free(o->url);
		}
		o->url = mystrdup(url);
		o->complete = 1;
		objectsTotal++;
		changed = 1;
		objectDraw(o, XtDisplay(imageCore), XtWindow(imageCore));
		state = circlePosition;
		break;
		case rectUrl:	
		o = &objects[objectsTotal];
		if (o->comments) {
			free(o->comments);
		}
		o->comments = mystrdup(comments);	
		if (o->url) {
			free(o->url);
		}
		o->url = mystrdup(url);
		o->complete = 1;
		objectsTotal++;
		changed = 1;
		objectDraw(o, XtDisplay(imageCore), XtWindow(imageCore));
		state = rectPosition;
		break;
		case testing:
		case deleting:
		if (lastSelected != (-1)) {
			o = &objects[lastSelected];
			if (o->url) {
				free(o->url);
			}
			o->url = mystrdup(url);
		}
		break;
	}
}

void UrlCancel() {
	XtPopdown(urlTransientShell);
	/* We simulate a third-button click to get the cancel effect */
	switch(state) {
		case polyUrl:
		state = polyAddpoints;
		Button3Down(imageCore, 0, 0, 0);	
		break;
		case circleUrl:
		state = circleDrag;
		Button3Down(imageCore, 0, 0, 0);	
		break;
		case rectUrl:
		state = rectDrag;
		Button3Down(imageCore, 0, 0, 0);	
		break;
	}
}

void UrlDefaultReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void UrlDefault(Widget w, XtPointer client_data, XtPointer call_data) {
	static Widget form;
	static Widget label;
	static Widget prompt;
	static Widget cancelCommand;
	static Widget okCommand;
	static Widget deleteCommand;
	static int init = 0;
	static XtActionsRec actions [] = {
		{ "ReturnAction", UrlDefaultReturnAction }
	};	
	if (!init) {
		XtTranslations TextFieldTranslations;
		urlDefaultTransientShell = XtVaCreatePopupShell(
			"Edit URL",
			transientShellWidgetClass,
			top,
			XtNallowShellResize, True,
			XtNinput, True,
			XtNtransientFor, top,		
			NULL);
		form = XtVaCreateManagedWidget(
			"urlDefaultForm",
			formWidgetClass,
			urlDefaultTransientShell,
			NULL);
		label = XtVaCreateManagedWidget(
			"Edit URL",
			labelWidgetClass,
			form,
			XtNborderWidth, 0,
			NULL);
		prompt = XtVaCreateManagedWidget(
			"URL:",
			labelWidgetClass,
			form,
			XtNfromVert, label,
			XtNborderWidth, 0,
			NULL);
		urlDefaultText = XtVaCreateManagedWidget(
			"urlDefaultText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextEdit,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNscrollHorizontal, XawtextScrollAlways,
			XtNfromVert, label,
			XtNfromHoriz, prompt,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapNever,
			XtNwidth, 320,
			XtNheight, 32,
			NULL);
		TextFieldTranslations = XtParseTranslationTable
			("<Key>Return: ReturnAction()\n\
				Ctrl<Key>R: no-op(RingBell)\n\
				Ctrl<Key>S: no-op(RingBell)\n");
		XtOverrideTranslations(urlDefaultText, TextFieldTranslations);
		XtAppAddActions(appContext, actions, XtNumber(actions));	
		okCommand = XtVaCreateManagedWidget(
			"OK",
			commandWidgetClass,
			form,
			XtNfromVert, urlDefaultText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		cancelCommand = XtVaCreateManagedWidget(
			"Cancel",
			commandWidgetClass,
			form,
			XtNfromHoriz, okCommand,
			XtNfromVert, urlDefaultText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		deleteCommand = XtVaCreateManagedWidget(
			"Delete",
			commandWidgetClass,
			form,
			XtNfromHoriz, cancelCommand,
			XtNfromVert, urlDefaultText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		XtAddCallback(okCommand, XtNcallback, UrlDefault, 0);
		XtAddCallback(cancelCommand, XtNcallback, UrlDefault, 0);
		XtAddCallback(deleteCommand, XtNcallback, UrlDefault, 0);
		init = 1;
	}
	if (w == cancelCommand) { 
		UrlDefaultCancel();
	} else if (w == okCommand) {
		UrlDefaultOK();
	} else if (w == deleteCommand) {
		UrlDefaultDelete();
	} else {
		if (defaultUrl) {
			XtVaSetValues(urlDefaultText,
				XtNstring, defaultUrl,
				NULL);
		} else {
			XtVaSetValues(urlDefaultText,
				XtNstring, "",
				NULL);
		}
		XtPopup(urlDefaultTransientShell, XtGrabNone);
		if (!defaultUrl) {
			XtUnmapWidget(deleteCommand);
		} else {
			XtMapWidget(deleteCommand);
		}
	}
}

void UrlDefaultReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	UrlDefaultOK();
}

void UrlDefaultDelete() {
	if (defaultUrl) {
		free(defaultUrl);
		defaultUrl = 0;
		UrlDefault(imageCore, 0, 0);
		changed = 1;
	}
	XtPopdown(urlDefaultTransientShell);
}
			
void UrlDefaultOK() {
	char *urlDefault;
	XtPopdown(urlDefaultTransientShell);
	XtVaGetValues(urlDefaultText,
		XtNstring, &urlDefault,
		NULL);
	if (defaultUrl) {
		free(defaultUrl);
		defaultUrl = 0;
	}
	if (strlen(urlDefault)) {
		defaultUrl = mystrdup(urlDefault);
	}
}

void UrlDefaultCancel() {
	XtPopdown(urlDefaultTransientShell);
}

static Widget saveAsText;
static Widget saveAsTransientShell;

void SaveAsReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params);

void SaveAs(Widget w, XtPointer client_data, XtPointer call_data) {
	static Widget form;
	static Widget label;
	static Widget prompt1;
	static Widget cancelCommand;
	static Widget okCommand;
	static Widget styleMenuButton, styleMenu, ncsaSmeBSB, cernSmeBSB;
	static int init = 0;
	static XtActionsRec actions [] = {
		{ "ReturnAction", SaveAsReturnAction }
	};	
	if (!mapfilename) {
		modalPopup("No map to save.", modalInfo, 0, 0);
		return;
	}	
	if (!init) {
		XtTranslations TextFieldTranslations;
		saveAsTransientShell = XtVaCreatePopupShell(
			"Save As",
			transientShellWidgetClass,
			top,
			XtNallowShellResize, True,
			XtNinput, True,
			XtNtransientFor, top,		
			NULL);
		form = XtVaCreateManagedWidget(
			"saveAsForm",
			formWidgetClass,
			saveAsTransientShell,
			NULL);
		label = XtVaCreateManagedWidget(
			"Save As",
			labelWidgetClass,
			form,
			XtNborderWidth, 0,
			NULL);
		styleMenuButton = XtVaCreateManagedWidget(
			"Style: NCSA",
			menuButtonWidgetClass,
			form,
			XtNfromHoriz, label,
 	      		XtNmenuName, "saveAsStyleMenu",
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		styleMenu = XtVaCreatePopupShell(
			"saveAsStyleMenu",
			simpleMenuWidgetClass,
			styleMenuButton,
			NULL);
		ncsaSmeBSB = XtVaCreateManagedWidget(
			"NCSA",
			smeBSBObjectClass,
			styleMenu, 
			NULL);
		XtAddCallback(ncsaSmeBSB, XtNcallback, SaveAs, 0);
		cernSmeBSB = XtVaCreateManagedWidget(
			"CERN",
			smeBSBObjectClass,
			styleMenu, 
			NULL);
		XtAddCallback(cernSmeBSB, XtNcallback, SaveAs, 0);
		prompt1 = XtVaCreateManagedWidget(
			"Map Filename:",
			labelWidgetClass,
			form,
			XtNfromVert, label,
			XtNborderWidth, 0,
			NULL);
		saveAsText = XtVaCreateManagedWidget(
			"saveAsText",	
			asciiTextWidgetClass,
			form,
			XtNeditType, XawtextEdit,
			XtNresizable, False,
			XtNresize, XawtextResizeNever,
			XtNscrollHorizontal, XawtextScrollAlways,
			XtNfromVert, label,
			XtNfromHoriz, prompt1,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			XtNwrap, XawtextWrapNever,
			XtNwidth, 320,
			XtNheight, 32,
			NULL);
		TextFieldTranslations = XtParseTranslationTable
			("<Key>Return: ReturnAction()\n\
				Ctrl<Key>R: no-op(RingBell)\n\
				Ctrl<Key>S: no-op(RingBell)\n");
		XtOverrideTranslations(saveAsText, TextFieldTranslations);
		XtOverrideTranslations(openGifText, TextFieldTranslations);
		XtAppAddActions(appContext, actions, XtNumber(actions));	
		okCommand = XtVaCreateManagedWidget(
			"OK",
			commandWidgetClass,
			form,
			XtNfromVert, openGifText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		cancelCommand = XtVaCreateManagedWidget(
			"Cancel",
			commandWidgetClass,
			form,
			XtNfromHoriz, okCommand,
			XtNfromVert, openGifText,
			XtNright, XtChainRight,	
			XtNleft, XtChainRight,	
			NULL);
		XtAddCallback(okCommand, XtNcallback, SaveAs, 0);
		XtAddCallback(cancelCommand, XtNcallback, SaveAs, 0);
		init = 1;
	}
	if (w == cancelCommand) { 
		XtPopdown(saveAsTransientShell);
	} else if (w == okCommand) {
		/* Simulate Return */
		SaveAsReturnAction(saveAsText, 0, 0, 0);
	} else if (w == cernSmeBSB) {
		appData.cern = 1;
		XtVaSetValues(styleMenuButton,
			XtNlabel, "Style: CERN",
			NULL);
	} else if (w == ncsaSmeBSB) {
		appData.cern = 0;
		XtVaSetValues(styleMenuButton,
			XtNlabel, "Style: NCSA",
			NULL);
	} else {
		if (appData.cern) {
			XtVaSetValues(styleMenuButton,
				XtNlabel, "Style: CERN",
				NULL);
		} else {
			XtVaSetValues(styleMenuButton,
				XtNlabel, "Style: NCSA",
				NULL);
		}
		if (mapfilename) {
			XtVaSetValues(saveAsText,
				XtNstring, mapfilename,
				NULL); 
		}
		XtPopup(saveAsTransientShell, XtGrabNone);
	}
}

void SaveAsReturnAction(Widget w, XEvent *event, 
	String *params, Cardinal *num_params) {
	char *mapname;
	XtPopdown(saveAsTransientShell);
	XtVaGetValues(saveAsText,
		XtNstring, &mapname,
		NULL);
	if (mapfilename) {
		free(mapfilename);
	}
	mapfilename = mystrdup(mapname);
	/* Inherit save */
	Save(w, 0, 0);
}
