
/* Copyright (C) 1992  AHPCRC, University of Minnesota
 * Earlier versions Copyright 1992,  Minnesota Supercomputer Center Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	University of Minnesota
 *
 * $Header: /usr/people/ken/gvl/bob/RCS/bob.c,v 2.2 92/10/27 16:50:11 ken Exp Locker: ken $
 *
 * $Log:	bob.c,v $
 * Revision 2.2  92/10/27  16:50:11  ken
 * Added basic point support
 * 
 * Revision 2.1  92/10/19  17:03:32  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  17:02:59  ken
 * Initial revision
 * 
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/Scale.h>
#include <Xm/List.h>
#include <Xm/ToggleB.h>
#include <Xm/ToggleBG.h>
#include <X11/Xatom.h>
#include <gl/gl.h>
#include <gl/device.h>
#include <gl/get.h>
#include <X11/Xirisw/GlxMDraw.h>

#include "util.h"
#include "skip.h"
#include "xtutil.h"
#include "glutil.h"
#include "draw.h"
#include "vox.h"
#include "bob.h"
#include "setup.h"


static void GrabSelection(void);


BobApp	*bob;


/*VARARGS1*/
static XtArgVal TempString(char *fmt, ...)
{
    va_list		args;
    static char		tbuf[128];
    static XmString	lastString = NULL;

    /*CONSTCOND*/    
    va_start(args, fmt);
    if (lastString)
	XmStringFree(lastString);
    
    (void) vsprintf(tbuf, fmt, args);
    lastString = XmStringCreateSimple(tbuf);
    
    return (XtArgVal) lastString;
}


void CheckDimension(int dim[3], int *maxdim, int center[3], int *stride)
{
    unsigned	i, maxi;
    int		dim2;
    
    if (!bob->file)
	return;
    
    dim2 = 0;
    for (i = 0; i < 3; ++i)
	if (dim[i] > dim2) {
	    dim2 = dim[i];
	    maxi = i;
	}
    if (bob->useRatio)
	for (i = 0; i < 3; ++i)
	    dim[i] = bob->dimRatio[i] * *maxdim;
    else
	for (i = 0; i < 3; ++i)
	    bob->dimRatio[i] = (float) dim[i] / (float) dim[maxi];
    
    *stride = MAX(*stride, 1);
    for (i = 0; i < 3; ++i) {
	dim[i] = MAX(dim[i], 2);
	dim[i] = MIN(dim[i], bob->file->dim[i]);
	while (*stride * dim[i] > bob->file->dim[i])
	    *stride -= 1;
    }
    *maxdim = MAX(MAX(dim[0], dim[1]), dim[2]);
    for (i = 0; i < 3; ++i) {
	dim2 = (dim[i] * *stride) / 2;
	center[i] -= dim2;
	if (center[i] < 0)
	    center[i] = 0;
	if (center[i] + dim[i] * *stride > bob->file->dim[i])
	    center[i] = bob->file->dim[i] - dim[i] * *stride;
	center[i] += dim2;
    }
}


void GetDimWidgets(int dim[3], int *maxdim, int center[3], int *stride)
{
    XmScaleGetValue(GetWidget("xcenter"), center + 0);
    XmScaleGetValue(GetWidget("ycenter"), center + 1);
    XmScaleGetValue(GetWidget("zcenter"), center + 2);
    XmScaleGetValue(GetWidget("stride"), stride);
    XmScaleGetValue(GetWidget("xsize"), dim + 0);
    XmScaleGetValue(GetWidget("ysize"), dim + 1);
    XmScaleGetValue(GetWidget("zsize"), dim + 2);
    XmScaleGetValue(GetWidget("msize"), maxdim);
}


void SetDimWidgets(int dim[3], int *maxdim, int center[3], int *stride)
{
    XmScaleSetValue(GetWidget("xcenter"), center[0]);
    XmScaleSetValue(GetWidget("ycenter"), center[1]);
    XmScaleSetValue(GetWidget("zcenter"), center[2]);
    XmScaleSetValue(GetWidget("stride"), *stride);
    XmScaleSetValue(GetWidget("xsize"), dim[0]);
    XmScaleSetValue(GetWidget("ysize"), dim[1]);
    XmScaleSetValue(GetWidget("zsize"), dim[2]);
    XmScaleSetValue(GetWidget("msize"), *maxdim);
}


void CoordinateDimensions(void)
{
    int		dim[3];
    int		center[3];
    int		maxdim, stride;
    
    GetDimWidgets(dim, &maxdim, center, &stride);
    CheckDimension(dim, &maxdim, center, &stride);
    SetDimWidgets(dim, &maxdim, center, &stride);
}


static void FileDimToScaleLimit(void)
{
    unsigned	*dim, maxdim;
    int		sv;
    
    if (!bob->file)
	return;
    
    dim = bob->file->dim;
    maxdim = MAX(MAX(dim[0], dim[1]), dim[2]);
    
    XmScaleGetValue(GetWidget("xcenter"), &sv);
    if (sv > dim[0])
	SetValue(XmNvalue, (XtArgVal) dim[0], NULL); 
    SetValue(XmNtitleString, TempString("X Center (1-%d)", dim[0]), NULL);
    SetValue(XmNmaximum, (XtArgVal) dim[0], GetWidget("xcenter"));
    
    XmScaleGetValue(GetWidget("ycenter"), &sv);
    if (sv > dim[1])
	SetValue(XmNvalue, (XtArgVal) dim[1], NULL); 
    SetValue(XmNtitleString, TempString("Y Center (1-%d)", dim[1]), NULL);
    SetValue(XmNmaximum, (XtArgVal) dim[1], GetWidget("ycenter"));
    
    XmScaleGetValue(GetWidget("zcenter"), &sv);
    if (sv > dim[2])
	SetValue(XmNvalue, (XtArgVal) dim[2], NULL); 
    SetValue(XmNtitleString, TempString("Z Center (1-%d)", dim[2]), NULL);
    SetValue(XmNmaximum, (XtArgVal) dim[2], GetWidget("zcenter"));
    
    XmScaleGetValue(GetWidget("xsize"), &sv);
    if (sv > dim[0])
	SetValue(XmNvalue, (XtArgVal) dim[0], NULL); 
    SetValue(XmNtitleString, TempString("X Size (1-%d)", dim[0]), NULL);
    SetValue(XmNmaximum, (XtArgVal) dim[0], GetWidget("xsize"));
    
    XmScaleGetValue(GetWidget("ysize"), &sv);
    if (sv > dim[1])
	SetValue(XmNvalue, (XtArgVal) dim[1], NULL); 
    SetValue(XmNtitleString, TempString("Y Size (1-%d)", dim[1]), NULL);
    SetValue(XmNmaximum, (XtArgVal) dim[1], GetWidget("ysize"));
    
    XmScaleGetValue(GetWidget("zsize"), &sv);
    if (sv > dim[2])
	SetValue(XmNvalue, (XtArgVal) dim[2], NULL); 
    SetValue(XmNtitleString, TempString("Z Size (1-%d)", dim[2]), NULL);
    SetValue(XmNmaximum, (XtArgVal) dim[2], GetWidget("zsize"));
    
    XmScaleGetValue(GetWidget("msize"), &sv);
    if (sv > maxdim)
	SetValue(XmNvalue, (XtArgVal) maxdim, NULL); 
    SetValue(XmNtitleString, TempString("Max Size (1-%d)", maxdim), NULL);
    SetValue(XmNmaximum, (XtArgVal) maxdim, GetWidget("msize"));
    
    XmScaleGetValue(GetWidget("frame"), &sv);
    if (sv > StackSize(bob->frameList))
	SetValue(XmNvalue, (XtArgVal) StackSize(bob->frameList), NULL);
    if (StackSize(bob->frameList) > 1)
	SetValue(XmNmaximum, (XtArgVal) StackSize(bob->frameList),
		 GetWidget("frame"));
    else
	SetValue(XmNmaximum, (XtArgVal) 2, GetWidget("frame"));
}


void AddDimEntry(char *dimline)
{
    char	s[256];
    XmString	xms;
    int		dim[3];
    int		center[3];
    int		maxdim, stride;
    float	*tbq, *tbt;
    
    if (dimline)
	(void) strncpy(s, dimline, 255);
    else {
	GetDimWidgets(dim, &maxdim, center, &stride);
	tbq = bob->vball->qrot;
	tbt = bob->vball->trans;
	(void) sprintf(s, 
		       "%dx%dx%d by %d  at %d,%d,%d  view %g %g %g %g %g %g %g",
		       dim[0], dim[1], dim[2], stride,
		       center[0], center[1], center[2],
		       tbq[0], tbq[1], tbq[2], tbq[3], 
		       tbt[0], tbt[1], tbt[2]);
    }
    
    xms = XmStringCreateSimple(s);
    XmListAddItemUnselected(GetWidget("dimlist"), xms, 0);
    XmStringFree(xms);
}


static void DeleteDimEntry(void)
{
    int		*plist, pcount;
    
    if (XmListGetSelectedPos(GetWidget("dimlist"), &plist, &pcount)) {
	XmListDeletePos(GetWidget("dimlist"), plist[0]);
	XtFree((XtPointer) plist);
    } else
	XmListDeletePos(GetWidget("dimlist"), 1);
}


static void ScanDim(char *dimline)
{
    char	*viewstr;
    int		i, nscan;
    int		dim[3];
    int		center[3];
    int		maxdim = 2, stride;
    float	tbq[4], tbt[3];
    
    viewstr = strstr(dimline,  "view");
    if (viewstr)
	nscan = viewstr - dimline;
    else
	nscan = strlen(dimline);
    
    for (i = 0; i < nscan; i++)
	if (!isdigit(dimline[i]))
	    dimline[i] = ' ';
    
    if (sscanf(dimline, "%d%d%d%d%d%d%d", dim+0, dim+1, dim+2, &stride,
	       center+0, center+1, center+2) < 7) {
	Error("Confusing dimension specification!\n");
	return;
    }
    
    if (viewstr) {
	viewstr += 4;
	if (sscanf(viewstr, "%f%f%f%f%f%f%f",
	       tbq + 0, tbq + 1, tbq + 2, tbq + 3,
	       tbt + 0, tbt + 1, tbt + 2) < 7) {
	    Error("Confusing view specification!\n");
	    return;
	}
	bob->vball->qrot[0] = tbq[0];
	bob->vball->qrot[1] = tbq[1];
	bob->vball->qrot[2] = tbq[2];
	bob->vball->qrot[3] = tbq[3];
	bob->vball->trans[0] = tbt[0];
	bob->vball->trans[1] = tbt[1];
	bob->vball->trans[2] = tbt[2];
    }
    
    bob->useRatio = False;
    CheckDimension(dim, &maxdim, center, &stride);
    SetDimWidgets(dim, &maxdim, center, &stride);
}


void SetCurrentFile(BobFile *bfile)
{
    int		fd;
    int		shmid;
    unsigned char *data;
    
    if (bob->file) {
	if (bob->file->fd)
	    (void) close(bob->file->fd);
	bob->file->fd = 0;
	if (bob->file->data)
	    (void) shmdt(bob->file->data);
	bob->file->data = NULL;
	bob->file->shmid = 0;
    }

    if (strncmp(bfile->fname, MEMFILE, strlen(MEMFILE)) == 0) {
	shmid = atoi(bfile->fname + strlen(MEMFILE));
	data = (unsigned char *) shmat(shmid, 0, 0);
	if (data  &&  data != (unsigned char *) -1) {
	    bfile->shmid = shmid;
	    bfile->data = data;
	}
    }

    if (!bfile->data) {
	fd = open(bfile->fname, O_RDONLY);
	if (fd < 0) {
	    Error("Can't open file %s\n", bfile->fname);
	    bob->file = NULL;
	    return;
	}
	bfile->fd = fd;
    }
    
    bob->file = bfile;
    FileDimToScaleLimit();
    XmListSelectItem(GetWidget("filelist"), bfile->listEntry, False);
    XmListSetBottomItem(GetWidget("filelist"), bfile->listEntry);
}


void SetFirstFile(void)
{
    BobFile		*bfile = SkipHead(bob->fileList);
    int			itemCount;
    XmStringTable	items;
    char		*entry = NULL;
    
    if (!bfile)
	return;
    SetCurrentFile(bfile);
    
    bob->iframe = bfile->firstFrame;
    XmScaleSetValue(GetWidget("frame"), bob->iframe);
    
    GetValue(XmNitemCount, &itemCount, NULL);
    GetValue(XmNitems, &items, GetWidget("dimlist"));
    if (itemCount) {
	XmListSelectItem(GetWidget("dimlist"), items[0], False);
	XmListSetBottomItem(GetWidget("dimlist"), items[0]);
	if (XmStringGetLtoR(items[0], XmSTRING_OS_CHARSET, &entry))
	    ScanDim(entry);
	Free(entry);
    }
    ReadData();
}


/*ARGSUSED*/
static Boolean ConvertSelect(Widget w, Atom *selection, Atom *target,
			     Atom *type, XtPointer *value, 
			     unsigned long *vallen, int *format)
{
    if (*target == XA_WINDOW) {
	Window	*cwin = MallocType(Window);
	
	MemCheck(cwin);
	*cwin = XtWindow(GetWidget("vox"));
	*value = (XtPointer) cwin;
	*vallen = sizeof(Window);
	*type = XA_WINDOW;
	*format = 8;
	
	return True;
    }
    
    if (*target == bob->xaColormap) {
	unsigned char	*cmap = CallocType(unsigned char, 768);
	unsigned	i;
	
	MemCheck(cmap);
	for (i = 0; i < 256; ++i) {
	    cmap[i*3]     = bob->ctab[i][0] * 255.0;
	    cmap[i*3 + 1] = bob->ctab[i][1] * 255.0;
	    cmap[i*3 + 2] = bob->ctab[i][2] * 255.0;
	}
	*value = (XtPointer) cmap;
	*vallen = 768;
	*type = bob->xaColormap;
	*format = 8;
	
	return True;
    }
    
    if (*target == bob->xaAlphamap) {
	unsigned char	*amap = CallocType(unsigned char, 256);
	unsigned	i;
	
	MemCheck(amap);
	for (i = 0; i < 256; i++)
	    amap[i] = bob->atab[i] * 255.0;
	*value = (XtPointer) amap;
	*vallen = 256;
	*type = bob->xaAlphamap;
	*format = 8;
	
	return True;
    }
    
    if (*target == bob->xaRenderInfo) {
	unsigned char	*cmap;
	char		*buf1;
	char		*buf2;
	unsigned	i;
	Matrix		viewMat, rendMat;
	float		elev, azim;
	
	if (!bob->vwin)
	    return False;

	cmap = CallocType(unsigned char, 1024);
	buf1 = CallocType(char, 1024);
	MemCheck(cmap);
	MemCheck(buf1);

	GLXwinset(bob->display, bob->vwin);
	pushmatrix();
	TrackballSetMatrix(bob->vball);
	mmode(MVIEWING);
	getmatrix(viewMat);
	minvert(viewMat, rendMat);
	popmatrix();

	for (i = 0; i < 256; ++i) {
	    cmap[i*4]     = bob->ctab[i][0] * 255.0;
	    cmap[i*4 + 1] = bob->ctab[i][1] * 255.0;
	    cmap[i*4 + 2] = bob->ctab[i][2] * 255.0;
	    cmap[i*4 + 3] = bob->atab[i]    * 255.0;
	}
	
	elev = atan2(rendMat[2][1], rendMat[2][2]) * 180.0 / M_PI;
	azim = atan2(rendMat[2][0], rendMat[2][2]) * 180.0 / M_PI;

	sprintf(buf1, "\
IXSIZE=%d\nIYSIZE=%d\n\
EYEX=%f\nEYEY=%f\nEYEZ=%f\n\
EDX=%f\nEDY=%f\nEDZ=%f\n\
EUX=%f\nEUY=%f\nEUZ=%f\n\
ELEV=%f\nAZIM=%f\n\
FOV=%f\n", 
		bob->vwidth, bob->vheight,
		 rendMat[3][0]+0.5,  rendMat[3][1]+0.5,  rendMat[3][2]+0.5,
		-rendMat[2][0],     -rendMat[2][1],     -rendMat[2][2],
		 rendMat[1][0],      rendMat[1][1],      rendMat[1][2],
		 elev, azim,
		bob->fieldOfView / 10.0
		);
		
	i = 1024 + strlen(buf1) + 1;
	buf2 = CallocType(char, i);
	MemCheck(buf2);

	memcpy(buf2, cmap, 1024);
	strcpy(buf2 + 1024, buf1);

	*value = (XtPointer) buf2;
	*vallen = i;
	*type = bob->xaRenderInfo;
	*format = 8;
	
	Free(cmap);
	Free(buf1);

	return True;
    }
    
    return False;
}


/*ARGSUSED*/
static void LoseSelect(Widget w, Atom *selection)
{
    bob->ownSelect = False;
}


static void GrabSelection(void)
{
    if (!bob->ownSelect) {
	bob->ownSelect = 
	    XtOwnSelection(GetWidget("vox"), bob->xaInterest,
			   XtLastTimestampProcessed(bob->display),
			   ConvertSelect, LoseSelect, NULL);
	(void) XtOwnSelection(GetWidget("vox"), bob->xaBobfile,
			      XtLastTimestampProcessed(bob->display),
			      ConvertSelect, LoseSelect, NULL);
	(void) XtOwnSelection(GetWidget("vox"), bob->xaRenderInfo,
			      XtLastTimestampProcessed(bob->display),
			      ConvertSelect, LoseSelect, NULL);
    }
}


/*ARGSUSED*/
static void HandleProperty(Widget w, XtPointer closure, XEvent *event,
			   Boolean *dispatch)
{
    unsigned char	*cmap = NULL;
    Atom		type;
    int			format;
    unsigned long	nitems, after;
    
    if (event->type != PropertyNotify  ||
	event->xproperty.state != PropertyNewValue)
	return;
    
    if (event->xproperty.atom == bob->xaColormap) {
	XGetWindowProperty(bob->display, XtWindow(GetWidget("vox")),
			   bob->xaColormap, 0, 192, False, bob->xaColormap,
			   &type, &format, &nitems, &after, &cmap);
	if (format  &&  nitems == 768) {
	    PackColor(cmap);
	    DrawVoxelWindow(False, False);
	}
	Free(cmap);
    }
    
    if (event->xproperty.atom == bob->xaAlphamap) {
	XGetWindowProperty(bob->display, XtWindow(GetWidget("vox")),
			   bob->xaAlphamap, 0, 64, False, bob->xaAlphamap,
			   &type, &format, &nitems, &after, &cmap);
	if (format  &&  nitems == 256) {
	    PackAlpha(cmap);
	    DrawVoxelWindow(False, False);
	}
	Free(cmap);
    }
    
    if (event->xproperty.atom == bob->xaBobfile) {
	XGetWindowProperty(bob->display, XtWindow(GetWidget("vox")),
			   bob->xaBobfile, 0, 1024, False, bob->xaBobfile,
			   &type, &format, &nitems, &after, &cmap);
	if (format) {
	    RestartFiles((char *) cmap, NULL);
	    SetFirstFile();
	    DrawVoxelWindow(False, False);
	    DrawFinderWindow();
	}
	Free(cmap);
    }
}


static void SetupSelection(void)
{
    bob->xaColormap = XInternAtom(bob->display, "IcolColormap", False);
    bob->xaAlphamap = XInternAtom(bob->display, "IcolAlphamap", False);
    bob->xaInterest = XInternAtom(bob->display, "IcolInterest", False);
    bob->xaBobfile  = XInternAtom(bob->display, "Bobfile", False);
    bob->xaRenderInfo = XInternAtom(bob->display, "RenderInfo", False);
    
    GrabSelection();
    XtAddEventHandler(GetWidget("vox"), PropertyChangeMask, FALSE,
		      HandleProperty, NULL);
}


static void VolumeAxis(unsigned axis)
{
    Matrix	m;
    char	buf[128];
    
    qmatrix(bob->vball->qrot, m);
    (void) sprintf(buf, "%.3f %.3f %.3f",
		   m[axis][0], m[axis][1], m[axis][2]);
    XmTextFieldSetString(GetWidget("axis"), buf);
}


static void StopAnimation(void)
{
    if (bob->animateWP)
	XtRemoveWorkProc(bob->animateWP);
    if (bob->anFile) {
	(void) fclose(bob->anFile);
	bob->anFile = NULL;
    }
    if (bob->anImage) {
	free(bob->anImage);	
	bob->anImage = NULL;
    }
    bob->anFollow = False;
    SetValue(XmNlabelString, TempString("Not Animating"),
	     GetWidget("anFrame"));
}


void SaveAnimationFrame(void)
{
    static char		*lastName = NULL;
    char		fname[128];
    register char	*src;
    register int	i;
    
    SetValue(XmNlabelString, TempString("Saving frame #%d", bob->anFrame),
	     GetWidget("anFrame"));
    (void) sprintf(fname, bob->anFileName, bob->anFrame);
    bob->anFrame++;
    
    if (fname[0] == '!') {
	(void) system(fname + 1);
	
    } else {
	if (!bob->anImage) {
	    if (bob->anPixmode)
		bob->anImageSize = bob->anWidth * bob->anHeight * 3;
	    else
		bob->anImageSize = bob->anWidth * bob->anHeight * 4;
	    bob->anImageSize = RoundUp(bob->anImageSize, 4096);
	    
	    bob->anImage = (unsigned long *) malloc(bob->anImageSize);
	    MemCheck(bob->anImage);
	}
	
	if (fname[0] == '|') {
	    bob->anFile = popen(fname+1, "w");
	    if (!bob->anFile) {
		Error("Can't open process %s\n", fname+1);
		return;
	    }
	    
	} else if (!bob->anFile || !lastName || strcmp(fname, lastName)) {
	    Free(lastName);
	    lastName = NewString(fname, 0);
	    if (bob->anFile)
		(void) fclose(bob->anFile);
	    bob->anFile = fopen(fname, "a+");
	    if (!bob->anFile) {
		Error("Can't open file %s\n", fname);
		return;
	    }
	    rewind(bob->anFile);
	    if (bob->anOffset)
		(void) fseek(bob->anFile, bob->anOffset, SEEK_SET);
	}
	
	GLXwinset(bob->display, bob->vwin);
	if (bob->anPixmode)
	    pixmode(PM_SIZE, 24);
	(void) lrectread(0, 0, bob->anWidth - 1, bob->anHeight - 1, 
			 bob->anImage);
	
	if (bob->anPixmode) {
	    if (bob->anBGR)
		(void) fwrite(bob->anImage, bob->anImageSize, 1, bob->anFile);
	    else {
		src = (char *) bob->anImage;
		for (i = 0; i < bob->anImageSize; i += 3) {
		    (void) fputc(src[i+2], bob->anFile);
		    (void) fputc(src[i+1], bob->anFile);
		    (void) fputc(src[i  ], bob->anFile);
		}
	    }
	} else {
	    src = (char *) bob->anImage;
	    if (bob->anBGR)
		for (i = 0; i < bob->anImageSize; i += 4) {
		    (void) fputc(src[i+1], bob->anFile);
		    (void) fputc(src[i+2], bob->anFile);
		    (void) fputc(src[i+3], bob->anFile);
		}
	    else
		for (i = 0; i < bob->anImageSize; i += 4) {
		    (void) fputc(src[i+3], bob->anFile);
		    (void) fputc(src[i+2], bob->anFile);
		    (void) fputc(src[i+1], bob->anFile);
		}
	}
	
	if (fname[0] == '|' && bob->anFile)
	    (void) pclose(bob->anFile);
    }
}


static void StartAnimation(void)
{
    float	theta;
    char	*tf;
    float	*qrot = bob->vball->qinc;
    Position	x, y, newx, newy;
    Dimension	shadow;
    Screen	*screen = XtScreen(bob->shell);
    
    TrackballStopSpinning(bob->vball);
    if (bob->spinWP)
	XtRemoveWorkProc(bob->spinWP);
    bob->spinWP = 0;
    
    bob->anFrame = 0;
    bob->anStep = 0;
    bob->anOffset = 0;
    bob->anFollow = False;
    
    if (bob->anFile) {
	(void) fclose(bob->anFile);
	bob->anFile = NULL;
    }
    Free(bob->anFileName);
    bob->anFileName = NULL;
    Free(bob->anImage);
    bob->anImage = NULL;
    
    tf = XmTextFieldGetString(GetWidget("axis"));
    if (sscanf(tf, "%f%f%f", qrot + 0, qrot + 1, qrot + 2) < 3) {
	Error("Bad Axis entry, using (0 1 0)\n");
	vset(qrot, 0, 1, 0);
    }
    vnormal(qrot);
    XtFree(tf);
    
    tf = XmTextFieldGetString(GetWidget("increment"));
    if (sscanf(tf, "%f", &theta) < 1) {
	Error("Bad rotation entry, using 2 deg\n");
	theta = 2;
    }
    theta *= -M_PI / 360.0;
    vscale(qrot, sinf(theta));
    qrot[3] = cosf(theta);
    XtFree(tf);
    
    tf = XmTextFieldGetString(GetWidget("frameinc"));
    if (sscanf(tf, "%d", &bob->anFrameInc) < 1) {
	Error("Bad frame increment, using 1\n");
	bob->anFrameInc = 1;
    }
    XtFree(tf);
    
    tf = XmTextFieldGetString(GetWidget("nstep"));
    if (strncasecmp(tf, "follow", 6) == 0) {
	bob->anFollow = True;
	bob->anStep = 1;
    } else if (sscanf(tf, "%u", &bob->anStep) < 1) {
	Error("Bad step entry, using 180\n");
	bob->anStep = 180;
    }
    XtFree(tf);
    
    if (XmToggleButtonGetState(GetWidget("anSave"))) {
	
	tf = XmTextFieldGetString(GetWidget("vsize"));
	if (sscanf(tf, "%u%u", &bob->anWidth, &bob->anHeight) == 2) {
	    if (bob->anWidth % 4) {
		bob->anWidth = RoundUp(bob->anWidth, 4);
		Error("Width must be a multiple of four.\n");
		Error("Rounding width up to %d\n", bob->anWidth);
	    }
	    SetValue(XmNwidth, (XtArgVal) bob->anWidth, NULL);	
	    SetValue(XmNheight, (XtArgVal) bob->anHeight, GetWidget("vox"));
	    GetValue(XmNshadowThickness, &shadow, GetWidget("voxframe"));
	    GetValue(XmNx, &x, NULL);
	    GetValue(XmNy, &y, bob->shell);
	    newx = x;
	    newy = y;
	    if (x + shadow + bob->vwidth > WidthOfScreen(screen))
		newx = WidthOfScreen(screen) - shadow - bob->vwidth;
	    if (y + shadow + bob->vheight > HeightOfScreen(screen))
		newy = HeightOfScreen(screen) - shadow - bob->vheight;
	    if (newx < -shadow)
		newx = -shadow;
	    if (newy < -shadow)
		newy = -shadow;
	    if (newx != x  ||  newy != y) {
		SetValue(XmNx, (XtArgVal) newx, NULL);
		SetValue(XmNy, (XtArgVal) newy, bob->shell);
	    }
	}
	XtFree(tf);
	
	bob->anFileName = XmTextFieldGetString(GetWidget("device"));
	
	tf = XmTextFieldGetString(GetWidget("offset"));
	bob->anOffset = ScanLong(tf);
	XtFree(tf);
	
	DrawVoxelWindow(False, True);
    }
    
    SetValue(XmNlabelString, TempString("Animating"), GetWidget("anFrame"));
    
    if (!bob->anFollow)
	bob->animateWP = 
	    XtAppAddWorkProc(bob->app, AnimateCB, (XtPointer) NULL);
}


static void StereoControl(unsigned start)
{
    Widget		w = GetWidget("stereoShell");
    int			status;
    static Window	oldFocus = 0;
    static int		oldRevert = 0;
    
    if (start) {
	XtPopup(w, XtGrabNone);
	status = XtGrabKeyboard(w, True, 
				GrabModeSync, GrabModeSync, CurrentTime);
	if (status != GrabSuccess) {
	    XtPopdown(w);
	    return;
	}
	
	XGetInputFocus(bob->display, &oldFocus, &oldRevert);
	XSetInputFocus(bob->display, XtWindow(w), oldRevert, CurrentTime);
	XAllowEvents(bob->display, AsyncBoth, CurrentTime);
	XFlush(bob->display);
	
	GLXwinset(bob->display, bob->swin);
	if (getgdesc(GD_STEREO))
	    setmonitor(STR_RECT);
	
	bob->stereo = 1;
	
    } else {
	XtPopdown(GetWidget("stereoShell"));
	
	XSetInputFocus(bob->display, oldFocus, oldRevert, CurrentTime);
	oldFocus = oldRevert = 0;
	XtUngrabKeyboard(GetWidget("stereoShell"), CurrentTime);
	
	GLXwinset(bob->display, bob->swin);
	if (getgdesc(GD_STEREO))
	    setmonitor(HZ60);
	
	bob->stereo = 0;
    }
}


/*ARGSUSED*/
void FollowInput(XtPointer cdata, int *fd, XtInputId *xid)
{
    if (!bob->followFile)
	return;

    RestartFiles(NULL, bob->followFile);
    SetFirstFile();
    
    if (bob->anFollow) {
	TrackballSpin(bob->vball);
	TrackballCopy(bob->vball, bob->fball);
	if (bob->anFileName)
	    DrawVoxelWindow(False, True);
	else
	    DrawVoxelWindow(False, False);
    } else
	DrawVoxelWindow(False, False);
    DrawFinderWindow();
}


static void SetEnvironment(char *name, char *fmt, ...)
{
    char	*buf, val[256];
    int		l;
    va_list	args;
    
    va_start(args, fmt);
    (void) vsprintf(val, fmt, args);

    l = strlen(name) + strlen(val) + 2;
    buf = CallocType(char, l);
    MemCheck(buf);
    
    (void) strcpy(buf, name);
    (void) strcat(buf, "=");
    (void) strcat(buf, val);
    putenv(buf);
}


static char *SetFromLine(char *lines)
{
    char	*s, *buf;
    int		i, l;

    s = strchr(lines, '\n');
    if (!s)
	s = lines + strlen(lines);

    l = s - lines + 1;
    buf = CallocType(char, l);
    MemCheck(buf);
    
    for (i = 0; lines[i] != '\0' && lines[i] != '\n'; i++)
	buf[i] = lines[i];
    buf[i] = '\0';
    putenv(buf);
    
    if (s[0])
	return s + 1;
    return s;
}


static void ColorOut(FILE *file, char *fmt, int i)
{
    (void) fprintf(file, fmt, i, 
		   (int) (bob->ctab[i][0] * 255),
		   (int) (bob->ctab[i][1] * 255),
		   (int) (bob->ctab[i][2] * 255));
}



static void AlphaOut(FILE *file, char *fmt, int i)
{
    (void) fprintf(file, fmt, i, 
		   (int) (bob->atab[i] * 255),
		   (int) (bob->atab[i] * 255),
		   (int) (bob->atab[i] * 255));
}


static void RemoteRender(void)
{
    String		rendBuf, rendHost, s;
    unsigned		i;
    Matrix		viewMat, rendMat;
    float		elev, azim;
    FILE		*file;
    
    if (!bob->vwin)
	return;
    
    GLXwinset(bob->display, bob->vwin);
    pushmatrix();
    TrackballSetMatrix(bob->vball);
    mmode(MVIEWING);
    getmatrix(viewMat);
    minvert(viewMat, rendMat);
    popmatrix();
    
    elev = atan2(rendMat[2][1], rendMat[2][2]) * 180.0 / M_PI;
    azim = atan2(rendMat[2][0], rendMat[2][2]) * 180.0 / M_PI;
    SetEnvironment("ELEV", "%f", elev);
    SetEnvironment("AZIM", "%f", azim);

    SetEnvironment("IXSIZE", "%d", bob->vwidth);
    SetEnvironment("IYSIZE", "%d", bob->vheight);

    SetEnvironment("EYEX", "%f", rendMat[3][0]+0.5);
    SetEnvironment("EYEY", "%f", rendMat[3][1]+0.5);
    SetEnvironment("EYEZ", "%f", rendMat[3][2]+0.5);
    
    SetEnvironment("EDX", "%f", -rendMat[2][0]);
    SetEnvironment("EDY", "%f", -rendMat[2][1]);
    SetEnvironment("EDZ", "%f", -rendMat[2][2]);

    SetEnvironment("EUX", "%f", rendMat[1][0]);
    SetEnvironment("EUY", "%f", rendMat[1][1]);
    SetEnvironment("EUZ", "%f", rendMat[1][2]);

    SetEnvironment("FOV", "%f", bob->fieldOfView / 10.0);

    rendHost = XmTextFieldGetString(GetWidget("rendHost"));
    SetEnvironment("RENDHOST", rendHost);

    file = popen("rsh $RENDHOST 'cat > /tmp/ctab.vr'", "w");
    if (file) {
	for (i = 0; i < 64; i++) {
	    ColorOut(file, "%4d%4d%4d%4d   ", i      );
	    ColorOut(file, "%4d%4d%4d%4d   ", i +  64);
	    ColorOut(file, "%4d%4d%4d%4d   ", i + 128);
	    ColorOut(file, "%4d%4d%4d%4d\n" , i + 192);
	}
	(void) fclose(file);
    }
	
    file = popen("rsh $RENDHOST 'cat > /tmp/otab.vr'", "w");
    if (file) {
	for (i = 0; i < 64; i++) {
	    AlphaOut(file, "%4d%4d%4d%4d   ", i      );
	    AlphaOut(file, "%4d%4d%4d%4d   ", i +  64);
	    AlphaOut(file, "%4d%4d%4d%4d   ", i + 128);
	    AlphaOut(file, "%4d%4d%4d%4d\n" , i + 192);
	}
	(void) fclose(file);
    }

    rendBuf = XmTextGetString(GetWidget("rendText"));
    (void) system(rendBuf);
    
    XtFree(rendBuf);
}


/*ARGSUSED*/
Boolean SpinCB(XtPointer closure)
{
    if (!TrackballSpinning(bob->vball)) {
	/* No longer spinning, remove this work proc */
	bob->spinWP = 0;
	return TRUE;
    }
    
    TrackballSpin(bob->vball);
    TrackballCopy(bob->vball, bob->fball);
    if (bob->stereo)
	DrawStereoWindow(False);
    else {
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
    }
    
    return FALSE;
}


/*ARGSUSED*/
Boolean AnimateCB(XtPointer closure)
{
    if (bob->anStep && bob->anFrame >= bob->anStep) {
	/* Done animating */
	StopAnimation();
	return TRUE;
    }
    
    if (bob->anFrameInc) {
	bob->iframe += bob->anFrameInc;
	if (bob->iframe > StackSize(bob->frameList))
	    bob->iframe = 1;
	if (bob->iframe < 1)
	    bob->iframe = StackSize(bob->frameList);
	XmScaleSetValue(GetWidget("frame"), bob->iframe);
	ReadData();
    }
    
    TrackballSpin(bob->vball);
    TrackballCopy(bob->vball, bob->fball);
    if (bob->anFileName)
	DrawVoxelWindow(False, True);
    else
	DrawVoxelWindow(False, False);
    DrawFinderWindow();
    
    return FALSE;
}


/*ARGSUSED*/
void VoxExposeCB(Widget w, XtPointer closure, XtPointer callData)
{
    static unsigned	dimInit = 0;
    
    if (!dimInit) {
	PositionDimDialog();
	dimInit = 1;
    }
    DrawVoxelWindow(False, False);
}


/*ARGSUSED*/
void VoxInitCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GLXwinset(XtDisplay(w), glx->window);
    SetupGL();
    bob->vwin = glx->window;
    bob->vwidth = glx->width;
    bob->vheight = glx->height;
    SetView(glx->width, glx->height);
    if (!bob->blend) {
	SetValue(XmNset, (XtArgVal) True, GetWidget("maxval"));
	SetValue(XmNsensitive, (XtArgVal) FALSE, GetWidget("stereo"));
	SetValue(XmNsensitive, (XtArgVal) FALSE, GetWidget("softEdge"));
    }
}


/*ARGSUSED*/
void VoxResizeCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GLXwinset(XtDisplay(w), glx->window);
    bob->vwidth = glx->width;
    bob->vheight = glx->height;
    SetView(glx->width, glx->height);
}


/*ARGSUSED*/
void VoxInputCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    static unsigned	mouseMoved;
    
    GrabSelection();
    if (bob->spinWP)
	XtRemoveWorkProc(bob->spinWP);
    bob->spinWP = 0;
    MouseOnTrackball(glx->event, bob->vwidth, bob->vheight, bob->vball);
    
    switch (glx->event->type) {
      case ButtonPress:
	mouseMoved = 0;
	break;
	
      case MotionNotify:
	mouseMoved = 1;
	TrackballCopy(bob->vball, bob->fball);
	DrawFinderWindow();
	DrawVoxelWindow(True, False);
	break;
	
      case ButtonRelease:
	if (TrackballSpinning(bob->vball))
	    bob->spinWP = XtAppAddWorkProc(bob->app, SpinCB, (XtPointer) NULL);
	else if (mouseMoved)
	    DrawVoxelWindow(False, False);
	break;
    }
}


/*ARGSUSED*/
void StereoExposeCB(Widget w, XtPointer closure, XtPointer callData)
{
    if (bob->stereo)
	DrawStereoWindow(False);
}


/*ARGSUSED*/
void StereoInitCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GLXwinset(XtDisplay(w), glx->window);
    SetupGL();
    bob->swin = glx->window;
    bob->swidth = glx->width;
    bob->sheight = glx->height;
}


/*ARGSUSED*/
void StereoResizeCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GLXwinset(XtDisplay(w), glx->window);
    bob->swidth = glx->width;
    bob->sheight = glx->height;
}


/*ARGSUSED*/
void StereoInputCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    unsigned		doMouse = False;
    static unsigned	mouseMoved;
    
    GrabSelection();
    if (bob->spinWP)
	XtRemoveWorkProc(bob->spinWP);
    bob->spinWP = 0;
    
    switch (glx->event->type) {
      case KeyPress:
	StereoControl(False);
	break;
	
      case ButtonPress:
      case ButtonRelease:
	if (glx->event->xbutton.y > YOFFSET)
	    glx->event->xbutton.y -= YOFFSET;
	glx->event->xbutton.y = 
	    ((long)glx->event->xbutton.y * bob->sheight) / YMAXSTEREO;
	doMouse = True;
	break;
	
      case MotionNotify:
	if (glx->event->xmotion.y > YOFFSET)
	    glx->event->xmotion.y -= YOFFSET;
	glx->event->xmotion.y = 
	    ((long)glx->event->xmotion.y * bob->sheight) / YMAXSTEREO;
	doMouse = True;
	break;
    }
    
    if (doMouse) {
	MouseOnTrackball(glx->event, bob->swidth, bob->sheight, bob->vball);
	
	switch (glx->event->type) {
	  case ButtonPress:
	    mouseMoved = 0;
	    break;
	    
	  case MotionNotify:
	    mouseMoved = 1;
	    TrackballCopy(bob->vball, bob->fball);
	    DrawStereoWindow(True);
	    break;
	    
	  case ButtonRelease:
	    if (TrackballSpinning(bob->vball))
		bob->spinWP = XtAppAddWorkProc(bob->app, SpinCB, 
					       (XtPointer) NULL);
	    else if (mouseMoved)
		DrawStereoWindow(False);
	    break;
	}
    }
}


/*ARGSUSED*/
void FinderExposeCB(Widget w, XtPointer closure, XtPointer callData)
{
    DrawFinderWindow();
}


/*ARGSUSED*/
void FinderInitCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GLXwinset(XtDisplay(w), glx->window);
    SetupGL();
    bob->fwin = glx->window;
    bob->fwidth = glx->width;
    bob->fheight = glx->height;
    SetView(glx->width, glx->height);
    DrawSetupFinder();
}


/*ARGSUSED*/
void FinderResizeCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GLXwinset(XtDisplay(w), glx->window);
    bob->fwidth = glx->width;
    bob->fheight = glx->height;
    SetView(glx->width, glx->height);
}


/*ARGSUSED*/
void FinderInputCB(Widget w, XtPointer closure, XtPointer callData)
{
    GlxDrawCallbackStruct *glx = (GlxDrawCallbackStruct *) callData;
    
    GrabSelection();
    if (bob->spinWP)
	XtRemoveWorkProc(bob->spinWP);
    bob->spinWP = 0;
    MouseOnTrackball(glx->event, bob->fwidth, bob->fheight, bob->fball);
    
    switch (glx->event->type) {
      case ButtonPress:
	TrackballStopSpinning(bob->vball);
	break;
	
      case MotionNotify:
	TrackballCopy(bob->fball, bob->vball);
	DrawFinderWindow();
	break;
	
      case ButtonRelease:
	if (glx->event->xbutton.button == Button1)
	    DrawVoxelWindow(False, False);
	break;
    }
}


/*ARGSUSED*/
void ButtonCB(Widget w, XtPointer closure, XtPointer callData)
{
    GrabSelection();
    
    switch ((ButtonEnum) closure) {
      case ButQuit:
	bob->running = FALSE;
	break;
      case ButOpen:
	bob->fileSave = 0;
	break;
      case ButSave:
	bob->fileSave = 1;
	break;
      case ButStereo:
	StereoControl(True);
	break;
      case ButHome:
	TrackballReset(bob->vball);
	TrackballCopy(bob->vball, bob->fball);
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
	break;
      case ButFlipX:
	TrackballFlip(bob->vball, 0);
	TrackballCopy(bob->vball, bob->fball);
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
	break;
      case ButFlipY:
	TrackballFlip(bob->vball, 1);
	TrackballCopy(bob->vball, bob->fball);
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
	break;
      case ButFlipZ:
	TrackballFlip(bob->vball, 2);
	TrackballCopy(bob->vball, bob->fball);
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
	break;
      case ButAnGo:
	StopAnimation();
	StartAnimation();
	break;
      case ButAnDone:
	StopAnimation();
	break;
      case ButAxisX:
	VolumeAxis(0);
	break;
      case ButAxisY:
	VolumeAxis(1);
	break;
      case ButAxisZ:
	VolumeAxis(2);
	break;
      case ButDimApply:
	ReadData();
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
	break;
      case ButDimAdd:
	AddDimEntry(NULL);
	break;
      case ButDimDelete:
	DeleteDimEntry();
	break;
      case ButStartCmap:
	(void) system("icol -title Color &");
	break;
      case ButStartAmap:
	(void) system("icol -title Alpha -m alpha &");
	break;
      case ButPoint:
	DrawVoxelWindow(False, False);
	break;
      case ButRender:
	RemoteRender();
	break;
    }
}


/*ARGSUSED*/
void ToggleCB(Widget w, XtPointer closure, XtPointer callData)
{
    XmToggleButtonCallbackStruct *td = 
	(XmToggleButtonCallbackStruct *) callData;
    int		sv;
    
    GrabSelection();
    
    switch ((ToggleEnum) closure) {
      case TogBounds:
	DrawVoxelWindow(False, False);
	break;
      case TogAnnotate:
	DrawVoxelWindow(False, False);
	break;
      case TogColorBar:
	DrawVoxelWindow(False, False);
	break;
      case TogDimActive:
	if (td->set) {
	    ReadData();
	    DrawVoxelWindow(False, False);
	}
	break;
      case TogSoft:
	DrawVoxelWindow(False, False);
	break;
      case TogMaxVal:
	SetValue(XmNsensitive, (XtArgVal) !td->set, GetWidget("stereo"));
	SetValue(XmNsensitive, (XtArgVal) !td->set, GetWidget("softEdge"));
	DrawVoxelWindow(False, False);
	break;
      case TogInterpolate:
	XmScaleGetValue(GetWidget("alpha"), &sv);
	if (td->set)
	    sv = (sv * 6) / 10;
	else
	    sv = (sv * 10) / 6;
	sv = MIN(sv, 200);
	XmScaleSetValue(GetWidget("alpha"), sv);
	bob->afact = (float) sv / 100.0;
	PackColor(NULL);
	ReadData();
	DrawVoxelWindow(False, False);
	break;
    }
}


/*ARGSUSED*/
void FileCB(Widget w, XtPointer closure, XtPointer callData)
{
    XmFileSelectionBoxCallbackStruct *fsb = 
	(XmFileSelectionBoxCallbackStruct *) callData;
    char	*fname;
    
    switch ((FileEnum) closure) {
      case FileOK:
	if (XmStringGetLtoR(fsb->value, XmSTRING_OS_CHARSET, &fname)) {
	    Free(bob->defName);
	    bob->defName = fname;
	    if (bob->fileSave)
		SaveDefaults();
	    else {
		RestartFiles(NULL, NULL);
		SetFirstFile();
		DrawVoxelWindow(False, False);
		DrawFinderWindow();
	    }
	}
	break;
      case FileCancel:
	break;
    }
}


/*ARGSUSED*/
void ListCB(Widget w, XtPointer closure, XtPointer callData)
{
    XmListCallbackStruct *ld = (XmListCallbackStruct *) callData;
    char	*entry;
    BobFile	key, *bfile;
    unsigned	i;
    
    GrabSelection();
    
    if (!XmStringGetLtoR(ld->item, XmSTRING_OS_CHARSET, &entry))
	return;
    switch ((ListEnum) closure) {
      case ListDim:
	ScanDim(entry);
	if (XmToggleButtonGadgetGetState(GetWidget("dactive"))) {
	    ReadData();
	    DrawVoxelWindow(False, False);
	    DrawFinderWindow();
	} else
	    DrawFinderSpecial(FinderDiff, 0);
	break;
      case ListFile:
	i = 0;
	while (entry[i]) {
	    if (isspace(entry[i])) {
		entry[i] = '\0';
		break;
	    }
	    ++i;
	}
	key.fname = entry;
	bfile = SkipSearch(bob->fileList, &key);
	Verify(bfile, "can't find file in list");
	bob->iframe = bfile->firstFrame;
	XmScaleSetValue(GetWidget("frame"), bob->iframe);
	ReadData();
	DrawVoxelWindow(False, False);
	DrawFinderWindow();
	break;
    }
    Free(entry);
}


/*ARGSUSED*/
void ScaleCB(Widget w, XtPointer closure, XtPointer callData)
{
    XmScaleCallbackStruct *sd = (XmScaleCallbackStruct *) callData;
    
    GrabSelection();
    
    switch ((ScaleEnum) closure) {
      case ScaleAlpha:
	if (bob->afact * 100 != sd->value) {
	    bob->afact = (float) sd->value / 100.0;
	    PackColor(NULL);
	    DrawVoxelWindow(False, False);
	}
	break;
      case ScaleFrame:
	sd->value = MIN(sd->value, StackSize(bob->frameList));
	if (bob->iframe != sd->value) {
	    bob->iframe = sd->value;
	    ReadData();
	    DrawVoxelWindow(False, False);
	}
	break;
      case ScaleCenterX:
      case ScaleCenterY:
      case ScaleCenterZ:
      case ScaleSizeX:
      case ScaleSizeY:
      case ScaleSizeZ:
      case ScaleSizeM:
      case ScaleStride:
	bob->useRatio = (ScaleEnum) closure == ScaleSizeM;
	if (sd->reason != XmCR_DRAG  &&
	    XmToggleButtonGadgetGetState(GetWidget("dactive"))) {
	    ReadData();
	    DrawVoxelWindow(False, False);
	    DrawFinderWindow();
	} else {
	    CoordinateDimensions();
	    DrawFinderSpecial(FinderDiff, 0);
	}
	break;
    }
}


/*ARGSUSED*/
void BugCB(Widget w, XtPointer closure, XtPointer callData)
{
    String	bugblab;
    char	mailcmd[256];
    FILE	*mailfile;
    
    bugblab = XmTextGetString(GetWidget("bugText"));
    if (!bugblab)
	return;
    
    (void) strncat(strcpy(mailcmd, "mail "), bob->bugAddress, 240);
    mailfile = popen(mailcmd, "w");
    if (!mailfile)
	return;
    
    (void) fprintf(mailfile, "To: %s\n", bob->bugAddress);
    (void) fprintf(mailfile, "Subject: Bob Bug Report\n\n");
    (void) fprintf(mailfile, "%s\n", bugblab);
    (void) fclose(mailfile);
    
    XtFree(bugblab);
}


main(int argc, char *argv[])
{
    XEvent	event;
    
    if (!SetupBob(&argc, argv))
	return 1;
    
    SetupSelection();
    
    while (bob->running) {
        XtAppNextEvent(bob->app, &event);
	XtDispatchEvent(&event);
    }
    
    SkipFreeData(bob->fileList, FreeFileEntry);
    return 0;
}

