
/*
	Copyright 1993  Jeremy Slade.  All rights reserved.
*/

#import "ItemCell.h"

#import "Globals.h"
#import "Group.h"
#import "PSWraps.h"

#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <mach/message.h>
#import <stdio.h>
#import <streams/streams.h>
#import <string.h>
#import <sys/file.h>
#import <sys/types.h>


const char *ICModeTitles[] = {
	"Unknown", "Large Browse", "Small Browse",
	"Large Icon", "Small Icon",
	NULL
};

const char *ICFormatTitles[] = {
	"Unknown", "Full Path", "File and Path",
	"File Only", "Relative", "No Path",
	NULL
};


// This macro is called whenever a property is set on an item
// Setting a property causes a Dynamic item to become static.
#define NO_LONGER_STATIC	{ if ( dynamic ) [self setDynamic:NO]; }


/*
 * Defines of various drawing attributes
 */
 
 // Things common to all modes
#define X_BORDER	(4)				// Same for all modes
#define Y_BORDER	(4)				// Same for all modes

// Large Browse mode
#define LB_ICON_W			(48.0)
#define LB_ICON_H			(48.0)
#define LB_ICON_X			(X_BORDER+4)
#define LB_ICON_Y			(Y_BORDER+4)
#define LB_DOTS_X			(LB_ICON_X-8)
#define LB_DOTS_Y			(LB_ICON_Y-8)
#define LB_PATH_H			(pathHeight)
#define LB_PATH_X			(LB_ICON_X + LB_ICON_W + X_BORDER)
#define LB_PATH_Y			(LB_ICON_Y + LB_ICON_H - LB_PATH_H )
#define LB_INFO_H			(infoHeight)
#define LB_INFO1_X			LB_PATH_X
#define LB_INFO1_Y			(LB_PATH_Y - LB_INFO_H)
#define LB_INFO2_X			LB_PATH_X
#define LB_INFO2_Y			(LB_INFO1_Y - LB_INFO_H)
#define LB_WIDTH			(100)
#define LB_HEIGHT			(LB_ICON_Y + LB_ICON_H + Y_BORDER)

// Small Browse mode
#define SB_ICON_W			(24.0)
#define SB_ICON_H			(24.0)
#define SB_ICON_X			(X_BORDER)
#define SB_ICON_Y			(Y_BORDER)
#define SB_PATH_H			(pathHeight)
#define SB_PATH_X 			( drawInfo.sb_icon ? \
			(SB_ICON_X + SB_ICON_W + X_BORDER) : (X_BORDER) )
#define SB_PATH_Y ( drawInfo.sb_icon ? \
			(SB_ICON_Y + SB_ICON_H - SB_PATH_H) : (pathDescender + 1) )
#define SB_WIDTH			(100)
#define SB_HEIGHT			( drawInfo.sb_icon ? \
			(SB_ICON_Y + SB_ICON_H + Y_BORDER) \
			: (SB_PATH_H + 1) )

// Large Icon mode
#define LI_ICON_W			(48.0)
#define LI_ICON_H			(48.0)
#define LI_ICON_X			(-24.0)
#define LI_ICON_Y			(24.0)
#define LI_PATH_H			(pathHeight + pathDescender)
#define LI_PATH_X			(0)
#define LI_PATH_Y			(LI_ICON_H + 4 + LI_PATH_H - pathDescender)

// Small Icon mode
#define SI_ICON_W			(24.0)
#define SI_ICON_H			(24.0)
#define SI_ICON_X			(-12.0)
#define SI_ICON_Y			(12.0)
#define SI_PATH_H			(pathHeight + pathDescender)
#define SI_PATH_X			(0)
#define SI_PATH_Y			(LI_ICON_H + 3 + LI_PATH_H - pathDescender)



// -------------------------------------------------------------------------
//   Stuff used in drawing
// -------------------------------------------------------------------------

DrawInfo drawInfo;	// Global structure of current drawing settings
int	nameMode;		// Current nameMode
id	pathFont;		// Font used to draw PATH LINE
id	infoFont;		// Font used to draw INFO LINEs
id	dots;			// Tri-dots icon
float pathHeight;
float pathDescender;
float infoHeight;
float infoDescender;

static id	iconTable = nil;	// HashTable of Icons


// The following Category of speaker declares the interface for a method
// that was supported in NS 2.x, but is not longer officially supported
// in NS 3.0, but of course it has to still be there in order for 2.x
// apps to run under 3.0, So it is ok for us to call it...
// (there's no garauntee of how long this will be true)
@interface Speaker ( ItemCell_Undocumented )
- (int)launchProgram:(const char *)app ok:(int *)flag;
@end


@implementation ItemCell // ------------------------------------------------


// -------------------------------------------------------------------------
//   Creating, initializing methods
// -------------------------------------------------------------------------


+ initialize
/*
	Set the class version number
	Set up the objects that are used by all instances to do drawing.
*/
{
	NXCoord  ascender, descender, lineHeight;
	
	// Set class version
	[self setVersion:ItemCell_VERSION];
	
	// Font used for drawing the Path line
	pathFont = [Font newFont:"Helvetica" size:12 matrix:NX_FLIPPEDMATRIX];
	NXTextFontInfo ( pathFont, &ascender, &descender, &lineHeight );
	pathHeight = lineHeight;
	pathDescender = descender;
	
	// Font used for drawing the Info lines
	infoFont = [Font newFont:"Helvetica" size:12 matrix:NX_FLIPPEDMATRIX];
	NXTextFontInfo ( infoFont, &ascender, &descender, &lineHeight );
	infoHeight = lineHeight;
	infoDescender = descender;
	
	// Tri-dots image to show App's running status
	dots = [NXImage findImageNamed:"dots"];
	
	// Set up the iconTable
	// Keys are the name of the icon, and the value is the id of the
	// NXImage that contains the actual icon
	iconTable = [[HashTable alloc] initKeyDesc:"*" valueDesc:"@"];
	
	return ( self );
}



- init
{
	return ( [self initPath:NULL] );
}



- initPath:(const char *)aPath
/*
	Designated initializer: initialize an instance and set it's path to aPath
*/
{
	[super init];
	
	[self setPath:aPath];

	isAutoLaunch = isGroupLaunch = NO;
	
	fileType = FT_UNKNOWN;

	specificApp = NO;
	createBrowser = YES;
	hideIcon = NO;
	dynamic = NO;
	
	return ( self );
}



- free
/*
	Free the instance and all support objects it allocated
*/
{
	if ( path ) NX_FREE ( path );
	if ( fileStr && fileStr !=path ) NX_FREE ( fileStr );
	if ( actualImage ) [actualImage free];
	if ( appName ) NX_FREE ( appName );
	if ( rootAt ) NX_FREE ( rootAt );
	
	return ( [super free] );
}



// -------------------------------------------------------------------------
//   Drawing Methods
// -------------------------------------------------------------------------


+ setDrawInfo:(const DrawInfo *)info
/*
	Set the current global draw info.  All drawing is done based on the current draw info, so a group that is going to display must first call this method to make its draw info current.
*/
{
	drawInfo = *info;
	
	// Determine the nameMode (so this doesn't have to be done each time
	// -fileStr is called)
	switch ( drawInfo.mode ) {
		case IC_LARGE_BROWSE:
			nameMode = drawInfo.lb_nameMode; break;
		case IC_SMALL_BROWSE:
			nameMode = drawInfo.sb_nameMode; break;
		case IC_LARGE_ICON:
		case IC_SMALL_ICON:
		default:
			nameMode = IC_FILEONLY; break;
	}

	return ( self );
}



+ getDrawInfo:(DrawInfo *)info
{
	*info = drawInfo;
	return ( self );
}



+ (int)drawMode
{
	return ( drawInfo.mode );
}



- calcCellSize:(NXSize *)size;
/*
	Calculate the minimum size needed to draw the cell based in the current draw info.
*/
{
	switch ( drawInfo.mode ) {
		case IC_LARGE_BROWSE:
			size->width = LB_WIDTH;
			size->height = LB_HEIGHT;
			break;
		case IC_SMALL_BROWSE:
			size->width = SB_WIDTH;
			size->height = SB_HEIGHT;
			break;
		case IC_LARGE_ICON: // Shouldn't ever get called for this mode
		case IC_SMALL_ICON: // Shouldn't ever get called for this mode
		default:
			size->width = 0;
			size->height = 0;
			break;
	}
	
	//if ( DEBUGGING ) printf ( "calcCellSize: %.0f, %.0f\n", size->width, size->height );
	return ( self );
}



- drawSelf:(const NXRect *)cellFrame inView:controlView
{
	[self drawInside:cellFrame inView:controlView];
	return ( self );
}



- drawInside:(const NXRect *)cellFrame inView:controlView
/*
	Draw only the interior of the cell.  NOTE: For all Y coords used in drawing, it assumes that controlView will be flipped ( [controlView isFlipped] == YES ).  This is because it assumes that it will always be drawn in a Matrix (or subclass), which is always flipped.
*/
{
	NXRect rects[2];
	NXPoint pt;
	const char *infoStr;
	
	if ( (drawInfo.mode == IC_LARGE_BROWSE ||
			drawInfo.mode == IC_SMALL_BROWSE) ) {
		// Erase the cell 
		PSsetgray ( (cFlags1.state || cFlags1.highlighted) ? NX_WHITE : NX_LTGRAY );
		NXRectFill ( cellFrame );
	
		// Draw a dark line above and below the cell if it is selected
		if ( (cFlags1.state || cFlags1.highlighted) ) {
			PSsetgray ( NX_DKGRAY );
			rects[0].origin.x = NX_X(cellFrame);
			rects[0].origin.y = NX_Y(cellFrame);
			rects[0].size.width = NX_WIDTH(cellFrame);
			rects[0].size.height = 1.0;
			rects[1] = rects[0];
			rects[1].origin.y = NX_MAXY(cellFrame) - 1.0;
			NXRectFillList ( rects, 2 );
		}
	}

	[self fileType];	// Make sure we have a fileType

	// Draw according to drawInfo
	switch ( drawInfo.mode ) {
	
		case IC_LARGE_BROWSE:
			
			// Draw the icon
			if ( !image ) [self getIcon];
			pt.x = NX_X(cellFrame) + LB_ICON_X;
			pt.y = NX_Y(cellFrame) + NX_HEIGHT(cellFrame) - LB_ICON_Y;
			if ( drawInfo.actualImage && actualImage )
				[actualImage composite:NX_SOVER toPoint:&pt];
			else
				[image composite:NX_SOVER toPoint:&pt];
			if ( fileType == FT_APPLICATION && drawInfo.lb_tridots ) {
				// Draw the dots if it's not running
				if ( NXPortNameLookup ( appName, "" ) == PORT_NULL ) {
					pt.x = NX_X(cellFrame) + LB_DOTS_X;
					pt.y = NX_Y(cellFrame) + NX_HEIGHT(cellFrame ) - LB_DOTS_Y;
					[dots composite:NX_SOVER toPoint:&pt];
				}
			}

			// Draw the fileStr
			pt.x = NX_X(cellFrame) + LB_PATH_X;
			pt.y = NX_Y(cellFrame) + NX_HEIGHT(cellFrame) - LB_PATH_Y;
			[pathFont set];
			PSsetgray ( NX_BLACK );
			PSshowxy ( pt.x, pt.y, (fileStr ? fileStr : [self fileStr]) );

			// Draw the Info Line 1
			if ( drawInfo.lb_info1 ) {
				infoStr = NULL;
				switch ( fileType ) {
					case FT_FILE: // Show the Item's app name
						if ( specificApp ) infoStr = appName;
						break;
					case FT_SUBDIR: // Show the Item's root path
						if ( createBrowser ) infoStr = rootAt;
						break;
					case FT_APPLICATION:
					case FT_UNKNOWN:
					default:		break;
				}

				if ( infoStr ) {
					pt.x = NX_X(cellFrame) + LB_INFO1_X;
					pt.y = NX_Y(cellFrame) + NX_HEIGHT(cellFrame) - LB_INFO1_Y;
					[infoFont set];
					PSsetgray ( NX_DKGRAY );
					PSshowxy ( pt.x, pt.y, infoStr );
				}
			}
			
			break;	// End of IC_LARGE_BROWSE
			

		case IC_SMALL_BROWSE:

			// Draw the 1/4-size icon
			if ( drawInfo.sb_icon ) {
				if ( !smallImage ) [self getIcon];
				pt.x = NX_X(cellFrame) + SB_ICON_X;
				pt.y = NX_Y(cellFrame)+ NX_HEIGHT(cellFrame) - SB_ICON_Y;
				[smallImage composite:NX_SOVER toPoint:&pt];
			}
			
			// Draw fileStr
			pt.x = NX_X(cellFrame) + SB_PATH_X;
			pt.y = NX_Y(cellFrame)+ NX_HEIGHT(cellFrame) - SB_PATH_Y;
			[pathFont set];
			PSsetgray ( NX_BLACK );
			PSshowxy ( pt.x, pt.y, (fileStr ? fileStr : [self fileStr]) );

			break;  // End of IC_SMALL_BROWSE
		
		
		case IC_LARGE_ICON:
			// Not Implemented
			break; // End of IC_LARGE_ICON
			
			
		case IC_SMALL_ICON:
			// Not Implemented
			break; // End of IC_SMALL_ICON

	}
		
	needsUpdate = NO;
	return ( self );
}



- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
{
	cFlags1.state = cFlags1.highlighted = (int)flag;
	[self drawInside:cellFrame inView:controlView];
	return ( self );
}



- (BOOL)needsUpdate
{
	return ( needsUpdate );
}



- (int)state
/*
	Returns TRUE if the cell is selected or highlighted, FALSE otherwise
*/
{
	return ( cFlags1.state | cFlags1.highlighted );
}



// -------------------------------------------------------------------------
//   Tracking
// -------------------------------------------------------------------------


- (BOOL)mouseDownAt:(const NXPoint *)startPt
	frame:(const NXRect *)cellFrame
	inView:controlView;
/*
	Called when a mouseDown: occurs within our frame.  Currently does nothing.  This might be used in the future to initiate direct editing of the path, appName, etc.
*/
{
	return ( NO );
}



// -------------------------------------------------------------------------
//   Setting Cell Attributes
// -------------------------------------------------------------------------


- setPath:(const char *)aPath
/*
	Set the path to aPath
*/
{
	NO_LONGER_STATIC;
	
	if ( !path || strcmp ( path, aPath ) ) { // Are we changing anything?

		[self resetFileStr];
		if ( path ) NX_FREE ( path );
		
		path = aPath ? NXCopyStringBuffer ( aPath ) : NULL;

		// Invalidate icons...
		image = nil;
		actualImage = nil;
		smallImage = nil;
		
		[group setChanged:YES];
		[group setNeedsShow:YES];
	}

	return ( self );
}



- resetFileStr
/*
	Reset the Item's fileStr to NULL so that it will be redetermined the next time it is drawn (actaully, the next time -fileStr gets called, which usually happens at draw time)
*/
{
	if ( fileStr && fileStr != path ) NX_FREE ( fileStr );
	fileStr = NULL;
	needsUpdate = YES;
	return ( self );
}



- setAutoLaunch:(BOOL)flag
/*
	Set the AutoLaunch flag as specified
*/
{
	NO_LONGER_STATIC;
	
	if ( isAutoLaunch != flag ) {
		isAutoLaunch = flag;
		[group setChanged:YES];
		[group setNeedsShow:YES];
	}
	
	return ( self );
}



- setGroupLaunch:(BOOL)flag
/*
	Set the GroupLaunch flag as specified
*/
{
	NO_LONGER_STATIC;
	
	if ( isGroupLaunch != flag ) {
		isGroupLaunch = flag;
		[group setChanged:YES];
		[group setNeedsShow:YES];
	}
	
	return ( self );
}



- getInfo
/*
	This method gets the item's file info from WM, which consists of its application, its type, and its extenstion.
*/
{
	char *app;
	id ws = [Application workspace];
	NXAtom typ;
	BOOL	ok;

	if ( !(path && strlen ( path )) )
		return ( self );
		
	fileType = FT_UNKNOWN;
	
	ok = [ws getInfoForFile:path	// Request file info from workspace
		application:&app
		type:&typ];
		
	if ( ok ) { // WM got the info ok
		
		// Copy the app name
		if ( !specificApp ) {
			if ( appName ) NX_FREE ( appName );
			appName = NXCopyStringBuffer ( app );
		}
				
		// Determine the file type
		if ( typ == NXDirectoryFileType || typ == NXFilesystemFileType )
			fileType = FT_SUBDIR;
		else
		if ( typ == NXApplicationFileType || typ == NXShellCommandFileType )
			fileType = FT_APPLICATION;
		else
		if ( typ == NXPlainFileType )
			fileType = FT_FILE;
		else
			fileType = FT_FILE; // = FT_UNKNOWN;
	}
	
	return ( self );
}


	
- getIcon
/*
	Get the icon for path.  First it looks in the CustomIcons directory.  Tiff images in this directory should be called [YY.<large|small>.tiff], where YY is the extension (e.g. 'h', 'm', 'snd'), and large means a normal (48x48) icon and small means a 1/4-size (24x24) icon.  If it doesn't find a matching icon in the CustomIcons dir, it then tries to get it's icon from Workspace Manager.
*/
{
	NXSize size;
	NXImageRep *rep;
	char *e, extension[50];
	char iconPath[MAXPATHLEN+1], iconName[50];
	id ws = [Application workspace];
	NXImage	*tempImage;
	
	
	if ( !(path && strlen ( path )) )
		return ( self );

	/*
		Get the item's normal-size icon image:
			first look for it by extenstion in the ICONS_DIR, then get
			it from WM if not found
	*/
	e = rindex ( path, '.' );
	if ( e ) e++;
	strcpy ( extension, (e ? e : "") );
	
	image = smallImage = nil;

	if ( fileType == FT_FILE ) {
		sprintf ( iconName, "%s.large.tiff", extension );
		sprintf ( iconPath, "%s/%s/%s/%s",
			NXHomeDirectory(), LIBRARY_DIR, ICONS_DIR, iconName );
	} else {
		strcpy ( iconName, path );
		strcpy ( iconPath, "" );
	}
	
	// Look and see if the image has been loaded before
	// If it has been loaded before, use it
	if ( !(image = (id)[iconTable valueForKey:iconName]) ) {
	
		// Hasn't been loaded before, look in ICONS_DIR
		if ( strlen ( iconPath ) && access(iconPath, R_OK) != -1 )
			image = [[NXImage alloc] initFromFile:iconPath];
		if ( image ) {
			// Make sure the icon is the right size (48x48)
			size.width = 48.0;
			size.height = 48.0;
			[[image setSize:&size] setScalable:YES];
		} else {
		
			// Couldn't find a custom icon, get it from the workspace
			tempImage = [ws getIconForFile:path];
			if ( tempImage ) {	// Got the icon ok	
				image = tempImage;
				[image setDataRetained:YES];
			}
		}
	}

	if ( !image ) // Unable to find an icon for this item
		return ( nil );
		
	// Put this image in the iconTable so it can be used again by other
	// items with the same extension
	if ( fileType == FT_FILE )
		[iconTable insertKey:NXUniqueString(iconName) value:image];
	
	// If the item is a tiff or eps image, get a scaled representation
	// of it as its image
	if ( !strcmp ( extension, "tiff" ) || !strcmp ( extension, "eps" ) )
		if ( (actualImage = [[NXImage alloc] initFromFile:path]) ) {
			[actualImage getSize:&size];
			if ( !size.width || (size.width > 24.0) )
				size.width = 48.0;
			if ( !size.height || (size.height > 24.0) )
				size.height = 48.0;
			[[actualImage setSize:&size] setScalable:YES];
		}
					
	
	/*
		Get the item's 1/4-size icon image, using the same process as
		for the normal icons
	*/
	
	if ( fileType == FT_FILE ) {
		sprintf ( iconName, "%s.small.tiff", extension );
		sprintf ( iconPath, "%s/%s/%s/%s",
			NXHomeDirectory(), LIBRARY_DIR, ICONS_DIR, iconName );
	} else {
		strcpy ( iconName, path );
		strcpy ( iconPath, "" );
	}
	
	// Look and see if the image has been loaded before
	// If it has been loaded before, use it
	if ( !(smallImage = (id)[iconTable valueForKey:iconName]) ) {
	
		// Hasn't been loaded before, look in ICONS_DIR
		if ( strlen ( iconPath ) && access(iconPath, R_OK) != -1 )
			smallImage = [[NXImage alloc] initFromFile:iconPath];
		if ( smallImage ) {
			// Make sure the icon is the right size (24x24)
			size.width = 24.0;
			size.height = 24.0;
			[[smallImage setSize:&size] setScalable:YES];
		} else {

			// Couldn't find a custom icon
			// Copy the full-size icon and scale it down
			size.width = 24.0;
			size.height = 24.0;
			smallImage = [[NXImage alloc] initSize:&size];
			[smallImage setScalable:YES];
			rep = [[image bestRepresentation] copy];
			[smallImage useRepresentation:rep];
			[rep setSize:&size];
		}
	}
		
	if ( !smallImage ) // Unable to find an icon for this item
		return ( nil );
	
	// Put this image in the iconTable so it can be used again by other
	// items with the same extension
	if ( fileType == FT_FILE )
		[iconTable insertKey:NXUniqueString(iconName) value:smallImage];
	
	
	return ( self );
}



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



- (int)fileType
/*
	Returns the type of file this item represents -- e.g. an application, a folder
*/
{
	if ( fileType == FT_UNKNOWN ) [self getInfo];
	return ( fileType );
}



- (const char *)fileStr
/*
	Returns a formatted string using the nameMode of the current drawInfo
*/
{
	char str[MAXPATHLEN+1];
	char *s, *ch, *home;
	const char *groupPath;
	
	switch ( nameMode ) {
		case IC_FULLPATH: // /Users/joe/Apps/Checkers.app
			fileStr = path;
			break;
			
		case IC_FILEANDPATH: // Checkers.app -- /Users/joe/Apps
			s = rindex ( path, '/' ); // Find last path separator
			if ( s && s != path ) {
				ch = s;
				*ch = '\0'; // Temporarily break string
				s++;
				if ( strstr ( path, NXHomeDirectory() ) == path ) {
					home = &path[strlen(NXHomeDirectory())];
					sprintf ( str, "%s -- ~%s", s, home );
				} else
					sprintf ( str, "%s -- %s", s, !(*path) ? "/" : path );
				*ch = '/'; // Restore original string
			} else
				sprintf ( str, path );
			fileStr = NXCopyStringBuffer ( str );
			break;
			
		case IC_FILEONLY: // Checkers.app
			s = rindex ( path, '/' ); // Find last path separator
			if ( s && s != path ) {
				ch = s;
				*ch = '\0'; // Temporarily break string
				s++;
				fileStr = NXCopyStringBuffer ( s );
				*ch = '/'; // Restore the original string
			} else
				fileStr = path;
			break;
			
		case IC_RELATIVE: // Apps/Checkers.app for /Users/joe
			groupPath = [group defaultPath];
			if ( groupPath && strstr ( path, groupPath ) && strcmp ( path, groupPath ) ) {
				s = path + strlen(groupPath) + 1; // Point after the '/'
				fileStr = NXCopyStringBuffer ( s );
			} else
				fileStr = path;
			break;
			
		case IC_NOPATH: // Don't display anything
			fileStr = NXCopyStringBuffer ( "" );
			break;
			
		case IC_NAME_UNKNOWN:
		default:
			fileStr = path;
			break;
	}	
			
	return ( fileStr );
}



- (const char *)filename
/* Returns the last component of the Item's path -- everything after the last '/' */
{
	char *s;
	if ( !path ) return ( NULL );
	s = rindex ( path, '/' );
	if ( s ) s++;
		else s = path;
	return ( s );
}



- (BOOL)isAutoLaunch
{
	return ( isAutoLaunch );
}



- (BOOL)isGroupLaunch
{
	return ( isGroupLaunch );
}



- (NXImage *)image
/*
	Return the NXImage of the item's icon
*/
{
	if ( !image ) [self getIcon];
	return ( image );
}



- (NXImage *)smallImage
/*
	Returns the NXImage used to draw the item's 1/4-size icon
*/
{
	return ( smallImage );
}



// -------------------------------------------------------------------------
//   Group
// -------------------------------------------------------------------------


- setGroup:aGroup
{
	group = aGroup;
	[self resetFileStr];
	return ( self );
}



- group
{
	return ( group );
}



// -------------------------------------------------------------------------
//   File-type Items
// -------------------------------------------------------------------------


- setAppName:(const char *)aString
/*
	Set the name of the app used to open the item.  If this method is never called on an item, it uses the default app as determined by Workspace.  If aString is NULL or an empty string, it goes back to using the default.
*/
{
	NO_LONGER_STATIC;
	
	if ( aString && strlen ( aString ) ) {
		if ( !specificApp || strcmp ( appName, aString ) )
			[group setChanged:YES];
			
		// Set a specific app
		specificApp = YES;
		
		// Copy the app's name
		appName = NXCopyStringBuffer ( aString );
	} else {
		if ( specificApp ) [group setChanged:YES];
		
		// Use the default app
		specificApp = NO;
	}
	
	[self getIcon];
	[group setNeedsShow:YES];
	return ( self );
}



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



- (BOOL)hasSpecificApp
/*
	Returns NO if the Item will use the default application determined by Workspace, or YES if it will use a specific (user-specified) app
*/
{
	return ( specificApp );
}



// -------------------------------------------------------------------------
//   Subdir-type Items
// -------------------------------------------------------------------------


- setCreateBrowser:(BOOL)flag
/*
	If flag is YES, then when this subdir is opened in Workspace, a new FileViewer will be created to display it.  The new Viewer will be rooted at the path contained in rootAt.
*/
{
	NO_LONGER_STATIC;
	
	if ( createBrowser != flag ) {
		createBrowser = flag;
		[group setChanged:YES];
		[group setNeedsShow:YES];
	}

	return ( self );
}



- rootBrowserAt:(const char *)fullPath
/*
	Sets the path that will be the root directory for the FileViewer created in Workspace Manager when this item is opened.  This only applies if createBrowser is true
*/
{
	NO_LONGER_STATIC;
	
	if ( rootAt != fullPath ||
		(rootAt && fullPath && strcmp ( rootAt, fullPath )) ) {
		
		if ( rootAt ) NX_FREE ( rootAt );
		
		rootAt = fullPath ? NXCopyStringBuffer ( fullPath ) : NULL;
		[group setChanged:YES];
		[group setNeedsShow:YES];
	}
	
	return ( self );
}



- (BOOL)createBrowser
{
	return ( createBrowser );
}



- (const char *)browserRootedAt
{
	return ( rootAt );
}



// -------------------------------------------------------------------------
//   App-type Items
// -------------------------------------------------------------------------


- setHideIcon:(BOOL)flag
/*
	if flag is YES, then when the Application is launched, it's Icon will be hidden.  If NO, its icon will show as it normally does
*/
{
	NO_LONGER_STATIC;
	
	if ( hideIcon != flag ) {
		hideIcon = flag;
		[group setChanged:YES];
		[group setNeedsShow:YES];
	}

	return ( self );
}



- (BOOL)hideIcon
{
	return ( hideIcon );
}



// -------------------------------------------------------------------------
//   Dynamic Items
// -------------------------------------------------------------------------


- setDynamic:(BOOL)flag
/*
	A Dynamic Item isn't archived.  It is a run-time only Item created from the Dynamic Item specs in the group. If a dynamic item is changed in any way (ie though the inspector, it has some properties set), then it is automatically set to be static, which means it then becomes a permanent part of the group.
*/
{
	if ( dynamic != flag ) {
		dynamic = flag;
		if ( dynamic ) { // Setting to be a dynamic item
		} else { // Going from a dynamic to static item
			[group setChanged:YES];
			[group setNeedsShow:YES];
		}
	}
	return ( self );
}



- (BOOL)isDynamic
{
	return ( dynamic );
}



// -------------------------------------------------------------------------
//   Launching
// -------------------------------------------------------------------------


- (BOOL)launch
/*
	Opens the item using Workspace Manager.  Returns yes if operation succeeded, else NO
*/
{
	id	workspace = [Application workspace];
	port_t	appPort;
	
	if ( !workspace ) {
		NXRunAlertPanel ( "Launch", "Unable to contact Workspace Manager!",
			NULL, NULL, NULL );
		return ( NO );
	}
		
	switch ( [self fileType] ) { // Open according to file type
		
		case FT_FILE: // General files
			if ( specificApp ) {
				// Open using specified application
				[workspace openFile:path
					withApplication:appName
					andDeactivate:YES];
			} else {
				// Open using default app
				[workspace openFile:path
					withApplication:NULL
					andDeactivate:YES];
			}		
			break;
		
		case FT_APPLICATION: // An application
			if ( (appPort = NXPortNameLookup ( appName, NULL )) != PORT_NULL ) {
				// The app is already running, activate it
				// activate it by calling Speaker's launchProgram:ok: method
				int status, flag;
				[[NXApp appSpeaker] setSendPort:
					NXPortFromName ( NX_WORKSPACEREQUEST, NULL )];
				status = [[NXApp appSpeaker] launchProgram:path ok:&flag];
			} else  {
				[workspace launchApplication:path
					showTile:(!hideIcon)
					autolaunch:isAutoLaunch];
			}
			break;
			
		case FT_SUBDIR: // A directory/folder
			if ( specificApp ) {
				// Open using specified application
				[workspace openFile:path
					withApplication:appName
					andDeactivate:YES];
			} else
			if ( createBrowser ) {
				[workspace selectFile:path
					inFileViewerRootedAt:((rootAt && rootAt[0]) ? rootAt : "/")];
			} else {
				[workspace selectFile:path
					inFileViewerRootedAt:""];
			}
			break;
			
		default:
			return ( NO );
	}

	needsUpdate = YES; // Needs to be redrawn to update tri-dots
	[[self controlView] setNeedsDisplay:YES];

	return ( YES );
}



// -------------------------------------------------------------------------
//   Archiving
// -------------------------------------------------------------------------


- awake
/*
	Finish initializing after reading an archived ItemCell
*/
{
	[super awake];
	
	cFlags1.state = cFlags1.highlighted = 0;
	
	if ( path && path[0] != '/' ) [self setPath:"/"];
	needsUpdate = YES;
	return ( self );
}



- read:(NXTypedStream *)stream
/*
	Reads an archived instance from stream, according to it's version number.
*/
{
	int versionNumber;
	
	[super read:stream];
	
	// Get versionNumber
	versionNumber = NXTypedStreamClassVersion ( stream, [[self class] name] );
	
	if ( versionNumber <= ItemCell_VERSION ) { // thru current version
		NXReadTypes ( stream, "*ccccc**",
			&path,
			&isAutoLaunch, &isGroupLaunch, &specificApp,
			&createBrowser, &hideIcon,
			&appName,
			&rootAt );
		NXReadPoint ( stream, &largeIconLoc );
		NXReadPoint ( stream, &smallIconLoc );
	} 
	
	else { // Unrecognized archive version
		[self errMsg:"ItemCell: unknown version %i in archived object!\n",
			versionNumber];
	}
	
	return ( self );
}



- write:(NXTypedStream *)stream
/*
	Writes the important instance variables to stream.
*/
{
	if ( dynamic )
		return ( self );	// DynamicItems don't archive

	[super write:stream];

	// Write the instance variables	
	NXWriteTypes ( stream, "*ccccc**",
		&path,
		&isAutoLaunch, &isGroupLaunch, &specificApp,
		&createBrowser, &hideIcon,
		&appName,
		&rootAt );
	NXWritePoint ( stream, &largeIconLoc );
	NXWritePoint ( stream, &smallIconLoc );
	
	return ( self );
}



@end
