/*
 *  YapApp.m
 *  Author: Ali Ozer
 *  Created: Mar 89 for 0.9
 *  Modified: Jul & Aug 89 for 1.0
 *  Modified: Aug 90 for 2.0.
 *  For BBFig to deo BoundingBox computation....
 *  Modified: Jan 92 for BBFig by Izumi Ohzawa (izumi@pinoko.berkeley.edu)
 *  Added Preferences: 92-05-14 Izumi Ohzawa.
 *
 *  YapApp is the application class used in Yap. It implements the
 *  central functionality of coordinating the output and document
 *  windows, opening documents, etc...
 *
 *  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 <appkit/appkit.h>
#import "YapApp.h"
#import "YapDocument.h"
#import "YapOutput.h"

#import <stdio.h>
#import <objc/List.h>
#import <sys/param.h>

@implementation YapApp

- appDidInit:sender
{
float r, g, b;
const char *sptr;
	static NXDefaultsVector BBFigDefaults = {
		{"BBoxMargin", "2"},
		{"RubberBandColor", "1 0 0"},
		{NULL}
	};

	NXRegisterDefaults([NXApp appName], BBFigDefaults);
	bbmargin = atoi(NXGetDefaultValue([NXApp appName], "BBoxMargin"));
	if(bbmargin < 0) bbmargin = 0;
	if(bbmargin >20) bbmargin = 20;
	sptr = NXGetDefaultValue([NXApp appName], "RubberBandColor");
	sscanf(sptr,"%f %f %f", &r, &g, &b);
	rbColor = NXConvertRGBToColor(r, g, b);	// Store in globals without alpha
        [outputView setRubberBandColor:rbColor];
	return self;
}

- outputView   
{
    return outputView;
}

- outputWindow
{
    return [outputView window];
}

#define DEFAULTWIDTH 612
#define DEFAULTHEIGHT 792
#define MINSIZE 72
#define MAXSIZE 3600

/*
 * Here we have a handle to the output window, created in IB. We create
 * the scroll and the yap output views and add them to this window.
 */
- setOutputWindow:anObject 
{
    NXRect initFrame = {{0.0, 0.0}, {DEFAULTWIDTH, DEFAULTHEIGHT}};

    id scrollView = [ScrollView new];

    outputView = [[YapOutput allocFromZone:[self zone]] initFrame:&initFrame];

    [anObject setBackgroundGray:NX_WHITE];
    [anObject removeFromEventMask:NX_KEYDOWNMASK|NX_KEYUPMASK];
    
    [scrollView setBorderType:NX_NOBORDER];
    [scrollView setHorizScrollerRequired:YES];
    [scrollView setVertScrollerRequired:YES];
 
    [anObject setContentView:scrollView];
    [scrollView setDocView:outputView];

    [anObject setDelegate:self]; // So we can get windowWillResize:toSize:
    [anObject display];

    [NXApp updateOutputWindowSize];

    return self;
}

/*
 * windowWillResize:toSize: is a delegate method that gets called
 * when a window is being resized. In our case, we want to limit the user
 * from growing the output window to a size that is greater than that of the
 * view.
 */
- windowWillResize:sender toSize:(NXSize *)sz
{
    if (sz->width > maxWindowSize.width) sz->width = maxWindowSize.width;
    if (sz->height > maxWindowSize.height) sz->height = maxWindowSize.height;

    return self;
}   

/*
 * updateOutputWindowSize should be called after the size of the output view is
 * changed. It simply makes sure the window isn't too big for the view. If the
 * window is indeed to big, it is resized smaller.
 */
- updateOutputWindowSize
{
    NXRect frame, content;
   
    // The next few lines allow us to get the window size for the window
    // containing a ScrollView and the yap output view.

    [[self outputView] getFrame:&frame];
    [ScrollView getFrameSize:&maxWindowSize forContentSize:&(frame.size)
	        horizScroller:YES vertScroller:YES borderType:NX_NOBORDER];

    // sizeWindow:: wants window size in content area; so we can use the
    // maxWindowSize from above. But to compare it to the window frame,
    // we first need to get the content area for the current frame.

    [[self outputWindow] getFrame:&frame];      
    [Window getContentRect:&content forFrameRect:&frame 
	    style:[[self outputWindow] style]];

    if (content.size.width > maxWindowSize.width ||
	content.size.height > maxWindowSize.height) { 
	[[self outputWindow] 
		sizeWindow:MIN(maxWindowSize.width, content.size.width)
			  :MIN(maxWindowSize.height, content.size.height)];
    }

    // Now we go from the content size to the window frame size, which is 
    // what we will use in windowWillResize:toSize:

    content.size = maxWindowSize;
    [Window getFrameRect:&frame forContentRect:&content
	    style:[[self outputWindow] style]];

    maxWindowSize = frame.size;

    return self;
}

/*
 * newDocument simply creates a new Yap document and displays it.
 */
- newDocument:sender
{
    [YapDocument new];

    return self;
}

/*
 * openDocument gets a file name from the user, creates a new document window,
 * and loads the specified file into it.
 */
- openDocument:sender
{
    // Allow ps, eps, and any other extension not handled by other apps.
    // Note that "" should come first in the list.
    static const char *const yapTypes[] = {"", "ps", "eps", NULL};  

    if ([[OpenPanel new] runModalForTypes:yapTypes]) {
	if ([YapDocument newFromFile:[[OpenPanel new] filename]] == nil) {
	    NXRunAlertPanel (NULL, "Could not open file.", "OK", NULL, NULL);
	}
    }

    return self;
}

/*
 * appOpenFile:type: is invoked by Workspace when the user double-clicks
 * on a file Yap is prepared to accept. By default, Yap is not prepared to open
 * any files, however, it can easily be made to open files of certain type
 * through the IB project inspector.
 */
- (int)appOpenFile:(const char *)path type:(const char *)type
{
    if ([YapDocument newFromFile:path] == nil) return NO;
    else return YES;
}

/*
 * The following method indicates that Yap is ready to open multiple
 * files at one time.
 */
- (BOOL)appAcceptsAnotherFile:sender
{
    return YES;
}

/*
 * Methods to load .nib files for the various panels.
 */
- showInfo:sender
{
    if (!infoPanel) {
	[self loadNibSection:"Info.nib" owner:self withNames:NO];
    }
    [infoPanel makeKeyAndOrderFront:sender];
    return self;
}

- showHelp:sender
{
    if (!helpPanel) {
	[self loadNibSection:"Help.nib" owner:self withNames:NO];
    }
    [helpPanel makeKeyAndOrderFront:sender];
    return self;
}

- showPrefs:sender
{
    if (!prefsPanel) {
	[self loadNibSection:"Prefs.nib" owner:self withNames:NO];
	[self updatePreferencesPanel:sender];
    }
    [prefsPanel makeKeyAndOrderFront:sender];
    return self;
}


- bbmarginSliderAction:sender
{
    /* just reflect the slider value into field */
    [marginBBoxField setIntValue:[sender intValue]];
    return self;
}

- (int)bbMargin
{
    return bbmargin;
}

/*
 * updatePreferencesPanel: is used to copy the existing situation into
 * the Prefences panel.
 */
- updatePreferencesPanel:sender
{
    NXRect outputFrame;

    [[self outputView] getFrame:&outputFrame];
    [outputWidthField setFloatValue:NX_WIDTH(&outputFrame)];
    [outputHeightField setFloatValue:NX_HEIGHT(&outputFrame)];
    [showCacheButton setState:[[self outputView] isCacheShown]];
    [clearCacheButton setState:[[self outputView] isCacheCleared]];

    [showGridButton setState:[[self outputView] isMeshON]];
    [computeBBoxButton setState:[[self outputView] isFigureBB]];
    [marginBBoxSlider setIntValue:bbmargin];
    [marginBBoxField setIntValue:bbmargin];

    [outputWidthField selectText:sender];
    [rbColorWell setColor:rbColor];

    return self;
}

/*
 * okPreferencesPanel: causes the values in Preferences to be read into the
 * application and applied to the various objects.
 */
- okPreferencesPanel:sender
{
float red, green, blue, alpha;
char strbuf[80];
NXCoord desiredWidth, desiredHeight;

    desiredWidth = [outputWidthField floatValue];
    if (desiredWidth < MINSIZE || desiredWidth > MAXSIZE) {
	desiredWidth = MIN(MAX(desiredWidth, MINSIZE), MAXSIZE);
	[outputWidthField setFloatValue:desiredWidth];
    }
    desiredHeight = [outputHeightField floatValue];
    if (desiredHeight < MINSIZE || desiredHeight > MAXSIZE) {
	desiredHeight = MIN(MAX(desiredHeight, MINSIZE), MAXSIZE);
	[outputHeightField setFloatValue:desiredHeight];
    }

    [[self outputView] sizeTo:desiredWidth :desiredHeight];
    [self updateOutputWindowSize];

    [[self outputView] setCacheShown:[showCacheButton state]];
    [[self outputView] setCacheCleared:[clearCacheButton state]];
    [[self outputView] setMeshON:[showGridButton state]];
    [[self outputView] setFigureBB:[computeBBoxButton state]];
    bbmargin = [marginBBoxSlider intValue];
    [marginBBoxField setIntValue:bbmargin];
    sprintf(strbuf, "%d", bbmargin);
    NXWriteDefault ([NXApp appName], "BBoxMargin", strbuf);

    rbColor = [rbColorWell color];
    // get RGB components for storing in defaults
    NXConvertColorToRGBA(rbColor, &red, &green, &blue, &alpha);
    sprintf(strbuf, "%7.5f %7.5f %7.5f", red, green, blue);
    NXWriteDefault([NXApp appName], "RubberBandColor", strbuf);
    [outputView setRubberBandColor:rbColor];

    [[sender window] orderOut:sender];
    [outputWidthField selectText:sender];

    return self;
}


/* This method code taken from Draw.app source */
- terminate:sender
/*
 * Overridden to be sure all documents get an opportunity to be saved
 * before exiting the program.
 */
{
    int count, choice;
    id window, document;

    count = [windowList count];		/* windowList is in NXApp which this one is */
    while (count--) {
	window = [windowList objectAt:count];
 	document = [window delegate];
	if ([window isDocEdited]) {
	    choice = NXRunAlertPanel("Quit", "You have unsaved documents.",
	    			     "Review Unsaved", "Quit Anyway", "Cancel");
	    if (choice == NX_ALERTOTHER)  {
		return self;
	    } else if (choice == NX_ALERTDEFAULT) {
		count = [windowList count];
		while (count--) {
		    window = [windowList objectAt:count];
		    document = [window delegate];
		    if ([document respondsTo:@selector(windowWillClose:action:)]) {
			if ([document windowWillClose:window action:"Quit"]) {
			    [window close];
			} else {
			    return self;
			}
		    }
		}
	    }
	    break;
	}
    }

    [super terminate:sender];

    return nil;
}
    

@end

