/* 

  This class was created to control the drawing from within logo for the NeXT.

  Created by :
          Gary F. Desrochers (garyd@slate.mines.colorado.edu)
          Sean Kerstiens     (skerstie@slate.mines.colorado.edu)

  Created For:
          Colorado School of Mines.

  Last edited: 
          June 25,1992

*/

#import "DrawView.h"
#import <zone.h>
#import <appkit/Matrix.h>
#import <dpsclient/wraps.h>
#import <appkit/NXImage.h>
#import <appkit/NXEPSImageRep.h>
#import <appkit/Control.h>
#import <appkit/OpenPanel.h> 
#import <appkit/color.h>
#import "pslogo.h"
#import <stdlib.h>
#import <mach.h>
#import <appkit/Application.h>
#import <appkit/OpenPanel.h>
#import <appkit/SavePanel.h>

@interface DrawView(DrawViewPrivate)
// Declare methods static to this class.
- _write:(const char *)filename;
- _open:sender _newDrawing:(BOOL)doClear;
- _read:(const char *)filename _newDrawing:(BOOL)doClear;
- _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain;
- _drawTurtleImage;
@end
@implementation DrawView

- drawTurtleWith:(double)angle :(double)tox :(double)toy :(int)hide
// This routine draws the Turtle when asked to by setting the boolean to YES,
// setting the coordinates and then asking for a display.
// should be combined with clearTurtle later.
{
    //  First check on whether to display the turtle or not.
    if(hide == 0)
    {
      //recache in a sense clears the NXImage.
      [idTurtle recache];
      drawTurtle = YES;
    }
    else
    {
      drawTurtle = NO;
    }
    // Set angle and Coordinates.
    turtleAngle = -angle;
    sRect.origin.x = tox -22.7;
    sRect.origin.y = toy -32.5;
    return [self display];
}

- initFrame:(const NXRect *)tF
{
    [super initFrame:tF];

    //  Setup permanent for use later.
    //  See the .h file for explanation of permanent variables.
    sRect.origin.x = bounds.size.width/2.0-22.7;
    sRect.origin.y = bounds.size.height/2.0-32.5;
    sRect.size.width = 45.4;
    sRect.size.height = 65.0;   
    rRect = bounds;
    bbox[0] = rRect.origin.x;
    bbox[1] = rRect.origin.y;
    bbox[2] = rRect.size.width;
    bbox[3] = rRect.size.height;
    thisStruct = &firstStruct;
    thisStruct->nextStruct = NULL;
    drawTurtle = YES;

    //  Make the Background a NXImage.  Makes it so that for alot of the 
    //  things that need to be done (like clearing the turtle and showing the
    //  turtle) easier.
    [(idBackground = [[NXImage allocFromZone:[self zone]] initSize:&rRect.size]) useDrawMethod:@selector(drawBack:) inObject:self];

    //  Now the turtle will be easier as a NXImage also. 
    turtleAngle = 0.0;
    idTurtle=[[NXImage allocFromZone:[self zone]] initSize:&sRect.size];

    //  So that they are easier to Composite, set each background to clear.
    [idBackground setBackgroundColor:NX_COLORWHITE];
    [idTurtle setBackgroundColor:NX_COLORCLEAR];

    //  We want the Turtle scalable.  Just makes it easier to draw.
    [idTurtle setScalable:YES];

    return self;

}

-drawSelf:(NXRect *)r: (int)count
{
    //  Always composite copy the background.
    [idBackground composite:NX_COPY toPoint:&rRect.origin];

    //  Draw the turtle only if drawTurtle is set to YES.
    if (drawTurtle) {
        [self _drawTurtleImage];
        [idTurtle composite:NX_SOVER toPoint:&sRect.origin];
    }
    return self;
}

- drawLine:(double)type :(double)fromx :(double)fromy :(double)tox: (double)toy;
{  

   // Lock the focus for our drawing of the line.
   [idBackground lockFocus];

   // Make sure the Color is set to black.
   NXSetColor(NX_COLORBLACK);

   // Drawing the line is easy.  Mix the Postscript commands into one command.
   PSWline (fromx,fromy,tox,toy);

   NXSetColor(NX_COLORWHITE);
   // Now unlock our focus.
   [idBackground unlockFocus];

   // Now for the hard part.  What if our drawing needs to be created from
   // scratch.  This needs to be done when printing for instance.
   // Save the data for the lines into a linked list.
   // Later on the option may be added to remove the previous line drawn.
   // Wouldn't be to hard to do.

   // Fill up the two arrays.
   thisStruct->cArray[0] = dps_ucache;
   thisStruct->cArray[1] = dps_setbbox;
   thisStruct->oArray[0] = (float)fromx;
   thisStruct->oArray[1] = (float)fromy;
   thisStruct->cArray[2] = dps_moveto;
   thisStruct->oArray[2] = (float)tox;
   thisStruct->oArray[3] = (float)toy;
   thisStruct->cArray[3] = dps_lineto;
   thisStruct->nextStruct = (struct prStruct *)malloc(sizeof(struct prStruct));
   thisStruct = thisStruct->nextStruct;
   thisStruct->nextStruct = NULL;
   // O.K. Everything set up for later.

   return [self display];
}

-clearScreen;
//  A clear screen needs to remove everything in the link list except the
//  first element and then redraw the background.
{

    struct prStruct *oldStruct = &firstStruct,*tempStruct = &firstStruct;

    //  Free our old background.
    [idBackground free];

    //  Loop through the link lists and delete all the old lines.
    tempStruct = tempStruct->nextStruct;
    while(tempStruct != NULL)
    {
         oldStruct = tempStruct;
         tempStruct = tempStruct->nextStruct;
         free(oldStruct);
    }
    thisStruct = &firstStruct;
    thisStruct->nextStruct = NULL;

    //  Now set get a new Background.
    [(idBackground = [[NXImage allocFromZone:[self zone]] 
                     initSize:&rRect.size]) 
                     useDrawMethod:@selector(drawBack:) 
                     inObject:self];

    // Set background Attribute
    [idBackground setBackgroundColor:NX_COLORCLEAR];

    return [self display];
}

- drawBack: image
// This routine will redraw the background if needed.  It is called for 
// routines like the printing routine.
{
     struct prStruct *tempStruct = &firstStruct;
     // Here is the big secret as how to get it to draw alot of lines real fast.
     // I guess you could also loop through the link list and calculate all the
     // values needed for one DPSDoUserPath.  Never tried it.
     // Another way would be to make a typed memory stream.  That way the linked
     // list could be gotten rid of. (Who knows? The Shadow knows!)
     while(tempStruct->nextStruct != NULL)
     {
     DPSDoUserPath(tempStruct->oArray,4,dps_float,tempStruct->cArray,
                   4,bbox,dps_ustroke);
     tempStruct = tempStruct->nextStruct;
     }
    return self;
}
- _drawTurtleImage
// This routine calls the post script to draw the turtle image.
{
   [idTurtle lockFocus];
   PSWTurtle(turtleAngle);
   [idTurtle unlockFocus];
   return self;
}

- print;
{    
// Printing is rather simple; just send printPSCode: to the text view
// you wish to print. The print panel will automatically pop up and unless
// the user cancels the printout the text view will be printed.
// This routine prints out the screen minus the turtle in postscript.

    // Need to save the old turtle type.
    BOOL save = drawTurtle;

    //  First turn of compositing the turtle.
    drawTurtle = NO;

    //  Find out stuff about printing.
    [NXApp printInfo];

    //  Print it out.  It is that simple.
    [self printPSCode:self];

    //  Put the turtle compositing back the way it was
    drawTurtle = save;

    return self;
}

- save:sender
// Called when Save is picked from the menu.
{
  return [self _doSaveWithNewName:NO retainName:YES];
}

- saveAs:sender
// Called when Save As... is picked from the menu.
{  
  return [self _doSaveWithNewName:YES retainName:YES];
}

- saveTo:sender
// Called when Save To... is picked from the menu.
{
  return [self _doSaveWithNewName:YES retainName:NO];
}

- add:sender
// Action method, called when the user chooses add in the menu.
{
   return [self _open:sender _newDrawing:NO];
}

- open:sender
// Action method, called when the user chooses open in the menu.
{
   return [self _open:sender _newDrawing:YES];
}

- _open:sender _newDrawing:(BOOL)doClear
// This is used for both open and add.
// The doClear variable says whether to add to or clear the drawing.
{
    const char *const *file;
    static const char *const fileType[2] = {"logo", NULL};
    id openPanel;
    char fullName[MAXPATHLEN];
    
    // Run the open panel, filtering for out type of document
    openPanel = [OpenPanel new];
    if ([openPanel runModalForTypes:fileType])
    {
       // Open the file returned by the open panel
       file = [openPanel filenames];
       // Need to piece together the name from filenames and directory calls.
       strcpy(fullName, [openPanel directory]);
       strcat(fullName, "/");
       strcat(fullName, *file);
       if(doClear)
          name = fullName;
       [self _read:fullName _newDrawing:doClear];
    }
    return self;
}
- _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain
{
    id savePanel;
    // filename to save into 
    const char *saveName; 
    NXZone *zone;

    // If the file is untitled or we are saving under a different name,
    // run the save panel to get a new name.
    zone = [self zone];
    if (!name || doNewName) {
	savePanel = [SavePanel new];
	[savePanel setRequiredFileType:"logo"];
	if ([savePanel runModalForDirectory:NULL file:name]) {
	   // If we want to keep this name, replace any old name.
	   if (doRetain) {
              NXZoneFree(zone, name);
              name = NXCopyStringBufferFromZone([savePanel filename], zone);
           }
	    saveName = [savePanel filename];
	} else
	    return self;
    } else
        // if we didn't run the Save Panel, save using the existing name.
	saveName = name;

    // Now do a _write routine.
    [self _write:saveName];

    return self;
}

- _write:(const char *)filename
// Writes a document to the given file using typedstreams.
{
    NXTypedStream *ts;
    float realVal;
    struct prStruct *tempStruct = &firstStruct;
    int howMany = 0;
    
    // Open the typed stream to a file for writing.
    ts = NXOpenTypedStreamForFile(filename, NX_WRITEONLY);
    if (ts)
       {
	    // Version of this write's data.
	    realVal = 1.0;
	    NXWriteType(ts, "f", &realVal);
            // Figure out how many lines there are.
            while(tempStruct->nextStruct != NULL)
	    {
	         howMany++;
                 tempStruct = tempStruct->nextStruct;
            }
            // Now save that number for the reading in of the file.
            /* NXWriteType(ts, "i", &howMany); */
            tempStruct = &firstStruct;
            // Continue by writing out all the lines.
            for(NXWriteType(ts,"i", &howMany);howMany >= 0;howMany--)
            {
	         NXWriteArray(ts, "f", 4, tempStruct->oArray);
	         NXWriteArray(ts, "c", 4, tempStruct->cArray);
                 tempStruct = tempStruct->nextStruct;
            }
            // Close the stream.  All done.
	    NXCloseTypedStream(ts);
        }
	return self;
}

- _read:(const char *)filename _newDrawing:(BOOL)doClear
// Reads a document from the given file using typedstreams.
{
    NXTypedStream *ts;
    float realVal;
    int howMany = 0;

    // Open the typed stream as a file.
    ts = NXOpenTypedStreamForFile(filename, NX_READONLY);
    if (ts)
       {
           // If this is an open the drawing needs to be cleared.
           if(doClear)
             [self clearScreen];
            // The version number is not used as of this version
            // but it needs to be retrieved anyway.
	    NXReadType(ts, "f", &realVal);
            // This next variable is used to find out how many lines
            // need to be added.
            NXReadType(ts, "i", &howMany);
            // Get howMany lines and add them into the drawing link list.
            for(;howMany >= 1;howMany--)
            {	    
	         NXReadArray(ts, "f", 4, thisStruct->oArray);
	         NXReadArray(ts, "c", 4, thisStruct->cArray);
                 thisStruct->nextStruct = 
                    (struct prStruct *)malloc(sizeof(struct prStruct));
		 thisStruct = thisStruct->nextStruct;
                 thisStruct->nextStruct = NULL;
            }
            // Close the typed stream (file)
	    NXCloseTypedStream(ts);
            // Tell the image to clear the image and that we want to 
            // redraw it when the next composite is called.
            [idBackground recache]; 
        }
    return [self display];
}
- new:sender
{
     // All we need to do is clear the screen for a new.
     return [self clearScreen];
}
@end
