
/* Generated by Interface Builder */

#import "StereoView.h"
#import "CubeView.h"
#import "Help.h"
#import <appkit/Control.h>
#import	<appkit/Button.h>
#import <appkit/graphics.h>
#import <appkit/OpenPanel.h>
#import <NXCType.h> 
#import <stdlib.h>
#import <math.h>
#import <strings.h>
#import <appkit/Pasteboard.h>
#import <dpsclient/dpsNeXT.h>
#import <defaults/defaults.h>  	// for NXArgv
#import <libc.h>		// for chdir, getwd
#define EYE 2.9/57.3
#define rad_to_deg  (180./M_PI)
#define MAXLINE 10000
#define MAXNUM  10000
#define MAXALLOC 100
#define TRUE -1
#define FALSE !TRUE

@implementation StereoView

- initFrame:(const NXRect *)frameRect
{
	
	textDidChange = 0;
	textNeedsSaving = 0;
  toshow = (datapoints **) calloc(MAXALLOC, sizeof(datapoints *));
  max_path = 100;
  path = (float *) calloc(2*max_path, sizeof(float));
	eyeOffset = EYE;
	openReq = [OpenPanel new];
	saveReq = [SavePanel new];
	[super initFrame:frameRect];
	bcopy(frameRect,&left_Rect,sizeof(NXRect));
	bcopy(frameRect,&right_Rect,sizeof(NXRect));

	NXDivideRect(&left_Rect,&right_Rect,NX_WIDTH(frameRect)/2.,2);

/*
	NXInsetRect(&left_Rect,0.1*NX_WIDTH(&left_Rect),0.1*NX_HEIGHT(&left_Rect));
	NXInsetRect(&right_Rect,0.1*NX_WIDTH(&right_Rect),0.1*NX_HEIGHT(&right_Rect));
*/
	leftView = [[CubeView alloc] initFrame:&left_Rect];
	rightView = [[CubeView alloc] initFrame:&right_Rect];
	[self setDrawOrigin:(NXCoord) NX_X(frameRect):(NXCoord) NX_Y(frameRect)];
	[self addSubview:leftView];
	[self addSubview:rightView];
	[self setAutoresizeSubviews:TRUE];
	fileNAME = (char *) calloc(1,256);
	strcpy(fileNAME,"Untitled");
	[self S_Reset:self];
	return self;
}

static char  angle_buffer[20];


- setDelegate:anObject
{
	delegate = anObject;
	return self;
}

- copy:sender			/* code contributed by Dan McCreary of NeXT */
{
  id pb = [Pasteboard new];	/* global Pasteboard object */ 
  NXStream  *st;		/* stream to collect data in */ 
  char      *data;		/* actual data buffer */ 
  int       length;		/* length of data */ 
  int       maxLength;		/* (not used here) */
				/* To see how to use the pasteboard */
				/* see page 10-33 of the SysRefMan */
  [self  prtCopy];
				/* declare that we will supply a */
				/* single type of data: PostScript */
  [pb declareTypes:&NXPostScriptPboard num:1 owner:self];
				/* get a stream which writes to memory */ 
  st = NXOpenMemory( NULL, 0, NX_WRITEONLY );
				/* write PostScript code for this view into the stream */ 
  [self copyPSCodeInside:NULL to:st];
				/* get actual data buffer from stream */ 
  NXGetMemoryBuffer( st, &data, &length, &maxLength );
				/* write PostScript data to pasteboard */
  [pb writeType:NXPostScriptPboard data:data length:length];
				/* deallocate stream, including its buffer */ 
  NXCloseMemory( st, NX_FREEBUFFER );
  [self display];
  return self; 
}


-(BOOL)acceptsFirstResponder
{				/* make this view accept first */
	return YES;		/* responder so it will understand copy */
}

/*
-resignFirstResponder
{
	return self;
}
*/
- showError:(char *)errorMessage
{
  NXRunAlertPanel("Error", errorMessage, "OK", NULL, NULL);
  return self;
}

- prtCopy
{
  	[leftView printPSCode:leftView];
  	[rightView printPSCode:rightView];
	[self display];
	return self;
}

- printPSCode:sender
{
  [leftView printPSCode:leftView];
  [rightView printPSCode:rightView];
  [self display];
  [super printPSCode:self];
  [self display];
  return self;
}

- textChanged:sender
{
	textDidChange = 1;
	return self;
}

- textEdited:sender
{
	textNeedsSaving = 1;
	return self;
}

- plot:sender
{
	NXStream	*stream;

	if (textDidChange == 1)	{
	if (delegate && [delegate respondsTo:@selector(stereoView:providePoints:)])
		[delegate stereoView:self providePoints:&stream];
		textDidChange = 0;
	[self readData:(NXStream *)stream];
	[leftView getStruct:&toshow:max_path:&path];
	[rightView getStruct:&toshow:max_path:&path];
	[leftView reScale:self];
	[rightView reScale:self];
		}
	[self set_Theta:self];
	[self set_Phi:self];
	[self set_dist:self];
    [self display];
	return self;
}

- clear:sender
{
  long int count;
  datapoints **all_data = toshow, *thesedata;

				/* release existing data */
  while ((thesedata = *all_data) != (datapoints *) NULL) {
    free(thesedata->displayed);
    count = 0;
    while(thesedata->all[count] != (float *) NULL) {
      free(thesedata->all[count]);
      count++;
    }
    free(thesedata->all);
    free(thesedata);
    *all_data++ = (datapoints *) NULL;
  }
	[leftView getStruct:&toshow:max_path:&path];
	[rightView getStruct:&toshow:max_path:&path];
	[leftView clear:self];
	[rightView clear:self];
	[self set_Theta:self];
	[self set_Phi:self];
	[self set_dist:self];
	[self display];
	[self textChanged:self];
	return self;
}

- S_Reset:sender
{
	float	floatValue;
	
	[leftView getStruct:&toshow:max_path:&path];
	[rightView getStruct:&toshow:max_path:&path];
	[ThetaSlider setFloatValue:0.];
	floatValue = [ThetaSlider floatValue];
  sprintf(angle_buffer, "%6.2f", rad_to_deg*floatValue);
  [showTheta setStringValue:angle_buffer];
	[PhiSlider setFloatValue:0.];
	floatValue = [PhiSlider floatValue];
  sprintf(angle_buffer, "%6.2f", rad_to_deg*floatValue);
  [showPhi setStringValue:angle_buffer];
	[DistanceSlider setFloatValue:2.0];
	floatValue = [DistanceSlider floatValue];
  sprintf(angle_buffer, "%6.2f", floatValue);
  [showDist setStringValue:angle_buffer];
	[leftView Reset:leftView];
	[rightView Reset:rightView];
	[leftView setTheta:-eyeOffset];
	[rightView setTheta:+eyeOffset];
	[[self window] setTitle:fileNAME];
    [self display];
	return self;
}

- set_Theta:sender
{
	float floatValue;
	
	floatValue = [ThetaSlider floatValue];
  sprintf(angle_buffer, "%6.2f", rad_to_deg*floatValue);
  [showTheta setStringValue:angle_buffer];
  [leftView setTheta:floatValue-eyeOffset];
  [rightView setTheta:floatValue+eyeOffset];
  [self display];
  return self;
}

- set_Phi:sender
{
	float floatValue;
	
	floatValue = [PhiSlider floatValue];

  sprintf(angle_buffer, "%6.2f", rad_to_deg*floatValue);
  [showPhi setStringValue:angle_buffer];
  [leftView setPhi:floatValue];
  [rightView setPhi:floatValue];
  [self display];
  return self;
}


- set_dist:sender
{
	float floatValue;
	
	floatValue = [DistanceSlider floatValue];

  sprintf(angle_buffer, "%6.2f", floatValue);
  [showDist setStringValue:angle_buffer];
  [leftView setdist:floatValue];
  [rightView setdist:floatValue];
  [self display];
  return self;
}

- setAxes:(int) intVal
{	
	[AxesButton setState:intVal];
	[leftView setAxes:intVal];
	[rightView setAxes:intVal];
	[self display];
	return self;
}

- (int)getAxes
{
	return [AxesButton state];
}

- toggle_Axes:sender
{
	[leftView toggleAxes:self];
	[rightView toggleAxes:self];
  [self display];
  return self;
}

- setCube:(int) intVal
{
	[CubeButton setState:intVal];
	[leftView setCube:intVal];
	[rightView setCube:intVal];
	[self display];
	return self;
}

- (int)getCube
{
	return [CubeButton state];
}

- toggle_Cube:sender
{
	[leftView toggleCube:self];
	[rightView toggleCube:self];
  [self display];
  return self;
}

- setEyeOffset:(float) floatVal
{
	int	eye;
	
	eyeOffset = floatVal;
			
	if (floatVal < 0.)eye = NO;
		else eye = YES;
	[EyeButton setState:eye];
	[self set_Theta:self];
    [self display];
	return self;
}

- (float) getEyeOffset
{
	return eyeOffset;
}

- toggle_Eyes:sender
{
	[self setEyeOffset: - eyeOffset];
  return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	NXRect frameRect;
	
	[super sizeTo:width :height];

/*
	[leftView free];
	[rightView free];
*/
	[self getFrame:&frameRect];
	bcopy(&frameRect,&left_Rect,sizeof(NXRect));
	bcopy(&frameRect,&right_Rect,sizeof(NXRect));

	NXDivideRect(&left_Rect,&right_Rect,NX_WIDTH(&frameRect)/2.,2);

/*
	leftView = [[CubeView alloc] initFrame:&left_Rect];
	rightView = [[CubeView alloc] initFrame:&right_Rect];
*/
	[leftView setFrame:&left_Rect];
	[leftView initialize];
	[rightView setFrame:&right_Rect];
	[rightView initialize];
	[self setDrawOrigin:(NXCoord) NX_X(&frameRect):(NXCoord) NX_Y(&frameRect)];
/*
	[self addSubview:leftView];
	[self addSubview:rightView];
	[self setAutoresizeSubviews:TRUE];
*/
	[self S_Reset:self];
	return self;
}

- windowDidResize:sender
{
	/*	This is not received since the controller handles it */
	printf("windowDidResize for StereoView\n");
	return self;
}

- windowWillResize:sender toSize:(NXSize *)aSize
{
	/*	This is not received since the controller handles it */
	printf("windowWillResize for StereoView\n");
	return self;
}

- openData:sender
{
  const char *const ext[5] = {"dat", "data", "3d", "S3d", NULL};

    if (textNeedsSaving)
		[self saveInRequest:sender];
  if([openReq runModalForTypes:ext] && (fileNAME = (char *)[openReq filename]))	{
    [self readSData:fileNAME];
	}
  else
    [self showError:"No file chosen or could not open file"];
  return self;
}

// saveRequest: saves the current window under its default name (found in
// the title bar). Note that if the title bar is empty or the default title
// is "Untitled" then saveRequest: will put up a save panel, giving the user
// a chance to specify a real title.

- saveRequest:sender
{
    const char *fileName;
    const char *const types[2] = {"S3d",
    				  NULL};
    
    id curWin = [self window];
    if (textNeedsSaving)
		[self saveInRequest:sender];
	[saveReq setRequiredFileType:types[0]];
    
    if (curWin == nil) 
	[self showError:"errorOnSaveRequest"];
    else {
	// Check to see if the current window is titled and the title is not
	// "Untitled". If so, save the file, else put up a save panel...
	fileName = [curWin title];
	if (strcmp (fileName, "Untitled"))
	    [self saveText:curWin inPath:fileName];
	else [self saveInRequest:sender];
    }
    textNeedsSaving = 0;
	return self;
}

// saveInRequest: gives the user a chance to save the current window
// under a new name. 

- saveInRequest:sender
{
    const char *fileName;
    const char *const types[2] = {"S3d",
    				  NULL};
    id		curWin;
    
    curWin = [self window];
    [saveReq setRequiredFileType:types[0]];

    if (curWin == nil) 
	[self showError:"errorOnSaveIn"];
    else {
	// Get a file name from the user; use title of the window as default.
	if (([saveReq runModalForDirectory:"."
						      file:[curWin title]]) &&
	      (fileName = [saveReq filename])) 
	    [self saveText:curWin inPath:fileName];
    }
    textNeedsSaving = 0;
    return self;
}

// saveWindow writes a window out the archive file whose name is specified
// by the second argument. The title of the current window is also set 
// accordingly. 

- saveText:(id)win inPath:(const char *)name
{	
	FILE *output;
	char thisline[MAXLINE];
	NXStream	*stream;
	BOOL s_gets();
	
  	if((output = fopen(name, "w")) == NULL) {
    sprintf(thisline, "Unable to open file %s", name);
    [self showError:thisline];
    return NULL;
  }
    
	if (delegate && [delegate respondsTo:@selector(stereoView:providePoints:)])
		[delegate stereoView:self providePoints:&stream];

  	s_gets(thisline, MAXLINE, stream);
  	while (!NXAtEOS(stream)) {
		fprintf(output,"%s",thisline);
  		s_gets(thisline, MAXLINE, stream);
		}
	fclose(output);
    [win setTitle:name];
    return self;
}

// closeRequest closes the current window by simulating a click on the
// closebutton. A check should probably be added to give the user the 
// option of saving the window before closing

- closeRequest:sender
{
     if (textNeedsSaving)
		[self saveInRequest:sender];
/*
  [[self window] performClose:sender];
*/
	if (delegate && [delegate respondsTo:@selector(stereoView:clearText:)])
		[delegate stereoView:self clearText:fileNAME];
   strcpy(fileNAME,"Untitled");
   [self clear:self];
   [self S_Reset:self];
    textNeedsSaving = 0;
   return self;
}

- readSData:(char *)fileName
{

	if ([self readFile:fileName] != NULL) {
		[self clear:self];
		[[self window] setTitle:fileName];
		[self textChanged:self];
	    [self plot:self];
		}
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	
	NXDrawWhiteBezel(rects,rects);
	[leftView drawSelf:&left_Rect:1];
	[rightView drawSelf:&right_Rect:1];
	return self;
}

- readFile:(char *)fileName
{
	FILE *input;
	char thisline[MAXLINE], *cpt, *trimmer;
	
  	if((input = fopen(fileName, "r")) == NULL) {
    sprintf(thisline, "Unable to open file %s", fileName);
    [self showError:thisline];
    return NULL;
  }

	if (delegate && [delegate respondsTo:@selector(stereoView:clearText:)])
					[delegate stereoView:self clearText:fileName];
  fgets(thisline, MAXLINE, input);
  while (!feof(input)) {
	trimmer = cpt = &thisline[0];
	while(*trimmer++ != '\n');	trimmer--;
	while(*trimmer-- == ' ');	trimmer++;
	if (*trimmer != '\n') *trimmer++ = '\n'; *trimmer = '\0'; 
	if (delegate && [delegate respondsTo:@selector(stereoView:pointDidChange:)])
					[delegate stereoView:self pointDidChange:cpt];
  fgets(thisline, MAXLINE, input);
  }
	fclose(input);
	return self;
}

static void float_copy (register int n, register float * to, register int
		   to_inc, register float * from, register int from_inc)
{
  if (n > 0)
    while (n--) {
      *to = *from;
      to += to_inc;
      from += from_inc;
    }
}

BOOL	s_gets(thisline, max, stream)
	char thisline[];
	int max;
	NXStream *stream;
{
	int i;
	
	i = 0;
	while (!NXAtEOS(stream))	{
		thisline[i++] = NXGetc(stream);
		if (i > max) return TRUE;
		if (thisline[i-1] == '\n') {
			thisline[i] = '\0';
			return TRUE;
			}
	}
	return(FALSE);
}

- (BOOL) readData:(NXStream *)stream
{
  float *scratch, *fpt;
  char thisline[MAXLINE] ,buffer[MAXLINE], *cpt, c;
  long int count, ncols, nchars, nlines ,tlines, expected_cols;
  datapoints **all_data = toshow, *thesedata;
  float radius;
  float	shade;
  
  long pts_read = 0;
  long num_allocs = 0;
  tlines = 0;
  
  scratch = (float *) calloc(MAXNUM, sizeof(float));
				/* release existing data */
  while ((thesedata = *all_data) != (datapoints *) NULL) {
    free(thesedata->displayed);
    count = 0;
    while(thesedata->all[count] != (float *) NULL) {
      free(thesedata->all[count]);
      count++;
    }
    free(thesedata->all);
    free(thesedata);
    *all_data++ = (datapoints *) NULL;
  }
  all_data = toshow;		/* read new data */
  s_gets(thisline, MAXLINE, stream);
	tlines++;
  while (!NXAtEOS(stream)) {
  	radius = 0.;
	shade = NX_BLACK;
    if(sscanf(thisline, " %1s ", &c) != 1) {
      sprintf(thisline, "Unable to read a specification from text");
      [self showError:thisline];
    return NO;
    }
    switch(c) {
    case 'P': case 'p': 
/*
 *	Scan here for optional arguments such as shade and radius or width
 *	set shade, radius (== width for lines)  pattern?  labels?
*/
	sscanf(thisline, "%s %f %f", buffer, &radius, &shade);
	
	case 'N': case 'n': case 'L': case 'l': case 'B':
    case 'b':
  	s_gets(thisline, MAXLINE, stream);	 /* load a new line */
	tlines++;
      break;
    default:
		/*
      sprintf(thisline, "the beginning of text in line %d doesn't look like a specification",tlines);
      [self showError:thisline];
		return NO;
		*/
      c = 'p';
	/* s_gets(thisline, MAXLINE, stream); *//* load a new line */
	tlines++;
    }
    cpt = thisline; fpt = scratch; ncols = 0;
	while(sscanf(cpt, "%f%n", fpt, &nchars) == 1) {
      cpt += nchars;
      if(++ncols > 3) {
	  	sprintf(buffer,
			"More than 3 columns read in line %d\n%s\n",
			tlines-1, thisline);
		[self showError:buffer];
		return NO;
		}
	  if (pts_read++ == MAXNUM)	{
	  	sprintf(thisline, "Storage space for pts. exceeded");
		[self showError:thisline];
    return NO;
		}
      fpt++;
    }
    if (ncols == 0) {
      sprintf(buffer,
	   "Line %d: <%s> does not contain numeric data",tlines,thisline);
      [self showError:buffer];
    return NO;
    }
				/* digest the rest of the lines */
    expected_cols = ncols; nlines = 1;
    while(s_gets(thisline, MAXLINE, stream)) {
      cpt = thisline; ncols = 0;	tlines++;
      while(sscanf(cpt, "%f %n", fpt, &nchars) == 1) {
	cpt += nchars;
	ncols++;
		  if (pts_read++ == MAXNUM)	{
	  	sprintf(thisline, "Storage space for pts. exceeded");
		[self showError:thisline];
    return NO;
		}
	fpt++;

      }
      if (!ncols) break;
      nlines++;
      if (ncols != expected_cols) {
	sprintf(buffer,
		"Expected %d values but got %d values at line %d\nBad Data is:%s",
		expected_cols, ncols, tlines,thisline);
	[self showError:buffer];
    return NO;
      }
    }				/* create the datapoints structure and */
				/* store the values */
    thesedata = *all_data++ = (datapoints *) malloc(sizeof(datapoints));
	if (++num_allocs > MAXALLOC)	{
		sprintf(thisline, "Number Distinct Groups > MAXALLOC");
		[self showError:thisline];
		return NO;
		}
    switch(c) {
    case 'N': case 'n':
      thesedata->type = NEITHER;
      break;
    case 'L': case 'l':
      thesedata->type = LINES;
      break;
    case 'P': case 'p':
      thesedata->type = POINTS;
	  thesedata->radius = radius;
	  thesedata->shade = shade;
/*
 *		Store radius, shade, label here in thesedata->radius, etc.
*/
      break;
    case 'B': case 'b':
      thesedata->type = BOTH;
      break;
    }
    thesedata->all = (float **)calloc((size_t) expected_cols + 1,
			      (size_t) sizeof(float *));
    for(ncols = 0; ncols < expected_cols; ncols++) {
      thesedata->all[ncols] = (float *)calloc((size_t) nlines, sizeof(float));
      float_copy(nlines, thesedata->all[ncols], 1, scratch + ncols,
		 expected_cols);
    }
    thesedata->all[expected_cols] = (float *)NULL;
				/* make sure path is large enough */
    if (2*nlines >= max_path)
      realloc(path, (2*nlines)*sizeof(float));
    thesedata->npts = nlines;
    memmove(thesedata->displayed = (float **) calloc(3, sizeof(float *)),
	    thesedata->all, 3 * sizeof(float *));	  
  }
  free(scratch);
  [self display];
  return YES;
}

@end
