// InspectorManager.m
// By Kevin Brain
// A class for managing an inspector.
// Compositing techniques and functions from the ToolInspector example
// by Sharon Biocca Zakhour, NeXT Developer Support Team
// You may freely copy, distribute and reuse the code in this example.  
// *Everybody involved* disclaims any warranty of any kind, expressed 
// or implied, as to its fitness for any particular use.

#import <objc/NXStringTable.h>
#import <appkit/Panel.h>
#import <appkit/View.h>
#import <appkit/PopUpList.h>
#import <appkit/Matrix.h>
#import <objc/Storage.h>
#import <objc/List.h>
#import <appkit/Application.h>	// for NXApp
#import <dpsclient/wraps.h>		// PScomposite
#import <libc.h>

#import "InspectorManager.h"
#define INTERVIEWSPACING 2

@implementation InspectorManager

/* Private functions */
static void 
compositeToBuffer(InspectorManager *self, float srcX, float srcY, float width, 
  float height, float dstX, float dstY)
{
    [[self->layouts contentView] lockFocus];
    PScomposite(srcX, srcY, width, height,
        [[self->inspectorPanel contentView] gState], dstX, dstY, NX_COPY);
    [[self->layouts contentView] unlockFocus];
}

static void 
compositeToScreen(InspectorManager *self, float srcX, float srcY, float width, 
  float height, float dstX, float dstY)
{
    [[self->inspectorPanel contentView] lockFocus];
    PScomposite(srcX, srcY, width, height, 
        [[self->layouts contentView] gState], dstX, dstY, NX_COPY);
    [self->inspectorPanel flushWindow];
    [[self->inspectorPanel contentView] unlockFocus];
}

/* instance methods */

- init
{
	[super init];
	if ([NXApp loadNibSection:"Inspector.nib" owner:self] == nil)
		NXRunAlertPanel(NULL,"Couldn't load Inspector.nib","OK",NULL,NULL);
    /* Tell the Inspector & Palette panels not to become the key window */
    [inspectorPanel setBecomeKeyOnlyIfNeeded: YES];   /* This will only */
 	   /* work if you don't miniaturize the panel. */
    [inspectorPanel setFloatingPanel:(BOOL)YES];
	useKeyEquivalents = YES;
	
	inspectorList = [[Storage alloc] initCount:0 elementSize:sizeof(struct inspectorListEntry) description:"@*"];
	groupList = [[Storage alloc] initCount:0 elementSize:sizeof(struct inspectorListEntry) description:"@*"];
	visibleInspectors = [[Storage alloc] initCount:0 elementSize:sizeof(unsigned int) description:"i"];
	[revertOKOut removeFromSuperview];
	[popupOut removeFromSuperview];
	[inspectorPanel display];			// to actually remove the controls
    /* 'layouts' is created in IB as the window containing all the */
    /* default inspectors side by side. */
    /* This window is non-deferred and always kept off-screen */
    [[[layouts contentView] allocateGState] lockFocus]; /* The lockFocus forces the */
    [[layouts contentView] unlockFocus];				/* gState to become defined */
    [[inspectorPanel contentView] allocateGState];
 
	[self addInspector: noInspectorBox 
		title:[inspectorStrings valueForStringKey: "NoSelectionInspector"]];
	[self addInspector: multiInspectorBox 
		title:[inspectorStrings valueForStringKey: "MultiSelectionInspector"]];
	[self addInspector: unapplicableInspectorBox 
		title:[inspectorStrings valueForStringKey: "UnapplicableInspector"]];
 
    /* Initialize the Inspector to the "Unapplicable Inspector" state */
	[self switchToInspector:(unsigned int)NOTAPPLICABLE];

    return self;
}

- (unsigned int)addInspector:(id)theView title:(const char *)theTitle
{
	return [self addInspector:theView title:theTitle atLocation:LOWERLEFTX :LOWERLEFTY];
}

- (unsigned int)addInspector:(id)theView title:(const char *)theTitle atLocation:(NXCoord)xLoc :(NXCoord)yLoc
{
	struct inspectorListEntry tempInspectorEntry;
	NXRect	theViewRect, cvRect;
	
	tempInspectorEntry.view = theView;
	tempInspectorEntry.showing = NO;
	tempInspectorEntry.title = malloc(sizeof(char)*(strlen((char *)theTitle)));
	strcpy(tempInspectorEntry.title,theTitle);

	[theView getFrame:&theViewRect];
	[[layouts contentView] getFrame:&cvRect];
	[[layouts contentView] addSubview:theView];
	if ((lastRect.origin.y+lastRect.size.height+ theViewRect.size.height+INTERVIEWSPACING) >= cvRect.size.height) {
		[layouts sizeWindow:(NXCoord)(cvRect.size.width + theViewRect.size.width + INTERVIEWSPACING)
			:(NXCoord)cvRect.size.height];
		lastRect.origin.x = cvRect.size.width;
		lastRect.origin.y = 0;
		}
	else {
		if ((lastRect.origin.x + theViewRect.size.width  + INTERVIEWSPACING) >= cvRect.size.width)
			[layouts sizeWindow:(NXCoord)(lastRect.origin.x + theViewRect.size.width + INTERVIEWSPACING)
				:(NXCoord)cvRect.size.height];
		lastRect.origin.y = lastRect.origin.y + lastRect.size.height + INTERVIEWSPACING;
		}
	lastRect.size = theViewRect.size;
	[theView moveTo:lastRect.origin.x :lastRect.origin.y];
	[theView display];
	[theView getFrame:&tempInspectorEntry.offscreenRect];
			
	[inspectorList addElement:&tempInspectorEntry];
	
	[theView moveTo:xLoc :yLoc];
	[theView removeFromSuperview];
	return [inspectorList count]-1;
}

- (unsigned int)addGroup:(const char *)theTitle
{
	struct inspectorListEntry tempGroupEntry;
	unsigned short temp;
	
	// add item to PopUpList
	if (([groupList count] < 9) && (useKeyEquivalents == YES))	// command-key equivalents for first 9
		temp = (unsigned short)[groupList count]+(unsigned short)'1';
	else
		temp = 0;	// no command-key equivalent for rest
	tempGroupEntry.view = [[popupOut target] addItem:theTitle 
						action:@selector(selectGroup:)
						keyEquivalent:temp];
	[tempGroupEntry.view  setTarget:self];
	if ([groupList count] == 0) {
		// remove the default 'None' item in PopUpList
		[[popupOut target] removeItemAt:(unsigned int)0];
		[popupOut setTitle:theTitle];
		}
	if ([groupList count] == 1)
		[self showGroupPopUp];
	tempGroupEntry.title = malloc(sizeof(char)*(strlen((char *)theTitle)));
	strcpy(tempGroupEntry.title,theTitle);
	[groupList addElement:&tempGroupEntry];
	return [groupList count]-1;
}

- setUseKeyEquivalents:(BOOL)use
{
	useKeyEquivalents = use;
	return self;
}

- switchToInspector:(unsigned int)newInspector
{
	struct inspectorListEntry *new,*tempOld;
	View *tempView;
	NXRect tempFrame;
	int i,*tempShowing;
	unsigned cnt = [visibleInspectors count];
	
	new = [inspectorList elementAt:newInspector];
	if (!new) return self;	// not a valid inspector
	if (new->showing == YES) return self;		// if already showing
	/* remove all other inspectors */
	if (cnt)			
		for (i=(int)cnt; i > 0;i--) {
			tempShowing = [visibleInspectors elementAt:i-1];
			tempOld = [inspectorList elementAt:(unsigned int)*tempShowing];
			tempOld->showing = NO;
			tempView = tempOld->view;
			if (tempView != NULL) {
				[tempView getFrame:&tempFrame];
				compositeToBuffer(self, tempFrame.origin.x, tempFrame.origin.y, NX_WIDTH(&tempFrame), 
					NX_HEIGHT(&tempFrame), tempOld->offscreenRect.origin.x, tempOld->offscreenRect.origin.y);
				[tempView removeFromSuperview];
				}
			}
	[visibleInspectors empty];

	[[inspectorPanel contentView] addSubview:new->view];
    /* Now composite the new tool view from the offscreen window into the */
    /* toolInspector */
	[new->view getFrame:&tempFrame];
    compositeToScreen(self, new->offscreenRect.origin.x, new->offscreenRect.origin.y, 
		NX_WIDTH(&tempFrame), NX_HEIGHT(&tempFrame), tempFrame.origin.x, tempFrame.origin.y);
	
	[inspectorPanel setTitle:new->title];
	new->showing = YES;
	i=(int)newInspector;
	[visibleInspectors addElement:&i];
    
    return self;
}

- showInspector:(unsigned int)newInspector
{
	struct inspectorListEntry *new;
	NXRect tempFrame;
	int tempNum;
	
	new = [inspectorList elementAt:newInspector];
	if (!new) return self;	// not a valid inspector

	if ([self showing:newInspector] == YES) return self;		// if already showing

	[inspectorPanel disableFlushWindow];
	[[inspectorPanel contentView] addSubview:new->view];
    /* Now composite the new tool view from the offscreen window into the */
    /* toolInspector */
	[new->view getFrame:&tempFrame];
    compositeToScreen(self, new->offscreenRect.origin.x, new->offscreenRect.origin.y, 
		NX_WIDTH(&tempFrame), NX_HEIGHT(&tempFrame), tempFrame.origin.x, tempFrame.origin.y);
	
	[inspectorPanel setTitle:new->title];
	[inspectorPanel reenableFlushWindow];
	[inspectorPanel flushWindowIfNeeded];
	new->showing = YES;
	tempNum = (int)newInspector;
	[visibleInspectors addElement:&tempNum];
    
    return self;
}

- hideInspector:(unsigned int)inspectorNum
{
	struct inspectorListEntry *new;
	View *tempView;
	NXRect tempFrame;
	int i,cnt = [visibleInspectors count],*tempNum;
	
	new = [inspectorList elementAt:inspectorNum];
	if (!new) return self;	// not a valid inspector
	if(cnt)			// find inspectorNum is visibleInspectors list
		for (i=cnt; i > 0;i--) {
			tempNum = [visibleInspectors elementAt:i-1];
			if (inspectorNum == (unsigned)*tempNum) {
				[visibleInspectors removeAt:(unsigned)i-1];
				new->showing = NO;
				tempView = new->view;
				if (tempView != NULL) {
					[tempView getFrame:&tempFrame];
					compositeToBuffer(self, tempFrame.origin.x, tempFrame.origin.y, NX_WIDTH(&tempFrame), 
						NX_HEIGHT(&tempFrame), new->offscreenRect.origin.x, new->offscreenRect.origin.y);
					[tempView removeFromSuperview];
					}
				}
			}
		
    return self;
}

- (BOOL)showing:(unsigned int)inspectorNum
{
	struct inspectorListEntry *new;
	
	if (inspectorNum >= [inspectorList count]) return NO;	// not a valid inspector
	else new = [inspectorList elementAt:(unsigned)inspectorNum];

	if (new->showing == YES) return YES;
	else return NO;
}

- (int)group
{
	// check if a group is selected (none is initially)
	if ([[popupOut target] selectedItem] == NULL)
		return 0;	// if no group selected, return default group (first group added)
	else
		return [[popupOut target] indexOfItem:[[popupOut target] selectedItem]];
}

- panel
{
	return inspectorPanel;
}

- popUpListButton
{
	return popupOut;
}

- revertOKMatrix
{
	return revertOKOut;
}

- setDelegate:(id)anObject
{
	delegate = anObject;
    return self;
}

- delegate
{
	return delegate;
}

- selectGroup:sender
// target of inspector panel group selector PopUpList
{
	[popupOut setTitle:[[sender selectedCell] title]];
	[inspectorPanel orderFront:self];
	if ([delegate respondsTo:@selector(groupChanged:to:)]) 
    {
        [delegate groupChanged:self to:[sender selectedRow]];
    }
	return self;
}

- revertPressed:sender
// target of inspector panel Revert button
{
	if ([delegate respondsTo:@selector(inspectRevert)]) 
    {
        [delegate inspectRevert:self];
    }
	return self;
}

- okPressed:sender
// target of inspector panel OK button
{
	if ([delegate respondsTo:@selector(inspectOK)]) 
    {
        [delegate inspectOK:self];
    }
	return self;
}

- showRevertOK
{
	[[inspectorPanel contentView] addSubview:revertOKOut];
	[revertOKOut display];
    return self;
}

- hideRevertOK
{
	[revertOKOut removeFromSuperview];
	// uncomment the following line to have the Revert/OK buttons
	// automatically removed by displaying the contentView 
	//	[[inspectorPanel contentView] display];
    return self;
}

- showGroupPopUp
{
	[[inspectorPanel contentView] addSubview:popupOut];
	[popupOut display];
    return self;
}

- hideGroupPopUp
{
	[popupOut removeFromSuperview];
	[[inspectorPanel contentView] display];
    return self;
}
@end

