ftp.nice.ch/pub/next/tools/dock/Locus.1.0.NI.bs.tar.gz#/Locus/Source/FolderController.m

This is FolderController.m in view mode; [Download] [Up]

/*
	Copyright 1993  Jeremy Slade.

	You are free to use all or any parts of the Locus project
	however you wish, just give credit where credit is due.
	The author (Jeremy Slade) shall not be held responsible
	for any damages that result out of use or misuse of any
	part of this project.

*/

/*
	Project: Locus

	File: FolderController.m

	Description: See FolderController.h

	Original Author: Jeremy Slade

	Revision History:
		Created
			V.101	JGS Tue Feb  2 19:28:05 GMT-0700 1993

*/

#import "FolderController.h"

#import "Activator.h"
#import "AppIconView.h"
#import "AtomList.h"
#import "DynamicItems.h"
#import "Folder.h"
#import "FolderViewer.h"
#import "Globals.h"
#import "Group.h"
#import "GroupBrowser.h"
#import "GroupBrowserMatrix.h"
#import "Inspector.h"
#import "SwapView.h"

#import <libc.h>
#import <mach/mach.h>
#import <objc/List.h>
#import <objc/NXStringTable.h>
#import <stdio.h>
#import <streams/streams.h>
#import <sys/file.h>
#import <sys/param.h>
#import <sys/stat.h>
#import <sys/types.h>


// Item Pasteboard
#define ITEM_PBOARD	"_ItemPasteboard_"


@implementation FolderController


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


+ initialize
{
	[self setVersion:FolderController_VERSION];
	return ( self );
}



- init
{
	[super init];
	
	itemPboard = [Pasteboard newName:ITEM_PBOARD];
	if ( !folderList ) folderList = [[List alloc] initCount:0];
	keyFolder = nil;
	
	return ( self );
}



- free
{
	[[folderList freeObjects] free];
	[itemPboard free];
	return ( [super free] );
}



// -------------------------------------------------------------------------
//    Activating
// -------------------------------------------------------------------------


- setup
/*
	Setup the FolderController.  The actions performed here are as follows:
		1) Check for existance of LIBRARY_DIR, create it if necessary
		2) Check for existance of ICONS_DIR, create it if necessary
		3) Open the startup Folders
*/
{
	char folderDir[MAXPATHLEN+1], iconPath[MAXPATHLEN+1];
	const char *path;
	int i, count;
	
	if ( isSetup )	// Setup has already been performed
		return ( self );
		
	/* Check for Library directory, make it if not found */
	sprintf ( folderDir, "%s/%s", NXHomeDirectory(), LIBRARY_DIR );
	if ( access ( folderDir, F_OK ) == -1 )
		mkdir ( folderDir, DIR_CREATE_MASK );

	/* Also check for the Icons directory */
	sprintf ( iconPath, "%s/%s/%s",
		NXHomeDirectory(), LIBRARY_DIR, ICONS_DIR );
	if ( access ( iconPath, F_OK ) == -1 )
		mkdir ( iconPath, DIR_CREATE_MASK );
	
	[appIconView attachToIcon:[NXApp appIcon]];
	[appIconView setDraggingEnabled:appIconAdd];
	
	/* Open the startup folders */
	count = [startupFolders count];
	for ( i=0; i<count; i++ ) {
		path = [self resolvePathForFolder:[startupFolders atomAt:i]];
		if ( ![self openFolder:path]
				&& ![self folderWithFilename:path] ) {
			// Error opening the folder
			NXRunAlertPanel ( "Startup", "Unable to open startup folder: %s",
				"Ok", NULL, NULL, path );
		}
	}
		
	isAutoLaunch = NO;	// Turn this off after initial setup
	isSetup = YES;
	
	return ( self );
}



- (BOOL)cleanup
/*
	Cleanup the FolderController before ending operations.  Returns YES if it is ok to terminate, NO otherwise ( e.g. there are unsaved folders ).  This is intended to be called from -appWillTerminate:
*/
{
	int i, count, ret;
	id	folder;
	
	// Check for unsaved folders
	count=[folderList count];
	for ( i=0; i<count; i++ ) {
		folder = [folderList objectAt:i];
		if ( [folder isChanged] ) {
			ret = NXRunAlertPanel ( "Warning",
				"%s has been changed.  Do you want to save it?",
				"Save", "Don't Save", "Cancel", [folder filename] );
			switch ( ret ) {
				case NX_ALERTDEFAULT: // Save
					[self writeFolder:folder];
					break;
				case NX_ALERTALTERNATE: // Don't Save
					break;
				case NX_ALERTOTHER: // Cancel
					return ( NO );
					
			}
		}
	}

	[appIconView detachFromIcon];

	return ( YES );
}



- active:sender;
/*
	Called when Locus is activated (becomes the active app).  Set the App Icon to show an active state.
*/
{
	if ( ![NXApp isActive] ) [NXApp activateSelf:NO];
	
	[appIconView setIcon:"ActiveIcon"];
	if ( ![[[NXApp keyWindow] delegate] isKindOf:[Folder class]] )
		[[keyFolder viewer] makeKeyWindow];
	
	return ( self );
}



- inactive:sender
/*
	Called after Locus is deactived -- is not longer the active app.  Set the App Icon to show an inactive state.
*/
{
	[appIconView setIcon:"InactiveIcon"];
	return ( self );
}



- doAutoLaunch:sender
/*
	Launch all items in all groups in keyFolder that are flagged as AutoLaunch.  This is performed on all startup Folders as they are opened when Locus is auto-launched.  This can also be done using the AutoLaunch menu item.
*/
{
	int grp, gcount, e, ecount;
	id group, item;
	
	if ( !keyFolder )
		return ( self );

	gcount=[keyFolder count];
	for ( grp=0; grp<gcount; grp++ ) {
		group = [keyFolder objectAt:grp];
		ecount=[group count];
		for ( e=0; e<ecount; e++ ) {
			item = [group objectAt:e];
			if ( [item isAutoLaunch] ) {
				[item launch];
			}
		}
	}
	
	return ( self );
}



// -------------------------------------------------------------------------
//   File operations
// -------------------------------------------------------------------------


- (const char *)resolvePathForFolder:(const char *)path
/*
	Resolves a relative path to a Folder into a full path.  See -relativePathForFolder: for more info.
*/
{
	static char resolvedPath[MAXPATHLEN+1];
	
	if ( !path ) return ( NULL );
	
	if ( path[0] == '/' ) { // Full path name specified, no resolving necessary
		strcpy ( resolvedPath,  path );
	} else
	if ( path[0] == '~' ) { // Relative to User's home dir
		sprintf ( resolvedPath, "%s%s", NXHomeDirectory(), &path[1] );
	} else { //  Assume it is in LIBRARY_DIR
		sprintf ( resolvedPath, "%s/%s/%s", NXHomeDirectory(), LIBRARY_DIR, path );
	}
	
	return ( resolvedPath );
}



- (const char *)relativePathForFolder:(const char *)path
/*
	Changes a full pathname to a Folder into a relative path, as it is shown in the Preferences browser, and as it is stored in Defaults database.  Folders in ~/Library/Locus  (LIBRARY_DIR) are shown only as the folder name itself without a path.  Folders located elsewhere in the user's home directory are shown as ~/..../Folder.locus.  All other folders are shown with their full path name.
*/
{
	static char relativePath[MAXPATHLEN+1];
	const char *p, *homeDir = NXHomeDirectory();
	
	if ( !path ) return ( NULL );
	
	if ( !strncmp ( path , homeDir, strlen ( homeDir ) ) ) {
		// The folder is somewhere in the user's home dir
		p = path + strlen ( homeDir ) + 1; // +1 to get past the '/'
		if ( strstr ( p, LIBRARY_DIR ) == p ) {
			// The folder is in the LIBRARY_DIR
			p += strlen ( LIBRARY_DIR ) + 1; // +1 to get past last '/'
			strcpy ( relativePath, p );
		} else {
			// The folder is somewhere other than in LIBRARY_DIR
			sprintf ( relativePath, "~/%s", p );
		}
	} else {
		// Folder is not anywhere in user's home dir
		strcpy ( relativePath, path );
	}
	
	return ( relativePath );
}



- readFolderFromPath:(const char *)path
/*
	Read a Folder (and its Groups) located by path (which should be the full pathname).  Returns a new Folder object.  If this is called during setup -- i.e. when the startup folders are beoing opened -- then creates an empty folder object even if it can't read it.
*/
{
	id	folder;
	
	if ( isSetup && access ( path, F_OK | R_OK ) != 0 )
		return ( nil );

	folder = [Folder new];
	[folder setFilename:path];
	[folder readGroups];
	[folder readInfo];
	
	return ( folder );	
}



- writeFolder:aFolder
/*
	Save the Folder object by writing both the Folder info and the Groups. The Folder is saved to the path specified by [aFolder filename]
*/
{
	[aFolder writeInfo];
	[aFolder writeGroups];
	return ( self );
}


		
- openFolder:(const char *)path
/*
	Open the folder specified by path and add it to folderList
*/
{
	id	newFolder;
	
	if ( !(newFolder = [self readFolderFromPath:path]) )
		return ( nil ); // Unable to read the Folder at path
	
	if ( ![self addFolder:newFolder] ) {
		// Unable to add the Folder to our folderList
		[newFolder free];
		return ( nil );
	}
	
	[newFolder showSelf:self];
	[self makeKeyFolder:newFolder];
	if ( isAutoLaunch ) [self doAutoLaunch:self];
		// Perform autoLaunch only at launch-time
	
	return ( self );
}



- openFolderFromOpenPanel:sender
/*
	Choose Folders to be opened using an OpenPanel.  Each time this method is called, the directory is initially the the last directory selected (the first time it is set to the home dir).
*/
{
	id openPanel = [OpenPanel new];
	static char directory[MAXPATHLEN+1] = "";
	const char *const types[2] = { FOLDER_EXT, NULL };
	char path[MAXPATHLEN+1];
	const char *const *files;
	int i;
	
	if ( !strlen ( directory ) )
		sprintf ( directory, "%s/%s", NXHomeDirectory(), LIBRARY_DIR );
	
	[[openPanel setTitle:"Open Folder"] setPrompt:"Folder:"];
	[openPanel allowMultipleFiles:YES];

	if ( [openPanel runModalForDirectory:directory file:NULL types:types] ) {
		strcpy ( directory, [openPanel directory] ); // Save directory
		files = [openPanel filenames];
		i = 0;
		while ( files[i] ) {
			sprintf ( path, "%s/%s", directory, files[i] );
			[self openFolder:path];
			i++;
		}
	}
		
	return ( self );
}



- newFolder:sender
/*
	Create a new, un-titled Folder, add it to the Folder list
*/
{
	id newFolder = [[Folder new] obtainViewer:nil];
	if ( ![self addFolder:newFolder] ) [newFolder free];
		else [self makeKeyFolder:newFolder];
	return ( newFolder );
}



- saveKeyFolder:sender
{
	[self saveFolder:keyFolder];
	return ( self );
}



- saveKeyFolderAs:sender
{
	[self saveFolderAs:keyFolder];
	return ( self );
}



- saveAllFolders:sender
/*
	Go through the folderList and save all folders that have been changed.
*/
{
	int i, count;
	count = [folderList count];
	for ( i=0; i<count; i++ )
		if ( [[folderList objectAt:i] isChanged] )
			[self saveFolder:[folderList objectAt:i]];
	return ( self );
}



- revertKeyFolder:sender
/*
	Undo all changes to the keyFolder but replacing it with the last saved version.  This method reads the saved folder and replaces the changed one with it.
*/
{
	id revertedFolder;
	
	if ( ![keyFolder filename] ) {
		// Don't do anything if the Folder has never been saved
		NXBeep();
		return ( self );
	}
	
	if ( !(revertedFolder = [self readFolderFromPath:[keyFolder filename]]) ) {
		NXRunAlertPanel ( "Revert", "Unable to restore the Folder.",
			NULL, NULL, NULL );
		return ( self );
	}
	
	[self replaceFolder:keyFolder with:revertedFolder];
	return ( self );
}



// -------------------------------------------------------------------------
//   Folders
// -------------------------------------------------------------------------


- keyFolder
{
	return ( keyFolder );
}



- (BOOL)addFolder:aFolder
/*
	Add aFolder to folderList if it isn't already there, then make it the keyFolder.
*/
{
	if ( [self folderLoaded:[aFolder filename]] ) {
		// Activate the exisiting one of it is there
		[self makeKeyFolder:[self folderWithFilename:[aFolder filename]]];
		return ( NO );
	}
	
	[folderList addObject:aFolder];
	return ( YES );
}



- saveFolder:aFolder
{
	if ( [aFolder filename] ) [self writeFolder:aFolder];
		else [self saveFolderAs:aFolder];
	return ( self );
}



- saveFolderAs:aFolder
{
	id savePanel = [SavePanel new];
	static char directory[MAXPATHLEN+1] = "";
	const char *filename;
	
	if ( !strlen ( directory ) ) 
		sprintf ( directory, "%s/%s", NXHomeDirectory(), LIBRARY_DIR );
		
	[[savePanel setTitle:"Save Folder"] setPrompt:"Folder:"];
	[savePanel setRequiredFileType:FOLDER_EXT];
	
	if ( [savePanel runModalForDirectory:directory file:NULL] ) {
		strcpy ( directory, [savePanel directory] );
		filename = [savePanel filename];
		[aFolder setFilename:filename];
		[aFolder replaceAllGroups]; // Overwrite any existing groups
		[self writeFolder:aFolder];
	}
	
	[aFolder showSelf:self];
	return ( self );
}



- removeFolder:aFolder
/*
	Remove the folder from folderList.  This happens when the Folder gets closed.
*/
{
	int i;
	
	i = [folderList indexOf:aFolder];
	if ( i == NX_NOT_IN_LIST )
		return ( nil ); // The folder is not in the list -- shouldn't happen
		
	[folderList removeObjectAt:i];
	
	// Choose the Folder to be made the keyFolder after this one is removed.
	if ( i>0 ) i--;
	if ( keyFolder == aFolder )
		if ( [folderList count] )
			[self makeKeyFolder:[folderList objectAt:i]];
		else
			[self makeKeyFolder:nil];
		
	[self updateDisplay];
	[aFolder free];
	return ( self );
}



- replaceFolder:aFolder with:newFolder
/*
	Replace aFolder with newFolder, assigning aFolder's viewer/window to newFolder.  This is used when reverting a changed Folder.
*/
{
	BOOL wasKeyFolder;
	
	if ( keyFolder == aFolder ) wasKeyFolder = YES;
		else wasKeyFolder = NO;

	[folderList replaceObject:aFolder with:newFolder];
	[newFolder replaceAllGroups]; // Make sure existing groups are overwritten
	
	[newFolder obtainViewer:[aFolder releaseViewer]];
	[newFolder showSelf:self];

	if ( wasKeyFolder ) {
		keyFolder = newFolder;
		[self makeKeyFolder:keyFolder];
	}
		
	[aFolder free];
	return ( self );
}



- makeKeyFolder:aFolder
/*
	Makes aFolder the keyFolder, and it's viewer the keyWindow
*/
{
	// Only do this stuff when the keyFolder is actually changing
	if ( keyFolder != aFolder ) {
		[[keyFolder viewer] resignMainWindow];
			// This will send resignKeyFolder
		keyFolder = aFolder;
		[self updateMenu:self];
	}
	
	// This will make the Folder's viewer the main and key window,
	// and the folder will be sent -becomeKeyFolder
	[[[[keyFolder viewer] orderFront:self] becomeMainWindow] makeKeyWindow];
	
	if ( keyFolder ) [keyFolder updateInspector:self];
		else [inspector inspect:nil];
	return ( self );
}



- folderWithFilename:(const char *)path
/*
	Searches the folderList for a folder with filename = path, returns it if found, nil otherwise.
*/
{
	int i, count;
	
	if ( !path )
		return ( nil );
	
	count=[folderList count];
	for ( i=0; i<count; i++ )
		if ( !strcmp ( path, [[folderList objectAt:i] filename] ) )
			return ( [folderList objectAt:i] );
			
	return ( nil );
}



- folderWithViewerNum:(int)winNum
/*
	Finds the folder whose Viewer has the global window number winNum
*/
{
	int i, count;
	
	if ( winNum == appIconNum ) { // winNum is the AppIcon window
		return ( keyFolder );
	}
	
	count=[folderList count];
	for ( i=0 ; i<count; i++ )
		if ( [[folderList objectAt:i] viewerNum] == winNum )
			return ( [folderList objectAt:i] );

	return ( nil ); // Didn't find a match
}



- (BOOL)folderLoaded:(const char *)path
/*
	Returns YES if the Folder specified by path is already opened.
*/
{
	return ( [self folderWithFilename:path] ? YES : NO );
}



// -------------------------------------------------------------------------
//   Groups
// -------------------------------------------------------------------------


- newGroup:sender
{
	// Forward to keyFolder
	[keyFolder newGroup:sender] ;
	return ( self );
}



- deleteCurrentGroup:sender
{
	// Forward message to keyFolder
	[keyFolder deleteCurrentGroup:sender];
	return ( self );
}



- launchCurrentGroup:sender
{
	// Forward message to keyFolder
	[keyFolder launchCurrentGroup:sender];
	[self perform:@selector(updateDisplay:) with:self
			afterDelay:100 cancelPrevious:NO];
	return ( self );
}



- explicitSortItems:sender
{
	// Forward message to keyFolder
	[[keyFolder currentGroup] explicitSortItems:sender];
	[self updateDisplay];
	return ( self );
}



- cleanUpCurrentGroup:sender
{
	// Forward message to keyFolder
	[[keyFolder currentGroup] cleanUp:sender];
	[self updateDisplay];
	return ( self );
}



- dynamicUpdateCurrentGroup:sender
{
	// Forward message to keyFolder
	[[keyFolder currentGroup] updateDynamicItems:sender];
	[self updateDisplay];
	return ( self );
}



// -------------------------------------------------------------------------
//   Items
// -------------------------------------------------------------------------


- runAddPanel:sender
/*
	Use an OpenPanel to choose items to be added to keyFolder's currentGroup
*/
{
	id addPanel = [OpenPanel new];
	const char *const *files;
	char path[MAXPATHLEN+1];
	id group = [keyFolder currentGroup];
	int i;
	
	[[addPanel setTitle:"Add Items"] setPrompt:"Path:"];
	[addPanel allowMultipleFiles:YES];
	[addPanel chooseDirectories:NO];
	
	if ( [addPanel runModalForDirectory:[group defaultPath]
			file:NULL types:([group doesRestrictTypes]
				? [group allowedTypes] : NULL)] ) {
		
		// Add selected items
		i = 0;
		files = [addPanel filenames];
		while ( files[i] ) {
			sprintf ( path, "%s/%s", [addPanel directory], files[i] );
			[[keyFolder currentGroup] addItem:path];
			i++;
		}
		
		[[keyFolder currentGroup] sortItems];
	}
	
	[self updateDisplay];
	return ( self );
}	



- launchItems:sender
{
	// Forward message to keyFolder
	[[keyFolder currentGroup] launchSelectedItems:sender];
	return ( self );
}



- removeItems:sender
{
	int ret;
	
	// Prompt user to verify that the items should be removed
	if ( verifyActions ) {
		if ( [[keyFolder currentGroup] selectionCount] == 1)
			ret = NXRunAlertPanel ( "Verify", "Are you sure you want to remove the item %s from the group %s ?",
				"Remove", "Cancel", NULL,
				[[[keyFolder currentGroup] selection] filename],
				[[keyFolder currentGroup] groupName] );
		else
			ret = NXRunAlertPanel ( "Verify", "Are you sure you want to remove %i selected items from the group %s ?",
				"Remove", "Cancel", NULL,
				[[keyFolder currentGroup] selectionCount],
				[[keyFolder currentGroup] groupName] );
	} else
		ret = NX_ALERTDEFAULT;
	
	switch ( ret ) {
		case NX_ALERTDEFAULT: // Remove
			// Forward message to keyFolder
			[[keyFolder currentGroup] removeSelectedItems:self];
			[self updateDisplay];
			break;
		case NX_ALERTALTERNATE: // Cancel
			break;
	}
	
	return ( self );
}



// -------------------------------------------------------------------------
//   Text Editing methods
// -------------------------------------------------------------------------


- cut:sender
/*
	If keyWindow is the keyFolder's viewer, all selected items are copied to the itemPboard and removed.  Otherwise, this message is forwarded to the first responder.
*/
{
	id keyWindow = [NXApp keyWindow];
	
	// The keyWindow is the keyFolder's viewer
	if ( keyWindow == [keyFolder viewer] ) {
		[self copy:self]; // Copy the items to the pasteboard
		[self removeItems:self]; // Delete the items
	} else
		[[keyWindow firstResponder] tryToPerform:@selector(cut:) with:self];
	
	return ( self );
}



- copy:sender
/*
	If the keyWindow is the keyFolder's viewer, the selected items are copied to itemPboard.  Otherwise, this message is forwarded to the firstResponder.
*/
{
	id keyWindow = [NXApp keyWindow];
	const char *const pboardTypes[2] = { NXFilenamePboardType, NULL };
	NXStream *stream;
	char *body;
	int len, mlen;
	int i, count, k;
	id list;
	
	if ( keyWindow == [keyFolder viewer]  ) { // Write selected items to pboard
		list = [[keyFolder currentGroup] selectionList];
		stream = NXOpenMemory ( NULL, 0, NX_WRITEONLY );
		if ( !stream ) { // Couldn't open stream
			[self errMsg:"copy: Unable to copy Items to Pboard\n"];
			[list free];
			return ( nil );
		}
		
		// Write selected paths to stream as tab-separated list
		for ( i=0, count=[list count], k=0; i<count; i++ ) {
			if ( [[list objectAt:i] state] ) {
				if ( k>0 ) NXPutc ( stream, '\t' );
				NXPrintf ( stream, "%s", [[list objectAt:i] path] );
				k++;
			}
		}
		
		// Write list to itemPboard
		NXGetMemoryBuffer ( stream, &body, &len, &mlen );
		[itemPboard declareTypes:pboardTypes num:1 owner:nil];
		[itemPboard writeType:NXFilenamePboardType data:body length:len];

		NXCloseMemory ( stream, NX_FREEBUFFER );
		[list free];	// We are responsible to free this
	} else
		[[keyWindow firstResponder] tryToPerform:@selector(copy:) with:self];
	
	return ( self );
}



- paste:sender
/*
	If keyWindow is the keyFolder's viewer, all items on the itemPboard are added to the currentGroup.  Otherwise this message is forwarded to firstResponder
*/
{
	id keyWindow = [NXApp keyWindow];
	const char * const *types;
	char *list = NULL;
	int len, i=0;
	char *s, *tab, path[MAXPATHLEN+1];
	
	if ( keyWindow == [keyFolder viewer] ) {
		
		if ( ![keyFolder currentGroup] ) {
			NXBeep();
			return ( nil );
		}
			
		// Get list of items from itemPboard
		types = [itemPboard types];
		while ( types[i] ) {
			if ( !strcmp ( types[i], NXFilenamePboardType ) ) {
				if ( ![itemPboard readType:NXFilenamePboardType
						data:&list length:&len] ) {
					list = NULL;
					len = 0;
				}
				break;
			}
			i++;
		}
		
		// Add all the items in the list
		if ( list ) {
			s = list;
			do {
				// Read the next path, at s
				sscanf ( s, "%[^\t]", path );
				
				// Add the path
				[[keyFolder currentGroup] addItem:path];
				
				// Move s to next path
				tab = index ( s, '\t' ); // Find next tab
				if ( tab ) s = tab + 1;
					else s = NULL;
			} while ( s );
		
			// Free the memory used by list
			vm_deallocate ( task_self(), (vm_address_t)list, len );
				
		}
		
		[[keyFolder currentGroup] sortItems];
		[self updateDisplay];
	} else
		[[keyWindow firstResponder] tryToPerform:@selector(paste:) with:self];
	
	return ( self );
}



- selectAll:sender
/*
	If keyWindow is the keyFolder's viewer, select all items in the keyFolder.  Otherwise, forward this message to firstResponder.
*/
{
	id keyWindow = [NXApp keyWindow];
	
	if ( keyWindow == [keyFolder viewer] ) {
		if ( ![[keyFolder currentGroup] selectAll:sender] ) NXBeep();
	} else
		[[keyWindow firstResponder]
				tryToPerform:@selector(selectAll:) with:self];
	
	return ( self );
}



// -------------------------------------------------------------------------
//   Tools
// -------------------------------------------------------------------------


- showInspector:sender
{
	[inspector showInspector:self];
	return ( self );
}



- toggleHideOnDeactivate:sender
/*
	Toggle the state of the hideDeactivate global variable.  This state doesn't get saved unless writePrefs:(MainController) gets called at some point
*/
{
	//// FIX: make sure this gets done whenever hideDeactive changes
	//// (in Prefs, etc)
	
	int i, count;
	hideDeactive = hideDeactive ? NO : YES;
	count = [folderList count];
	for ( i=0; i<count; i++ ) {
		[[[folderList objectAt:i] viewer] setHideOnDeactivate:hideDeactive];
	}
	[self updateMenu:self];
	return ( self );
}



// -------------------------------------------------------------------------
//   DragDelegate Methods -- forwarded from the dragDest view/window
// -------------------------------------------------------------------------


static BOOL wasActive = NO;


- setDragDest:object
{
	dragDest = object;
	return ( self );
}



- dragDest
{
	return ( dragDest );
}



#define DRAG_OP	(NX_DragOperationAll)
- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
/*
	These messages are forwarded from the actual View that they occured in. (The view should have called [folderController setDragDest:self] in this method before fowarding it here.  The View will be either the AppIconView, one of the ActivatorBars, or a GroupBrowser.
*/
{
	if ( [keyFolder currentGroup] && dragDest == [keyFolder currentGroup] ) {
		// Don't allowing dragging into group we are dragging out of
		return ( NX_DragOperationNone );
	}
	
	wasActive = [NXApp isActive];
	[appIconView setIcon:"AcceptIcon"];	
	return ( [sender draggingSourceOperationMask] & DRAG_OP );
}



- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
	return ( DRAG_OP );
}



- draggingExited:(id <NXDraggingInfo>)sender
{
	[self setDragDest:nil];
	[appIconView setIcon:( wasActive ? "ActiveIcon" : "InactiveIcon")];
	return ( nil );
}



- (BOOL)prepareForDragOperation:(id <NXDraggingInfo>)sender
{
	return ( YES );
}


		
- (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
/*
	This method performs the actual operation for a dragging session.  In this case, this means it adds the files/paths that were dragged in as items in the keyFolder's current group, or the folder they were dragged into if not the key folder. 
*/
{
	Folder	*destFolder = nil;
	char *paths;
	int pLen;
	char *p, path[MAXPATHLEN+1];
	
	// Determine the folder being dragged into
	if ( dragDest == appIconView || dragDest == activator )
		destFolder = keyFolder;
	else if ( [dragDest isKindOf:[GroupBrowser class]] )
		destFolder = [[dragDest window] delegate];
	if ( ![destFolder currentGroup] ) {
		NXRunAlertPanel ( "Error",
			"No destination folder and/or group to drag into!",
			NULL, NULL, NULL );
		vm_deallocate ( task_self(), (vm_address_t)paths, pLen );
		return ( YES );
	}
	
	// Parse path list, add the files
	// the path list is in the NXFilenamePboardType format, which
	// means that it is a list of tab-separated paths.
	[[sender draggingPasteboard] readType:NXFilenamePboardType
		data:&paths length:&pLen];
	
	// Read all the paths in the list, add them to the currentGroup
	p = paths;
	while ( p ) {
		sscanf ( p, "%[^\t]", path );
		[[destFolder currentGroup] addItem:path];
		if ( (p = index ( p,'\t' )) ) p++;
	}
	
	// Free the path list -- our responsibility to do this
	vm_deallocate ( task_self(), (vm_address_t)paths, pLen );
	
	[[destFolder currentGroup] sortItems];

	// Send the showSelf: delayed to avoid loopback problems...
	[destFolder perform:@selector(showSelf:) with:self
		afterDelay:1 cancelPrevious:NO];
	
	return ( YES );
}


	
- concludeDragOperation:(id <NXDraggingInfo>)sender
{
	[self setDragDest:nil];
	[appIconView setIcon:( wasActive ? "ActiveIcon" : "InactiveIcon")];
	return ( self );
}


	
// -------------------------------------------------------------------------
//   Misc
// -------------------------------------------------------------------------


- updateMenu:sender
/*
	Update the menu items to reflect current status.  Mostly this means enabling or disabling them depending on if their action is available.  This method is called from updateDisplay, instead of being the Menu's update action, which if hopefully more efficient.
*/ 
{
	if ( !keyFolder ) {
		[menu_SaveFolder setEnabled:NO];
		[menu_SaveFolderAs setEnabled:NO];
		[menu_SaveAll setEnabled:NO];
		[menu_RevertFolder setEnabled:NO];
		[menu_NewGroup setEnabled:NO];
		[menu_DeleteGroup setEnabled:NO];
		[menu_LaunchGroup setEnabled:NO];
		[menu_SortGroup setEnabled:NO];
		[menu_CleanGroup setEnabled:NO];
		[menu_DynamicUpdate setEnabled:NO];
		[menu_AddItem setEnabled:NO];
		[menu_LaunchItem setEnabled:NO];
		[menu_DeleteItem setEnabled:NO];
	} else {
		[menu_SaveFolder setEnabled:YES];
		[menu_SaveFolderAs setEnabled:YES];
		[menu_SaveAll setEnabled:YES];
		[menu_RevertFolder setEnabled:YES];
		[menu_NewGroup setEnabled:YES];
		
		if ( [keyFolder currentGroup] ) {
			[menu_DeleteGroup setEnabled:YES];
			[menu_LaunchGroup setEnabled:YES];
			[menu_SortGroup setEnabled:YES];
			[menu_CleanGroup setEnabled:YES];
			[menu_DynamicUpdate setEnabled:YES];
			[menu_AddItem setEnabled:YES];
			[menu_LaunchItem setEnabled:YES];
			[menu_DeleteItem setEnabled:YES];
		} else {
			[menu_DeleteGroup setEnabled:NO];
			[menu_LaunchGroup setEnabled:NO];
			[menu_SortGroup setEnabled:NO];
			[menu_CleanGroup setEnabled:NO];
			[menu_DynamicUpdate setEnabled:NO];
			[menu_AddItem setEnabled:NO];
			[menu_LaunchItem setEnabled:NO];
			[menu_DeleteItem setEnabled:NO];
		}
	}
	
	[menu_HideOnDeactivate setTitle:
		(hideDeactive ? "Don't hide" : "Hide on deactivate")];

	return ( self );
}



- updateDisplay
/*
	Update the display of the keyFolder, and the inspector.
*/
{
	[keyFolder showSelf:self];
	[inspector update];
	return ( self );
}



- updateDisplay:sender
/*
	Just calls -updateDisplay.  This is basically used so updateDisplay can be used with a delayed perform ( perform:with:afterDelay:cancelPrevious: )
*/
{
	return ( [self updateDisplay] );
}



@end


These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.