//=============================================================================
//
//		Copyright (C) 1996 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableConnInspector.M
//
//		A custom Interface Builder connection inspector so that the
//		doubleTarget and doubleAction can be set.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableConnInspector.M,v 1.6 96/08/30 15:09:37 sunshine Exp $
// $Log:		MiscTableConnInspector.M,v $
//	Revision 1.6  96/08/30	15:09:37  sunshine
//	Added missing (void) to -initScroll:numCols:.
//	
//	Revision 1.5  96/05/05	11:11:34  sunshine
//	Fixed bug: Was leaking connector object when breaking connections.
//	Fixed bug: Was allowing non-target/action connection to FirstResponder.
//	Fixed bug: Was not treating FirstResponder as a special case (and setting
//	"destination" to 0 when appropriate so archiving would crash with
//	"IBFirstResponder does not respond to -write:".	 Now handles FirstResponder
//	specially.
//	
//	Revision 1.4  96/05/02	18:10:57  zarnuk
//	No longer preselects an action if there is no pre-existing connection.
//	Cleaned up the init stuff a little bit.
//-----------------------------------------------------------------------------
#if defined(NX_COMPILER_RELEASE_3_3)	// 3.3+ only.  Not available for 3.2-
#import "MiscTableConnInspector.h"
#import "MiscTableConnector.h"
#import <misckit/MiscTableScroll.h>

extern "Objective-C" {
#import <objc/objc-runtime.h>
#import <foundation/NSArray.h>
#import <foundation/NSBundle.h>
#import <foundation/NSDictionary.h>
#import <foundation/NSString.h>
}

enum MiscTSOutletSlot
		{
		MISC_TS_NAME_OS,
		MISC_TS_CONN_OS,
		MISC_TS_PTR_OS
		};

enum MiscTSActionSlot
		{
		MISC_TS_NAME_AS,
		MISC_TS_CONN_AS
		};


static char const ICON_DIMPLE[] = "MiscTableDimple";
static char const ICON_ARROW [] = "MiscTableRightArrow";
static char const ICON_BLANK [] = "MiscBlankImage";

static NXImage* IMAGE_DIMPLE;
static NXImage* IMAGE_ARROW;
static NXImage* IMAGE_BLANK;

static NSString*		OUTLET_ACTION_FILE = @"MiscTableConnector";
static NSDictionary*	OUTLET_ACTION_DICT = 0;

static id				FIRST_RESP = (id)~0;
static NSString*		FIRST_RESP_NAME = @"FirstResponder";


//=============================================================================
// PRIVATE IB METHODS
//=============================================================================
@interface Object(MISC_TS_IB)
- getIBClassName;		// (NSString)
- getIBPrintName;		// (NSString)
@end

@interface IBDocument: Object
- getDataFor:obj;
- objectData;			// (IBClassData)
- classData;			// (IBClassData)
- controlConnectorForSource:obj;
- connectorForSource:obj outlet:name;
@end

@interface IBClassData: Object
- getOutletsOf:obj;
- getActionsOf:obj;
- getOutletsOf:obj deep:(BOOL)deep;
- getActionsOf:obj deep:(BOOL)deep;
@end


//=============================================================================
// CATEGORIES OF PRIVATE IB CONNECTION CLASSES
//=============================================================================

@interface IBConnector(MiscTableScroll)
- (NSString*) outletName;
- (NSString*) actionName;
- (NSString*) actionValue;
@end

@interface IBControlConnector(MiscTableScroll)
- (NSString*) outletName;
- (NSString*) actionName;
- (NSString*) actionValue;
@end

@implementation IBConnector(MiscTableConnector)
- (NSString*) outletName		{ return [self label]; }
- (NSString*) actionName		{ return 0; }
- (NSString*) actionValue		{ return 0; }
@end

@implementation IBControlConnector(MiscTableConnector)
- (NSString*) outletName		{ NSString* s = @"target"; return s; }
- (NSString*) actionName		{ NSString* s = @"action"; return s; }
- (NSString*) actionValue		{ return [self label]; }
@end


//=============================================================================
// TABLE SCROLL CATEGORY
//=============================================================================
@interface MiscTableScroll(IBconnect)
- (char const*) getConnectInspectorClassName;
@end

@implementation MiscTableScroll(IBconnect)
- (char const*) getConnectInspectorClassName
		{ return "MiscTableConnInspector"; }
@end


//=============================================================================
// TABLE CONNECTOR CATEGORY
//=============================================================================
@interface MiscTableConnector(IB)
- (NSString*) outletName;
- (NSString*) actionName;
- initSource:src destination:dst label:lbl outlet:out action:act;
- setLabel:lbl;
- write:(NXTypedStream*)stream;
- getIBPrintName;
@end

@implementation MiscTableConnector(IB)

- (NSString*) outletName { return [NSString stringWithCString:outletName]; }
- (NSString*) actionName { return [NSString stringWithCString:actionName]; }

- initSource:src destination:dst label:lbl outlet:out action:act
	{
	[super initSource:src destination:dst label:lbl];
	outletName = 0;
	actionName = 0;
	selectorName = 0;
	[self clone:[out cString] into:&outletName];
	[self clone:[act cString] into:&actionName];
	[self clone:[lbl cString] into:&selectorName];
	return self;
	}

- setLabel:lbl
	{
	[self clone:[lbl cString] into:&selectorName];
	return [super setLabel:lbl];
	}

- write:(NXTypedStream*)stream
	{
	[super write:stream];
	NXWriteType( stream, @encode(char*), &outletName );
	NXWriteType( stream, @encode(char*), &actionName );
	NXWriteType( stream, @encode(char*), &selectorName );
	return self;
	}

- getIBPrintName
	{
	return [NSString stringWithFormat:
				@"%@/%@", [self outletName], [self actionValue] ];
	}

@end



//=============================================================================
// TABLE CONNECTOR INSPECTOR IMPLEMENTATION
//=============================================================================
@implementation MiscTableConnInspector

//-----------------------------------------------------------------------------
// +initialize
//-----------------------------------------------------------------------------
+ initialize
	{
	char path[ MAXPATHLEN + 1 ];
	NXBundle* bundle = [NXBundle bundleForClass:self];

	[bundle getPath:path forResource:ICON_DIMPLE ofType:"tiff"];
	IMAGE_DIMPLE = [[NXImage alloc] initFromFile:path];
	[IMAGE_DIMPLE setName:ICON_DIMPLE];

	[bundle getPath:path forResource:ICON_ARROW ofType:"tiff"];
	IMAGE_ARROW = [[NXImage alloc] initFromFile:path];
	[IMAGE_ARROW setName:ICON_ARROW];

	IMAGE_BLANK = [[NXImage alloc] init];
	[IMAGE_BLANK setName:ICON_BLANK];

	NSBundle* nsbundle = [NSBundle bundleForClass:self];
	NSString* s = [nsbundle pathForResource:OUTLET_ACTION_FILE
						ofType:@".strings"];
	OUTLET_ACTION_DICT = [[NSDictionary alloc] initWithContentsOfFile:s];

	return self;
	}


//-----------------------------------------------------------------------------
// +actionForOutlet:
//-----------------------------------------------------------------------------
+ (NSString*) actionForOutlet:(NSString*)name
	{
	return [OUTLET_ACTION_DICT objectForKey:name];
	}


//-----------------------------------------------------------------------------
// -actionForOutlet:
//-----------------------------------------------------------------------------
- (NSString*) actionForOutlet:(NSString*)name
	{
	return [OUTLET_ACTION_DICT objectForKey:name];
	}


//-----------------------------------------------------------------------------
// -free
//-----------------------------------------------------------------------------
- free
	{
	[connList free];
	[curout release];
	return [super free];
	}


//-----------------------------------------------------------------------------
// -initScroll:numCols:
//-----------------------------------------------------------------------------
- (void) initScroll: (MiscTableScroll*) scroll numCols:(int)numCols
	{
	[scroll setAutoSortRows:YES];
	[scroll setSelectionMode:MISC_RADIO_MODE];
	[scroll setDelegate:self];
	[scroll setTarget:self];
	[scroll setDoubleTarget:self];
	for (int i = 0; i < numCols; i++)
		[scroll addCol];
	}


//-----------------------------------------------------------------------------
// -init
//-----------------------------------------------------------------------------
- init
	{
	[super init];

	connList = [[List alloc] init];

	char buf[ MAXPATHLEN + 1 ];
	NXBundle* bundle = [NXBundle bundleForClass: [self class]];
	[bundle getPath:buf forResource:[[self class] name] ofType:"nib"];
	[NXApp loadNibFile:buf owner:self withNames:NO fromZone:[self zone]];

	[self initScroll:outletScroll numCols:3];
	[outletScroll setAction:@selector(outletClick:)];
	[outletScroll setDoubleAction:@selector(outletDblClick:)];
	[outletScroll setCol:MISC_TS_NAME_OS size:83];
	[outletScroll setCol:MISC_TS_NAME_OS title:"Outlets"];
	[outletScroll setCol:MISC_TS_CONN_OS size:13];
	[outletScroll setCol:MISC_TS_CONN_OS sizeable:NO];
	[outletScroll setCol:MISC_TS_CONN_OS cellType:MISC_TABLE_CELL_ICON];
	[outletScroll setCol:MISC_TS_CONN_OS sortType:MISC_SORT_TAG];
	[outletScroll setCol:MISC_TS_PTR_OS size:13];
	[outletScroll setCol:MISC_TS_PTR_OS sizeable:NO];
	[outletScroll setCol:MISC_TS_PTR_OS cellType:MISC_TABLE_CELL_ICON];
	[outletScroll setCol:MISC_TS_PTR_OS sortType:MISC_SORT_TAG];
	[outletScroll setCol:MISC_TS_PTR_OS sortDirection:MISC_SORT_DESCENDING];

	[self initScroll:actionScroll numCols:2];
	[actionScroll setAction:@selector(actionClick:)];
	[actionScroll setDoubleAction:@selector(actionDblClick:)];
	[actionScroll setCol:MISC_TS_NAME_AS size:96];
	[actionScroll setCol:MISC_TS_NAME_AS title:"Actions"];
	[actionScroll setCol:MISC_TS_CONN_AS size:13];
	[actionScroll setCol:MISC_TS_CONN_AS sizeable:NO];
	[actionScroll setCol:MISC_TS_CONN_AS cellType:MISC_TABLE_CELL_ICON];
	[actionScroll setCol:MISC_TS_CONN_AS sortType:MISC_SORT_TAG];
	[actionScroll setCol:MISC_TS_CONN_AS sortDirection:MISC_SORT_DESCENDING];

	[outletScroll setNextText:actionScroll];
	[actionScroll setNextText:outletScroll];

	return self;
	}


//-----------------------------------------------------------------------------
// -getConnList
//-----------------------------------------------------------------------------
- (void) getConnList
	{
	[[NXApp activeDocument] listConnectors:connList forSource:object];
	}


//-----------------------------------------------------------------------------
// -findConnectionForOutlet:
//		Note: this method is only accurate when connList is valid.
//-----------------------------------------------------------------------------
- findConnectionForOutlet:(NSString*) outletName
	{
	unsigned int const lim = [connList count];
	for (unsigned int i = 0; i < lim; i++)
		{
		id conn = [connList objectAt:i];
		if ([outletName isEqualToString:[conn outletName]])
			return conn;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// -connectionForOutlet:
//-----------------------------------------------------------------------------
- connectionForOutlet:(NSString*) outletName
	{
	[self getConnList];
	return [self findConnectionForOutlet:outletName];
	}


//-----------------------------------------------------------------------------
// virtualDestFor:
//-----------------------------------------------------------------------------
- virtualDestFor:dest
	{
	if (dest == 0 || [[dest getIBClassName] isEqualToString:FIRST_RESP_NAME])
		return FIRST_RESP;
	return dest;
	}


//-----------------------------------------------------------------------------
// realDestFor:
//-----------------------------------------------------------------------------
- realDestFor:dest
	{
	return (dest == FIRST_RESP ? 0 : dest);
	}


//-----------------------------------------------------------------------------
// -set:outlet:
//		Note: this method is only accurate when connList is valid.
//-----------------------------------------------------------------------------
- (void) set:(int)row outlet:(NSString*)name
	{
	id conn = [self findConnectionForOutlet:name];
	id dest = (conn == 0 ? 0 : [self virtualDestFor:[conn destination]]);

	[outletScroll setRow:row tag:int(dest)];

	[[outletScroll cellAt:row:MISC_TS_NAME_OS]
		setStringValue:[name cString]];

	BOOL const is_connected = (conn != 0);
	[[[outletScroll cellAt:row:MISC_TS_CONN_OS]
		setImage: (is_connected ? IMAGE_DIMPLE : IMAGE_BLANK)]
		setTag: int(is_connected)];

	BOOL const has_action = ([self actionForOutlet:name] != 0);
	[[[outletScroll cellAt:row:MISC_TS_PTR_OS]
		setImage: (has_action ? IMAGE_ARROW : IMAGE_BLANK)]
		setTag: int(has_action)];
	}


//-----------------------------------------------------------------------------
// -loadOutlets
//		Note: this method is only accurate when connList is valid.
//
//		Note: For some silly reason, the -getOutletsOf:deep: method filters
//		out the "target" outlet.  (Who knows why -- must be more hardcoded
//		internal IB junk.)	Therefore, we need to add it back in, manually.
//-----------------------------------------------------------------------------
- (void) loadOutlets
	{
	NSArray* list = [[[NXApp activeDocument] classData]
								getOutletsOf:[object getIBClassName]];
	[outletScroll setAutodisplay:NO];
	unsigned int const lim = [list count];
	if (lim > 0)
		{
		[outletScroll renewRows:lim+1];			// Add one for target.
		for (int i = 0; i < lim; i++)
			[self set:i outlet:[list objectAtIndex:i]];
		[self set:lim outlet:@"target"];
		[outletScroll sortRows];
		}
	else
		[outletScroll empty];
	[outletScroll setAutodisplay:YES];
	}


//-----------------------------------------------------------------------------
// -selectedOutletName
//-----------------------------------------------------------------------------
- (NSString*) selectedOutletName
	{
	NSString* s = 0;
	int const r = [outletScroll selectedRow];
	if (r >= 0)
		{
		char const* const t = [outletScroll stringValueAt:r:MISC_TS_NAME_OS];
		s = [NSString stringWithCString:t];
		}
	return s;
	}


//-----------------------------------------------------------------------------
// -shouldShowActions:
//-----------------------------------------------------------------------------
- (BOOL) shouldShowActions:(id*)old_dest
	{
	int const r = [outletScroll selectedRow];
	if (r >= 0 && [outletScroll tagAt:r:MISC_TS_PTR_OS] != 0)
		{
		*old_dest = (id)[outletScroll rowTag:r];
		return YES;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// -loadActions
//-----------------------------------------------------------------------------
- (void) loadActions
	{
	[actionScroll setAutodisplay:NO];
	[actionScroll empty];
	id old_dest = 0;
	if ([self shouldShowActions:&old_dest])
		{
		int sel_row = -1;
		id dest = (curdst != 0 ? curdst : old_dest);
		if (dest == 0 && [NXApp isConnecting])
			dest = [self virtualDestFor:[NXApp connectDestination]];
		if (dest != 0)
			{
			id doc = [NXApp activeDocument];
			NSString* lbl = 0;
			if (dest == old_dest)
				{
				NSString* oname = [self selectedOutletName];
				id conn = [self connectionForOutlet:oname];
				if (conn != 0)
					lbl = [conn label];
				}

			NSString* className = (dest == FIRST_RESP ?
								FIRST_RESP_NAME : [dest getIBClassName]);
			NSArray* list = [[doc classData] getActionsOf:className deep:YES];
			unsigned int const lim = [list count];
			[actionScroll renewRows:lim];
			for (unsigned int i = 0; i < lim; i++)
				{
				NSString* s = (NSString*) [list objectAtIndex:i];
				[[actionScroll cellAt:i:MISC_TS_NAME_AS]
					setStringValue:[s cString]];
				BOOL const connected = lbl != 0 && [lbl isEqualToString:s];
				[[[actionScroll cellAt:i:MISC_TS_CONN_AS]
					setImage: (connected ? IMAGE_DIMPLE : IMAGE_BLANK)]
					setTag: int(connected)];
				if (connected)
					sel_row = (int) i;
				}
			}
		if ([actionScroll numRows] > 0)
			{
			[actionScroll sortRows];
			if (sel_row >= 0)
				{
				[actionScroll selectRow:sel_row];
				[actionScroll scrollRowToVisible:sel_row];
				}
			}
		}
	[actionScroll setAutodisplay:YES];
	}


//-----------------------------------------------------------------------------
// -selectOutlet:
//-----------------------------------------------------------------------------
- (void) selectOutlet:(char const*)name
	{
	int row = -1;
	if (name != 0)
		{
		int const lim = [outletScroll numRows];
		for (int i = 0; i < lim; i++)
			{
			int const j = [outletScroll rowAtPosition:i];
			char const* t = [outletScroll stringValueAt:j:MISC_TS_NAME_OS];
			if (t != 0 && strcmp( t, name ) == 0)
				{
				row = j;
				break;
				}
			}
		}
	BOOL const was_auto = [outletScroll isAutodisplay];
	[outletScroll setAutodisplay:NO];
	[outletScroll selectRow:row];
	if (row >= 0)
		[outletScroll scrollRowToVisible:row];
	[self loadActions];
	[outletScroll setAutodisplay:was_auto];
	}


//-----------------------------------------------------------------------------
// -setCurout:
//-----------------------------------------------------------------------------
- (void) setCurout:(NSString*)newout
	{
	[curout autorelease];
	curout = newout;
	[curout retain];
	}


//-----------------------------------------------------------------------------
// -outletClick:
//-----------------------------------------------------------------------------
- outletClick:sender
	{
	[window disableFlushWindow];
	int const r = [outletScroll selectedRow];
	if (r >= 0)
		{
		char const* const t = [outletScroll stringValueAt:r:MISC_TS_NAME_OS];
		NSString* s = [NSString stringWithCString:t];
		cursrc = object;
		[self setCurout:s];
		id conn = [self connectionForOutlet:s];
		if (conn != 0)
			{
			id dst = [conn destination];
			curdst = [self virtualDestFor:dst];
			[NXApp displayConnectionBetween:[conn source] and:dst];
			}
		else
			curdst = 0;
		}
	[self loadActions];
	[[window reenableFlushWindow] flushWindow];
	return self;
	}


//-----------------------------------------------------------------------------
// -outletDblClick:
//		NOTE *1* The only action allowed on the outlet column for 
//		target/action style outlets is breaking "old" connections that
//		do not have an action set.
//-----------------------------------------------------------------------------
- outletDblClick:sender
	{
	int const r = [outletScroll selectedRow];
	if (r >= 0)
		{
		[window disableFlushWindow];
		id old_dest = 0;
		id new_dest = 0;
		id doc = [NXApp activeDocument];

		char const* const t = [outletScroll stringValueAt:r:MISC_TS_NAME_OS];
		NSString* outletName = [NSString stringWithCString:t];
		id conn = [self connectionForOutlet:outletName];

		BOOL do_break = NO;
		BOOL do_make = NO;
		BOOL const has_action = (BOOL) [outletScroll tagAt:r:MISC_TS_PTR_OS];
		if (has_action)
			{
			do_break = (conn != 0 && [conn actionName] == 0);	// NOTE *1*
			}
		else
			{
			do_break = (conn != 0);
			old_dest = [self virtualDestFor:[conn destination]];
			new_dest = [self virtualDestFor:[NXApp connectDestination]];
			do_make = (new_dest != FIRST_RESP && new_dest != old_dest);
			}

		if (do_break)
			{
			[doc removeConnector:conn];
			[conn free];
			}

		if (do_make)
			{
			conn = [[IBOutletConnector alloc]
						initSource:object
						destination:[self realDestFor:new_dest]
						label:outletName];
			[doc addConnector:conn];
			}

		if (do_break || do_make)
			[self ok:0];

		[[window reenableFlushWindow] flushWindow];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// -actionClick:
//-----------------------------------------------------------------------------
- actionClick:sender
	{
	return self;
	}


//-----------------------------------------------------------------------------
// -actionDblClick:
//		*1* FIXME: Stop this cycle of stupidity!  If we can get a better,
//		more generic subclass to work, that accepts the name of the outlet,
//		the name of the action-variable as part of the constructor, then we
//		should do that.
//-----------------------------------------------------------------------------
- actionDblClick:sender
	{
	int const r = [actionScroll selectedRow];
	if (r >= 0)
		{
		[window disableFlushWindow];
		id doc = [NXApp activeDocument];
		id dest = [self virtualDestFor:[NXApp connectDestination]];
		NSString* outletName = [self selectedOutletName];
		id conn = [self connectionForOutlet:outletName];
		if (conn != 0)
			{
			[doc removeConnector:conn];
			[conn free];
			}
		int const tag = [actionScroll tagAt:r:MISC_TS_CONN_AS];
		if (tag == 0)
			{
			char const* t = [actionScroll stringValueAt:r:MISC_TS_NAME_AS];
			NSString* s = [NSString stringWithCString:t];
			id realDest = [self realDestFor:dest];
			if ([outletName isEqualToString:@"target"])
				{
				conn = [[IBControlConnector alloc]
								initSource:object destination:realDest label:s];
				}
			else
				{
				conn = [[MiscTableConnector alloc]
								initSource:object destination:realDest label:s
								outlet:outletName
								action:[self actionForOutlet:outletName]];
				}
			if (conn != 0)
				{
				[self setCurout:outletName];
				[doc addConnector:conn];
				}
			}
		[self ok:0];
		[[window reenableFlushWindow] flushWindow];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// findRowWithTag
//		Find the first row (in visual-order) with the given tag.
//-----------------------------------------------------------------------------
static int findRowWithTag( MiscTableScroll* scroll, int tag )
	{
	unsigned int const lim = [scroll numRows];
	for (unsigned int i = 0; i < lim; i++)
		{
		int const r = [scroll rowAtPosition:i];
		if ([scroll rowTag:r] == tag)
			return r;
		}
	return -1;
	}


//-----------------------------------------------------------------------------
// -preselectOutlet
//-----------------------------------------------------------------------------
- (NSString*) preselectOutlet
	{
	int row = findRowWithTag( outletScroll, int(curdst) );
	if (row < 0)
		{
		row = findRowWithTag( outletScroll, 0 );
		if (row < 0)
			row = 0;
		}

	char const* const s = [outletScroll stringValueAt:row:MISC_TS_NAME_OS];
	return [NSString stringWithCString:s];
	}


//-----------------------------------------------------------------------------
// -preselect
//-----------------------------------------------------------------------------
- (void) preselect
	{
	if ([outletScroll numRows] > 0)
		{
		id oldsrc = cursrc;
		id olddst = curdst;
		if ([NXApp isConnecting])
			{
			cursrc = [NXApp connectSource];
			curdst = [self virtualDestFor:[NXApp connectDestination]];
			}
		else
			{
			cursrc = object;
			curdst = 0;
			}
	
		if (oldsrc != cursrc || olddst != curdst)
			[self setCurout:[self preselectOutlet]];
	
		char const* const t = [curout cString];
		[self selectOutlet:t];
		}
	}


//-----------------------------------------------------------------------------
// -revert:
//-----------------------------------------------------------------------------
- revert:sender
	{
	[window disableDisplay];
	[super revert:sender];
	[self getConnList];
	[self loadOutlets];
	[self loadActions];
	[self preselect];
	[[window reenableDisplay] display];
	return self;
	}


//-----------------------------------------------------------------------------
// -ok:
//-----------------------------------------------------------------------------
- ok:sender
	{
	[super ok:sender];
	[self revert:0];
	return self;
	}


//-----------------------------------------------------------------------------
// -wantsButtons
//-----------------------------------------------------------------------------
- (BOOL) wantsButtons
	{
	return NO;
	}

@end

#endif // defined(NX_COMPILER_RELEASE_3_3)
