/*
 *  YapDocument.m
 *  Author: Ali Ozer
 *  Created: Aug 28, 1988
 *  Modified for 0.8: Sep 1988
 *  Modified for 0.9 and revised: Feb & Mar 1989
 *  Modified for 1.0 and nibified: Jun & Jul 1989
 *  Modified for 2.0 and zonified: Aug 1990
 *  Modified: Jan 92 for BBFig by Izumi Ohzawa (izumi@pinoko.berkeley.edu)
 *
 *  YapDocument class implements the Yap documents --- for every open 
 *  window, we have another instance of the YapDocument class. Each instance
 *  loads itself into a separate zone.
 *
 *  You may freely copy, distribute and reuse the code in this example.
 *  NeXT disclaims any warranty of any kind, expressed or implied,
 *  as to its fitness for any particular use.
 */


#import <defaults/defaults.h>
#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <appkit/Font.h>
#import <appkit/PrintInfo.h>
#import <appkit/SavePanel.h>
#import <appkit/ScrollView.h>
#import <appkit/Window.h>
#import <appkit/TextField.h>
#import <appkit/Button.h>
#import <appkit/Panel.h>
#import <appkit/Text.h>
#import <objc/List.h>
#import <streams/streams.h>
#import <mach/mach.h>
#import <string.h>
#import <objc/zone.h>

#import "PSText.h"
#import "YapDocument.h"
#import "YapApp.h"
#import "YapOutput.h"

#define XOFFSET 5.0 	// Offset of subsequent windows
#define YOFFSET -20.0
#define MAXSIZE 1.0e38	// Maximum size of a text object

#define UNTITLED "UNTITLED"

@implementation YapDocument

static  char strbuf[128];

/*
 * The next two methods allow us to cache/reuse zones.
 */
static id zoneList = nil;

+ (NXZone *)newZone
{
    if (!zoneList || ![zoneList count]) {
	return NXCreateZone(vm_page_size, vm_page_size, YES);
    } else {
	return (NXZone *)[zoneList removeLastObject];
    }
}

+ (void)reuseZone:(NXZone *)aZone
{
    if (!zoneList) zoneList = [List new];
    [zoneList addObject:(id)aZone];
}


/*
 * Create a new instance of YapDocument with the specified file in the
 * buffer.  If the file cannot be opened, no document is created and nil
 * is returned.
 */
+ newFromFile:(const char *)fileName
{
    NXStream *stream = NULL;
    id docWin;		/* Window belonging to this document. */
    id textObj;		/* The text object we put in the window. */
    NXRect textFrame;	/* The frame of the text object in our window */

    if (fileName && !(stream = NXMapFile(fileName, NX_READONLY))) {
	return nil;
    }

    self = [[self allocFromZone:[self newZone]] init];
    
    if (![NXApp loadNibSection:"Document.nib" owner:self withNames:NO fromZone:[self zone]]) {
	NXCloseMemory (stream, NX_FREEBUFFER);
        [self free];
	return nil;
    }

    /*
     * Loading the nib file above sets the document outlet to the
     * scrollview that contains PS code;
     * so we can use this outlet to get at the window & such.
     */
    docWin = [document window];
    [[document docView] getFrame:&textFrame];
 
    /*
     * Put the window offset from the previous document window... If no
     * previous window exists, or the main window is undetermined, then
     */ 
    if ([NXApp mainWindow]) {
	NXRect winFrame, winLoc;
	[[NXApp mainWindow] getFrame:&winFrame];
	[[docWin class] getContentRect:&winLoc
			forFrameRect:&winFrame
			style:[docWin style]];
	[docWin moveTo:NX_X(&winLoc) + XOFFSET :NX_Y(&winLoc) + YOFFSET];
    }
	
    [self setName:UNTITLED];
    [docWin setDelegate:self];

    if (stream) {
	char *text;
	int len, maxLen;
	NXGetMemoryBuffer (stream, &text, &len, &maxLen);
	textObj = [[PSText allocFromZone:[self zone]] initFrame:&textFrame text:text alignment:NX_LEFTALIGNED];
	[self setName:fileName];
	NXCloseMemory (stream, NX_FREEBUFFER);
    } else {
	textObj = [[PSText allocFromZone:[self zone]] initFrame:&textFrame];
	[self setName:UNTITLED];
    }

    /*
     * Put this new text object in the window and free the IB-created one.
     */
    [[document setDocView:textObj] free];

    /*
     * Set various parameters.
     */

    outputView = [NXApp outputView];
    [document setAutoresizeSubviews:YES];
    [textObj setVertResizable:YES];		// Grow down as you type
    [textObj setHorizResizable:NO];		// Let it do sideways too
    [textObj setMonoFont:YES];
    [textObj setOpaque:YES];
    [textObj setMinSize:&textFrame.size];
    NX_WIDTH(&textFrame) = NX_HEIGHT(&textFrame) = MAXSIZE;
    [textObj setMaxSize:&textFrame.size];	// Can grow
    [textObj setSel:0:0];			// Set the selection
    [textObj setDelegate:self];
    [textObj sizeToFit];

    [computeBBSwitch setState:[outputView isFigureBB]];
    [gridEnableSwitch setState:[outputView isMeshON]];
    [blueEnableSwitch setState:[outputView isBlueON]];

    [docWin makeKeyAndOrderFront:self];

    [self initializePrintInfo];

    return self;
}

+ new
{
    return [self newFromFile:NULL];
}

- initializePrintInfo
{
    static BOOL printInfoInitialized = NO;
    if (!printInfoInitialized) {
	[[NXApp printInfo] setVertCentered:NO];
	[[NXApp printInfo] setHorizCentered:NO];
	[[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
	[[NXApp printInfo] setMarginLeft:36.0 right:36.0 top:72.0 bottom:72.0];
	printInfoInitialized = YES;
    }
    return self;
}

/*
 * Delegate method for the document Text object. We use this method
 * to detect when the text in the window is modified.
 */
- text:text isEmpty:(BOOL)empty
{
    if (![[document window] isDocEdited]) {
    	[[document window] setDocEdited:YES];
    }
    return NO;
}

/*
 * Delegate method for the document Text object. We use this method
 * to detect when the font is changed so we an write it out as the default.
 */
- textWillConvert:textObject fromFont:oldFont toFont:newFont
{
    if (newFont) {
        char str[80];
	sprintf (str, "%f", [newFont pointSize]);
	NXWriteDefault ([NXApp appName], "NXFontSize", str);
	NXWriteDefault ([NXApp appName], "NXFont", [newFont name]);
	[Text setDefaultFont:newFont];
    }

    return newFont;
}

// This fixes the problem of messed up text when you resize the
// document window.
- windowDidResize:sender
{
    id docText;
	docText = [document docView];
	[docText calcLine];
	[docText sizeToFit];
	return self;
}

/*
 * saveDocument: will write out the contents of the document
 * to the specified file. The best (perhaps not the cleanest but the
 * most efficient) way to dump a Text object to a file seems to be
 * to open the file (with open()), then to use NXOpenFile(), and
 * finally use writeText: to dump the contents out. 
 */
- (int)saveDocument:(const char *)fileName
{
    BOOL saveOK;
    int fd;     // File descriptor
    int retval = 0;
    NXStream *stream = NULL;

    if (saveOK = 
	  (((fd = open (fileName, O_WRONLY|O_CREAT|O_TRUNC, 0644)) != -1) &&
            (stream = NXOpenFile (fd, NX_WRITEONLY))))
	[[document docView] writeText:stream];
    if (stream) NXClose (stream);
    if (fd != -1) close (fd);

    if (saveOK) {
      	[self setName:fileName];
	[[document window] setDocEdited:NO];
    } else {
        if(NXRunAlertPanel(NULL, "Can't write file.", "OK", "Save As", NULL)
		== NX_ALERTALTERNATE)
		retval = -1;
    }
    return retval;
}

/*
 * Set/Get the name of the document. This is the same as the title.
 */
// There was a bug in the original Yap v19, where filename changes for
// no reason whan saved without executed (happens when multiple files open.
// The bug has been fixed.  The problem was that "name" and "documentName"
// are the identical pointers to the same memory for "Save".  And the orig
// code was freeing "name" before documentName was copied to a new location.

- setName:(const char *)documentName
{
  char *temp;

    documentName = documentName ? documentName : "";
    temp = NXCopyStringBufferFromZone (documentName, [self zone]);
    if (name) free(name);
    name = temp;
    [[document window] setTitleAsFilename:name];
    return self;
}

-(const char *)name
{
    return name;
}

/*
 * windowWillClose: gets called by windows who have this instance of 
 * YapDocument as delegate. The document simply marks itself and its zone
 * to be free and lets the window know it can close.  A more sophisticated
 * program might want to put up an alert if the document was edited but not
 * saved.
 */
- windowWillClose:sender action:(const char *)action
{
    if([[document window] isDocEdited])
    {
    	int dstatus = NXRunAlertPanel(action,
				"Save edited document: %s ?",
			        "Save", "Don't Save", "Cancel", name);
	if(dstatus == NX_ALERTDEFAULT)		/* save */
	    [self save:self];
	else if(dstatus == NX_ALERTOTHER)	/* cancel */
	    return nil;
    }
    [outputView setCurrentDocument: nil];
    [sender setDelegate:nil];
    [self free];
    return sender;  // We need to return a non-nil value --- here's one.

}

- windowWillClose:sender
{
    return [self windowWillClose:sender action:"Close"];
}


/*
- textWillChange:sender
{
	[[document window] setDocEdited:YES];
	return sender;
}
*/

- free
{
    NXZone *docZone = [self zone];
    if (name) free(name);
    [super free];
    [YapDocument reuseZone:docZone];
    return nil;
}
    
/*
 * save: saves the current document. If the document is untitled, it 
 * puts up a savePanel to get the user to enter a file name. saveAs:
 * saves the document under a new name by putting up a savePanel.
 */
- save:sender 
{
    const char *fileName = [self name];

    if ((fileName == NULL) ||
	(strcmp (fileName, UNTITLED) == 0) || (strcmp (fileName, "") == 0))
    {  /* fileName is not valid, get a new one */
      if ([[SavePanel new] runModalForDirectory:"." file:UNTITLED] == NO) 
	return self;
      else
	fileName = [[SavePanel new] filename];
    }

    if (fileName)
       if([self saveDocument:fileName])
          [self saveAs:self];	/* Couldn't save -- try another name */
  
    return self;
}
  
- saveAs:sender 
{
  again:
    if ([[SavePanel new] runModalForDirectory:"." file:[self name]])
    {
    	if([self saveDocument:[[SavePanel new] filename]])
	   goto again;
    }
    return self;
}

- execute:sender
{
  float llx, lly, urx, ury, bbm;
  int utime;
    [computeBBSwitch setState:[outputView isFigureBB]];
    [gridEnableSwitch setState:[outputView isMeshON]];
    NXPing();
    [[NXApp outputView] executeCodeFrom: self
    			andReturnBB: &llx : &lly : &urx : &ury
			usertime: &utime];
    bbm = (float)[NXApp bbMargin];
    if(utime >= 0)
    {
      llx -= bbm;
      lly -= bbm;
      urx += bbm;
      ury += bbm;
      sprintf(strbuf, "%%%%BoundingBox: %.0f %.0f %.0f %.0f", llx, lly, urx, ury);
    }
    else
      strcpy(strbuf,"");
    [bbTextField setStringValue: strbuf];
    return self;
}   



- document
{
     return document;
}


- setBBoxField:(float)llx :(float)lly :(float)urx :(float)ury
{
    sprintf(strbuf, "%%%%BoundingBox: %.0f %.0f %.0f %.0f", llx, lly, urx, ury);
    [bbTextField setStringValue: strbuf];
    return self;
}


/*
 * To get around the problem of printPSCode: going up the responder chain and
 * causing print panel to come back after Cancel, we use the following glue.
 */
- print:sender
{
    [[document docView] printPSCode:sender];
    return self;
}

/*
 * Insert %%BoundigBox comment in "bbTextField" into PSText
 */
- insertBB:sender
{
    id docText;
//    int selstart,selend;
    docText = [document docView];
//    [docText getSel: &selstart : &selend];
    strncpy(strbuf, [bbTextField stringValue], 80);
    strcat(strbuf, "\n");
    [[document window] setDocEdited:YES];
    [docText replaceSel: strbuf];

    return self;
}

/*
 * Insert %!PS-Adobe-2.0 EPSF-2.0 comment in "versionField" into PSText
 */
- insertVersion:sender
{
    id docText;
    docText = [document docView];
    [docText setSel:0 :0];
    strncpy(strbuf, [versionField stringValue], 40);
    strcat(strbuf,"\n");
    [[document window] setDocEdited:YES];
    [docText replaceSel: strbuf];
    return self;
}

/* Insert both of the above at the Top of the PSText */
- insertBoth:sender
{
    id docText;
    docText = [document docView];
    [docText setSel:0 :0];
    strncpy(strbuf, [versionField stringValue], 40);
    strcat(strbuf,"\n");
    strncat(strbuf, [bbTextField stringValue], 80);
    strcat(strbuf, "\n");
    [[document window] setDocEdited:YES];
    [docText replaceSel: strbuf];
    return self;
}


- setComputeBB:sender
{
    [outputView setFigureBB:[sender state]];
    return self;
}

- setMeshEnable:sender
{
    [outputView setMeshON:[sender state]];
    return self;
}

- setBlueEnable:sender
{
    [outputView setBlueON:[sender state]];
    return self;
}


@end
