// InspectorManager.m
// By Kevin Brain (ksbrain@zeus.UWaterloo.ca)

// A class that manages the displaying of elements of a list of view 
// objects that are periodically displayed and removed from a window.

// 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"

@implementation InspectorManager

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

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

/* instance methods */
- init
{
	return [self initFromNib: "Inspector.nib"];
}

- initFromNib:(const char *) nibFile
{
	[super init];
	if ([NXApp loadNibSection: nibFile owner:self] == nil)
		NXRunAlertPanel(NULL,"Couldn't load %s","OK",NULL,NULL, nibFile);
	useKeyEquivalents = YES;

	inspectorList = [[Storage alloc] initCount:0 elementSize:sizeof(struct inspectorListEntry) 
			description:@encode(struct inspectorListEntry)];
	groupList = [[Storage alloc] initCount:0 elementSize:sizeof(char *) description:"*"];
	visibleInspectors = [[Storage alloc] initCount:0 elementSize:sizeof(unsigned int) description:"i"];
	[revertOKOut removeFromSuperview];
	[popupOut removeFromSuperview];
	[inspectorPanel display];			// to actually remove the controls
    [[inspectorPanel contentView] allocateGState];
 
    /* 'messagePanel' is created in IB as the window containing the message inspector*/
    /* This window is non-deferred and always kept off-screen */
	[self addInspector: messageBox 
		title:[inspectorStrings valueForStringKey: "MessageInspector"] 
		atLocation:LOWERLEFTX :LOWERLEFTY cached:NO cacheWindow:messagePanel];
 
    /* Initialize the Inspector to display "Unapplicable Inspector" message */
	[self showMessage:(const char *)"Not\nApplicable"];

    return self;
}

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

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

	if (isCached) {
		if (theCacheWindow == nil) {
			[theView getFrame:&theViewRect];
			theViewRect.origin.x = [inspectorList count];
			tempInspectorEntry.cacheWindow = [[Window alloc] initContent:(const NXRect *)&theViewRect
				style:(int)NX_PLAINSTYLE
				backing:(int)NX_RETAINED
				buttonMask:(int)0
				defer:(BOOL)NO];
			}
		else
			tempInspectorEntry.cacheWindow = theCacheWindow;
		 /* The lockFocus forces the gState to become defined */
		[[[tempInspectorEntry.cacheWindow contentView] allocateGState] lockFocus];
		[[tempInspectorEntry.cacheWindow contentView] addSubview:theView];
		if (theCacheWindow == nil) {
			[theView moveTo:0 :0];	// move view to origin of offscreen buffer content view
			[tempInspectorEntry.cacheWindow display];
			}
		[theView getFrame:&tempInspectorEntry.offscreenRect];
//		[theView display];		// draw in offscreen buffer
		[[tempInspectorEntry.cacheWindow contentView] unlockFocus];
		}
	[inspectorList addElement:&tempInspectorEntry];
	
	[theView moveTo:xLoc :yLoc];
	[theView removeFromSuperview];
	return [inspectorList count]-1;
}

- (unsigned int)addGroup:(const char *)theTitle
{
	char *tempTitle;
	unsigned short temp;
	id	tempView;
	
	// add item to PopUpList
	// command-key equivalents for first 9
	if (([groupList count] < 9) && (useKeyEquivalents == YES))
		temp = (unsigned short)[groupList count]+(unsigned short)'1';
	else
		temp = 0;	// no command-key equivalent for rest
	tempView = [[popupOut target] addItem:theTitle 
						action:@selector(selectGroup:)
						keyEquivalent:temp];
	[tempView 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];
	tempTitle = NXCopyStringBuffer((const char *)theTitle);
	[groupList addElement:&tempTitle];
	return [groupList count]-1;
}

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

- switchToInspector:(unsigned int)newInspectorNum
{
	struct inspectorListEntry *new,*tempOld;
	View *tempView;
	NXRect tempFrame;
	int i,*tempShowing;
	unsigned cnt = [visibleInspectors count];
	
	new = [inspectorList elementAt:newInspectorNum];
	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];
				if (tempOld->cached)
					compositeToBuffer(self, tempOld->cacheWindow, 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];
	if (new->cached) {
		// Now composite the new inspector view from the offscreen window
		// into the inspector panel
		[new->view getFrame:&tempFrame];
		compositeToScreen(self, new->cacheWindow, new->offscreenRect.origin.x, new->offscreenRect.origin.y, 
			NX_WIDTH(&tempFrame), NX_HEIGHT(&tempFrame), tempFrame.origin.x, tempFrame.origin.y);
		}
	else
		[new->view display];
	
	[inspectorPanel setTitle:new->title];
	new->showing = YES;
	i=(int)newInspectorNum;
	[visibleInspectors addElement:&i];
    
    return self;
}

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

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

	[inspectorPanel disableFlushWindow];
	[[inspectorPanel contentView] addSubview:new->view];
	if (new->cached) {
		// Now composite the new inspector view from the offscreen window
		// into the inspector panel
		[new->view getFrame:&tempFrame];
		compositeToScreen(self, new->cacheWindow, new->offscreenRect.origin.x, new->offscreenRect.origin.y, 
			NX_WIDTH(&tempFrame), NX_HEIGHT(&tempFrame), tempFrame.origin.x, tempFrame.origin.y);
		}
	else
		[new->view display]; 
	
	[inspectorPanel setTitle:new->title];
	[inspectorPanel reenableFlushWindow];
	[inspectorPanel flushWindowIfNeeded];
	new->showing = YES;
	tempNum = (int)inspectorNum;
	[visibleInspectors addElement:&tempNum];
    
    return self;
}

- hideInspector:(unsigned int)inspectorNum
{
	struct inspectorListEntry *inspector;
	View *tempView;
	NXRect tempFrame;
	int i,cnt = [visibleInspectors count],*tempNum;
	
	inspector = [inspectorList elementAt:inspectorNum];
	if (!inspector) 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];
				inspector->showing = NO;
				tempView = inspector->view;
				if (tempView != NULL) {
					[tempView getFrame:&tempFrame];
					if (inspector->cached)
						compositeToBuffer(self, inspector->cacheWindow, 
								tempFrame.origin.x, tempFrame.origin.y,
								NX_WIDTH(&tempFrame),NX_HEIGHT(&tempFrame),
								inspector->offscreenRect.origin.x, inspector->offscreenRect.origin.y);
					[tempView removeFromSuperview];
					}
				}
			}
		
    return self;
}

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

	if (inspector->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]];
}

- showMessage:(const char *)theMessage
{
	[inspectorPanel disableFlushWindow];
	[self switchToInspector:MESSAGE];
	[messageTextField setStringValue:theMessage];
	[messageTextField display];
	[[inspectorPanel reenableFlushWindow] flushWindowIfNeeded];
	return self;
}

- messageTextField {return messageTextField; }

- 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 
	// (otherwise the image of the buttons will still be visible)
	//	[[inspectorPanel contentView] display];
    return self;
}

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

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

